zoukankan      html  css  js  c++  java
  • 异常处理第二讲,结构化异常(微软未公开)

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

     转载请注明出处

    讲解之前,请熟悉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
    ",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,"我处理了异常
    ",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/

    转载请注明出处,谢谢

  • 相关阅读:
    Node.Js安装教程
    使用Idea 配置maven
    Sublime Text3 使用记录
    配置Java 环境变量
    什么是应届生?要不要签三方?看看就知道了
    Python学习(二)——深度学习入门介绍
    python学习(一)——python与人工智能
    php学习(二)——html + css
    19、SOAP安装,运用与比对结果解释
    24、Linux 多线程压缩工具pigz 的学习
  • 原文地址:https://www.cnblogs.com/iBinary/p/7596199.html
Copyright © 2011-2022 走看看