Chapter 3 of Hacking: The Art of Exploitation is all about exploitation. As mentioned in my post on stack-based buffer overflows, one of the key points to exploiting these types of overflows is gaining control of the return address from a function call.

I’ll be using the source code from my stack-based buffer overflows post. First, lets look at what main() looks like disassembled.

(gdb) disassemble main
Dump of assembler code for function main:
0x0000000100000ecd <main+0>:    push   rbp
0x0000000100000ece <main+1>:    mov    rbp,rsp
0x0000000100000ed1 <main+4>:    sub    rsp,0x10
0x0000000100000ed5 <main+8>:    mov    DWORD PTR [rbp-0x4],edi
0x0000000100000ed8 <main+11>:   mov    QWORD PTR [rbp-0x10],rsi
0x0000000100000edc <main+15>:   mov    rax,QWORD PTR [rbp-0x10]
0x0000000100000ee0 <main+19>:   mov    rdi,QWORD PTR [rax]
0x0000000100000ee3 <main+22>:   call   0x100000e88 <function>
0x0000000100000ee8 <main+27>:   mov    eax,0x0
0x0000000100000eed <main+32>:   leave
0x0000000100000eee <main+33>:   ret
End of assembler dump.

I’ve highlighted the call to the function and one instruction after it. The return address is always the next instruction address after the function call. The ‘call’ instruction pushes the return address onto the stack.

Let’s set a break point in function() so we can examine the stack after the ‘call’ instruction

(gdb) break 13
Breakpoint 1 at 0x100000ea4: file ../src/main.c, line 13.
(gdb) run hello
Breakpoint 1, function (in=0x7fff5fbff2e8 "/TheXploit/Debug/TheXploit") at ../src/main.c:14
(gdb) x/16xw $rsp
0x7fff5fbff0c0: 0x5fbff288      0x00007fff      0x5fbff2e8      0x00007fff
0x7fff5fbff0d0: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fff5fbff0e0: 0x00000000      0x00000000      0x282afe15      0xa5aa35fb
0x7fff5fbff0f0: 0x5fbff110      0x00007fff      0x00000ee8      0x00000001

Here we’ve examined 16 words from the top of the stack frame for function(). You may not notice it at first but the return address is the last 8 bytes (0x7fff5fbff0f9 – 0x7fff5fbff0ff) in the output above. It’s in Little Endian byte order so it appears backwards; the lower order bytes appear first. Look back at the disassembled main() function at the address after the call to function(), it’ll be 0x0000000100000ee8, exactly what’s on the stack (remember to swap them for lower order bytes first)!

More to come about overwriting this address to gain control of the program!