在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序能够检测到本身是否被调试器附加了,若是探知本身正在被调试,确定是有人试图反汇编啦之类的方法破0解本身。为了了解如何破0解反调试技术,首先咱们来看看反调试技术。
1、Windows API方法
Win32提供了两个API, IsDebuggerPresent和CheckRemoteDebuggerPresent能够用来检测当前进程是否正在被调试,以IsDebuggerPresent函数为例,例子以下:
BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);
破0解方法很简单,就是在系统里将这两个函数hook掉,让这两个函数一直返回false就能够了,网上有不少作hook API工做的工具,也有不少工具源代码是开放的,因此这里就不细谈了。
2、查询进程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;
}
3、查询进程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;
}
4、查询进程堆的一些标志位
这个方法是第三个方法的变种,只要进程被调试,进程在堆上分配的内存,在分配的堆的头信息里,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;
}
5、使用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;
}
6、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);
}
7、触发异常的方法
这个技术的原理是,首先,进程使用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;
}
8、调用DeleteFiber函数
若是给DeleteFiber函数传递一个无效的参数的话,DeleteFiber函数除了会抛出一个异常之外,仍是将进程的LastError值设置为具体出错缘由的代号。然而,若是进程正在被调试的话,这个LastError值会被修改,所以若是调试器绕过了第七步里讲的反调试技术的话,咱们还能够经过验证LastError值是否是被修改过来检测调试器的存在,示例代码:
bool DeleteFiberApproach()
{
char fib[1024] = {0};
// 会抛出一个异常并被调试器捕获
DeleteFiber(fib);
// 0x57的意思是ERROR_INVALID_PARAMETER
return (GetLastError() != 0x57);
}