TLS 回调中的线程入口点作为 AntiDebug 技术

逆向工程 反调试 线 入口点
2021-07-04 01:42:32

我看到了一个名为The Ultimate Anti-Reversing Reference 的文档,它描述了各种反调试技术。在点4.Thread Local Storage有提到

每当创建或销毁线程时都会调用线程本地存储回调(除非进程调用 kernel32 DisableThreadLibraryCalls() 或 ntdll LdrDisableThreadCalloutsForDll() 函数)。这包括当调试器附加到进程时由 Windows 创建的线程。调试器线程是特殊的,因为它的入口点不指向图像内部。相反,它指向 kernel32.dll 内部。因此,一个简单的调试器检测方法是使用线程本地存储回调来查询每个创建的线程的起始地址。可以使用此 32 位代码进行检查,以检查 32 位或 64 位版本的 Windows 上的 32 位 Windows 环境:

 push eax
 mov eax, esp
 push 0
 push 4
 push eax
 ;ThreadQuerySetWin32StartAddress
 push 9
 push -2 ;GetCurrentThread()
 call NtQueryInformationThread
 pop eax
 cmp eax, offset l1
 jnb being_debugged
 ...

我写了 C++ 代码如下

bool fooBar()
{
    uintptr_t dwStartAddress;
    TFNNtQueryInformationThread ntQueryInformationThread = (TFNNtQueryInformationThread)GetProcAddress(
    GetModuleHandle(TEXT("ntdll.dll")), "NtQueryInformationThread");

    if (ntQueryInformationThread != 0) {

        NTSTATUS status = ntQueryInformationThread(
            (HANDLE)-2,
            (_THREADINFOCLASS)9,
            &dwStartAddress,
            sizeof(dwStartAddress),
            nullptr);
       cout << hex << "dwStartAddress: 0x" << dwStartAddress << dec << endl;
    }

我在 TLS 回调中运行它

EXTERN_C
#ifdef _M_X64
#pragma const_seg (".CRT$XLB")
const
#else
#pragma data_seg (".CRT$XLB")
#endif
PIMAGE_TLS_CALLBACK p_thread_callback = fooBar;
#pragma data_seg ()
#pragma const_seg ()

dwStartAddress 的值指向 .exe 模块,而不是文本中所述的 kernel32.dll。无论我是只运行 exe 还是在调试器中运行,或者将调试器附加到进程中(虽然我对附加不是很有经验,所以也许我在这里做错了什么)。

我做错了什么,还是文本错误/不再有效?

1个回答

当调试器想要附加到进程时,它将执行以下操作(请参阅ReactOS 上DebugActiveProcess 实现):

  1. 连接到调试器 DbgUiConnectToDbg
  2. 告诉内核开始调试进程 NtDebugActiveProcess
  3. DbgBreakPoint在附加进程中发出一个DbgUiIssueRemoteBreakin

DbgUiIssueRemoteBreakin函数在指向 的调试对象中创建一个线程,该线程DbgUiRemoteBreakin又调用DbgBreakPoint.

这种反调试技巧不再适用于 Windows 7 及更高版本,因为它DbgUiIssueRemoteBreakin创建了DbgUiRemoteBreakin带有SkipThreadAttach标志线程相关博客文章)。这会导致新创建的线程不DllMain使用DLL_THREAD_ATTACHorDLL_THREAD_DETACH原因调用或 TLS 回调