2024年4月16日发(作者:)

The Shellcoder’s Handbook: Discovering and Exploiting Security Holes(七)

7

Windows Shellcode

本书一位作者的女友经常说写shellcode很容易,凭心而论,在Linux上是不太难,但

在Windows上还是有一定难度的,有时候会使人垂头丧气。在开始学习本章之前,我们将

先回顾一些shellcode的要点,然后研究Windows shellcode那令人着迷的特性。沿着这条主

线,我们还将讨论AT&T与Intel句法间的不同,某些Win32漏洞给我们带来的影响,并探

讨高级Windows shellcode的发展方向。

句法和过滤器

首先,不使用编码器/译码器又能工作,而体积又很小的windows Shellcode少之又少。

无论如何,如果有许多破解代码需要你去完成,你可能会想到在破解代码中采用编码器/译

码器 API来避免经常调整shellcode。例如Immunity CANVAS就使用了“附加的”编码器/

译码器。也就是说,它把shellcode视为一组unsigned long列表,把列表中的每个unsigned long

加上x(x可以通过不断重试随机数来找到),经过这样的处理,会得到一组新的没有坏字符

的unsigned long列表。虽然编码器/译码器工作得很好,但还是有人乐意使用XOR、character-

或word-based之类的方法。

重要的是:应该牢记译码器只是把x扩展到不同字符范围的y=f(x)函数。如果x仅仅包

含小写字母,那么可以把f(x) 看作是把小写字母转换成二进制字符并转到那里的函数;当

然,也可以把f(x) 看作是把小字字母转换成大字字母并转到那里的函数。换句话说,当你

遇到设置严密的过滤器(译注:现在有很多程序在接受用户输入时,会过滤一些恶意字符)

时,不要急于一次解决所有的问题,尝试使用多重解码,把攻击串分段转换为二进制等方法,

可能会更容易些。

在本章,我们不介绍编码器/译码器,并假设你知道怎样把二进制数据复制到进程空间

并跳到它。只要你会写Linux shellcode,就应该能编写x86汇编代码。我写windows shellcode

和写Linux shellcode一样,使用相同的工具。从长远来看,熟练掌握几种工具会使编写

shellcode变得更轻松。依我之见,不必花大把的钞票购买Visual Studio,免费的Cygwin

就不错。安装Cygwin可能有点慢,所以你可以试着运行某个程序(gcc,()

as,或其它),来确认安装是否完成。当然,有些人喜欢用NASM或其它的工具,但我认为

这些工具在编辑代码及测试时略有不便。

X86 AT&T 与Intel 句法的对比

在X86汇编代码格式里,AT&T与Intel句法有两个主要的不同点。第一个是AT&T句

法使用[助记符source,dest];而Intel使用[助记符dest,source]。当人们用GUN的gas(AT&T

使用),OllyDbg(Intel 使用)或其它的Windows工具查看汇编代码时,这种互相颠倒的形

欢迎访问,翻译@。

103

The Shellcoder’s Handbook: Discovering and Exploiting Security Holes(七)

式可能会使人摸不着头脑。当然,假如你可以灵活转换这种形式,那么在At&T与Intel之

间还有另外一个重要的不同点:寻址。

X86的寻址有如下的形式:两个寄存器,一个位移量,一个比例因子,如1,2,4或8。

所以,mov eax,[ecx+ebx*4+5000] (OllyDbg中的Intel 句法)等同于mov

5000(%ecx,%ebx,4),%eax (GUN AS中的AT&T句法)。

我建议大家学习AT&T句法,理由是它的句法清晰(译注:选择最适合自己的,而不

是人云亦云。)。考虑一下mov eax,[ecx+ebx],哪个是基址寄存器,哪个是变址寄存器?特别

是在缺少特征时(avoid character),更容易引起混淆。出现这种情形的主要原因还是因为寻

址的问题:两个寄存器似乎是一样的,可以互换;但实际上如果互换,生成的机器指令将完

全不同。

7.1 创建

我们在开始写Windows shellcode时,通常会碰到一个大问题,Win32不提供直接的系

统调用。而真正令人惊奇的是,这是由许多人讨论后决定的。正如Windows的一贯风格那

样,这个特性有令人讨厌的一面,也有值得称赞的一面。值得称赞的是,它使Win32系统

设计者在修复漏洞或扩展内部系统调用API时,不会影响现有的程序。

为了使shellcode在其它程序中运行,需要对其做适当修整,例如:

它必须可以找到需要的Win32 API函数,并生成调用表。

为了建立连接,它必须能加载需要的函数库。

它必须可以连接远程服务器,下载并执行后续的shellcode。

它必须确保自己可以正常退出,并使原来的进程继续运行或终止。

它必须能阻止其它线程对它的终止。

如果它想让后续的Win32调用继续使用堆,它还必须能修复一个或多个堆。

找到需要的Win32 API函数,一般是指在shellcode中硬编码这些函数的地址,或者是

硬编码Windows某个版本的GetProcAddressA()和LoadLibraryA()地址。编写Windows

shellcode最快速的方法之一是硬编码函数地址,当然,这种方法不适合特殊的可执行文件或

某些版本的Windows。正如SQL Slammer蠕虫显示的那样,硬编码函数地址有时候非常有

用。

注解:Slammer 的源代码在互联网上广为流传,它是非常好的、学习硬编码函数地址

的例子。

但在shellcode中硬编码函数地址,将使shellcode依赖特定的可执行文件或操作系统版

本。为了避免这种情形发生,我们只能改用其它方法。一种方法是在进程里模拟链接正常的

DLL,然后找出函数的位置。另一种方法是搜索函数使用的内存空间,通过寻

找进程环境块(Process environment block,PEB)(中国黑客经常用这个方法)找出函数的

位置。在随后的章节中,我们还将介绍怎样利用Windows异常处理系统(exception-handling

system)搜索整个内存空间。

欢迎访问,翻译@。

104