[toc]

Timing

在调试器中跟踪进程时,指令和执行之间会有巨大的延迟。可以测量代码某些部分之间的“本地”延迟,并使用几种方法将其与实际延迟进行比较。

1. RDPMC/RDTSC

这些指令要求将标志PCE设置在CR4寄存器中。


RDPMC指令只能在内核模式下使用。

C/C ++代码

  1. bool IsDebugged(DWORD64 qwNativeElapsed)
  2. {
  3. ULARGE_INTEGER Start, End;
  4. __asm
  5. {
  6. xor ecx, ecx
  7. rdpmc
  8. mov Start.LowPart, eax
  9. mov Start.HighPart, edx
  10. }
  11. // ... some work
  12. __asm
  13. {
  14. xor ecx, ecx
  15. rdpmc
  16. mov End.LowPart, eax
  17. mov End.HighPart, edx
  18. }
  19. return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
  20. }

RDTSC是用户模式指令。

C/C ++代码

  1. bool IsDebugged(DWORD64 qwNativeElapsed)
  2. {
  3. ULARGE_INTEGER Start, End;
  4. __asm
  5. {
  6. xor ecx, ecx
  7. rdtsc
  8. mov Start.LowPart, eax
  9. mov Start.HighPart, edx
  10. }
  11. // ... some work
  12. __asm
  13. {
  14. xor ecx, ecx
  15. rdtsc
  16. mov End.LowPart, eax
  17. mov End.HighPart, edx
  18. }
  19. return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
  20. }

2. GetLocalTime()

C/C ++代码

  1. bool IsDebugged(DWORD64 qwNativeElapsed)
  2. {
  3. SYSTEMTIME stStart, stEnd;
  4. FILETIME ftStart, ftEnd;
  5. ULARGE_INTEGER uiStart, uiEnd;
  6. GetLocalTime(&stStart);
  7. // ... some work
  8. GetLocalTime(&stEnd);
  9. if (!SystemTimeToFileTime(&stStart, &ftStart))
  10. return false;
  11. if (!SystemTimeToFileTime(&stEnd, &ftEnd))
  12. return false;
  13. uiStart.LowPart = ftStart.dwLowDateTime;
  14. uiStart.HighPart = ftStart.dwHighDateTime;
  15. uiEnd.LowPart = ftEnd.dwLowDateTime;
  16. uiEnd.HighPart = ftEnd.dwHighDateTime;
  17. return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed;
  18. }

3. GetSystemTime()

C/C ++代码

  1. bool IsDebugged(DWORD64 qwNativeElapsed)
  2. {
  3. SYSTEMTIME stStart, stEnd;
  4. FILETIME ftStart, ftEnd;
  5. ULARGE_INTEGER uiStart, uiEnd;
  6. GetSystemTime(&stStart);
  7. // ... some work
  8. GetSystemTime(&stEnd);
  9. if (!SystemTimeToFileTime(&stStart, &ftStart))
  10. return false;
  11. if (!SystemTimeToFileTime(&stEnd, &ftEnd))
  12. return false;
  13. uiStart.LowPart = ftStart.dwLowDateTime;
  14. uiStart.HighPart = ftStart.dwHighDateTime;
  15. uiEnd.LowPart = ftEnd.dwLowDateTime;
  16. uiEnd.HighPart = ftEnd.dwHighDateTime;
  17. return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed;
  18. }

4. GetTickCount()

C/C ++代码

  1. bool IsDebugged(DWORD dwNativeElapsed)
  2. {
  3. DWORD dwStart = GetTickCount();
  4. // ... some work
  5. return (GetTickCount() - dwStart) > dwNativeElapsed;
  6. }

5. ZwGetTickCount()/ KiGetTickCount()

这两种功能仅在内核模式下使用。

就像用户模式GetTickCount()GetSystemTime()一样,内核模式ZwGetTickCount()KUSER_SHARED_DATA页面读取。该页面以只读方式映射到虚拟地址的用户模式范围,并在内核范围内以读写方式映射。系统时钟滴答会更新系统时间,该时间直接存储在此页面中。

ZwGetTickCount()GetTickCount()的使用方式相同。使用KiGetTickCount()比调用ZwGetTickCount()更快,但是比直接从KUSER_SHARED_DATA页面读取稍慢

C/C ++代码

  1. bool IsDebugged(DWORD64 qwNativeElapsed)
  2. {
  3. ULARGE_INTEGER Start, End;
  4. __asm
  5. {
  6. int 2ah
  7. mov Start.LowPart, eax
  8. mov Start.HighPart, edx
  9. }
  10. // ... some work
  11. __asm
  12. {
  13. int 2ah
  14. mov End.LowPart, eax
  15. mov End.HighPart, edx
  16. }
  17. return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
  18. }

6. QueryPerformanceCounter()

C/C ++代码

  1. bool IsDebugged(DWORD64 qwNativeElapsed)
  2. {
  3. LARGE_INTEGER liStart, liEnd;
  4. QueryPerformanceCounter(&liStart);
  5. // ... some work
  6. QueryPerformanceCounter(&liEnd);
  7. return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed;
  8. }

7. timeGetTime()

C/C ++代码

  1. bool IsDebugged(DWORD dwNativeElapsed)
  2. {
  3. DWORD dwStart = timeGetTime();
  4. // ... some work
  5. return (timeGetTime() - dwStart) > dwNativeElapsed;
  6. }

缓解措施

  • 在调试期间:只需用NOP填充计时检查,然后将这些检查的结果设置为适当的值。
  • 对于反反调试解决方案的开发:不需要做任何事情,因为所有的计时检查都不是很可靠。也可以hook计时函数并加快两次调用之间的时间。