Debugging: The D Module

The Debugging (d) Module is used to debug the binary. This module sets breakpoints, views registers, and runs the binary. It also drives our dynamic analysis, which involves actively running the binary and observing its behavior. This is done by watching the registers, the stack, and the instructions as they are executed.

In radare2, dynamic analysis is done in debug mode. We can enter debug mode by opening a binary and using the -d flag.

The Help Page

[0x00000000]> d? Usage: d # Debug commands | d:[?] [cmd] run custom debug plugin command | db[?] breakpoints commands | dbt[?] display backtrace based on dbg.btdepth and dbg.btalgo | dc[?] continue execution | dd[?][*+-tsdfrw] manage file descriptors for child process | de[-sc] [perm] [rm] [e] debug with ESIL (see de?) | dg <file> generate a core-file (WIP) | dh [plugin-name] select a new debug handler plugin (see dbh) | dH [handler] transplant process to a new handler | di[?] show debugger backend information (See dh) | dk[?] list, send, get, set, signal handlers of child | dL[?] list or set debugger handler | dm[?] show memory maps | do[?] open process (reload, alias for 'oo') | doo[args] reopen in debug mode with args (alias for 'ood') | doof[file] reopen in debug mode from file (alias for 'oodf') | doc close debug session | dp[?] list, attach to process or thread id | dr[?] cpu registers | ds[?] step, over, source line | dt[?] display instruction traces | dw <pid> block prompt until pid dies | dx[?][aers] execute code in the child process | date [-b] use -b for beat time

Breakpoints and Watchpoints

Breakpoints and watchpoints pause execution at a specific point in the program. This is useful for debugging and reverse engineering. Radare2 has several commands for managing breakpoints and watchpoints.

Using Breakpoints

The db submodule is responsible for breakpoints. This module allows you to create, delete, and manage breakpoints. We can use db? to see the available commands.

You set breakpoints using db plus the address or symbol. The argument can also be a symbol plus an offset, which radare2 will resolve to its proper address. Here are some examples of setting breakpoints:

[0xf7f8d8a0]> db main [0xf7f8d8a0]> db sym.read_in+8 [0xf7f8d8a0]> db 0x080491e2

Use db to view the breakpoint list. You can use dbi to list the breakpoints by index; however, this only shows their address, not their name.

0x0804923c - 0x0804923d 1 --x sw break enabled valid cmd="" cond="" name="main" module="/ironforge/args" 0x080491f7 - 0x080491f8 1 --x sw break enabled valid cmd="" cond="" name="sym.read_in+8" module="/ironforge/args" 0x080491e2 - 0x080491e3 1 --x sw break enabled valid cmd="" cond="" name="0x080491e2" module="/ironforge/args"

To remove a breakpoint, use db -<name> or db -<address>. You can use dbi -<index> to remove a breakpoint by its index.

[0xf7f8d8a0]> db -main [0xf7f8d8a0]> dbi -2

Use the dbe and dbd commands to enable and disable breakpoints, respectively. The dbie and dbid commands work the same way, using indices for their arguments.

[0xf7f8d8a0]> dbe main [0xf7f8d8a0]> dbd main [0xf7f8d8a0]> dbie 1 [0xf7f8d8a0]> dbid 1

Setting Watchpoints

Watchpoints are breakpoints that are triggered when a specific memory address is accessed. This is useful for detecting when a variable is changed. We can use dbw to set a watchpoint. This takes two arguments: the address to watch and the watch flags (read, write, or both).

[0xf7f8d8a0]> dbw 0x0804a000 rw

The list of watchpoints is stored in db with the breakpoints.

Memory Analysis

This section will cover how to view registers, memory segments, and the heap.

Stack Analysis
Stack Analysis is not covered in this section. This is collectively covered in Printing - The p Module|Printing - The p Module since printing the stack is the same as most other memory. Some information is also covered in Visual Mode|Visual Mode.

Viewing Registers

Use the dr submodule to get more information about the registers.

Info
Use dr? to view the help pages for this submodule.
[0x0804923c]> dr eax = 0x0804923c ebx = 0xf7e2a000 ecx = 0x8e819449 edx = 0xffe4a930 esi = 0xffe4a9c4 edi = 0xf7f47b80 esp = 0xffe4a90c ebp = 0xf7f48020 eip = 0x0804923c eflags = 0x00000246 oeax = 0xffffffff

Viewing Memory Segments

Use the dm submodule to get more information about the memory segments.

[0x0804923c]> dm 0x08048000 - 0x08049000 - usr 4K s r-- /ironforge/args /ironforge/args ; segment.ehdr 0x08049000 - 0x0804a000 * usr 4K s r-x /ironforge/args /ironforge/args ; map._home_joybuzzer_args.r_x 0x0804a000 - 0x0804b000 - usr 4K s r-- /ironforge/args /ironforge/args ; map._home_joybuzzer_args.r__ 0x0804b000 - 0x0804c000 - usr 4K s r-- /ironforge/args /ironforge/args ; map._home_joybuzzer_args.rw_ 0x0804c000 - 0x0804d000 - usr 4K s rw- /ironforge/args /ironforge/args ; obj._GLOBAL_OFFSET_TABLE_ 0xf7c00000 - 0xf7c20000 - usr 128K s r-- /usr/lib/i386-linux-gnu/libc.so.6 /usr/lib/i386-linux-gnu/libc.so.6 0xf7c20000 - 0xf7da2000 - usr 1.5M s r-x /usr/lib/i386-linux-gnu/libc.so.6 /usr/lib/i386-linux-gnu/libc.so.6 0xf7da2000 - 0xf7e27000 - usr 532K s r-- /usr/lib/i386-linux-gnu/libc.so.6 /usr/lib/i386-linux-gnu/libc.so.6 0xf7e27000 - 0xf7e28000 - usr 4K s --- /usr/lib/i386-linux-gnu/libc.so.6 /usr/lib/i386-linux-gnu/libc.so.6 0xf7e28000 - 0xf7e2a000 - usr 8K s r-- /usr/lib/i386-linux-gnu/libc.so.6 /usr/lib/i386-linux-gnu/libc.so.6 0xf7e2a000 - 0xf7e2b000 - usr 4K s rw- /usr/lib/i386-linux-gnu/libc.so.6 /usr/lib/i386-linux-gnu/libc.so.6 ; ebx 0xf7e2b000 - 0xf7e35000 - usr 40K s rw- unk0 unk0 0xf7f09000 - 0xf7f0b000 - usr 8K s rw- unk1 unk1 0xf7f0b000 - 0xf7f0f000 - usr 16K s r-- [vvar] [vvar] ; map._vvar_.r__ 0xf7f0f000 - 0xf7f11000 - usr 8K s r-x [vdso] [vdso] ; map._vdso_.r_x 0xf7f11000 - 0xf7f12000 - usr 4K s r-- /usr/lib/i386-linux-gnu/ld-linux.so.2 /usr/lib/i386-linux-gnu/ld-linux.so.2 0xf7f12000 - 0xf7f37000 - usr 148K s r-x /usr/lib/i386-linux-gnu/ld-linux.so.2 /usr/lib/i386-linux-gnu/ld-linux.so.2 ; map._usr_lib_i386_linux_gnu_ld_linux.so.2.r_x 0xf7f37000 - 0xf7f46000 - usr 60K s r-- /usr/lib/i386-linux-gnu/ld-linux.so.2 /usr/lib/i386-linux-gnu/ld-linux.so.2 ; map._usr_lib_i386_linux_gnu_ld_linux.so.2.r__ 0xf7f46000 - 0xf7f48000 - usr 8K s r-- /usr/lib/i386-linux-gnu/ld-linux.so.2 /usr/lib/i386-linux-gnu/ld-linux.so.2 ; map._usr_lib_i386_linux_gnu_ld_linux.so.2.rw_ 0xf7f48000 - 0xf7f49000 - usr 4K s rw- /usr/lib/i386-linux-gnu/ld-linux.so.2 /usr/lib/i386-linux-gnu/ld-linux.so.2 0xffe2c000 - 0xffe4d000 - usr 132K s rwx [stack] [stack] ; map._stack_.rwx

We can use the dm. command to find the memory segment of a specific address. It defaults to the seek address if no address is provided.

[0x0804923c]> dm. 0x08049000 - 0x0804a000 * usr 4K s r-x /ironforge/args /ironforge/args ; map._home_joybuzzer_args.r_x [0x0804923c]> dm. @ 0xffe4a90c 0xffe2c000 - 0xffe4d000 * usr 132K s rwx [stack] [stack] ; map._stack_.rwx

This submodule provides several commands to allocate, deallocate, and map virtual memory. I don't have any writeups using the write flag, but I might add this in the future.

Heap Analysis

The dmh submodule is responsible for displaying information about the heap. Use dmh to show a map of the heap:

[0x7fae46236ca6]> dmh Malloc chunk @ 0x55a7ecbce250 [size: 0x411][allocated] Top chunk @ 0x55a7ecbce660 - [brk_start: 0x55a7ecbce000, brk_end: 0x55a7ecbef000]

Alternatively, use dmhg to get a graph of the heap:

[0x7fae46236ca6]> dmhg Heap Layout .------------------------------------. | Malloc chunk @ 0x55a7ecbce000 | | size: 0x251 | | fd: 0x0, bk: 0x0 | `------------------------------------' | .---' | | .---------------------------------------------. | Malloc chunk @ 0x55a7ecbce250 | | size: 0x411 | | fd: 0x57202c6f6c6c6548, bk: 0xa21646c726f | `---------------------------------------------' | .---' | | .----------------------------------------------------. | Top chunk @ 0x55a7ecbce660 | | [brk_start:0x55a7ecbce000, brk_end:0x55a7ecbef000] | `----------------------------------------------------'
Using dmhb
To print linked lists using the dmhb command, you must set dbg.glibc.demangle to be true.

Running the Binary

Upon opening radare2, the binary is already running. Radare2 defaults to the binary's entry point (listed in rabin2's output) and puts a temporary breakpoint at that location. This is often inconvenient for us since we don't care much about this compiler-generated code. We can set our own breakpoint at the main function and then continue execution there.

Use dc to continue execution to the next breakpoint.

[0xf7f8d8a0]> db main [0xf7f8d8a0]> dc INFO: hit breakpoint at: 0x804923c

Returning to the Instruction Pointer

As we are navigating the output, scanning through instructions and the stack, we might lose the place of the instruction pointer. An important note in radare2 is that the seek address is not the same as the instruction pointer. This causes confusion with new users as they attempt to understand where they are in the binary.

The best way to return to the instruction pointer is to use s eip. If you are in visual mode, the . command also returns the display to the instruction pointer.

Info
More information on visual mode can be found Visual Mode|here.

Stepping

There are two kinds of steps when using a debugger: stepping in and stepping over. Stepping commands are outlined in the ds submodule.

  • Step In: This method steps into any called functions and pauses at the first instruction. This allows you to walk through called functions.
  • Step Over: This method steps over a function and immediately executes all its contents. This is useful for library functions where their instructions aren't important.

To step in, use the ds instruction. To step over, use the dso instruction. You can use ds <num> or dso <num> to step <num> instructions.

Danger
These instructions are different in visual mode. In visual mode, s is for stepping in and S is for stepping over.

Continuing

You can use the dc command to continue execution until the next breakpoint or watchpoint is hit.

[0xf7f8d8a0]> dc

You can use the dcu command to continue execution until a specified address is reached.

[0xf7f8d8a0]> dcu 0x08049210 INFO: Continue until 0x08049210 using 1 bpsize Good luck winning here! INFO: hit breakpoint at: 0x8049210

You can use the dcr command to continue performing step-over instructions until the next ret instruction is reached.

[0xf7f8d8a0]> dcr
Info
This does not fully replicate the finish command in gdb. This command goes until the next ret instruction rather than the current function's ret.

The best way to handle this is to set a breakpoint at the ret instruction and then continue execution, or use the dcu command.

Stack Traces

A stack trace is a list of all the functions called up to this point. It is useful for debugging and understanding the program's flow.

To view the stack trace, use the dbt command.

[0x080491f3]> dbt 0 0x8049210 sp: 0x0 0 [sym.read_in] eip sym.read_in+33

As far as I know, radare2 does not provide support for moving up and down the stack trace. If this is not the case, please let me know, and I'll update this section!

Running Backwards

You can run programs in reverse order to better understand how to reach a certain location in a binary. Radare2 has a reverse debugger that can seek the program counter backward.

To do this, you must save the program state. This is handled via the dts submodule (the Trace Sessions submodule). Use dts+ to start a trace session.

[0x080491ef]> dts+ INFO: Reading 4096 byte(s) from 0x0804c000 INFO: Reading 4096 byte(s) from 0xf7e2a000 INFO: Reading 40960 byte(s) from 0xf7e2b000 INFO: Reading 8192 byte(s) from 0xf7faf000 INFO: Reading 4096 byte(s) from 0xf7fee000 INFO: Reading 135168 byte(s) from 0xffa57000

Use dsb to restore the previously recorded state by reverse-executing the instructions.

[0xf7fb5549]> dsb [0x080491fb]>

Use dcb to continue reverse-executing instructions until the next breakpoint.

[0xf7cb5683]> dcb [0x080491fb]>

Debugging Forks

When a binary forks, it creates a new process. This is useful for creating child processes that can run in parallel. However, this can be a pain for debugging because you have to debug each process individually.

This can be done in radare2 by deciding at the time of the fork. Use dcf to continue execution until the next fork. Once here, consult the dp submodule to handle processing commands.

The dp submodule is used to list and manage processes. Use dp to list the current processes. Use dp <num> to switch to a process. Use dp- to kill the current process.

Reopening Files: The o Module