Best Practices¶
This guide outlines technical best practices for developing high-quality embedded security labs that are educational, reliable, and maintainable.
Code Quality Standards¶
Coding Style¶
Consistent Formatting¶
// Use consistent style throughout the lab
// This example follows the EmbSec style guide
#include <embsec/embsec.h>
#include <stdint.h>
#include <stdbool.h>
// Constants in UPPER_CASE
#define BUFFER_SIZE 64
#define MAX_ATTEMPTS 3
// Type definitions
typedef struct {
uint32_t id;
char name[32];
bool is_admin;
} user_t;
// Function prototypes with descriptive names
static void process_user_input(void);
static bool validate_credentials(const char *username, const char *password);
static void vulnerable_function(char *input);
// Global state clearly marked
static volatile uint32_t g_auth_level = 0;
static user_t g_current_user = {0};
// Functions with clear purpose
static void process_user_input(void) {
char buffer[BUFFER_SIZE];
uart_puts("Enter command: ");
uart_gets(buffer);
// Clear control flow
if (strcmp(buffer, "login") == 0) {
handle_login();
} else if (strcmp(buffer, "admin") == 0) {
handle_admin();
} else {
uart_puts("Unknown command\n");
}
}
Meaningful Names¶
// BAD: Cryptic names
void f(char *b) {
char l[32];
strcpy(l, b); // What does this do?
}
// GOOD: Self-documenting
void process_username(char *username_input) {
char local_username[MAX_USERNAME_LENGTH];
strcpy(local_username, username_input); // Clear intent
}
Comment Strategy¶
// DO: Explain the "why", not the "what"
// Delay to make timing attacks measurable in QEMU
delay_ms(10);
// Allocate on heap to ensure consistent addresses across runs
user_t *user = malloc(sizeof(user_t));
// DON'T: State the obvious
i++; // Increment i
Memory Safety¶
Buffer Handling¶
// Always provide safe alternatives alongside vulnerable functions
void safe_string_copy(char *dest, const char *src, size_t dest_size) {
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
}
// Vulnerable function for teaching
void vulnerable_string_copy(char *dest, const char *src) {
strcpy(dest, src); // No bounds checking - intentional!
}
// Clear which function is vulnerable
void process_input(void) {
char buffer[32];
#ifdef SECURE_MODE
safe_string_copy(buffer, user_input, sizeof(buffer));
#else
vulnerable_string_copy(buffer, user_input); // VULNERABLE
#endif
}
Resource Management¶
// Track allocations to prevent leaks
typedef struct allocation {
void *ptr;
size_t size;
struct allocation *next;
} allocation_t;
static allocation_t *g_allocations = NULL;
void *tracked_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr) {
allocation_t *track = malloc(sizeof(allocation_t));
track->ptr = ptr;
track->size = size;
track->next = g_allocations;
g_allocations = track;
}
return ptr;
}
void cleanup_all_allocations(void) {
while (g_allocations) {
allocation_t *next = g_allocations->next;
free(g_allocations->ptr);
free(g_allocations);
g_allocations = next;
}
}
Error Handling¶
Graceful Failures¶
// Provide useful error messages
bool load_config(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) {
uart_printf("Error: Cannot open config file '%s'\n", filename);
uart_puts("Using default configuration\n");
return load_default_config();
}
// Process file...
fclose(fp);
return true;
}
// Recover from student errors
void student_function_wrapper(void) {
// Save state before student code
uint32_t saved_sp = get_sp();
__try {
student_provided_exploit();
} __except {
uart_puts("\n[ERROR] Exploit crashed - recovering\n");
set_sp(saved_sp);
show_hint_about_crash();
}
}
Defensive Programming¶
// Validate all inputs, even in vulnerable functions
void vulnerable_but_stable(void) {
char buffer[32];
char *input = get_user_input();
// Sanity check to prevent total crash
if (!input || strlen(input) > 1024) {
uart_puts("Input too large - truncating\n");
input[1024] = '\0';
}
// Still vulnerable but won't crash QEMU
strcpy(buffer, input);
}
Security Considerations¶
Flag Generation Security¶
Proper Flag Generation¶
// Use the SDK's secure flag generation
#include <embsec/flag.h>
// DON'T hardcode flags
// BAD:
const char *flag = "embsec{1234567890abcdef}";
// GOOD: Generate based on salt
void reveal_flag(void) {
generate_flag(); // Uses LAB_SALT from compile time
uart_printf("Flag: %s\n", embsec_flag);
}
// Ensure flag is only accessible through exploit
void init_flag_protection(void) {
// Clear flag memory
memset(embsec_flag, 0, sizeof(embsec_flag));
// Only generate when needed
g_flag_generated = false;
}
Salt Management¶
# CMakeLists.txt - Keep salts unique and secret
embsec_add_lab(
NAME my-lab
SALT "${MY_LAB_SECRET_SALT_2024}" # Use environment variable
)
Preventing Unintended Solutions¶
Validate Exploitation Path¶
// Track how the student reached the flag
typedef enum {
PATH_NONE = 0,
PATH_BUFFER_OVERFLOW = 1,
PATH_FORMAT_STRING = 2,
PATH_LOGIC_BUG = 4
} exploit_path_t;
static exploit_path_t g_exploit_path = PATH_NONE;
void win_function(void) {
// Log how we got here
if (g_exploit_path == PATH_NONE) {
uart_puts("Warning: Reached win function through unintended path\n");
}
generate_flag();
uart_printf("Flag: %s\n", embsec_flag);
// Log for analysis
uart_printf("Exploit path: 0x%x\n", g_exploit_path);
}
// Mark exploitation points
void vulnerable_function(void) {
char buffer[32];
g_exploit_path |= PATH_BUFFER_OVERFLOW;
uart_gets(buffer); // Overflow here
}
Input Validation¶
// Prevent simple bypasses
void check_input_validity(const char *input) {
// Disallow shell metacharacters if not needed
const char *forbidden = ";&|`$";
for (int i = 0; forbidden[i]; i++) {
if (strchr(input, forbidden[i])) {
uart_puts("Invalid character in input\n");
return;
}
}
process_input(input);
}
Documentation Standards¶
Code Documentation¶
Function Headers¶
/**
* @brief Process user authentication request
*
* This function handles the authentication flow, including
* password verification and privilege escalation. Contains
* an intentional buffer overflow vulnerability in the username
* field for educational purposes.
*
* @param username User-provided username (UNSAFE - no bounds check)
* @param password User-provided password
* @return true if authentication successful, false otherwise
*
* @note VULNERABLE: Username buffer can be overflowed
* @note Target address for exploitation: &grant_admin_access
*/
bool authenticate_user(const char *username, const char *password) {
char username_buffer[32]; // Vulnerable buffer
// Intentional vulnerability for teaching
strcpy(username_buffer, username);
return check_password(username_buffer, password);
}
Inline Documentation¶
void complex_vulnerability(void) {
// Step 1: Leak the stack canary value
uint32_t canary = *(uint32_t *)get_canary_address();
// Step 2: Build payload preserving canary
struct {
char overflow[64];
uint32_t saved_canary;
uint32_t saved_fp;
uint32_t saved_lr;
} payload;
// Step 3: Overflow with canary preservation
memset(payload.overflow, 'A', sizeof(payload.overflow));
payload.saved_canary = canary; // Preserve canary
payload.saved_fp = 0x41414141; // Dummy frame pointer
payload.saved_lr = (uint32_t)win_function | 1; // Target (Thumb)
// Trigger vulnerability
vulnerable_memcpy(&payload, sizeof(payload));
}
Student-Facing Documentation¶
Clear README Structure¶
# Lab X: Vulnerability Type
## Overview
Brief description of what students will learn.
## Learning Objectives
By completing this lab, you will:
- Understand [specific vulnerability]
- Learn how to [exploitation technique]
- Practice [debugging skill]
## Background
### Technical Concept
Explanation of the vulnerability type...
### ARM-Specific Details
Important ARM architecture notes...
## Setup Instructions
1. Build the lab
2. Run in QEMU
3. Connect debugger (optional)
## The Challenge
Your task is to...
## Hints (Progressive Disclosure)
<details>
<summary>Hint 1 (No points deducted)</summary>
General guidance about approach
</details>
<details>
<summary>Hint 2 (-25 points)</summary>
More specific direction
</details>
<details>
<summary>Hint 3 (-50 points)</summary>
Key insight needed
</details>
## Tools and Resources
- ARM GDB
- Python for exploit development
- Hex dump utilities
## Submission
Submit your exploit script that retrieves the flag.
Instructor Documentation¶
INSTRUCTOR.md Template¶
# Lab X: Quick Reference
## Vulnerability Details
- **Type**: Buffer overflow in `process_input()` line 45
- **Buffer size**: 32 bytes
- **Overflow target**: Return address at offset 64
- **Target function**: `win_function` at 0x00001234
## Key Addresses (Fixed)
- Win function: 0x00001234 (remember Thumb bit: 0x00001235)
- Debug function: 0x00001300
- String table: 0x00002000
## Common Student Mistakes
1. **Forgetting Thumb bit**: ARM functions need LSB=1
2. **Wrong endianness**: Use little-endian packing
3. **Missing newline**: Input processing expects '\n'
## Testing Commands
```bash
# Quick exploit test
echo -e 'AAAA....\x35\x12\x00\x00' | ./run_lab.sh
# Full exploit
python3 solution/exploit.py | ./run_lab.sh
# Debug session
./debug_lab.sh -x lab-name
Debugging Strategies¶
Built-in Debugging Features¶
Debug Menu Implementation¶
void show_debug_menu(void) {
uart_puts("\n=== Debug Menu ===\n");
uart_puts("1. Show memory map\n");
uart_puts("2. Dump stack\n");
uart_puts("3. Show registers\n");
uart_puts("4. Toggle verbose mode\n");
uart_puts("5. Return to main\n");
}
void debug_command_handler(char cmd) {
switch (cmd) {
case '1':
show_memory_map();
break;
case '2':
dump_stack(32); // 32 words
break;
case '3':
dump_registers();
break;
case '4':
g_verbose_mode = !g_verbose_mode;
uart_printf("Verbose mode: %s\n",
g_verbose_mode ? "ON" : "OFF");
break;
}
}
Memory Inspection Helpers¶
void examine_memory(uint32_t addr, size_t words) {
uart_printf("\nMemory dump from 0x%08x:\n", addr);
for (size_t i = 0; i < words; i += 4) {
uart_printf("%08x: ", addr + i*4);
for (size_t j = 0; j < 4 && i+j < words; j++) {
uint32_t *ptr = (uint32_t *)(addr + (i+j)*4);
uart_printf("%08x ", *ptr);
}
uart_puts(" ");
// ASCII representation
for (size_t j = 0; j < 16 && i*4+j < words*4; j++) {
uint8_t byte = *(uint8_t *)(addr + i*4 + j);
uart_putc(isprint(byte) ? byte : '.');
}
uart_puts("\n");
}
}
Logging and Tracing¶
Execution Tracing¶
#ifdef ENABLE_TRACE
#define TRACE(fmt, ...) \
uart_printf("[TRACE] %s:%d: " fmt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define TRACE(fmt, ...) ((void)0)
#endif
void vulnerable_function(void) {
char buffer[32];
TRACE("Entering with SP=0x%08x", get_sp());
TRACE("Buffer at 0x%08x", (uint32_t)buffer);
uart_gets(buffer);
TRACE("Input length: %d", strlen(buffer));
TRACE("Returning to 0x%08x", get_return_address());
}
State Logging¶
typedef struct {
uint32_t timestamp;
const char *event;
uint32_t data;
} log_entry_t;
static log_entry_t g_event_log[MAX_LOG_ENTRIES];
static int g_log_index = 0;
void log_event(const char *event, uint32_t data) {
g_event_log[g_log_index].timestamp = get_system_ticks();
g_event_log[g_log_index].event = event;
g_event_log[g_log_index].data = data;
g_log_index = (g_log_index + 1) % MAX_LOG_ENTRIES;
}
void dump_event_log(void) {
uart_puts("\n=== Event Log ===\n");
for (int i = 0; i < MAX_LOG_ENTRIES; i++) {
int idx = (g_log_index + i) % MAX_LOG_ENTRIES;
if (g_event_log[idx].event) {
uart_printf("[%08d] %s: 0x%08x\n",
g_event_log[idx].timestamp,
g_event_log[idx].event,
g_event_log[idx].data);
}
}
}
Performance Optimization¶
Memory Efficiency¶
Minimize RAM Usage¶
// Use const for read-only data (stored in flash)
const char * const menu_options[] = {
"1. Login",
"2. Debug Info",
"3. Exit"
};
// Pack structures
typedef struct __attribute__((packed)) {
uint8_t type;
uint8_t flags;
uint16_t length;
uint32_t data;
} message_t;
// Use bitfields for flags
typedef struct {
uint32_t is_authenticated : 1;
uint32_t is_admin : 1;
uint32_t debug_enabled : 1;
uint32_t reserved : 29;
} status_flags_t;
Optimize String Usage¶
// Reuse format strings
static const char fmt_hex[] = "0x%08x";
static const char fmt_addr[] = "Address: 0x%08x\n";
// Use string tables
const char * const error_messages[] = {
[ERR_NONE] = "Success",
[ERR_AUTH] = "Authentication failed",
[ERR_OVERFLOW] = "Buffer overflow detected",
[ERR_INVALID] = "Invalid input"
};
Code Size Optimization¶
Combine Similar Functions¶
// Instead of multiple similar functions
void print_buffer_address(void) {
uart_printf("Buffer: 0x%08x\n", (uint32_t)buffer);
}
void print_stack_address(void) {
uart_printf("Stack: 0x%08x\n", get_sp());
}
// Use a generic function
void print_labeled_address(const char *label, uint32_t addr) {
uart_printf("%s: 0x%08x\n", label, addr);
}
Testing Checklist¶
Pre-Release Validation¶
Functionality Tests¶
- Lab compiles without warnings
- Runs in QEMU without crashes
- Normal path works as expected
- Vulnerability is exploitable
- Flag generation works correctly
- All menu options function
Security Tests¶
- Flag not accessible without exploit
- No unintended solutions exist
- Input validation prevents crashes
- Memory bounds are respected
- Salt is unique across labs
Educational Tests¶
- Learning objectives are met
- Difficulty is appropriate
- Hints provide good guidance
- Debug features work correctly
- Documentation is clear
Technical Tests¶
- Works on Linux/Mac/Windows
- Automated tests pass
- Memory usage within limits
- Performance is acceptable
- No hardcoded addresses
Continuous Improvement¶
Collect Metrics¶
// Track how students solve the lab
typedef struct {
uint32_t attempts;
uint32_t hints_used;
uint32_t time_to_solve;
exploit_path_t path_taken;
} student_metrics_t;
void log_solution_metrics(void) {
uart_printf("\n=== Solution Metrics ===\n");
uart_printf("Attempts: %d\n", g_metrics.attempts);
uart_printf("Hints used: %d\n", g_metrics.hints_used);
uart_printf("Time: %d seconds\n", g_metrics.time_to_solve);
uart_printf("Path: %s\n", get_path_name(g_metrics.path_taken));
}
Version Control¶
// Track lab version in binary
const char * const LAB_VERSION = "1.2.3";
const char * const LAB_BUILD_DATE = __DATE__ " " __TIME__;
void show_version_info(void) {
uart_printf("Lab Version: %s\n", LAB_VERSION);
uart_printf("Built: %s\n", LAB_BUILD_DATE);
uart_printf("SDK Version: %s\n", EMBSEC_SDK_VERSION);
}
Summary¶
Following these best practices ensures your labs are:
- Educational: Clear learning path with appropriate difficulty
- Reliable: Consistent behavior across platforms
- Maintainable: Clean code that's easy to update
- Secure: Proper flag generation and protection
- Debuggable: Built-in tools for troubleshooting
Remember: The goal is student learning, not frustration. Make vulnerabilities discoverable, provide good debugging tools, and ensure exploits work reliably.