In this video we're going to cover phase one of the House of Orange.
This phase can sometimes be omitted if an appropriate bug is present
but I think it helps showcase the beauty of this technique
so we'll learn the full House of Orange as it was originally published.
Since we're starting at phase one, we might as well get acquainted with our vulnerable binary, so
navigate to the 'house_of_orange' directory, in which you'll find the house_of_orange binary and matching
pwntools template.
Checksec reveals that the binary was compiled with full RELRO, stack canaries, NX and is position-
independent.
It's also linked against GLIBC version 2.23, the earliest version of GLIBC
we'll be dealing with during this course.
So phase one depends, of course, on what sort of bug this binary has,
so let's load it into GDB,
run it and find out what we're dealing with.
There's both a libc and heap leak to allow us to focus on the technique, and we're presented with some
slightly different options than normal.
Option 1 allows us to request up to two so-called "small" chunks
and when we select that option, we're not prompted for a size,
the binary has already chosen it for us.
Breaking in GDB and inspecting the heap with 'vis' shows that this binary is requesting 0x20-sized
chunks
every time we pick option 1.
Now there's nothing special about 0x20-sized chunks, and we'll eventually see that these chunk
sizes aren't very important at all.
Option 1 works like this to streamline how we interact with the binary so we don't get too far off
course when considering what to do next. Production binaries may give a user more or less control over
the size and number of requests to malloc.
Option 2 requests a single "large" chunk, large in this case is also a relative term
but we can find out what the program considers a large chunk by breaking in GDB
and this time issuing the 'heap' command. 'heap' prints a summary of the default heap, although it can be
used to print other heaps too,
and it's great for getting an overview of a heap with large chunks on it,
something that 'vis' isn't well suited for.
We can see from its output that after the 0x20 chunks, a 0xfd0-sized chunk has been allocated,
which is just short of a page on this architecture.
Again, there's nothing special about this size of chunk,
it's just large enough to trigger a specific malloc behavior that we'll be introduced to shortly.
Lastly, option 3 allows us to edit specifically the first small chunk that we allocated, whether
that was before or after the large chunk.
I'll write
some garbage data into it, then inspecting the heap shows us that that data was written into the first small
chunk's user data.
You'll notice there's no 'target' option this time.
Remember that the House of Orange is a technique designed to drop a shell,
so that will be our aim throughout.
At this point, I want you to pause the video, get familiar with the house_of_orange binary and see
if you can find the bug.
Once you've done that, return here and we'll start considering our options for exploiting this binary.
If you found the bug
good work.
If not, I can tell you that this binary has a heap overflow bug in the 'edit' option.
If I pick 'edit' and send too much data for a 0x20-sized chunk to hold, when I break in GDB and inspect
the heap with 'dq' and our 'mp_' trick, we can see that my data has overflowed from that first 0x20
chunk into the succeeding chunk.
This overflow is pretty egregious, clocking in at 216 bytes.
We could certainly argue over realism here and different bugs might allow us to achieve the same result,
but this bug simplifies things to help us learn the technique.
So let's consider how we might leverage this heap overflow.
The way I've used it here allows me to overwrite the size fields of the other two chunks on this heap.
But that's not much use if we can't convince malloc to operate on them somehow
once we've tampered with their size fields, and the only thing malloc can do to an allocated chunk is
free it.
Conspicuous by its absence in this program is a 'free' option, which begs the question: if phase two
of the House of Orange is an unsortedbin attack, which requires a free unsorted chunk, how are we
going to leverage the unsortedbin attack if we're unable to free chunks in the first place?
Well, that's what phase one is going to answer for us.
We need to somehow leverage our overflow to generate a free chunk using only calls to the malloc() function.
And we know overwriting other chunk's size fields won't help,
our only other option is the top chunk size field.
In the House of Force we overwrote the top chunk size field with a large value,
that's no use here because we're restricted to three comparably small chunks.
But what would happen if we replaced it with a small value?
To answer this, we need to understand what malloc does when it receives a request too large to be
serviced from an arena's bins or top chunk.
First, it will attempt to acquire enough memory to accommodate the request.
Normal arenas achieve this by changing the permissions on pre-mapped heap memory,
but the main arena does things differently.
It invokes the brk() syscall to request more memory from the kernel, malloc then uses the top chunk
size field to determine whether the new memory is contiguous to the end of the heap.
If it is, malloc simply extends the top chunk into the new memory and makes the allocation from the
now larger top chunk.
But let's consider what would happen if this occurred after we had overwritten the top chunk size
field with a small value.
When a large request arrives, malloc does the same as before, requesting more memory via the brk
syscall,
but now it appears to malloc as though the new memory doesn't border the end of the heap. When this
happens
malloc assumes that the kernel was unable to map contiguous memory for this heap,
perhaps it ran out of space,
and since the new memory is larger, malloc starts a new heap there
instead. It does this by setting the main arena's top chunk pointer to the new memory,
and so as not to waste space,
it frees the old top chunk.
So if we leverage the overflow bug to shrink the top chunk, then make a request larger than the top
chunk,
malloc will generate a free chunk in the path of our overflow, which we can use for our unsortedbin attack,
all without the process ever calling the free() function.
And this is phase one of the House of Orange.
So now that we have a plan, let's make a copy of the template script and open it in our editor.
Scrolling to the example shows us the three helper functions for this binary; small_malloc() to request
those 0x20-sized chunks,
large_malloc() to request a chunk
just short of a page size, and edit() to write data into the first small chunk.
Start by removing the call to large_malloc(),
we'll keep the first small_malloc() call since that's the chunk we're going to overflow from
and the edit() call, which we'll use to trigger the overflow.
The edit() call already fills the first small chunk's user data,
so anything we add after that will overflow into the top chunk size field.
Let's set the top chunk to a 0x90-sized chunk.
That way, when it's freed, it will get linked into the unsortedbin. Then confirm
this worked by running our script with the debugging options.
Let's switch to the binary to make the large request, which will trigger the top chunk extension code.
Unfortunately, we encounter an error message.
This one is actually the result of a failed assert rather than a malloc_printerr() check,
but the result is the same:
the process stops.
Switch back to GDB so we can reveal the source of this error.
Use the 'frame' command to switch to the sysmalloc() frame.
It's this function that's responsible for requesting memory from the kernel on malloc's behalf.
The 'context code' command shows us the assert statement that's causing problems,
we're running afoul of these two lines
specifically. The first checks whether the top chunk's prev_inuse flag is set, since the top chunk
should always have a set
prev_inuse bit
it's an error to have it cleared.
The second line is checking whether the top chunk ends on a page boundary, which it currently does
not.
So we need to return to our editor and make these changes.
Let's start by setting the top chunk size field to a value that aligns the end of the top chunk to
a page boundary.
We can do this by subtracting the space we've already allocated, a 0x20-sized chunk, from the size
of a page on this architecture.
And don't forget, we also need to add 1 to set the prev_inuse bit.
Those changes should satisfy the assert statement in sysmalloc().
So now we can add a call to the large_malloc() helper function to trigger the top chunk extension code.
Run your script with the debugging options
and this time the program continues to run.
Break in GDB and invoke the 'heap' command, which reveals that there's now a free unsorted chunk directly
in the path of our overflow from that 0x20 chunk.
For the curious, the 'vis' command shows malloc's use of so-called "fencepost" chunks.
These are illegally sized 0x10 chunks, which are placed at the end of a heap that no longer has
a top chunk.
Their purpose is to allow malloc to safely look forward two chunks when determining forward consolidation
candidates without accidentally reading unmapped memory.
Now that phase one is complete
we're in a position to overwrite the old top chunk's bk, setting up our unsortedbin attack.
But how do we tie phases two and three together?
What's the link between an unsortedbin attack and file stream exploitation?
Well, we learned about how vtable hijacking features in file stream exploits,
so what if we targeted one of the standard I/O vtable pointers?
Our unsortedbin attack would replace that vtable pointer with the address of the main arena's unsorted
bin.
Then the next time a standard I/O member function was called, the main arena would be treated as a
vtable.
This isn't particularly helpful,
the main arena consists primarily of empty linked lists
at this point, and attempting to execute those addresses would just lead to a general protection fault
as we tried to execute memory marked as non-executable.
Even if we were to populate some of those bins with pointers to heap memory by sorting chunks into them,
heaps are no more executable than arenas.
So what other options do we have?
Well, what about the _IO_list_all pointer? Remember that _IO_list_all is the head of a list of every
file stream
this process has open and it's used when GLIBC
needs to perform an operation on all open file streams, typically cleanup procedures.
One of those cleanup procedures is performed when a program exits, either via the GLIBC exit() function
or by returning from its main() function.
Let's say we target the _IO_list_all pointer with our unsortedbin attack, replacing it with
a pointer into the main arena,
then we exit the program. As the program exits and GLIBC cleans up, it will attempt to flush the
buffers of any open file streams.
It does this by iterating over every file stream in the _IO_list_all list,
determining whether its buffers require flushing,
and if so, calling a specific member function named 'overflow' on that file stream,
nothing to do with this sort of overflows
we've been dealing with mind you.
This is a more interesting situation because now the main arena is being treated as a file stream,
and this gives us the opportunity to point one of that file stream's fields at heap memory that we
control.
We can do this by sorting the old top chunk into a bin of our choosing,
we control its size field via the overflow bug, after all.
And if we request a non-exact fitting chunk, it will get sorted rather than allocated.
But our unsortedbin attack will still work because as we learned earlier, sorting still involves
a partial unlink.
Things are getting complicated, so let's return to our editor and make some changes so we can visualize
what we're trying to achieve.
We'll add a line that sets up our unsortedbin attack against the
_IO_list_all pointer. Using the edit() helper function we'll overflow a second time into the old top chunk, setting
its size field to 0x21.
We'll keep writing,
setting the chunk's fd to any value
and its bk to the address of _IO_list_all minus 16.
By doing this, we've created a situation where there's a 0x20-sized chunk in the unsortedbin
with a bk that points near the _IO_list_all pointer.
Lastly, we'll add a call to the small_malloc() helper function to make a request for a 0x20
sized chunk.
This will cause malloc to allocate the old top chunk,
since we set its size to 0x20, triggering our unsortedbin attack against _IO_list_all.
I know I said we'd sort the old top chunk rather than allocate it, but let's take this one step at
a time.
Once you've added those lines, run your script with the debugging options.
Break in GDB and 'print _IO_list_all'
to confirm that our unsortedbin attack was successful.
We can see from the output that it now points into the main arena.
Now we're by no means done yet,
but let's exit the program anyway and see what GLIBC makes of its new
_IO_list_all pointer. Continue in GDB,
switch to the program window and select the 'quit' option.
Then switch back to your GDB window.
The pwntools scripts for this technique set a breakpoint on the GLIBC function responsible for flushing
file stream buffers during cleanup,
and that's the breakpoint we've hit here.
The backtrace panel shows us that this function is called
_IO_flush_all_lockp() and that it was called from _IO_cleanup().
If we continue, the process, unsurprisingly, segfaults. And shown in the source panel is the check that
_IO_flush_all_lockp() uses to determine whether a file stream requires flushing.
The reason for the segfault is irrelevant,
after all this function is trying to treat the main arena like a file stream.
What is important is how this check works.
The line containing _IO_OVERFLOW in all caps is the one calling the 'overflow' member function.
The first argument, 'fp', represents the file stream overflow() is being called from.
There are two checks prior to this line,
each one of which must pass in order for overflow() to be called.
The first check passes if the '_mode' field of the file stream is less than or equal to zero and its
_IO_write_ptr
field is larger than its _IO_write_base field.
Essentially, this is checking if anything remains to be written in this file stream's buffer before
the program exits.
I'll leave the second check for you to decipher on your own,
but note the test for _mode larger than zero.
If we dereference _IO_list_all with 'print', we can see things from the _IO_flush_all_lockp() function's
perspective: the file stream that it thinks overlaps the main arena has a negative _mode field, but
its _IO_write_base and ptr fields are identical, meaning it will fail the first check due to
the latter
and the second check due to the former.
In this case _IO_flush_all_lockp()
won't call this file stream's overflow() function and will instead move on to the next stream via
the current stream's _chain pointer.
We can see that right now
_chain points back into the main arena,
and with a little GDB-fu, we can print the address of the _chain member.
If we combine this with 'dq' to dump 26 quadwords of the main arena, we can determine which part
of the main arena is being treated as the _chain pointer.
If the 'dq' output looks slightly odd, that's because earlier versions of GLIBC, like this one,
2.23, have one less quadword at the start of their arenas.
You can take a look at the 'Arena Layout' diagram in the PDF to find out which one.
Nevertheless, using that diagram, we can determine that this is the top chunk pointer, making these
the unsortedbin fd and bk respectively,
and then we can just count the smallbin bk's in increments of 16 bytes: 0x20, 0x30, 0x40
0x50, 0x60.
The bk of the 0x60 smallbin is what's being treated as this rogue file stream's _chain pointer.
So if we change the size field of the old top chunk from 0x21 to 0x61 before our unsorted
bin attack, the old top chunk will be sorted into the 0x60 smallbin rather than being allocated,
and end up as the _chain pointer of the rogue file stream overlapping the main arena.
This allows us to forge our own fake file stream on the heap, providing our own vtable pointer and
vtable entries. Sound complicated?
Don't worry,
the House of Orange is one of the more involved heap exploitation techniques,
so don't hesitate to rewind the video and revisit the material until you're comfortable with it.
Once you are, let's continue. Download the second House of Orange template script from this lecture's
resources,
the contents of which are on my screen now.
We're going to use this to avoid too much typing,
this technique is difficult enough as it is. The first part of this script does exactly what we did earlier,
leverage a heap overflow bug to shrink the top chunk, then make a request that it couldn't service
to generate a free unsorted chunk in the path of our overflow.
The second part uses the overflow again to set up both our unsortedbin attack and forge a counterfeit
file stream on the heap.
Remember that you can use the 'ptype' or 'dt' commands in pwndbg to find out the offsets of each field
when you're building structs like this.
Notice that the old top chunk's size is being set to 0x61 to ensure it gets sorted into the
0x60 smallbin and its bk is being overwritten with the address of _IO_list_all minus 16,
the target of our unsortedbin attack.
The remaining fields belong to the file stream overlapping the old top chunk that we'll finish crafting
now.
When our exploit triggers the _IO_flush_all_lockp() function, it will follow the corrupt _IO_list_all
pointer to the main arena, which will fail to buffer flushing checks.
It will then move on to the next file stream by following the _chain pointer, which overlaps the
0x60 smallbin bk.
The old top chunk will have been sorted into this position during the unsortedbin attack,
so _IO_flush_all_lockp() will follow the _chain pointer to the heap where our fake file stream resides,
perform the buffer flushing checks on it and hopefully call its overflow() member function.
All we have to do is ensure that our counterfeit file stream passes those buffer flushing checks and
provide a vtable pointer to a vtable in which the overflow() entry is populated by the function we
want to execute.
If you remember the buffer flushing checks, then pause the video briefly and modify the 'write_base',
'write_ptr' and 'mode' variables to ensure they pass.
If not, then just follow along with me now.
The 'write_base' field must be less than the 'write_ptr' field,
so let's set them to one and two, respectively.
The 'mode' field must be less than or equal to zero,
so let's set it to zero.
Now the buffer flushing checks will pass and the overflow() entry in this file stream's vtable will
be called. The vtable pointer is already set up,
it just needs to be a pointer to controlled memory.
In this case I've saved a little space by overlapping the fake vtable with an unused part of our
file stream,
but it could theoretically be placed anywhere.
The contents of the 'overflow' variable will form the overflow() entry in our fake vtable.
Before we try to use a one-gadget here,
recall that when the overflow() function is called, its first argument is the address of the file stream
it's called from.
That means if we write the string "/bin/sh" into the first quadword of our file stream, which
is where the '_flags' field resides, then point the overflow()
vtable entry at the GLIBC system() function,
the call becomes system("/bin/sh")
and we'll get a shell without the need for a one-gadget.
And all of this sounds great, but there's just one problem.
Remember when we practiced the unsortedbin attack, we made sure that the corrupt chunk was allocated
rather than sorted.
This was because the partial unlink has the side effect of copying our corrupt bk over the unsorted
bin bk, meaning that if the corrupt chunk is sorted, malloc will continue searching the unsorted
bin, following the corrupt bk and inevitably failing the unsortedbin's
size sanity check at the destination.
This would result in one of the dreaded malloc_printerr() calls, aborting the process.
Nonetheless, let's run our script with the debugging options to confirm that everything is in place
and we can address that issue shortly.
GDB stops, and looking at the backtrace panel, we see the familiar pattern of malloc_printerr() followed
by abort().
But look at the current frame,
we've actually stopped in _IO_flush_all_lockp(), the exact function we wanted to trigger.
It turns out that in GLIBC versions prior to 2.27, the abort() function flushes file streams,
because what's more sensible than operating on data after detecting corrupt data?
We know what _IO_flush_all_lockp()
is going to do,
so let's follow the chain.
Dereferencing _IO_list_all shows us what we saw earlier, a file stream overlapping the main arena
that fails the buffer flushing checks.
This time, however, the _chain member points to the heap rather than back into the main arena.
We can use some GDB-fu to follow the _chain pointer and see what will be made of our counterfeit file
stream.
Note our _IO_write_base and _ptr fields, set to one and two respectively, a _mode field of zero
and an overflow() vtable entry
of system().
Lastly, the _flags field at the start of the struct contains the string
"/bin/sh".
Let GDB continue
and even though it will complain at us, if we switch to the program window, we've snatched a shell
from the jaws of abort().
Notice how GLIBC had even got as far as printing an error message and a backtrace before the shell
dropped,
not bad for just three calls to malloc().
And that is the House of Orange.
To summarize, we leveraged an overflow into the top chunk to shrink its size field to a small, page-
aligned value.
Then we made a request too large to be serviced from the newly shrunken top chunk.
This triggered malloc's top chunk extension code, which requests memory from the kernel
and if that memory is contiguous to the top chunk, extends the top chunk into it.
However, because we had tampered with the top chunk size field, from malloc's point of view the newly
acquired memory wasn't contiguous to the top chunk,
so it was instead used as the new top chunk, whilst
the old top chunk was freed into the unsortedbin.
This left us with an unsorted chunk in the path of our overflow.
We leveraged the overflow a second time to overwrite the unsorted chunk's backward pointer, setting up
an unsortedbin attack against the _IO_list_all pointer.
We also took this opportunity to forge a file stream on the heap, overlapping the old top chunk and
set the size of the old top chunk to 0x60.
Then we made a third and final request for a chunk that wasn't 0x60-sized. malloc, finding
neither fast nor small chunks to service our request,
began searching the unsortedbin, where it came across the old top chunk. But it wasn't an exact fit,
so malloc sorted it into the 0x60 smallbin, triggering our unsortedbin attack in the process.
malloc continued to search the unsortedbin, following a now corrupt bk to a nonexistent chunk which
failed a size sanity check, causing GLIBC to begin its abort procedure.
Before sending our process the abort signal, GLIBC attempted to clean up its open file streams.
It began by following the corrupt _IO_list_all pointer to the main arena,
the pointer having been subject to our unsortedbin attack and now holding the address of the main arena's
unsortedbin.
When treated as a file stream, the main arena failed the checks that would trigger a call to its overflow()
function, causing the cleanup function
_IO_flush_all_lockp() to move on to the next file stream in the list via its _chain pointer.
The _chain pointer overlapped the 0x60 smallbin bk, which was where the call to malloc
that started this whole series of events sorted the old top chunk and where our fake file stream resided.
We had crafted our fake file stream such that it would pass the checks in order to have its overflow()
member function called,
at which point our vtable pointer was followed to our fake vtable in which we'd replaced the overflow()
entry with the address of the system()
function.
The overflow() function is normally called with a pointer to its file stream as the first argument,
and we'd replaced the _flags field of our file stream with the string
"/bin/sh".
This resulted in the call to overflow() being interpreted as a call to system("/bin/sh"), dropping a shell
and bringing us to the end of the House of Orange technique.
Now, I know that was a lot to take on board.
We've learned new malloc internals, we learned about the unsortedbin attack and a little bit about
file stream exploitation.
So don't feel pressured to move on to the next video until you're comfortable with this technique.
There's one last challenge binary you're going to face and you'll need to have a solid understanding
of the House of Orange to tackle it.
So review this video, consult the PDF for a summary of the technique and a recap of the malloc internals
involved.
And as usual, there's a commented solution script available from this lecture's resources.
But before we wrap this video up, there's a few important things to note about the House of Orange.
The first is that phase one can be ignored entirely
if you're able to tamper with existing unsortedbin metadata. We had to leverage the top chunk extension
code because our vulnerable binary wouldn't allow us to free chunks
and there were none present as part of normal operation.
This might not be the case in a production binary, which is much more likely to have free chunks available.
Also related to phase one is the fact that the top chunk extension code we took advantage of only works
like that when the arena in question is the main arena.
Other arenas aren't vulnerable to phase one, as their top chunk size isn't used to determine whether
newly acquired memory is contiguous.
Lastly, and most importantly, notice that for the first time our exploit isn't 100 percent reliable
when run under production conditions.
Sometimes we get a shell,
other times we don't.
The reason behind this lies in how the _mode field of the rogue file stream that overlaps the arena is
formed. _mode is a 32 bit field and ends up overlapping the least-significant bytes of the 0xc0 smallbin
fd.
This results in its most-significant bit, the one that determines whether it's interpreted as a negative
value or not, being subject to ASLR.
If we run our exploit with GDB attached but without ASLR disabled,
'continue' at the
_IO_flush_all_lockp() breakpoint and wait for the process to segfault,
instead of dropping a shell, we can see that the file stream overlapping the main arena passed the buffer
flushing checks this time and _IO_flush_all_lockp() attempted to call its overflow() function, resulting
in a segfault.
Printing the 'fp' variable now confirms the fault occurred whilst processing the file stream overlapping
the main arena and that its _mode field was interpreted as a positive value this time, dropping through
to this second buffer flushing check, which it passed.
The reason it passes that second check is left for you to pursue,
but it's possible to counteract this behavior by sorting chunks into different bins before you finalize
your attack,
we just didn't have the heap control to do that here.
The reason we targeted the _chain pointer rather than the vtable pointer of the file stream overlapping
the main arena is also left as a research project.
Once you're happy with the House of Orange and its limitations, we're going to cover one more malloc
internals concept: remaindering,
before we tackle the last challenge binary to test what you've learned.
I'll see you in the next video.