Closed f0lg0 closed 3 years ago
Hi! Thanks for bringing this up - I've been thinking about what the best way to get around this problem would be for a while now.
The lack of kallsyms_lookup_name()
is definitely annoying, and your method is pretty cool. The way I've been tackling it so far has been to work out the kernel's load address from a function in low memory that is exported (I'm using sprint_symbol()
at the moment, but there's probably something better), and then brute force upwards, using sprint_symbol()
to resolve the name of any function at each address. Luckily, syscalls and interesting functions (like procfile read/write handlers) are pretty early in memory, so the delay for brute-forcing is hardly noticeable.
The function I'm using (e.g. here in a new branch) looks like this:
unsigned long kaddr_lookup_name(const char *fname_raw)
{
int i;
unsigned long kaddr;
char *fname_lookup, *fname;
fname_lookup = kzalloc(NAME_MAX, GFP_KERNEL);
if (!fname_lookup)
return 0;
fname = kzalloc(strlen(fname_raw)+4, GFP_KERNEL);
if (!fname)
return 0;
/*
* We have to add "+0x0" to the end of our function name
* because that's the format that sprint_symbol() returns
* to us. If we don't do this, then our search can stop
* prematurely and give us the wrong function address!
*/
strcpy(fname, fname_raw);
strcat(fname, "+0x0");
/*
* Get the kernel base address:
* sprint_symbol() is less than 0x100000 from the start of the kernel, so
* we can just AND-out the last 3 bytes from it's address to the the base
* address.
* There might be a better symbol-name to use?
*/
kaddr = (unsigned long) &sprint_symbol;
kaddr &= 0xffffffffff000000;
/*
* All the syscalls (and all interesting kernel functions I've seen so far)
* are within the first 0x100000 bytes of the base address. However, the kernel
* functions are all aligned so that the final nibble is 0x0, so we only
* have to check every 16th address.
*/
for ( i = 0x0 ; i < 0x100000 ; i++ )
{
/*
* Lookup the name ascribed to the current kernel address
*/
sprint_symbol(fname_lookup, kaddr);
/*
* Compare the looked-up name to the one we want
*/
if ( strncmp(fname_lookup, fname, strlen(fname)) == 0 )
{
/*
* Clean up and return the found address
*/
kfree(fname_lookup);
return kaddr;
}
/*
* Jump 16 addresses to next possible address
*/
kaddr += 0x10;
}
/*
* We didn't find the name, so clean up and return 0
*/
kfree(fname_lookup);
return 0;
}
I've also pushed my working branch to GitHub here (in my testing so far, all the modules that use ftrace_helper.h
work again on kernel 5.10.5-arch1-1
).
I'd be interested to hear what you think!
Hi! That solution is pretty neat but I am worried about performance (and about the percentage of failure), even though you have underlined the fact that it's barely noticeable. I am really impressed by your solution though, I am still not pretty good at bytes operations so I would have never thought of that. I was looking around trying to find another solution and these are the ideas that came to my mind:
/proc/kallsyms
with sudo privileges and extracts the address of kallsyms_lookup_name
and dumps it to a file that we can access later on in the rootkit (full command: sudo cat /proc/kallsyms | grep kallsyms_lookup_name > dump.txt
) alongside inserting the module with insmod
. I don't really like this solution but it could work.kprobes
and came up with this method that is noticeable shorter. Here's the FULL module.
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
static struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" };
int __init init_test(void) { register_kprobe(&kp);
// x --> unsigned hexadecimal integer
pr_alert("Found at 0x%px \n", kp.addr);
return 0;
}
void __exit cleanup_test(void) { unregister_kprobe(&kp); }
MODULE_LICENSE("GPL");
module_init(init_test); module_exit(cleanup_test);
Output in the kernel logs:
`f0lg0-pc kernel: Found at 0xffffffff9c13c480`
Output in `/proc/kallsysm`:
ffffffff9c13bc50 T module_kallsyms_lookup_name ffffffff9c13c480 T kallsyms_lookup_name
I haven't tried to hook it up to the lib but the addresses are equal.
- The last method was described in this [Tweet](https://twitter.com/spendergrsec/status/1298336464632262659), I haven't fully looked into it yet but it seems kinda cool and it's kinda similar to yours. It uses `sprint_symbol_no_offset() in a loop over the kernel image for just one trivial example` to find the address of `loglevel`.
Thank you for replying to me and let me know what you think, looking forward on finding a stable solution!
Now this is a lovely solution! I threw it into this branch (just for the root backdoor for now) and it works really nicely!
Early on, I check the kernel version, and setup the struct:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
#define KPROBE_LOOKUP 1
#include <linux/kprobes.h>
static struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name"
};
#endif
and then later on, resolve the kallsyms_lookup_name()
symbol in fh_resolve_hook_address()
:
#ifdef KPROBE_LOOKUP
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
kallsyms_lookup_name_t kallsyms_lookup_name;
register_kprobe(&kp);
kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
unregister_kprobe(&kp);
#endif
I'm gonna test the other techniques to make sure that they all work properly, but this definitely looks like the one to go with.
Awesome! Completely agree, it seems the shortest and quickest way to do it. I don't know enough about kprobes performance but it looks reasonable. Plus all the sweet checking you do to trigger this patch only if needed makes it perfect. Glad I helped you and thanks for your awesome work!
These changes have been merged into master (and I credited you with the idea in a comment!). Thanks again for the sweet find!
Excellent!
The best solution I found on the internet, thanks. ;-p
This kprobe method depends on CONFIG_KPROBE
and CONFIG_KALLSYMS
to be set. At least Slackware 15.0 does not have CONFIG_KPROBE
set, and I suspect there are others as well.
Relevant docs: https://docs.kernel.org/trace/kprobes.html#configuring-kprobes
Just for my understanding: why is the usage of both kallsyms_lookup_name AND kprobes needed? I may got something twisted because in my head both try to reach the same goal right? finding the address of the hooked syscall? thx for the replies
Issue
In newer kernel versions (> 5.7.0) the function
kallsyms_lookup_name
, used in your ftrace_helper.h library, is not exported anymore by default. This means that compiling the code provided by you (also found here) on newer kernels will fail throwing:ERROR: modpost: "kallsyms_lookup_name" undefined!
More references:
https://lkml.org/lkml/2020/2/25/576 https://lwn.net/Articles/813350/
Solution
I've done some research online and found this workaround, which compiles and works on Manjaro with kernel 5.9.16; so I have decided to link this awesome solution to your library.
patch.c - here's the solution wrote by @zizzu0 and modified by me
usable by including patch.h
And finally, the patched function in the lib ftracer_hekper.h
Conclusion
The described methods works also on older kernels, like in the 5.4.0-58 used in the Vagrant instance provided by your blog. It works by extracting the dynamic address from the kernel. I haven't opened a pull request because I couldn't find the ftracer_helper lib here (I have only found it as a gist) and especially because I am a begginner in kernel land, my code could have been written and integrated better. It was a cool exercise for a beginner like me, hope you will find it useful!
Sorry for my English, I ain't a native speaker :)