Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarchive

archiveJan 3, 2021

TLS (Thread Local Storage) Callbacks

How Windows PE TLS directories and IMAGE_TLS_CALLBACK hooks run before the entry point, and why reverse engineers care for anti-debugging.

Thread Local Storage (TLS)

  • Every thread in a process shares the same virtual address space.
  • A function's locals are per calling thread; statics and globals are shared across the process.
  • TLS gives each thread its own storage so process-wide global or static data can behave like thread-local data when you need isolation.

IMAGE_DATA_DIRECTORY[9]

  • The PE header's TLS Table entry points here.
  • IMAGE_NT_HEADERS → IMAGE_OPTIONAL_HEADER → IMAGE_DATA_DIRECTORY[9]

./0.png
./0.png
{: width="65%" height="65%"}

  • At RVA 01AAF3A0 sits an IMAGE_TLS_DIRECTORY structure.

IMAGE_TLS_DIRECTORY

typedef struct _IMAGE_TLS_DIRECTORY64 {
    ULONGLONG StartAddressOfRawData;
    ULONGLONG EndAddressOfRawData;
    ULONGLONG AddressOfIndex;         // PDWORD
    ULONGLONG AddressOfCallBacks;     // PIMAGE_TLS_CALLBACK *;
    DWORD SizeOfZeroFill;
    union {
        DWORD Characteristics;
        struct {
            DWORD Reserved0 : 20;
            DWORD Alignment : 4;
            DWORD Reserved1 : 8;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;
 
} IMAGE_TLS_DIRECTORY64;
 
typedef IMAGE_TLS_DIRECTORY64 * PIMAGE_TLS_DIRECTORY64;
 
typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD   StartAddressOfRawData;
    DWORD   EndAddressOfRawData;
    DWORD   AddressOfIndex;             // PDWORD
    DWORD   AddressOfCallBacks;         // PIMAGE_TLS_CALLBACK *
    DWORD   SizeOfZeroFill;
    union {
        DWORD Characteristics;
        struct {
            DWORD Reserved0 : 20;
            DWORD Alignment : 4;
            DWORD Reserved1 : 8;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;
 
} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;
 
#ifdef _WIN64
#define IMAGE_ORDINAL_FLAG              IMAGE_ORDINAL_FLAG64
#define IMAGE_ORDINAL(Ordinal)          IMAGE_ORDINAL64(Ordinal)
typedef IMAGE_THUNK_DATA64              IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA64             PIMAGE_THUNK_DATA;
#define IMAGE_SNAP_BY_ORDINAL(Ordinal)  IMAGE_SNAP_BY_ORDINAL64(Ordinal)
typedef IMAGE_TLS_DIRECTORY64           IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY64          PIMAGE_TLS_DIRECTORY;
#else
#define IMAGE_ORDINAL_FLAG              IMAGE_ORDINAL_FLAG32
#define IMAGE_ORDINAL(Ordinal)          IMAGE_ORDINAL32(Ordinal)
typedef IMAGE_THUNK_DATA32              IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA32             PIMAGE_THUNK_DATA;
#define IMAGE_SNAP_BY_ORDINAL(Ordinal)  IMAGE_SNAP_BY_ORDINAL32(Ordinal)
typedef IMAGE_TLS_DIRECTORY32           IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY32          PIMAGE_TLS_DIRECTORY;
#endif
  • IMAGE_TLS_DIRECTORY comes in x86 and x64 shapes.

./1.png
./1.png

  • Looking at the members at RVA 01AAF3A0:
  • AddressOfCallbacks points at an array of TLS callback addresses (as VAs). The array ends with NULL.
  • A program can register more than one TLS callback.
  • When the process starts, before entry-point code runs, the system calls each function listed in that array.

TLS callback function

  • Reverse engineers see TLS callbacks most often as an anti-debugging technique.
  • They run before the entry point (EP).
  • The loader also invokes them when threads are created or destroyed.
  • That includes the process main thread, so callbacks fire before EP code — which is why anti-debug tooling leans on them.
  • Per thread: once on create, once on exit.

IMAGE_TLS_CALLBACK

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved
    );
  • The signature looks a lot like DllMain().

DllMain()

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved )  // reserved
  • Parameter order and meaning match.

  • DllHandle is the module handle / load address.

  • Reason says why the callback ran:

    #defind DLL_PROCESS_ATTACH  1
    #define DLL_THREAD_ATTACH   2
    #define DLL_THREAD_DETACH   3
    #DEFINE DLL_PROCESS_DETACH  0

DLL_PROCESS_ATTACH

  • Before the main thread reaches main, registered TLS callbacks run with Reason = 1 (DLL_PROCESS_ATTACH).

DLL_THREAD_ATTACH

  • After those callbacks finish, main runs.
  • When a user thread is created, TLS callbacks run again with Reason = 2 (DLL_THREAD_ATTACH).

DLL_THREAD_DETACH

  • Once those callbacks return, the thread's start function runs.
  • When that thread exits, TLS callbacks fire with Reason = 3 (DLL_THREAD_DETACH).

DLL_PROCESS_DETACH

  • After user threads finish and the main thread exits, TLS callbacks run one last time with Reason = 0 (DLL_PROCESS_DETACH).

related

  1. Jun 2, 2020/archiveWindows PE File Format
  2. Jan 3, 2021/archiveWindows PEB (Process Environment Block)
  3. Jan 3, 2021/archiveTEB (Thread Environment Block)

graphfeed