Debugger Flag ============= Download Challenge Files ------------------------ :download:`Download the GDB challenge binary here: gdb_challenge.bin <../../_static/gdb_challenge_26.bin>` :download:`Download the GDB challenge elf here: gdb_challenge.elf <../../_static/gdb_challenge_26.elf>` Once downloaded, update the firmware running on one of your boards using the flash tool. .. code-block:: uvx ectf hw flash -n GDBIMG Follow the instructions to get starting debugging with OpenOCD and GDB here: :doc:`../getting_started/openocd`. Finally, the GDB challenge firmware will print a flag to the UART0 port if you succeed, so you'll need a way to read from the serial debug port to get the flag. Getting Your Bearings --------------------- When the device finishes starting up, you should see output along the lines of: `"Welcome to the GDB challenge"` printed to the serial console. .. The LED will .. cycle for 3 flashes before running the GDB challenge function and repeating. After starting an OpenOCD session and connecting to GDB, you should see an output similar to:: (gdb) target remote localhost:3333 Remote debugging using localhost:3333 0x00006f26 in DL_Common_delayCycles (cycles=4000000) at bazel-out/k8-opt/bin/source/ti/driverlib/dl_common.c:49 warning: 49 bazel-out/k8-opt/bin/source/ti/driverlib/dl_common.c: No such file or directory (gdb) You are now using GDB. The CPU should have halted once the debugger was connected and we're now ready to start debugging! Setting a Breakpoint -------------------- Now that we have control of the system, let's continue to main. To do that, we must first set a hardware breakpoint using `hbreak` or `hb` for shorthand: To get to the start of the challenge function, set a breakpoint at the `gdb_challenge` use `*gdb_challenge` to get to the start of the function (because * tells gdb get this address and if you pass it a function it jumps right to the code the user made) function and run to it using ``continue`` or ``c``:: (gdb) hb *gdb_challenge Breakpoint 1 at 0x6750: file src/HSM.c, line 95. Note: automatically using hardware breakpoints for read-only addresses. (gdb) c Continuing. Thread 2 "max32xxx.cpu" hit Breakpoint 1, gdb_challenge () at src/debugger_challenge.c:102 102 void __attribute__((optimize("O0"))) gdb_challenge() { Now you are stopped at the beginning of the `gdb_challenge` function. Let's view the current register values with ``info registers``: :: (gdb) info registers r0 0xdeadbeef -559038737 r1 0xfeedface -17958194 r2 0xcafecafe -889271554 r3 0xc0ff3311 -1057017071 r4 0x3d0900 4000000 r5 0x6fdb 28635 r6 0x5a5a5a5a 1515870810 r7 0xffffffff -1 r8 0xffffffff -1 r9 0xffffffff -1 r10 0xffffffff -1 r11 0xffffffff -1 r12 0xffffffff -1 sp 0x20207fd8 0x20207fd8 lr 0x654b 25931 pc 0x6750 0x6750 xpsr 0x81000000 -2130706432 msp 0x20207fd8 0x20207fd8 psp 0xfffffffc 0xfffffffc primask 0x0 0 basepri 0x0 0 faultmask 0x0 0 control 0x0 0 We can also view values in memory with ``x`` specified by address or symbol: :: (gdb) x gdb_challenge 0x6744 : 0xb082b510 (gdb) x 0x6744 0x6744 : 0xb082b510 **Write down the raw 4-byte value of the instruction(s) in memory at `to_hex` for later as value1** Use what you've learned so far to set a breakpoint at the start of the ``do_some_math`` function. **Write down the raw value of the stack pointer (SP) after hitting this new breakpoint as value2. Hint: It should end with 0xb0.** Stepping Through Code --------------------- With execution paused at the breakpoint for ``do_some_math``, let's inspect the disassembly for the function using ``disass`` :: (gdb) disass Dump of assembler code for function do_some_math: => 0x00006c9e <+0>: push {r4, r5, r6, r7, lr} 0x00006ca0 <+2>: sub sp, #20 0x00006ca2 <+4>: str r0, [sp, #16] 0x00006ca4 <+6>: str r1, [sp, #12] 0x00006ca6 <+8>: str r2, [sp, #8] 0x00006ca8 <+10>: str r3, [sp, #4] 0x00006caa <+12>: ldr r4, [sp, #16] 0x00006cac <+14>: ldr r5, [sp, #12] 0x00006cae <+16>: adds r7, r4, r5 0x00006cb0 <+18>: ldr r1, [sp, #8] 0x00006cb2 <+20>: mov r0, r5 0x00006cb4 <+22>: bl 0x69d8 <__aeabi_idivmod> 0x00006cb8 <+26>: mov r6, r0 0x00006cba <+28>: muls r6, r7 0x00006cbc <+30>: ldr r7, [sp, #4] 0x00006cbe <+32>: mov r0, r7 0x00006cc0 <+34>: mov r1, r4 0x00006cc2 <+36>: bl 0x69d8 <__aeabi_idivmod> 0x00006cc6 <+40>: mov r0, r1 0x00006cc8 <+42>: muls r0, r6 0x00006cca <+44>: eors r4, r5 0x00006ccc <+46>: adds r1, r7, r4 0x00006cce <+48>: bl 0x69d8 <__aeabi_idivmod> 0x00006cd2 <+52>: mov r0, r1 0x00006cd4 <+54>: add sp, #20 0x00006cd6 <+56>: pop {r4, r5, r6, r7, pc} End of assembler dump. Instead of using breakpoints, we can instead step through this function instruction by instruction using `si` for step instruction: :: (gdb) si 0x00006cac 26 in src/HSM.c (gdb) si 0x00006cae 26 in src/HSM.c (gdb) si 0x00006cb0 26 in src/HSM.c You can see that we are stepping through the instructions of the function. If you run ``disass`` again, you will see our position has changed: :: (gdb)disass Dump of assembler code for function do_some_math: 0x00006c9e <+0>: push {r4, r5, r6, r7, lr} 0x00006ca0 <+2>: sub sp, #20 0x00006ca2 <+4>: str r0, [sp, #16] => 0x00006ca4 <+6>: str r1, [sp, #12] 0x00006ca6 <+8>: str r2, [sp, #8] 0x00006ca8 <+10>: str r3, [sp, #4] 0x00006caa <+12>: ldr r4, [sp, #16] 0x00006cac <+14>: ldr r5, [sp, #12] 0x00006cae <+16>: adds r7, r4, r5 0x00006cb0 <+18>: ldr r1, [sp, #8] 0x00006cb2 <+20>: mov r0, r5 0x00006cb4 <+22>: bl 0x69d8 <__aeabi_idivmod> 0x00006cb8 <+26>: mov r6, r0 0x00006cba <+28>: muls r6, r7 0x00006cbc <+30>: ldr r7, [sp, #4] 0x00006cbe <+32>: mov r0, r7 0x00006cc0 <+34>: mov r1, r4 0x00006cc2 <+36>: bl 0x69d8 <__aeabi_idivmod> 0x00006cc6 <+40>: mov r0, r1 0x00006cc8 <+42>: muls r0, r6 0x00006cca <+44>: eors r4, r5 0x00006ccc <+46>: adds r1, r7, r4 0x00006cce <+48>: bl 0x69d8 <__aeabi_idivmod> 0x00006cd2 <+52>: mov r0, r1 0x00006cd4 <+54>: add sp, #20 0x00006cd6 <+56>: pop {r4, r5, r6, r7, pc} End of assembler dump. Setting a Watchpoint -------------------- Instead of manually stepping through individual instructions, we can also automatically run through code and break when a variable, register, or value in memory changes by setting a watchpoint. Let's set a watchpoint on register ``r2`` and continue running: :: (gdb) watch $r2 Watchpoint 3: $r2 By default, GDB will just print the old and new value in signed integer format, so let's tell GDB to inspect register ``r2`` when it breaks on the watchpoint in order to automatically see the hexadecimal representation: :: (gdb) commands Type commands for breakpoint(s) 3, one per line. End with a line saying just "end". >info registers r2 >end Now, we can continue running until ``r2`` changes: :: (gdb) c Continuing. Watchpoint 3: $r2 Old value = -889271554 New value = -8979097 __aeabi_idivmod () at /scratch/build_jenkins/workspace/BuildAndValidate_Worker/llvm_cgt/llvm-project/compiler-rt/lib/builtins/arm/aeabi_idivmod.S:38 warning: 38 /scratch/build_jenkins/workspace/BuildAndValidate_Worker/llvm_cgt/llvm-project/compiler-rt/lib/builtins/arm/aeabi_idivmod.S: No such file or directory r2 0xff76fd67 -8979097 **Continue running through the `do_some_math` function until the value of r2 starts with 0xca and record that value as value3** When we're done watching ``r2``, we can delete the watchpoint. First, view the existing breakpoints and watchpoints with ``info break`` or ``i b`` for shorthand: :: (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x00006750 in gdb_challenge at src/HSM.c:95 breakpoint already hit 1 time 2 breakpoint keep y 0x00006caa in do_some_math at src/HSM.c:26 breakpoint already hit 1 time 3 watchpoint keep y $r2 breakpoint already hit 6 times info registers r2 The break number of the ``r3`` watchpoint is ``3``, so we will delete that break number, and check the break numbers again to make sure we successfully removed the watchpoint: :: (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x00006750 in gdb_challenge at src/HSM.c:95 breakpoint already hit 1 time 2 breakpoint keep y 0x00006caa in do_some_math at src/HSM.c:26 breakpoint already hit 1 time Writing to Registers and Memory ------------------------------- Set a breakpoint at 0x00006cd6 (the end of `do_some_math`) and continue there: :: (gdb) hb *0x00006cd6 Breakpoint 4 at 0x6cd6: file src/HSM.c, line 26. (gdb) c Continuing. Breakpoint 4, 0x00006cd6 in do_some_math (a=26453, b=-1, c=1515870810, d=28635) at src/HSM.c:26 warning: 26 src/HSM.c: No such file or directory With the `set` command we can now modify registers (make sure to reset them): :: (gdb) info registers r0 r0 0x0 0 (gdb) set $r0=111 (gdb) info registers r0 r0 0x6f 111 (gdb) set $r0=0 And memory: :: (gdb) x 0x2001fff8 0x2001fff8: 0x00000000 (gdb) set *0x2001fff8=0x111 (gdb) x 0x2001fff8 0x2001fff8: 0x00000111 (gdb) set *0x2001fff8=0 Capturing the flag ------------------ With what you've learned, set a breakpoint at the first instruction of the `check_flag` function and continue up to there. Make sure the breakpoint is at the first instruction in the function and not deeper down. `check_flag` has five arguments; let's check them out. The ARM calling convention is to place the first four arguments in registers (r0-r3) and further arguments are pushed to the stack. Print the registers and then the top value on the stack to view the arguments: :: (gdb) info registers r0 0x11111111 286331153 r1 0x22222222 572662306 r2 0x33333333 858993459 r3 0x44444444 1145324612 r4 0x6ffa 28666 r5 0x6fdb 28635 r6 0x5a5a5a5a 1515870810 r7 0xffffffff -1 r8 0xffffffff -1 r9 0xffffffff -1 r10 0xffffffff -1 r11 0xffffffff -1 r12 0xffffffff -1 sp 0x20207fd8 0x20207fd8 lr 0x677b 26491 pc 0x6230 0x6230 xpsr 0x1000000 16777216 msp 0x20207fd8 0x20207fd8 psp 0xfffffffc 0xfffffffc primask 0x0 0 basepri 0x0 0 faultmask 0x0 0 control 0x0 0 (gdb) x $sp 0x20207fd8: 0x55555555 We can see that arguments 1-4 (0x11111111, 0x22222222, 0x33333333, and 0x44444444) are in registers r0 through r3, and the top value of the stack hold the fifth argument (0x55555555). Now, using what you have learned, change the values of the function arguments so that the first argument is set to `value1`, the third argument is set to `value2`, and the fifth argument is set to `value3`. Next, continue program execution and check the serial console output. If you did everything correctly, you should see a flag, and if not, you should see an explanation of which argument was incorrect. If done correctly, When you're done, type `q` to quit GDB. :: (gdb) q A debugging session is active. Inferior 1 [Remote target] will be detached. Quit anyway? (y or n) y [Inferior 1 (Remote target) detached]