KRWX: Kernel Read Write Execute
Introduction
Github project: https://github.com/kiks7/KRWX
During the last few months/year I was studying and approaching the Kernel Exploitation subject and during this journey I developed few tools that assissted me (and currently assist) on better understanding specific topics. Today I want to release my favourine one: KRWX (Kernel Read Write Execute). It is a simple LKM (Linux Kernel Module) that lets you play with kernel memory, allocate and free kernel objects directly from user-land!
What
The main goal of this tool is to use kernel functions from userland (from C code) in order to avoid slower kernel debugging and developing of kernel modules to demostrate specific vulnerabilities (instead, you can emulate them with provided IOCTLs). Also, it can assist the exploitation phase.
These are the project main features (all these features are accessible from a low level user from user-land):
- Read and write into kernel memory
- Read entire blocks of memory
- Arbitrary allocate objects directly calling
kmalloc
- Arbitrary
kfree
objects (and also free arbitrary addresses, if you want) - Allocate/free multiple objects
- Log every
copy_[from|to]_user
/kmalloc
/kfree
called by the KRWX module through hooking (readable fromdmesg
).
Mainly, a more powerful read and write primitive :]
Why
Initially I was writing this module to study the SLUB memory allocator in Linux by allocating, freeing and re-allocating arbitrary chunks easily from an userland process. That automatically leads to study also some exploitation techniques that, with this module, I found a lot easier to understand since you can easily play with kernel memory as you are the god of your system. Then I started to heavily use it for multiple purposes and that’s the reason why I’m sharing it.
How
These are some exported functions:
void* kmalloc(size_t arg_size, gfp_t flags)
-> Allocate a chunk with specificsize
andflag
options.int kfree(void* address)
-> Free arbitrary chunks by theiraddress
(also, you can free arbitrary memory).unsigned long int kread64(void* address)
-> Read 8 bytes of memory ataddress
.int kwrite64(void* address, uint64_t value)
-> Write 8 bytes specified byvalue
intoaddress
.void read_memory(void* start_address, size_t size)
-> Readsize
amount of memory starting fromstart_address
.
And, since one of my favourite hobby is overengineer and I’m lazy enough to do not want to write loops everytime:
void multiple_kmalloc(void** array, uint32_t n_objs, uint32_t size)
-> Allocaten_objs
number of objects with specifiedsize
and return addresses inarray
.void multiple_kfree(void** array, uint64_t to_free[], uint64_t to_free_size)
-> Free specified addresses into_free
fromarray
(to_free_size
is the size of theto_free
array). If you’re interested in the source code feel free to check out the github project.
Examples
Allocate, free and read arbitrary chunks
You can find the full source code in example/01.c
. Here will follows some snippets and a little walkthrough.
First, include the external library and call its initialization function (init_krwx
):
#include "./lib/krwx.h"
int main(){
init_krwx();
[..]
}
So, 10 chunks with size 256 are allocated using multiple_kmalloc
, and the memory of the 7th allocation is read using read_memory
after writing 0x4141414141414141
at its first bytes:
void* chunks[10];
multiple_kmalloc(&chunks, 10, 256);
kwrite64(chunks[7], 0x4141414141414141);
read_memory(chunks[7], 0x10);
The indexes 3, 4 and 7 of the chunks
array are freed using multiple_kfree
:
uint64_t to_free[] = {3, 4, 7};
multiple_kfree(&chunks, &to_free, ( sizeof(to_free) / sizeof(uint64_t) ) );
Once they are freed, new chunks with the same size are allocated and initialized with 0x4343434343434343
, and the memory of the 7h freed chunk is displayed using read_memory
again:
kwrite64(kmalloc(256, _GFP_KERN), 0x4343434343434343);
kwrite64(kmalloc(256, _GFP_KERN), 0x4343434343434343);
kwrite64(kmalloc(256, _GFP_KERN), 0x4343434343434343);
kwrite64(kmalloc(256, _GFP_KERN), 0x4343434343434343);
kwrite64(kmalloc(256, _GFP_KERN), 0x4343434343434343);
read_memory(chunks[7], 0x10);
The result is:
[*] Allocating 10 chunks with size 256
[*] Allocated @0xffffffc00503b900
[*] Allocated @0xffffffc00503b600
[*] Allocated @0xffffffc00503b100
[*] Allocated @0xffffffc00503bc00
[*] Allocated @0xffffffc00503b400
[*] Allocated @0xffffffc00503b000
[*] Allocated @0xffffffc00503b500
[*] Allocated @0xffffffc00503b800
[*] Allocated @0xffffffc00503ba00
[*] Allocated @0xffffffc00503bd00
0xffffffc00503b800: 0x4141414141414141 0xffffffc0001a8928
[*] Freeing @0xffffffc00503bc00
[*] Freeing @0xffffffc00503b400
[*] Freeing @0xffffffc00503b800
0xffffffc00503b800: 0x4343434343434343 0xffffffc0001a8928
With few lines of code has been demostrated how our 7th chunk has been replaced with a new one after it has been freed (the read_memory
targeted the chunks[7]
).
As simple as it is, it has been written for demonstration purposes.
Use-After-Free
To simulate a UAF scenario it’s simple as few lines of code:
void* chunk = kmalloc(<SIZE>, <FLAGS>);
kfree(chunk);
// Allocate your target chunk
// Simulate UAF using k[write|read]64()
For example, if we want to simulate an attack scenario where we want to replace our vulnerable freed chunk with a target object (for example an iovec
struct) we can allocate a chunk with kmalloc
and later kfree
it just before allocating the target structure:
// Allocate the vulnerable object
void* chunk = kmalloc(150, _GFP_KERN);
// Allocate target object
struct iovec iov[10] = {0};
char iov_buf[0x100];
iov[0].iov_base = iov_buf;
iov[0].iov_len = 0x1000;
iov[1].iov_base = iov_buf;
iov[1].iov_len = 0x1337;
int pp[2];
pipe(pp);
if(!fork()){
kfree(chunk); // Freeing the chunk just before allocating the iovec
readv(pp[0], iov, 10); // allocate iovec and blocks (keeping the object in the kernel)
exit(0);
}
sleep(1); // Give time to the child process
read_memory(chunk, 0x40);
Then, with read_memory
we can show the block of memory in our interest and as you can see from the following output, our arbitrary allocated/freed object has been replaced with the target object:
Allocated chunk @0xffffffc0052c5a00
0xffffffc0052c5a00: 0x0000007fd311ff58 0x0000000000001000
0xffffffc0052c5a10: 0x0000007fd311ff58 0x0000000000001337
0xffffffc0052c5a20: 0x0000000000000000 0x0000000000000000
0xffffffc0052c5a30: 0x0000000000000000 0x0000000000000000
Instead of just print the content, you can simulate a UAF read/write using k[read|write]
and play with it.
The full code of this example can be found in client/example/02.c
Setup
To compile the module change the K
variable in the Makefile
with your compiled kernel root directory and compile with make
, then insmod
.
Conclusions
Personally, I used it to study the SLUB allocator, understand UAF/Heap Overflows/Double Free/userfaultd and some hardening features in the kernel, but it can assist the exploitation phase too or more. Blog posts on some Kernel vulnerabilities and their attack methodologies will follow these months and this module will come useful to demonstrate them. So, stay tuned and enjoy !
PS. The “Execute” part of the name will be a future implementation to control pc/rip
.