异常处理第二讲,结构化异常(微软未公开)
讲解之前,请熟悉WinDbg的使用,工具使用的博客链接
一丶认识段寄存器FS的内容,以及作用
首先我们要先认识一下段寄存器FS的作用,和内容,
我们打开OD,随便附加一个32位程序,看下段寄存器内容是什么
现在先介绍一下段寄存器吧
段寄存器,保存的是系统信息的一个表.而FS则是存的下标,在OD中,这个都是固定的
32位系统中,没有分段的概念了. 在16位系统中,我们定位物理地址的时候
是段 * 16 偏移 = 20位地址 ,而32位,其实也是这样做的,也是段 偏移的形式
只不过32系统扩展了,直接就可以寻址了,所以 CS DS ES SS 等段寄存器的值都是0了
0 偏移,那么现在就可以把0省略了.
FS中下标中存的什么,我们可以看下
使用WinDbg看,因为OD是不能看的,你跳转过去是没有显示任何信息的
关于FS下标存储的是什么,我们可以去看雪的论坛去看下具体存放的什么.
看下帖子内容,请点击: https://bbs.pediy.com/thread-175833.htm
二丶从FS寄存器中,查看TEB线程内容,以及异常链表
我们为什么要知道TEB的内容
是这样的,我们以前的筛选器异常,什么异常都会去处理的.但是我们觉着很不足,因为我们不知道具体的那个函数出现了异常,所以我们要对异常处理作进一步的升级
我们要知道那个函数出现了异常才可以.
那么怎么知道那个函数出现了异常哪,那么这就和FS里面的TEB里面的内容有关了.
TEB 也就是线程相关
我们使用WinDbg看下TEB的内容
我们看到了第一个框,WinDbg已经帮我们解释出来了(如果解释不出来,请看下自己的符号路径是否下载了,具体设置在熟悉WinDbg的博客中有讲解,以及现在的dt命令也有讲解)
第一个框,存放的是异常信息,我们还可以DT 一下,进去看一下
第二个框,我们可以看到是和进程相关的.
那么第一个框我们先DT 一
可以看出,这个地方是存放异常的地方,那么我们现在再次进入后面的结构体
注意,后面这个结构体,是未公开的,也就是微软不让我们自己用的.但是使用WinDbg解析符号我们得到了,或者我们去MSDN上搜索一下,也是搜索不到了.这个都是通过逆向得来的
,那现在我们看下这个表,显示的是异常信息表,我们DT 进去看
进去后
进去后,我们发现了,他是一个链表,next,指向了下一次的异常信息结构体
,而第二个就是一个函数指针,注意这个我们是可以查到的.打开VC6.0 把后面的结构体复制过去,然后使用VA 插件的GO功能,可以看到是什么结果
可以看出,这个结构体保存的是返回值信息,我们也可以去WinDbg中DT一下看下
因为是未公开的,所以只知道返回值是什么意思,
第一个是代表,我不处理,继续执行(这个筛选器异常已经讲过了)
第二个是我已经处理了.
看了上面介绍的怎么多,可能不知道什么意思
其实SHE(结构化异常) 就是使用内联汇编,给每个函数注册一个筛选器异常,然后每个函数都有自己的回调函数,而回调函数是第上面截图的第二个参数Handler,这个是一个函数指针.
因为未公开的,所以不知道.
但是我们也可以找得到,还是在VC6.0中定义上面那个结构体,然后GO过去
我们在上面找到的只是返回值,但是在下面寻找的时候,我们发现,使用的上面typedef定义的结构体
用来定义这个Handler了.
关于注册,关于注册,我们下面细讲,但是现在我们先熟悉一下段寄存器FS的使用
三丶熟悉段寄存器的使用,创建反调试程序
还记得我们上次,也就是第一次dt的时候,花了两个框吗,我们看到了一个PEB
PEB就是和进程相关的,不知道的我下方再次贴下图
我们先进去看下他有什么好玩的
进去之后,看到这里有一个检测Dbg调试的功能,那我们内联汇编使用一下FS寄存器,写一个调试检测是否调试.
下面写的代码可能不懂,因为你必须去看看雪的那篇帖子,才知道FS中到底是什么
这里截图一部分,我们大概要知道是什么.
看下以下代码
代码语言:javascript复制#include "stdafx.h"
#include <STDLIB.H>
int main(int argc, char* argv[])
{
char isDbg;
__asm
{
mov eax,fs:[0x18] //找到teb的位置
mov eax,[eax 0x30] //teb 30找到PEB的位置,对其取内容得到第一个首地址
mov eax,[eax 0x2] //首地址 偏移找到debug的位置对其取内容
mov isDbg,al //求出是否在调试
}
printf("%drn",isDbg);
system("pause");
}
,接着看下下面的图片
首先介绍一下我这次些联汇编是什么意思
mov eax,fs:[0x18] 对照看雪的部分截图我得到了 TEB的位置,而刚才的TEB我们也dt看了以下
现在再看下
第二步,mov eax,[eax 0x30]
从之句话中,我们得出了PEB指向的内容,也就是 DT _PEB ,得到第一个地址.
第三步: mov eax,[eax 0x2]
这句话代表的意思则是,我要从 PEB的首地址 2个偏移 然后得出里面的内容是什么.
而我们看下PEB里面是什么
这个正是我们要取出来的判断是否在调试的标志,而因为我们 eax 0x2的出来的是它的地址,但是我们有对它取内容了,所以结果放在了eax当中,如果不同,可以自己调试一下看看.
现在因为他是UChar类型,也就是无符号类型,所以一个字节,会放在al当中,所以我们把al的值,给了变量了.
第四步:输出我们变量的值是什么.
我们看下我们使用VC调试的时候输出什么
首先调试起来
单步一下
结果输出的是1
那么我们不调试,直接运行起来,看下结果是什么
结果是0,那么现在就好办了,我们可以开辟个线程,然后判断这个标志,如果为1,代表被调试了
那么我们就要让软件崩溃,开线程崩溃,为什么要崩溃,因为你让软件退出的话,会逆向的人,它会在ExitProcess的位置下段点,然后回溯,就可以找到你判断标志位的原因,而现在你可以判断标志位,然后如果为1我就开启一个线程,而这个线程我随便让它访问个错误的值,比如
给指针为NULL,然后再给NULL赋值,注意,只有当标志位1才开启,不为1不开启,这样崩溃了,他就会以为C05,而不调试的时候,你软件就是正常的.
当然上面的代码我是通过TEB寻得PEB地址然后加了02偏移,你也可以直接写
第30个下标就是PEB,
mov eax,fs:[0x30]
mov eax,[eax 0x2]
mov isdbg,al
一样可以.
而且你也可以隐藏模块,下方也有模块的链表.我们也可以字节遍历这个链表,找到自己的模块,然后隐藏.
四丶SEH结构化异常处理详解
上面我们说了很多,主要就是为了SHE结构化的讲解,比如FS寄存器的使用,因为当你会使用的时候,我们为每一个函数注册一个结构化异常处理就简单明了了.
那么我们开始注册一个异常处理
注册的意思:
我们上面第二步已经把异常的处理的链表找出了了,我们也知道了第二个参数是函数指针.
既然我们每个函数都注册一个异常处理,也就是要往这个链表中插入一个异常链
注意: 我们是往头上插入
(注意,只能在VC6.0中使用,高版本会有别的方法)
第一,我们要想一个问题,既然我们要注册一个结构化异常处理
下面且看我写一下异常处理的代码注册的代码;
代码语言:javascript复制#include "stdafx.h"
#include <WINDOWS.H>
#include <STDLIB.H>
#include <WINNT.H>
EXCEPTION_DISPOSITION __cdecl HANDLER1(
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext)
{
MessageBox(NULL,"我处理了异常rn",NULL,NULL);
return ExceptionContinueSearch;
}
void fun1()
{
__asm
{
push offset HANDLER1
push fs:[0]
mov fs:[0],esp
}
char *p = NULL;
*p = 1;
__asm
{
pop fs:[0]
add esp,4
ret
}
}
int main(int argc, char* argv[])
{
fun1();
system("pause");
}
请先看下上面的代码
我们一开始的内联汇编,是要先注册
代码语言:javascript复制__asm
{
push 函数指针
push fs:[0]
mov fs:[0],esp
}
这句话什么意思,因为函数从右向左传递参数
请看下图
我们首先取得了fs:[0] 也就是第一个异常链的位置
我们dt一下看看
0偏移是_NT_TIB
我们接着dt一下这个
发现了0偏移就是异常链表,是一个指针,所以我们可以直接push fs:[0]了
那么这句话什么意思,
我们使用OD调试一下我们的程序看下
单步调试一下看下什么结果
我们也要调到FS 的数据区位置
单步调试,跟着走,看下会有什么结果
首先是压入的函数的地址,我们跳转过去看下 ctrl G
OD自动帮我们标出来了结构异常处理程序,
现在看下第二句,压入FS地址的0的内容.也就是旧的异常链表
现在栈顶位置,然后重新赋值给FS:[0]的位置
现在,我们这三行的意思就是往fs[0]位置的异常链表的头部插入一个链表
现在的FS:[0]的位置是我们当前的位置,那么调用的时候会调用我们当前注册的HANDLE1的回调函数,当我们把这个链表注销后,才会把以前的链表的位置换回去
如果不懂,看下面图片:
如果真的不理解,那么内存布局多看几遍,其实把FS:[0]位置改为我们的栈的位置,而以前的异常链表的位置我们已经保存了.
所以下面可以pop fs:[0] 把我们第一个栈顶的位置,也就是保存的以前的异常链表的位置,换回去了.
现在我们试下我们程序的正常运行
五丶C 中的try catch 语法的实现
我们学过C 的都知道,C 中有一个语法叫做try catch
也可以 throw 一个异常出来
只不过一个是主动抛异常,一个是被动的抛异常
现在假设,我们fun1 函数里面调用了fun2,(fun2也不注册异常处理)
我们fun2出现了异常,但是我们不想处理怎么办.
那么它会往上面一层寻找,那么上面一层,也就是我们注册的fun1的异常处理的位置,会调用对应的fun1的回调函数
那么我们现在试一下.
而Fun2()
那么我们运行起来,看下信息框来了没有.
发现来了,那么这个就是异常处理中的 throw的原理,会往上层查找.
课堂资料就是注册SEH的几行代码,请根据博客编写,自己手动敲下