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
断点的小知识
我们在OD
的77394DA1
设置了个一个断点,原始机器码为8BC8
.其实在断点后这个机器码会改变为CCC8
,但是在OD
中不会显示这个改变。
我们可以通过WinHex
查看的话即可看到这个情况
OD
断点截图如下
WinHex
:
CC
是什么?
CC
其实就是int3
断点
以上是关于Win32 异常过滤器使用与异常上报分析等的主要内容,如果未能解决你的问题,请参考以下文章
关于异常System.ComponentModel.Win32Exception