I came across a document called The Ultimate Anti-Reversing Reference, which describes various Anti-Debugging techniques. in point 4.Thread Local Storage There is a mention

Thread Local Storage callbacks are called whenever a thread is created or destroyed (unless the process calls the kernel32 DisableThreadLibraryCalls() or the ntdll LdrDisableThreadCalloutsForDll() functions). That includes the thread that is created by Windows when a debugger attaches to a process. The debugger thread is special, in that its entrypoint does not point inside the image. Instead, it points inside kernel32.dll. Thus, a simple debugger detection method is to use a Thread Local Storage callback to query the start address of each thread that is created. The check can be made using this 32-bit code to examine the 32-bit Windows environment on either the 32-bit or 64-bit versions of Windows:

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

I wrote the c++ code as below

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

    if (ntQueryInformationThread != 0) {

        NTSTATUS status = ntQueryInformationThread(
       cout << hex << "dwStartAddress: 0x" << dwStartAddress << dec << endl;

and I'm running this inside the TLS callbacks

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

The value of dwStartAddress points to the .exe module, not to kernel32.dll as stated in the text. Regardless if I just run the exe or run in debugger or attach a debugger to the process (tho I'm not very experienced with attaching so maybe I'm doing something wrong here).

Am I doing something wrong, or the text is wrong / no longer valid?

  • 1
    Are you running an infinite loop inside the main function of your executable? The TLS callback should be called for every thread created. Once before main and once when you attach to the process while it’s running.
    – mrexodia
    Jul 17, 2018 at 22:21
  • I used system("pause") but I changed to inifite loop now and still no output that would suggest that TSL callback was called 2nd time
    – Marcin K.
    Jul 18, 2018 at 0:29
  • I think I tried everything and I just can't get a debugger to spawn thread as described in this document. Im using windows 10. If anyone could check it also on windows 10 and maybe some other OS I would be really thankful
    – Marcin K.
    Jul 19, 2018 at 14:01
  • Today I ran into this again, but in my case a TLS callback was called on debug attach. I’ll show you tomorrow
    – mrexodia
    Jul 20, 2018 at 1:05
  • My debugged some more and it seems there is call to LdrDisableThreadCalloutsForDll with address of kernel32.dll as argument which... well should disable this for kernel32.dll spawned threads. Now the question is how to disable this call. This call is made BEFORE main, BEFORE crtMain, BEFORE TLS callbacks. When could it be? On dll loading?
    – Marcin K.
    Jul 20, 2018 at 10:02

1 Answer 1


When a debugger wants to attach to a process it will do the following things (see the DebugActiveProcess implementation on ReactOS):

  1. Connect to the debuggee with DbgUiConnectToDbg
  2. Tell the kernel to start debugging the process with NtDebugActiveProcess
  3. Issue a DbgBreakPoint in the attached-to process with DbgUiIssueRemoteBreakin

The DbgUiIssueRemoteBreakin function creates a thread in the debuggee pointed at DbgUiRemoteBreakin, which in turn calls DbgBreakPoint.

This anti-debug trick no longer works from Windows 7 and onwards because the DbgUiIssueRemoteBreakin creates the DbgUiRemoteBreakin thread with the SkipThreadAttach flag (relevant blogpost). This causes the newly-created thread to not call DllMain or TLS callbacks with the DLL_THREAD_ATTACH or DLL_THREAD_DETACH reason.

  • 1
    Indeed that seems to be an issue. Could you also please confirm if I am correct on this one: Not only the callbacks are not called due to SkipThreadAttach flag, but also even if they were called, the Thread Entry Point would point into ntdll.dll not into kernel32.dll right?
    – Marcin K.
    Jul 23, 2018 at 18:24
  • 1
    Yes. The start address of the thread is in DbgUiRemoteBreakin, which is an export of ntdll.dll
    – mrexodia
    Jul 23, 2018 at 18:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.