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]

- At RVA
01AAF3A0sits anIMAGE_TLS_DIRECTORYstructure.
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;
#endifIMAGE_TLS_DIRECTORYcomes in x86 and x64 shapes.

- Looking at the members at RVA
01AAF3A0: AddressOfCallbackspoints 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.
-
DllHandleis the module handle / load address. -
Reasonsays 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 withReason = 1(DLL_PROCESS_ATTACH).
DLL_THREAD_ATTACH
- After those callbacks finish,
mainruns. - 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).