本帖最后由 SuperProgram 于 2019-9-30 11:25 编辑
Windows运行过程中,不可避免会产生各种异常(由内核或应用程序),系统提供了一套强大的异常处理机制,灵活的使用它,可以让我们的应用程序变的更健壮。
了解涉及异常处理的数据结构
IDT 系统中断表
​
有异常产生时,处理器根据IDT的中断号,找到对应的处理函数 KiTrapxx,异常处理函数会将异常封装到一个数据结构。
[C++] 纯文本查看 复制代码 typedef struct _EXCEPTION_RECORD32 {
DWORD ExceptionCode; //异常代码
DWORD ExceptionFlags; //异常标志
DWORD ExceptionRecord; //EXCEPTION_RECORD32指针
DWORD ExceptionAddress; //产生异常时的地址
DWORD NumberParameters; //异常附加信息数量
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //异常附加信息,指针指向一个数组
} EXCEPTION_RECORD32, *PEXCEPTION_RECORD32;
CONTEXT异常上下文 ,不太严谨,姑且这样认为吧,它包含了异常产生时,执行环境的详细信息
[C++] 纯文本查看 复制代码 typedef struct _CONTEXT {
//标识哪些成员是有效的
DWORD ContextFlags;
//调试寄存器
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;
//
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
//
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
当KiTrapxxx处理完成并将异常信息封装好后,转而调用系统内核nt!KiDispatchException函数。
KiDispatchException 该函数是异常处理的核心函数,该函数代码比较长,这里贴出函数头的部分代码。
​
该函数会根据异常类型及是否存在调试器,来进行不同的处理。
1.如果存在内核调试器,并设置处理进度为第一次处理,内核调试器如果不能处理异常则会中断,重新把控制权交给用户,如果调试器处理了异常,那么会从发生异常的地方继续执行指令。
2.不存在内核调试器,或者内核调试器不能处理异常,内核就会调用RtlDispatchException函数,依次调用注册的异常处理函数。(SEH)
3.如果RtlDispatchException也没有能够处理异常,内核会重新将异常交给调试器。
4.如果调试器仍然处理不了,此时就可以见到久违的蓝屏错误了。(0x0000008e)
1.如果存在内核调试器,将异常交给调试器处理。
2.如果存在用户态调试器,则交给调试器处理,调试器未处理该异常或者不存在调试器,KiDispatchException会压入2个数据结构,exception_record、context,让后将控制权返回给用户态函数,位于ntdll!RtlDispatchException。
3.如果RtlDispatchException未能处理异常,此时会再次将异常返回给内核KiDispatchException,它会将异常重新转给用户态调试器,如果仍然无法处理异常,就结束进程。
4.结束进程前,系统会再次调用注册的异常处理函数,然后会结束异常进程。
SEH (结构化异常处理)
前面讲到的是内核异常处理流程,下面说下用户态的异常处理。
SEH是一种异常处理机制,发生异常时,系统通过它找到处理函数,处理该异常。
涉及到的数据结构
TEB 线程环境块(翻译过来是这样的)
[C++] 纯文本查看 复制代码 typedef struct _TEB {
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock;
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64];
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle; // Windows 2000 only
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
} TEB, *PTEB;
TIB 线程信息块
typedef struct _NT_TIB32 { DWORD ExceptionList; DWORD StackBase; DWORD StackLimit; DWORD SubSystemTib;#if defined(_MSC_EXTENSIONS) union { DWORD FiberData; DWORD Version; };#else DWORD FiberData;#endif DWORD ArbitraryUserPointer; DWORD Self;} NT_TIB32, *PNT_TIB32;
异常处理结构( ExceptionList成员)
[C++] 纯文本查看 复制代码 typedef struct _NT_TIB32 {
DWORD ExceptionList;
DWORD StackBase;
DWORD StackLimit;
DWORD SubSystemTib;
#if defined(_MSC_EXTENSIONS)
union {
DWORD FiberData;
DWORD Version;
};
#else
DWORD FiberData;
#endif
DWORD ArbitraryUserPointer;
DWORD Self;
} NT_TIB32, *PNT_TIB32;
异常处理结构( ExceptionList成员)[C++] 纯文本查看 复制代码 typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next; //下一个处理
PEXCEPTION_ROUTINE Handler; //异常处理函数
} EXCEPTION_REGISTRATION_RECORD; 异常处理函数声明
typedef
_IRQL_requires_same_
_Function_class_(EXCEPTION_ROUTINE)
EXCEPTION_DISPOSITION
NTAPI
EXCEPTION_ROUTINE (
_Inout_ struct _EXCEPTION_RECORD *ExceptionRecord, //异常信息
_In_ PVOID EstablisherFrame, //陷阱帧
_Inout_ struct _CONTEXT *ContextRecord, //异常上下文
_In_ PVOID DispatcherContext // 未知
);
typedef EXCEPTION_ROUTINE *PEXCEPTION_ROUTINE;
从XP到现在的win10,这些数据结构都没有大的变动。
从以上流程知道,要处理异常,需要注册SEH处理函数,SEH位于TIB头,是一个单项链表,通过next指向下一个处理函数。
SEH异常处理代码(此处代码来自网上,做了部分调整)
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <winternl.h>
//异常处理函数
DWORD dwTest;
EXCEPTION_DISPOSITION NTAPI ExceptHandler(
_Inout_ struct _EXCEPTION_RECORD *ExceptionRecord,
_In_ PVOID EstablisherFrame,
_Inout_ struct _CONTEXT *ContextRecord,
_In_ PVOID DispatcherContext) {
printf("进入异常处理\n");
printf("异常地址:%X<异常代码:%X>\n", ExceptionRecord->ExceptionAddress,
ExceptionRecord->ExceptionCode);
ContextRecord->Eax = (DWORD)(&dwTest);
return ExceptionContinueExecution;
}
int main()
{
PTEB teb=NULL;
_asm
{
mov eax,fs:[0]
mov teb,eax
}
printf("TEB %X \n", teb);
printf("注册SEH\n");
__asm {
lea eax, ExceptHandler //将异常处理函数地址装入eax
push eax //异常处理函数入栈
push fs : [0] //构造EXCEPTION_REGISTRATION_RECORD
mov dword ptr fs : [0], esp //将构造好的处理函数加入SEH链表头部
}
__asm {
xor eax, eax
mov dword ptr[eax], 1234h //内存访问无效异常
}
printf("删除SEH\n");
//卸载异常处理函数并恢复堆栈
__asm {
pop dword ptr fs : [0]
add esp, 4
}
printf("dwTest=%X\n", dwTest);
system("pause");
}
​
下面在调试器中看下系统的异常处理流程,首先在内核的KiDispatchException函数下断,运行exe。
bp 0x83ef7dc1虚拟机有问题,触发异常后关闭,就在用户模式下调试了。
注册SEH前的异常处理列表0:000> dd fs:[0]
0053:00000000 0019ff60 001a0000 0019d000 00000000
0053:00000010 00001e00 00000000 00260000 00000000
0053:00000020 00004d5c 00000308 00000000 0026002c
0053:00000030 0025d000 00000000 00000000 00000000
0053:00000040 00000000 00000000 00000000 00000000
0053:00000050 00000000 00000000 00000000 00000000
0053:00000060 00000000 00000000 00000000 00000000
0053:00000070 00000000 00000000 00000000 00000000
0x19ff60 ExceptionList地址,继续查看 dd 0x19ff600:000> dd 0x19ff60
0019ff60 0019ffcc 00434bb0 af181fd2 00000000
0019ff70 0019ff80 74b50419 0025d000 74b50400
0019ff80 0019ffdc 7750662d 0025d000 6bd86c90
0019ff90 00000000 00000000 0025d000 00000000
0019ffa0 00000000 00000000 00000000 00000000
0019ffb0 00000000 00000000 00000000 00000000
0019ffc0 00000000 0019ff8c 00000000 0019ffe4
0019ffd0 775186d0 1c9b01fc 00000000 0019ffec
19ffcc 指向下一个异常处理函数,434bb0地址为当前异常处理函数地址。
我们看下,下一个异常处理函数0:000> dd 0x19ffcc
0019ffcc 0019ffe4 775186d0 1c9b01fc 00000000
0019ffdc 0019ffec 775065fd ffffffff 775251d2
0019ffec 00000000 00000000 0042c6e5 0025d000
0019fffc 00000000 78746341 00000020 00000001
001a000c 00003318 000000dc 00000000 00000020
001a001c 00000000 00000014 00000001 00000007
001a002c 00000034 0000017c 00000001 00000000
001a003c 00000000 00000000 00000000 00000000
0x775186d0处函数 ,这是MSVC编译器对系统SEH异常的增强,后面会陆续讲到。
​
接着安装我们的SEH异常处理函数。
004325e6 8d05cfc34200 lea eax, [targetexe!ILT+970(?ExceptHandlerYG?AW4_EXCEPTION_DISPOSITIONPAU_EXCEPTION_RECORDPAXPAU_CONTEXT (0042c3cf)]004325ec 50 push eax004325ed 64ff3500000000 push dword ptr fs:[0]004325f4 64892500000000 mov dword ptr fs:[0], esp fs:0053:00000000=0019ff60 //安装异常处理函数004325fb 33c0 xor eax, eax004325fd c70034120000 mov dword ptr [eax], 1234h //产生内存写入异常
查看安装SEH后的异常处理列表0:000> dd 19fe44
0019fe44 0019ff60 0042c3cf 00646110 00499d2c
0019fe54 0025d000 cccccccc cccccccc cccccccc
0019fe64 cccccccc cccccccc cccccccc cccccccc
0019fe74 cccccccc cccccccc cccccccc cccccccc
0019fe84 cccccccc cccccccc cccccccc cccccccc
0019fe94 cccccccc cccccccc cccccccc cccccccc
0019fea4 cccccccc cccccccc cccccccc cccccccc
0019feb4 cccccccc cccccccc cccccccc cccccccc
0x42c3cf 这里是我们注册的SEH处理函数地址。
​
内核会将异常转到用户态,然后调用我们注册的处理函数,这就是windows异常处理的整体流程,后面会陆续分享涉及SEH、VEH等处理细节。
文章地址 https://blog.csdn.net/vs2008ASPNET/article/details/101752690 |