SEH异常之编译器原理探究
_try_except原理
调用_except_handle3
这个异常处理函数,这里并不是每个编译器的异常处理函数都是相同的,然后存入结构体,将esp的值赋给fs:[0]
,再就是提升堆栈的操作
每个使用 _try _except
的函数,不管其内部嵌套或反复使用多少_try _except
,都只注册一遍,即只将一个 _EXCEPTION_REGISTRATION_RECORD
挂入当前线程的异常链表中(对于递归函数,每一次调用都会创建一个 _EXCEPTION_REGISTRATION_RECORD
,并挂入线程的异常链表中)。
typedef struct _EXCEPTION_REGISTRATION_RECORD {struct _EXCEPTION_REGISTRATION_RECORD *Next;PEXCEPTION_ROUTINE Handler;} EXCEPTION_REGISTRATION_RECORD;
可以看到只有一个异常处理函数
那么这里编译器是如何做到只用一个异常处理函数的呢?编译器把原来_EXCEPTION_REGISTRATION_RECORD
结构进行了拓展,添加了三个成员
struct _EXCEPTION_REGISTRATION{struct _EXCEPTION_REGISTRATION *prev;void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);struct scopetable_entry *scopetable;int trylevel;int _ebp;};
新堆栈结构如下
scopetable
struct scopetable_entry
{DWORD previousTryLevel //上一个try{}结构编号 PDWRD lpfnFilter //过滤函数的起始地址PDWRD lpfnHandler //异常处理程序的地址
}
查看地址可以发现有三个结构体
存储着的正式异常函数的开始地址和结束地址
第一个值previousTryLevel
是上一个try
结构的编号,这里如果在最外层就是-1,如果在第二层就是0,如果在第三层就是1,以此类推
trylevel
该成员表示代码运行到了哪个try
结构里面,进入一个try
则加1,try
结构执行完成之后则减1
_except_handler3
1.CPU检测到异常 -> 查中断表执行处理函数 -> CommonDispatchException
-> KiDispatchException
-> KiUserExceptionDispatcher
-> RtlDispatchException
->VEH
-> SEH
2.执行_except_handler3
函数
<1> 根据trylevel
选择scopetable
数组
<2> 调用scopetable
数组中对应的lpfnFilter
函数
1.EXCEPTION_EXECUTE_HANDLER(1) 执行except代码
2.EXCEPTION_CONTINUE_SEARCH(0) 寻找下一个
3.EXCEPTION_CONTINUE_EXECUTION(-1) 重新执行
<3> 如果lpfnFilter
函数返回0 向上遍历 直到previousTryLevel=-1
假设有两个异常点
首先找到trylevel
为0
然后找到异常过滤表达式为1
然后遍历数组的lpfnFilter
如果返回值为1则调用异常处理函数,如果为0则该异常函数不处理,如果为-1则继续从原异常点向下执行
假设在B这个地方出异常,得到trylevel
为2
那么这里就回去遍历lpfnFilter
为2的地方
假设这里返回值为0,则继续查找,注意这个地方是向上查找,首先判断当前previousTryLevel
的值是否为-1,如果为-1就停止查找(-1代表已经是最外层)try
结构,然后再向上找,假设这里返回值仍然为0,判断previousTryLevel
的值为-1,就停止查找,没有找到响应的异常处理函数
_try_finally原理
无论try
结构体中是什么代码,都会执行finally
里面的代码
// SEH6.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>VOID ExceptionTest()
{__try{return;printf("Other code");}__finally{printf("Must run this code");}
}int main(int argc, char* argv[])
{ExceptionTest();getchar();return 0;
}
局部展开
当try
里面没有异常,而是return
、continue
、break
等语句时,就不会走_except_handle3
这个函数,而是调用_local_unwind2
进行展开
然后调用[ebx + esi*4 + 8]
跟进去就到了finally
语句块的地方
我们探究一下实现的原理,这里本来应该是lpfnFilter
参数,指向异常处理过滤的代码的地址,但是这里是0。只要这个地方的地址为0就是finally
语句块
__global_unwind2
函数最终会调用一个RtlUnwind
函数,该函数内容比较杂乱,其大体流程如下
全局展开
// SEH6.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>VOID ExceptionTest()
{__try{__try{__try{*(int*)0 = 1;}__finally{printf("Must run this code : A");}}__finally{printf("Must run this code : B");}}__except(1){printf("Here is Exception_functions");}
}int main(int argc, char* argv[])
{ExceptionTest();getchar();return 0;
}
全局展开就是一层一层的向上找异常处理函数,finally
模块还是照常执行
未处理异常
入口程序的最后一道防线
这里调用mainCRTStartup()
,然后调用入口程序
相当于这里才是一个进程开始执行的地方
这里有一个call调用,跟进去看看
发现有修改fs:[0]
的操作,这里就相当于编译器为我们注册了一个异常处理函数
这里到kernel32.dll
里面的BaseProcessStart
里面看一下,这里有一个注册SEH异常处理函数的操作
线程启动的最后一道防线
// SEH7.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>DWORD WINAPI ThreadProc(LPVOID lpParam)
{int i = 1;return 0;
}int main(int argc, char* argv[])
{CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);getchar();return 0;
可以发现线程也是从kernel32.dll
开始的
然后跟进调用
可以发现还是注册了一个异常处理函数
还是去IDA里面看BaseThreadStart
函数,发现也注册了一个SEH
异常的函数
UnhandledExceptionFilter
相当于编译器为我们生成了一段伪代码
__try
{}
__except(UnhandledExceptionFilter(GetExceptionInformation())
{//终止线程//终止进程
}
只有程序被调试时,才会存在未处理异常
UnhandledExceptionFilter的执行流程:
1) 通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发 2) 如果没有被调试: 查询是否通过SetUnhandledExceptionFilter注册处理函数 如果有就调用 如果没有通过SetUnhandledExceptionFilter注册处理函数 弹出窗口 让用户选择终止程序还是启动即时调试器 如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLER
SetUnhandledExceptionFilter
如果没有通过SetUnhandledExceptionFilter
注册异常处理函数,则程序崩溃
测试代码如下,我自己构造一个异常处理函数callback
并用SetUnhandledExceptionFilter
注册,构造一个除0异常,当没有被调试的时候就会调用callback
处理异常,然后继续正常运行,如果被调试则不会修复异常,因为这是最后一道防线,就会直接退出,起到反调试的效果
// SEH7.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>long _stdcall callback(_EXCEPTION_POINTERS* excp)
{excp->ContextRecord->Ecx = 1;return EXCEPTION_CONTINUE_EXECUTION;
}int main(int argc, char* argv[])
{SetUnhandledExceptionFilter(callback);_asm{xor edx,edxxor ecx,ecxmov eax,0x10idiv ecx}printf("Run again!");getchar();return 0;
}
直接启动可以正常运行
使用od打开则直接退出
KiUserExceptionDispatcher
只有当前程序处于调试的时候才可能产生未处理异常
1) 调用RtlDispatchException 查找并执行异常处理函数2) 如果RtlDispatchException返回真,调用ZwContinue再次进入0环,但线程再次返回3环时,会从修正后的位置开始执行。3) 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发
(参见KiUserExceptionDispatcher代码)
SEH异常之编译器原理探究相关推荐
- 浏览器工作原理探究详解
浏览器工作原理探究 标签: 浏览器工作原理 / web性能优化 引言 最近对web的性能优化比较感兴趣,而前端代码主要在浏览器工作的.如果对浏览器的工作原理了解清楚,可以为web性能优化提供方向以及理 ...
- Source Map的原理探究
摘要: Source Map很神奇,它的原理挺复杂的- 原文:source map 的原理探究 地址:https://www.cnblogs.com/Wayou/p/understanding_fro ...
- KVM 虚拟化原理探究--启动过程及各部分虚拟化原理
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- C++异常 和 SEH异常 的一点小结
[如何区分异常] "C++异常" 就是 try{}catch(...){} "SEH异常" 就是 __try{} __except(-1/0/1){} 目前微软 ...
- Leveldb学习笔记:leveldb的使用与原理探究
文章目录 前言 一.leveldb是什么? 二.leveldb的使用 1.打开一个数据库 2.Status 3.关闭一个数据库 4.数据库读写 5.原子更新 6.同步写 7.并发 8.迭代器 9.Sn ...
- java lock的原理,Java中Lock原理探究
在对于lock锁的使用上,很多人只是掌握了最基础的方法,但是对实现的过程不是很清楚.这里我们对lock锁功能的实现进行分析,以ReentrantLock为例,分析它的锁类型,并对相关的调用方法进行展示 ...
- vmware nat模式原理探究,实现虚拟机跨网段管理
vmware nat模式原理探究: 理解nat模式,我们能更加了解主机与虚拟机之间如何通信,以及虚拟机如何实现上网. 以及便于我们分析虚拟机与主机无法通信和无法上外网的问题. 下面通过实战:虚拟网络拓 ...
- 并发队列-无界非阻塞队列 ConcurrentLinkedQueue 原理探究
并发队列-无界非阻塞队列 ConcurrentLinkedQueue 原理探究 http://www.importnew.com/25668.html 一. 前言 常用的并发队列有阻塞队列和非阻塞队列 ...
- KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听
书读百变,其义自见! 将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base 代码中有详 ...
最新文章
- Leangoo看板Jenkins配置指南
- 纪念小柴昌俊 | 中微子天体物理学的诞生
- cvpr2019/cvpr2018/cvpr2017(Papers/Codes/Project/Paper reading)
- 元素位置互换之移位算法
- linux mysql优化_Linux上跑MySQL优化技巧
- 计算机专业考注册测绘师经验,注册测绘师考试攻略
- Autodesk 3DMax2023下载安装教程
- 仅三行代码的按键扫描程序,绝对够经典(秒杀郭天祥讲解的按键扫描)
- Python 自动发送邮件
- python 实现复数计算器
- 斯坦福NLP名课带学详解 | CS224n 第15讲 - NLP文本生成任务(NLP通关指南·完结)
- 关于js 中 try catch用法
- 【MATLAB】求复合函数
- 全球及中国表面保护胶带行业研究及十四五规划分析报告
- mongodb中文件导入报错error inserting documents解决方法
- 强制删除桌面多余或恶意IE图标方法 :强制删除桌面多余或恶意IE图标方法:
- 豆瓣2018年度电影榜单
- siri 苹果语音控制功能
- 如何用matlab绘制180°根轨迹、参数根轨迹
- 八门大神可以修改服务器游戏吗,八门神器怎么修改游戏 修改游戏完美教程
热门文章
- javaWeb-filter(过滤器二)
- 岛屿的最大面积(dfs)
- 无法调用独显,nvidia-setting打开失败
- linux eclipse cuda,CUDA Linux eclipse intel Mosix
- 20221219今天的世界发生了什么
- 友立公司编著的教程中有关“场顺序”选择的相关说明
- Chrome 已称王,IE 今何在?
- 信息系统项目管理师第四版知识摘编:第2章 信息技术发展
- php ues导入,php中同时使用多个use导入命名空间时的问题
- ubuntu安装google app engine环境