written by hartmannsyg (solved by hartmannsyg and fs)
In following Greycat’s adventures, you have stumbled upon a factory that produces weirdly-shaped rings. Upon closer inspection, you realise that the rings seem very familiar – they looked exactly like the Sling Rings you saw from the Marvel Comics universe! Having some time leftover, you decide to explore the factory. Alas, you eventually come to realise that these Sling Rings were in fact not the same as those you knew: during forging, their destinations have to already be set. You wonder what you could do with these rings…
Author: uhgnc challs.nusgreyhats.org 35678
This was like an Avengers:Endgame level of collab solve, mainly because there are like many puzzle pieces to put together.
Actually now that I think about it, slingrings did show up in Avengers:Endgame
┌──(rwandi㉿ryan)-[~/ctf/grey/slingring-factory] └─$ checksec slingring_factory [*] '/home/rwandi/ctf/grey/slingring-factory/slingring_factory' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No |
We see that we basically have all security checks enabled, so the challenge won’t be something trivial.
Vulnerabities
Right off the bat, we have a format string vuln:
1 | undefined8 main(void) { long in_FS_OFFSET; char name [6]; long canary; canary = *(long *)(in_FS_OFFSET + 0x28); setup(); puts("What is your name?"); fgets(name,6,stdin); printf("Hello, "); |
The program then calls menu()
:
1 | void menu(void) |
We are able to create (forge), read (show), use (???) and delete (discard) slingrings? Also this “CRUD functionality” just reeks of a heap challenge (oh no)
The forge_slingring()
, show_slingrings()
and discard_slingring()
methods are about what we would expect (glorified malloc(0x84)
, read, free()
), except that show_slingrings()
allow us to read the contents of discarded rings (i.e. freed chunks).
This basically gives us a libc leak via unsorted bins. The crux of this exploit is that unsorted bins contain an address within libc when we can use to get the libc base. For us to create a freed chunk within an unsorted bin:
- Fill up tcache
Each bin contains a maximum of 7 same-size chunks ranging from 24 to 1032 bytes on 64-bit systems and 12 to 516 bytes on 32-bit systems.
from https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/
So we need to free() 7 other chunks before we can get an unsortedbin - The chunk’s size is big enough that it does not go into the fastbin
Since a chunk size of 0x90, (0x84 gets rounded up to 0x90, or 144) is greater than the fastbin max size of 88, our freed bin will not be in the fastbin. - Ensure there are no neighboring freed chunks in the small, large and unsorted bins
When we fill up tcache, there are no other bins other than tcache bins, so we can create an unsorted bin - Ensure that our chunk is not next to the top chunk
If not, it will get merged with the top chunk. As long as there is a chunk between the freed bin and the top chunk, this will not happen.
(There are other checks involved but these are the more relevant ones)
Now let’s look at the sus “use” functionality:
1 | void use_slingring(void) { long in_FS_OFFSET; char ring [4]; |
We have a buffer overflow as we are reading 0x100 bytes into a buffer (spell
) of size 56.
Putting it all together
We have a format string vuln, but we have PIE and ASLR so we need a leak somewhere to use it for an arb write, and we can only use it once right at the very start. We have a libc leak, but only after our format string vuln. We have a buffer overflow, but there is canary protections. After a lot of thinking, we realized we could leak the canary from the format string vulnerability, so we made a game plan:
- leak canary with format string vulnerabitiy
canaries are shared across all functions so the canary that was leaked in themain()
function will be the same as the canary inuse_slingring()
- leak libc by reading an unsorted bin
- ROP
system("/bin/sh")
with the buffer overflow inuse_slingring()
1 | from pwn import * |