Files
Embedded-Hacking/WEEK01/WEEK01-04.md
2026-03-19 15:01:07 -04:00

11 KiB

Embedded Systems Reverse Engineering

Repository

Week 1

Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts

Non-Credit Practice Exercise 4: Connect GDB & Basic Exploration

Objective

Set up GDB (GNU Debugger) to dynamically analyze the "hello, world" program running on your Pico 2, verifying that your debugging setup works correctly.

Prerequisites

  • Raspberry Pi Pico 2 with "hello-world" binary already flashed
  • OpenOCD installed and working
  • GDB (arm-none-eabi-gdb) installed
  • Your Pico 2 connected to your computer via USB CMSIS-DAP interface
  • CMake build artifacts available (.elf file from compilation)

Task Description

In this exercise, you'll:

  1. Start OpenOCD to provide a debug server
  2. Connect GDB to the Pico 2 via OpenOCD
  3. Set a breakpoint at the main function
  4. Examine registers and memory while the program is running
  5. Verify that your dynamic debugging setup works

Important Setup Notes

Before you start, make sure:

  • Your Pico 2 is powered on and connected to your computer
  • You have OpenOCD installed for ARM debugging
  • You have GDB (specifically arm-none-eabi-gdb) installed
  • Your binary file (0x0001_hello-world.elf) is available in the build/ directory

Step-by-Step Instructions

Step 1: Start OpenOCD in Terminal 1

Open a new terminal window (PowerShell, Command Prompt, or WSL):

On Windows (PowerShell/Command Prompt):

openocd ^
  -s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
  -f interface/cmsis-dap.cfg ^
  -f target/rp2350.cfg ^
  -c "adapter speed 5000"

Expected Output:

Open On-Chip Debugger 0.12.0+dev
...
Info : CMSIS-DAP: SWD detected
Info : RP2350 (dual core) detected
Info : Using JTAG interface
...
Info : accepting 'gdb' connection on tcp/3333
Step 2: Start GDB in Terminal 2

Open a second terminal window and navigate to your project directory:

arm-none-eabi-gdb build\0x0001_hello-world.elf

Expected Output:

Reading symbols from build\0x0001_hello-world.elf...
(gdb)
Step 3: Connect GDB to OpenOCD

At the GDB prompt (gdb), type:

target extended-remote localhost:3333

Expected Output:

Remote debugging using localhost:3333
(gdb)

(The warning is normal - you already loaded the .elf file, so it doesn't matter)

Step 4: Reset and Halt the Target

To reset the Pico 2 and prepare for debugging, type:

monitor reset halt

Expected Output:

(gdb)

(This resets the processor and halts it, preventing execution until you tell it to run)

Step 5: Set a Breakpoint at main

To stop execution at the beginning of the main function:

b main

Expected Output:

Breakpoint 1 at 0x10000234: file ../0x0001_hello-world.c, line 4.
(gdb)

What this means:

  • Breakpoint 1 is set at address 0x10000234
  • That's in the file ../0x0001_hello-world.c at line 4
  • The breakpoint is at the main function
Step 6: Continue Execution to the Breakpoint

Now let the program run until it hits your breakpoint:

c

Expected Output:

Continuing.

Breakpoint 1, main () at ../0x0001_hello-world.c:4
4       stdio_init_all();
(gdb)

Great! Your program is now halted at the beginning of main().

Step 7: Examine the Assembly with disas

To see the assembly language of the current function:

disas

Expected Output:

Dump of assembler code for function main:
=> 0x10000234 <+0>:     push    {r3, lr}
   0x10000236 <+2>:     bl      0x1000156c <stdio_init_all>
   0x1000023a <+6>:     ldr     r0, [pc, #8]    @ (0x10000244 <main+16>)
   0x1000023c <+8>:     bl      0x100015fc <__wrap_puts>
   0x10000240 <+12>:    b.n     0x1000023a <main+6>
   0x10000242 <+14>:    nop
   0x10000244 <+16>:    adds    r4, r1, r7
   0x10000246 <+18>:    asrs    r0, r0, #32
End of assembler dump.
(gdb)

Interpretation:

  • The => arrow shows where we're currently stopped (at 0x10000234)
  • We can see the push, bl (branch and link), ldr, and b.n (branch) instructions
  • This is the exact code you analyzed in the Ghidra exercises!
Step 8: View All Registers with i r

To see the current state of all CPU registers:

i r

Expected Output:

r0             0x0                 0
r1             0x10000235          268436021
r2             0x80808080          -2139062144
r3             0xe000ed08          -536810232
r4             0x100001d0          268435920
r5             0x88526891          -2007865199
r6             0x4f54710           83183376
r7             0x400e0014          1074659348
r8             0x43280035          1126694965
r9             0x0                 0
r10            0x10000000          268435456
r11            0x62707361          1651536737
r12            0xed07f600          -318245376
sp             0x20082000          0x20082000
lr             0x1000018f          268435855
pc             0x10000234          0x10000234 <main>
xpsr           0x69000000          1761607680

Key Registers to Understand:

Register Value Meaning
pc 0x10000234 Program Counter - we're at the start of main
sp 0x20082000 Stack Pointer - top of our stack in RAM
lr 0x1000018f Link Register - where we return from main
r0-r3 Various Will hold function arguments and return values
Step 9: Step Into the First Instruction

To execute one assembly instruction:

si

Expected Output:

0x10000236 in main () at ../0x0001_hello-world.c:5
5       stdio_init_all();
(gdb)

The pc should now be at 0x10000236, which is the next instruction.

Step 10: Answer These Questions

Based on what you've observed:

Question 1: GDB Connection
  • Was GDB able to connect to OpenOCD? (Yes/No)
  • Did the program stop at your breakpoint? (Yes/No)

Question 2: Breakpoint Address
  • What is the memory address of the main function's first instruction?

  • Is this in Flash memory (0x100...) or RAM (0x200...)?

Question 3: Stack Pointer
  • What is the value of the Stack Pointer (sp) when you're at main?

  • Is this in Flash or RAM?

Question 4: First Instruction
  • What is the first instruction in main?

  • What does it do? (Hint: push = save to stack)

Question 5: Disassembly Comparison
  • Look at the disassembly from GDB (Step 7)
  • Compare it to the disassembly from Ghidra (Exercise 1)
  • Are they the same?

Deeper Exploration (Optional Challenge)

Challenge 1: Step Through stdio_init_all
  1. Continue stepping: si (step into) or ni (next instruction)
  2. Eventually, you'll reach bl 0x1000156c <stdio_init_all>
  3. Use si to step into that function
  4. What instructions do you see?
  5. What registers are being modified?
Challenge 2: View Specific Registers

Instead of viewing all registers, you can view just a few:

i r pc sp lr r0 r1 r2

This shows only the registers you care about.

Challenge 3: Examine Memory

To examine memory at a specific address (e.g., where the string is):

x/16b 0x100019cc

This displays 16 bytes (b = byte) starting at address 0x100019cc. Can you see the "hello, world" string?

Challenge 4: Set a Conditional Breakpoint

Set a breakpoint that only triggers after a certain condition:

b *0x1000023a if $r0 != 0

This is useful when you want to break on a condition rather than every time.

Questions for Reflection

  1. Why does GDB show both the C source line AND the assembly?

    • This is because the .elf file contains debug symbols
    • What would happen if we used a stripped binary?
  2. How does GDB know the assembly for each instruction?

    • It disassembles the binary on-the-fly based on the architecture
  3. Why is the Stack Pointer so high (0x20082000)?

    • It's at the top of RAM and grows downward
    • Can you calculate how much RAM this Pico 2 has?
  4. What's the difference between si (step into) and ni (next instruction)?

    • si steps into function calls
    • ni executes entire functions without stopping inside them

Important GDB Commands Reference

Command Short Form What It Does
target extended-remote localhost:3333 Connect to OpenOCD
monitor reset halt Reset and halt the processor
break main b main Set a breakpoint at main function
continue c Continue until breakpoint
step instruction si Step one instruction (into calls)
next instruction ni Step one instruction (over calls)
disassemble disas Show assembly for current function
info registers i r Show all register values
x/Nxy ADDRESS x Examine memory (N=count, x=format, y=size)
quit q Exit GDB

Examples for x command:

  • x/10i $pc - examine 10 instructions at program counter
  • x/16b 0x20000000 - examine 16 bytes starting at RAM address
  • x/4w 0x10000000 - examine 4 words (4-byte values) starting at Flash address

Troubleshooting

Problem: "OpenOCD not found"

Solution: Make sure OpenOCD is in your PATH or use the full path to the executable

Problem: "Target not responding"

Solution:

  • Check that your Pico 2 is properly connected
  • Make sure OpenOCD is running and shows "accepting 'gdb' connection"
  • Restart both OpenOCD and GDB
Problem: "Cannot find breakpoint at main"

Solution:

  • Make sure you compiled with debug symbols
  • The .elf file must include symbol information
  • Try breaking at an address instead: b *0x10000234
Problem: GDB shows "No source available"

Solution:

  • This happens with stripped binaries
  • You can still see assembly with disas
  • You can still examine memory and registers

Summary

By completing this exercise, you've:

  1. ? Set up OpenOCD as a debug server
  2. ? Connected GDB to a Pico 2 board
  3. ? Set a breakpoint and halted execution
  4. ? Examined assembly language in a live debugger
  5. ? Viewed CPU registers and their values
  6. ? Verified your dynamic debugging setup works

You're now ready for Week 2, where you'll:

  • Step through code line by line
  • Watch variables and memory change
  • Understand program flow in detail
  • Use this knowledge to modify running code

Next Steps

  1. Close GDB: Type quit or q to exit
  2. Close OpenOCD: Type Ctrl+C in the OpenOCD terminal
  3. Review: Go back to the Ghidra exercises and compare static vs. dynamic analysis
  4. Prepare: Read through Week 2 materials to understand what's coming next