sice_cream
Last updated
Was this helpful?
Last updated
Was this helpful?
Just pwn this program and get a flag. Connect with nc 2019shell1.picoctf.com 3972. libc.so.6 ld-2.23.so.
Test program run:
checksec sice_cream
Full RELRO
means the GOT
is not writeable, which rules out quite a few attack vectors. Having PIE
disabled is nice though, and suggests that we might need to use a fixed address of some function or global variable in our exploit.
Reverse the binary file using (). main()
function:
reintroduce()
function:
eat()
function:
buy()
function:
menu()
function:
printfile()
function (at address 0x00400cc4
):
Key takeaways from the source code:
We are restricted to buy
ing pointers of fastbin size (maxing out at 0x58).
Eating a sice cream free
s the respective pointer, but does not NULL
it out. This looks like the beginning of a double free vulnerability. This is similar to "Bug 1" from the "zero_to_hero" writeup.
Global variable DAT_00602040
(refereed to as name
, with address 0x00602040
) contains the user provided name and DAT_00602140
(address 0x00602140
) is an array of creams
that buy()
adds to and eat free
s.
name
is directly above creams
in memory.
If we reintroduce()
with a name
of the full length 256
, we can leak the first pointer in creams
.
We can only allocate fastbin size chunks, which will not produce libc pointers. We need to figure out a way to create a smallbin chunk in order to leak libc. Smallbins have a backwards pointer which points to the main_arena
when the smallbin is at the head of smallbin free list. We can make a fake smallbin chunk since reintroduce()
gives us full control of 255
bytes (not 256
because of \n
). However, we need to be able to free this chunk in order to actually see the libc pointers and free()
is only called on members of the creams
array. Therefore, we need to put a pointer to our fake smallbin chunk into creams
.
This can be done because we can control creams
since it is located directly below name
in memory. While name
is not vulnerable to an overflow, we can write the header of a fake fastbin chunk into it right above creams
, thus enabling us to control creams
.
Therefore, we will write the following data into creams
:
Now that creams[0]
points towards our fake smallbin chunk, we can run eat(0)
to free that chunk, thus placing the chunk in the unsorted bin and writing the location of libc to the backward pointer. If a smallbin is at the head of the list then it will have a backwards pointer which points to the main_arena
. Therefore, we can leak the libc address by calling reintroduce(AAAABBBBCCCCDDD)
. We replace the first 16 bytes with non-null characters (since they were null before) because the libc address begins at byte 17. We can now calculate the critical offsets.
Once we have leaked the address of libc, we're still very limited. We don't have a write-what-where of any kind. Just as importantly, we don't have a stack leak. However, this binary uses libc version 2.23 which is vulnerable to the "House of Orange" exploit.
Sources to understand the "House of Orange" exploit:
This is a complicated exploit, so I recommend reading the above sources since they can probably explain it better than me. Nevertheless, lets go through the process of the "House of Orange" exploit.
Next, we need to understand how it is possible to abuse the FILE
structure by overwriting the vtable. When attacking a process, one interesting target on the heap is the FILE
structure used with "stream functions" (fopen()
, fread()
, fclose()
, etc) in glibc. Most of the FILE
structure is pointers to the various memory buffers used for the stream, flags, etc. What's interesting is that this isn't actually the entire structure. When a new FILE
structure is allocated and its pointer returned from fopen()
, glibc has actually allocated an internal structure called struct _IO_FILE_plus
, which contains struct _IO_FILE
and a pointer to struct _IO_jump_t
, which in turn contains a list of pointers for all the functions attached to the FILE
. This is its vtable, which, just like C++ vtables, is used whenever any stream function is called with the FILE
. So on the heap, we have:
This vtable can be used to gain control of execution flow in a program. The method to do this is known as "fake vtable hijacking." The central idea of this process is to implement the vtable of _IO_FILE_plus by pointing the vtable to the memory we control and placing the function pointer in it.
Vtable hijacking is divided into two types: 1. One is to directly rewrite the function pointer in the vtable, which can be realized by writing at any address. 2. The other is to overwrite the vtable pointer to the memory we control, and then arrange the function pointer in it.
We will be using the second type.
Recall from the "zero_to_hero" writeup "Unsorted bin" section: Instead of immediately putting newly freed chunks onto the correct bin, the heap manager coalesces it with neighbors, and dumps it onto a general unsorted linked list. During malloc, each item on the unsorted bin is checked to see if it "fits" the request. If it does, malloc can use it immediately. If it does not, malloc then puts the chunk into its corresponding small or large bin.
The "House of Orange" exploit relies on a free block in the unsorted bin, which we have. Because of how the attack works, we require that:
The chunk in the unsorted bin is of size 0x60
Its prev_size field is a valid string to pass as parameter 1 of whatever address you give it.
Its back pointer is set to target - 0x10
(see below).
The idea is to overwrite the _IO_list_all
pointer with a fake file pointer, whose _IO_OVERLOW
points to system and whose first 8 bytes are set to '/bin/sh'
, so that calling _IO_OVERFLOW(fp, EOF)
translates to system('/bin/sh')
.
For each allocation, malloc
tries to serve the chunks in the unsorted list first, therefore, iterates over the list. Furthermore, it will sort all non-fitting chunks into the corresponding bins. If we set the size to 0x61 (97) (PREV_INUSE
bit has to be set) and trigger a non-fitting smaller allocation, malloc
will sort the old chunk into the smallbin-4
. Since this bin is currently empty the previously unsorted chunk will be the new head therefore, occupying the smallbin[4]
location in the main_arena
and eventually representing the fake file pointer's fd-ptr.
In addition to sorting, malloc
will also perform certain size checks on them, so after sorting the old top chunk and following the bogus fd pointer to _IO_list_all
, it will check the corresponding size field, detect that the size is smaller than MINSIZE
(size <= 2 * SIZE_SZ
) and finally triggering the abort call that gets our chain rolling.
We also need to satisfy the constraints on the fake file pointer required by the function _IO_flush_all_lockp
:
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
These can be solved with:
Summarized process to trigger a shell:
Request an allocation smaller than 0x58
, so that the unsorted chunk will try to be processed.
The unsorted bin write triggered in the process will set _IO_list_all
to our arena top.
The unsorted check itself will fail and trigger the abort sequence.
The abort sequence will subsequently make the checks discussed in step 9. This convinces the program that our memory region is a valid _IO_FILE
structure.
As the procedure continues, it will call the jump table[3]
function where our gadget is waiting.
Pop a shell and cat
the flag.
flag{th3_r3al_questi0n_is_why_1s_libc_2.23_still_4_th1ng_8d25b643}
We can get malloc
to return the chunk at 0x602130
by abusing the double free vulnerability. We allocate two chunks, A nd B, by executing buy()
. Then execute free(A); free(B); free(A)
. The second chunk (chunk B) is necessary to bypass the "double free or corruption (fasttop)" check. The structure of the fastbin for our chosen size now looks like: head -> A -> B -> A -> tail
. Fastbins are treated as last in, first out (LIFO). We can now poison the forward pointer of chunk A by allocating a chunk of the same size as A and writing our address. Allocating a chunk of the same size as A, will give us the same memory address of A and make the fastbin look like this: head -> B -> A -> tail
. We write the address of our fake chunk, 0x602130
, to A. Next, we allocate another chunk to remove B from the list: head -> A -> tail
. The head points to A
, which we overwrote to the address of our fake chunk header. We now have the ability to write 0x602040
(address of our fake smallbin chunk) into creams[0]
. More info about the fastbin double free vulnerability at ().
For more details about this approach visit () and demo of the "fastbin_dup_into_stack" exploit from ().
, mostly talks about "stage 1", which is not that useful for this challenge ()
, specifically phase 2 of the attack on line 127 ()
()
()
()
()
()
First we need to understand what a "virtual function table" (vtable for short) is. The "virtual function table" or "virtual method table" is a list of method pointers that each class has. It contains pointers to the virtual methods in the class. Each instance of a class has a pointer to the table, which is used when you call a virtual method from the instance. This is because a call to a virtual method should call the method associated with the class of the actual object, not the class of the reference to the object. More info available in this () and in ().
More info in (), the (), and the ()
When we allocate a chunk, the heap manager processes the unsorted bin first. It removes the chunk in unsorted bin whether or not the size matches. However, it does not check the completeness of the linked list. Before the unsorted chunk is removed from the unsorted bin, we can overwrite the backwards pointer with any address minus 0x10
. When malloc
tries to satisfy a request by splitting this free chunk the value at address that BK points to gets overwritten with the address of the unsorted-bin-list in libc's main_arena
. We decide to use this to overwrite _IO_list_all
with the address of unsorted bin. This is known as the "unsorted bin attack". You can find more info from ().
()
Run the and get the flag python2 script.py
: