预计阅读本页时间:-
9.6 利用代码漏洞
前面已经介绍了内部人员是如何危害系统安全的,在本节中,我们将介绍外部人员(outsider)(主要通过互联网)对操作系统进行攻击和破坏的方式。几乎所有的攻击机制都利用了操作系统或是被广泛使用的软件(如IE浏览器和微软Office)中的漏洞。一种典型的攻击形成方式是,有人发现了操作系统中的一个漏洞,接着发现如何利用该漏洞攻击计算机。
每一种攻击都涉及特定程序中的特定漏洞,其中利用某些反复出现的漏洞展开的攻击值得我们学习。在本节中,我们将研究一些攻击的工作原理,由于本书的核心是操作系统,因此重点将放在如何攻击操作系统上,而利用系统和软件漏洞对网页和数据库的攻击方式本节都没有涉及。
有很多方式可以对漏洞进行利用,在一种直接的方法中,攻击者会启动一个脚本,该脚本按顺序进行如下活动:
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
1)运行自动端口扫描,以查找接受远程连接的计算机。
2)尝试通过猜测用户名和密码进行登录。
3)一旦登录成功,则启动特定的具有漏洞的程序,并产生输入使得程序中的漏洞被触发。
4)如果该程序运行SETUID到root,则创建一个SETUID root shell。
5)启动一个僵尸程序,监听IP端口的指令。
6)对目标机器进行配置,确保该僵尸程序在系统每次重新启动后都会自动运行。
上述脚本可能会运行很长时间,但是它有很可能最终成功。攻击者确保只要目标计算机重新启动时,僵尸程序也启动,就使得这台计算机一直被控制。
另一种常用的攻击方式利用了已经感染病毒的计算机,在该计算机登录到其他机器的时候,计算机中的病毒启动目标机器中的漏洞程序(就像上面提到的脚本一样)。基本上只有第一步和第二步与上述脚本文件不同,其他步骤仍然适用。不论哪种方法,攻击者的程序总是要在目标机器中运行,而该机器的所有者对该恶意程序一无所知。
9.6.1 缓冲区溢出攻击
之所以有如此多的攻击是因为操作系统和其他应用程序都是用C语言写的(因为程序员喜欢它,并且用它来进行有效的编译)。但遗憾的是,没有一个C编译器可以做到数组边界检查。如下面的代码虽然并不合法,但系统却没有进行检验:
int I;
char c[1024];
i=12000;
c[i]=0;
结果内存中有10 976个字节超出了数组c的范围,并有可能导致危险的后果。在运行时没有进行任何检查来避免这种情况。
C语言的属性导致了下列攻击。在图9-24a中,我们看到主程序在运行时局部变量是放在栈里的。在某些情况下,系统会调用过程A,如图9-24b所示。标准的调用步骤是把返回地址(指向调用语句之后的指令)压入栈,然后将程序的控制权交给A,由A不断减少栈指针地址来分配本地变量的存储空间。

假设过程A的任务是得到完整的路径(可能是把当前目录路径和文件名串联起来),然后打开文件实施一些操作。A拥有固定长度的缓冲区(如数组)B,它存放着文件名,如图9-24b所示。使用定长缓冲区存放文件名比起先检测实际大小再动态分配空间要容易得多。如果缓冲区只有1024个字节,那么能够放得下所有的文件名吗?特别是当操作系统把文件名的长度限制(或者更好的是对全路径名的长度限制)在不超过255(或其他固定的长度)个字符时。
然而,上述推论有致命的错误。假设用户提供了一个长达2000个字符的文件名,在使用时就会出错,但攻击者却不予理会。当过程A把文件名复制到缓冲区时,文件名溢出并覆盖了图9-24c的灰色部分。更糟的是,如果文件名足够长,它还会覆盖返回地址,这样当过程A返回时,返回地址是从文件名的中间截取的。如果这一地址是随机数,系统将跳到该随机地址,并可能引起一系列的误操作。
但是如果文件名没有包含某些随机地址会怎么样呢?如果它包含的是有效的二进制地址并且设计得十分吻合某个过程的起始地址,那又会怎么样呢?例如吻合过程B的起始地址。如果真是这样,那么当过程A运行结束后,过程B就开始运行。实际上,攻击者会用他的恶意代码来覆盖内存中的原有代码,并且让这些代码被执行。
同样的技巧还运用于文件名之外的其他场合。如用在对较长的环境变量串、用户输入或任何程序员创建了定长缓冲区并需要用户输入变量的场合。通过手工输入一个含有运行程序的串,就有可能将这段程序装入到栈并让它运行。C语言函数库的gets函数可以把(未知大小的)串变量读入定长的缓冲区里,但并不校验是否溢出,这样就很容易遭受攻击。有些编译器甚至通过检查gets的使用来发出警告。
现在我们来讨论最坏的部分。假设被攻击的UNIX程序的SETUID为root(或在Windows里拥有管理员权限的程序),被插入的代码可以进行两次系统调用,把攻击者磁盘里的shell文件的权限改为SETUID root的权限,这样当程序运行时攻击者就拥有了超级用户的权限。或者,攻击者可以映射进一个特定的共享文件库,从而实施各种各样的破坏。还可以十分容易地通过exec系统调用来覆盖当前shell中运行的程序,并利用超级用户的权限建立新的shell。
更糟的是,恶意代码可以通过互联网下载程序或脚本,并将其存储在本地磁盘上。此后该恶意代码就可以创建一个进程直接从本地运行恶意程序或是脚本。该进程可以一直监听IP端口,从而等待攻击者的命令,这将目标机器变为僵尸。恶意代码必须保证每次机器启动后,恶意程序或脚本可以被启动,然而不论在Windows或所有版本的UNIX系统下,这都是很容易实现的。
绝大多数系统安全问题都与缓冲区溢出漏洞相关,而这类漏洞很难被修复,因为已有的大量C代码都没有对缓冲区溢出进行检查。