Created by Aviel Warschawski
KPlugs is a Linux kernel module which provides an interface for dynamically executing scripts inside the Linux kernel. KPlugs uses a simple bytecode interpreter (the KPlugs Virtual Machine), and an interface that allows a user to dynamically load scripts into the kernel and execute them directly from user space. Because the interface is dynamic, it's easy to implement a user-mode library that wraps anything in the kernel!
KPlugs comes with a Python library that compiles a subset of the Python language to the KPlugs bytecode, and lets you easily load and execute your "kernel Python script".
The Python compiler is very basic and was made to fit our own purposes, but if it's not exactly what you are looking for, you can modify it to create your own subset.A few reasons why KPlugs is a strong and unique tool:
Get the last version sources here.
A simple KPlugs Python script:
The KPlugs Python library comes with some example classes (and you are encouraged to use them in your scripts):
#!/usr/bin/python3 import kplugs import struct with kplugs.Context() as context: plug = context.Plug() mem = context.Mem() sym = context.Symbol() caller = context.Caller() hook = context.Hook() jiffies = sym["jiffies"] # Getting the current time kernel_buf = mem.alloc(kplugs.WORD_SIZE) # Allocating a buffer in kernel space mem[kernel_buf] = mem[jiffies:jiffies+kplugs.WORD_SIZE] # Copying the current time to our kernel buffer print ("The current time is: ", struct.unpack("P", mem[kernel_buf:kernel_buf+kplugs.WORD_SIZE])) kernel_func = r''' def my_hook(kp, regs): print("The registers are stored in 0x%lx" % regs) return 0 ''' my_hook = plug.compile(kernel_func) hook.hook("vmalloc", my_hook) # Hook the kernel function - vmalloc p = caller["vmalloc"](0x100) # Execute the kernel function - vmalloc. the hook should be executed caller["vfree"](p) hook.unhook(my_hook)
#!/usr/bin/python3 import kplugs with kplugs.Context() as context: kernel_func = r''' STATIC("my_helper") def my_function(): try: my_helper() print("OK") except word as err: print("Exception number: %d" % -err) raise err def my_helper(): KERNEL_undefined_function() ''' plug = context.Plug() my_function = plug.compile(kernel_func) my_function()
Using kernel functions:
#!/usr/bin/python3 import kplugs import codecs with kplugs.Context() as context: kernel_func = r''' BUFFER_SIZE = 0x10 ERROR_POINT = 12 # taken from types.h def get_initramfs(user_initram): buffer(user_initram, BUFFER_SIZE) pointer(initram) pointer(sizeptr) initram = KERNEL_kallsyms_lookup_name("__initramfs_start") sizeptr = KERNEL_kallsyms_lookup_name("__initramfs_size") if initram == 0 or sizeptr == 0: raise ERROR_POINT size = DEREF(sizeptr) if size < 0 or size > BUFFER_SIZE: size = BUFFER_SIZE KERNEL_memcpy(user_initram, initram, size) return size ''' plug = context.Plug() buf = bytearray(0x10) get_initramfs = plug.compile(kernel_func) size = get_initramfs(buf) print ("The initramfs starts with: '%s'" % (codecs.encode(bytes(buf[:size]), "hex").decode(), ))
What is KPlugs?
KPlugs is a linux kernel module that gives an interface for dynamically executing scripts inside the Linux kernel. The user can create KPlugs functions and execute them directly from user space. The functions then run on a virtual machine designed to be simple, but still give the user all he needs to safely implement any functionality. KPlugs comes with a Python library that compiles a subset of the Python language to the KPlugs bytecode, complete with bindings to the kernel module interface, allowing the user to simply load, execute and unload functions directly from a Python script.
KPlugs is licensed under GPLv3.
What architectures are supported by KPlugs?
KPlugs was written for x86, both 32 and 64 bits. Since version 1.1 KPlugs also supports ARM machines. KPlugs can be changed to support any other architecture by adjusting the assembly file "calling_wrapper.s". More information on that inside the file.
What Linux versions are supported by KPlugs?
KPlugs was only tested on Linux kernel version 4.14.15. However, because it uses only standard kernel functions, it should work on any recent kernel.
How do I use KPlugs?
Download KPlugs from the download section.
KPlugs is a kernel module and as such requires your kernel's sources to compile. The KPlugs Makefile assumes you have the sources of your Linux kernel in the standard location (/lib/modules/`uname -r`/). If you put the sources elsewhere, edit the Makefile and point it to the right location.
You should then be able to build the module using make.
If everything worked, you should have two modules inside the Release and Debug directories. The debug version just adds some debug prints to help you debug the kernel module if you wish.
Finally, loading the kernel module is done by running "insmod ./Release/kplugs.ko" (or ./Debug/kplugs.ko if you want to debug the module).
How do I use the KPlugs Python library?
The Python library is (surprisingly) located inside the python directory. It contains three files:
Why did you create your own bytecode instead of porting the Python engine to kernel mode?
At first, we considered porting the entire Python engine. We eventually decided against it because of two main reasons:
How do I create functions with KPlugs?
The KPlugs module creates the device file "/dev/kplugs" to communicate with the user. Every file descriptor a user holds to that device is called a plug. Each plug receives a context. A user can load functions to a plug's private context, so that only he could use those functions (unless he gives another process a pointer to his function), or he could load them to a special context - The global context. When a user tries to execute a function using the KPlugs interface, KPlugs tries to find it in the plug's context first, and then it tries to find it in the global context.
Functions loaded to a local context are automatically unloaded when its plug is unplugged - when the user closes the file descriptor, but functions loaded to the global context are unloaded only when the user issues the unload command (or if the KPlugs kernel module is removed using rmmod).
KPlugs doesn't allow loading two functions with the same name to the same context. You may create an anonymous function - a function without a name. An anonymous function uses the function's address as an identifier instead of the function's name.
What are the KPlugs bytecode's variable types?
KPlugs has four variable types: word, pointer, buffer and array:
How does KPlugs resolve function names at run-time?
Most of the functions inside the Linux kernel use the same calling convention. This is why you can compile a kernel module and let other modules call your functions. The problem is that you have to know the function prototype at compile-time, which is obviously problematic when you want to load scripts inside the kernel. Other solutions such as SystemTap tackle this problem by compiling a module for every script you want to load.
For KPlugs we had to find another solution because we wanted the symbols to be resolved at run-time. Most of the kernel functions use either fastcall or cdecl as their calling convention (the vast majority uses fastcall). Lucky for us, these two conventions don't require knowing beforehand how many arguments were passed to us, i.e., two identical functions with the same calling convention and a different number of arguments are compiled exactly the same.
We exploit that fact when calling kernel functions from within bytecode. We create functions pointers for every calling convention, and use the right function pointer depending on the chosen calling convention for the specific function. The reason we need more than just the standard calling convention is because sometimes the compiler chooses another calling convention for functions with a variable number of arguments (like printk). For now KPlugs support the standard calling convention and the calling convention of variable-length argument functions. Knowing the function's calling convention, the KPlugs VM calls find_symbol to find the requested function's address, and can then set up the argument and call it.
Also, when you create a KPlugs function you can choose the calling convention for it. Your choice doesn't have any effect when you call your function through the KPlugs interface, but it does when you call your function directly by its address. In that case a proper wrapper function (depending on which calling convention you chose) will be executed. The wrapper will get the caller's arguments (It can't know the exact number so it will just pop the maximum number of arguments that a function takes), and start the VM.
What if I have more questions?
You can contact us, and we'll answer your questions as soon as we can.
If you have any comment, we'd love to hear from you. Contact us firstname.lastname@example.org
Created by Aviel Warschawski