Skip to content

Debugging EmbSec Labs

This guide covers debugging techniques for EmbSec labs using GDB, QEMU, and specialized tools for embedded security research.

Overview

The debugging infrastructure provides:

  • GDB Integration - Full symbolic debugging
  • QEMU Support - Hardware-accurate emulation
  • Exploit Development - Specialized helpers
  • Automated Scripts - Streamlined workflow

Quick Start

# Debug a lab with GDB
make debug-01-buffer-overflow

# Debug with exploit helpers
make exploit-01-buffer-overflow

# Manual GDB session
arm-none-eabi-gdb build/labs/01-buffer-overflow/01-buffer-overflow

GDB Debugging

Basic Setup

The debug script automatically:

  1. Starts QEMU with GDB server
  2. Loads symbols from ELF file
  3. Sets up useful commands
  4. Breaks at main
# Start debug session
./tools/scripts/debug_lab.sh 01-buffer-overflow

# With custom port
./tools/scripts/debug_lab.sh -p 1235 01-buffer-overflow

GDB Commands

Essential commands for embedded debugging:

# Connection
target remote :1234          # Connect to QEMU
monitor system_reset         # Reset target

# Execution
continue                     # Resume execution
step                        # Step into
next                        # Step over
finish                      # Step out

# Breakpoints
break main                  # Break at function
break *0x12345             # Break at address
watch *(int*)0x20000000    # Watchpoint

# Memory examination
x/32xw $sp                 # Stack contents
x/16i $pc                  # Disassembly
x/s 0x12345               # String at address

# Registers
info registers             # All registers
info reg $sp $pc          # Specific registers
set $r0 = 0x41414141      # Modify register

Custom Commands

The debug script provides helpers:

# Shortcuts
regs        # Show all registers
stack       # Display stack (32 words)
dis         # Disassemble around PC

# Reset helpers
reset       # Reset and continue
restart     # Reset and break at main

# Memory helpers
xxd 0x20000000           # Hex dump 256 bytes
xxd 0x20000000 0x100     # Hex dump specified size

# Search
find_string "password"    # Search memory for string
functions                # List all functions

Exploit Development Mode

Enabling Exploit Mode

# Use -x flag
make exploit-01-buffer-overflow

# Or directly
./tools/scripts/debug_lab.sh -x 01-buffer-overflow

Exploit Helpers

Additional commands for vulnerability research:

# Pattern generation
pattern_create 100          # Cyclic pattern
pattern_offset Aa0A        # Find offset

# Security features
checksec                    # Check protections

# ROP gadgets
ropgadget                   # Find useful gadgets

# Payload testing
send_payload "AAAA"         # Send to stdin

Pattern Generation Example

Finding buffer overflow offset:

# Generate pattern
(gdb) pattern_create 100
Aa0Aa1Aa2Aa3Aa4Aa5...

# Send to program
(gdb) continue
Enter password: Aa0Aa1Aa2Aa3Aa4Aa5...

# Program crashes, check PC
(gdb) info reg $pc
pc    0x41613341

# Find offset
(gdb) pattern_offset 3Aa4
Offset: 72

QEMU Debugging Features

Monitor Commands

Access QEMU monitor:

# System control
monitor system_reset        # Reset
monitor system_powerdown    # Shutdown
monitor stop               # Pause execution

# Memory info
monitor info registers     # CPU state
monitor info mem          # Memory mappings

# Devices
monitor info qtree        # Device tree
monitor info irq          # Interrupt state

Memory Regions

Typical memory map:

0x00000000 - 0x00040000  Flash (256KB)
0x20000000 - 0x20010000  SRAM (64KB)
0x40000000 - 0x44000000  Peripherals
0xE0000000 - 0xE0100000  System Control

Peripheral Access

Debug peripheral registers:

# UART0 status
x/x 0x4000C000

# GPIO port A
x/x 0x40004000

# System control
x/x 0xE000E000

Debugging Workflows

1. Crash Analysis

When program crashes:

# Check crash location
(gdb) info reg $pc $lr $sp

# Examine stack
(gdb) bt
(gdb) x/32xw $sp

# Check last executed instruction
(gdb) x/i $pc-4

# Examine function
(gdb) disas $pc-20,$pc+20

2. Vulnerability Discovery

Finding buffer overflows:

# Set breakpoint before strcpy
(gdb) break strcpy
(gdb) commands
> x/s $r1
> continue
> end

# Watch for stack corruption
(gdb) watch *(int*)($sp+0x40)

# Break on return
(gdb) break *($lr)

3. Exploit Development

Developing working exploit:

# Find target function
(gdb) print &grant_access
$1 = 0x8001234

# Check Thumb bit
(gdb) set $target = 0x8001234 | 1

# Test payload
(gdb) set {int}($sp+72) = $target
(gdb) continue

4. Format String Analysis

Debugging format strings:

# Break at printf
(gdb) break printf
(gdb) commands
> x/s $r0
> x/8xw $sp
> continue
> end

# Find offset
(gdb) x/s $r0
"AAAA %x %x %x %x"
(gdb) x/8xw $sp
0x41414141 ...  # Find position

Advanced Debugging

Hardware Breakpoints

ARM supports limited hardware breakpoints:

# Hardware breakpoint
(gdb) hbreak *0x8001234

# Hardware watchpoint
(gdb) watch -l *(int*)0x20001000

# Check available
(gdb) info breakpoints

Conditional Breakpoints

Break on specific conditions:

# Break when buffer contains pattern
(gdb) break vulnerable_function if *(int*)$r0 == 0x41414141

# Break on Nth call
(gdb) break malloc
(gdb) condition 1 $_hit_count == 5

Scripting GDB

Automate debugging tasks:

# Save to exploit.gdb
define exploit
    # Reset target
    monitor system_reset

    # Break at vulnerability
    break vulnerable_function
    continue

    # Send payload
    set $payload = "A" * 72
    set $target = 0x8001235
    call (int)write(0, $payload, 72)
    call (int)write(0, &$target, 4)

    # Continue execution
    continue
end

# Run script
(gdb) source exploit.gdb
(gdb) exploit

Python Extensions

Create powerful debugging scripts:

# gdb_helper.py
import gdb

class FindVulnCommand(gdb.Command):
    def __init__(self):
        super().__init__("find-vuln", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        # Search for unsafe functions
        unsafe = ["strcpy", "gets", "sprintf"]
        for func in unsafe:
            try:
                addr = gdb.parse_and_eval(f"&{func}")
                print(f"Found {func} at {addr}")
            except:
                pass

FindVulnCommand()

Load in GDB:

(gdb) source gdb_helper.py
(gdb) find-vuln

Debugging Tips

1. Symbol Loading

Ensure symbols are loaded:

(gdb) file build/labs/01-buffer-overflow/01-buffer-overflow
(gdb) info functions main

2. Stack Alignment

ARM requires aligned stack:

# Check alignment
(gdb) print $sp & 7
$1 = 0  # Good, aligned

# Fix if needed
(gdb) set $sp = $sp & ~7

3. Thumb Mode

Cortex-M uses Thumb exclusively:

# Ensure Thumb bit set
(gdb) set $pc = $pc | 1

# Check current mode
(gdb) show arm force-mode

4. Endianness

ARM is little-endian:

# Pack addresses correctly
(gdb) set {int}0x20001000 = 0x41424344
(gdb) x/4b 0x20001000
0x44 0x43 0x42 0x41  # "DCBA"

Common Issues

GDB Won't Connect

(gdb) target remote :1234
Remote connection closed

Solutions:

  • Check QEMU is running
  • Verify port isn't in use
  • Try different port with -p

No Symbols

(gdb) break main
Function "main" not defined.

Solutions:

  • Load symbols: file <binary>
  • Check binary has debug info
  • Rebuild with -g flag

Wrong Architecture

warning: Selected architecture arm is not compatible

Solutions:

(gdb) set architecture arm
(gdb) set arm force-mode thumb

Can't Set Breakpoints

Cannot insert breakpoint 1.
Cannot access memory at address 0x8001234

Solutions:

  • Check address is valid
  • Use hardware breakpoints
  • Verify memory is mapped

Integration with Tools

IDA Pro Integration

Export symbols for IDA:

# Generate map file
arm-none-eabi-objdump -t binary > binary.map

# Generate listings
arm-none-eabi-objdump -d binary > binary.lst

Radare2 Integration

Debug with radare2:

# Start r2 in debug mode
r2 -d gdb://localhost:1234

# Load binary info
e asm.arch=arm
e asm.bits=32

Binary Ninja Integration

Remote debugging setup:

  1. Start QEMU with GDB
  2. Connect Binary Ninja debugger
  3. Set architecture to ARMv7
  4. Load at base 0x00000000

Debugging Resources

Memory Map Reference

Region Start Size Description
Flash 0x00000000 256KB Code, RO data
SRAM 0x20000000 64KB Stack, heap, data
Peripherals 0x40000000 1MB Memory-mapped I/O
System 0xE0000000 1MB NVIC, SysTick, etc

Register Reference

Register Purpose Notes
R0-R3 Arguments/Return Scratch
R4-R11 Variables Preserved
R12 Scratch IP register
R13 Stack Pointer SP
R14 Link Register Return address
R15 Program Counter PC

Useful Memory Locations

Common locations to examine:

  • Stack: $sp to $sp+0x100
  • Return address: *(int*)($sp+X)
  • Global data: 0x20000000+
  • Heap (if used): After .bss

Next Steps