Win32 异常过滤器使用与异常上报分析等

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Win32 异常过滤器使用与异常上报分析等相关的知识,希望对你有一定的参考价值。

概念

异常过滤器是windows提供给我们处理异常的一种机制。当你存在一个未捕获的异常时,系统会把这个异常丢给调试器(比如od或者x86dbg等),如果不存在调试器那么直接丢给异常处理器,当你捕获到异常时你可以收集异常或者弹出一个窗口给用户,甚至你可以处理这个异常让用户继续运行。

官方文档

我们来看几个小例子

int main()


	long *plong = nullptr;
	*plong = 2L;
	std::cout << "Hello World!\\n";

	return EXIT_SUCCESS;


上面的代码运行后因为访问空指针会奔溃,我来完成一个需求,当app奔溃的时候弹出一个窗口让用户感知这类问题,这里就可以用到我们的异常过滤器了

LONG myExceptionFilter(_EXCEPTION_POINTERS *lpExceptionInfo) 

	//弹出信息
	MessageBoxA(NULL, "很不幸遇到未知异常,我们将此信息上报给工程分析", "奔溃咯", MB_OK);

    //函数返回值非常重要具体您可以看官方文档返回,这里返回的意思是按照程序设定情况处理奔溃(弹出系统窗口然后退出程序)
	return EXCEPTION_CONTINUE_SEARCH;



int main()

	//设置异常过滤器,奔溃时会回调函数
	 SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)myExceptionFilter);


	long *plong = nullptr;
	*plong = 2L;
	std::cout << "Hello World!\\n";

	return EXIT_SUCCESS;




假设我们需要收集奔溃信息给后端然后分析呢?那么我们就需要进行转储(dump)操作,我们这里做一个简单的演示和如何分析.

关于dump文件的生产可以看如下代码


#include <iostream>
#include <Windows.h>
#include <Windows.h>
#include <DbgHelp.h>

#pragma comment(lib, "Dbghelp.lib") 
//生产minidump文件到程序目录下,当然你也可以使用fulldump。
LONG myExceptionFilter(_EXCEPTION_POINTERS *lpExceptionInfo) 


	MessageBoxA(NULL, "很不幸遇到未知异常,我们将此信息上报给工程分析", "奔溃咯", MB_OK);
	// Open a file
	HANDLE hFile = CreateFile("MiniDump.dmp", GENERIC_READ | GENERIC_WRITE,
		0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hFile != NULL && hFile != INVALID_HANDLE_VALUE) 

		// Create the minidump 
		MINIDUMP_EXCEPTION_INFORMATION mdei;
		mdei.ThreadId = GetCurrentThreadId();
		mdei.ExceptionPointers = lpExceptionInfo;
		mdei.ClientPointers = FALSE;

		MINIDUMP_TYPE mdt = MiniDumpNormal;
		BOOL retv = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
			hFile, mdt, (lpExceptionInfo != 0) ? &mdei : 0, 0, 0);

		if (!retv) 
			printf(("MiniDumpWriteDump failed. Error: %u \\n"), GetLastError());
		
		else 
			printf(("Minidump created.\\n"));
		

		// Close the file
		CloseHandle(hFile);

	
	else 
		printf(("CreateFile failed. Error: %u \\n"), GetLastError());
	
	return EXCEPTION_CONTINUE_SEARCH;



int main()

	 SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)myExceptionFilter);

	

	long *plong = nullptr;
	*plong = 2L;
	std::cout << "Hello World!\\n";

	return EXIT_SUCCESS;


上面的代码运行后我们会在程序目录下看到如下文件MiniDump.dmp

注意:我们这里存在文件符号文件(pdb)进行错误分析

我们这里用windbg预览版打开dump相关文件

其实dump包含很多当前程序奔溃前的信息,举个例子:
查看线程


加载的模块

内存信息

寄存器信息

废话不多说我们看看如何分析上面的奔溃

在有符号情况输入命令.ecxr

在无符号情况输入命令.ecxr

我们再看看其他例子,如何使用异常过滤器进行补修错误异常


int j = 0;
LONG myExceptionFilter(_EXCEPTION_POINTERS *lpExceptionInfo) 

	
	j = 4;
	//这个返回值告诉系统异常已经被处理了可以继续运行前面哪行
	return EXCEPTION_CONTINUE_EXECUTION;



int main()

	 SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)myExceptionFilter);

	 int i =6;
	 //非常明显除0异常
	 int ret = i / j;
	 std::cout << ret << std::endl;
	 getchar();
	return EXIT_SUCCESS;



运行后结果:

特别注意上面的代码千万不要直接通过IDE运行否则异常过滤器不会执行(异常首先会给调试器),或者您使用如下方式运行:

进阶使用:LPTOP_LEVEL_EXCEPTION_FILTER里面包含了寄存器信息,你可以修改寄存器跳过错误行或者做其他骚操作

windbg的安装

安裝

现有两个版本一个是预览版ui非常酷炫也实用,另一个是旧版本

预览版

旧版本:

安装:
安装说明

对于预览版直接打开微软商店搜索即可或者如下链接

https://www.microsoft.com/zh-cn/p/windbg-preview/9pgjgd53tn86?activetab=pivot:overviewtab

逆向工程的运用

我们知道异常首先会抛出给调试器,那么我们可以在app中故意抛出一个异常,那么如果程序被调试就会交给调试器导致无法继续破解。如果没有调试器那么我们的处理器自行把这个错误修正再继续运行。
我们这里给出一个demo,这个demo在没有被调试的情况下回无限弹出信息:

.586
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc
   
   includelib user32.lib
   includelib kernel32.lib


WinMain proto :DWORD,:DWORD,:DWORD,:DWORD


.data
   ClassName db "MainWinClass",0
   AppName  db "Main Window",0
   g_szCC  db "这里有CC断点",0
   g_szover  db "程序结束",0
   g_sz1  db "非法程序后的一个窗口",0
   g_sz2  db "非法程序后的二个窗口",0
   g_sz3  db "检测到单步",0
   g_sz4  db "检测到返回",0
   g_sz5  db "异常返回",0
   g_sz6  db "测试",0
   g_sz7  db "非法程序后的三个窗口",0
	g_sz8  db 1
.data?
   hInstance HINSTANCE ?
   CommandLine LPSTR ?

.code

MyUnhandledExceptionFilter proc pExceptionInfo:ptr EXCEPTION_POINTERS
	LOCAL @pContext:ptr PCONTEXT
	LOCAL @pExpRecord:ptr PEXCEPTION_RECORD
	
	mov ebx,pExceptionInfo
	assume ebx:ptr EXCEPTION_POINTERS
	push [ebx].ContextRecord
	pop @pContext
	
	push [ebx].pExceptionRecord
	pop @pExpRecord
	assume ebx:nothing
	
	
	
	
	mov ebx,@pExpRecord
	assume ebx:ptr EXCEPTION_RECORD
	;跳过内存访问异常的指令
	mov edx,@pContext
	assume edx:ptr CONTEXT
	
	
	mov ecx,0
		
	.if [ebx].ExceptionCode == EXCEPTION_ACCESS_VIOLATION
		
		add [edx].regEip,2
		
		;设置单步
		or [edx].regFlag,100h
	invoke MessageBox,NULL,offset g_sz6,NULL,MB_OK
	.elseif [ebx].ExceptionCode == EXCEPTION_SINGLE_STEP
		;判断是否有cc
		mov eax,[edx].regEip
		
		.if byte ptr [eax] == 0cch
		;	;说明有cc
			invoke MessageBox,NULL,offset g_szCC,NULL,MB_OK
			;检测到int3指令,证明被注入程序退出
			mov eax,EXCEPTION_EXECUTE_HANDLER
			ret
	;	.elseif  byte ptr [eax] == 0C3h
	;		;如果是ret,则不设置单步
	;		invoke MessageBox,NULL,offset g_sz4,NULL,MB_OK
		.else
			
	
			.if g_sz8==1
				mov g_sz8,0
				mov  [edx].regEip,offset _SafePlace2
				 ;设置单步tf标志位
				or [edx].regFlag,100h
			.endif
			
			
			
		.endif
			
	.endif
	

	
	
	assume ebx:nothing
	assume edx:nothing
	
	mov eax,EXCEPTION_CONTINUE_EXECUTION
	ret

MyUnhandledExceptionFilter endp

; ---------------------------------------------------------------------------


MyProtoctFun proc
	
	mov eax,0ffffffffh
	mov eax,[eax]
	invoke MessageBox,NULL,offset g_sz1,NULL,MB_OK
	mov eax,0
	mov eax,0
	mov eax,0

	
_SafePlace3:	
	invoke MessageBox,NULL,offset g_sz7,NULL,MB_OK
	
	
	ret

MyProtoctFun endp

start:
	invoke SetUnhandledExceptionFilter,offset MyUnhandledExceptionFilter
	
	invoke MyProtoctFun
	
	
	_SafePlace2:	
	invoke MessageBox,NULL,offset g_sz2,NULL,MB_OK
	mov g_sz8,1
	invoke MyProtoctFun
	mov eax,0
	mov eax,0
	mov eax,0
	invoke MessageBox,NULL,offset g_szover,NULL,MB_OK

end start

断点的小知识

我们在OD77394DA1设置了个一个断点,原始机器码为8BC8.其实在断点后这个机器码会改变为CCC8,但是在OD中不会显示这个改变。
我们可以通过WinHex查看的话即可看到这个情况

OD断点截图如下

WinHex:

CC是什么?


CC其实就是int3断点

以上是关于Win32 异常过滤器使用与异常上报分析等的主要内容,如果未能解决你的问题,请参考以下文章

bugly异常上报语音sdk接入总结

关于异常System.ComponentModel.Win32Exception

flutter版bugly已完成,欢迎使用

C++软件异常分析与排查的学习历程

99%的程序都没有考虑的网络异常?使用Fundebug.notify()主动上报

前端错误监控的简单设计与实现