Home Heap Exploitation: Heap introduction and Use-After-Free vulenrability
Post
Cancel

Heap Exploitation: Heap introduction and Use-After-Free vulenrability

This blog post contains what I’ve learned about heap concepts and exploit techniques. I’ve gathered this knowledge after doing thorough research, using the sources mentioned below.

References:

  • https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
  • https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/
  • https://ir0nstone.gitbook.io/notes/types/heap/chunks
  • https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk

Introduction

Heap is an area of memory that can be dynamically allocated. In C, it is often used with functions like malloc (or similar function such as calloc, realloc, …) to request allocated space in heap and using free is used to mark the area as available

Chunks

When requesting space in the heap, the heap manager stores not only the requested size data but also the metadata, which serves to save the space size and state, along with alignment-padding bytes. The combination of metadata and user data is commonly referred to as a “chunk”

Basic chunk allocation strategies

Allocating from freed chunk: As memory gets passed back to free, the heap manager tracks these freed chunks in a series of different linked lists called “bins”. When an allocation request is made, the heap manager searches those bins for a free chunk that’s big enough to service the request. If it finds one, it can remove that chunk from the bin, mark it as “allocated”

Allocating from the top of the heap (called the “top chunk” or “remainder chunk”): the heap manager first looks at the free space at the top chunk to see if there is enough space there. If there is, the heap manager manufactures a new chunk out of this free space.

Off-heap allocations via nmap: When requesting for a large chunk (128KB-512KB on 32-bit systems or 32MB on 64 bit systems), these chunk are allocated off-head using a direct call to mmap. When these call free, the heap manager releases the entire region back to the system via munmap.

Arena and subheap: For single-threaded applications this is the only main arena that the heap manager will ever use. However, as new threads join the process, the heap manager allocates and attaches secondary arenas to each new thread. Unlike the main arena, these secondary arenas allocate chunks from one or more subheap

Allocated chunk structure:

struct malloc_chunk {
	INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk, if it is free. */
	INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
	struct malloc_chunk* fd; /* double links -- used only if this chunk is free. */
	struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
	struct malloc_chunk* fd_nextsize; /* double links -- used only if this chunk is free. */
	struct malloc_chunk* bk_nextsize;
};

typedef struct malloc_chunk* mchunkptr;

The mchunk_prev_size stores the size of previous chunk if previous chunk is free. Otherwise, the heap saves space and mchunk_prev_size is part of the previous chunk’s user data.

The mchunk_size store the chunk size. Since chunk sizes are always 8-byte aligned (or 16-byte aligned on 64-bit systems), the low three bits of the mchunk_size are always zero. These three bits are then used to store the “A”, “M”, and “P” flags.

  • P (PREV_INUSE): 0 when previous chunk is free. If the flag is set to 1, it means that the previous chunk is already in use and not available for allocation.
  • M (IS_MMAPED): is used to indicate that the chunk is a huge allocation that was allocated off-heap via mmap. The other two bits are ignored.
  • A (NON_MAIN_ARENA): 0 for chunks in the main arena. Each thread spawned receives its own arena and for those chunks, this bit is set.

Freed chunk structure

Free chunks store metadata stored in corresponding “free bins” that operate as linked lists. The FD and BK pointer have been added.

BINS

The heap manager needs to keep track of freed chunks so that malloc can reuse them during allocation requests. The heap manager maintains a series of lists called “bins”.

There are 5 type of bins: small bins, large bins, unsorted bin, fast bins and tcache bins. The small, large, and unsorted bins all live together in the same array

1
2
3
4
bin[0] = null
bin[1] = unsorted bin
bin[2 to 63] = small bins
bin[64 to 126] = large bins
Small bins

There are 62 small bins, each small bins stores chunks that are all the same fixed size. Every chunk less than 512 bytes (or than 1024 bytes on 64-bit systems)

Large bins

There are 63 large bins, similar to small bins, but large bins used for chunks over 512 bytes (or 1024 bytes on 64-bit systems).

Unsorted bin

The unsorted bin serves as an optimizing cache layer, taking advantage of the fact that frees often occur in clusters, followed by allocations of chunks with similar sizes. Instead of directly placing newly freed chunks into their appropriate bins, the heap manager first merges them with neighboring chunks and adds them to 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.

Fast bins

Similar to small bins, each fast bin is dedicated to a specific, fixed chunk size. There are a total of 10 fast bins, designed to handle chunks of size 16, 24, 32, 40, 48, 56, 64, 72, 80, and 88 bytes plus chunk metadata.

Fast bins are employed as an optimization layer, and unlike other bins, chunks in fast bins are never merged with their neighboring chunks. This behavior is due to the heap manager not setting the “P” bit at the start of the next chunk, which prevents coalescing and allows for faster and more efficient allocations and deallocations of small-sized chunks.

Tcache bins

Tcache bins is design for multithread programs.

Use-After-Free

Introduction

Use-After-Free (UAF) is a vulnerability that occurs in programming languages lacking memory safety. It happens when a pointer continues to reference a memory block that has been freed, and that same memory block is subsequently allocated again for a different purpose.

Example

For example, in C++, use malloc to allocate a 20-bytes space for storing user roles, the address of this space is stored in the variable ptr1.

Then, use free(ptr1), the heap manager will clear the data in that memory block and keeps track this freed chunk by adding it to a appropriate bin. Importantly, ptr1 still holds the memory address, as it has not been set to NULL.

Later on, allocate another 20-byte memory space to store user website information using malloc, and and the address is stored in the pointer variable ptr2. The heap manager will allocate a chunk from the previous freed chunk. At this point, both ptr1 and ptr2 reference the same memory address.

If you were to use ptr1 again within the program, you would have control over the data by modifying it via ptr2, since they now point to the same memory location.

Debugging

Let test this scenario in C++ program:

#include <iostream>
#include <string.h>

int main(int argc, char **argv)
{
    std::string role_str("normal user");
    std::string website_str("example.local");

    char *role;
    char *website;

    // Allocate memory for role
    role = (char *)malloc(20);
    // Assign role = "normal user"
    strcpy(role, role_str.c_str());

    printf("[+] User role: %s\n", role);

    // Releasing role
    free(role);

    // Allocate memory for website
    website = (char *)malloc(20);
    // Assign role = "normal user"
    strcpy(website, website_str.c_str());

    // Reuse role
    printf("[+] User website: %s\n", website);
    printf("[+] User role: %s", role);
}

Build then program:

1
gcc -g uaf.cpp

The output:

1
2
3
[+] User role: normal user
[+] User website: example.local
[+] User role: example.local

Let debug this vulnerable program using GDB. Begin by initiating the debugging session using GDB:

1
$ gdb ./uaf

Next, set breakpoints at critical lines that will help us analyze the use-after-free vulnerability:

1
2
3
4
5
6
pwndbg> break uaf.cpp:13
Breakpoint 1 at 0x130d: file uaf.cpp, line 13.
pwndbg> break uaf.cpp:20
Breakpoint 2 at 0x1354: file uaf.cpp, line 20.
pwndbg> break uaf.cpp:23
Breakpoint 3 at 0x1360: file uaf.cpp, line 23.

With breakpoints in place, proceed to run the program. Prior to executing line 13, the heap already contains two allocated chunks:

As you use the next command to step through the program, it will move forward without entering any functions. Upon executing the malloc function, a 32-byte (0x20 in hexadecimal) chunk is allocated by the heap manager at memory address 0x55555556b2a0 to store the role data:

Upon reaching the free command, the chunk located at 0x55555556b2a0 is marked as free and is tracked by the tcachebins:

Eventually, the program proceeds until the website variable is allocated:

Notably, the previously freed chunk at 0x55555556b2a0 has been reallocated for the “website” variable. To further investigate, we can inspect the addresses pointed to by the role and website pointers using the p (or print) command:

Remarkably, both the role and website pointers reference the same memory address. This implies that we can manipulate the role variable if it is reused without any change in its memory address. This phenomenon constitutes a use-after-free vulnerability, which we will explore and exploit further in subsequent sections.

Stay tuned as we dig deeper into the exploitation of this vulnerability and learn about potential security implications and preventive measures.

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