Post

Heap 0x01

Introduction

Before getting into the glibc’s heap, let’s take a really brief look at the stack. Then we can take a look at the heap. On the part 2, we will cover the free function and its bins.

The stack

On modern operating systems, each thread of an application has its own stack. The stack has a fixed size, which is allocated by the operating system and given to the process when it starts. It works LIFO and generally stores local variables, return address pointers, function arguments, etc.

The heap

The heap is a tad different. It deals with dynamically allocated memory. This means that the heap won’t have a fixed size, as it grows as more memory is needed. The way the heap is managed can vary according to the implementation being used: The heap implementation and how it works on Linux can vary from the Windows implementation.

Let’s see what happens when you issue a malloc request (assuming you are using glibc) and there is no heap region mapped yet (usually happens when you’ve just started a program). This is the source code of the program I will be showing:

1
2
3
4
5
6
#include<stdlib.h>  
  
int main(void) {  
	malloc(24);
	return 0;  
}

If you just run it, nothing will happen (as expected). We will be taking a look into it using gdb. I will be using pwndbg, but you can use anything of your choice (or maybe not even anything at all and just gdb by itself), but I really recommend pwndbg.

Putting a breakpoint at the main function and running it, issue a vmmap command:

no_mappings

If you take a closer look, there is no heap mappings yet. Let’s use the n command to execute the malloc function and then take another look at the vmmap:

mappings

As you can see, there is a heap region now! This is because we just issued the malloc function. Cool, right?

With pwndbg, now use the vis command (this is a shortcut for vis_heap_chunks)

vis

This will show all of the chunks located on the heap. There is a big chunk at the start of it (that we won’t get into much details right now), and there is a chunk just after it (colored purple) which has a 0x0000000000000021 written on it. At the end, there is a 0x0000000000020d51 (which we also won’t get into much details right now).

Why not using something like a mmap?

The biggest problem with this is that it won’t give us a flexible size of memory to work with (as it must be a multiple of 4096, a page size).

Also, it requires kernel involvement. This means that it won’t be a blazingly fast operation.

The heap implementation will more or less do one big memory allocation and then distribute it on smaller heap chunks. This will eliminate the previous problems just mentioned, as we won’t need kernel involvement every time and we will have a possibility of distributing smaller chunks when an allocation is issued.

A heap chunk

Metadata and data

Let’s keep it simple by looking at just the purple chunk. This is the chunk we got when a malloc(24) function got called. There is a 0x21 written on it. This is actually the size of the chunk we just got. This counts the first 8 bytes (which will count as the metadata) and the actual chunk data region. 8 bytes (metadata) + 24 bytes (actual data) = 32 bytes (0x20). But what is this metadata stuff for?

The metadata

At the first 8 bytes, there is a 0x21 written on it. Why is it 0x21 instead of 0x20 then? The chunks must not be misaligned, this is not good for a series of reasons. As so, they will always need to be a multiple of 8 (on 32 bits systems) or 16 (on 64 bits systems). See what is happening now? The last 3 bits will always be zero.

Let’s see it at the level of the bits. Imagine you have some weird number like 397: 0b110001101 Now multiply it by 8: 0b110001101000 Multiplying by 8 simply means adding 3 zeros at the end of a binary number.

As it will always be zero, we will just use them for other purposes like saving flags. There is a 0x21 written on it because the last bit is set for a flag that is called prev_inuse. This flag will have some functionalities we will see later on this series.

Let’s keep poking at the metadata with other stuff. Change the malloc(24) on the code to a malloc(8) and issue another vis command:

4

The size on the metadata of our chunk didn’t change, even though we changed the requested size on the malloc function. This is because the heap implementation will use “fixed” chunk sizes for optimizations (specially for when we free the chunk). So we won’t get a 16 bits sized chunk. We will be taking a better look at the free function and the free bins on the next post.

Top chunk

Remember that value at the end of the heap chunks that I said we would talk about later on? Well, let’s take a look at it now !

This value is the top chunk size. When we first issue the malloc function, it will ask the kernel for memory for the heap region. As it turns out, it will ask way more memory than what we are requesting, the biggest reason is that it doesn’t want to ask for it again as it is not that fast. This means we can request more chunks with malloc without asking the kernel for more memory.

Let’s look at it. This is the source code I will be debugging:

1
2
3
4
5
6
7
8
#include<stdlib.h>  
  
int main(void) {  
	void *a = malloc(0x8);
	void *b = malloc(0x8);
	void *c = malloc(0x8);
	return 0;  
}

Put a breakpoint on the main function and run. I recommend compiling the source code with debug information (you can do that by using the -g flag)

Run the first malloc and issue a vis command: 5

This is at the bottom of the chunks.

Ok, everything is as usual and we have already seen that.

Now, issue another malloc by using the n command. Use vis to see what happens: 6 As you can see, we got ourselves another chunk. But if you pay close attention, the last value (the top chunk size) is now 0x20d31 instead of 0x20d51. It actually shrink! Instead of having to request more memory to the kernel, we simply use the top chunk at the end of the region to serve the malloc request.

This process would be different if we had an available chunk on one of the free lists, but we still didn’t cover them, so let’s look at only this case for now.

Nice, right? Let’s do another allocation then:

7

Nothing new around here. Let’s make things a bit more interesting.

Requesting more memory than the top chunk

On the last example, there was a 0x20d11 value at the top chunk. What if we spend all of it?

This is the modified source code:

1
2
3
4
5
6
7
8
9
10
#include<stdlib.h>  
  
int main(void) {  
	void *a = malloc(0x8);  
	void *b = malloc(0x8);  
	void *c = malloc(0x8);  
	void *d = malloc(0x20ce0);  
	void *e = malloc(0x8);  
	return 0;  
}

I added 2 malloc calls at it. Look at the chunks just after the malloc at the d variable.

8

The top chunk is now of size 0x20. Making another malloc request:

9

We can see that the heap grew in size as it got a new top chunk (you can check it by using a vmmap command just after the last allocation and just before it). We will take a better look at the top chunk and how some stuff works around it when we see the House of Force and House of Orange attacks, which will be covered on a future blog post.

Arenas

There is an important concept when we are talking about the heap which is called “arena”. Basically, an arena is a memory region related to each thread and it contains a reference to one or more heaps (each heap can only be at an unique arena). There is this cool image I saw on this amazing blog post. 10

Before using arenas, the way of blocking more than one thread using the same heap was through the use of a mutex. Nowadays, each arena still uses a mutex, but now threads can make operations on the heap without worrying with each other (as they are interacting with other arenas). When there is no way of creating more arenas, some threads will have to share the same arena and we will be having slower performances using mutexes to block threads accessing the same heap.

References

Azeria Labs
https://ir0nstone.gitbook.io/notes/binexp/heap/
The toddler’s introduction to Heap exploitation (Part 1) | by +Ch0pin🕷️ | InfoSec Write-ups
Introduction To GLIBC Heap Exploitation - Max Kamper
Heap Exploitation - Nightmare
Heap exploitation, glibc internals and nifty tricks. - Quarkslab’s blog

This post is licensed under CC BY 4.0 by the author.