如何在较新的Linux内核中挂钩sys_clone

时间:2018-02-21 18:05:37

标签: c assembly linux-kernel hook clone

我正在尝试挂钩sys_clone,以绕过工具unhide http://www.unhide-forensics.info/使用的方法。我们的想法是将PID从真实转换为假,从伪转换为真实,使工具看到隐藏的PID,而不是隐藏的连续标识符。 我的意思是,这个工具不断地分叉并检查在所有循环中是否有一些忙碌的PID并且它不可见。 这个想法是通过提供连续的PID来欺骗这个工具。

问题是,当一个程序执行sys_clone的钩子处理程序时,在返回之后,wait(& status)会给出一个SEGVFAULT信号。

我遵循的方法与我用来挂钩所有其他系统调用的方法相同,包括sys_forksys_vfork和其他系统调用。

sys_vforksys_fork使用相同的函数_do_fork()来创建新流程: https://elixir.bootlin.com/linux/v4.15.1/source/kernel/fork.c#L2148

https://elixir.bootlin.com/linux/v4.15.1/source/kernel/fork.c#L2111

https://elixir.bootlin.com/linux/v4.15.1/source/kernel/fork.c#L2123

EDITED : 我创建了一个最小的工作示例,重现了这个bug,所以你可以更好地测试它,抱歉因为我之前没有:

#include <linux/module.h>
#include <linux/uaccess.h>

void **sys_call_table = NULL;
asmlinkage long (*sys_read)(long a1, long a2, long a3, long a4, long a5, long a6) = NULL;
asmlinkage long (*sys_clone)(long a1, long a2, long a3, long a4, long a5, long a6) = NULL;
asmlinkage long my_clone64(long a1, long a2, long a3, long a4, long a5, long a6);

char *sct_str;

module_param(sct_str, charp, 0);

inline void disable_wp(void) {
        asm("cli\n\tmov\t%cr0, %rax\n\tand\t$0xfffffffffffeffff, %rax\n\tmov\t%rax, %cr0\n\tsti");
}

inline void enable_wp(void) {
        asm("cli\n\tmov\t%cr0, %rax\n\tor\t$0x10000, %rax\n\tmov\t%rax, %cr0\n\tsti");
}

int init_module(void) {
        int ret = 0;
        mm_segment_t old_fs;

        kstrtoul(sct_str, 16, (unsigned long *) &sys_call_table);
        printk("%lx\n", sys_call_table);

        if (!sys_call_table) {
                return -1;
        }

        sys_read = sys_call_table[__NR_read];
        sys_clone = sys_call_table[__NR_clone];

        // hook sys_clone
        printk("be\n");
        disable_wp();
        sys_call_table[__NR_clone] = my_clone64;
        enable_wp();
        printk("af\n");

        // wait user's ENTER
        old_fs = get_fs();
        set_fs(KERNEL_DS);
        sys_read(0, (long)&ret, 1, 0, 0, 0);
        set_fs(old_fs);

        // restore sys_clone
        disable_wp();
        sys_call_table[__NR_clone] = sys_clone;
        enable_wp();

        return -1;
}

void cleanup_module(void) {
}


asmlinkage long my_clone64(long a1, long a2, long a3, long a4, long a5, long a6) {
        long ret = 0;
        printk("pid = %d\n", ret);
        ret = sys_clone(a1, a2, a3, a4, a5, a6);
        return ret;
}

MODULE_LICENSE("GPL");

使用此Makefile编译:

obj-m += so.o

KERNEL_HEADERS = /lib/modules/$(shell uname -r)/build

all:
        make V=1 -C $(KERNEL_HEADERS) M=$(PWD) modules

clean:
        make V=1 -C $(KERNEL_HEADERS) M=$(PWD) clean

使用make进行编译并加载do:

diwou@diwou-VirtualBox:~/arpso$ sudo grep sys_call_table /proc/kallsyms
ffffffff9de00180 R sys_call_table
ffffffff9de01540 R ia32_sys_call_table
diwou@diwou-VirtualBox:~/arpso$ sudo insmod so.ko sct_str="ffffffff9de00180"
[NOW PRESS ENTER TO UNHOOK]
insmod: ERROR: could not insert module so.ko: Operation not permitted
diwou@diwou-VirtualBox:~/arpso$

Operattion not permitted归因于我使用的返回码(-1)来自动卸载模块。

这是其他会话的输出,当sys_clone被挂钩时。我运行/bin/ls

diwou@diwou-VirtualBox:~$ ls
Violación de segmento (`core' generado)

取消挂钩sys_clone后(按ENTER键),命令ls再次起作用。

修改 如果您使用以下代码替换my_clone64,则会发生相同的情况。但是,如果您按call更改jmp,则可以正常工作:

asm(
".globl my_clone64\n\t"
".type my_clone64, @function\n"
"my_clone64:\n\t"
"call sys_clone(%rip)\n\t"
// do something with %rax
"ret\n\t"
".size my_clone64, .-my_clone64\n\t"
);

这指向Linux内核中的某种实现......我是对的吗?

修改 有趣的是,当我在一个ssh会话中运行strace -f bash并且read(0,等待我的输入时,我在另一个会话中加载LKM,并在ls中写入bash ls被追踪,并且有效。我可以看到ls命令的输出。 但是如果我打开一个ssh会话,我加载LKM,并在bash上写clone(strace: Process 3233 attached child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa5f68be9d0) = 3233 [...] [pid 3233] stat("arpso", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0 [pid 3233] open("arpso", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3 [pid 3233] fstat(3, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0 [pid 3233] getdents(3, /* 15 entries */, 32768) = 480 [pid 3233] lstat("arpso/so.c", {st_mode=S_IFREG|0664, st_size=1575, ...}) = 0 [pid 3233] lstat("arpso/so.ko", {st_mode=S_IFREG|0664, st_size=5760, ...}) = 0 [pid 3233] lstat("arpso/modules.order", {st_mode=S_IFREG|0664, st_size=31, ...}) = 0 [pid 3233] lstat("arpso/Module.symvers", {st_mode=S_IFREG|0664, st_size=0, ...}) = 0 [pid 3233] lstat("arpso/so.o", {st_mode=S_IFREG|0664, st_size=5440, ...}) = 0 [pid 3233] lstat("arpso/so.mod.o", {st_mode=S_IFREG|0664, st_size=2528, ...}) = 0 [pid 3233] lstat("arpso/so.mod.c", {st_mode=S_IFREG|0664, st_size=542, ...}) = 0 [pid 3233] lstat("arpso/Makefile", {st_mode=S_IFREG|0664, st_size=177, ...}) = 0 [pid 3233] getdents(3, /* 0 entries */, 32768) = 0 [pid 3233] close(3) = 0 [...] +++ exited with 0 +++ diwou@diwou-VirtualBox:~$ ls Violación de segmento (`core' generado) diwou@diwou-VirtualBox:~$ 而不进行跟踪,只是一个常见的ssh会话,输出是分段错误:

require 'dotenv/load'
require 'faraday'

class OverviewController < ApplicationController

  def api_key
    ENV["API_KEY"]
  end

  def url
    "https://example.com"+api_key
  end

  def index
    conn = Faraday.new(url, request: {open_timeout: 1, timeout: 1}) do |c|
      c.response :json, :content_type => /\bjson$/
      c.adapter Faraday.default_adapter
    end

  response = conn.get url
  @hash = response.body['data']

  end
end

任何帮助都将不胜感激。

感谢。

1 个答案:

答案 0 :(得分:1)

我找到了答案。

只是普通挂钩的问题(也就是替换系统调用表中的系统调用处理程序)实际上(至少在内核4.15.1中),定义了一个名为ptregs_sys_clone的额外符号,这最终会产生一些技巧call/jmpsys_clone。并且嵌套调用不是一个选项,因为retaddr被检查来自一个或另一个路径......所以我找到的解决方案是替换sys_clone中对ptregs_sys_clone的引用。

代码如下。

core.c

#include <linux/uaccess.h>

void **sys_call_table = NULL;
asmlinkage long (*sys_read)(long a1, long a2, long a3, long a4, long a5, long a6) = NULL;
asmlinkage long (*ptregs_sys_clone)(struct pt_regs *regs) = NULL;
asmlinkage long (*sys_clone)(long a1, long a2, long a3, long a4, long a5, long a6) = NULL;
asmlinkage long my_clone64(long a1, long a2, long a3, long a4, long a5, long a6);

extern char *sct_str;

void disable_wp(void) {
        asm("cli\n\tmov\t%cr0, %rax\n\tand\t$0xfffffffffffeffff, %rax\n\tmov\t%rax, %cr0\n\tsti");
}

void enable_wp(void) {
        asm("cli\n\tmov\t%cr0, %rax\n\tor\t$0x10000, %rax\n\tmov\t%rax, %cr0\n\tsti");
}

int patch_ptregs_syscall(void *addr, long newaddr, long *oldaddr) {
        int i = 0, ret = 0, *p = NULL;
        long vaddr = 0; // variable address

        p = addr + 3; // point to offset in: lea offset(%rip), %register
        //printk("value before patch = %lx\n", *p);
        vaddr = (long)*p + addr + 7; // offset + %rip + lea' size
        if (oldaddr) {
                *oldaddr = vaddr;
        }
        printk("address %lx, ofsset %lx\n", vaddr, *p);
        vaddr = newaddr - (long)addr - 7;
        printk("new address %lx, new offset %lx\n", newaddr, vaddr);
        disable_wp();
        ret = probe_kernel_write(p, &vaddr, sizeof(int));
        enable_wp();
        if (ret != 0) {
                return -2;
        }

        return 0;
}

int install_hooks(void) {
        int ret = 0;
        mm_segment_t old_fs;

        if (!sct_str) {
                return -2;
        }

        kstrtoul(sct_str, 16, (unsigned long *) &sys_call_table);
        printk("sct: %lx\n", sys_call_table);

        if (!sys_call_table) {
                return -2;
        }

        sys_read = sys_call_table[__NR_read];
        ptregs_sys_clone = sys_call_table[__NR_clone];

        // hook sys_clone
        patch_ptregs_syscall(ptregs_sys_clone, (long)my_clone64, (long *)&sys_clone);

        // wait user's ENTER
        old_fs = get_fs();
        set_fs(KERNEL_DS);
        sys_read(0, (long)&ret, 1, 0, 0, 0);
        set_fs(old_fs);

        // restore sys_clone
        patch_ptregs_syscall(ptregs_sys_clone, (long)sys_clone, NULL);

        return -1;
}

asmlinkage long my_clone64(long a1, long a2, long a3, long a4, long a5, long a6) {
        pid_t pid = 0;

        pid = sys_clone(a1, a2, a3, a4, a5, a6);
        printk("pid %d\n", pid);
        return pid;
}

main.c中:

#include <linux/module.h>

extern int install_hooks(void);

char *sct_str;

module_param(sct_str, charp, 0);

int init_module(void) {
        return install_hooks();
}

void cleanup_module(void) {
}

MODULE_LICENSE("GPL");

生成文件

obj-m += so.o

so-objs := main.o core-asm.o

EXTRA_CFLAGS := -O0

KERNEL_HEADERS = /lib/modules/$(shell uname -r)/build

all:
        make V=1 -C $(KERNEL_HEADERS) M=$(PWD) core.s
        gcc -c core.s -o core-asm.o
        make V=1 -C $(KERNEL_HEADERS) M=$(PWD) modules

clean:
        make V=1 -C $(KERNEL_HEADERS) M=$(PWD) clean
祝你好运,感谢你的所有评论&amp;帮助