ENPM809V

Kernel Hacking Part 4 - Heap

What we will cover

  • Overview of the Heap
  • Overview of Heap Protections
  • Heap/Other Kinds of Exploitation

Kernel Heap Review

Kernel Heap != User Heap

  • GLBC implementation uses a mechanism of binning
    • Tcache
    • Unsorted
    • Large/Small bins
    • etc.
  • The Linux Kernel uses an entirely different system
    • GLIBC is not performant/memory efficient enough
      • Shuffles chunks between bins
      • Writes a lot of metadata

Slab Allocators

  • Very old! Used in many OS' including Linux, FreeBSD, and Solaris.
  • Various Kinds
    • SLOB (deprecated)
    • SLAB (deprecated)
    • SLUB - Default on Linux

How Slab Allocators Works

  • For a given chunk size, a cache is created
  • Caches hold slabs
  • Slabs consist of one or more pages
  • Slabs consist of a series of slots (for pages)
  • Slots can contain objects
    • Objects are initialized once, and data is reused whenever possible

Free slot

Free slot

Object

Slab

Cache

How Slab Allocators Works

How Slab Allocators Works

  • Each CPU's have their own cache, and there is always a currently active slab.
  • When a new slab is needed, it can be swapped in (or a partial slab can be swapped in too)
  • Partial slabs are tracked via nr_partial or kmem_cache_node
  • Full Slabs are not tracked unless kernel is compiled to be able to

Slots

char *a = kmalloc(256);
char *b = kmalloc(256);

Slots

  • SLAB Memory uses a concept called slots
  • Slots are fixed-width memory that can be allocated or freed
    • No space wasted for metadata
  • Freed objects are pushed onto a singly linked list called the freelist
    • The next pointer is stored in the unused slot
      • Writes the address of the next free slot into the memory of the freed slab.

Slots

char *a = kmalloc(256);
char *b = kmalloc(256);
free(a);

Slots

char *a = kmalloc(256);
char *b = kmalloc(256);
free(a);
free(b); 

Slots

char *a = kmalloc(256);
char *b = kmalloc(256);
free(a); // Head of the freelist
free(b); // head->next of freelist
char *c = kmalloc(256); //We get from the free list

Slab Allocator - Caches

  • If you want to create your own cache, call kmem_cache_create
    • To allocate memory from a specific cache, call kmem_cache_allocate
  • Private caches are used to isolate specific objects

 

  • kmalloc is used for general allocation
    • Will choose from a general purpose cache (already created by the system) based upon requested size
    • Cache naming convention kmalloc-8k where 8k is the slot size
    • Untrusted allocations can be created with the GFP_KERNEL_ACCOUNT flag
      • Allocates from a common cache with the naming convention kmalloc-ck-8k

Kernel Heap Protections

Freelist Randomization

  • By default, the the SLAB allocator puts freed objects onto the freelist in sequential order
    • Why is this bad?

Freelist Randomization

  • When compiling the Linux Kernel, you can set CONFIG_SLAB_FREELIST_RANDOMIZATION to true
    • This will randomize where in the freelist freed objects are placed
  • This makes it a lot harder to know where objects are!
  • Can you think of anything that might be like this?

How Freelist Randomization Works

  • Generate a random sequence of indexes (stored as a linked list)
    • Stored in kmem_cache.random_seq
    • Determines initial freelist order
  • This is still not full-proof
    • The freelist is still a singly linked list.
    • Order is randomized once at slab creation
  • How is this exploitable?
    • Attacker can try to determine the order of the freelist.
    • Once figured out, he can determine the order of nodes

UAF/Freelist Randomization Bypass

  • Lets say we have a freelist, and a pointer to index 0
  • We can now figure out where the next slot is by reading the next pointer
    • We can also play around with allocations/frees to read from the pointer too.
  • Below is a scenario where an attacker can read ptr.

Freelist Hardening

  • Mechanism for protecting the read from the next pointer.
    • Similar to that of tcache safe linking
  • XOR's three values to prevent usage of the next pointer
    • ptr ^ &ptr ^ random cache-specific value

Freelist Hardening

How might an attacker bypass this

  • Lets say ptr = Next
  • Attacker reads the obscured next point (&ptr ^ ptr ^ random)
  • Attacker frees, and there is no new next beyond that (&ptr ^ NULL ^ random)
  • Attacker calculates (&ptr ^ ptr ^ random) ^ (&ptr ^ NULL ^ random) = ptr
  • Now we can encode/decode obscure pointers at will!
    • We can figure out the value of ptr (the next free block) is
    • We can influence what the value of ptr is

Hardened User Copying

  • Two functions primarily for user copying
    • copy_from_user
    • copy_to_user
  • These functions are our primary ways of getting data to and from the user
#define OBJECT 256
kmem_cache_t *cache = kmem_cache_create("my_cache", OBJECT, 0, constructor, 0)
char *valid_obj_ptr = kmem_cache_alloc(cache, flags); 
copy_to_user(userBuff, obj, sizeof(OBJECT)*2) //out of bounds read
copy_from_user(obj, userBuff, sizeof(OBJECT)*2) //out of bounds write

Consider the following:

Hardened User Copying

#define OBJECT 256
kmem_cache_t *cache = kmem_cache_create("my_cache", OBJECT, 0, constructor, 0)
char *valid_obj_ptr = kmem_cache_alloc(cache, flags); 
copy_to_user(userBuff, obj, sizeof(OBJECT)*2) //out of bounds read
copy_from_user(obj, userBuff, sizeof(OBJECT)*2) //out of bounds write

Hardened User Copying

  • Another kernel compilation configuration option can prevent this:
    • CONFIG_HARDENED_USERCOPY
  • Cache keeps an offset and size value for all objects
  • This value can ensure data is only copied/read from the object.

Hardened User Copying

  • Another kernel compilation configuration option can prevent this:
    • CONFIG_HARDENED_USERCOPY
  • Cache keeps an offset and size value for all objects
  • This value can ensure data is only copied/read from the object.

Object To Work With

Hardened User Copying

  • Another kernel compilation configuration option can prevent this:
    • CONFIG_HARDENED_USERCOPY
  • Cache keeps an offset and size value for all objects
  • This value can ensure data is only copied/read from the object.

Object To Work With

Hardened User Copying

  • This is verified by two functions:
    • kmem_cache_create_usercopy
    • __check_heap_object
  • copy_from_user, copy_to_user, and kmem_cache_create handle thsi for us.

Hardened User Copying

KASLR

  • Same as userspace KASLR except it is per-boot time
    • Kernel can only be initialized once!
  • Same way to get the base address! Find a leak, calculate the offset

KASLR - Kernel Oops

  • Kernel Oops - The kernel errors in a non-fatal way
    • Gives error details including register information
    • Found in dmesg outpout
    • Why can this be bad?
  • Prevent this by configuring the to panic on oops
    • Prevents safely recovering from kernel error
    • Downside is that the machine has to reboot
    • Can be avoided by utilizing multiple processes

Kernel Exploitation

Heap Vulnerabilities Still Exist!

  • Out of bounds access
  • Double Free
  • Use After Free
  • Overlapping Allocations

Heap Vulnerabilities Still Exist!

  • Out of bounds access
  • Double Free
  • Use After Free
    • Leaking metadata (freelist)
    • Corrupting metadata (freelist)
    • Creating arbitrary read/write primitives
      • This might be utilizing some other functions like memcpy that doesn't have the protections like copy_to_user and copy_from_user
  • Overlapping Allocations

Heap Spraying

  • A technique used in exploits to facilitate the execution of arbitrary code
  • Perform allocations in chunks of a fixed size with desired user input
    • This is generally your exploit or filler
      • We can use filler if we want to have a higher chance of overwriting allocated objects
  • This is good for random freelist because we are not sure where the target freed object is located

Heap Spraying

Heap Spraying

  • Our objective in this case is to overwrite allocated objects. The targeted object helps us do that
    • Could be the last one in the freelist
    • Other reasons ...
  • We need to fill up the entire slab, so that we can create an exploitable setup
    • Controlling other objects mean we control kernel memory

Heap Spraying

Heap Spraying

High chance of success after filling/allocating the slab & target object via heap spray

General Use Cache Exploitation

  • When calling kmalloc, returns objects from the general-use caches
    • General-use caches hold many different object types of similar sizes
    • Used by all processes
    • Can be created/freed by triggering kernel functionality (generally through syscalls)
  • That doesn't mean they are all exploitable. Some of the desirable properties include
    • Controllable object size (even if it's fixed)
    • Circumventing copy_to_user and copy_from_user checks
    • Controlling object pointers/function pointers

Inter-Proces Communication

  • Linux has various mechanisms for communication between userspace processes
    • Shared Memory
    • Pipes
    • Message Queues
    • and many others.
  • Sometimes, the functionality is a syscall API and don't utilize copy_to_user and copy_from_user
    • Particularly we are going to focus on POSIX Message Queue and Pipe Buffers and how they work in the kernel

POSIX Message Queue API (userspace)

  • mq_open - open message queue
  • mq_send/msgsend - send a message *important*
  • mq_receive/msgrecv - receive a message *important*
    • Triggers kernel function copy_msg which we will see in two slides
  • mq_close - close a message queue
  • mq_notify - send a notification
  • ...

 

man 7 mq_overview

What This Creates In The Kernel

https://elixir.bootlin.com/linux/v6.7.9/source/include/linux/msg.h#L9

What This Creates In The Kernel

  • m_list contains pointers to messages in the queue
  • m_ts determines the size of the message

What This Creates In The Kernel

https://elixir.bootlin.com/linux/v6.7.9/source/ipc/msgutil.c#L46

pipe_buffer

  • pipe_buffer is a struct used for IPC via pipes.
  • What if we overwrite the function pointers in the pipe_buf_operations?
  • Gain total control!

binfmt

  • The Linux Kernel executes many different kinds of binaries
    • ELF
    • Shell Scripts
    • Kernel Modules?????????
  • Yes it can load kernel modules potentially!
    • The last check it does is call modprobe on all unknown images
    • There is a magic that it checks to verify this in the kernel, and stores it as a global variable.
    • If we can take control of this, we can control kernel modules!

binfmt

char *args[] = {"./binary", NULL};
execv(args[0], args);

// Kernel then checks if this is a ELF or shell script

argv[0] = modprobe_path;
argv[1] = "-q";
argv[2] = "--";
argv[3] = module_name;	/* check free_modprobe_argv() */
argv[4] = NULL;

/**
 * modprobe_path is a global variable in the kenrel that can be overwritten!
 * The functionality can be triggered by executing an unknown magic
 * Taking advantage of this unknown magic allows us to load kernel modules
 **/

Putting It All Together

  • Now that we have seen all of this, we now know what we can exploit
  • You might need to put some of these things together to actually craft a true working exploit
    • freelist + Kernel ROP
    • freelist + Kernel ROP + binfmt abuse
    • etc.
  • Take a look at the additional resources, look at CTFs, practice, practice, practice.

Additional Resources

References

  • This has been derived from pwn.college. It has been one of the best resources for me to learn about kernel exploitation
    • Many of the images have been from pwn.college as well.

Kernel Hacking Part 4 - Heap

By Ragnar Security

Kernel Hacking Part 4 - Heap

  • 97