异常情况

以下方法有意的引起异常,以验证进程是否运行在调试器下。

1. UnhandledExceptionFilter()

如果发生异常并且未注册任何异常处理程序(或已注册但未处理此类异常),则将调用kernel32!UnhandledExceptionFilter()函数。可以使用kernel32!SetUnhandledExceptionFilter()注册自定义未处理的异常过滤器。但是,如果程序在调试器下运行,则不会调用自定义过滤器,并且会将异常传递给调试器。因此,如果未处理的异常筛选器已注册并将控件传递给它,则该进程将不会在调试器中运行。


x86汇编(FASM)

  1. include 'win32ax.inc'
  2. .code
  3. start:
  4. jmp begin
  5. not_debugged:
  6. invoke MessageBox,HWND_DESKTOP,"Not Debugged","",MB_OK
  7. invoke ExitProcess,0
  8. begin:
  9. invoke SetUnhandledExceptionFilter, not_debugged
  10. int 3
  11. jmp being_debugged
  12. being_debugged:
  13. invoke MessageBox,HWND_DESKTOP,"Debugged","",MB_OK
  14. invoke ExitProcess,0
  15. .end start

C / C ++代码

  1. LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
  2. {
  3. PCONTEXT ctx = pExceptionInfo->ContextRecord;
  4. ctx->Eip += 3; // Skip \xCC\xEB\x??
  5. return EXCEPTION_CONTINUE_EXECUTION;
  6. }
  7. bool Check()
  8. {
  9. bool bDebugged = true;
  10. SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UnhandledExceptionFilter);
  11. __asm
  12. {
  13. int 3 // CC
  14. jmp near being_debugged // EB ??
  15. }
  16. bDebugged = false;
  17. being_debugged:
  18. return bDebugged;
  19. }

2. RaiseException()

诸如DBC_CONTROL_CDBG_RIPEVENT之类的异常不会传递给当前进程的异常处理程序,而是由调试器使用。这使我们可以注册一个异常处理程序,使用kernel32!RaiseException()函数引发这些异常,并检查控件是否传递给了我们的处理程序。如果未调用异常处理程序,则该进程可能正在调试中。


C / C ++代码

  1. bool Check()
  2. {
  3. __try
  4. {
  5. RaiseException(DBG_CONTROL_C, 0, 0, NULL);
  6. return true;
  7. }
  8. __except(DBG_CONTROL_C == GetExceptionCode()
  9. ? EXCEPTION_EXECUTE_HANDLER
  10. : EXCEPTION_CONTINUE_SEARCH)
  11. {
  12. return false;
  13. }
  14. }

3.使用异常处理程序隐藏控制流

这种方法不检查是否存在调试器,但有助于按异常处理程序的顺序隐藏程序的控制流。

我们可以注册一个引发另一个异常的异常处理程序(结构化异常处理SEH or 向量化异常处理VEH),该异常将传递给下一个引发下一个异常的处理程序,依此类推。最后,处理程序的顺序应导向我们要隐藏的进程。

使用结构化异常处理程序:


C / C ++代码

  1. #include <Windows.h>
  2. void MaliciousEntry()
  3. {
  4. // ...
  5. }
  6. void Trampoline2()
  7. {
  8. __try
  9. {
  10. __asm int 3;
  11. }
  12. __except (EXCEPTION_EXECUTE_HANDLER)
  13. {
  14. MaliciousEntry();
  15. }
  16. }
  17. void Trampoline1()
  18. {
  19. __try
  20. {
  21. __asm int 3;
  22. }
  23. __except (EXCEPTION_EXECUTE_HANDLER)
  24. {
  25. Trampoline2();
  26. }
  27. }
  28. int main(void)
  29. {
  30. __try
  31. {
  32. __asm int 3;
  33. }
  34. __except (EXCEPTION_EXECUTE_HANDLER) {}
  35. {
  36. Trampoline1();
  37. }
  38. return 0;
  39. }

使用向量异常处理程序:


C / C ++代码

  1. #include <Windows.h>
  2. PVOID g_pLastVeh = nullptr;
  3. void MaliciousEntry()
  4. {
  5. // ...
  6. }
  7. LONG WINAPI ExeptionHandler2(PEXCEPTION_POINTERS pExceptionInfo)
  8. {
  9. MaliciousEntry();
  10. ExitProcess(0);
  11. }
  12. LONG WINAPI ExeptionHandler1(PEXCEPTION_POINTERS pExceptionInfo)
  13. {
  14. if (g_pLastVeh)
  15. {
  16. RemoveVectoredExceptionHandler(g_pLastVeh);
  17. g_pLastVeh = AddVectoredExceptionHandler(TRUE, ExeptionHandler2);
  18. if (g_pLastVeh)
  19. __asm int 3;
  20. }
  21. ExitProcess(0);
  22. }
  23. int main(void)
  24. {
  25. g_pLastVeh = AddVectoredExceptionHandler(TRUE, ExeptionHandler1);
  26. if (g_pLastVeh)
  27. __asm int 3;
  28. return 0;
  29. }

缓解措施

  • 调试期间:

    • 对于调试器检测检查:只需用NOP填充相应的检查。
    • 对于控制流隐藏:您必须手动跟踪程序直到有效负载。
  • 对于反反调试工具开发:这类技术的问题在于,不同的调试器会使用不同的异常,并且不将其返回给调试器。这意味着您必须为特定的调试器实现一个插件,并更改在相应异常之后触发的事件处理程序的行为。