SEH 进阶(1)
来源:互联网 发布:美加净防晒霜 知乎 编辑:程序博客网 时间:2024/06/05 07:13
SHE进阶
了解了上一篇的文章之后,我们写一个简单的例子来验证我们的想法,并学习新的知识。
不同的编译器提供的增强版本SHE 可能不同,但是它们都是基于windows 底层SHE 的。我们使用Win10 1703 + VS2010 生成X86 Rlease 程序来验证已经学过的知识,后面使用XP x86 7600 来学习编译器版本的SHE。
编译如下程序
#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <stdio.h>DWORD scratch;EXCEPTION_DISPOSITION__cdeclexcept_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ){ unsigned i; // Indicate that we made it to our exception handler printf( "Hello from an exception handler\n" ); // Change EAX in the context record so that it points to someplace // where we can successfully write ContextRecord->Eax = (DWORD)&scratch; // Tell the OS to restart the faulting instruction return ExceptionContinueExecution;}int main(){ DWORD handler = (DWORD)except_handler; __asm { // Build EXCEPTION_REGISTRATION record: push handler // Address of handler function push FS:[0] // Address of previous handler mov FS:[0],ESP // Install new EXECEPTION_REGISTRATION } __asm { mov eax,0 // Zero out EAX mov [eax], 1 // Write to EAX to deliberately cause a fault } printf( "After writing!\n" ); __asm { // Remove our EXECEPTION_REGISTRATION record mov eax,[ESP] // Get pointer to previous record mov FS:[0], EAX // Install previous record add esp, 8 // Clean our EXECEPTION_REGISTRATION off stack } return 0;}
当我们使用VS 并添加我们自定义的异常处理程序,将有以下警告,且程序运行崩溃
warning C4733: 内联 asm 分配到“FS:0”: 处理程序未注册为安全处理程序
解决方案:
在 Visual Studio 开发环境中设置此链接器选项
1. 打开项目的“属性页”对话框。有关详细信息,请参见设置 Visual C++ 项目属性。
2. 选择 Linker 文件夹。
3. 选择“命令行”属性页。
4. 将该选项/SAFESEH:NO键入“附加选项”框中。
之后我们的程序正确执行并输出如下结果:
#include <windows.h>#include <stdio.h>EXCEPTION_DISPOSITION__cdecl_except_handler(struct _EXCEPTION_RECORD *ExceptionRecord,void * EstablisherFrame,struct _CONTEXT *ContextRecord,void * DispatcherContext ){printf( "Home Grown handler: Exception Code: %08X Exception Flags %X",ExceptionRecord->ExceptionCode, ExceptionRecord->ExceptionFlags );if ( ExceptionRecord->ExceptionFlags & 1 )printf( " EH_NONCONTINUABLE" );if ( ExceptionRecord->ExceptionFlags & 2 )printf( " EH_UNWINDING" );if ( ExceptionRecord->ExceptionFlags & 4 )printf( " EH_EXIT_UNWIND" );if ( ExceptionRecord->ExceptionFlags & 8 )printf( " EH_STACK_INVALID" );if ( ExceptionRecord->ExceptionFlags & 0x10 )printf( " EH_NESTED_CALL" );printf( "\n" );// Punt... We don't want to handle this... Let somebody else handle itreturn ExceptionContinueSearch;}void HomeGrownFrame( void ){DWORD handler = (DWORD)_except_handler;__asm{ // Build EXCEPTION_REGISTRATION record:push handler // Address of handler functionpush FS:[0] // Address of previous handlermov FS:[0],ESP // Install new EXECEPTION_REGISTRATION}*(PDWORD)0 = 0; // Write to address 0 to cause a faultprintf( "I should never get here!\n" );__asm{ // Remove our EXECEPTION_REGISTRATION recordmov eax,[ESP] // Get pointer to previous recordmov FS:[0], EAX // Install previous recordadd esp, 8 // Clean our EXECEPTION_REGISTRATION off stack}}int main(){_try {HomeGrownFrame(); }_except( EXCEPTION_EXECUTE_HANDLER ) {printf( "Caught the exception in main()\n" );}return 0;
}
运行后出现如下结果:
我们看到,except_handler函数被调用了两次,且传入的参数不同。
为什么同一个异常处理函数在这里被调用了两次?
首先我们观察两次调用的调用堆栈。
我们看到其调用堆栈并不相同。
从我们已经知道的知识来讲。
HomeGrownFrame 函数内部安装了一个自己的异常,在main函数中调用该函数之前已经进入一个异常函数中。因此我们可以得到类似于下面的堆栈:
0xFFFFFFFEH
__except_handler4
_try _catch 对应的异常处理函数
HomeGrownFrame 函数内部安装的异常块,except_handler
当该异常出现的时候,except_handler返回ExceptionContinueSearch,然后函数依然向下搜索,直到有异常处理函数声明了可以处理它。但是,为什么这里显示调用了两次except_handler函数。
为了搞清楚这个问题,我们要学习编译器提供的SHE 的机制,这里我们用Ring0 学习,其实机制是相同的。
Ring0层的实验
最简单的单个try 块的情况
#include "SEHDemo.h"void SEHTest() {int i = 0;__try{i++;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}}NTSTATUSDriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString){SEHTest();return STATUS_SUCCESS;}
代码逆向如下
.text:00011010 ; _DWORD __stdcall SEHTest().text:00011010 _SEHTest@0 proc near ; CODE XREF: DriverEntry(x,x)+5 p.text:00011010.text:00011010 i = dword ptr -1Ch.text:00011010 ms_exc = CPPEH_RECORD ptr -18h
对应结构体如下
struct EH3_EXCEPTION_REGISTRATION{DWORDNext;DWORDExceptionHandler;DWORDScopeTable;DWORDTryLevel;};struct CPPEH_RECORD{DWORDOleEsp;DWORDExcPtr;EH3_EXCEPTION_REGISTRATION registration;};stru_12108 对应的结构体.rdata:00012108 stru_12108 dd 0FFFFFFFEh ; GSCookieOffset.rdata:00012108 ; DATA XREF: SEHTest()+7 o.rdata:00012108 dd 0 ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108 dd 0FFFFFFD4h ; EHCookieOffset.rdata:00012108 dd 0 ; EHCookieXOROffset.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN5 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN6 ; ScopeRecord.HandlerFunc
.text:00011010.text:00011010 mov edi, edi.text:00011012 push ebp.text:00011013 mov ebp, esp.text:00011015 push 0FFFFFFFEh.text:00011017 push offset stru_12108.text:0001101C push offset __except_handler4.text:00011021 mov eax, large fs:0.text:00011027 push eax.text:00011028 add esp, -0Ch.text:0001102B push ebx.text:0001102C push esi.text:0001102D push edi
对应堆栈如下
.text:0001102E mov eax, ___security_cookie.text:00011033 xor [ebp+ms_exc.registration.ScopeTable], eax.text:00011036 xor eax, ebp.text:00011038 push eax.text:00011039 lea eax, [ebp+ms_exc.registration].text:0001103C mov large fs:0, eax.text:00011042 mov [ebp+ms_exc.old_esp], esp.text:00011045 mov [ebp+i], 0.text:0001104C mov [ebp+ms_exc.registration.TryLevel], 0
对应堆栈如下
;这三条指令是内部执行的指令 i++
.text:00011053 mov eax, [ebp+i].text:00011056 add eax, 1 .text:00011059 mov [ebp+i], eax
;下面是异常的卸载
.text:0001105C mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:00011063 jmp short loc_11082.text:00011065.text:00011065 $LN5: ; DATA XREF:.rdata:stru_12108o.text:00011065 mov eax, 1 ; Exception filter 0 for function11010.text:0001106A.text:0001106A $LN7:.text:0001106A retn.text:0001106B ;---------------------------------------------------------------------------.text:0001106B.text:0001106B $LN6: ; DATA XREF: .rdata:stru_12108o.text:0001106B mov esp, [ebp+ms_exc.old_esp] ; Exceptionhandler 0 for function 11010.text:0001106E push offset Format ; "Hello\r\n".text:00011073 call _DbgPrint.text:00011078 add esp, 4.text:0001107B mov [ebp+ms_exc.registration.TryLevel],0FFFFFFFEh
对应的堆栈
.text:00011082 loc_11082: ; CODE XREF: SEHTest()+53 j.text:00011082 mov ecx, [ebp+ms_exc.registration.Next].text:00011085 mov large fs:0, ecx
;恢复fs:0 即原来的异常处理函数
;堆栈平衡
.text:0001108C pop ecx.text:0001108D pop edi.text:0001108E pop esi.text:0001108F pop ebx.text:00011090 mov esp, ebp.text:00011092 pop ebp.text:00011093 retn.text:00011093 _SEHTest@0 endp
上面是仅仅一个try 语句时候的情况,那么,当有多个try 语句,是怎样的情况呢?
void SEHTest(){int i = 0;__try {i++;}__except(EXCEPTION_EXECUTE_HANDLER) {DbgPrint("Hello\r\n");}__try{i--;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}}
前面简历堆栈的操作是相同的,而且其第一个try 块的操作也是相同的,不同的如下所示。在将结构体的TryLevel 设置为 0FFFFFFFEh 之后,原来的函数已经开始了结束函数的操作,而这里是跳转到了下一个目标地址。
.text:0001105C mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:00011063 jmp short loc_11082.text:00011065 $LN6: ; DATA XREF: .rdata:stru_12108 o.text:00011065 mov eax, 1 ; Exception filter 0 for function 11010.text:0001106A.text:0001106A $LN8:.text:0001106A retn.text:0001106B ; ---------------------------------------------------------------------------.text:0001106B.text:0001106B $LN7: ; DATA XREF: .rdata:stru_12108 o.text:0001106B mov esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 11010.text:0001106E push offset Format ; "Hello\r\n".text:00011073 call _DbgPrint.text:00011078 add esp, 4.text:0001107B mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:00011082
该目标地址处的操作如下所示,将TryLevel 设置为1,而上面为0
.text:00011082 loc_11082: ; CODE XREF: SEHTest()+53 j.text:00011082 mov [ebp+ms_exc.registration.TryLevel], 1.text:00011089 mov ecx, [ebp+i].text:0001108C sub ecx, 1.text:0001108F mov [ebp+i], ecx进行i—操作
.text:00011092 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
同样是将TryLevel 设置为 0FFFFFFFEh:
.text:00011099 jmp short loc_110B8.text:0001109B ; ---------------------------------------------------------------------------.text:0001109B.text:0001109B $LN10: ; DATA XREF: .rdata:stru_12108 o.text:0001109B mov eax, 1 ; Exception filter 1 for function 11010.text:000110A0.text:000110A0 $LN12:.text:000110A0 retn.text:000110A1 ; ---------------------------------------------------------------------------.text:000110A1.text:000110A1 $LN11: ; DATA XREF: .rdata:stru_12108 o.text:000110A1 mov esp, [ebp+ms_exc.old_esp] ; Exception handler 1 for function 11010.text:000110A4 push offset Format ; "Hello\r\n".text:000110A9 call _DbgPrint.text:000110AE add esp, 4.text:000110B1 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:000110B8.text:000110B8 loc_110B8: ; CODE XREF: SEHTest()+89 j
到这里向下函数的执行是一样的,即恢复原来的fs:0,然后恢复堆栈。
另外,上下两个程序中引用的结构体,也发生了变化。上面的结构体的长度和下面的结构体长度不同。很明显,这是一个不定长的结构
.rdata:00012108 stru_12108 dd 0FFFFFFFEh ; GSCookieOffset.rdata:00012108 ; DATA XREF: SEHTest()+7 o.rdata:00012108 dd 0 ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108 dd 0FFFFFFD4h ; EHCookieOffset.rdata:00012108 dd 0 ; EHCookieXOROffset第一个异常块.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN6 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN7 ; ScopeRecord.HandlerFunc第二个异常块.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN10 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN11 ; ScopeRecord.HandlerFunc
我们再次增加函数内try结构的个数如下
void SEHTest(){ int i = 0; __try{ i++; } __except(EXCEPTION_EXECUTE_HANDLER){ DbgPrint("Hello\r\n"); } __try{ i--; } __except(EXCEPTION_EXECUTE_HANDLER){ DbgPrint("Hello\r\n"); } __try{ i += 2; } __except(EXCEPTION_EXECUTE_HANDLER){ DbgPrint("Hello\r\n"); }}
我们发现其添加了如下结构
loc_110B8:mov [ebp+ms_exc.registration.TryLevel], 2mov edx, [ebp+i]add edx, 2mov [ebp+i], edxmov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEhjmp short loc_110EE
上述变长结构体变为这样:
.rdata:00012108 stru_12108 dd 0FFFFFFFEh ; GSCookieOffset.rdata:00012108 ; DATA XREF: SEHTest()+7 o.rdata:00012108 dd 0 ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108 dd 0FFFFFFD4h ; EHCookieOffset.rdata:00012108 dd 0 ; EHCookieXOROffset.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN7 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN8 ; ScopeRecord.HandlerFunc.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN11 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN12 ; ScopeRecord.HandlerFunc.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN15 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN16 ; ScopeRecord.HandlerFunc
似乎发现了什么,每添加一个try 块,添加一个代码块,且其TryLevel递增,上面的结构体增加一个单位的子结构。
然后我们再添加嵌套形式的SHE,使其格式如下:
void SEHTest() {int i = 0;__try {i++;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}__try {i--;__try {i += 2;}__except(EXCEPTION_EXECUTE_HANDLER) {DbgPrint("Hello\r\n");}}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}}
上面第一个代码块的执行依然不变,但是,当执行下面的结构的时候,我们来看。
.text:00011082 loc_11082: ; CODE XREF: SEHTest()+53 j.text:00011082 mov [ebp+ms_exc.registration.TryLevel], 1.text:00011089 mov ecx, [ebp+i].text:0001108C sub ecx, 1.text:0001108F mov [ebp+i], ecx
i-- 对应的代码块,TryLevel 为 1,但是其运行结束后没有将TryLevel 恢复到0FFFFFFFEh。而是直接执行了下面的嵌套的try 块
.text:00011092 mov [ebp+ms_exc.registration.TryLevel], 2.text:00011099 mov edx, [ebp+i].text:0001109C add edx, 2.text:0001109F mov [ebp+i], edx.text:000110A2 mov [ebp+ms_exc.registration.TryLevel], 1
i += 2 对应的代码块,TryLevel为 2,然后其执行结束后,自动将TryLevel 设置为1,也不是0FFFFFFFEh。
.text:000110A9 jmp short loc_110C8
最后函数跳转到如下地址,然后将其TryLevel 设置为 0FFFFFFFEh ,然后恢复fs:0 恢复堆栈等操作。
.text:000110C8 loc_110C8: ; CODE XREF: SEHTest()+99 j.text:000110C8 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:000110CF jmp short loc_110EE
我们可以看到,try 块与其包含的代码块的对应是通过TryLevel 来对应的,在进入一个新块之前会设置这个块的等级,然后在执行完这个块之后会恢复到之前TryLevel。而这里的TryLevel可以理解为对应的变长结构体的子结构体的下标。
下面我们来看这个子结构有什么变化,因为与上个程序相比,尽管块的个数没有改变,但是块的结构改变了------增加了嵌套的try 块这样的结构。
.rdata:00012108 stru_12108 dd 0FFFFFFFEh ; GSCookieOffset.rdata:00012108 ; DATA XREF: SEHTest()+7 o.rdata:00012108 dd 0 ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108 dd 0FFFFFFD4h ; EHCookieOffset.rdata:00012108 dd 0 ; EHCookieXOROffset.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN7 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN8 ; ScopeRecord.HandlerFunc.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN11 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN12 ; ScopeRecord.HandlerFunc.rdata:00012108 dd 1 ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN15 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN16 ; ScopeRecord.HandlerFunc
左边是之前的,右边是现在的。很容易发现,第三个结构的EnclosingLevel成员不同,综合代码我们发现,其值跟代码中“进入3号try 块之前的TryLevel 状态以及,出3号try块之后恢复的TryLevel 状态相同”如果在对比前面两个try 块对应的子结构发现,这个EnclosingLevel 就是try 块对应的外层的try 块的下标。而0FFFFFFFEh 代表的是外层没有try 块(这个编译器生成的try 块)。
掌握了单个函数内部的try 嵌套之后我们来看try 块内部调用函数的情况。
void SubFunc() {int j;__try {j++;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("World\r\n");}}void SEHTest() {int i = 0;__try {SubFunc();}__except(EXCEPTION_EXECUTE_HANDLER) {DbgPrint("Hello\r\n");}}
函数SEHTest 内部是最简单的形式了。如下
.text:00011017 push offset stru_12108.text:0001101C push offset __except_handler4
另外,看那个变长结构:
.rdata:00012104 dd rva _unwind_handler4.rdata:00012108 stru_12108 dd 0FFFFFFFEh ; GSCookieOffset.rdata:00012108 ; DATA XREF: SubFunc()+7 o.rdata:00012108 dd 0 ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108 dd 0FFFFFFD4h ; EHCookieOffset.rdata:00012108 dd 0 ; EHCookieXOROffset.rdata:00012108 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012108 dd offset $LN5 ; ScopeRecord.FilterFunc.rdata:00012108 dd offset $LN6 ; ScopeRecord.HandlerFunc.rdata:00012128 stru_12128 dd 0FFFFFFFEh ; GSCookieOffset.rdata:00012128 ; DATA XREF: SEHTest()+7 o.rdata:00012128 dd 0 ; GSCookieXOROffset ; SEH scope table for function 110A0.rdata:00012128 dd 0FFFFFFD4h ; EHCookieOffset.rdata:00012128 dd 0 ; EHCookieXOROffset.rdata:00012128 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel.rdata:00012128 dd offset $LN5_0 ; ScopeRecord.FilterFunc.rdata:00012128 dd offset $LN6_0 ; ScopeRecord.HandlerFunc
两个函数分别有两个该结构。
到这里我们就了解了编译器的SHE 的结构了。一个函数如果有SEH 结构,将只进行一次注册,即使用编译器提供的__except_handler4函数,并用一个变长结构来表示单个函数内部try 块的嵌套结构。如果try 块中有函数调用,且该函数内部也有SHE 结构,其嵌套是通过系统SHE 机制来确保的。即,当子函数中的SHE 不能满足需要,将遍历其父函数的SHE 函数(对于同一编译器来说是同一个异常处理函数)。这样可以确保不同编译器编译的模块之间的调用不会因为SHE 的实现不同而导致错误。下一节我们来看看这个__except_handler4 函数,来解决文章开头出现的问题。
- SEH 进阶(1)
- SEH 进阶(2)
- SEH stack 结构探索(1)--- 从 SEH 链的最底层(线程第1个SEH结构)说起
- SEH示例代码1
- SEH学习心得【1】
- SEH X64(1)
- SEH
- SEH
- seh
- seh
- 学习心得---关于seh(1)
- SEH stack 结构探索(3)--- __exception_handler4() 探秘1
- Final SEH示例1_1
- Final SEH示例1_2
- SEH IN ASM 研究(1)
- SEH(结构异常处理)
- SEH相关数据结构(续)
- 破解笔记1——SEH
- lintcode 155 二叉树的最小深度
- NS 网络模拟和协议仿真黄化吉教材的修正——continue用例P24
- XMU 1612 刘备闯三国之桃园结义 【二分】
- 1057: 天平平衡 [递归]
- 下一个爱因斯坦或许出自超级人工智能阶段
- SEH 进阶(1)
- NYOJ-171-聪明的kk(第三届河南省程序设计大赛D题(简单dp))
- opencv从零开始——3. 了解通道,对比度,亮度
- Is Subsequence
- 搜狗 输入法安装和问题解决
- 一份初级答卷:iOS (1)
- 找工作绕不过之内存管理
- 慕课:网页布局基础课程——学习笔记
- ios navigationController中界面跳转 :X–> A–>B–>C 返回 C–>A