[toc]
Timing
在调试器中跟踪进程时,指令和执行之间会有巨大的延迟。可以测量代码某些部分之间的“本地”延迟,并使用几种方法将其与实际延迟进行比较。
1. RDPMC/RDTSC
这些指令要求将标志PCE设置在CR4寄存器中。
RDPMC指令只能在内核模式下使用。
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed){ULARGE_INTEGER Start, End;__asm{xor ecx, ecxrdpmcmov Start.LowPart, eaxmov Start.HighPart, edx}// ... some work__asm{xor ecx, ecxrdpmcmov End.LowPart, eaxmov End.HighPart, edx}return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;}
RDTSC是用户模式指令。
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed){ULARGE_INTEGER Start, End;__asm{xor ecx, ecxrdtscmov Start.LowPart, eaxmov Start.HighPart, edx}// ... some work__asm{xor ecx, ecxrdtscmov End.LowPart, eaxmov End.HighPart, edx}return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;}
2. GetLocalTime()
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed){SYSTEMTIME stStart, stEnd;FILETIME ftStart, ftEnd;ULARGE_INTEGER uiStart, uiEnd;GetLocalTime(&stStart);// ... some workGetLocalTime(&stEnd);if (!SystemTimeToFileTime(&stStart, &ftStart))return false;if (!SystemTimeToFileTime(&stEnd, &ftEnd))return false;uiStart.LowPart = ftStart.dwLowDateTime;uiStart.HighPart = ftStart.dwHighDateTime;uiEnd.LowPart = ftEnd.dwLowDateTime;uiEnd.HighPart = ftEnd.dwHighDateTime;return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed;}
3. GetSystemTime()
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed){SYSTEMTIME stStart, stEnd;FILETIME ftStart, ftEnd;ULARGE_INTEGER uiStart, uiEnd;GetSystemTime(&stStart);// ... some workGetSystemTime(&stEnd);if (!SystemTimeToFileTime(&stStart, &ftStart))return false;if (!SystemTimeToFileTime(&stEnd, &ftEnd))return false;uiStart.LowPart = ftStart.dwLowDateTime;uiStart.HighPart = ftStart.dwHighDateTime;uiEnd.LowPart = ftEnd.dwLowDateTime;uiEnd.HighPart = ftEnd.dwHighDateTime;return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed;}
4. GetTickCount()
C/C ++代码
bool IsDebugged(DWORD dwNativeElapsed){DWORD dwStart = GetTickCount();// ... some workreturn (GetTickCount() - dwStart) > dwNativeElapsed;}
5. ZwGetTickCount()/ KiGetTickCount()
这两种功能仅在内核模式下使用。
就像用户模式GetTickCount()或GetSystemTime()一样,内核模式ZwGetTickCount()从KUSER_SHARED_DATA页面读取。该页面以只读方式映射到虚拟地址的用户模式范围,并在内核范围内以读写方式映射。系统时钟滴答会更新系统时间,该时间直接存储在此页面中。
ZwGetTickCount()与GetTickCount()的使用方式相同。使用KiGetTickCount()比调用ZwGetTickCount()更快,但是比直接从KUSER_SHARED_DATA页面读取稍慢。
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed){ULARGE_INTEGER Start, End;__asm{int 2ahmov Start.LowPart, eaxmov Start.HighPart, edx}// ... some work__asm{int 2ahmov End.LowPart, eaxmov End.HighPart, edx}return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;}
6. QueryPerformanceCounter()
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed){LARGE_INTEGER liStart, liEnd;QueryPerformanceCounter(&liStart);// ... some workQueryPerformanceCounter(&liEnd);return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed;}
7. timeGetTime()
C/C ++代码
bool IsDebugged(DWORD dwNativeElapsed){DWORD dwStart = timeGetTime();// ... some workreturn (timeGetTime() - dwStart) > dwNativeElapsed;}
缓解措施
- 在调试期间:只需用
NOP填充计时检查,然后将这些检查的结果设置为适当的值。 - 对于反反调试解决方案的开发:不需要做任何事情,因为所有的计时检查都不是很可靠。也可以hook计时函数并加快两次调用之间的时间。
