Windows异常学习笔记(五)—— 未处理异常

Windows异常学习笔记(五)—— 未处理异常

前言

一、学习自滴水编程达人中级班课程,官网:https://bcdaren.com
二、海东老师牛逼!

要点回顾

windows异常处理流程

CPU检测到异常
↓
查IDT表执行中断处理函数
↓
CommonDispatchException		//存储异常相关信息
↓
KiDispatchException			//异常分发处理函数,查找由哪一个处理程序处理这个异常
							//判断0环异常还是3环异常
							//若为3环,修正EIP,指向KiUserExceptionDispatcher(ntdll.dll)
↓
KiUserExceptionDispatcher	//通过RtlDispatchException查找异常处理函数位置,VEH/SEH
↓
RtlDispatchException		//先查找VEH,若找不到,查找SEH(从FS:[0]开始遍历)
↓
VEH
↓
SEH

思考:会不会存在一种情况,SEH中也没有程序能处理当前异常
答案:一般来说,不存在

最后一道防线

描述:当我们程序刚开始执行时,编译器已经替我们注册了一个异常处理程序,因此叫做最后一道防线

实验一:理解最后一道防线

1)编译并运行以下代码(在return处设置断点)

#include <stdio.h>
#include <windows.h>

int main()
{
	int x = 1;

	return 0;
}

2)查看堆栈调用情况
Windows异常学习笔记(五)—— 未处理异常
可以发现,程序并不是从main函数开始运行的,而是从kernel32.dll的某个位置开始运行的

3)查看kernel32中的代码
Windows异常学习笔记(五)—— 未处理异常
4)查看7181704b位置调用的函数的具体代码

7C81704B   call        7C8024D6		//在堆栈中注册了一个异常处理程序

7C8024D6   push        7C839AC0h
7C8024DB   mov         eax,fs:[00000000]	//读取了FS:[0]
7C8024E1   push        eax
7C8024E2   mov         eax,dword ptr [esp+10h]
7C8024E6   mov         dword ptr [esp+10h],ebp
7C8024EA   lea         ebp,[esp+10h]
7C8024EE   sub         esp,eax
7C8024F0   push        ebx
7C8024F1   push        esi
7C8024F2   push        edi
7C8024F3   mov         eax,dword ptr [ebp-8]
7C8024F6   mov         dword ptr [ebp-18h],esp
7C8024F9   push        eax
7C8024FA   mov         eax,dword ptr [ebp-4]
7C8024FD   mov         dword ptr [ebp-4],0FFFFFFFFh
7C802504   mov         dword ptr [ebp-8],eax
7C802507   lea         eax,[ebp-10h]
7C80250A   mov         fs:[00000000],eax	//修改了FS:[0]
7C802510   ret

5)使用IDA进行查看(BaseProcessStart)
Windows异常学习笔记(五)—— 未处理异常
6)总结:当我们程序刚开始执行时,编译器已经替我们注册了一个异常处理程序,因此在主线程中,找不到SEH的情况基本是不会发生的

思考:如果新起一个线程,会出现找不到SEH的情况吗?
答案:不会,参考实验二

实验二:新线程的最后一道防线

1)编译并运行以下代码(在新线程中设置断点)

#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	int x = 1;

	return 0;
}

int main()
{
	CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
	getchar();
	return 0;
}

2)当中断在新线程中时,查看新线程堆栈调用情况
Windows异常学习笔记(五)—— 未处理异常
可以发现,线程也不是从我们提供的线程函数开始运行的,仍然是从kernel32.dll的某个位置开始运行的

3)查看kernel32中的代码
Windows异常学习笔记(五)—— 未处理异常
在地址7C80B6E3处也调用了地址7C8024D6的代码(__SEH_prolog),注册了异常处理函数

总结

  1. 无论是主线程,还是新创建的线程,都存在最后一道防线,即编译器为我们注册的异常处理程序的结构
  2. 这个异常处理程序相当于以下代码
    __try
    {
    
    }
    __except(UnhandledExceptionFilter(GetExceptionInformation())
    {
    	//终止线程
    	//终止进程
    }
    

UnhandledExceptionFilter

描述

  1. 最后一道防线调用的异常处理程序的过滤函数
  2. 只有当它的返回值为EXCEPTION_CONTINUE_SEARCH(0)时,当前线程不存在对应的SEH
  3. 只有在当前程序处于调试状态时,才会发生上述情况

执行流程

  1. 通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发
  2. 如果没有被调试:
    1)查询是否通过SetUnhandledExceptionFilter注册处理函数 如果就调用
    2)如果没有通过SetUnhandledExceptionFilter注册处理函数,弹出窗口,让用户选择终止程序还是启动即时调试器
    3)如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLER,此时会执行except中的代码

实验三:理解UnhandledExceptionFilter

1)编译并运行以下代码

#include <stdio.h>
#include <windows.h>

long __stdcall callback(_EXCEPTION_POINTERS *excp)
{
	excp->ContextRecord->Ecx = 1;
	return EXCEPTION_CONTINUE_EXECUTION;
}

int main()
{
	SetUnhandledExceptionFilter(callback);

	__asm
	{
		xor edx, edx
		xor ecx, ecx
		mov eax, 0x10
		idiv ecx
	}

	printf("程序正常执行\n");
	getchar();
	return 0;
}

2)执行结果
Windows异常学习笔记(五)—— 未处理异常
3)双击exe执行,执行结果
Windows异常学习笔记(五)—— 未处理异常

4)总结

  1. 当程序处于调试状态时,UnhandledExceptionFilter返回EXCEPTION_CONTINUE_SEARCH,此时该异常没有SEH能够进行处理,程序无法向下运行
  2. 当程序不处于调试状态时,UnhandledExceptionFilter返回EXCEPTION_EXECUTE_HANDLER,异常由注册的callback函数进行处理

思考:遇到这种情况如何进行调试
答案:HOOK NtQueryInformationProcess

未处理异常

执行流程

KiUserExceptionDispatcher
↓
RtlDispatchException			//查找并执行异常处理函数
								//如果返回真,调用ZwContinue再次进入0环
								//但线程再次返回3环时,会从修正后的位置开始执行
							
								//如果返回假,调用ZwRaiseException进行第二轮异常分发
								//(参见KiUserExceptionDispatcher代码)

Windows异常学习笔记(五)—— 未处理异常

上一篇:[安全攻防进阶篇] 十.熊猫烧香病毒机理IDA和OD逆向分析--病毒释放过程(中)


下一篇:C语言常见的自增\自减,判断,循环等反汇编