Finding the Return Address on the Stack
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!
Comments are closed.