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¶
-
Flag Generation:
-
User Interaction:
-
Debug Information:
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¶
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¶
- Write and test exploit script
- Document in writeup.md
- 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¶
- Consistent Formatting: Use clang-format with project settings
- Clear Variable Names: Make vulnerable code obvious
- Comments: Explain the educational purpose
- Error Handling: Realistic but not obfuscated
Security Considerations¶
- Isolated Vulnerabilities: One main vulnerability per lab
- Realistic Scenarios: Based on real-world bugs
- Progressive Difficulty: Build on previous concepts
- Clear Goals: Obvious success criteria (flag)
Educational Value¶
- Historical Context: Connect to real vulnerabilities
- Clear Explanations: Document the "why" not just "how"
- Multiple Approaches: Show different exploitation techniques
- Defensive Measures: Always discuss mitigations
Testing¶
- Positive Tests: Verify exploit works
- Negative Tests: Normal operation isn't broken
- Edge Cases: Boundary conditions
- Cleanup: Reset state between tests
Common Patterns¶
Menu Systems¶
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¶
- Build Failures
- Check CMakeLists.txt syntax
- Verify all source files exist
-
Ensure embsec library is linked
-
Serial Communication
- Verify /dev/ttyACM0 permissions
- Check baud rate (115200)
-
Ensure only one connection active
-
Exploit Failures
- Verify addresses with debugger
- Check for THUMB bit on ARM
-
Ensure proper byte ordering
-
Test Failures
- Reset hardware between tests
- Check timing/delays
- Verify serial buffer flushing
Debugging Tips¶
-
Use GDB:
-
Add Debug Output:
-
Memory Dumps:
Contributing¶
When contributing new labs:
- Follow the structure exactly
- Include comprehensive documentation
- Provide working exploits
- Add automated tests
- Test on actual hardware
- Submit PR with all components
Resources¶
Templates¶
- Lab Template - Starting point for new labs
- Instructor Guide - Teaching notes
Documentation¶
Community¶
- GitHub Issues for bug reports
- Discord for discussions
- Wiki for additional resources
Well-structured labs make learning enjoyable and effective!