Introduction
cshell2
is a heap challenge I did during the corCTF 2022 event. It was pretty classic so I will not describe a lot.
If you begin with heap challenges, I advice you to read previous heap writeup.
TL; DR
- Fill tcache.
- Heap overflow in
edit
on thebio
field which allows to leak the address of the unsortedbin. - Leak heap and defeat safe-linking to get an arbitrary write through tcache poisoning.
- Hiijack GOT entry of
free
tosystem
. - Call
free("/bin/sh")
. - PROFIT
Reverse Engineering
Let’s take a look at the provided binary and libc:
1 | $ ./libc.so.6 |
A very recent libc plus a non PIE-based binary without FULL RELRO
. Thus we could think to some GOT hiijacking stuff directly on the binary. Let’s take a look at the add
function:
1 | unsigned __int64 add() |
It creates a chunk by asking several fields but nothing actually interesting there. Let’s take a look at the show
function:
1 | unsigned __int64 show() |
It prints a chunk only if it’s allocated (size entry initialized in the size array) and if the index is right.
Then the delete
function:
1 | unsigned __int64 delete() |
Quite common delete
handler, it prevents double free.
The vulnerability is in the edit
function:
1 | unsigned __int64 edit() |
It reads size_array[2 * idx] - 32LL
bytes into a 0x100
-sized buffer which leads to a heap overflow.
Exploitation
There is no actual issue, we can allocate whatever chunk bigger than 0x407
, the only fancy thing we have to do would be to defeat safe-linking to get an arbitrary write with a tcache poisoning attack on the 0x410
tcache bin. Here is the attack I led against the challenge but that’s not the most optimized.
The plan is to:
- Allocate two
0x408
-sized chunks : pivot and victim, in order to easily get later libc leak. - Allocate 9 more chunks and then fill the
0x410
tcachebin with them (with only 7 of them). - Delete
victim
and overflow pivot up to the next free pointer ofvictim
to get a libc leak. - Allocate a
0x408
-sized chunk to get the8
-th chunk (withinchunk_array
) which is on the top of the bin. - Leak the heap same way as for libc, but we have to defeat safe-linking.
- Delete the
9
-th chunk to put it in the tcachebin at the first position. - Then we can simply
edit
chunk8
and overflow over chunk9
to poison its nextfp
to hiijack it toward the GOT entry offree
. - Pop chunk
9
from the freelist and then request another the target memory area : the GOT entry offree
. - Write
system
into the GOT entry offree
. - Free whatever chunk for which
//bin/sh
is written at the right begin. - PROFIT.
To understand the attack process I’ll show the heap state at certain part of the attack.
Libc / heap leak
First we have to fill the tcache. We allocate a chunk right after chunk0
we do not put into the tcache to be able to put it in the unsortedbin to make appear unsortedbin’s address:
1 | add(0, 1032, b"//bin/sh\x00", b"", b"", 1337, b"") # pivot |
Then let’s get a heap leak, we request back from the tcache the 8-th chunk, we free the 9
-th chunk that is allocated right after the 8
-th to be able to leak its next free pointer same way as for the libc previously. Plus we have to defeat safe-linking. To understand the defeat of safe-linking I advice you to read this. It ends up to the decrypt_pointer
function that makes use of known parts of the encrypted fp
to decrypt the whole pointer. I didn’t code the function by myself, too lazy for that, code comes from the AeroCTF heap-2022 writeup.
1 | def decrypt_pointer(leak: int) -> int: |
Then here we are, we leaked both libc and heap base addresses. We just have to to tcache poisoning on free
.
Tcache poisoning + PROFIT
We overflow the 8
-th chunk to overwrite the next freepointer of chunk9
that is stored at the HEAD of the 0x410
tcachebin. Then we got an arbitrary write.
We craft a nice header to be able to request it back from the tcache, and we encrypt the next
with the location of the chunk9
to pass safe-linking checks.
Given we hiijack GOT we initialized properly some pointers around to avoid segfaults. We do not get a write into the GOT entry of free
cause it is unaliagned and malloc
needs 16
bytes aligned next free pointer.
1 | edit(11, b"", b"", b"", 1337, b"X"*(1032 - 64) + pwn.p64(0x411) + pwn.p64(((heap + 0x2730) >> 12) ^ (exe.got.free - 0x8))) |
Here we are:
1 | nasm@off:~/Documents/pwn/corCTF/cshell2$ python3 exploit.py REMOTE HOST=be.ax PORT=31667 |
Appendices
Final exploit:
1 | #!/usr/bin/env python |