CloneWarS
Points
90
Solves
13
Category
PwnDescription:
A long time ago in a galaxy far, far away….ssh yeet@ctf2.kaf.sh -p 7000 password: 12345678
CloneWarS
TLDR
- Leak heap from R2D2
- Overflow top_chunk size
- Leak global file pointer
- Use house of force to write into file
- Trigger system(file)
Binary Analysis
The binary is the only file we get from this challenge:
1 | $ file CloneWarS |
From the file command output we know that:
- ELF compiled for x86_x64 architecture
- Dynamically linked
- Not stripped
Using checksec to see the enabled protections:
1 | $ checksec CloneWarS |
- 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)
- NX (Non executable stack)
- PIE (Position Independent Executable) is on (If we want to use rop we need a way to leak the base address)
Static Analysis
Using Ida to check on the main function we can see we have a bunch of options:
1 | while ( v3 != 7 ) |
By looking at build_death_star:
1 | unsigned __int64 build_death_star() |
As we can see above we have a controlled sized malloc this is important if we want to use certain exploits on the heap.
By looking at R2D2:
1 | unsigned __int64 R2D2() |
R2D2 gives us a free leak to the heap because of this we can calculate the offset to the HEAP BASE.
Checking out theprep_starship:
1 | unsigned __int64 prep_starship() |
As you can see because of memset we can overflow the heap by an amount we can control (capacity of the troppers) and we can also control the content that will overflow it (kind of starships).
Analysing make_troopers
1 | unsigned __int64 make_troopers() |
Nothing wrong with this one (in terms of security at least) but this one can be useful to store some content to a certain pointer specially if we manage to make malloc return an arbirtrary pointer to a place we want.
light_sabers is the same as make_troopers but instead of putting a null byte at the 8th position of the read string it puts at the 0x14-1 which is right at the end of the string.
Analysing cm2_dark_side:
1 | int cm2_dark_side() |
file is a global variable located at the BSS once again we get a free leak with this we can get the offset to the pie base and get access to the rest of the global variables, this function also hints us that the final objective of this challenge is to find a way to change the content of file to get a shell or print the flag.
House of force the jedi overflow
It’s not a coincidence that the theme of this challenge is about star wars, Obi wan intuitively says to us:
The ingredients to use house of force can be interpreted as follows:
- The exploiter must be able to overwrite the top chunk.
- There is a malloc() call with an exploiter-controllable size.
- There is another malloc() call where data are controlled by the exploiter.
We checked all the requirements:
- We have a heap-overflow at the function prep_starship through memset.
- We have a multiple malloc calls with controllable sizes for example in build_death_star.
- We have a malloc call where we can control its data in make_troopers and light_sabers.
So the core of this attack is to overwrite av->top with an big arbitrary value so it can later force malloc (which uses the top chunk) to return an arbitrary pointer to an address we want to modify.
So what is the top_chunk ? top_chunk also known as the wilderness is a special chunk that defines how much space is left in the current heap arena, this chunk is located at the top of the heap.
On this sample program we can see right after the first allocation the heap is initialized, the first chunk is the tc ache_p_struct next is the allocated chunk by us.
Finally right at the top of the heap we have the wilderness the space left in the arena is defined in the field mchunk_size so lets see what happens when we allocate a 2nd chunk:
When it exceeds the space left, heap expansion is triggered mapping a new memory page.
So what happens when the top chunk is used to allocate the size of the heap block to any value controlled by the user? The answer is that you can make the top chunk point to whatever we want (yes everywhere even in a position before because of overflow), which is equivalent to an arbitrary address write. However, in glibc, the size of the user request and the existing size of the top chunk are verified.
1 | Void_t* |
Perhaps, if you can override with size to a large value, you can easily pass this verification, we can do this with an overflow vulnerability to tamper the top_chunk size.
1 | (unsigned long) (size) >= (unsigned long) (nb + MINSIZE) |
In the Malloc Maleficarum it is written that the wilderness chunk should have the highest size possible (preferably 0xFFFFFFFFFFFFFFFF) which is the largest number in unsigned long in x64.
1 | /* Treat space at ptr + offset as a chunk */ |
After that, the top pointer will be updated, and the next heap block will be allocated to this location.
Writing the exploit
The first thing is find a way to connect with SSH to connect to the server I did that with:
1 | r =process("sshpass -p 12345678 ssh -p 7000 -tt yeet@ctf2.kaf.sh".split()) |
You need to have sshpass installed tho and also you need to add the server ip to the known hosts before which can be done by saying yes while connecting for the first time via command line:
1 | $ ssh -p 7000 yeet@ctf2.kaf.sh |
First we need to get a HEAP address leak we can get this by executing R2D2 option:
1 | def r2d2(n): |
Next step is to tamper the size of the wilderness with pstartships via memset:
1 | # OVERFLOW TOP_CHUNK |
The top_chunk before overflow:
The top_chunk after overflow:
Now the place we want to write is at FILE global string pointer we can do this by going to the darkside(cm2_dark_side):
1 | # LEAK FILE PTR |
Now we calculate the evilsize required to write at FILE can be done with FILE-TOP_CHUNK-8*4:
1 | HEAP = HEAP_L-0x1380 # HEAPBASE |
To calculate WILD_OFFSET you can put a break point right before malloc inside buildDeathStar and calculate with this:
Write sh into file:
1 | r.sendlineafter('Your choice: ', '4') |
The full exploit:
1 | from pwn import * |
Running it:
1 | $ python CloneWarS.py REMOTE |