文章作者:Cyg07
信息来源:邪恶八进制信息安全团队(
www.eviloctal.com)
注:本文首发零位空间(www.0wei.com),后由原创作者友情提交到邪恶八进制信息安全团队。
前言
Windows的结构化异常处理(SEH)方面是自己一直想学习的一个方面,因为它对自己进一步学习脱壳是非常有用的。今天就挑了个编写有问题的程序来看看,通过这个小程序学习SHE过程还是有点原因的。邪恶八进制上的ASM兄写了篇《今天你“hello word”了吗?》[
https://forum.eviloctal.com/read-htm-tid-26516.html],自己感觉他的分析过程存在很大的错误,故简单的写篇文章,仅作个学习笔记。如果各位大侠觉得有错,欢迎指正或提出补充,不胜感激。
分析过程
[分析平台]:Window 2003 sp0
[分析工具]: OllyDBG
[分析对象]:
+++++++++++++++++++++++++++++++++++++++++++++++
;编译方式:ml /c /coff /Zi helloworld.asm
.386
.model flat,stdcall
option casemap:none
include D:\masm32\include\windows.inc
include D:\masm32\include\user32.inc
includelib D:\masm32\include\user32.lib
include D:\masm32\include\kernel32.inc
includelib D:\masm32\lib\kernel32.lib
.data
szCaption db '恭喜',0
szText db 'hello word',0
.code
start:
invoke MessageBox, NULL, offset szText, szCaption, MB_OK
;利用伪指令调用MessageBox的时候,szCaption前没有书写一个”offset” 或 “addr”
invoke ExitProcess,NULL
end start
++++++++++++++++++++++++++++++++++++++++++++++++++
[异常现象]:
[初步分析]:
编译器在处理过程中没检测参数造成了,这个值就等价于scCaption字符串变量中第一个8位二进制的字符B9(“恭喜”这个汉字类型为unicode,每个字有16位再加上字符数组最后一位默认的“00”总共在PE文件的数据段中占有33位),也正是因为PE程序直接读取000000B9这个系统空间触发了Windows的SEH。
我们再继续看下PE程序是如何调用API的
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
PE程序对API的调用都是
push xxx (参数2) ;保存在堆栈
push xxx (参数1) ;保存在堆栈
call API ;实际上就是调用IAT中保存的函数地址(相应的在哪个dll提供的函数)
;进而直接跳转过去再使用
;push [esp+-立即数] ;获取保存在堆栈中的具体参数
...
call API ;这里才是真正的操作只有往这里继续查看才会找出触发异常的具
;体地址和指令,从异常现象可以看出0x77ce169b这个地址发生了异常。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
接下来我们简单来看看我们整个编译完毕后的PE文件的结构:
我们定义的变量是可读可写变量,自然是在.data区段中了,跟随看看我们所定义的szCaption,szText,如下图:
好了,让我们是用OllyDBG来具体看看这个过程吧:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00401005 > $ /E9 42000000 JMP 0.0040104C //OD载入后停在这里
0040100A . |E9 11000000 JMP 0.00401020
-----------------------------------------------------------------------------------------
0040104C > \6A 00 PUSH 0
0040104E . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401050 A0 00404100 MOV AL,BYTE PTR DS:[414000]
00401055 66:0FB6C0 MOVZX AX,AL ; |
00401059 66:50 PUSH AX ;中间这3句就szCaption部分
0040105B 68 05404100 PUSH 0.00414005 ; ASCII "hello word"
00401060 . 6A 00 PUSH 0 ; |hOwner = NULL
00401062 . E8 15000000 CALL <JMP.&USER32.MessageBoxA> ; F7跟入
00401067 . 6A 00 PUSH 0 ; /ExitCode = 0
00401069 . E8 08000000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
{
上面这段代码一看就是:
invoke MessageBox, NULL, offset szText, szCaption, MB_OK
invoke ExitProcess,NULL
}
----------------------------------------------------------------------------------------------------
0040107C $- FF25 CC504100 JMP DWORD PTR DS:[<&USER32.MessageBoxA>] //F7后来到这里,我们先看看堆栈中的结构
可以看到Title参数为000000B9
------------------------------------------------------------------------------------------------------
77CF5FBE > 833D E4F2D277 0>CMP DWORD PTR DS:[77D2F2E4],0 //来到这里
77CF5FC5 0F85 18640100 JNZ USER32.77D0C3E3 //这个校验的作用请知道的大侠赐教
77CF5FCB 6A 00 PUSH 0 //NULL
77CF5FCD FF7424 14 PUSH DWORD PTR SS:[ESP+14] // MB_OK
77CF5FD1 FF7424 14 PUSH DWORD PTR SS:[ESP+14] // 000000B9
77CF5FD5 FF7424 14 PUSH DWORD PTR SS:[ESP+14] // 00414005 ASCII "hello word"
77CF5FD9 FF7424 14 PUSH DWORD PTR SS:[ESP+14] //NULL
77CF5FDD E8 08000000 CALL USER32.MessageBoxExA
77CF5FE2 C2 1000 RETN 10
再看下堆栈:
--------------------------------------------------------------------------------------------------------
OK,基本流程说清楚了,这里我不再具体跟踪MessageBox函数的过程,也没那个能力,我们直接找到出问题的语句设置断点
77CE169B 8A08 MOV CL,BYTE PTR DS:[EAX] //F2 设断
77CE169D 40 INC EAX
77CE169E 84C9 TEST CL,CL
77CE16A0 ^ 75 F9 JNZ SHORT USER32.77CE169B
当读取EAX寄存器中的值[000000B9]时,产生异常
OD下方出现 访问违例:读取[000000B9] - 按shift+F7/F8/F9键跳过异常以继续执行程序
Windows检测到了这个进程异常,就会直接调用异常回调函数:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
push lpExceptionPointers
call Handler
Handler Proc lpExceptionPointers
...
ret
Handler endp
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lpExceptionPointers实际上是指向EXCEPTION_POINTERS、而EXCEPTION_POINTERS又主要指向EXCEPTION_RECORD和CONTEX结构
它们的结构大致如下:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
typedef strut_EXCEPTION_POINTERS{
+0 pEXCEPTION_RECORD ExceptionRecord DWORD ? //指向EXCEPTION_RECORD结构的指针
+4 pCONTEXT ContextRecord DWORD ? //指向EXCEPTION_CONTEXT结构的指针
}_EXCEPTION_POINTERS ends
=================================================================
typedef struct _CONTEXT {
/*000*/ DWORD ContextFlags;
/*004*/ DWORD Dr0;
/*008*/ DWORD Dr1;
/*00C*/ DWORD Dr2;
/*010*/ DWORD Dr3;
/*014*/ DWORD Dr6;
/*018*/ DWORD Dr7; 调试寄存器
/*01C*/ FLOATING_SAVE_AREA FloatSave ; 浮点寄存器区
/*08C*/ DWORD SegGs;
/*090*/ DWORD SegFs;
/*094*/ DWORD SegEs;
/*098*/ DWORD SegDs; 段寄存器
/*09C*/ DWORD Edi;
/*0A0*/ DWORD Esi;
/*0A4*/ DWORD Ebx;
/*0A8*/ DWORD Edx;
/*0AC*/ DWORD Ecx;
/*0B0*/ DWORD Eax; 通用寄存器
/*0B4*/ DWORD Ebp;
/*0B8*/ DWORD Eip;
/*0BC*/ DWORD SegCs;
/*0C0*/ DWORD EFlags;
/*0C4*/ DWORD Esp;
/*0C8*/ DWORD SegSs; 控制寄存器
/*0CC*/ BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
/*2CC*/ } CONTEXT;
==================================================================
EXCEPTION_RECORD struct{ //共6个成员
+0 DWORD ExceptionCode //异常代码,定义了产生异常的原因
+4 DWORD ExceptionFlags //异常标志 ?
+8 struct EXCEPTION_RECORD //指针,指向下一个EXCEPTION_RECORD结构
+C DVOID ExceptionAddress //异常发生的地址
+10 DWORD NumberParameters //下面ExceptionInformation所包含的dword数目
+14 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS] //ExceptionInformation == 15
}EXCEPTION_RECORD ends
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
执行这条指令后,我们可以看到程序现在跳到系统领空,转为内核模式
检测到了这个异常,就会向堆栈压入上面提到的3个结构[LIFO],压入顺序为EXCEPTION_RECORD, EXCEPTION_CONTEXT, EXCEPTION_POINTERS
77F35267 > 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+4]
77F3526B 8B1C24 MOV EBX,DWORD PTR SS:[ESP] //执行有异常的指令滞后立刻转到这里
马上察看下堆栈信息:
{
0012FC40 0012FC48 ------->指针,指向EXCEPTION_RECORD结构,即EXCEPTION_RECORD的首地址-----\这就是EXCEPTION_POINTERS
0012FC44 0012FC64-------->指针,指向EXCEPTION_CONTEXT结构,即EXCEPTION_CONTEXT的首地址
0012FC48 C0000005 //1--异常代码.这里开始就是EXCEPTION_RECORD结构
0012FC4C 00000000 //2--异常标志=0
0012FC50 00000000 //3--指针,指向下一个EXCEPTION_RECORD结构,这里没有构成异常链表,只有一处异常
0012FC54 77CE169B //4--异常发生的地址,这就是发生异常的那条指令的地址.
0012FC58 00000002 //5--ExceptionInformation的dword个数=2
0012FC5C 00000000 //6--ExceptionInformation
0012FC60 000000B9 //7--异常内容
0012FC64 0001003F //这里开始就是EXCEPTION_CONTEXT结构,ContextFlags
0012FC68 00000000 //Dr0
0012FC6C 00000000 //Dr1
0012FC70 00000000 //Dr2
0012FC74 00000000 //Dr3
0012FC78 00000000 //Dr6
0012FC7C 00000000 //Dr7
…
我们重点看看0012FC64 +B8=0012FD1C
0012FD18 0012FF3E
0012FD1C 77CE169B USER32.77CE169B //异常发生的地址,这就是发生异常的那条指令的地址
0012FD20 0000001B
}
继续跟踪:
77F3526E 51 PUSH ECX //指针,指向EXCEPTION_RECORD结构
77F3526F 53 PUSH EBX //指针,指向EXCEPTION_CONTEXT结构
77F35270 E8 BE380200 CALL ntdll.77F58B33 //我们F7跟入继续跟入察看Windows是如何处理该异常的
77F35275 0AC0 OR AL,AL
77F35277 74 0C JE SHORT ntdll.77F35285
77F35279 5B POP EBX
77F3527A 59 POP ECX
77F3527B 6A 00 PUSH 0
77F3527D 51 PUSH ECX
77F3527E E8 D0FFFFFF CALL ntdll.ZwContinue
77F35283 EB 0B JMP SHORT ntdll.77F35290
77F35285 5B POP EBX
77F35286 59 POP ECX
77F35287 6A 00 PUSH 0
77F35289 51 PUSH ECX
77F3528A 53 PUSH EBX
77F3528B E8 EA000000 CALL ntdll.ZwRaiseException
77F35290 83C4 EC ADD ESP,-14
77F35293 890424 MOV DWORD PTR SS:[ESP],EAX
77F35296 C74424 04 01000>MOV DWORD PTR SS:[ESP+4],1
77F3529E 895C24 08 MOV DWORD PTR SS:[ESP+8],EBX
77F352A2 C74424 10 00000>MOV DWORD PTR SS:[ESP+10],0
77F352AA 54 PUSH ESP
77F352AB E8 08000000 CALL ntdll.RtlRaiseException
77F352B0 C2 0800 RETN 8
继续跟到这段代码里:
77F58CD0 55 PUSH EBP
77F58CD1 8BEC MOV EBP,ESP
77F58CD3 FF75 0C PUSH DWORD PTR SS:[EBP+C]
77F58CD6 52 PUSH EDX
77F58CD7 64:FF35 0000000>PUSH DWORD PTR FS:[0]
77F58CDE 64:8925 0000000>MOV DWORD PTR FS:[0],ESP
{
//堆栈建立了1个err结构
0012FB78 0012FFE0 指向下一个 SEH 记录的指针,这里已经只有1个异常,直接
//指向链尾了
0012FB7C 77F58AD0 当前异常回调函数地址
0012FFE0 FFFFFFFF SEH 链尾部
0012FFE4 77E40ABC SE 处理器
即
EXCEPTION_REGISTRATION STRUCT
Prev dd ? ;指向下一个EXCEPTION_REGISTRATION结构
Handler dd ? ;当前异常回调函数地址
}
77F58CE5 FF75 14 PUSH DWORD PTR SS:[EBP+14]
77F58CE8 FF75 10 PUSH DWORD PTR SS:[EBP+10]
77F58CEB FF75 0C PUSH DWORD PTR SS:[EBP+C]
77F58CEE FF75 08 PUSH DWORD PTR SS:[EBP+8]
77F58CF1 8B4D 18 MOV ECX,DWORD PTR SS:[EBP+18]
77F58CF4 FFD1 CALL ECX
77F58CF6 64:8B25 0000000>MOV ESP,DWORD PTR FS:[0]
77F58CFD 64:8F05 0000000>POP DWORD PTR FS:[0]
77F58D04 8BE5 MOV ESP,EBP
77F58D06 5D POP EBP
77F58D07 C2 1400 RETN 14
{
//此时我们马上看看堆栈:
0012FB68 0012FC48 //指针,指向EXCEPTION_RECORD结构,即EXCEPTION_RECORD的首地址 -----回调函数的参数1
0012FB6C 0012FFE0 //指向err结构.可以看看上面我们截取的SEH链表
0012FB70 0012FC64 //指针,指向EXCEPTION_CONTEXT结构,即EXCEPTION_CONTEXT的首地址-----回调函数的参数3
0012FB74 0012FC20 //参数4 _lpDispatchrContext ? 最先被压入堆栈.
0012FB78 0012FFE0 指向下一个 SEH 记录的指针
0012FB7C 77F58AD0 SE 处理器
0012FB80 0012FFE0
}
总结
关于SEH的文章Hume大侠和kanxue上的朋友已经写了不少,就像本文开头说的,仅仅是是个人的学习笔记。最后,感谢machenglin老师一直的热心指导。
参考[抄袭]文章:
《软件加密技术内幕》 第一章、第四章 作者:dREAMtHEATER、Hume
链接:
http://www.pediy.com
《加密与解密二版菜鸟学习笔记(2) - SEH 结构化异常处理》 作者:ytcswb
链接:
http://bbs.pediy.com/showthread.php?threadid=10651