zoukankan      html  css  js  c++  java
  • 反调试技术

    反调试技术在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己。为了了解如何破解反调试技术,首先我们来看看反调试技术。
    一、Windows API方法 Win32提供了两个API, IsDebuggerPresent和CheckRemoteDebuggerPresent可以用来检测当前进程是否正在被调试,以IsDebuggerPresent函数为例,例子如下:
    BOOL ret = IsDebuggerPresent(); printf("ret = %d ", ret);
    破解方法很简单,就是在系统里将这两个函数hook掉,让这两个函数一直返回false就可以了,网上有很多做hook API工作的工具,也有很多工具源代码是开放的,所以这里就不细谈了。
    二、查询进程PEB的BeingDebugged标志位
    当进程被调试器所附加的时候,操作系统会自动设置这个标志位,因此在程序里定期查询这个标志位就可以了,例子如下:
    bool PebIsDebuggedApproach() {        char result = 0;        __asm        {                       // 进程的PEB地址放在fs这个寄存器位置上               mov eax, fs:[30h]                           // 查询BeingDebugged标志位               mov al, BYTE PTR [eax + 2]               mov result, al        }
           return result != 0; }
    三、查询进程PEB的NtGlobal标志位
    跟第二个方法一样,当进程被调试的时候,操作系统除了修改BeingDebugged这个标志位以外,还会修改其他几个地方,其中NtDll中一些控制堆(Heap)操作的函数的标志位就会被修改,因此也可以查询这个标志位,例子如下:
    bool PebNtGlobalFlagsApproach() {        int result = 0;
           __asm        {                       // 进程的PEB               mov eax, fs:[30h]                           // 控制堆操作函数的工作方式的标志位               mov eax, [eax + 68h]                           // 操作系统会加上这些标志位FLG_HEAP_ENABLE_TAIL_CHECK,                           // FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,                           // 它们的并集就是x70                           //                           // 下面的代码相当于C/C++的                           //     eax = eax & 0x70               and eax, 0x70               mov result, eax        }
           return result != 0; }
    四、查询进程堆的一些标志位
    这个方法是第三个方法的变种,只要进程被调试,进程在堆上分配的内存,在分配的堆的头信息里,ForceFlags这个标志位会被修改,因此可以通过判断这个标志位的方式来反调试。因为进程可以有很多的堆,因此只要检查任意一个堆的头信息就可以了,所以这个方法貌似很强大,例子如下:
    bool HeapFlagsApproach() {        int result = 0;
           __asm        {                       // 进程的PEB               mov eax, fs:[30h]                       // 进程的堆,我们随便访问了一个堆,下面是默认的堆               mov eax, [eax + 18h]                           // 检查ForceFlag标志位,在没有被调试的情况下应该是               mov eax, [eax + 10h]               mov result, eax        }
           return result != 0; } 反调试技术二

    五、使用NtQueryInformationProcess函数
    NtQueryInformationProcess函数是一个未公开的API,它的第二个参数可以用来查询进程的调试端口。如果进程被调试,那么返回的端口值会是-1,否则就是其他的值。由于这个函数是一个未公开的函数,因此需要使用LoadLibrary和GetProceAddress的方法获取调用地址,示例代码如下:
    // 声明一个函数指针。
    typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
           HANDLE processHandle,
           PROCESSINFOCLASS processInformationClass,
           PVOID processInformation,
           ULONG processInformationLength,
           PULONG returnLength);
    bool NtQueryInformationProcessApproach()
    {
           int debugPort = 0;
           HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll "));
           NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
           if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) )
                  printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed ");
           else
                  return debugPort == -1;
           return false;
    }
    六、NtSetInformationThread方法
    这个也是使用Windows的一个未公开函数的方法,你可以在当前线程里调用NtSetInformationThread,调用这个函数时,如果在第二个参数里指定0x11这个值(意思是ThreadHideFromDebugger),等于告诉操作系统,将所有附加的调试器统统取消掉。示例代码:
    // 声明一个函数指针。
    typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle,
           THREADINFOCLASS threadInformationClass,
           PVOID threadInformation,
           ULONG threadInformationLength);
    void NtSetInformationThreadApproach()
    {
           HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
          NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread");
       
           NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0);
    }
    七、触发异常的方法
    这个技术的原理是,首先,进程使用SetUnhandledExceptionFilter函数注册一个未处理异常处理函数A,如果进程没有被调试的话,那么触发一个未处理异常,会导致操作系统将控制权交给先前注册的函数A;而如果进程被调试的话,那么这个未处理异常会被调试器捕捉,这样我们的函数A就没有机会运行了。
    这里有一个技巧,就是触发未处理异常的时候,如果跳转回原来代码继续执行,而不是让操作系统关闭进程。方案是在函数A里修改eip的值,因为在函数A的参数_EXCEPTION_POINTERS里,会保存当时触发异常的指令地址,所以在函数A里根据这个指令地址修改寄存器eip的值就可以了,示例代码如下:
    // 进程要注册的未处理异常处理程序A
    LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei)
    {
           SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
                  pei->ContextRecord->Eax);
           // 修改寄存器eip的值
           pei->ContextRecord->Eip += 2;
           // 告诉操作系统,继续执行进程剩余的指令(指令保存在eip里),而不是关闭进程
           return EXCEPTION_CONTINUE_EXECUTION;
    }
    bool UnhandledExceptionFilterApproach()
    {
           SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
           __asm
           {
                  // 将eax清零
                  xor eax, eax
                  // 触发一个除零异常
                  div eax
           }
           return false;
    }
    八、调用DeleteFiber函数
    如果给DeleteFiber函数传递一个无效的参数的话,DeleteFiber函数除了会抛出一个异常以外,还是将进程的LastError值设置为具体出错原因的代号。然而,如果进程正在被调试的话,这个LastError值会被修改,因此如果调试器绕过了第七步里讲的反调试技术的话,我们还可以通过验证LastError值是不是被修改过来检测调试器的存在,示例代码:
    bool DeleteFiberApproach()
    {
           char fib[1024] = {0};
           // 会抛出一个异常并被调试器捕获
           DeleteFiber(fib);
           // 0x57的意思是ERROR_INVALID_PARAMETER
           return (GetLastError() != 0x57);
    }
  • 相关阅读:
    2021.01.28 Rating赛 解题/补题报告
    2021.01.23 Rating赛 补题&解题报告
    ACM 实验室2020.11.08天梯赛练习*5
    6. Python 基础 dict 字典 查找方法 set() 集合 公共方法
    5. python 基础 list [] 列表 tuple () 元组
    4. python 操作字符串 字符串的一些方法
    3. python基础 转化字符类型 循环
    2. python 数据类型 格式化
    1. Python是编译性语言解释性语言 pyCharm 配置
    科二 教育教学知识与能力4
  • 原文地址:https://www.cnblogs.com/hekkoav/p/4681736.html
Copyright © 2011-2022 走看看