zoukankan      html  css  js  c++  java
  • 两种异常(CPU异常、用户模拟异常)的收集

     Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

    两种异常(CPU异常、用户模拟异常)的收集

     文章的核心:异常收集的是什么?(TrapFrame与ExceptionRecord);如何收集异常?(看文章)。

    1. 异常的分类

      ① CPU指令异常 (比如除零异常) CPU运行检测到;

      ② 用户模拟异常 (throw 1)

      其在收集是存在不同,但在派发时和处理时是完全相同的,下面我们就来分析一下其存在的不同。

    2. CPU指令异常 - 除零异常(分析)

      1)Trap00函数分析

        除零异常会引发中断,其执行 Trap00 函数,该函数逆向分析如下,我们在三环进零环时,已经学习过一个_TrapFrame结构,其本质也是填写该结构。

        注意,我们应该清楚,TrapFrame结构不只是三环进零环使用,而是只要走IDT表都要使用该结构,其是通过TrapFrame怎么返回,至于从三环还是零环进来这不重要。

        其分析情况如下图,Trap00总结做已下事情:

        ① 保存TrapFrame基本的信息;

        ② 检查DebugActive是否存在调试器,若存在调试器,则保存Dr相关寄存器;

        ③ 判断来自内核或三环,来确定其错误号(0xc0000094/0xc0000095);

        ④ 其最后有三次机会走CommonDispatchException函数。

        

      2) CommonDispatchException 异常记录函数分析

        该函数就是生成ExceptionRecord这个结构体,填写完成,然后调用nt!KiDispatchException进行下一步分析(三环的模拟异常直接在三环形成,然后拷贝到零环)。

        我们要知道ExceptionRecord的作用:存储了异常码异常属性、异常记录(链表,多次出现异常时记录)异常发生地址异常有关参数

        其中该函数的重要一点:其ExceptionFlags置为0,我们之后关注KiDispatchException如何处理的。

        

    3.用户模拟异常分析

      像thorw 1 这种引发的是用户模拟异常,我们下面就来分析一下如下代码:

    #include "stdafx.h"
    #include <windows.h>
    
    int main(int argc, char* argv[])
    {
        throw 11;
        
        return 0;
    }

      9:        throw 11;
      00401028   mov         dword ptr [ebp-4],0Bh
      0040102F   push        offset __TI1H (00423580)
      00401034   lea         eax,[ebp-4]
      00401037   push        eax
      00401038   call        __CxxThrowException@8 (004011e0)

      1)CxxThrowException反汇编分析

        如下是CxxThrowException函数的反汇编,该函数需要两个参数,一个是我们模拟的代码,另外一个是ExceptionList,我们下面详细分析该段代码的执行流程:

        004011E0   push        ebp
        004011E1   mov         ebp,esp
        004011E3   sub         esp,20h
        004011E6   push        esi
        004011E7   push        edi
        004011E8   mov         ecx,8
        004011ED   mov         esi,offset string "The value of ESP was not properl"...+0E0h (00422118)
        004011F2   lea         edi,[ebp-20h]
        004011F5   rep movs    dword ptr [edi],dword ptr [esi]
        // 复制一段结构体
        004011F7   mov         eax,dword ptr [ebp+8]
        004011FA   mov         dword ptr [ebp-8],eax
        004011FD   mov         ecx,dword ptr [ebp+0Ch]
        00401200   mov         dword ptr [ebp-4],ecx
        // 传入参数
        00401203   lea         edx,[ebp-0Ch]
        00401206   push        edx  // 参数指针
        00401207   mov         eax,dword ptr [ebp-10h]
        0040120A   push        eax // 参数个数
        0040120B   mov         ecx,dword ptr [ebp-1Ch]
        0040120E   push        ecx // 异常标志位
        0040120F   mov         edx,dword ptr [ebp-20h]
        00401212   push        edx  // 异常码
        00401213   call        dword ptr [__imp__RaiseException@16 (0042a154)]

        该代码所做的事情如下:

        ① 先从内存中拷贝一段0x20字节的固定结构体到堆栈中;

        ② 将ExceptionList也拷贝到堆栈中(该结构体内部);

        ③ 传入有关参数调用RaiseException函数。

        可能光看比较抽象,下面画出其详细的栈帧图就知道了,注意,ThrowCode虽然从用户代码传入进来,但分析其函数并没有用到,而是直接调用一段固定的异常码。

        而&ThrowCode以及异常链被作为其参数存储,这样通过分析就可以轻易找到其ThrowCode值,其作为参考之后来处理SEH。

          

       2)kernel32!RaiseException函数分析

        前面分析过,其CxxThrowException函数调用该函数,该函数的本质就是生成一个ExceptionRecord结构体,

        我们之前分析过,如果是CPU指令异常,其在CommonDispatchException函数中生成该结构体;

        函数分析如下,该过程比较好理解,注意其触发异常的地址标记为该地址,并不是ThrowCode的地址,这个是你要明确的,后续在将异常处理时这里用到SEH异常,我们再继续分析

        

       3)ntdll!RtlRaiseException函数分析

        该函数分析如下,其在三环进入零环前在堆栈中保存了一个_CONTEXT结构体,我们之前在用户APC分析过,返回三环时要在零环向三环堆栈中写入一个CONTEXT用于保存。

        来自用户层的异常我们确定其必须返回,,则就进零环之前就直接在三环中预先保存了一个_CONTEXT结构体,至于其如何使用,我们在异常的处理中会详细分析到。

        这里我们要关注CONTEXT几个比较重要的点:eip与esp,因为其是程序的重要落脚点,发现其是该函数的返回地址与上一个函数的堆栈图(本质就是kernel!RaiseException调用时的现场)。

        

       4)nt!RtlRaiseException函数分析分析

        上面我们经过分析ntdll!RtlRaiseException,发现其调用ZwRaiseException函数进内核,其对应nt!NtRaiseException函数,其分析如下图

        该函数如下所示,其中值得注意的一点是:KThread.TrapFrame保存着TrapFrame.ebx,进零环时,mov edx,esp,其esp保存着call的返回地址,即ebx保存着三环栈顶,也是三环的返回地址。

        这是这里一个比较重要的细节,你是一定要明确的。

        

       5)nt!KiRaiseException函数分析

        我们之前在三环生成了ExceptionRecord与Context,但是我们要在零环使用,其如何使用的呢?

        分析了这个函数你就会明白,其在nt!KiRaiseException函数中调用完成的两者的复制,之后将Context转换为TrapFrame其KiDispatchException要使用。

        之前我们存在一个疑问,为什么不能使用三环进零环的一个TrapFrame而非得从三环拿过来一个Context转换?

        猜想:因为该TrapFrame必须是异常现场的TrapFrame,而CPU中断直接调用IDT异常表很容易保存,但是用户模拟的必须是从三环到零环的,其TrapFrame是关于系统调用的。

          因此必须调用ntdll!RtlRaiseException三环保存的Context,这是直接记录异常现场的,这样你就能很好理解其中的逻辑。    

          备注:在调用KiDispatchException上方,其存在一句代码:ExceptionCode &=  EFu,其表示将用户模拟异常的位置0,因此用户模拟异常最高位不可能为0,CPU异常,比如c0000094,区分。            

          

       

    4. 总结

      到此为止,我们分析过上面两种异常的收集流程,其最终都会流向nt!KiDispatchException函数,下面一张图简单汇总,如果看详情,直接回去看有关函数,都说的很详细。

      我们要知道异常收集的是什么-ExceptionRecord以及TrapFrame,怎么收集的?按上面来分析即可。

      

    5.通过异常来进行三环与零环通信的思路

      对于异常,我们存在一种思路,即通过除零异常触发,然后hook除零异常,接收到消息,然后读取全局变量或者节区。

      我们将数据存储在全局变量或者存放在一个专门的PE节区中,我们在Hook的除零异常代码中读取,然后执行,这样就很好理解了。

      之后我们会通过这个猜想来实现我们的代码。

  • 相关阅读:
    leetcode 309. Best Time to Buy and Sell Stock with Cooldown
    leetcode 714. Best Time to Buy and Sell Stock with Transaction Fee
    leetcode 32. Longest Valid Parentheses
    leetcode 224. Basic Calculator
    leetcode 540. Single Element in a Sorted Array
    leetcode 109. Convert Sorted List to Binary Search Tree
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 84. Largest Rectangle in Histogram
    leetcode 338. Counting Bits
    git教程之回到过去,版本对比
  • 原文地址:https://www.cnblogs.com/onetrainee/p/12765370.html
Copyright © 2011-2022 走看看