• 软件调试


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

    软件调试

    1.调试程序如何与被调试程序 
    2. 调试事件的采集
    3. 调试事件的处理流程
    4. 异常的调试流程
    5. 软件断点
    6. 内存断点
    7. 硬件断点
    8. 单步异常与单步步过
    9. 硬件HOOK

    1.调试程序如何与被调试程序 

      1)API建立存在两种方式

        ①CreateProcess

        ②DebugActiveProcess

      2) 调试器如何创建调试对象 - Kernel32!DebugActiveProcess函数分析

        

        ① DbgUiConnectToDbg函数分析:

          其函数分析如下:简单的就是调用zw函数进零环,在零环创建一个内核对象然后返回三环。

          其内核模块创建的具体细节,可以搜索nt!CreateDebugObject函数。

          

         ② DebugObject数据结构如下

           typedef struct _DEBUG_OBJECT {
                KEVENT EventsPresent;
                FAST_MUTEX Mutex;
                LIST_ENTRY EventList;
                ULONG Flags;
           } DEBUG_OBJECT, *PDEBUG_OBJECT;

        ③ DebugObject 创建完之后放在那里

          如何我们查看Kernel32!DebugActiveProcess函数时,我们发现虽然调用DbgUiConnectToDbg创建一个DEBUG_OBJECT,但我们并没有看到该结构体。

          其返回值是一个是否调用成功,那存放在哪里了呢?我们查看其zwCreateDebugObject的反汇编,如下:

          我们可以得出,其存放在当前调试器线程的 Teb+0xF24 的位置中。

          

      3)被调试进程与调试进程建立关系 - Kernel32!DebugActiveProcess函数分析

         ①DbgUiConnectToDbg函数分析:

          我们之前分析过该函数通过调用 DbgUiConnectToDbg() 创建调试对象,现在我们分析其是如何与被调试进程建立联系的。

          分析如下,很明显调用一个DbgUiDebugActiveProcess()函数来建立联系的。

          

         ② ntdll!DbgUiDebugActiveProcess函数分析:

          分析如下图所示,这样再结合我们对于调试器的行为,则很好理解。

          

        ③ Nt!NtDebugActiveProcess函数分析:

          如下图,进入内核之后,先做的就是通过三环句柄来获取进程对象与调试对象,然后调用一个函数,在函数内部将其关联起来。

          

         ④nt!DbgkpSetProcessDebugObject函数分析

          该函数内容很多,但很容易看出核心操作就是将进程的DebugPort中传入DEBUG_OBJECT的地址,如下所示:

          

      4总结:

        要牢记大体流程,通过 DebugActiveProcess 来进行具体分析。

        首先,先调用内核创建一个调试对象,返回三环句柄,将句柄存储在Teb+0xF24中;之后将被调试程序的句柄与调试对象句柄一起传入零环中,在零环_EPROCESS.DebugPort中。

        值得注意的是:调试器是通过句柄与被调试对象建立连接的,而被调试程序是在内核中建立连接的,一个_TEB,一个_EPROCESS,这是建立的关键区别。

        

    2. 调试事件的采集

      1)调试事件在哪儿

        如下:其是_DEBUG_OBJECT结构体,看存在一个 EventList,其就是一个调试事件链表。

        被调试程序各种事件发往调试对象的EventList中,然后由调试器接收并进行处理,其大体流程就是这个样子。

         typedef struct _DEBUG_OBJECT {
            KEVENT EventsPresent;
            FAST_MUTEX Mutex;
              LIST_ENTRY EventList; +0x30
           ULONG Flags;
         } DEBUG_OBJECT, *PDEBUG_OBJECT;

      2)何为调试事件?

        被调试进程所做的任何一件事,难道都要报告被调试器吗?当然不是,其存在如下七种调试事件:

        typedef enum _DBGKM_APINUMBER
        {
          DbgkmExceptionApi = 0, // 异常
          DbgkmCreateThreadApi = 1, // 创建线程
          DbgkmCreateProcessApi = 2, // 创建进程
          DbgkmExitThreadApi = 3, // 退出线程
          DbgkmExitProcessApi = 4, // 进程退出
          DbgkmLoadDllApi = 5, // 映射DLL
          DbgkmUnloadDllApi = 6, // 反映射DLL
          DbgkmErrorReportApi = 7, // 内部错误(已废弃)
          DbgkmMaxApiNumber = 8, // 这组常量的最大值 (已废弃)
        } DBGKM_APINUMBER;

      3)如何生成调试事件?(调试事件采集函数)

        我们下面介绍一组函数,其是Dbg**函数,其在各个活动事件的必经之路上。

        当被调试程序正在这些事件的一种,其都会经过这些函数,然后其检查是否处于被调试状态,如果处于就发送事件到链表中。

        

         ① Nt!DbgKpSendApiMessage函数分析

          该函数首先要判断该线程是否要暂停,然后调用DbgkpQueueMessage,该函数将生成调试事件,然后加入到调试对象链表中。

          其中我们可以得出一个反调试思路:这里是线程暂停的关键地方,我们可以保护我们的程序,hook该地方,当程序执行到这里,如果发现是我们的进程,则不暂停,

            这样,其就算接收到我们的调试事件了,也不会暂停我们的程序。

          

         ② Nt!DbgKpQueueMessage函数分析

          该函数主要目的是生成调试事件,根据传入过来的有关信息来填写,之后使用KeSetEvent。

          其中的详细过程可以在《等待对象》,那一节查看。

          

            

    3. 调试事件的处理流程

      现在,被调试进程的所有调试事件都会发送到DebugEventList中,我们在编写调试器时,使用一个循环来循环遍历是否产生新的消息。

      void main() {

       DebugActiveProcess/CreateProcess;

       while(Alive){

        WaitForDebugEvent(&debugEvent,WaitTime);

        switch(DebugEvent.dwDebugEventCode)

        { .... }

        ContinueDebugEvent();

       }

      }

      1)WaitForDebugEvent、ContinueDebugEvent函数

        循环遍历该事件链表,如果存在事件就会取出来,处理完之后然后等待进一步执行。

        当处理完成之后,调用ContinueDebugEvent让被调试程序恢复执行,这个很好理解。

      2)发送虚假信息

        我们在调试过程时,会看见发送虚假的调试对象,这是为什么呢?

        很简单,我们及时以ActiveDebugProcess附加的方式调试进程,也可以看到其进程的线程以及各种模块加载的情况。

        这样很奇怪,进程模块加载已经完成了,为什么还能接收到加载信息呢,其实就是依赖于这组函数。

        当以附加的形式添加信息时,其会发送虚假的信息给调试器,让其可以显示出调试信息,但其不会中断,这很容易理解。

        

    4.异常的调试流程

      如下图,用户层出现的异常调试流程,之前我们提到过异常处理流程,可其是如何发送给三环的调试器呢?

      注意③⑤,其调用的函数DbgKForwardException函数,该函数就是异常的收集函数,之后的流程我们上面已经分析过。

      

    5. 软件断点

      硬件断点,众所周知是CC指令,INT 3 中断,0x80000003异常码。

      我们查看 Trap03,很明显看出其执行流程,我们之后学过异常和处理程序,接下来就知道怎么做了。

      

    6. 内存断点

      内存断点本质就是调用 VirtualProtectEx 函数来修改页的属性(PTE),将其属性修改为写,当进行读的时候,会触发异常,然后执行异常的分发流程。

      其内存断点触发时执行 Trap0E 号中断函数,我们分析该中断,发现其也是调用CommonDispatchException函数,0xc0000006来触发异常。

      

    7. 硬件断点

      1)CPU的Dr调试寄存器

        硬件断点本质调用CPU的Dr0~7,8个调试寄存器。

        其相关属性如下图,其中Dr0~Dr3存储断点地址,Dr4~Dr5保存,Dr6~Dr7设置Dr0~Dr4相关寄存器的属性。

        因为断点地址只记录在Dr0~Dr3中,因此硬件断点最多只能记录四个。

        

      2)硬件断点的处理程序

        硬件断点如果被检测,则会走Trap01中断,触发0x80000004中断(软件断点为0x8000003中断),其派发如下图

         

      3)硬件断点的单步异常与TF位产生的单步异常区分

        硬件断点产生的是单步异常,通过TF位也会产生单步异常。(单步异常我们之后会讲)

        那操作系统到底如何区分其单步异常是如何产生的呢?答案是通过Dr6寄存器的有关属性(B0~B4)。

      4)代码写硬件断点的时机

        调用GetThreadContext()和SetThreadContext(),获取线程上下文和设置线程上下文来,然后对Dr寄存器进行有关设置。

        有一点需要注意的,在调用SetThreadContext(),应该暂停其他线程,否则可能出现设置错误。

    8. 单步异常与单步步过

      1)单步异常:

        ①单步异常的实现:

          我们在使用调试器时肯定使用过单步异常(OD的F7),如果让我们来设置,肯定执行一行代码来设置软件断点或硬件断点。

          但是CPU在设计之初已经考虑过我们这个需求了,其Eflag.TF位就是用来标识的,CPU每运行一次代码,会检查该标志位。

          如果将其设置为1,其就会触发一次单步异常,走Trap01中断(与硬件断点的执行顺序是一样的)。

        ②单步异常的代码实现:

          先设置软件断点,将被调试线程断开,调用GetThreadContext()和SetThreadContext()来修改TF位后继续运行。

      2)单步步过

        ①单步步过的实现:

          单步步过是调试器编写者去实现,并不会像单步步入一样可以通过修改TF位实现。

          根据反汇编引擎动态计算,如果下一条指令是CALL类指令,则在其再下一条指令设置断点,然后运行。

          因此,通过硬件断点或软件断点都可以实现,自己有自己的思路就好。

        ②通过反调试干掉单步步过:

          根据单步步过的反调试思路,我们可以设置大量嵌套无用函数,在里面动态修改返回地址,最后并不会执行call函数的下一个地址,

          而单步步过在call下一个地址下断点,这样该程序就会跑飞,很好理解这种原理。

    9.硬件HOOK

      其核心原理就是修改线程的Dr寄存器,然后再加入一个VEH异常处理函数。

      其中VEH是全局异常,我们在自己的进程添加,在其他线程也可以获取到。

      我们在VEH异常处理程序中先判断当前EIP是否是要hook的地址,如果是则执行hook代码,之后再还原进去,其实就是这个样子,很好理解。

  • 相关阅读:
    通信信号处理的一些基本常识
    欧拉公式
    css3圆角讲解
    css3投影讲解、投影
    css3变形讲解
    浏览器兼容问题
    css3渐变详解
    css中em与px
    복 경 에 갑 니 다 去北京
    我在北京:)
  • 原文地址:https://www.cnblogs.com/onetrainee/p/12627607.html
Copyright © 2020-2023  润新知