zoukankan      html  css  js  c++  java
  • 检查进程是否被调试

    转自:http://www.cnblogs.com/this-543273659/archive/2013/03/04/2943380.html

    在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己。为了了解如何破解反调试技术,首先我们来看看反调试技术。
     
    一、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);
    }
  • 相关阅读:
    Binary Search Tree Iterator 解答
    Invert Binary Tree 解答
    Min Stack 解答
    Trapping Raining Water 解答
    Candy 解答
    Jump Game II 解答
    Implement Hash Map Using Primitive Types
    Gas Station 解答
    Bucket Sort
    HashMap 专题
  • 原文地址:https://www.cnblogs.com/priarieNew/p/9756080.html
Copyright © 2011-2022 走看看