Debugging Refresher


Computing 101.

A critical part of working with computing is understanding what goes wrong when something inevitably does. This module will build on your prior exposure to GDB with some more debugging of programs: digging in, poking around, and gaining knowledge. This is one of the most critical skills that you will learn in your computing journey, and this module will hopefully help water the seed that we planted before.

As you know, GDB is a very powerful dynamic analysis tool which you can use in order to understand the state of a program throughout its execution. You will become more familiar with some of its capabilities in this module.


Lectures and Reading


Challenges

This level gets you re-familiarized with gdb. To get started with this level, and all the other levels of this module, run /challenge/embryogdb_levelXYZ, where XYZ is the level number. That program will launch gdb. Run the actual level logic with r, and follow the prompts to get that flag!


RELEVANT DOCUMENTATION:

Next, we'll learn about how to print out the values of registers.

You can see the values for all your registers with info registers. Alternatively, you can also just print a particular register's value with the print command, or p for short. For example, p $rdi will print the value of $rdi in decimal. You can also print it's value in hex with p/x $rdi.

In order to solve this level, you must figure out the current random value of register r12 in hex.

As before, start the challenge, invoke the run gdb command, then follow the instructions. When you've printed out what you need, remember to continue to move on to the next step of the challenge!


RELEVANT DOCUMENTATION:

Next, we'll learn to use gdb to peek into process memory!

You can examine the contents of memory using the x/<n><u><f> <address> parameterized command. In this format <u> is the unit size to display, <f> is the format to display it in, and <n> is the number of elements to display. Valid unit sizes are b (1 byte), h (2 bytes), w (4 bytes), and g (8 bytes). Valid formats are d (decimal), x (hexadecimal), s (string) and i (instruction). The address can be specified using a register name, symbol name, or absolute address. Additionally, you can supply mathematical expressions when specifying the address.

For example, x/8i $rip will print the next 8 instructions from the current instruction pointer. x/16i main will print the first 16 instructions of main. You can also use disassemble main, or disas main for short, to print all of the instructions of main. Alternatively, x/16gx $rsp will print the first 16 values on the stack. x/gx $rbp-0x32 will print the local variable stored there on the stack.

You will probably want to view your instructions using the CORRECT assembly syntax. You can do that with the command set disassembly-flavor intel.

In order to solve this level, you must figure out the random value on the stack (the value read in from /dev/urandom). Think about what the arguments to the read system call are.


RELEVANT DOCUMENTATION:

A critical part of dynamic analysis is getting your program to the state you are interested in analyzing. So far, these challenges have automatically set breakpoints for you to pause execution at states you may be interested in analyzing. It is important to be able to do this yourself.

There are a number of ways to move forward in the program's execution. You can use the stepi <n> command, or si <n> for short, in order to step forward one instruction. You can use the nexti <n> command, or ni <n> for short, in order to step forward one instruction, while stepping over any function calls. The <n> parameter is optional, but allows you to perform multiple steps at once. You can use the finish command in order to finish the currently executing function. You can use the break *<address> parameterized command in order to set a breakpoint at the specified-address. You have already used the continue command, which will continue execution until the program hits a breakpoint.

While stepping through a program, you may find it useful to have some values displayed to you at all times. There are multiple ways to do this. The simplest way is to use the display/<n><u><f> parameterized command, which follows exactly the same format as the x/<n><u><f> parameterized command. For example, display/8i $rip will always show you the next 8 instructions. On the other hand, display/4gx $rsp will always show you the first 4 values on the stack. Another option is to use the layout regs command. This will put gdb into its TUI mode and show you the contents of all of the registers, as well as nearby instructions.

In order to solve this level, you must figure out a series of random values which will be placed on the stack. As before, run will start you out, but it will interrupt the program and you must, carefully, continue its execution.

You are highly encouraged to try using combinations of stepi, nexti, break, continue, and finish to make sure you have a good internal understanding of these commands. The commands are all absolutely critical to navigating a program's execution.


RELEVANT DOCUMENTATION:

NOTE: This challenge will require you to read and understand assembly! Don't worry, this skill will come in quite handy later in pwn.college.

We write code in order to express an idea which can be reproduced and refined. We can think of our analysis as a program which injests the target to be analyzed as data. As the saying goes, code is data and data is code.

While using gdb interactively as we've done with the past levels is incredibly powerful, another powerful tool is gdb scripting. By scripting gdb, you can very quickly create a custom-tailored program analysis tool. If you know how to interact with gdb, you already know how to write a gdb script--the syntax is exactly the same. You can write your commands to some file, for example x.gdb, and then launch gdb using the flag -x <PATH_TO_SCRIPT>. This file will execute all of the gdb commands after gdb launches. Alternatively, you can execute individual commands with -ex '<COMMAND>'. You can pass multiple commands with multiple -ex arguments. Finally, you can have some commands be always executed for any gdb session by putting them in ~/.gdbinit. You probably want to put set disassembly-flavor intel in there.

Within gdb scripting, a very powerful construct is breakpoint commands. Consider the following gdb script:

start
break *main+42
commands
  x/gx $rbp-0x32
  continue
end
continue

In this case, whenever we hit the instruction at main+42, we will output a particular local variable and then continue execution.

Now consider a similar, but slightly more advanced script using some commands you haven't yet seen:

start
break *main+42
commands
  silent
  set $local_variable = *(unsigned long long*)($rbp-0x32)
  printf "Current value: %llx\n", $local_variable
  continue
end
continue

In this case, the silent indicates that we want gdb to not report that we have hit a breakpoint, to make the output a bit cleaner. Then we use the set command to define a variable within our gdb session, whose value is our local variable. Finally, we output the current value using a formatted string.

Use gdb scripting to help you collect the random values in this level. This may feel difficult, but will serve you well in your journey ahead.


RELEVANT DOCUMENTATION:

As it turns out, gdb has FULL control over the target process. Not only can you analyze the program's state, but you can also modify it. While gdb probably isn't the best tool for doing long term maintenance on a program, sometimes it can be useful to quickly modify the behavior of your target process in order to more easily analyze it.

You can modify the state of your target program with the set command. For example, you can use set $rdi = 0 to zero out $rdi. You can use set *((uint64_t *) $rsp) = 0x1234 to set the first value on the stack to 0x1234. You can use set *((uint16_t *) 0x31337000) = 0x1337 to set 2 bytes at 0x31337000 to 0x1337.

Suppose your target is some networked application which reads from some socket on fd 42. Maybe it would be easier for the purposes of your analysis if the target instead read from stdin. You could achieve something like that with the following gdb script:

start
catch syscall read
commands
  silent
  if ($rdi == 42)
    set $rdi = 0
  end
  continue
end
continue

This example gdb script demonstrates how you can automatically break on system calls, and how you can use conditions within your commands to conditionally perform gdb commands.

In the previous level, your gdb scripting solution likely still required you to copy and paste your solutions. This time, try to write a script that doesn't require you to ever talk to the program, and instead automatically solves each challenge by correctly modifying registers / memory.


RELEVANT DOCUMENTATION:

This level will expose you to some of the true power of gdb.


RELEVANT DOCUMENTATION:

The previous level showed you raw, but unrefined power. This level will force you to refine it, as the win function will no longer work. break at it, look around, and understand what is wrong.


RELEVANT DOCUMENTATION:


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score