nc 188.8.131.52 9999
Binary and libc-2.27.so is given, since is 2.27 version we know that tcache is being used and on this version there isn’t any security checks if a chunk is placed in tcache bin.
First we start by using the file command:
$ file warmup
With this we know:
- ELF compiled for x86_x64 architecture.
- Dynamically linked.
- Stripped (A little bit harder to reverse).
Next step is to check protections:
Brief analysis of these protections:
- FULL RELRO (GOT entries are read only we can’t overwrite them)
- STACK CANARY (The stack is protected with the canary if there is a stack overflow we need to find a way to leak it)
- The Stack is not executable (We can’t execute shellcode, techniques like ROP can bypass this)
- PIE (Position Independent Executable) is on (If we want to use rop we need a way to leak the base address)
As always for this kind of challenges we are presented with a menu:
We can add, delete and modify.
By adding we can only specify the content:
# # # ##### ####### #######
For delete and edit we can both specify a index in case of modify we can also modify the content we previously added on creation.
As you have noticed we don’t have any read/print function that would allow us to print the contents of the created items because of this we need to find another way to leak a libc address, we can do it by manipulation the IO_FILE struct to make puts leaking an address.
The plan is:
- Use tcache dup to overwrite the size of a chunk into a size of unsorted bin range (0x91 for example)
- Fill the tcachebin size 0x91 by freeing it 7 times (max 7)
- One more free will put this chunk into an unsorted bin both fd and bk are updated into libc addresses from the main arena.
- Do a 4 bit brute force by updating the last 2 bytes of the fd libc address to get stdout.
- Resize from 0x91 back to 0x51 so next free gets into a tcache bin again.
- Overwrite stdout->_flags with 0xfbad1800 and _IO_read_ptr, _IO_read_end, _IO_read_base with NULL and the last byte of _IO_write_base with NULL.
- Extract libc addresses from next puts.
- Overwrite free_hook with system and modify its fd to /bin/sh\x00, Doing a free will get a shell for us.
Libc-2.27 uses tcache so every allocated chunk bellow 0x410 when freed is placed in a tcachebin , their behaviour will be very similar to when they were inserted in a fastbin chunk before tcache was introduced. The main problem is we can only allocate 0x40 chunks (0x51 -> size + flags) for example if we allocate one item this is how it looks like in the heap:
In order to transform the chunk above into a chunk in range of a unsorted bin(unsorted bin because after a free it will update fd bk pointers into libc addresses) we kinda need an arbitrary write.
This can be achieved with double free and by changing the fd pointer into a place we want to write, when malloc executes it will return the modified fd pointer in our case we want it to be right at the chunk header (0x5595b74c0660) so we can modify the size from 0x51 to 0x91.
Imagine after chunk A we allocate more 2 chunks B and C, if we free B first, free will check if there is any chunk inside tcachebin of size 0x50:
As you can see above there isn’t any list of size 0x50 so it will update the fd of the chunk to null.
The reason why there is a tcachebin is to reuse space on the heap when a new chunk is allocated, if it’s a perfect fit for example malloc will look at the list of that size and reposition the new allocated chunk on the same places where old chunks were freed. And this is why double freeing is so powerful, since one of the pointers is repeated in the list if we allocate one and modify the last byte of the fd to 0x60 we can make the next malloc to return to the fd we want getting an arbitrary write.
So this is how the exploit looks like right now:
The tcache bin list right now is:
0x50 : 0x56018d3ae670 -> 0x56018d3ae670
If we do this mallocs:
# 0x50 : 0x56018d3ae670 -> 0x56018d3ae670
After this if we edit index 3:
free(0) # 0x50 [ 1]: 0x56018d3ae670 <- 0x0
Now that we have a 0x91 chunk we need to fill tcachebin of 0x91, we can do this by freeing it 7 times:
for _ in xrange(7):
The reasons why I created chunk B and C was to be able to free this chunk, because tcache is full next free will have security checks, on my old write up of penpal world I did the same thing and I explained why chunk B and C bypass this checks you can find it at https://teamrocketist.github.io/2019/08/17/Pwn-RedpwnCTF-penpal-world/
The first thing we want to do now is to convert this bin again back to 0x51 size we still have its pointer saved at index 3 so we can easily do it with:
We want to do this because tcachebin(0x91) is full , we want to manipulate tcachebins again, since we already double freed before at tcachebin(0x50).
The other reason is that we can only malloc chunks of size 0x51.
Lets compare the difference of stdout address and the address that got placed at the fd .
pwndbg> p/x stdout
$3 = 0x7f8421047760
fd pointer at chunk A:
pwndbg> x/20gx 0x56018d3ae660+0x10
We want to modify 0x7f8421046ca0 to 0x7f8421047760 we only need to change the last 2 bytes, we know that the last 3 numbers of stdout never change(670), they are always the same, so the only thing we need to brute force is the 4th this means if we try to modify the last two bytes of the fd to p16(0x7760) we would have a probability of 1/16 because the only possibilities for last bytes of stdout are:
So on our second malloc, the pointer returned will be the libc address but before that we need to modify the last 2 bytes from one of the 16 possibilities:
Now if we succeed to bruteforce stdout we need to overwrite stdout->_flags with 0xfbad1800 and _IO_read_ptr, _IO_read_end, _IO_read_base with NULL and the last byte of _IO_write_base with NULL, if we do this next puts will leak a bunch of libc addresses a more detailed explanation on why this works can be found at https://vigneshsrao.github.io/babytcache/ this guy explains it very well.
#context.log_level = 'debug'
add(p64(0x0fbad1800)+ 3*p64(0) + '\x00')
log.failure("not lucky enough!")
Now adapting a bit more our code to make sure we got libc:
In the end, we have everything we need modify the fd of the object you want to free with /bin/sh and overwrite free_hook with system so next time we trigger free we get a shell!
from pwn import *