We're going to learn the malloc internals required to understand our first heap exploitation technique
the House of Force.
Now malloc is just a collection of functions and metadata for the purpose of dishing out those chunks
of heap memory to processes.
And today, we're concerned with just two of those functions:
The malloc() function for requesting heap memory and the free() function for recycling it.
Yes, it is a little confusing to have a function called malloc() used to request heap memory from the
dynamic memory allocator, also called malloc.
But at least it's a simple function.
It takes a single argument: the size, in bytes, of memory that you want, and it returns a pointer to that
amount of memory or slightly more for you to do with as you wish.
And to help us visualize how heap memory is requested via malloc(), we're going to debug a small demo
binary.
Working within your exploit-dev environment, open a terminal and navigate to the HeapLAB folder you
downloaded earlier, then to the house_of_force directory, in which you'll see a collection of binaries
and scripts.
We're going to debug the demo binary under GDB with the command 'gdb demo'.
Once GDB has launched, we'll set a breakpoint on the main() function with the command 'break main', which
can be shortened to 'b main'.
Then we'll run the target binary with 'run' or just 'r'.
Welcome to pwndbg.
There's a lot going on on screen, but right now we're only concerned with the source panel, third
from the bottom. To stop pwndbg printing all this other information
every time we step through the source code, we'll issue the 'set context-sections code' command
which restricts future output to just source code. For the rest of the debugging session
if you want to print the source panel, you can use the 'context' command. In the source panel
we can see that all this program does is request some heap memory using the malloc function several
times, starting with this first request for nine bytes where it saves the return value in a variable
called 'a'.
But before we let the program do that, let's take a look at the heap.
The 'vmmap' command prints a memory map for this process
and from the legend at the top we can see that the heap should be colored blue, but there's no blue
text in the command's output.
This is because malloc won't request memory from the kernel to form a heap until it needs it.
So go ahead and use the 'next' command, which can be shortened to 'n' to execute that first request to
malloc for nine bytes. pwndbg shows us that execution has progressed to line seven
now printed in bold and indicated by this arrow.
Now the output of the 'vmmap' commands shows us that we have a heap at address 0x602000
with a size of 0x21000 bytes.
That's just over one hundred and thirty five thousand bytes.
Note that this video was captured on an Ubuntu 18.04 VM,
if you're using a different OS or running on bare metal, these values may differ.
Next, we'll inspect this heap memory using the 'vis_heap_chunks'
command, which can be shortened to 'vis'. If your output looks different to mine
not to worry, you may be using different terminal color settings.
In my case, the region colored cyan is the chunk returned from that first request to malloc for nine
bytes.
This can be confirmed by typing 'print a' or 'p a', which shows us that the return value from that call
points here, to the first quadword of this chunk's
so-called "user data".
The first thing you'll notice is that we got more than nine bytes. Three quadwords at 24 bytes, to
be precise.
It turns out that this is the minimum chunk size that malloc will provide you with. If we use the 'next'
command to request just one byte
'vis' shows us that malloc still returns a minimum sized chunk. Even when the program requests zero bytes
with the 'next' command
'vis' reveals that it was still issued with the minimum chunk size with 24 bytes of user data.
You may be wondering what this field here is, before this first chunk's user data, but still part of
the same chunk.
Well, malloc uses so-called inline metadata, meaning that much like how stack metadata, such as
saved base pointers and return addresses are stored on the stack itself,
malloc stores some of its heap metadata on the heap. Specifically in this case, a size field.
Every chunk has a size field indicating the sum total of bytes that make up the chunk, including the
size field itself.
So in the case of these chunks, 24 bytes of user data plus eight bytes of size field makes 32 or
0x20 bytes.
However, we're seeing a 0x21 value here.
That's because malloc's chunk sizes increase in increments of sixteen bytes.
For example, if we use 'next' to request exactly 24 bytes of heap memory,
'vis' shows us that we were allocated a 0x20-sized chunk with exactly 24 bytes of user data.
Now, if we use 'next' again to request 25 bytes of heap memory, 'vis' indicates that this time we got
the next chunk size up, 0x30
with 40 bytes of user data.
Because the least significant nybble of each size field isn't used to represent chunk size
it's instead used to represent flags.
In this case, the least significant bit is set indicating the prev_inuse
flag. The prev_inuse flag simply means that the previous chunk, the adjacent chunk in lower memory,
is being used by the program.
Since all of these chunks are in use, all their prev_inuse flags are set.
The first chunk's prev_inuse flag will always be set because it has no previous chunk.
If you want to review information about chunk flags, you can find it in the 'Chunks' section of the PDF.
You might also be asking, why doesn't the first chunk start here at the beginning of this memory map,
leaving us with this offset pattern?
Well, technically, it does.
malloc considers chunks to start 16 bytes before their user data.
So from malloc's perspective, the first chunk does start there.
When our program is dealing with chunks, we'll see pointers to a chunk's user data,
but when malloc gets hold of those chunks, we'll start seeing addresses 16 bytes lower.
To add another layer of confusion, malloc chunk shapes, at least when viewed like this, are variable
and depend on a couple of factors, including the size of the chunk, the size of the previous chunk,
and whether either or both of those chunks are free.
We'll get to free chunks later.
Fortunately for our first two techniques, we're only going to see chunks shaped like these.
Lastly, I'd like to draw your attention to this region labeled the top chunk.
You'll recall from the output of our 'vmmap' command earlier that this heap is about one hundred and thirty
five thousand bytes long.
But we're only seeing a couple of hundred bytes here at most.
malloc treats that remaining, as yet unused, heap memory as a single large chunk known as the top chunk.
It's called the top chunk, even though when viewing memory from low to high addresses, it appears
as though it's the bottom chunk
it's the top chunk in terms of residing at the highest address.
Every time we've requested heap memory via the malloc() function, malloc has broken off a piece of this
heap's top chunk, given it a size field, and handed us a pointer to its user data.
And if we keep requesting chunks like this, malloc will keep taking them from the top chunk until there's
not enough memory in the top chunk to service our request.
At which point malloc will attempt to extend the top chunk.
And to keep track of whether a top chunk is large enough to service a request, it also gets a size
field, which we can see here.
And interestingly enough, in many versions of GLIBC, top chunk size fields are not subject to
any integrity checks
and this forms the basis of the House of Force.
You can close GDB with the 'quit' command or ctrl+d.
And in the next video, we'll take a look at our first vulnerable binary and see how we might leverage
what we've learned so far about malloc to exploit it.