You are not logged in.
Pages: 1
Topic closed
So I got bored last night and decided I wanted to learn C and some Linux programming. Didn't really have any experience so I started with a basic template of what how to write a kernel module that will printk "Hello world" so it can be viewed in dmesg. After I was finished I sat and thought, what can I create? Well I thought a way to intercept understand how the system calls were working would be fun. I did not want to do anything malicious as this was my own machine. I also know this is not really something that can be used to hack into a system as once someone has root access the machine is theirs to do what they want with anyway.
So I went off writing a module that what I thought would be as simple as having an "extern void *sys_call_table;" declared and manipulating the addresses in the table to swap out the function pointer to my own function. It didn't work so I googled around a bit and noticed this is no longer an exported symbol. More googling later, I started looking for a System.map in /boot/ to see if there were any exported addresses there, but arch does not seem to include the System.map anymore. Apparently (which i thought was really neat) there is /proc/kallsyms to show all the exported symbols in the Linux kernel that I could easily access manually though a pointer. Searching through this there was no sys_call_table either.
[niriven@(none) ~]$ cat /proc/kallsyms | grep sys_call_table
[niriven@(none) ~]$
Given that kernel source no longer exports this I guess this makes sense. But I want access to the sys_call_table. How do I do it? I know it is sitting in the kernel memory space somewhere. I than realized I have access to all of the kernel space memory and I could search it to find an array that contains a pointer to a couple system calls i am interested in. Googling around some more it looks like some others have took this approach before so I copied some code out and modified it a bit to do the search for the sys_call_table. Voila, I found the address of the table! This means I can start changing the tables system call references and intercept some code, or so I thought. I tried swapping out a system call (eg. sys_exit) for my own (new_sys_exit) and the kernel had no part in this. I took a look at the output in dmesg and noticed that this memory was not addressable. What does this this mean? Onto more googling! I saw people modifying the sys_call_table with no problems in the 2.6 series of kernels, so what changed? It looked as though kernel developers changed this memory to be protected so others could not modify it. At this point I was ready to give up because I figured read-only memory is something I could not change but I decided to persist and find out if theres any neat hacks to get around this. I found that others where calling some kernel level API's to change certain pages of memory from ro to rw, changing the memory and changing it back. Unfortunitally it looked as though when i tried to call them the kernel module compiled but insmod reported that there were undefined symbol errors which meant I could not access these functions anymore.
I thought I was trapped and could no longer get into this memory and start modifying it to do what I wanted. Just before giving up I found someone post on a yahoo answers comment about disabling page protection at a processor level by changing the 16th bit in the cr0 register (I think this is intel specific). I decided to try it and finally, I could change any memory in the kernel space that I wanted!
Ah what a fun way to spend 6 hours to learn C and some of the internals of how Linux works. I did google a lot but who doesn't? I also learned much more than expected about these topics in the process which I am happy with. Anyway, Here is the code I created as it might be of some use to others. As some others have said this is not a hack, you need root to try this out. If you have root, you essentially own the system anyway so this is not too malicious, but fun if you want to do some system call interception on your own system on a 3.3 kernel.
interceptor.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/delay.h>
#include <asm/paravirt.h>
unsigned long **sys_call_table;
unsigned long original_cr0;
asmlinkage long (*ref_sys_read)(unsigned int fd, char __user *buf, size_t count);
asmlinkage long new_sys_read(unsigned int fd, char __user *buf, size_t count)
{
long ret;
ret = ref_sys_read(fd, buf, count);
if(count == 1 && fd == 0)
printk(KERN_INFO "intercept: 0x%02X", buf[0]);
return ret;
}
static unsigned long **aquire_sys_call_table(void)
{
unsigned long int offset = PAGE_OFFSET;
unsigned long **sct;
while (offset < ULLONG_MAX) {
sct = (unsigned long **)offset;
if (sct[__NR_close] == (unsigned long *) sys_close)
return sct;
offset += sizeof(void *);
}
return NULL;
}
static int __init interceptor_start(void)
{
if(!(sys_call_table = aquire_sys_call_table()))
return -1;
original_cr0 = read_cr0();
write_cr0(original_cr0 & ~0x00010000);
ref_sys_read = (void *)sys_call_table[__NR_read];
sys_call_table[__NR_read] = (unsigned long *)new_sys_read;
write_cr0(original_cr0);
return 0;
}
static void __exit interceptor_end(void)
{
if(!sys_call_table) {
return;
}
write_cr0(original_cr0 & ~0x00010000);
sys_call_table[__NR_read] = (unsigned long *)ref_sys_read;
write_cr0(original_cr0);
msleep(2000);
}
module_init(interceptor_start);
module_exit(interceptor_end);
MODULE_LICENSE("GPL");
Makefile
obj-m += interceptor.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
As for the output:
[root@RRL125-ARCH niriven]# dmesg
[ 3108.147363] intercept: 0x64
[ 3108.241925] intercept: 0x6D
[ 3108.287888] intercept: 0x65
[ 3108.380025] intercept: 0x73
[ 3108.443986] intercept: 0x67
[ 3108.540326] intercept: 0x0D
[ 3109.697865] intercept: 0x1B
[ 3109.697883] intercept: 0x5B
[ 3109.697893] intercept: 0x41
[ 3109.916600] intercept: 0x0D
[ 3113.848476] intercept: 0x63
[ 3113.936372] intercept: 0x6C
[ 3113.997244] intercept: 0x65
[ 3114.082137] intercept: 0x61
[ 3114.145038] intercept: 0x72
[ 3114.272825] intercept: 0x0D
[ 3115.129938] intercept: 0x64
[ 3115.216999] intercept: 0x6D
[ 3115.341207] intercept: 0x73
To install on Arch Linux, make sure you have linux-headers and base-devel is installed
pacman -S linux-headers base-devel
Ensure interceptor.c and Makefile are in a directory, compile, and install as root, check dmesg, then remove module
make
insmod interceptor.ko
dmesg
rmmod interceptor
Hopefully this will be a help to someone else looking to learn similar topics!
-Niriven
Last edited by niriven (2014-09-30 02:19:20)
Offline
Cool! There's always ptrace() for intercepting system calls too
Offline
Nice post!
You save me some hours to locate the syscall table!
For the info, you can get rid of the asm, the kernel already provides that! Have a look at /usr/src/linux-$(uname -r)/arch/x86/include/asm/paravirt.h
write_cr0 and read_cr0
Cheers!
Offline
Cool! There's always ptrace() for intercepting system calls too
Thanks i'll take a look! BTW very interesting website you have there, I've always wanted to do nice simulations like you are doing though I lack the math knowledge.
Offline
Nice post!
You save me some hours to locate the syscall table!
For the info, you can get rid of the asm, the kernel already provides that! Have a look at /usr/src/linux-$(uname -r)/arch/x86/include/asm/paravirt.h
write_cr0 and read_cr0Cheers!
Good point, I wasn't aware those existed. The direct assembly might be a little more flexible as it is immune to kernel API changes
Offline
tavianator wrote:Cool! There's always ptrace() for intercepting system calls too
Thanks i'll take a look! BTW very interesting website you have there, I've always wanted to do nice simulations like you are doing though I lack the math knowledge.
Thanks! It's nothing you can't pick up by reading a few books, if you're really interested.
Offline
Updated post for linux 3.4 and included a Makefile if anyone wanted to try it.
Offline
Updated for 3.6, added a *very* basic key log example instead of sys_exit.
Offline
With Ubuntu 12.04, this code gives segfaults, with the execution of some commands like 'sudo service ssh stop' or 'make'. Debugging showed it has to do with the trick to enable writing in the page to modify the sys_call table.
I couldn't figure out exactly what is going.. .any of you has an idea what is happing?
Offline
Ouch, sorry about that. I have ubuntu installed at home and will take a look in about 8 hours (after work) and post back here. I'll send you my email in a PM if you have anymore details on why it does not work.
Offline
This is a really interesting subject!
I have a couple of questions:
1. Isn't it possible to use xchg to swap the functions?
Something like:
ref_sys_open = (void *) xchg(sys_call_table[__NR_open], new_sys_open); <---- switch to custom function
xchg(sys_call_table[__NR_open], ref_sys_open); <----- siwtch back to original function
2. I'm getting "BUG: unable to handle kernel paging request" and functions aren't switched back to original ones. That's probably from the functions that try to set RW/RO, but I'm not sure why it's happening. (I'm on x64). Have you had the same problem?
Offline
This is cool, but way over my head. The other day I felt like learning some C too. I wrote a server/client that passed a char * back and forth. I like what you did better.
Offline
Thanks cris9288. I even have an example in the forums somewhere about creating a TCP client with non blocking sockets in a Linux kernel module. Why? Dunno, but seemed fun to try.
alexandernst, I was acutally going review this again and see how i can make it better tonight, with kernel 3.8. I'll see if i can reproduce your problem. I am not sure of the advantages of using xchg, but i'll take a look at that as well
Last edited by niriven (2013-04-23 01:13:04)
Offline
niriven, maybe you'll be interested in having a look at my project: https://github.com/alexandernst/procmon
Offline
Very nice alexandernst, this is what i was initially going to try to do, but obviously i did not get as far as you I was going to write a way to access the data like you did in userland, then expose webservices with node.js, and have a webgl UI for visualization, though havn't gotten too far yet I'll look more at that code tonight
Offline
niriven, thank you Maybe we can join forces? I'm planning to do everything on a model-view basis, (Qt5 for the view part), but as there will be a strong separation between data and UI you'll be able to write a NodeJS + WebGL UI.
Offline
Thanks , it's useful for me.
Offline
Updated for Linux 3.14. Switched to _cr0 functions instead of using hacky ASM.
Offline
@niriven what for msleep(2000); in interceptor_end()
i don't catch that.
Things being equal, the simplest explanation tends to be the right.
Offline
Please do not necrobump.
niriven hasn't been on the forums in three years, you're unlikely to get a response from them.
Closing.
Sakura:-
Mobo: MSI MAG X570S TORPEDO MAX // Processor: AMD Ryzen 9 5950X @4.9GHz // GFX: AMD Radeon RX 5700 XT // RAM: 32GB (4x 8GB) Corsair DDR4 (@ 3000MHz) // Storage: 1x 3TB HDD, 6x 1TB SSD, 2x 120GB SSD, 1x 275GB M2 SSD
Making lemonade from lemons since 2015.
Offline
Pages: 1
Topic closed