tl;dr
- Double fetch race Condition in store_note function.
- overwrite size during race window to get buffer overflow.
- Do SROP for execve(“/bin/sh\x00”)
Challenge Points: 856
No. of solves: 18
Author: spektre
Challenge description
Heard of heap notes? this ain’t one.
Initial analysis
The binary is standard x86 64-bit Dynamic stripped executable.
The mitigations enabled on the binary are as follows:
1 | Arch: amd64-64-little |
On reversing the binary, we can see there are 6 options avaiable:
- Store Note - stores note in the shared memory.
- Delete Note - memset note to 0.
- Print Note - prints the note.
- Upgrade Note - Upgrade size of the note.
- Encrypt/Decrypt - Encrypt note and store note in shared memory.
- Exit
The binary operates with two threads, one thread does all the store, delete, print, upgrade and encrypt functionality and the other thread checks size of the note and memcpy into buf[64] if size is less than 64 once store_note is done.
Vulnerability
store_note in thread 1 :
1 | void store_note(sh_mem *ptr) { |
Functions running on thread 2 :
1 | void process(sh_mem *ptr){ |
thread2() starts once store_input is done. If you look closely, we can see there is a Race Condition in process() function which Double fetches size for size check and memcpy, with a sleep() in between. Which gives us enough time to overwrite the size in the race window using Upgrade().
Upgrade() :
1 | void upgrade_note(sh_mem *ptr) { |
we can only use upgrade if thread2() completes executing as it checks if ptr->thread2_done
is false.
in thread2(), ptr->size_input is set to false
every time loop, but ptr->thread2_done
is not reset, so we can use upgrade_note() during store_note() anytime after the first loop is done. This allows us to overwrite size during the race window to get buffer overflow.
Exploitation
The plan for the exploit is as follows:
- Use encrypt_decrypt() function to dump the encrypted payload into the shared memory.
- store_note() once to get
ptr->thread2_done == true
- store_note again and overwrite size using upgrade() during the race window to get buffer overflow
- Now in the rop chain read “/bin/sh\x00” into bss using read_input
- Now set rax to 0x3b using alarm() (prep for SROP to trigger execve(“/bin/sh\x00”))
- Using alarm() twice returns the number of seconds remaining. so first call alarm(0x3b) and then alarm(0).
- Now setup SigreturnFrame.
You can find the full exploit here
You can also solve this using ret2libc instead of SROP. The shared memory allocated is right before ld.so page, which has a pointer to an mmaped region. That mmaped region is located right below libc mapping. which gives us enough info to get libc base address, calculate execve address and then do execve(“/bin/sh\x00”).
Conclusion
This is my first time making challenge for a ctf. I had a lot of fun and learnt a lot while making this challenge. Hope you had fun while solving as well.
Flag: bi0sCTF{D3j4_vu!_1v3_ju5t_b33n_1n_th15_pl4c3_b3f0r3_0b91342067c4}