UNbreakable International - Team Phase
Last updated
Last updated
Participated under the banner of HCS, ranked 2nd out of 341 teams.
Challenge | Category | Points | Solves |
---|---|---|---|
Since strground was "too hard"...
We're given a binary, lets do some footprinting.
running the program, it's a typical heap CRUD challenge
Let's jump to ghidra and decompile~
Choosing the first option creates a chunk of size 64 and read some data to the chunk then assign at a certain offset a pointer to a function.
The second option does roughly the same, only it has a different size, offset, a different array in the global variable to keep track the chunks, different counter and also different print function assigned to it.
Based upon its name (print_current_ctf
and print_ctf_description
) the functions that is assigned when the chunk is created is presumably used to print the chunk's content.
Looking at what option 5 and those functions decompiled verifies this:
from this we can roughly recreate the two structure, namely ctf
and descriptions
as follow:
at this point I also probably mentioned there's a win function within the binary.
and so the goal is clear, the intended solution is to overwrite the function pointer with win()
such that when we call option 5, it will spawn a shell instead.
Note there's some limitation made by the author as shown below,
We're only limited to 3 allocation at a time for each type
We can only create description only if there's the corresponding previously ctf
struct has been made
Next is the spicy stuff, let's take a look how it frees stuff
As you guessed, it's an Use After Free vulnerability. Caused by dangling pointer since the program doesn't nullify the global array variable.
By dynamic analysis I created one chunk of each type and found something very interesting we could take advantage
Notice, even though both structure have a different size and the malloc call specify a different size as well, we ended up getting a same sized chunk (0x50). This is actually an expected behaviour from malloc.
The exploit stuff is quite easy, I have some illustration below to get a bigger picture how it works
First, we allocate two chunk of ctf
and one chunk for description
And then we would free the first and the second chunk IN ORDER. Order matters because of how malloc utilizes cache and recycling of chunks. Our heap condition would look like this:
also note: we're still able to access these chunks since the program doesn't nullify it.
Next, we allocate a new description
, and because of how malloc implements free chunk caching, it will recycle chunk1's segment.
The interesting bit is that since we're allocating a description
struct, we're able to write more up to the offset of the function pointer, allowing us to overwrite it with whatever we want.
notice how description[0]
and ctfs[0]
now occupies or points to the same memory region. And the when we use option 5, it will use either one of the function pointer depending to which struct it dereferencing.
Now we just need to overwrite the function pointer with win()
and call option 5. Once it dereference the ctfs[0]
, it will call win()
instead of print.
Below is the full exploit script:
Flag: CTF{9b93e344611c1fa883es647n5a26130s23124ae5e56bc5005a319a710ae55a92}
Silence speaks louder than words.
We're given a binary, lets do some footprinting.
Next, let's decompile the binary. We only have 2 function of interest notably main()
and wish()
shown below
main()
basically does a quite huge amount of buffer overflow and then returns. In this situation it would be trivial to resort to ret2libc or ret2syscall, however this would prove to be unlikely or at least hard to execute due to 2 reason below:
the program doesn't imports function that can has write functionality like puts()
or printf()
, and so to execute system()
we would find another way to leak addresses.
though the program have syscall;
and pop rdi; ret;
gadget, it does not have any other trivial gadget to control registers. And as the program returns from main, its registers are quite dirty for us to execute syscall as shown below.
wish()
will fill the global variable string
with binsh that will enables us to spawn shell. This heavily emphasizes that the intended solution is to execute execve()
.
since there's no PIE, after ROP-ing to wish()
we can see the content of string
and get its offset to /bin/sh
with the address hardcoded.
Next, I spent quite some time analyzing the gadgets found by ROPgadget to chain the gadgets trying to control RAX, RSI and RDX.
First, I try to think myself how to control RAX since its how we control the syscall number to be executed. I think of giving an input of exactly 0x3b from fgets()
in order to control it, just to realize unlike read()
, gets()
and fgets()
does not return the number of bytes read but the pointer to destination buffer.
And I discovered an unusual (?) opcodes or instruction that doesn't get automatically get decompiled by ghidra just right after wish()
shown below:
With ghidra, we can use the CTRL + D
shortcut to manually decompile it and look what we've got:
And it's where some of some gadgets that for some reason doesn't ROPgadget wasn't able to get. And after some time analyzing the gadgets, I was able to collect and chain these gadgets to get code execution:
With this to chain it, first we need to thought of RAX. On the screenshot above just right after main returns, RAX currently holds a pointer, which is quite large in decimal.
We have a relatively large buffer so we can increment AL to 0x3b to execve to no problem. But we need to set it 0x0 beforehand.
To do that we will use the multiplication gadget, but in order to that, we need RDX to be 0x0 as well, luckily we have just the gadget to do that.
Below is the full exploit script
Flag: CTF{94688cdd453093ee28814f908a81a73595e0cdfcb1ef8bbbb83e0a7cf5af611d}
Gadget | Why? |
---|---|
pop rdi ;
ret ;
control RDI
xor rsi, rsi;
mov rsi, rax;
ret;
set RSI to NULL
xor edx, edx;
xor edi, edi;
nop; nop;
xor r12, r12;
mov r12; rax;
ret;
set RDX to NULL
imul edx ;
shr rdx, 0x3f ;
ret
control RAX, the multiplication result of imul edx ;
will be stored in EAX
inc al;
ret;
control RAX, AL is the 8 bit portion of RAX
heeaap
Binary Exploitation
450 pts
9
not-allowed
Binary Exploitation
432 pts
12