反调试技术揭秘(转)

 

 

在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编之类的方法破解自己。为了了解如何破解反调试技术,首先我们来看看反调试技术。

 

 

 

一、Windows API 方法

 

Win32 提供了两个 API, IsDebuggerPresent 和 CheckRemoteDebuggerPresent 可以用来检测当前进程是否正在被调试,以IsDebuggerPresent 函数为例,例子如下:

BOOL ret = IsDebuggerPresent();

printf("ret = %d\n", 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\n");

       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);
}

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值