[toc]
Timing
在调试器中跟踪进程时,指令和执行之间会有巨大的延迟。可以测量代码某些部分之间的“本地”延迟,并使用几种方法将其与实际延迟进行比较。
1. RDPMC/RDTSC
这些指令要求将标志PCE
设置在CR4
寄存器中。
RDPMC
指令只能在内核模式下使用。
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed)
{
ULARGE_INTEGER Start, End;
__asm
{
xor ecx, ecx
rdpmc
mov Start.LowPart, eax
mov Start.HighPart, edx
}
// ... some work
__asm
{
xor ecx, ecx
rdpmc
mov End.LowPart, eax
mov End.HighPart, edx
}
return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
}
RDTSC
是用户模式指令。
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed)
{
ULARGE_INTEGER Start, End;
__asm
{
xor ecx, ecx
rdtsc
mov Start.LowPart, eax
mov Start.HighPart, edx
}
// ... some work
__asm
{
xor ecx, ecx
rdtsc
mov End.LowPart, eax
mov 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 work
GetLocalTime(&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 work
GetSystemTime(&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 work
return (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 2ah
mov Start.LowPart, eax
mov Start.HighPart, edx
}
// ... some work
__asm
{
int 2ah
mov End.LowPart, eax
mov End.HighPart, edx
}
return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
}
6. QueryPerformanceCounter()
C/C ++代码
bool IsDebugged(DWORD64 qwNativeElapsed)
{
LARGE_INTEGER liStart, liEnd;
QueryPerformanceCounter(&liStart);
// ... some work
QueryPerformanceCounter(&liEnd);
return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed;
}
7. timeGetTime()
C/C ++代码
bool IsDebugged(DWORD dwNativeElapsed)
{
DWORD dwStart = timeGetTime();
// ... some work
return (timeGetTime() - dwStart) > dwNativeElapsed;
}
缓解措施
- 在调试期间:只需用
NOP
填充计时检查,然后将这些检查的结果设置为适当的值。 - 对于反反调试解决方案的开发:不需要做任何事情,因为所有的计时检查都不是很可靠。也可以hook计时函数并加快两次调用之间的时间。