sice_cream
Problem
Just pwn this program and get a flag. Connect with nc 2019shell1.picoctf.com 3972. libc.so.6 ld-2.23.so.
Solution
Stage 1: Analysis
Test program run:
checksec sice_cream
Full RELRO
means theGOT
is not writeable, which rules out quite a few attack vectors. HavingPIE
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 Ghidra (cheat sheet).
main()
function:reintroduce()
function:eat()
function:buy()
function:menu()
function:printfile()
function (at address0x00400cc4
):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 notNULL
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 asname
, with address0x00602040
) contains the user provided name andDAT_00602140
(address0x00602140
) is an array ofcreams
thatbuy()
adds to and eatfree
s.name
is directly abovecreams
in memory.If we
reintroduce()
with aname
of the full length256
, we can leak the first pointer increams
.
Stage 2: Leak LIBC (fastbin_dup_stack)
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 sincereintroduce()
gives us full control of255
bytes (not256
because of\n
). However, we need to be able to free this chunk in order to actually see the libc pointers andfree()
is only called on members of thecreams
array. Therefore, we need to put a pointer to our fake smallbin chunk intocreams
.This can be done because we can control
creams
since it is located directly belowname
in memory. Whilename
is not vulnerable to an overflow, we can write the header of a fake fastbin chunk into it right abovecreams
, thus enabling us to controlcreams
.Therefore, we will write the following data into
creams
:We can get
malloc
to return the chunk at0x602130
by abusing the double free vulnerability. We allocate two chunks, A nd B, by executingbuy()
. Then executefree(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 toA
, which we overwrote to the address of our fake chunk header. We now have the ability to write0x602040
(address of our fake smallbin chunk) intocreams[0]
. More info about the fastbin double free vulnerability at heap-exploitation.dhavalkapil.com (Archive).Now that
creams[0]
points towards our fake smallbin chunk, we can runeat(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 themain_arena
. Therefore, we can leak the libc address by callingreintroduce(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.For more details about this approach visit Feng's "How to heap?" post (Archive) and this demo of the "fastbin_dup_into_stack" exploit from shellphish/how2heap (Archive).
Stage 3: House of Orange
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:
shellphish/how2heap house_of_orange.c, specifically phase 2 of the attack on line 127 (Archive)
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.
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 StackOverflow answer (Archive) and in pabloariasal's blog post (Archive).
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 theFILE
structure used with "stream functions" (fopen()
,fread()
,fclose()
, etc) in glibc. Most of theFILE
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 newFILE
structure is allocated and its pointer returned fromfopen()
, glibc has actually allocated an internal structure calledstruct _IO_FILE_plus
, which containsstruct _IO_FILE
and a pointer tostruct _IO_jump_t
, which in turn contains a list of pointers for all the functions attached to theFILE
. This is its vtable, which, just like C++ vtables, is used whenever any stream function is called with theFILE
. 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.
More info in this post about the FILE structure (Archive), the ctf-wiki FILE introduction (Archive), and the ctf-wiki page about the fake vtable exploit (Archive)
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).
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
. Whenmalloc
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'smain_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 shellphish/how2heap (Archive).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 tosystem('/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 thesmallbin-4
. Since this bin is currently empty the previously unsorted chunk will be the new head therefore, occupying thesmallbin[4]
location in themain_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 thanMINSIZE
(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.
Run the script.py and get the flag
python2 script.py
:
Flag
flag{th3_r3al_questi0n_is_why_1s_libc_2.23_still_4_th1ng_8d25b643}
Last updated