Security Blog
Examining x86_64 Memory with GDB
The Art of Exploitation book has a nice section on using GDB to inspect memory. We start by disassembling the simple Hello World post.
Start by compiling hello_world.c with the ‘-g’ option.
-g Produce debugging information in the operating system's native format (stabs, COFF , XCOFF , or DWARF 2). GDB can work with this debugging information.
By default, when you disassemble code in GDB, it uses the AT&T format for the assembly instructions. The Art of Exploitation book prefers to use the Intel format. I’m not familiar with either but in case you want to follow along with the book, I’ll use the Intel syntax in my examples.
Since the book was written, GDB has changed the way you set the syntax format so you’ll want to modify it. The book says to do
set dis intel
You’ll want to do
set disassembly-flavor intel
You can create a file in your home directory called .gdbinit so you don’t have to specify this every time you start GDB. You can do all of this in one command
nobody@nobody:~$ echo "set disassembly-flavor intel" > ~/.gdbinit
Now lets fire up GDB. The book uses the ‘-q’ option which is optional
-q ``Quiet''. Do not print the introductory and copyright messages. These messages are also suppressed in batch mode.
nobody@nobody:$ gcc -g hello_world.c nobody@nobody:$ gdb -q ./a.out Reading symbols from a.out...done. (gdb)
Now that we have GDB up, lets use it to disassemble the x86_64 code. You can type the full version ‘disassemble main’ or the short version ‘disass main’
(gdb) disass main Dump of assembler code for function main: 0x0000000000400524 <+0>: push rbp 0x0000000000400525 <+1>: mov rbp,rsp 0x0000000000400528 <+4>: mov edi,0x40062c 0x000000000040052d <+9>: call 0x400418 <puts@plt> 0x0000000000400532 <+14>: mov eax,0x0 0x0000000000400537 <+19>: leave 0x0000000000400538 <+20>: ret End of assembler dump.
If you’re following along in the book, you’ll notice that the output is slightly different. The book uses the older x86 architecture, I’m using the x86_64 architecture. That means all of the 32-bit ‘E’ registers now have their equivalent 64-bit ‘R’ registers. For example, ‘esp’ is the 32-bit version of the “Extended Stack Pointer” and ‘rsp’ is the 64-bit version of the “Register Stack Pointer”.
I’ve highlighted the important part of the code, the body of main. Lets use GDB’s examine memory commands to see exactly what’s going on
GDB uses the ‘x’ command to look at the contents of memory. You can tell GDB what format you’d like to see the memory in and unit you’d like to see it in
Formats: o - octal d - decimal x - hexadecimal u - unsigned integer s - string t - binary Units: b - byte h - half w - word g - double word
You can also tell GDB how many you’d like to see by prefixing it’s type with a number. Since we know that “Hello World” is a string, we can use the x/s command to view the contents of memory where we think that string is stored.
(gdb) x/s 0x40062c 0x40062c: "Hello World!"
We can also look at the equivalent ASCII characters of “Hello World” in memory
(gdb) x/12db 0x40062c 0x40062c: 72 101 108 108 111 32 87 111 0x400634: 114 108 100 33
An interesting thing about GCC is that it automatically optimizes printf() with 1 argument to use puts(). This confused me at first since the code used printf() but the disassembly shows a call to puts()
0x000000000040052d <+9>: call 0x400418 <puts@plt>
The puts() function automatically adds a new line, that’s why when we examine memory in GDB, we don’t see the ‘n’ character that we added when we called printf() in the code
More to come; we’re just getting started!
Comments are closed.