Now, let’s discuss various techniques used to leak libc, ELF and heap addresses in a typical heap exploitation CTF challenge.

calloc

void *calloc(size_t nmemb, size_t size);

According to Linux man pages, The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero. If nmemb or size is 0, then calloc() returns either NULL, or a unique pointer value that can later be successfully passed to free(). One important thing to notice is that unlike malloc, calloc zeroes out the allocated memory.

The following code snippet is present in all versions of libc (malloc.c) till libc-2.35 (including it).

#define IS_MMAPPED 0x2

......

void *__libc_calloc (size_t n, size_t elem_size)
{
	....
	  if (chunk_is_mmapped (p))
    {
      if (__builtin_expect (perturb_byte, 0))
        return memset (mem, 0, sz);

      return mem;
    }
    ....

}

It is clearly visible that if the IS_MMAPPED bit is set, calloc will not zero out the chunk. Thus, a libc address can be leaked by first inserting a chunk into the unsorted bin, setting its IS_MMAPPED flag, allocating it from the unsorted bin and then reading its data.

Note: One impor tant thing to note down is that calloc does not allocate from the tcache.

leaking libc when chunk size cannot be controlled by the user and no heap overflow is present

Consider a case when malloc(0x50) is called by the program itself i.e the size of a chunk cannot be controlled by the attacker. In this case, there’s no tcache. Free some chunks and read data to get a heap leak. Leak the heap base address by subtracting the appropriate offset. Suppose, the program allocates 0x60 sized chunks for you and allows you to write maximum 0x50 bytes into a chunk. In this case, we can create a fake chunk in the data of some valid chunk. Suppose a valid chunk starts at some address, let’s say address_one. In this case, we can create a fake chunk at address_one + 0x10 . Writing 0x50 bytes in this fake chunk will allow us to modify the PREV_SIZE and SIZE field of the next chunk. Allocate one more chunk after the next chunk (to prevent later consolidation with top chunk). Now, write data into the fake chunk such that the SIZE field of the next chunk becomes larger, let’s say 0xc1. This would create a larger chunk, overlapping with the chunk whose SIZE field has just been overwritten. Freeing this chunk will insert it into the unsorted bin and then a libc address can be read if a read after free bug is present.

leaking libc when no UAF is present but user has control over a global buffer and double free is present

In this case, the user can double free to corrupt the fastbin list by inserting the global buffer and later allocate it. Let’s say the user can write a large amount of data( >=0x100) into the global buffer. Allocate that buffer from the free list, create a fake chunk which comes outside the size range of fastbins. After that, you need to create two more chunks after the fake chunk (to set the size fields of next and next to next chunk). For example, a fake chunk would look like:

0x602040:	0x0000000000000000	0x0000000000000091
0x602050:	0x6161616161616161	0x6161616161616161
0x602060:	0x6161616161616161	0x6161616161616161
0x602070:	0x6161616161616161	0x6161616161616161
0x602080:	0x6161616161616161	0x6161616161616161
0x602090:	0x6161616161616161	0x6161616161616161
0x6020a0:	0x6161616161616161	0x6161616161616161
0x6020b0:	0x6161616161616161	0x6161616161616161
0x6020c0:	0x6161616161616161	0x6161616161616161
0x6020d0:	0x0000000000000000	0x0000000000000021
0x6020e0:	0x0000000000000000	0x0000000000000000
0x6020f0:	0x0000000000000000	0x0000000000000021
0x602100:	0x0000000000000000	0x0000000000000000

Freeing the chunk of size 0x90 will insert it into the unsorted bin.