Program analysis with gdb

The gdb debugger allows us to observe and analyse program execution step-by-step. We illustrate, through examples, the main functionalities of gdb.

A simple example

Consider again our simple C program.

If we compile it with the -g option we have additional information on the source code, directly available from gdb.

Listing and disassembling

We can now run gdb and issue command list to show the source code (thanks to the -g option given to gcc)

We now want to disassemble the program. Remember we are using the Intel syntax for assembly. In gdb we can set Intel syntax as follows:

NOTE: This can be made the default syntax by executing the following (in the home directory) so that we don’t have to issue set disassembly intel all the time:

By issuing disassemble main we can now show the assembly code of function main.

Breakpoints

gdb allows for defining breakpoints: points in the program where execution will stop so that we can inspect the state. This can be done, for example, by issuing break main which will put a breakpoint at the beginning of main function code.

We note that breakpoint is set to instruction main+17. Previous instructions are the function prologue that initialize the stack (these can vary depending on the compiler).

NOTE: To set a breakpoint at a specific address you need to use the syntax break *addr. For example break *0x804845b.

We can now run the program by issuing run

Once we are in the middle of a program execution we can inspect the state of processor and memory as described below.

Inspecting registers

Registers can be inspected by issuing info registers

We have illustrated registers in the previous class.

We observe that the instruction pointer eip contains value 0x804845b which is, in fact, where we placed the breakpoint. So the execution has stopped just before the first instruction of our C program, as expected.

NOTE: We can inspect a single register by putting its name after info register. For example:

Examining memory

We can examine memory locations using the ‘examine’ (or just ‘x’) command. When we issue this command we can specify a format separated by /. For example, if we write x/x the second x stands for hexadecimal and prints the content of the location in hexadecimal format.

Here is a list of possible formats:

  • o, octal
  • x, hexadecimal
  • u, decimal (unsigned)
  • t, binary
  • i, instruction
  • c, character
  • s, string

In the assembly of our program there is an explicit reference to a memory address:

We inspect the content of that address using the different formats:

Notice that the last one (string) returns “%d ” which is in fact the format string of the printf! This instruction is pushing on the stack the (address of the) string before the call to printf. All the other cases show that 4 bytes string in different formats. For example the hexadecimal 0x00206425 is 0x00 (string termination) 0x20 (space) 0x64 (character d) and 0x25 (character %). So basically it is ” d%” the string in the wrong order. This is due to the “endianess” of x86 processors: words are stored in little-endian byte-order (the least-significative first).

Before the format we can specify the number of elements to show. For example x/1i shows one instruction while x/4i shows four instructions. We exemplify below. Notice that we can refer to the content of a register using $name_of_register:

Finally, after the format we can optionally specify the dimension: b byte, h (halfword) 2 bytes, w 4 bytes, g 8 bytes. In the example below we print 8 bytes in hexadecimal format starting from register eip

Step-by-step execution

Current instruction stores value 0x0 in the word pointed by ebp-0xc, that we deduce to be the position on the stack of integer variable i.

We examine the value in position ebp-0xc, we execute one instruction with command nexti and we finally check again the value. Notice the usage of $ before the register name:

We can see that the value of i has been set to 0 which is the i=0 command in the C code.
Let us see what is the current instruction:

The for loop is implemented by jumping to the program point 0x804847b. We perform the jump and we inspect the next two instructions:

The above instructions compare i (ebp-0xc) with value 9 and, if the value in i is less than or equal 9 the program jumps to <main+26>. Since i is 0 the program jumps. After two more nexti we are in the following situation:

The next 4 instructions perform the call to printf by first pushing i and “%d ” (address 0x8048530) on the top of the stack and then calling the function. The sixth instruction is the increment of i.
We can execute all of the 6 instruction by issuing nexti 6 and this leads the program again in the comparison code but now the value of i is 1:

After seven more instructions we will have that i is 2 and so on:

By issuing continue (abbreviated as c) we can complete execution:

Program analysis

gdb can be used to analyse executable programs. We illustrate a simple analysis of a program that checks a password. The program, unfortunately, does not protect the password using cryptographic hashes (as should always be done) making it possible to recover it.

The executable program is available here and can be also found as /opt/shared/gdb/password on testbed. After you have watched the demo try yourself the attack!

Exercise

Try to find Darth Vader’s email password. The client is available on testbed at /opt/shared/gdb/vadermail (compiled with llvm with no optimisations) or /opt/shared/gdb/vadermail-gcc (compiled with gcc on testbed).

Resources

Leave a Reply

Your email address will not be published. Required fields are marked *