TLS callbacks Assembly x86-64

Learn how execute code before the entry point with the TLS callback trick and x86-64 assembly.

Introduction

Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread. Developers use TLS to provide unique data for each thread that the process can access using a global index.

TLS calls are subroutines that are called by the system before the entry point. There is a .tls section in the PE file that describes the place of TLS callbacks.

Some malwares employ TLS callbacks to detect debuggers, notwithstanding, this trick has been used for years and modern analysis tools detect it.

TLS callbacks 64-bit

We developed this assembly code to demonstrate the functioning of TLS callbacks.

format PE64 GUI 4.0
entry start

include 'include\win64a.inc'
include 'include\api\user32.inc'

section '.text' code readable executable
proc start
    invoke  MessageBox, 0, szHelloWorld, szHelloWorld, MB_OK
    ret
endp

proc callback1
    invoke  MessageBox, 0, szCallback1, szTitle, MB_OK
    ret
endp

proc callback2
    invoke  MessageBox, 0, szCallback2, szTitle, MB_OK
    ret
endp

section '.rdata' data readable
    szTitle       db 'Callback Message', 0
    szHelloWorld  db 'Hello World', 0
    szCallback1   db 'This is the first tls callback', 0
    szCallback2   db 'This is the second tls callback', 0

section '.tls' data readable writeable
data 9
    .StartAddressOfRawData  dq 0
    .EndAddressOfRawData    dq 0
    .AddressofIndex         dq adress_of_index
    .AddressOfCallBacks     dq adress_of_callbacks
    .SizeOfZeroFill         dq 0
    .Characteristics        dq 0

    adress_of_index         dq 0
    adress_of_callbacks     dq callback1, callback2, 0
end data

section '.idata' import data readable
library user32, 'user32.dll'

Compile the code above with Fasm to obtain a 64-bit Windows executable

We can clearly see the structure of the TLS section.

This program will execute three MessageBox functions in a specific order.

  1. "This is the first tls callback"
  2. "This is the second tls callback"
  3. "Hello World"

As you can see, despite it is the first and only function called after the entry point, the MessageBox with the "Hello World" message will be executed last.

By default most debuggers break at the entry point and consequently the TLS callbacks function are executed.
An attacker could insert an anti-debugging routine inside the TLS callback function to mislead an analyst.

TLS callbacks 32-bit

The code below is the 32-bit version of the 64-bit code above.

format PE GUI 4.0
entry start

include 'include\win32a.inc'
include 'include\api\user32.inc'

section '.text' code readable executable
proc start
    invoke  MessageBox, 0, szHelloWorld, szHelloWorld, MB_OK
    ret
endp

proc callback1
    invoke  MessageBox, 0, szCallback1, szTitle, MB_OK
    ret
endp

proc callback2
    invoke  MessageBox, 0, szCallback2, szTitle, MB_OK
    ret
endp

section '.rdata' data readable
    szTitle         db 'Callback Message', 0
    szHelloWorld    db 'Hello World', 0
    szCallback1     db 'This is the first tls callback', 0
    szCallback2     db 'This is the second tls callback', 0

section '.tls' data readable writeable
data 9
    .StartAddressOfRawData  dd 0
    .EndAddressOfRawData    dd 0
    .AddressofIndex         dd adress_of_index
    .AddressOfCallBacks     dd adress_of_callbacks
    .SizeOfZeroFill         dd 0
    .Characteristics        dd 0

    adress_of_index         dd 0
    adress_of_callbacks     dd callback1, callback2, 0
end data

section '.idata' import data readable 
library user32, 'user32.dll'

Compile the code above with Fasm to obtain a 32-bit Windows executable