Skip to content

Lab Structure Guide

This document explains the standardized structure used across all EMBSEC labs, helping you understand how to navigate, build, and extend the lab exercises.

Directory Layout

Each lab follows a consistent directory structure:

labs/XX-vulnerability-name/
├── CMakeLists.txt      # Build configuration
├── README.md           # Lab-specific instructions
├── metadata.yml        # Lab metadata and configuration
├── src/                # Vulnerable source code
│   └── main.c          # Main lab implementation
├── solution/           # Reference solutions
│   ├── exploit.py      # Working exploit script
│   └── writeup.md      # Detailed solution walkthrough
├── tests/              # Automated testing
│   └── test_lab.py     # Pytest test cases
└── .gitignore          # Build artifacts to ignore

Component Details

CMakeLists.txt

The build configuration for each lab:

# Lab name and target configuration
embsec_lab(
    TARGET 01-buffer-overflow
    SOURCES src/main.c
    INCLUDES include
    LIBRARIES embsec
)

# Optional: Additional compile flags
target_compile_definitions(01-buffer-overflow PRIVATE
    BUFFER_SIZE=64
    DEBUG_MODE=1
)

metadata.yml

Lab metadata used by the build system and documentation:

name: Buffer Overflow
difficulty: easy
category: memory-corruption
author: Your Name
points: 100
tags:
  - stack
  - buffer-overflow
  - arm
  - embedded
description: |
  Classic stack buffer overflow vulnerability demonstrating
  the Morris Worm fingerd exploit in an embedded context.
learning_objectives:
  - Understand stack layout on ARM Cortex-M3
  - Control program execution flow
  - Develop working exploits
prerequisites:
  - Basic C programming
  - Understanding of memory layout
  - Python scripting

Source Code Structure

src/main.c

Each lab's main source file typically includes:

#include <embsec/embsec.h>
#include <embsec/uart.h>
#include <embsec/flag.h>

#define LAB_SALT "unique_salt_for_flag_generation"

// Vulnerable function demonstrating the bug
void vulnerable_function(void) {
    // Intentionally vulnerable code
}

// Main application loop
int main(void) {
    embsec_init();

    // Lab-specific implementation

    return 0;
}

Common Patterns

  1. Flag Generation:

    embsec_flag_t flag;
    embsec_generate_flag_str(&flag, LAB_SALT);
    embsec_printf("Flag: %s\n", flag.value);
    

  2. User Interaction:

    embsec_printf("Enter input: ");
    char buffer[64];
    embsec_gets_n(buffer, sizeof(buffer));
    

  3. Debug Information:

    #ifdef DEBUG_MODE
    embsec_printf("Debug: Address = 0x%08X\n", (uint32_t)&variable);
    #endif
    

Solution Components

solution/exploit.py

A working exploit demonstrating the vulnerability:

#!/usr/bin/env python3
"""
Lab XX: Vulnerability Name
Author: Your Name
Date: YYYY-MM-DD
"""

import serial
import struct
import time
from pwn import *

# Configuration
SERIAL_PORT = '/dev/ttyACM0'
BAUD_RATE = 115200

def exploit():
    # Connect to target
    io = serialtube(SERIAL_PORT, BAUD_RATE)

    # Implement exploit logic
    payload = b"A" * 64
    payload += struct.pack('<I', 0xdeadbeef)

    # Send payload
    io.sendline(payload)

    # Get flag
    io.interactive()

if __name__ == "__main__":
    exploit()

solution/writeup.md

Detailed explanation following this template:

# Lab Name - Solution Writeup

**Author**: Your Name
**Difficulty**: Easy/Medium/Hard
**Category**: Category

## Executive Summary

Brief description of the vulnerability and exploitation approach.

## Vulnerability Analysis

### Code Review

- Identify vulnerable code
- Explain why it's vulnerable
- Show relevant code snippets

### Exploitation Strategy

- Step-by-step exploitation process
- Key insights and techniques used

## Exploit Development

### Information Gathering

- What information is needed
- How to obtain it

### Payload Construction

- Detailed payload breakdown
- Why each component is necessary

### Execution

- How to run the exploit
- Expected output

## Key Takeaways

- Important lessons learned
- Common pitfalls to avoid
- Real-world applications

## References

- Relevant papers and resources

Testing Framework

tests/test_lab.py

Automated tests using pytest:

import pytest
import subprocess
import serial
import time
from pathlib import Path

class TestLab:
    @pytest.fixture
    def serial_connection(self):
        """Setup serial connection to target."""
        ser = serial.Serial('/dev/ttyACM0', 115200, timeout=5)
        yield ser
        ser.close()

    def test_normal_operation(self, serial_connection):
        """Test normal (non-exploit) operation."""
        # Send normal input
        serial_connection.write(b'1\n')
        time.sleep(0.1)
        serial_connection.write(b'normalinput\n')

        # Check response
        response = serial_connection.read(1000)
        assert b'Access Denied' in response

    def test_exploit(self, serial_connection):
        """Test that exploit works correctly."""
        # Run exploit
        result = subprocess.run(
            ['python3', 'solution/exploit.py'],
            capture_output=True,
            timeout=10
        )

        # Verify flag obtained
        assert b'embsec{' in result.stdout
        assert result.returncode == 0

    def test_crash_handling(self, serial_connection):
        """Test system handles crashes gracefully."""
        # Send malformed input
        serial_connection.write(b'1\n')
        serial_connection.write(b'A' * 1000 + b'\n')

        # System should reset or handle gracefully
        time.sleep(2)
        serial_connection.write(b'3\n')  # Reset command
        response = serial_connection.read(1000)
        assert b'System Reset' in response or b'menu' in response

Build System Integration

Lab Registration

Labs are automatically discovered by the build system if they follow the structure. The CMakeLists.txt in the labs directory scans for subdirectories:

# labs/CMakeLists.txt
file(GLOB LAB_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/*/CMakeLists.txt")
foreach(lab_cmake ${LAB_DIRS})
    get_filename_component(lab_dir ${lab_cmake} DIRECTORY)
    add_subdirectory(${lab_dir})
endforeach()

Build Commands

Common build operations:

# Build specific lab
make 01-buffer-overflow

# Flash to hardware
make flash-01-buffer-overflow

# Run tests
make test-01-buffer-overflow

# Clean build artifacts
make clean-01-buffer-overflow

Binary Output

Built binaries are placed in:

build/labs/XX-vulnerability-name/
├── XX-vulnerability-name.elf  # ELF file with debug symbols
├── XX-vulnerability-name.bin  # Raw binary for flashing
├── XX-vulnerability-name.hex  # Intel HEX format
└── XX-vulnerability-name.map  # Linker map file

Creating New Labs

Step 1: Create Directory Structure

# Create new lab directory
mkdir labs/03-integer-overflow
cd labs/03-integer-overflow

# Create subdirectories
mkdir src solution tests

# Create files
touch CMakeLists.txt README.md metadata.yml
touch src/main.c
touch solution/exploit.py solution/writeup.md
touch tests/test_lab.py

Step 2: Configure CMakeLists.txt

embsec_lab(
    TARGET 03-integer-overflow
    SOURCES src/main.c
    LIBRARIES embsec
)

Step 3: Write metadata.yml

name: Integer Overflow
difficulty: medium
category: arithmetic
author: Your Name
points: 150
tags:
  - integer-overflow
  - arithmetic
  - embedded
description: |
  Demonstrates integer overflow vulnerabilities in embedded systems
  with limited integer sizes and lack of overflow detection.

Step 4: Implement Vulnerability

Create the vulnerable code in src/main.c following the patterns above.

Step 5: Develop Solution

  1. Write and test exploit script
  2. Document in writeup.md
  3. Create automated tests

Step 6: Test Everything

# Build
make 03-integer-overflow

# Test locally
qemu-system-arm -M lm3s6965evb -kernel build/labs/03-integer-overflow/03-integer-overflow.elf

# Run automated tests
pytest labs/03-integer-overflow/tests/

Best Practices

Code Style

  1. Consistent Formatting: Use clang-format with project settings
  2. Clear Variable Names: Make vulnerable code obvious
  3. Comments: Explain the educational purpose
  4. Error Handling: Realistic but not obfuscated

Security Considerations

  1. Isolated Vulnerabilities: One main vulnerability per lab
  2. Realistic Scenarios: Based on real-world bugs
  3. Progressive Difficulty: Build on previous concepts
  4. Clear Goals: Obvious success criteria (flag)

Educational Value

  1. Historical Context: Connect to real vulnerabilities
  2. Clear Explanations: Document the "why" not just "how"
  3. Multiple Approaches: Show different exploitation techniques
  4. Defensive Measures: Always discuss mitigations

Testing

  1. Positive Tests: Verify exploit works
  2. Negative Tests: Normal operation isn't broken
  3. Edge Cases: Boundary conditions
  4. Cleanup: Reset state between tests

Common Patterns

Standard menu implementation:

void show_menu(void) {
    embsec_printf("\n=== Lab Menu ===\n");
    embsec_printf("1. Vulnerable Option\n");
    embsec_printf("2. Information\n");
    embsec_printf("3. Reset\n");
    embsec_printf("Choice: ");
}

void handle_menu(void) {
    char choice = embsec_getchar();
    embsec_printf("%c\n", choice);

    switch(choice) {
        case '1': vulnerable_function(); break;
        case '2': show_info(); break;
        case '3': embsec_system_reset(); break;
        default: embsec_printf("Invalid option\n");
    }
}

Debug Information

Conditional debug output:

#ifdef DEBUG_BUILD
void show_debug_info(void) {
    embsec_printf("\n[DEBUG] Memory Layout:\n");
    embsec_printf("  Stack: 0x%08X\n", (uint32_t)&local_var);
    embsec_printf("  Target: 0x%08X\n", (uint32_t)target_function);
}
#endif

Flag Generation

Consistent flag generation:

void grant_flag(void) {
    embsec_flag_t flag;

    embsec_printf("\n[SUCCESS] Vulnerability exploited!\n");
    embsec_generate_flag_str(&flag, LAB_SALT);
    embsec_printf("Flag: %s\n", flag.value);

    // Optional: Log success
    embsec_log("Lab completed: %s", flag.value);
}

Troubleshooting

Common Issues

  1. Build Failures
  2. Check CMakeLists.txt syntax
  3. Verify all source files exist
  4. Ensure embsec library is linked

  5. Serial Communication

  6. Verify /dev/ttyACM0 permissions
  7. Check baud rate (115200)
  8. Ensure only one connection active

  9. Exploit Failures

  10. Verify addresses with debugger
  11. Check for THUMB bit on ARM
  12. Ensure proper byte ordering

  13. Test Failures

  14. Reset hardware between tests
  15. Check timing/delays
  16. Verify serial buffer flushing

Debugging Tips

  1. Use GDB:

    arm-none-eabi-gdb lab.elf
    (gdb) target remote :3333
    (gdb) monitor reset halt
    

  2. Add Debug Output:

    #define DEBUG_PRINT(fmt, ...) \
        embsec_printf("[DBG] " fmt "\n", ##__VA_ARGS__)
    

  3. Memory Dumps:

    embsec_hexdump(buffer, sizeof(buffer));
    

Contributing

When contributing new labs:

  1. Follow the structure exactly
  2. Include comprehensive documentation
  3. Provide working exploits
  4. Add automated tests
  5. Test on actual hardware
  6. Submit PR with all components

Resources

Templates

Documentation

Community

  • GitHub Issues for bug reports
  • Discord for discussions
  • Wiki for additional resources

Well-structured labs make learning enjoyable and effective!