技术开发 频道

Linux内核级后门的原理和简单实战

    【IT168 技术文档】

    让我们看一下另一个write(2)例程并看看它是如何进行预处理的。

[root@plaguez kernel]# cat no2.c #include <linux/types.h> #include <linux/fs.h> #include <sys/syscall.h> #include <asm/unistd.h> #include <sys/types.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <ctype.h> _syscall3(ssize_t,write,int,fd,const void *,buf,size_t,count);/*构建一个write调用*/ main() { char *t = "this is a test.n"; write(0, t, strlen(t)); } [root@plaguez kernel]# gcc -E no2.c > no2.C [root@plaguez kernel]# indent no2.C -kr indent:no2.C:3304: Warning: old style assignment ambiguity in "=-". Assuming "= -" [root@plaguez kernel]# tail -n 50 no2.C #9 "no2.c" 2 ssize_t write(int fd, const void *buf, size_t count) { long __res; __asm__ __volatile("int $0x80":"=a"(__res):"0"(4), "b"((long) (fd)), "c"((long) (buf)), "d"((long) (count))); if (__res >= 0) return (ssize_t) __res; errno = -__res; return -1; }; main() { char *t = "this is a test.n"; write(0, t, strlen(t)); } [root@plaguez kernel]# exit

    注意那个write()里的"0"这个参数匹配SYS_write,在/usr/include/sys/syscall.h中定义。 

    * 构建你自己的系统调用。 

    这里给出了几个构建你自己的系统调用的方法。举个例子, 你可以修改内核代码并且加入你自己的代码。一个比较简单可行的方法, 不过, 一定要被写成可加载内核模块。 

    没有一个代码可以象可加载内核模块那样可以当内核需要的时候被随时加载的。 

    我们的主要意图是需要一个很小的内核,当我们需要的时候运行insmod命令,给定的驱动就可以被自动加载。这样卸除来的lkm程序一定比在内核代码树里写代码要简单易行多了。 

    * 写lkm程序 

    一个lkm程序可以用c来很容易编写出来。它包含了大量的 #defines, 一些函数, 一个初始化模块的函数,叫做init_module(),和一个卸载函数:cleanup_module()。 

    这里有一个经典的lkm代码结构:

#define MODULE #define __KERNEL__ #define __KERNE_SYSCALLS__ #include <linux/config.h> #ifdef MODULE #include <linux/module.h> #include <linux/version.h> #else #define MOD_INC_USE_COUNT #define MOD_DEC_USE_COUNT #endif #include <linux/types.h> #include <linux/fs.h> #include <linux/mm.h> #include <linux/errno.h> #include <asm/segment.h> #include <sys/syscall.h> #include <linux/dirent.h> #include <asm/unistd.h> #include <sys/types.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <ctype.h> int errno; char tmp[64]; /* 假如,我们要用到ioctl调用 */ _syscall3(int, ioctl, int, d, int, request, unsigned long, arg); int myfunction(int parm1,char *parm2) { int i,j,k; /* ... */ } int init_module(void) { /* ... */ printk("nModule loaded.n"); return 0; } void cleanup_module(void) { /* ... */ }

    检查代码中的 #defines (#define MODULE, #define __KERNEL__)和#includes (#include <linux/config.h> ...) 

    一定要注意的是我们的lkm讲要被运行在内核状态,我们就不能用libc包装的函数了, 但是我们可以通过前面所讨论的_syscallX()宏来构建系统调用。 

    你可以这样编译你的模块'gcc -c -O3 module.c' 并且利用'insmod module.o'来加载。

提一个建议, lkm也可以用来在不完全重建核心代码的情况下来修改内核代码。举个例子, 你可以修改write系统调用让它隐藏一部分给定的文件,就象我们把我们的backdoors放到一个非常好的地方:当你无法再信任你的系统内核的时候会怎么样呢? 

    * 内核和系统调用后门 

    在简单介绍了上述理论,我们主要可以用来做什么呢。我们可以利于lkm截获一些对我们有影响的系统调用, 这样可以强制内核按照我们的方式运行。例如:我们可以利用ioctl系统调用来隐藏sniffer所造成的网卡PROMISC模式的显示。非常有效。 

    去改变一个给定的系统调用,只需要在你的lkm程序中增加一个定义extern void *sys_call_table[],并且利用init_module()函数来改变sys_call_table里的入口来指向我们自己的代码。改变后的调用可以做我们希望它做的一切事情, 利用改变sys_call_table来导出更多的原系统调用。

    译者后话:这篇文章相对比较浅显易懂,所以可以作为大家入门lkm编程来用,它着重讲述了linux系统调用system call的原理,以及我们如何通过lkm来截获它并换成我们想要的代码来建立后门程序。再次强调本文的依据是linux内核版本2.0.x,大家在自己系统实现时请对比内核代码来做改变。

0
相关文章