zoukankan      html  css  js  c++  java
  • 第52章:动态反调试技术

    异常

    ①SEH,以 DynAD_SEH.exe 程序为例

    首先改变了 SEH 链,在 int 3 触发异常,此时注意栈中的 SEH 链,对对应的函数下断点即可暂停下来。

    可以看到 EIP 被改变后,函数的执行流程即被改变。对 Contex(0xB6) 结构体中 EIP 指向的地址下断点。

    此时直接就会显示 Not debugging ,而不是因为调试器处理了异常而导致程序退出。

    ② SetUnhandlerExceptionFilter

    进程中发生异常时,如果 SEH 未处理或注册的 SEH 根本不存在,则会调用执行系统的 kernel32! UnhandlerExceptionFilter :

    该函数内部会运行系统的最后一个异常处理器(Top Level Exception Filter OR Last Exception Filter).而函数的内部调用了ntdll ! NtQueryInformationProcess(ProcessDebugPort)  .如果程序正常运行,则运行系统最后的异常处理函数,如果进程处于调试状态,则将异常派送给调试器。通过这个 API 可以修改系统最后的异常处理器。使用时只需要将新的 Top Level Exception Filter 作为该 API 的参数即可:

    代码:

    #include "stdio.h"
    #include "windows.h"
    #include "tchar.h"
    
    LPVOID g_pOrgFilter = 0;
    
    LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS pExcept)        //注册的函数的声明以及实现
    {
        SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)g_pOrgFilter);
    
        // 8900    MOV DWORD PTR DS:[EAX], EAX
        // FFE0    JMP EAX
        pExcept->ContextRecord->Eip += 4;
    
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    
    void AD_SetUnhandledExceptionFilter()
    {
        printf("SEH : SetUnhandledExceptionFilter()
    ");
    
        g_pOrgFilter = (LPVOID)SetUnhandledExceptionFilter(                      //类型转换
                                    (LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter); // 注册函数地址,而非调用函数
    
        __asm {
            xor eax, eax;
            mov dword ptr [eax], eax      //发生异常代码的首字节地址与 printf 刚好相差 4 字节
            jmp eax                     
        }
        
        printf("  => Not debugging...
    
    ");
    }
    
    int _tmain(int argc, TCHAR* argv[])
    {
        AD_SetUnhandledExceptionFilter();
    
        return 0;
    }

    在程序中看一下:

    程序的流程是,先打印字符串,然后调用 SetHandledExceptionFilter() 注册新的 Top Level Exception Filter (当中包含异常处理代码),触发异常。异常触发后进入异常处理模块:

    此时调用的异常处理函数并不是第一个 SEH 链上的函数:

    因为并没有处理该异常,返回后继续调用后续的其它异常处理器:

    进入 call ecx 后,会执行到 UnhandledExceptionFilter() ,表明 SEH 中没有函数处理了异常,将由系统异常处理函数处理:

    在函数内部调用了 NtQueryInformationPorcess() API ,参数是 7(DebugPort)在后面比较时修改其值(FFFFFFFF => 00000000)即可:

    修改值后,才会发生跳转,在执行 RtlDecodePointer() 后,会发现 Eax 的返回值就是 00401000:

    此处就直接调用了:

    再次通过 SetUnhandledExceptionFilter() ,设置最后一个异常函数,并且修改Contex => Eip(+4)即从 401252 -> 401056:

    401052 是发生异常的地址:

    然后函数退出,即执行完异常处理,回到 Contex => Eip 代码处(ZwContinue):

    ③ Timing Check

                                

    RDTSC 指令

    x86 CPU 存在一个名为 TSC(Time Stamp Counter,时间戳计数器)的64位寄存器。CPU 对每个时钟周期计数,然后保存到 TSC. RDTSC 是一条指令,用于将 TSC 值读取到 EDX:EAX 寄存器中。

    程序的核心非常简单,在两个 rdtsc 命令之间加入一个计数循环,时间多了就被判定为有调试器。

    Ja 指令只有在  ZF 和 CF 全零时才会执行跳转,修改其中一个 Ja 指令就失效了。

    ④ 陷阱标志

    在 x64 dbg 中,程序单步执行不会出现 Single Step 异常,但直接跑会遇到该异常。单步执行时,遇到 Jmp FFFFFFFF,会出现 Access_Violation 异常,但会转到本该处理 Single_Step 异常的异常处理函数处,导致程序仍然会执行到 Not Debugging 。

    在 OD 中,程序程序单步执行不会出现 Single Step 异常,但直接跑会遇到该异常。单步执行时,遇到 Jmp FFFFFFFF,会直接跳到 FFFFFFFF,并将无法执行命令。

    异触发后,程序处理该异常,将 Eip 的值修改,跳转到 Not Debugging 处。

    ⑤ INT 2D

    该指令是内核模式中触发断点异常的指令,但也可以在用户模式下触发异常。

    在程序中看一下:

    使用单步执行指令,执行完 INT 2D 后,直接跳过后面的 nop 指令,并触发异常,红色框即是异常处理函数:

    在 x64dbg 中无论使用哪种方式,都会在 401021 处触发异常,进入异常处理函数,并且都会忽略下一个字节的指令。

    在 OD 中,步进指令会直接跑飞,但是不会触发异常。直接运行同样不会触发异常,并且二者都会忽略下一个字节的指令。

    如果对 nop 指令下断点, x64dbg 会直接跳过,而 OD 会断在这个断点上。               什么?            

    ⑥ 检测 API 首字节 CC

    ⑦ 内存校验和

  • 相关阅读:
    进程间的通讯(IPC)方式
    进程间通信IPC之--共享内存
    TMDS协议
    HDMI接口与协议
    HDMI的CEC是如何控制外围互联设备的
    SVN并行开发管理策略
    关于 javascript event flow 的一个bug
    H面试程序(15): 冒泡排序法
    android应用如何启动另外一个apk应用
    [置顶] 一份诚恳的互联网找工作总结和感想(附:怎样花两年时间去面试一个人)
  • 原文地址:https://www.cnblogs.com/Rev-omi/p/13721336.html
Copyright © 2011-2022 走看看