
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
- GLIBC is not performant/memory efficient enough

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.
- The next pointer is stored in the unused slot

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 writeConsider 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 generally your exploit or filler
- 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
- RetSpill Exploitation Technique Paper: https://adamdoupe.com/publications/retspill-ccs2023.pdf
- RetSpill Exploitation Technique Demo: https://github.com/sefcom/RetSpill

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