ENPM809V

Dynamic Memory Vulnerabilities

Today's Lesson

  • Objective: Memory Allocator/Heap Vulnerabilities
    • Learn about different areas in memory
    • The Dangers of Heap
    • Memory Allocators and Their Vulnerabilities
    • Exploiting Memory Allocators

Review: ELF Regions of Memory

  • ELF .text: where the code lives
  • ELF .plt: where library function stubs live
  • ELF .got: where pointers to imported symbols live
  • ELF .bss: used for uninitialized global writable data (such as global arrays without initial values)
  • ELF .data: used for pre-initialized global writable data (such as global arrays with initial values)
  • ELF .rodata: used for global read-only data (such as string constants)
  • stack: local variables, temporary storage, call stack metadata

Stack Vs Heap

  • stack: local variables, temporary storage, call stack metadata
  • heap: long-lasting dynamically allocated memory

 

Stack data lasts per function while heap memory is there until freed. 

Kernel Space

Stack

Memory Mapping Region

Heap

BSS Segment

Data Section

Text Segment (ELF)

...

Dynamic Memory Handling

The Basics

  • Dynamic memory is synonymous with the heap of a program
  • C Programmers use functions like malloc and realoc to allocate memory while the program is running.
    • Not possible with the stack.

Caveats

  • All dynamically allocated memory must be freed by the program
  • Any memory not freed is considered leaked
  • Dynamically allocated memory is referenced by pointers
    • Meaning we can access memory that has been freed.

Heap Implementations

  • There are many different implementations that use different algorithms
    • Goal is to either make it faster or more secure
  • They all have some constant concepts:
    • Chunks - Allocations of the heap
    • Bins - Linked lists of free chunks.
      • To make it faster to reallocate memory of the same size
      • Each bin is of the same size

Goal of Heap Implementations

  • Per the original malloc() author here:
  • Maximizing Compatibility
  • Maximizing Portability
  • Minimizing Space
  • Minimizing Time
  • Maximizing Tunability
  • Maximizing Locality
  • Maximizing Error Detection

What is a chunk?

  • A structure containing metadata and data that the user dynamically allocated
    • Multiple chunks form a linked list
      • Each chunk points to the next chunk
      • Chunk structure may vary based on chunk size

What is a chunk?

Common Dynamic Allocators

  • malloc() - allocate some memory
    • Based on ptmalloc
  • free() - free an allocated memory chunk
  • realloc() - change the size of an allocated memory chunk
  • calloc() - allocate and zero-out memory chunk.

 

For Linux, use the GNU Libc implementation

Ideas for Getting Heap Memory

  • mmap()
    • Dynamically allocate pages of memory (4096 bytes)
    • Requires Kernel Involvement
    • System call

Potentially create a dynamic allocation around it, but not ideal.

 

Various Dynamic Allocators Implementations

  • dlmalloc() - First Dynamic allocator release by Doug Lea
  • ptmalloc() - POSIX Thread aware fork of dlmalloc
  • jemalloc() - FreeBSD, FireFox, and Android
  • Segment Heap and NT Heap - Windows
  • Kernel Allocators
    • kmalloc

Let's Focus on ptmalloc

  • ptmalloc() does not use mmap
  • Uses the GNU Allocator in Libc
  • Utilizes the Data Segment to dynamically allocate memory
    • Historic Oddity
  • Managed by the brk and sbrk system calls

 

Note: Only for small allocations. For large allocations, it uses mmap.

 

Dangers of the Heap

Developer Mindset for Building Dynamic Allocators 

  • Allocator Developers focus on speed over security
  • Create optimizations with unintended consequences
    • Security researchers then create exploits
    • After unintended consequences are exploited, patches are applied

Non-Allocator Vulnerabilities

  • Forgetting to free memory
    • DDOS, system crashing, resource exhaustion
  • Forgetting memory was already freed
    • Using freed memory
    • Double Free
  • Heap buffer overflow
  • Corrupting metadata used to trach state
    • tcache poisoning (will get to soon)

How Can This be Vulnerable?

int main()
{
    char *some_data = malloc(256);
    scanf("%s", some_data);
    printf("%s\n", some_data);

    char really_long_string[512] = {'A'}
    strncpy(
    	some_data,
    	really_long_string,
        strnlen(really_long_string, 512)); 
    
    free(some_data); 

    return 0;
}
int main()
{
    char *some_data = malloc(256);
    scanf("%s", some_data);
    printf("%s\n", some_data);
    
    // Make sure it is freed. 
    free(some_data); 
    
    scanf("%s", some_data);
    
    if (strncmp(some_data, PASSWORD, 256))
    {
    	printf("I am authenticated\n"); 
    }
    
    return 0;
}
int main()
{
    char *some_data = malloc(256);
    scanf("%s", some_data);
    printf("%s\n", some_data);
    /* You put code here */ 
    
    /* End of your code */ 
    return 0;
}
int main()
{
    char *some_data = malloc(256);
    scanf("%s", some_data);
    printf("%s\n", some_data);

    free(some_data);    
    free(some_data); 

    return 0;
}

Use After Free

What is it?

  • Anytime memory gets used after it has been freed.
  • Can produced undefined behavior in a program
    • Can use memory that is reallocated
    • Can cause the program to crash
  • Why is this case?
    • The memory in question can be allocated by another program.

Use After Free

int vuln()
{
	char *buffer; 
    int size = 0; 
  	printf("Size of buffer: ");
    scanf("%d", &size);
    
    // Allocating Memory
    buffer = malloc(size); 
   	printf("Input: "); 
    scanf("%s", buffer);
  	printf("%s\n", buffer); 
    
    // What happens here?
    free(buffer);
    printf("Print again: %s\n", buffer); 
    
    return 0;
}
int vuln()
{
	char *buffer; 
    int size = 0; 
  	printf("Size of buffer: ");
    scanf("%d", &size);
    
    // Allocating Memory
    buffer = malloc(size); 
   	printf("Input: "); 
    scanf("%s", buffer);
  	printf("%s\n", buffer); 
    
    // What happens here?
    free(buffer);
    buffer = NULL;
    
    printf("Print again: %s\n", buffer); 
    
    return 0;
}

Heap-Overflow

Heap-Overflow

  • The same as a buffer overflow, except we are doing it in the heap.
  • We use this to manipulate data or pointers in dynamic memory
    • If a function calls a function pointer
    • Changing the value of a variable

Classwork/Demo

Double Free

Double Free

int vuln()
{
	char *buffer; 
    int size = 0; 
  	printf("Size of buffer: ");
    scanf("%d", &size);
    
    // Allocating Memory
    buffer = malloc(size); 
   	printf("Input: "); 
    scanf("%s", buffer);
  	printf("%s\n", buffer); 
    
    // What happens here?
    free(buffer);
    free(buffer);
    
    return 0;
}

Double Free

  • Causes a write what where exploit which allows you to do arbitrary writes
    • Allows us to change registers and important data
    • Especially valuable for binning/heap allocator vulnerabilities

Metadata

Per Chunk Metadata

  • Also generated by ptmalloc
  • Tracks addresses that are allocated
  • malloc(x) returns the memory address, but ptmalloc tracks the size (and previous size)
    • prev_size is not used by tcache
    • the last three bytes of size are flags
      • Bit 0: PREV_IN_USE

      • Bit 1: IS_MMAPPED

      • Bit 2: NON_MAIN_ARENA

unsigned long mchunk_prev_size;

unsigned long mchunk_size;

Dynamically Allocated Memory Chunk

chunk_addr

mem_addr:

prev_size

prev_size

size

a[0]

a[1]

size

b[0]

b[1]

chunk1: int *a = malloc(0x8)

chunk1: int *b = malloc(0x8)

Per Chunk Metadata Overlapping

Note: Only applies if PREV_INUSE flag is set

prev_size

prev_size

size

a[0]

a[1]

size

b[0]

b[1]

chunk1: int *a = malloc(0x8)

chunk1: int *b = malloc(0x8)

Per Chunk Metadata Overlapping

Note: Only applies if PREV_INUSE flag is set

What about being freed?

unsigned long mchunk_prev_size;

unsigned long mchunk_size;

Dynamically Allocated Memory Chunk

b[1]

unsigned long mchunk_prev_size;

unsigned long mchunk_size;

Cache-Specific Metadata

Bins

What are they?

  • Bins are chunks of freed memory of similar size so that they can be quickly be re-used.
  • They are put in as the very last step if all else fails during the free process.

What are the steps for freeing?

  1. If the chunk has the M bit set in the metadata, the allocation was allocated off-heap and should be munmaped.
  2. Otherwise, if the chunk before this one is free, the chunk is merged backwards to create a bigger free chunk.
  3. Similarly, if the chunk after this one is free, the chunk is merged forwards to create a bigger free chunk.
  4. If this potentially-larger chunk borders the “top” of the heap, the whole chunk is absorbed into the end of the heap, rather than stored in a “bin”.
  5. Otherwise, the chunk is marked as free and placed in an appropriate bin.

Implementations

  • Multiple Implementations
    • Small Bins, Large Bins, Fast Bins, Tcache (per-thread)
  • Each have their own benefits and drawbacks
  • Generally implemented as a linked list

Unoptimized Bins

  • Unsorted, Small and Large bins are the unopimized version of the heap-manager binning system.
  • Each contain various sizes of freed chunks
  • Simple implementation, bins shared by all processes/threads

Small/Large Bins

  • Small bins
    • Stores up to 512/1024 byte chunks (32/64 bit systems)
    • 62 total bins 
  • Large Bins
    • Stores chunks greater than 512/1024 bytes (32/64 bit systems)
    • 63 total bins
  • Small/Large bins can be merged together to free up bins

Bin #

prev_size

Chunk Size

FD Pointer

BK Pointer

Unused Space

prev_size

Chunk Size

FD Pointer

BK Pointer

Unused Space

Small/Large Bins

  • This is incredibly useful because we have a pre-ordered
  • Differences:
    • Small bins are binned based on exact size
    • Large bins are based on size range

Bin #

prev_size

Chunk Size = 10

FD Pointer

BK Pointer

Unused Space

prev_size

Chunk Size = 10

FD Pointer

BK Pointer

Unused Space

Unsorted Bin

  • Optimization by the heap manager
  • Based on the observation that many frees are followed by allocations of similar sizes
  • Saves time from putting chunks in the correct bin

Bin #

prev_size

Chunk Size = 10

FD Pointer

BK Pointer

Unused Space

prev_size

Chunk Size = 64

FD Pointer

BK Pointer

Unused Space

Fast Bins

  • A further optimization of the previous binning strategies
  • Keeps recently released small chunks on a fast turnaround queue. 
    • Only 10 such fast bins of sizes 16 through 80 (incrementing by 8). 
  • Bins are never merged by their neighbors
  • Cons: Fast-Bins are never truly freed/merged with other bins

Bin #

prev_size

Chunk Size = 10

FD Pointer

BK Pointer

Unused Space

prev_size

Chunk Size = 64

FD Pointer

BK Pointer

Unused Space

Credit to pwn.college

Credit to pwn.college

More Informatioon

tcache

What is tacache?

  • Definition: Thread-Local-Caching
  • Used in ptmalloc to speed up small-repeated allocations in a single thread
  • Implemented in a singly-linked list
    • Each thread has its own list of headers containing metadata about the memory regions

What is tacache?

typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
typedef struct tcache_entry
{
  struct tcache_entry *next;
  struct tcache_perthread_struct *key;
} tcache_entry;

Example In Use

a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);

free(b);
free(a);
free(c);
free(d);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 0

count_32 = 0

entry_16 = NULL

entry_32 = NULL

tcache entry B

next: NULL

Key: NULL

tcache entry C

next: NULL

Key: NULL

tcache entry A

next: NULL

Key: NULL

tcache entry D

next: NULL

Key: NULL

Example In Use

a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);

free(b);
free(a);
free(c);
free(d);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 1

count_32 = 0

entry_16 = B

entry_32 = NULL

tcache entry B

next: NULL

Key: Mike

tcache entry C

next: NULL

Key: NULL

tcache entry A

next: NULL

Key: NULL

tcache entry D

next: NULL

Key: NULL

Example In Use

a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);

free(b);
free(a);
free(c);
free(d);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 2

count_32 = 0

entry_16 = B

entry_32 = NULL

tcache entry B

next: A

Key: Mike

tcache entry C

next: NULL

Key: NULL

tcache entry A

next: NULL

Key: Mike

tcache entry D

next: NULL

Key: NULL

Example In Use

a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);

free(b);
free(a);
free(c);
free(d);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 2

count_32 = 1

entry_16 = B

entry_32 = C

tcache entry B

next: A

Key: Mike

tcache entry C

next: NULL

Key: Mike

tcache entry A

next: NULL

Key: Mike

tcache entry D

next: NULL

Key: NULL

Example In Use

a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);

free(b);
free(a);
free(c);
free(d);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 2

count_32 = 2

entry_16 = B

entry_32 = C

tcache entry B

next: A

Key: Mike

tcache entry C

next: D

Key: Mike

tcache entry A

next: NULL

Key: Mike

tcache entry D

next: NULL

Key: Mike

What is actually happening on Free?

  1. tcache selects the right bin to put the freed memory
    • Based on size
  2. Ensures that the entry hasn't been freed already
    • Prevent double frees
  3. Put the freed memory into it's proper list
  4. Record it inside of the tcache_perthread_struct

How do we retrieve a cached memory chunk?

  1. Choose the bin based on size of chunk requested
  2. Check to see if there areany entries
  3. If there are entries, reuse them 

What is not done?

  • Clearing all sensitive pointers (only key is cleared)
  • Checking if the next address makes sense

Example In Use

a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 2

count_32 = 2

entry_16 = B

entry_32 = C

tcache entry B

next: A

Key: Mike

tcache entry C

next: D

Key: Mike

tcache entry A

next: NULL

Key: Mike

tcache entry D

next: NULL

Key: Mike

Example In Use

a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 1

count_32 = 2

entry_16 = A

entry_32 = C

tcache entry B

next: NULL

Key: NULL

tcache entry C

next: D

Key: Mike

tcache entry A

next: NULL

Key: Mike

tcache entry D

next: NULL

Key: Mike

Example In Use

a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 0

count_32 = 2

entry_16 = NULL

entry_32 = C

tcache entry B

next: NULL

Key: NULL

tcache entry C

next: D

Key: Mike

tcache entry A

next: NULL

Key: NULL

tcache entry D

next: NULL

Key: Mike

Example In Use

a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 0

count_32 = 1

entry_16 = NULL

entry_32 = D

tcache entry B

next: NULL

Key: NULL

tcache entry C

next: NULL

Key: NULL

tcache entry A

next: NULL

Key: NULL

tcache entry D

next: NULL

Key: Mike

Example In Use

a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);

tcache_perthread_struct -- Mike

tcache_perthread_struct -- Mike

counts: 

entries:

count_16 = 0

count_32 = 0

entry_16 = NULL

entry_32 = NULL

tcache entry B

next: NULL

Key: NULL

tcache entry C

next: NULL

Key: NULL

tcache entry A

next: NULL

Key: NULL

tcache entry D

next: NULL

Key: NULL

Exploiting Bins

Bin Exploitation

  • Bin Exploitation come and go based on version of Libc
    • The one for the homework is for < version LIBC 2.29
  • They are based on the core heap-based vulnerabilities
    • Double Free
    • Use After Free
    • Etc.

Consolidation/Moving Between Bins

  • A correctly sized malloc can change which bins a chunk is located
    • Cause Consolidation by moving from unsorted to small/large bin, clearing fast bins
    • Move from small bin into tcache
    • etc.

Fake Chunks

Carefully creating a region of memory with heap-metadata in it can trick the heap allocator to think it's another chunk of memory!

  • Depends on specifics of the program (and sometimes LIBC too)
  • Can result in broken heap

Fake Chunks

What are some things we might need to include in the metadata structure?

  • Might need a prev chunk pointer
  • Might need a prev_size value
  • All depends on what the implementation of the meatdata is like

Homework

Memory Corruption in the Heap

  • Various in-class heap-based exploitation
    • Overwriting Heap Data
    • Bin poisoning

Additional Resources