300 points
Category: Binary Exploitation
Tags : #heap #useafterfree #writewhatwhere #tcache #poisoning #safelinking
I'm starting to write a game about horse racing, would you mind testing it out? Maybe you can find some of my easter eggs... Hopefully it's a heap of fun!
vuln
, libc.so.6
, ld-linux-x86-64.so.2
, nc saturn.picoctf.net <port>
THIS WRITE UP IS NOT A COMPLETE SOLUTION but details what progress I did achieve during the event, prior to getting stuck.
I plan to revisit this challenge with the hopes to complete it after the event.
Checking the binary security :
$ checksec vuln
[*] 'picoCTF/horsetrack/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./'
Disassembled vuln
binary in Ghidra and begun analysis, renaming functions and variables along the way to describe their function.
Program uses a datastructure dynamically allocated in main()
to store horses within a series of stables. There is a maximum capacity of 18 horses. Each horse within the stable is represented by the following data structure:
stables = horse[18]
horse
{
// name of the horse (64-bit pointer)
char *name; // &horse + 0x0
// race position
int position; // &horse + 0x8
// 1 = stable occupied with horse via add_horse(), 0 = no horse in this stable removed via remove_horse()
int stable_in_use; // &horse + 0xc
}
Analysis of heap operations :
malloc()
inmain()
to allocate space for aforementioned stables structuremalloc()
inadd_horse()
to allocate horse name string buffer when adding a new horse- size of
malloc()
is under user control, however is range bounded properly (not exploitable)
- size of
free()
inremove_horse()
to release string buffer containing horse's name- string buffer pointer is not cleared after free and remains dangling (exploitable)!
To make use of the exploitable dangling pointer, a secret menu item was found in the disassembly, item 0
which allows for moving of a horses position giving them a headstart in a race (refer position
element of horse structure), however this cheating is detected via a global flag. But thats not the important part of this function, this function allows up to 16 characters to be written to a selected horses name string buffer. Even more so, the stable_in_use
flag is not checked and therefore this horse may have already been removed... hence a mechanism for "use after free"!
Attack mechanism :
-
add_horse()
.. allocates buffer -
remove_horse()
.. frees buffer -
move_horse()
... writes to top 16 bytes (conventiently 64-bit pointer size) of the freed buffer, overwritting allocator data structures (TCache free list, forward pointer in this case).─────────────────────────────────── Tcachebins for thread 1 ─────────────────────────────────── Tcachebins[idx=0, size=0x20, count=2] ← Chunk(addr=0x4055d0, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x4055b0, size=0x20, flags=PREV_INUSE)
gef➤ vmmap [ Legend: Code | Heap | Stack ] Start End Offset Perm Path 0x00000000400000 0x00000000401000 0x00000000000000 r-- /home/scottw/picoCTF/horsetrack/vuln 0x00000000401000 0x00000000402000 0x00000000001000 r-x /home/scottw/picoCTF/horsetrack/vuln 0x00000000402000 0x00000000403000 0x00000000002000 r-- /home/scottw/picoCTF/horsetrack/vuln 0x00000000403000 0x00000000404000 0x00000000002000 r-- /home/scottw/picoCTF/horsetrack/vuln 0x00000000404000 0x00000000405000 0x00000000003000 rw- /home/scottw/picoCTF/horsetrack/vuln 0x00000000405000 0x00000000426000 0x00000000000000 rw- [heap]
And this is where the wheels started falling of my challenge attempt. Typically in the past this kind of exploit I'd use a TCache Poisoning attack to have malloc()
return an arbritary pointer to a target memory location. With the ambitious plan of overwritting the free
GOT entry with system
with a horse aptly named /bin/sh
.
However vuln
appears to utilise GLIBC 2.33, which I quickly learnt benefits from safe-linking of the entries within the TCache bins, mitigating the standard TCache Poisoning attack. I was quickly coming to the end of my knowledge and current experience.
With a non-PIE and only partial relocatable binary, attempts were made to use fixed heap addresses as obtained through gdb, but the end result was always :
malloc(): unaligned tcache chunk detected
There was no fooling the GLIBC TCache Poisoning mitigations for me.
Unsolved.