摘要:在这篇文章里, 我们将看到各种不同的后门技术,特别是Linux的可装载内核模块(LKM)。 我们将会发现lkm后门比传统的后门程序更加复杂,更加强大,更不易于被发现。知道这些之后,我们可以制造我们自己的基于lkm的rootkit程序, 主要体现在tcp/ip层, 因为我们相信这是在系统管理员面前最好的隐藏后门的地方。 翻译:e4gle(大鹰) Backdoor and Linux LKM Rootkit - smashing the kernel at your own risk 来源:CS290I Project Report 译:alert7,el8 from m4in security teams. CS290I Project Report Backdoor and Linux LKM Rootkit - smashing the kernel at your own risk by: Jingyu Zhou and Lin Qiao 简介: 在这篇文章里, 我们将看到各种不同的后门技术,特别是linux的可装载内核模块(LKM)。 我们将会发现lkm后门比传统的后门程序更加复杂,更加强大,更不易于被发现。知道这些之后,我们可以制造我们自己的基于lkm的rootkit程序, 主要体现在tcp/ip层, 因为我们相信这是在系统管理员面前最好的隐藏后门的地方。 序言。 在一些黑客组织中, rootkit (或者backdoor) 是一个非常感兴趣的话题。 各种不同的rootkit被开发并发布在internet上。在这些rootkit之中, LKM尤其被人关注, 因为它是利用现代操作系统的模块技术。作为内核的一部分运行,这种rootkit将会越来越比传统技术更加强大更加不易被发觉。一旦被安装运行到目标机器上, 系统就会完全被控制在hacker手中了。甚至系统管理员根本找不到安全隐患的痕迹, 因为他们不能再信任它们的操作系统了。 本文章以及我们开发的一些强大的lkm程序都是基于linux kernel 2.2.x版本的。我们的目的是尽可能多的隐藏足迹。 在接下来的一部分, 我们将介绍一下已经存在的后门技术, 然后和lkm技术相比较, 最后讨论我么的lkm程序的设计与实现。 已存在的后门技术 - 系统管理员的悲惨世界。~0~ 后门程序的目的就是甚至系统管理员企图弥补系统漏洞的时候也可以给hacker系统的访问权限。 后门程序使本地用户取得root权限可以这样做: 设置uid程序, 系统木马程序, cron后门。 1. 设置uid程序。 黑客在一些文件系统理放一些设置uid脚本程序。无论何时它们只要执行这个程序它们就会成为root。 2. 系统木马程序。黑客替换一些系统程序, 如"login"程序。因此, 只要满足一定的条件,那些程序就会给黑客最高权限。 3. Cron 后门。黑客在cron增加或修改一些任务, 在某个特定的时间程序运行,他们就可以获得最高权限。 后门程序给远程用户以最高访问权限可以这样做: ".rhost" 文件, ssh认证密钥, bind shell, 木马服务程序。 1. ".rhosts" 文件。一旦 "+ +"被加入某个用户的.rhosts文件里, 任何人在任何地方都可以用这个账号来登陆进来而不需要密码。 2. ssh 认证密钥。黑客把他自己的公共密钥放到目标机器的ssh配置文件"authorized_keys"里, 他可以用该账号来访问机器而不需要密码。 3. Bind shell。黑客绑定一个shell到一个特定的tcp端口。任何人telnet这个端口都可以获得交互的shell。更多精巧的这种方式的后门可以基于udp,或者未连接的tcp, 甚至icmp协议。 4. Trojaned服务程序。任何打开的服务都可以成为木马来为远程用户提供访问权限。例如, 利用inetd服务在一个特定的端口来创建一个bind shell,或者通过ssh守护进程提供访问途径。 在入侵者植入和运行后门程序之后, 他会找一些方法和系统管理员开一些善意的玩笑。这主要涉及到两个方面问题: 如何来隐藏他的文件且如何来隐藏他的进程。 为了隐藏文件, 入侵者需要做如下事情: 替换一些系统常用命令如"ls", "du", "fsck"。在底层方面, 他们通过把硬盘里的一些区域标记为坏块并把它的文件放在那里。或者如果他足够疯狂,他会把一些文件放入引导块里。 为了隐藏进程, 他可以替换 "ps"程序, 或者通过修改argv[]来使程序看起来象一个合法的服务程序。有趣的是把一个程序改成中断驱动的话,它就不会出现在进程表里了。 LKM - 还有比这个更臭屁的么? 我们已经看到过一些常规的技术。现在的问题是: 系统管理员可以找出它们么?实际上, 一个好的系统管理员可以很轻易的找出它们中的%99。 问题是入侵者必须修改或者创建一些重要文件。 如果系统管理员保存一份"tripwire"数据库, 通过这些可以确定安全隐患的存在。通过浏览文件系统可以去掉suid程序, ".rhosts" 文件, 等。 相反, 利用lkm我们可有效的突破这些限制。首先,我们在重要的系统目录里不必修改或创建任何文件。我们可以把lkm程序放在/tmp或/var/tmp目录下, 一般系统管理员是不会监视这些目录的。 其次, 我们可以隐藏我们想要的任何东西, 象文件, 进程, 和网络连接。 因为要得到这些信息, 用户必须依赖系统调用。 因此我们可以修改内核结构, 我们可以用我们自己的函数来替换原系统调用。最后,我们甚至可以攻击或修改tcp/ip协议栈并且去愚弄系统内核! 以下部分,我们将介绍如何利用这些机制以及实现方法。 我们的LKM - "Kicking Kernel Ass From Left to Right" [3] 我们的lkm程序主要是基于linux kernel 2.2.x及tcp/ip上的实现, 因为一个优秀的后门程序一定会给远程用户访问该系统的权限。在目标机器上打开一个端口,运行一个服务是非常容易暴露的。我们需要尽可能的隐藏自己。 第一个想法是我们在目标机器上不运行任何进程来等待连接,我们在tcp/ip协议栈里来创建一个函数来替代它。 无论何时一个特殊的udp或tcp包被接受,内核将会检查这个包来确定是否是指定的特殊包。假如是的话, 内核将派生一个进程来执行命令。我们可以使用任何内核可以支持的协议包。 现在我们来实现它。在内核里, 每个协议在*inet_protocol_base和*inet_protos[MAX_INET_PROTOS] hash注册自己。 当系统初始化时, 所有支持的协议会再inet_protocol_base注册。他们被加到inet_protos的哈希表里。不管什么时候一个IP包达到时, 内核将检查这个哈希表,找相应的处理函数和系统调用。我们就在这个点上进行hack。我们将用我们的处理函数来替换原始的协议的处理函数。因此,我们可以截获数据包并且分析它。假如它是我们需要的, 我们将执行我们的命令。 假如不是,仅仅只需要调用原来的函数。 我们同时处理TCP和UDP的原因是假如那里有一些防火墙的话,UDP可能不能穿过。因此,我们只需要发一个源地址被伪造的数据包到目的机子。此外,对于TCP的数据包,它也不需要使用SYN位。事实上,现在我们的客户程序使用的是ACK的包。 第二个想法更使人感兴趣。 如果一台目标的机子上有个WEB的服务并且安了一个只允许WEB通信的防火墙,那么我们如何来穿过它呢? 我们能否得到一个交互的shell呢?答案是肯定的。方法如下: ____________ _________________________ 攻击者 web server 80 <=======> 53333 __________ _______________________ ____________________________________ 1025 ==> 80 or 1025 <== 80 假设我们在web服务器上已经绑定了一个bind shell后门并且监听53333端口(可以利用第一个方法来完成) 现在我们需要把攻击者到web服务器上的流量从80端口重定向到53333端口, 从53333端口到攻击者的流量必须被改成80端口。 实现部分。改变接收的包是很容易的, 我们可以借用第一个lkm的思路- 无论何时我们都检查到来的tcp包如果必要我们修改它的目的端口。为了改变发出的包, 这就有点困难了。 因为tcp/ip协议栈的实现涉及到linux内核的一些底层的静态函数。它不太容易被置换(但是是可能的, 细节参见附录)。 我们利用的是大部分发布时就被编译进内核中的防火墙。每个到来的包,转发的包, 或发出的包必须通过防火墙。并且防火墙函数是可以被动态地加载到内核里的!我们利用系统导出函数register_firewall() 在系统防火墙规则之前插入我们自己的规则。假如我们发现一些来自于53333端口的包, 我们可以自动改变它到80。 关于此实现的另外的细节是无论何时我们改变数据包, 我们必须去重新计算校验和。 更有趣的事情是我们可以在web服务器和其他一些机器上监听网络流量,我们可以看到他们的不同之处。 在其他机器上的sniffer看起来象普通的web流量, 但是在web服务器上的sniffer是一些无用的流量纪录。具体细节参见附录。 现在我们谈一下如何来截获系统调用。为了隐藏入侵者的足迹, 文件, 进程,网络连接必须隐藏起来。 因为这些信息都是可以从特殊的系统调用里面获得的, 我们可以接获一些感兴趣的系统调用。 1. 隐藏文件。象这些命令如"ls", "du" 使用sys_getdents() 来获得目录信息。 所以lkm程序必须过滤这些输出来达到隐藏文件的目的。 2. 隐藏进程。在linux的实现中,进程的信息被映射到/proc文件系统去了。我们的工作仍旧是捕获sys_getdents()调用在进程链表中标记为不可见。通常的手法是设置任务的信号标志位为一些未用的信号量,比如31就是一个例子。 3. 隐藏网络连接。 和隐藏进程相似, 在这个例子中我们是这去隐藏一些包括/proc/net/tcp和/proc/net/udp的文件。所以我们改变sys_read()。 无论何时读包含匹配字符串的这两个文件的时候, 系统调用将不会声明在使用它。 4. 重定向可执行文件。 有时候, 入侵者可能会需要替换系统的二进制文件, 象"login", 但不想改变原文件。他可以截获sys_execve()。因此, 无论何时系统尝试去执行"login"程序的时候, 它都会被重定向到入侵者给定的其他程序。 5. 隐藏sniffer。这儿我们指隐藏网络接口的杂拨模式。在这里我们要替换的是sys_ioctl()。 6. 和lkm通信。 黑客已经很好的安装了他的lkm。现在他需要告诉内核来隐藏其他文件。他该怎么做呢?我们知道从用户态切换到和心态通常是通过系统调用来进行的, 所以我们必须修改一些系统调用。 例如, 我们将截获sys_settimeofday()。当一个指定的参数被传递, 我们的系统调用将会为我们做一些适当的事情。 7. 隐藏lkm本身。一个优秀的lkm程序必须很好地隐藏它自己。系统里的lkm是用单向链表连接起来的, 为了隐藏lkm本身我们必须把它从链表中移走以至于lsmod这样的命令不能把它显示出来。 8. 隐藏符号表。通常的lkm中的函数将会被导出以至于其他模块可以使用它。因为我们是入侵者, 所以隐藏这些符号是必须的。幸运的是, 有一个宏可以供我们使用:"EXPORT_NO_SYMBOLS"。 把这个宏放在lkm的最后可以防止任何符号的输出。 经验和结论。 做个lkm程序是一个非常有趣而又非常危险的事情。有趣的是你可以在系统内核中作你想做的事情。 但这也是非常危险的, 它可以使你的服务陷入混乱, 破坏你的数据, 并且可以在你的系统里做任何怪异的事情。 我们的经验有: 在安装了lkm程序几天后我们的网络层不工作了, 只工作五分钟就要重起一次; 无论何时发送数据包, 象这些应用程序 telnet, netscape, pine都将会产生core dump; 在安装lkm程序后立马重起。所以, 就象标题所说的那样,后果自负! 值得一提的是写一个lkm程序你可以更好地了解到系统是如何工作的。例如, /proc文件系统有很好的特性。因为lkm程序工作在内核空间, 调试lkm程序就变得比一般程序要困难。 使用"printk"函数可以解决一些问题。但这不是最好的解决方法。通过注册在/proc文件系统里的我们的文件和目录的数据结构, 我们可以访问到任何时间的内核空间的信息。我们甚至可以通过写这个文件来修改内存, 尽管一般不建议这样做。 结束语 从经验来看, 很明显的lkm程序可以在linux上安装,一旦系统被攻破并且被安装了lkm的rootkit程序, 这就变的很难被发现了。因为甚至操作系统都不能信任了。如果机器不允许关机,唯一的发现入侵者的方法是通过分析在网络其他机器上的sniffer结果。 或者, 利用其他的操作系统来监测硬盘。所有这两个方法都很难去做, 因为你不知道你要找什么。 所以,所以最好的安全措施就是防止被攻击者入侵系统。 参考资料。 1. Bypassing Integrity Checking Systems. http://phrack.infonexus.com/search.pHtml?view&article=p51-9 2. Weakening the Linux Kernel. http://phrack.infonexus.com/search.phtml?view&article=p52-18 3. Building Into The Linux Network Layer. http://phrack.infonexus.com/search.phtml?view&article=p55-12 4. (nearly) Complete Linux Loadable Kernel Modules. http://packetstorm.securify.com/groups/thc/LKM_HACKING.html 5. Backdoors. http://www.dataguard.no/bugtraq/1997_3/0310.html 6. Runtime Kernel Kmem Patching. http://www.big.net.au/~silvio/runtime-kernel-kmem-patching.txt 附录 1. 替换协议例程来改变发送数据包。 在linux内核中,每个BSD socket 在内核结构中实际上是一对socket/sock。发送数据包的例程是利用sock结构中的tcp_opt*成员来实现的, 轮流使用af_specific成员。有意思的是这里所有的ipv4都使用相同的地址, 例如, 所有的af_specific 将指向同样的内核地址, 这个结构地址保存了一个例程集合。我们怎么在"tcp_func"这个实力中修改函数地址呢? 方法很简单, 但实现起来却不那么容易。因为没有一个很简单的方法去得到那个结构的地址。一个可能的方法是: 从进程链表(任务结构链表)中找出它打开的文件; 然后从这些文件中找出实际指向socket的地址; 从socket可以得到sock结构地址; 最后获得"ipv4_specific"的地址。 我们来理顺以下上面所说的流程: task -> files_strUCt -> file -> inode -> socket -> sock -> tcp_opt -> tcp_func. 其他的方法就更加困难和危险了。这个灵感是来自于[6]。我们可以通过搜索内核内存来找到我们要的明确的函数地址。然后修改内核内存。首先我们跳转到我们代码的地址然后跳回常规例程。 2. 从web服务器和从网络工作站上的流量监听。 在这段中, mamet是一个装了我们的后门的服务器且leone是一个攻击者。 开始的tcpdump输出纪录了除了mamet之外的一些网络工作站的流量。我们可以烦先这时正常的。leone的2603端口 和mamet的80端口的通信: 14:16:27.214888 eth0 > leone.cs.ucsb.edu.2603 > mamet.cs.ucsb.edu.www: S 3840116896:3840116896(0) win 32120
(出处:http://www.sheup.com)