Solves: 31 Medium
Heap exploitation is cool, and the best is when no free is used. >Try to pwn the challenge and get the flag remotely.
- You must spawn an instance to solve this challenge. You can connect to it with netcat: nc IP PORT
Remote service at : nc 184.108.40.206 1336
- Setup heap layout
- fill tcachebin for 0x400 sized chunks
- free large 0x400 sized chunk to get libc addresses
- oob read onto the chunk right before the large freed chunk => libc leak
- request a small 0x20 sized chunk that gets free right after, it falls at the begin of the chunk in the unsortedbin, oob read like just before => heap leak.
- tcache poisoning (we’re able to deal with safe-linking given we leaked heap)
- With the help of tcache poisoning, overwrite
$ checksec --file ./heap-hop
What we can see is that a recent libc is provided (which means with safe-linking) and that the binary isn’t PIE.
Here is basically the main logic of the binary:
int __cdecl main(int argc, const char **argv, const char **envp)
Basic layout for a heap exploitation challenge, we’re allowed to create, read and edit a given track. As we already read in the initial statement we apparently cannot free a track.
Let’s first take a look at the create function:
unsigned __int64 handle_create()
It crafts a chunk, and then allocates a chunk for a given size (< 0x480). The read function is very basic:
unsigned __int64 handle_read()
tracks[v1]->size bytes from
tracks[v1]->track. Which means no need to worry about badchars for the leak.
The bug lies in the
unsigned __int64 handle_edit()
There are two bugs, or at least interesting behaviours around realloc. First there is an out of bound (oob) read / write, indeed if we give a size smaller than
v0->track could be changed to a smaller chunk and thus
read(0, (void *)tracks[idx]->track, tracks[idx]->size); could write over the end of the chunk. Secondly we can free a chunk by giving zero to the size.
Given tcache poisoning seems to be pretty easy to achieve, we need to find where we could use our arbitrary write. If you remind well, the binary isn’t PIE based and has only partial RELRO, which means we could easily hiijack the GOT entry of a function (like realloc) to replace it with system and then call
realloc("/bin/sh"). This way we need to get a heap and a libc leak.
To get a libc leak we can fill the tcache and free a large chunk to make appear libc addresses on the heap and then read it through the oob read. Which gives:
create(0, b"", 5, b"0")
The heap looks like this:
0x1d83120 0x0000000000000000 0x0000000000000041 ........A....... <= chunk used to get the oob r/w
I advice you to take a look at the heap layout if you do not understand the exploit script.
Now we got a libc leak we’re looking for a heap leak, it is basically the same thing as above, but instead of freeing a large chunk, we free a small
0x20 sized chunk. To understand the defeat of safe-linking I advice you to read this. Which gives:
# leak heap to craft pointers
To achieve tcache poisoning we just need to get the
0x20 sized chunk right after the out of bound chunk. Then we free it and we use the out of bound chunk to overwrite the forward pointer of the victim chunk to
&realloc@GOT. Given we leaked the heap we can easily bypass the safe-linking protection.
#== tcache poisoning
Then we just have to do:
# edit => realloc("/bin/sh") => system("/bin/sh")
nasm@off:~/Documents/pwn/pwnme/heap$ python3 exploit.py REMOTE HOST=220.127.116.11 PORT=1336
Here is the final exploit: