milabs / khook

Linux Kernel hooking engine (x86)
GNU General Public License v2.0
327 stars 50 forks source link

Possible to hook sys_kill(pid, signal)? #3

Closed alichtman closed 5 years ago

alichtman commented 5 years ago

Just wanted to start out by saying this is an awesome project! Nice work!


I'm having a problem hooking sys_kill, and I was hoping you'd be able to help me out.

sys_kill is defined here in linux/syscalls.h.

I have been trying to hook this function for a while and can not get anything to compile. I was able to hook kill_pid, however, that did not hook the kill syscall.

// BUG: COMPILES BUT DOESN'T HOOK KILL.
KHOOK(kill_pid);
static int khook_kill_pid(struct pid *pid, int sig, int priv) {
    if (pid->numbers->nr == MAGIC_NUM) {
        return do_the_thing();
    } else {
        return KHOOK_ORIGIN(kill_pid, pid, sig, priv);
    }
}

Is there a way to hook the actual kill syscall? Or can it not be done since the syscall table is no longer exported, post-Kernel 2.6?

milabs commented 5 years ago

Hey, thank you for using KHOOK.

As for hooking system calls you have to hook sys_XXX symbols like sys_kill. See the example below:

KHOOK_EXT(long, sys_kill, long, long);
static long khook_sys_kill(long pid, long sig) {
        printk("sys_kill -- %s pid %ld sig %ld\n", current->comm, pid, sig);
        return KHOOK_ORIGIN(sys_kill, pid, sig);
}

Just use the right prototype for this sys_kill...

alichtman commented 5 years ago

Oh, whoops. My brain was not working correctly last night. Thanks for the help!

However, the example did not work for me. When I run $ kill <some_pid> in bash, this debug statement isn't printed.

I tried changing the first parameter to a pid_t type, and that did not solve the problem either.

I'm pretty sure that $ kill calls sys_kill(), but I'm not sure if that's true.

milabs commented 5 years ago

It's weird. I've tried to kill -9 1 and the dmesg shows me: [ 4630.052329] sys_kill -- bash pid 1 sig 9

By the way, my kernel is 4.8.0-53-generic

alichtman commented 5 years ago

I'm working on 4.18.0-17-generic. Still not seeing the same behavior you're describing. Hmm.

milabs commented 5 years ago

OK, it makes sense then. From some point they changed syscalls notation and argument passing. So, now you have to look for __x64_sys_kill with long (*)(const struct pt_regs *) proto instead of sys_kill. The code may look like this (didn't test):

KHOOK_EXT(long, __x64_sys_kill, const struct pt_regs *);
static long khook___x64_sys_kill(const struct pt_regs *regs) {
        printk("sys_kill -- %s pid %ld sig %ld\n", current->comm, regs->di, regs->si);
        return KHOOK_ORIGIN(__x64_sys_kill, regs);
}
alichtman commented 5 years ago

Oh, alright, cool! I'll try this out when I have a minute.

alichtman commented 5 years ago

Worked perfectly. Thanks for the help!

milabs commented 5 years ago

Updated the README with this example. Thank you for the question.

alichtman commented 5 years ago

Can you explain where you found this new function signature?

KHOOK_EXT(long, __x64_sys_kill, const struct pt_regs *);
static long khook___x64_sys_kill(const struct pt_regs *regs)

I'm having trouble reproducing this with other syscalls.

For example, I have this hook for msgctl, and it's not being tripped.

KHOOK_EXT(long, __x64_ksys_msgctl, int, int, struct msqid_ds __user *);
static long khook___x64_ksys_msgctl(int msqid, int cmd, struct msqid_ds __user *buf)

Here is a relevant LKML email: https://lore.kernel.org/lkml/tip-d5a00528b58cdb2c71206e18bd021e34c4eab878@git.kernel.org/

But I can't figure out where you found the prototype for the updated sys_kill function. As far as I can tell, it's not in the source code. But I also know that can't be right, so I'm a little stuck. Google has turned up nothing so far.

Edit: I found this: https://github.com/torvalds/linux/blob/618d919cae2fcaadc752f27ddac8b939da8b441a/arch/x86/include/asm/syscall_wrapper.h#L125

and updated my code to:

KHOOK_EXT(long, __x64_ksys_msgctl, const struct pt_regs *);
static long khook___x64_ksys_msgctl(const struct pt_regs * regs) {
    // int msqid, int cmd, struct msqid_ds __user *buf
    action_task* task;
    // Read first two arguments
    if (regs->di == -1 && regs->si == -1) {
        if (condition) { 
            printk(KERN_EMERG "sys_msgctl -- preparing...\n");
            ...
            return 0;
        } else {
            printk(KERN_EMERG "sys_msgctl\n");
            task = (action_task*) regs->dx;
            ...
            return 0;
        }
    } else {
        return KHOOK_ORIGIN(__x64_ksys_msgctl, regs);
    }
}

However, this still does not hook msgctl calls.

milabs commented 5 years ago

TLDR: __x64_ksys_msgctl -> __x64_sys_msgctl

https://elixir.bootlin.com/linux/latest/source/ipc/msg.c#L614

SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
{
    return ksys_msgctl(msqid, cmd, buf);
}

You could ether hook ksys_msgctl(int, int, struct msqid_ds *) as a kernel function (in case if it is exported as a ksym with name -- check by grepping /proc/kallsyms) or hook __x64_sys_msgctl/sys_msgctl as a system call handler.

alichtman commented 5 years ago

ksys_msgctl does appear in /proc/kallsyms, and I've made this change: __x64_ksys_msgctl -> __x64_sys_msgctl.

It's still not being hooked properly.

The syscall definition you pasted in was the reason I initially had sys_msgctl hooked like this:

KHOOK_EXT(long, __x64_ksys_msgctl, int, int, struct msqid_ds __user *);
static long khook___x64_ksys_msgctl(int msqid, int cmd, struct msqid_ds __user *buf)

Should I change that ^ to __x64_sys_msgctl?

I feel like I’m missing some core understanding of what I’m doing. Do you have any recommendations for topics I should read up on?

milabs commented 5 years ago

Should I change that ^ to __x64_sys_msgctl?

YES (but I didn't test it)

milabs commented 5 years ago

This works well for me:

KHOOK_EXT(long, __x64_sys_msgctl, const struct pt_regs *);
static long khook___x64_sys_msgctl(const struct pt_regs *regs)
{
    printk("%s -- msqid:%ld cmd:%ld ptr:%p\n", current->comm, regs->di, regs->si, (void *)regs->dx);
    return KHOOK_ORIGIN(__x64_sys_msgctl, regs);
}

main.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int main(int argc, const char *argv[])
{
    msgctl(100, 200, (void *)300);
    return 0;
}

$ dmesg [108112.452874] a.out -- msqid:100 cmd:200 ptr:000000008b1f8f3d

alichtman commented 5 years ago

What you posted did work for me. Funny enough, I actually posted that signature in a previous comment. I think my error was that the line below was never hit. I passed a -1 in to msgctl for the first two parameters and it was turned into INT_MAX (although I don't understand why, since ints are signed...):

if (regs->di == -1 && regs->si == -1) {

Anyways, the issue is now resolved. Thank you again for the help.

KTalinki commented 5 years ago

hi, I am trying to hook sys_open and some other syscalls to intercept the file system activity, on kernel 4.17/4.18 on x86_64.

/proc/kallsyms shows x64_sys_open, but not ksys_open. Not sure if I need to hook ksys_open or x64_sys_open I am getting compilation error of 'undelcared identifier' for sys_open & __x64_sys_open.

What are the right headers I need to include ?

Along with sys_open, I need to hook sys_creat, sys_openat, sys_execve, sys_truncate, sys_ftruncate, sys_write, etc...

Before kernel 4.17, I was able to find the symbol for sys_xyz, but from Kernel 4.17 onward, I am not sure if I need to hook __x64_sys_xyz or ksys_xyz or sys_xyz.

In syscalls.h Some system call are declared with /__ARCH_WANT_SYSCALL_DEPRECATED/

Please help, thank you, Kumar T

milabs commented 5 years ago

@KTalinki Use the following code for every "modern" syscall handler (replace XXX with open or so):

KHOOK_EXT(long, __x64_sys_XXX, const struct pt_regs *);
static long khook___x64_sys_XXX(const struct pt_regs *regs)
{
    // do the job
    return KHOOK_ORIGIN(__x64_sys_XXX, regs);
}
KTalinki commented 5 years ago

Thank you milabs.

If I were to use hook in a traditional way of finding a specific syscall table address and hook it, which specific syscall API to hook sys_open or x64_sys_open or ksys_open sys_open or x64_sys_creat or ksys_creat sys_openat or x64_sys_openat or ksys_openat sys_execve or x64_sys_exeeve or ksys_execve etc ...

and what are the header files x64_sys_creat or x64_sys_openat are declared ?

It will be great if someone can share a sample with what is needed to hook syscalls on kernels from 4.17 onward.

thank you, Kumar T

milabs commented 5 years ago

Dear @KTalinki, you don't need to have headers to hook __x64_sys_XXX as they all have the same prototype (like I shown before). As for the ksys_xxx() symbols I'd suggest you to look for the Linux kernel source code to see how they are used. Finally, you're the kernel developer and you have to be able to dig the source code.

KTalinki commented 5 years ago

thank you @milabs , the way our existing code is structured, it uses the declaration of the syscalls from .../include/linux/syscalls.h But on kernel 4.17 with x64_sys_open, is not declared in And <arch/x85/include/generated/asm/syscalls_64.h> has the declaration something like __SYSCALL_64(2, X64_sys_open,)

The issue I am running in to is compilation errors for x64_sys_open and SYSCALL_64. Including <arch/x85/include/generated/asm/syscalls_64.h> is not helping.

Any help is appreciated, thank you, Kumar

milabs commented 5 years ago

@KTalinki Again, you don't have to include ANY header to hook __x64_XXX, just use the KHOOK_EXT macro:

KHOOK_EXT(long, __x64_sys_XXX, const struct pt_regs *);
static long khook___x64_sys_XXX(const struct pt_regs *regs)
{
    // do the job
    return KHOOK_ORIGIN(__x64_sys_XXX, regs);
}

Which is the error with using this code?

wbt165 commented 4 years ago

@milabs hello,I want to hook sys_exevce. I'm using the KHOOK_EXT macro,which work well on the 64-bit machines,for example Redhat6.8 x86_64 or CentOS8 x86_64.The code is as follows: Linux Redhat6.8 2.6.32-642.el6.x86_64:

KHOOK_EXT(long, sys_execve, char __user *, char __user * __user *, char __user * __user *, struct pt_regs *);
static long khook_sys_execve(char __user *name, char __user * __user *argv, char __user * __user *envp, struct pt_regs *regs)
{
    long ret = 0;
    ret = KHOOK_ORIGIN(sys_execve, name, argv, envp, regs);
    return ret;
}

CentOS8 x86_64:

KHOOK_EXT(long, __x64_sys_execve, const struct pt_regs *);
static long khook___x64_sys_execve(const struct pt_regs *regs)
{
    long ret = 0;
    ret = KHOOK_ORIGIN(__x64_sys_execve, regs);
    return ret;
}

BUT,I have a problem on the 32-bit machine. I test it on Linux Redhat 6.8 2.6.32-642.el6.i686.The code is as follows:

KHOOK_EXT(int, sys_execve, struct pt_regs *);
static int khook_sys_execve(struct pt_regs *regs)
{
    int ret = 0;
    ret = KHOOK_ORIGIN(sys_execve, regs);
    return ret;
}

I Found the sys_execve() symbols from the source code as follow: image

After I insmod, I execute "ls" or any other commands. ERROR as following: image

I suspect there is a problem with this code executing on a 32-bit machine.I hope you can take a look in your busy schedule. Thanks for your help!

milabs commented 4 years ago

@wbt165 I'd recommend you to hook do_execve instead of sys_execve for exec case. Could you try to hook it that way?

wbt165 commented 4 years ago

@milabs Thanks for your response! I test do_execve,and the results as follows:

Thanks for your help!

milabs commented 4 years ago

@wbt165 Unfortunately, 32-bit stub is not implemented as I never had such requirement for myself (see https://github.com/milabs/khook/blob/master/khook/x86/stub.S#L24). It would be great if you'll try to implement missing part of the macro.

milabs commented 4 years ago

If you could test that code on 32-bit system?

.macro CALL_COPY_N_ARGS n
    sub $(\n * 4), %esp
    .set i, 0
    .rept \n
        mov ((\n + i + 1) * 4)(%esp), %eax
        mov %eax, (i * 4)(%esp)
        .set i, i + 1
    .endr
    mov $0xcacacaca, %eax
    call *%eax
    add $(\n * 4), %esp
.endm
wbt165 commented 4 years ago

If you could test that code on 32-bit system?

.macro CALL_COPY_N_ARGS n
  sub $(\n * 4), %esp
  .set i, 0
  .rept \n
      mov ((\n + i + 1) * 4)(%esp), %eax
      mov %eax, (i * 4)(%esp)
      .set i, i + 1
  .endr
  mov $0xcacacaca, %eax
  call *%eax
  add $(\n * 4), %esp
.endm

@milabs Unfortunately,this code has NO effect……

milabs commented 4 years ago

@milabs Unfortunately,this code has NO effect……

Made a branch for you to test: https://github.com/milabs/khook/tree/i686-fix Could you please test it?

wbt165 commented 4 years ago

@milabs Thank you for creating a branch! I tested it on Linux Redhat6.8 2.6.32-642.el6.i686, but it didn't work. The code is as follow:

#include <linux/uaccess.h>
#include <linux/slab.h>
static char *duplicate_filename(const char __user *filename)
{
    char *kernel_filename;
    int nRet;

    kernel_filename = kmalloc(4096, GFP_KERNEL);
    if (!kernel_filename)
    {
        pr_info("%s - NULL!\n", __func__);
        return NULL;
    }

    nRet = strncpy_from_user(kernel_filename, filename, 4096);
    if (nRet < 0) {
        pr_info("%s - nRet = %d!\n", __func__, nRet);
        kfree(kernel_filename);
        return NULL;
    }

    return kernel_filename;
}

KHOOK_EXT(int, sys_execve, struct pt_regs *);
static int khook_sys_execve(struct pt_regs *regs)
{
    int ret = 0;
    char *filename;

    filename = duplicate_filename((char __user *)regs->bx);
    pr_info("%s - %s!\n", __func__, filename);

    ret = KHOOK_ORIGIN(sys_execve, regs);
    pr_info("%s ret = %d\n", __func__, ret);

    kfree(filename);

    return ret;
}

After I insmod, I execute "ls" or any other commands. ERROR as following: image The debug info is as follow: image