异常处理第二讲,结构化异常(微软未公开)

            异常处理第二讲,结构化异常(微软未公开)

 转载请注明出处

讲解之前,请熟悉WinDbg的使用,工具使用的博客链接: http://www.cnblogs.com/iBinary/p/7589722.html

 

一丶认识段寄存器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中到底是什么

 

 

 

这里截图一部分,我们大概要知道是什么.

看下以下代码

 

#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("%d\r\n",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中使用,高版本会有别的方法)

第一,我们要想一个问题,既然我们要注册一个结构化异常处理

下面且看我写一下异常处理的代码注册的代码;

#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,"我处理了异常\r\n",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");
 
}

 

请先看下上面的代码

我们一开始的内联汇编,是要先注册

__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的几行代码,请根据博客编写,自己手动敲下

 

 

博客园IBinary原创 博客连接:http://www.cnblogs.com/iBinary/

转载请注明出处,谢谢

 

posted @ 2017-09-26 12:58  iBinary  阅读(2601)  评论(2编辑  收藏  举报