Skip to content

kallsyms_lookup_name is not exported anymore in kernels > 5.7 #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
f0lg0 opened this issue Jan 10, 2021 · 10 comments
Closed

kallsyms_lookup_name is not exported anymore in kernels > 5.7 #3

f0lg0 opened this issue Jan 10, 2021 · 10 comments

Comments

@f0lg0
Copy link

f0lg0 commented Jan 10, 2021

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

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>

// added by me
#include "inc/patch.h"

#define KPROBE_PRE_HANDLER(fname) static int __kprobes fname(struct kprobe *p, struct pt_regs *regs)

long unsigned int kln_addr = 0;
unsigned long (*kln_pointer)(const char* name) = NULL;

static struct kprobe kp0, kp1;

KPROBE_PRE_HANDLER(handler_pre0) {
    kln_addr = (--regs->ip);

    return 0;
}

KPROBE_PRE_HANDLER(handler_pre1) {
    return 0;
}

static int do_register_kprobe(struct kprobe* kp, char* symbol_name, void* handler) {
    int ret;

    kp->symbol_name = symbol_name;
    kp->pre_handler = handler;

    ret = register_kprobe(kp);
    if (ret < 0) {
        pr_err("do_register_kprobe: failed to register for symbol %s, returning %d\n", symbol_name, ret);
        return ret;
    }

    pr_info("Planted krpobe for symbol %s at %p\n", symbol_name, kp->addr);

    return ret;
}

// this is the function that I have modified, as the name suggests it returns a pointer to the extracted kallsyms_lookup_name function
kln_p get_kln_p(void) {
    int status;

    status = do_register_kprobe(&kp0, "kallsyms_lookup_name", handler_pre0);

    if (status < 0) return NULL;

    status = do_register_kprobe(&kp1, "kallsyms_lookup_name", handler_pre1);

    if (status < 0) {
        // cleaning initial krpobe
        unregister_kprobe(&kp0);
        return NULL;
    }

    unregister_kprobe(&kp0);
    unregister_kprobe(&kp1);

    pr_info("kallsyms_lookup_name address = 0x%lx\n", kln_addr);

    kln_pointer = (unsigned long (*)(const char* name)) kln_addr;

    return kln_pointer;
}

usable by including patch.h

#ifndef PATCH_H
#define PATCH_H

typedef unsigned long (*kln_p)(const char*);
kln_p get_kln_p(void);

#endif

And finally, the patched function in the lib ftracer_hekper.h

#include "patch.h"

static int fh_resolve_hook_address(struct ftrace_hook* hook) {
    // new method
    kln_p kln = get_kln_p();
    if (kln == NULL) return -1;

    hook->address = kln(hook->name);

    if (!hook->address) {
        printk(KERN_DEBUG "rootkit: unresolved symbol: %s\n", hook->name);
        return -ENOENT;
    }

#if USE_FENTRY_OFFSET
    * ((unsigned long*)hook->original) = hook->address + MCOUNT_INSN_SIZE;
#else
    * ((unsigned long*)hook->original) = hook->address;
#endif

    return 0;
}

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 :)

@xcellerator
Copy link
Owner

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!

@f0lg0
Copy link
Author

f0lg0 commented Jan 11, 2021

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:

  • Since loading a module into the kernel requires root, it's possible to build a load bash script that greps into /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.
  • I have been searching more stuff about 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, 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!

@xcellerator
Copy link
Owner

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.

@f0lg0
Copy link
Author

f0lg0 commented Jan 11, 2021

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!

@xcellerator
Copy link
Owner

These changes have been merged into master (and I credited you with the idea in a comment!).
Thanks again for the sweet find!

@ncorbuk
Copy link

ncorbuk commented May 24, 2021

Excellent!

@thebigcicca
Copy link

The best solution I found on the internet, thanks. ;-p

@NicholasBHubbard
Copy link

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

@OpaxIV
Copy link

OpaxIV commented Sep 23, 2024

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

@begbaj
Copy link

begbaj commented Feb 3, 2025

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

So you have either methods on the same file that conditionally compile depending on the version of the kernel, you're not actually using both.

I agree, tho that it is confusing and hard to read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants