跟踪程序:SEH_1源码编译连接后的程序:
调试工具:Ollydbg v1.1(异常设置为忽略所有异常,即所有的复选框都打上勾)
调试平台:XP SP2
1.操作系统为每个线程分配一个TEB结构的数据块,并用FS指向它:
NT_TIB STRUCT
+0 ExceptionList dd ?
StackBase dd ?
StackLimit dd ?
SubSystemTib dd ?
union
FiberData dd ?
Version dd ?
ends
ArbitraryUserPointer dd ?
Self dd ?
NT_TIB ENDS
EXCEPTION_REGISTRATION_RECORD struct
+0 prev dd ?
+4 handler dd ?
EXCEPTION_REGISTRATION_RECORD ends
FS指向NT_TIB结构,FS:[0]就指向了EXCEPTION_REGISTRATION_RECORD结构
*************************************************************************************
执行到异常发生指令上是看堆栈,里面就是_EXCEPTION_REGISTRATION结构的prev和handler:
异常指令:
00401018 |. 8B06 mov eax, [esi]
此时堆栈:
0012FFBC 0012FFE0 指向下一个 SEH 记录的指针
0012FFC0 0040102F SE处理程序
在 地址0040102F上下断bp 0040102F,然后F9运行即可来到异常处理代码处
*************************************************************************************
2.异常发生时,操作系统向引起异常的线程的堆栈压入3个结构:
EXCEPTION_RECORD,CONTEXT,EXCEPTION_POINTERS
EXCEPTION_POINTERS STRUCT
+0 pExceptionRecord DWORD ?
+4 ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
即是说:EXCEPTION_POINTERS包含指向EXCEPTION_RECORD,CONTEXT这两个结构的指针
*************************************************************************************
执行到异常发生指令上继续单步即可进入系统异常处理代码,查看堆栈,里面就是EXCEPTION_POINTERS结构
的ExceptionRecord和ContextRecord,分别指向EXCEPTION_RECORD,CONTEXT这两个结构
系统异常处理程序入口:
7C92EAF0 8B1C24 mov ebx, [esp]
7C92EAF3 51 push ecx
7C92EAF4 53 push ebx
7C92EAF5 E8 C78C0200 call 7C9577C1
此时堆栈:
0012FCCC 0012FCD4 ;指向EXCEPTION_RECORD结构
0012FCD0 0012FCF0 ;指向CONTEXT结构
*************************************************************************************
3.EXCEPTION_RECORD结构包含了发生异常的详细信息,这些信息独立于CPU
EXCEPTION_RECORD STRUCT
+0 ExceptionCode DWORD ? ;异常发生的原因代码
+4 ExceptionFlags DWORD ? ;异常处理操作标志
+8 pExceptionRecord DWORD ?
+0Ch ExceptionAddress DWORD ? ;异常发生的地址
+10h NumberParameters DWORD ?
+14h ExceptionInformation DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
EXCEPTION_RECORD ENDS
*************************************************************************************
执行到异常发生指令上继续单步即可进入系统异常处理代码,查看堆栈,里面就是EXCEPTION_POINTERS结构
的ExceptionRecord和ContextRecord,分别指向EXCEPTION_RECORD,CONTEXT这两个结构
系统异常处理程序入口处堆栈:
0012FCCC 0012FCD4 ;指向EXCEPTION_RECORD结构
0012FCD0 0012FCF0 ;指向CONTEXT结构
此时用命令 d 0012FCD4即可查看EXCEPTION_RECORD结构的内容:
0012FCD4 05 00 00 C0 00 00 00 00 00 00 00 00 18 10 40 00 ..?.......@.
0012FCE4 02 00 00 00 00 00 00 00 .......
或直接在堆栈中找 0012FCD4,其实就在下面不远的地方:
0012FCD4 C0000005 ;除零异常
0012FCD8 00000000
0012FCDC 00000000
0012FCE0 00401018 SEH_1.00401018 ;异常发生地址是 00401018
0012FCE4 00000002
0012FCE8 00000000
*************************************************************************************
4.CONTEXT结构包含了特定处理器的寄存器数据
CONTEXT STRUCT
ContextFlags DWORD ?
//调试寄存器
+4 iDr0 DWORD ?
+8 iDr1 DWORD ?
+0C iDr2 DWORD ?
+10 iDr3 DWORD ?
+14 iDr6 DWORD ?
+18 iDr7 DWORD ?
//浮点寄存器
FloatSave FLOATING_SAVE_AREA <>
//段寄存器
+8C regGs DWORD ?
+90 regFs DWORD ?
+94 regEs DWORD ?
+98 regDs DWORD ?
//通用寄存器
+9C regEdi DWORD ?
+A0 regEsi DWORD ?
+A4 regEbx DWORD ?
+A8 regEdx DWORD ?
+AC regEcx DWORD ?
+B0 regEax DWORD ?
//控制寄存器
+B4 regEbp DWORD ?
+B8 regEip DWORD ?
+BC regCs DWORD ?
+C0 regFlag DWORD ?
+C4 regEsp DWORD ?
+C8 regSs DWORD ?
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
*************************************************************************************
执行到异常发生指令上继续单步即可进入系统异常处理代码,查看堆栈,里面就是EXCEPTION_POINTERS结构
的ExceptionRecord和ContextRecord,分别指向EXCEPTION_RECORD,CONTEXT这两个结构
系统异常处理程序入口处堆栈:
0012FCCC 0012FCD4 ;指向EXCEPTION_RECORD结构
0012FCD0 0012FCF0 ;指向CONTEXT结构
此时用命令 d 0012FCF0即可查看CONTEXT结构的内容:
0012FCF0 3F 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ?..............
0012FD00 00 00 00 00 00 00 00 00 00 00 00 00 7F 02 FF FF ............
0012FD10 00 00 FF FF FF FF FF FF 5E E4 2D F7 08 00 C4 05 ..^??.?
0012FD20 28 6E B3 F7 10 00 FF FF 6C 00 6C 00 04 01 05 01 (n橱.l.l.
0012FD30 A8 D0 6E 00 69 00 00 00 00 00 00 00 00 00 00 00 ㄐn.i...........
0012FD40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0012FD50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0012FD60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0012FD70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0012FD80 3B 00 00 00 23 00 00 00 23 00 00 00 38 07 93 7C ;...#...#...8搢
0012FD90 00 00 00 00 00 60 FD 7F 94 EB 92 7C B0 FF 12 00 .....`?旊抾?.
0012FDA0 00 00 00 00 F0 FF 12 00 18 10 40 00 1B 00 00 00 ....?.@....
0012FDB0 46 03 01 00 BC FF 12 00 F.?.#
或直接在堆栈中找 0012FCF0,就在下面不远的地方:
0012FCF0 0001003F ;
0012FCF4 00000000 ;iDr0
0012FCF8 00000000 ;iDr1
0012FCFC 00000000 ;iDr2
0012FD00 00000000 ;iDr3
0012FD04 00000000 ;iDr6
0012FD08 00000000 ;iDr7
0012FD0C FFFF027F
0012FD10 FFFF0000
0012FD14 FFFFFFFF
0012FD18 F72DE45E
0012FD1C 05C40008
0012FD20 F7B36E28
0012FD24 FFFF0010
0012FD28 006C006C
0012FD2C 01050104
0012FD30 006ED0A8
0012FD34 00000069
0012FD38 00000000
0012FD3C 00000000
0012FD40 00000000
0012FD44 00000000
0012FD48 00000000
0012FD4C 00000000
0012FD50 00000000
0012FD54 00000000
0012FD58 00000000
0012FD5C 00000000
0012FD60 00000000
0012FD64 00000000
0012FD68 00000000
0012FD6C 00000000
0012FD70 00000000
0012FD74 00000000
0012FD78 00000000
0012FD7C 00000000
0012FD80 0000003B
0012FD84 00000023
0012FD88 00000023
0012FD8C 7C930738 ntdll.7C930738
0012FD90 00000000
0012FD94 7FFD6000
0012FD98 7C92EB94 ntdll.KiFastSystemCallRet
0012FD9C 0012FFB0 ;regEcx
0012FDA0 00000000 ;regEax
0012FDA4 0012FFF0 ;regEbp
0012FDA8 00401018 SEH_1.00401018 ;regEip 异常发生的地址
0012FDAC 0000001B ;regCs
0012FDB0 00010346 UNICODE "C:/Program Files/ATI Technologies/ATI Control Panel;F:/"
0012FDB4 0012FFBC
0012FDB8 00000023
*************************************************************************************
5.当系统检测到异常时,正在执行的线程立即被中断,处理由用户模式转向内核模式,控制权就交给了异常
调试程序,NT系统的异常处理程序是NTDLL.DLL中的KiUserExceptionDispatcher函数来实现的。
加载程序后下断点:bp KiUserExceptionDispatcher,F9运行被断下来到:
7C92EAEC > 8B4C24 04 mov ecx, [esp+4]
7C92EAF0 8B1C24 mov ebx, [esp]
7C92EAF3 51 push ecx
7C92EAF4 53 push ebx
7C92EAF5 E8 C78C0200 call 7C9577C1
这就是上面2.中执行异常代码后进入的空间,所以有两种方法来到此位置:
一是直接单步异常代码直接来到,二是下断点bp KiUserExceptionDispatcher,F9(Shift + F9)运行来到
*************************************************************************************
当系统对异常处理完毕后就会跳到我们的异常处理代码处,我们来找该跳转代码:(对XP SP2)
KiUserExceptionDipatcher函数入口:
7C92EAEC > 8B4C24 04 mov ecx, [esp+4]
7C92EAF0 8B1C24 mov ebx, [esp]
7C92EAF3 51 push ecx
7C92EAF4 53 push ebx
7C92EAF5 E8 C78C0200 call 7C9577C1 ;F7跟进
来到:
7C9577C1 8BFF mov edi, edi ; ntdll.7C930738
7C9577C3 55 push ebp
7C9577C4 8BEC mov ebp, esp
7C9577C6 83EC 64 sub esp, 64
........
7C957852 8D45 EC lea eax, [ebp-14]
7C957855 50 push eax
7C957856 FF75 0C push dword ptr [ebp+C]
7C957859 53 push ebx
7C95785A 56 push esi
7C95785B E8 F3BEFCFF call 7C923753 ;F7跟进
来到:
7C923753 BA D837927C mov edx, 7C9237D8
7C923758 EB 0D jmp short 7C923767
7C92375A 90 nop
.......
7C923767 53 push ebx
7C923768 56 push esi
7C923769 57 push edi
7C92376A 33C0 xor eax, eax
7C92376C 33DB xor ebx, ebx
7C92376E 33F6 xor esi, esi
7C923770 33FF xor edi, edi
7C923772 FF7424 20 push dword ptr [esp+20]
7C923776 FF7424 20 push dword ptr [esp+20]
7C92377A FF7424 20 push dword ptr [esp+20]
7C92377E FF7424 20 push dword ptr [esp+20]
7C923782 FF7424 20 push dword ptr [esp+20]
7C923786 E8 0E000000 call 7C923799 ;F7跟进
来到:
7C923799 55 push ebp
7C92379A 8BEC mov ebp, esp
7C92379C FF75 0C push dword ptr [ebp+C]
7C92379F 52 push edx
7C9237A0 64:FF35 0000000>push dword ptr fs:[0]
7C9237A7 64:8925 0000000>mov fs:[0], esp
7C9237AE FF75 14 push dword ptr [ebp+14] ;
7C9237B1 FF75 10 push dword ptr [ebp+10] ;压入CONTEXT
7C9237B4 FF75 0C push dword ptr [ebp+C] ;压入EXCEPTION_REGISTERATION_RECORD
7C9237B7 FF75 08 push dword ptr [ebp+8] ;压入EXCEPTION_RECORD
7C9237BA 8B4D 18 mov ecx, [ebp+18] ;取异常处理句柄
7C9237BD FFD1 call ecx ;指向SEH Handler;由此处返回到我们的程序,F7进
7C9237BF 64:8B25 0000000>mov esp, fs:[0]
7C9237C6 64:8F05 0000000>pop dword ptr fs:[0]
7C9237CD 8BE5 mov esp, ebp
7C9237CF 5D pop ebp
7C9237D0 C2 1400 retn 14
直接来到call ecx的办法:bp 7C9237BD,但这有个局限性,就是不同系统的DLL版本不同。
单步执行 call ecx 时的堆栈:
0012FBF0 0012FCD4 ;指向EXCEPTION_RECORD结构
0012FBF4 0012FFBC ;指向EXCEPTION_REGISTERATION_RECORD结构
0012FBF8 0012FCF0 ;指向CONTEXT结构
0012FBFC 0012FCA8 ;指向DispatchContext
ecx中即为我们的异常处理代码地址,或来到0012FFBC处:
0012FFBC 0012FFE0 指向下一个 SEH 记录的指针
0012FFC0 0040102F SE处理程序
这个0040102F即为我们的异常处理代码地址,就是我们在初始化ERR结构时压入的。
*************************************************************************************
6.异常处理回调函数
_Handler proc _lpExceptionRecord:DWORD,_lpSEH:DWORD,_lpContext:DWORD,_lpDispatcherContext:DWORD
参数:
_lpExceptionRecord: 指向EXCEPTION_RECORD结构
_lpSEH: 指向EXCEPTION_REGISTERATION结构
_lpContext: 指向CONTEXT结构
_lpDispatcherContext: ;
返回值:
ExceptionContinueExecution equ 0 返回异常处或修改后的EIP处继续执行
ExceptionContinueSearch equ 1 不处理异常,系统通知下个异常处理例程
ExceptionNestedException equ 2
ExceptionCollidedUnwind equ 3
*************************************************************************************
F7进call ecx时即调用了异常处理回调函数,来到我们的异常处理代码:
0040103E /$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL; 结构异常处理程序
00401040 |. 68 10204000 push 00402010 ; |Title = "ok"
00401045 |. 68 20204000 push 00402020 ; |Text = "SEH succeed"
0040104A |. 6A 00 push 0 ; |hOwner = NULL
0040104C |. E8 1F000000 call <jmp.&user32.MessageBoxA> ; /MessageBoxA
00401051 |. 55 push ebp
00401052 |. 8BEC mov ebp, esp
00401054 |. 8B5D 10 mov ebx, [ebp+10]
00401057 |. C783 A0000000>mov dword ptr [ebx+A0], <模块入口点>
00401061 |. B8 00000000 mov eax, 0
00401066 |. 8BE5 mov esp, ebp
00401068 |. 5D pop ebp
00401069 /. C3 retn
看堆栈:
0012FBEC 7C9237BF 返回到 ntdll.7C9237BF
0012FBF0 0012FCD4 ;指向CONTEXT结构
0012FBF4 0012FFBC ;指向EXCEPTION_REGISTERATION结构
0012FBF8 0012FCF0 ;指向EXCEPTION_RECORD结构
以上4个就是压入的异常处理回调函数的参数,CONTEXT结构也在,这样我们就可以在这里改变CONTEXT的值
以达到我们的目的,如清除断点,修改程序流向等。
异常处理回调函数是系统异常处理程序的子过程,所以直接用ret返回就会回到系统领空
*************************************************************************************
7.从异常处理代码可知程序返回后会从异常发生处继续执行,下断,bp 00401018
单步从
00401069 /. C3 retn
返回系统来到:
7C9237BF 64:8B25 0000000>mov esp, fs:[0]
7C9237C6 64:8F05 0000000>pop dword ptr fs:[0]
7C9237CD 8BE5 mov esp, ebp
7C9237CF 5D pop ebp
7C9237D0 C2 1400 retn 14 ;F7返回
来到:
7C92378B 5F pop edi ; ntdll.7C930738
7C92378C 5E pop esi
7C92378D 5B pop ebx
7C92378E C2 1400 retn 14 ;F7返回
来到:
7C957860 F605 5AC3997C 8>test byte ptr [7C99C35A], 80
7C957867 8BF8 mov edi, eax
7C957869 0F85 16720100 jnz 7C96EA85
7C95786F 395D 08 cmp [ebp+8], ebx
7C957872 0F84 1B720100 je 7C96EA93
7C957878 8BC7 mov eax, edi
7C95787A 33C9 xor ecx, ecx
7C95787C 2BC1 sub eax, ecx
7C95787E ^ 0F85 8631FFFF jnz 7C94AA0A
7C957884 F646 04 01 test byte ptr [esi+4], 1
7C957888 0F85 4F720100 jnz 7C96EADD
7C95788E C645 FF 01 mov byte ptr [ebp-1], 1
7C957892 5F pop edi
7C957893 5B pop ebx
7C957894 8A45 FF mov al, [ebp-1]
7C957897 5E pop esi
7C957898 C9 leave
7C957899 C2 0800 retn 8 ;F7返回
来到:
7C92EAFA 0AC0 or al, al
7C92EAFC 74 0C je short 7C92EB0A
7C92EAFE 5B pop ebx
7C92EAFF 59 pop ecx
7C92EB00 6A 00 push 0
7C92EB02 51 push ecx
7C92EB03 E8 11EBFFFF call ZwContinue ;F7进
来到:
7C92D619 > B8 20000000 mov eax, 20
7C92D61E BA 0003FE7F mov edx, 7FFE0300
7C92D623 FF12 call [edx] ;F7进
来到:
7C92EB8B > 8BD4 mov edx, esp
7C92EB8D 0F34 sysenter ;F7,就会来到我们的断点地址00401018
7C92EB8F 90 nop
7C92EB90 90 nop
7C92EB91 90 nop
7C92EB92 90 nop
7C92EB93 90 nop
7C92EB94 > C3 retn
来到这里:
00401018 |. 8B06 mov eax, [esi]
0040101A |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
0040101C |. 68 10204000 push 00402010 ; |Title = "ok"
00401021 |. 68 13204000 push 00402013 ; |Text = "修",B8,"",B4,"了异常?,A1,""
00401026 |. 6A 00 push 0 ; |hOwner = NULL
00401028 |. E8 43000000 call <jmp.&user32.MessageBoxA> ; /MessageBoxA
0040102D |. 64:8F05 00000>pop dword ptr fs:[0]
00401034 |. 83C4 04 add esp, 4
00401037 |. 6A 00 push 0 ; /ExitCode = 0
00401039 /. E8 2C000000 call <jmp.&kernel32.ExitProcess> ; /ExitProcess
OVER ^_^