Search code examples
assemblycallbacklinkermasmida

MASM doesn't recognize my TLS callback


I have produced an assembler listing of my .C source file. And in the C source i have implemented tls like this:

char *msg = "callback";
void NTAPI tls_callback(PVOID DllHandle, DWORD dwReason, PVOID lpVd)
{
    MessageBoxA(0,msg,msg,0);

}

#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:tls_callback_func") 
#else
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:_tls_callback_func")
#endif

#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK tls_callback_func = tls_callback;
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif

__declspec(thread) char *tlsData = "tls static data";

I have produced assembly listing of this C file, and tls now look like this:

PUBLIC  _tls_callback@12
PUBLIC  _tls_callback_func
PUBLIC  _tlsData

_TLS    SEGMENT
_tlsData 
    DB  00H
    DB  00H
    DB  00H
    DB  00H
    DB  00H
    DB  40H
    DB  30H
    DB  80H
_TLS    ENDS
CRT$XLF SEGMENT
_tls_callback_func DD FLAT:_tls_callback@12
CRT$XLF ENDS


_TEXT   SEGMENT
_DllHandle$ = 8                     ; size = 4
_dwReason$ = 12                     ; size = 4
_lpVd$ = 16                     ; size = 4
_tls_callback@12 PROC                   ; COMDAT

    push    ebp
    mov ebp, esp

    mov edx, DWORD PTR _msg

    push 0
    push edx
    push edx
    push 0
    call DWORD PTR __imp__MessageBoxA@16
; Line 34
    pop ebp
    ret 12                  ; 0000000cH
_tls_callback@12 ENDP
_TEXT   ENDS

I don't see that the tls pattern is produced however, i looked up in IDA PRO the pattern should be:

.rdata:004921A8 __tls_used      dd offset __tls_start  
.rdata:004921AC TlsEnd_ptr      dd offset __tls_end
.rdata:004921B0 TlsIndex_ptr    dd offset __tls_index
.rdata:004921B4 TlsCallbacks_ptr dd offset _tls_callback_func
.rdata:004921B8 TlsSizeOfZeroFill dd 0
.rdata:004921BC TlsCharacteristics dd 100000h

So do i need to define a new tls segment and place those patter in there? Or it should be in the data section?

I compile it like this:

ml.exe listing.asm /coff

I have looked up produced file inside ida pro, and i see that tls directory wasn't produced at all, how do i tell masm or its linker to make directory?


Solution

  • The Microsoft linker uses the symbol __tls_used (on x86 systems) or _tls_used (on non-x86 systems) as the address of the TLS Directory. The TLS Directory contains a pointer to a zero terminated array of TLS callbacks. So by creating a suitable TLS Directory and giving it the name __tls_used/_tls_used you can have a TLS callback function in assembly code.

    Here's an example program that demonstrates this:

        PUBLIC  __tls_used
        PUBLIC  start
    
        EXTERN  __imp__GetStdHandle@4:DWORD, __imp__WriteFile@20:DWORD, __imp__ExitProcess@4:DWORD
    
    _BSS    SEGMENT PUBLIC DWORD FLAT 
    tls_index DD    ?
    _BSS    ENDS
    
    _RDATA  SEGMENT PUBLIC PARA FLAT ALIAS('.rdata') READ
    
    __tls_used:
        DD  OFFSET tls_start
        DD  OFFSET tls_end
        DD  OFFSET tls_index
        DD  OFFSET tls_callbacks
        DD  tls_bss_end - tls_end
        DD  0   ; chracterstics
    
    tls_callbacks:
        DD  OFFSET tls_callback
        DD  0
    
    main_msg:
        DB  "Main entry called.", 13, 10
    main_msg_len = $ - main_msg
    
    _RDATA  ENDS
    
    _DATA   SEGMENT PUBLIC FLAT
    tls_cb_msg:
        DB  "TLS callback called. Reason: 0", 13, 10
    tls_cb_msg_len = $ - tls_cb_msg
    _DATA   ENDS
    
    
    _TLS    SEGMENT PUBLIC DWORD FLAT ALIAS('.tls')
    tls_start:  
        ; put initialized TLS variable definitions here
    tls_end:
        ; put uninitialized TLS variable definitions here
    tls_bss_end:
    _TLS    ENDS
    
    _TEXT   SEGMENT PUBLIC PARA 'CODE' FLAT
        ASSUME  DS:FLAT
    
    tls_callback:
        mov eax, [esp + 8]
        add BYTE PTR [tls_cb_msg + tls_cb_msg_len - 3], al
    
        push    -11 ; nStdHandle
        call    [__imp__GetStdHandle@4]
    
        push    0   ; lpOverlapped
        push    0   ; lpNumberOfBytesWritten
        pushd   tls_cb_msg_len ; nNumberOfBytesToWrite
        push    tls_cb_msg ; lpBuffer
        push    eax ; hFile
        call    [__imp__WriteFile@20]
        ret 12
    
    start:
        push    -11 ; nStdHandle
        call    [__imp__GetStdHandle@4]
    
        push    0   ; lpOverlapped
        push    0   ; lpNumberOfBytesWritten
        pushd   main_msg_len ; nNumberOfBytesToWrite
        push    main_msg ; lpBuffer
        push    eax ; hFile
        call    [__imp__WriteFile@20]
    
        push    0   ; uExitCode
        call    [__imp__ExitProcess@4]
        int 3
    
    _TEXT   ENDS
    
        END start
    

    Note that above code isn't compatible with the Visual C++ runtime (CRT) implementation of TLS. If you're planing to work with C++ code then you'll want let the CRT provide the TLS Directory and other information. You can tell it to use one of your callbacks by putting a pointer to the function in the .CRT$XL? section where the question mark ? is replaced by a letter from B to Y. Using the letter B will cause it to be called before the CRT TLS callback is called. Using a letter from D to Y will have it called after. So the code you'd want would be something like:

    _CRT SEGMENT PUBLIC DWORD ALIAS('.CRT$XLD')
         DD OFFSET my_tls_callback
    _CRT ENDS