Skip to content
Packing & Cryptersintermediate

TLS Callbacks

Malware registers Thread Local Storage callbacks that execute before the PE entry point, running anti-debug or unpacking logic that most debuggers miss at startup.

Thread Local Storage (TLS) is a Windows PE feature that lets a binary register callback functions in a special .tls section. The Windows loader calls every registered TLS callback before the process entry point, and again each time a new thread is created. Most debuggers break at WinMain / the PE entry point — by which time TLS callbacks have already run unobserved.

Malware exploits this to:

  • Run anti-debugging or anti-analysis checks silently.
  • Decrypt or unpack the real payload before the entry point.
  • Detect single-step execution by measuring timing inside the callback.

How it works

c
#include <windows.h>

// The TLS callback — executed before main()
void NTAPI TlsCallback(PVOID hModule, DWORD fdwReason, PVOID pContext)
{
    if (fdwReason == DLL_PROCESS_ATTACH) {
        // Anti-debug check: PEB.BeingDebugged
        PPEB pPeb = (PPEB)__readgsqword(0x60);
        if (pPeb->BeingDebugged) {
            // Silently corrupt a key global so the main payload fails
            ExitProcess(0);
        }
        // Alternatively: decrypt the payload here
    }
}

// Register the callback in the .CRT$XLB section (MSVC)
#pragma comment(linker, "/INCLUDE:_tls_used")
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_tls_callback = TlsCallback;
#pragma data_seg()

The PE's IMAGE_TLS_DIRECTORY in the optional header points to a null-terminated array of callback pointers. In a hex editor or PE parser, the .tls section and the data directory entry at index 9 are the giveaways.

text
IMAGE_DIRECTORY_ENTRY_TLS (index 9):
  VirtualAddress: 0x00015000
  Size:           0x00000018

IMAGE_TLS_DIRECTORY64:
  StartAddressOfRawData: ...
  EndAddressOfRawData:   ...
  AddressOfCallBacks:    0x00015010  <- pointer to callback array

Detection & analysis

During debugging:

  • In x64dbg: Options → Preferences → Events → TLS Callbacks — enable this to break at each callback before the entry point.
  • In WinDbg: sxe ld or set a breakpoint on the TLS callback array address before resuming from the loader breakpoint.
  • IDA: use the Thread Local Storage segment view; callbacks appear as cross-references from the AddressOfCallBacks array.

Static / automated detection:

  • YARA: search for "TLS_CALLBACK" or "TLScallback" strings, or for a non-zero IMAGE_DIRECTORY_ENTRY_TLS data directory entry.
  • PE parsers (pefile, PE-bear): flag any binary with a populated TLS directory that is not a well-known runtime (MSVC, Go, Rust all use TLS legitimately — check the callback count and content).
  • Unprotect technique ID: U0124.
Votes

Comments(0)