The question we're going to answer in this video is "how can we hijack the flow of execution with our
arbitrary write?"
Well, let's think about this in terms of ASLR regions, we could target the stack, which might
hold all manner of useful data like function pointers, not to mention saved return addresses.
However, stack state is very program dependent.
What's more, in this case the stack is subject to ASLR and we haven't been able to leak its address.
So the stack is out.
We could target the binary.
Indeed, we already did this when we overwrote the target data. Tampering with structures like the
PLT or fini array can yield code execution.
For the sake of completeness, the procedure linkage table, or PLT, is a writable array of function
pointers, every function the program calls that resides in an external library like GLIBC is represented
in the PLT.
The reason it remains writable for the program's lifetime is to support something called lazy linking,
whereby a function's address is only resolved when it's first called.
Overwriting PLT entries is a great way to hijack the flow of execution.
For example, this binary uses the printf() function provided by GLIBC, if we overwrite the printf()
PLT entry with the address of some code
we want to execute, the next time the program tries to call printf(), our code will be executed instead.
The fini array is another array of function pointers,
each function in the fini array is called as the program exits.
Overwriting fini array slots allows you to hijack the flow of execution
if you can coerce the program into exiting.
Unfortunately, you'll recall from the output of checksec that this binary was compiled with full
RELRO, meaning that both the PLT and fini array are marked read-only after initialization.
So the binary is also out.
We could target the heap.
One advantage of doing so is that we wouldn't need a heap leak because the distance between the top
chunk and our target would be relative.
It would be great if there were some function pointers or other sensitive data on the heap
but unfortunately, in this case, there's nothing aside from our own data
so the heap is out.
OK, well, we have a libc leak
so what can we target in libc?
libc also has a procedure linkage table alongside two other structures called __exit_funcs and
tls_dtors which behave similarly to the fini array.
But although the GLIBC PLT is writable throughout the program lifetime, it can be difficult to trigger
calls to the functions within it.
What's more, both __exit_funcs and tls_dtors are protected by something called Pointer Guard
which makes reliably tampering with them somewhat difficult.
However, it turns out that there is a heap-specific way to hijack the flow of execution,
I'm talking about the malloc hooks.
Each of malloc's core functions, such as malloc() and free(), has an associated hook which takes the form
of a writable function pointer in GLIBC's data section.
Under normal circumstances these hooks can be used by developers to do things like implement their
own memory allocators or to collect malloc statistics.
We're going to use them to drop a shell.
Make a copy of the exploit we wrote in the last video and open it in your editor,
I'll just edit mine in place.
Scroll to the four lines that we added, we'll leave the first line
that's what sets up our House of Force primitive by overwriting the top chunk sized field with a large
value via the overflow.
This time the distance we want to cover with our large allocation is the difference between the
top chunk and the malloc hook.
We're going to use the malloc hook because it's the only one of malloc's core functions that we can reliably
call in this binary.
We don't need the delta() function anymore because this chunk won't wrap around the VA space like it
did before.
So all we need to do is set the 'distance' variable to libc, which represents the GLIBC shared
object in our script,
.sym to access its symbols,
and the name of the malloc hook's symbol is underscore underscore malloc
underscore hook.
Let's subtract 0x20 from that address, just like we did before
so the allocation stops just short of the malloc hook.
And we want the difference between the malloc hook and the top chunk, which we'll calculate in the same
way we did earlier with the address of the heap, plus 0x20 to account for the 0x20-sized
chunk
we already requested.
Let's see what sort of state this leaves the process in. Comment out the last request for now, then
run your exploit script with the GDB and NOASLR
options. Break in GDB with ctrl+c
and this time, we want to inspect the memory surrounding the malloc hook with 'dq' ampersand for
address of, __malloc_hook minus two, we use minus two instead
of minus 16 because GDB does pointer math depending on the type we pass it.
This null quadword is the malloc hook
and as I mentioned earlier, it's a function pointer.
When it's null like this, it's ignored and calls to the malloc() function work as normal. When it isn't
null
however, calls to malloc() are redirected to the address that it holds.
The 'top_chunk' command reveals that our last request left the top chunk right here, preparing us to
overwrite the malloc hook.
Notice how this time we scrubbed a lot less from the top chunk size because the distance between the
heap and our target was much smaller.
So let's make another request from this top chunk and overwrite the malloc hook. Close GDB with quit
or ctrl+d and return to your editor. Uncomment the last request and replace the string we use
to overwrite the target with the unmapped address
0xdeadbeef using the p64() function.
Run your script with the GDB and NOASLR options,
break and inspect the malloc hook with the command 'print __malloc_
hook.
This confirms that it has indeed been overwritten by our deadbeef value.
The next time the malloc() function is called, execution will be redirected to this unmapped address.
So let's continue,
switch windows to the interactive prompt and make a request for any size.
Usually the binary would prompt us for data
at this point, but it's not responding.
Switch windows back to GDB where we can see that execution has halted at the invalid address
0xdeadbeef.
Great.
This means that by overwriting the malloc hook, then calling malloc(), we can hijack the flow of execution.
The question now becomes, "what do we want to execute?"
Often that's situation dependent,
a denial of service may be all you need,
perhaps you want to leak sensitive information. In our case
we want a shell,
and one way of achieving this is via GLIBC's system() function.
The man page for system() shows that it's a simple function, taking a single argument; a pointer to a
string
that's interpreted as a shell command.
So if we redirect execution to GLIBC's system() function and pass the address of the string "/bin
/sh",
which is the path to one of the shell executables,
this process will give us a shell.
So let's head back to our editor and replace the invalid 0xdeadbeef address that we wrote into the malloc
hook with the address of GLIBC's system() function.
Now, all future calls to malloc() will be redirected to system().
Furthermore, any arguments originally passed to malloc() will also be passed to system().
This means that we can masquerade the address of our "/bin/sh" string as the size of a request.
The only remaining question now is "where can we write or find the string /bin
/sh?".
Well, in our case, we have a heap leak, so one way to achieve this could be to write the command
onto the heap.
Perhaps when we make our large request, not forgetting to null-terminate it, then all we need to do
is record the address of our command, which resides at the start of the second chunk's user data,
so heap plus 0x30 and pass that address to malloc() as if it were a request size.
Now, when we run our script, this time under production conditions without the GDB and NOASLR options,
that last call to malloc() evaluates to system("/bin/sh"), giving us a shell.
So to summarize, we built our House of Force primitive via an overflow by overwriting a heap's top
chunk size field with a large value.
Then we made a large request from that top chunk that spanned the gap between the heap and the malloc
hook in GLIBC's data section.
The next request we made yielded a chunk that overlapped the malloc hook, which we subsequently overwrote
with the address of GLIBC's system() function.
Then we made one final request to malloc with the address of the command
we wanted system() to run masquerading as the size argument.
In this case, the command was "/bin/sh" because we wanted a shell and we'd written it onto the heap.
The call to malloc() became a call to system("/bin/sh"), giving us the shell we wanted.
There is an alternative to writing our command string onto the heap, the string "/bin/sh" actually
exists within the GLIBC shared object.
The reason for this is that functions like system() already do the equivalent of run /bin/sh via the execve
syscall which is then used to run the command passed by the program.
We can use the existing "/bin/sh" string in the libc shared object like so.
And when we run our script again, we get the same result:
a shell.
And that's it for the House of Force technique.
It's quite a strong primitive
and it works against GLIBC versions 2.28 and below. GLIBC 2.29 introduced a
top chunk size check that compares the size of a top chunk against a variable called system_mem, which
mitigates this technique.
The House of Force does require knowledge of the distance between a top chunk and your target, unless
your target resides on the same heap and that you're able to make arbitrarily large requests to malloc.
Once again, if you're unsure of any of the steps we took, there's a commented solution script available
for download from this section's resources.
And don't forget, you can find an overview of the House of Force, along with all the malloc internals
we've covered so far in the PDF.
In the next video, we'll learn the malloc internals required to understand our second heap exploitation
technique, the Fastbin Dup.