发新话题
打印

[原创]Windows结构化异常处理学习笔记

[原创]Windows结构化异常处理学习笔记

文章作者: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

附件

Windows结构化异常处理学习笔记.rar (34 KB)

2008-1-10 21:06, 下载次数: 37

TOP

提供楼主去看看hume兄的关于SEH的介绍....hume的那个介绍比较不错
http://www.rootkitcn.com

TOP

ayareiMM没看帖~.....无语。
另外我发现把所有的SEH写成SHE了~~~~无语,马上改。
另外普通会员无法上传附件呢?
麻烦root更正下WORD文档里的书写错误,谢谢。

TOP

确实没看,因为SEH实在是榨取的没什么了....
http://www.rootkitcn.com

TOP

提示: 作者被禁止或删除 内容自动屏蔽

TOP

学习中,谢谢

TOP

发新话题