The SFT-stealth tute
by MGL/SVL

    Well, this topic was some time ago discussed heavily on IRC, and as I
    promised to write some kind of idiot guide to this topic, here you have it.
    The dude, who finally forced me to write this, was Sep_IR

    As usual, if you can't code a memory resident virus, forget this article,
    it's high over your current abilities. If you 're AV, then as usual fork
    off :)

                                   .... ( or gimme a good payed job )

    A. What SFT is

    SFT stands for System File Table and it is some kind of DOS data structure
    used by DOS itself to handle all the access to the file opened both via
    FCB or via handle. As SFT is internal DOS data structure, it can , if
    modified, be very powerful and effective tool for any virus coder. According
    to 51st issue of Ralph's Interrupt List is structure of SFT as described
    bellow :

    Format of DOS 4.0-6.0 system file tables and FCB tables:

    Offset  Size    Description

     00h    DWORD   pointer to next file table (offset FFFFh if last)
     04h    WORD    number of files in this table
     06h            3Bh bytes per file

    ----------- Here starts SFT entry for each handle ------------------

             Offset Size    Description

     06h     00h    WORD    here point ES:DI after executing Get_SFT_Adress
                            from section 1.1
                            number of file handles referring to this file
                            FFFFh if in use but not referenced
     08h     02h    WORD    file open mode (see AX=6C00h,#0715 at AH=3Dh)
                            bit 15 set if this file opened via FCB
     0Ah     04h    BYTE    file attribute (see #0731 at AX=4301h)
     0Bh     05h    WORD    device info word (see also #0734 at AX=4400h)
                            bit 15 set if remote file
                            bit 14 set means do not set file date/time on closing
                            bit 13 set if named pipe
                            bit 12 set if no inherit
                            bit 11 set if network spooler
                            bit 7  set if device, clear if file (only if local)
                            bits 6-0 as for AX=4400h
     0Dh     07h    DWORD   pointer to device driver header if character device
                            else pointer to DOS Drive Parameter Block
                            (see AH=32h) or REDIR data
     11h     0Bh    WORD   starting cluster of file (local files only)
     13h     0Dh    WORD    file time in packed format (see #0971)
     15h     0Fh    WORD    file date in packed format (see #0972)
     17h     11h    DWORD   file size
     1Bh     15h    DWORD   current offset in file (SFT)
                            LRU counters (FCB table, two WORDs)
            ---local file---
     1Fh     19h    WORD    relative cluster within file of last cluster accessed
     21h     1Bh    DWORD   number of sector containing directory entry
     25h     1Fh    BYTE    number of dir entry within sector (byte offset/32)
            ---network redirector---
     1Fh     19h    DWORD   pointer to REDIRIFS record
     23h     1Dh  3 BYTEs   ???
            ------
     26h     20h 11 BYTEs   filename in FCB format (no path/period, blank-padded)
     31h     2Bh    DWORD   (SHARE.EXE) pointer to previous SFT sharing same file
     35h     2Fh    WORD    (SHARE.EXE) network machine number which opened file
                            (Windows Enhanced mode DOSMGR uses the virtual machine
                              ID as the machine number; see INT 2F/AX=1683h)
     37h     31h    WORD    PSP segment of file's owner (see #0691 at AH=26h)
                              (first three entries for AUX/CON/PRN contain segment
                              of IO.SYS startup code)
     39h     33h    WORD    offset within SHARE.EXE code segment of
                            sharing record (see #0902)  0000h = none
     3Bh     35h    WORD    (local) absolute cluster number of last clustr accessed
                            (redirector) ???
     3Dh     37h    DWORD   pointer to IFS driver for file, 0000000h if native DOS

    Note #1:  the OS/2 2.0 DOS Boot Session does not properly fill in the filename
              field due to incomplete support for SFTs; the OS/2 2.0 DOS Window
              does not appear to support SFTs at all

    Note #2:  all this stuff above is taken (and can be found) in Interrupt List
              under INT 21H subfunction 52h

    B. How to get SFT entry

    Quit easy. All you need is file handle. Then you should execute piece of code
    like follows:

    Get_SFT_Entry_Adress proc near

            push ax         ; save AX
            push bx         ; save file handle
            mov ax,1220h
            int 2fh         ; get Job File Table Entry (M$-Dos 3.0 and above)
                            ; for actual handle
            jc error        ; if CF set , can't proceed
            mov bl,es:[di]  ; ES:DI points to JFT Entry for current handle
            cmp bl,0ffh     ; if BL=0ffh, handle is not open
            je error        ; otherwise BL holds SFT entry number
            mov ax,1216h    ; get adress of SFT Entry
            int 2fh         ; AX 'll be destroyed on ret
            jc error        ; BX is greater than FILES= in config.sys
            clc             ; ES:DI points to system file table entry

    error:  pop bx          ; procedure returns CF set if some error
            pop ax          ; occured, otherwise CF is clear and ES:DI
            ret             ; holds desired value. BX and AX 're
                            ; not changed
    endp

    Note #3: All the stuff is also supported by DR-DOS 5.0+

    Ofcos, if you use such a procedure for getting SFT entry, your virus
    can be easily fooled by ANY AV TSR which HOOKS and modifies INT 2F vector.
    You should call the original INT 2F vector !
    Moreover, another danger is the procedure itself. Such a structure was used
    for 1st time (as far as i know) in Number of the Beast virus and shitload
    of scanners uses some part of this procedure as 'scanstring', and these INT
    2F calls are suspicious for heuristic as well. So, you should at least hide
    this procedure behind the layer(s) of true polymorphic encryption.

    C. Places of interest in SFT entry (for vx coders)

    Offset:      02h - WORD - indicates file open mode, the same as is used by
                     INT 21H/3dh for file open. If you modify this field,
                     you can force always R/W access. That's very interes-
                     ting for any coder. You  do not have to use the obvious
                     and for heuristic quite sensitive code ala:

                      lea dx,[filename_buffer]
                      mov ax,3d02h
                      call old_21_vector

    You can either use just mov ax,3d02h instead of mov ax,3d00h , or you can
    modify this field directly in SFT on need to change to desired value basis.
    Remember, bit 15 is set if the file was opened via FCB.

    Offset:      04h - BYTE - file attribute - the same rules as for INT 21h/43h.
                     Here you can modify attribs at your will, but remember , you
                     have to restore the original ones ! And if you modify the
                     attribs by using the SFT, you have to restore it before you
                     close the handle. If you don't, on file close are the attri-
                     butes changed, and you can be catched by some lame integrity
                     checker, or just per view in Norton Commander

    Offset:      0Dh - WORD - file time in packed format
    Offset:      0Fh - WORD - file date in packed format

                     Here you can stealth time stamp, if you use it as 'mark'

    Offset:      11h - DWORD- file size - veery interesting value. If you subtract
                              virus size from this DWORD, no one can LSEEK to the
                              body of the virus. So you do not have to to handle
                              INT 21/4202 at all. If your virus is full stealth,
                              and i hope it is, you have to handle stealth on
                              read and/or write access. Read is simple to handle:

    Read stealth:           1. Test, if read is from changed area of the file
                            2. If so, extend the file size (add virus size)
                            3. Put original stuff in their buffer
                            4. Subtract virus size

    But the most problematic thing to handle is write stealth. If you write to
    such a modified handle, the file 'll be corrupted, Fat chain 'll be damaged,
    etc ... If you want to handle this problem, structure of infected file
    should be like

            --------------------------------------------------------
           | Header| F   I   L   E   | Virus body | Stored header   |
            --------------------------------------------------------

    where Header, resp. Stored headers stand for every piece of code, which
    virus changes, resp. saves when file is infected. Solution is then easy.

    Read stealth:           1. Extend file size (add virus size)
                            2. Read all the original saved stuff to some
                               buffer in memory.
                            3. Move file pointer to the end of intected file
                            4. Cut the file to its uninfected size

    Order of steps 1-4 is critical and can't be changed !

                            5. Fully disinfect the file, using the stuff
                               stored in memory buffer
                            6. Allow their write
                            7. Reinfect file

    In my humble opinion, the right moment for reinfection is in this case
    the closing of the file. If you reinfect the file right after write to
    this file, you risk, in the case of multiple  writes to file substantial
    slow down of the disk operations. Then you have to disinfect/reinfect the
    file as many times as the write operation is performed. If you reinfect
    on close, only one disinfection/reinfection is necessary.

    Note #4: It works ! :)

    Offset:      15h - DWORD - current offset in file (SFT). This is the same
                               as file pointer. If you want to LSEEL to the
                               start of the file put here 0, to LSEEK EOF, put
                               here file size. If you do not use INT 21/42 , in
                               some cases you can avoid heuristic detection.
                               (In fact, use of SFT modification can be used as
                                heuristic flag in the future too :( )

    Offset:      20h - 11 BYTEs  - filename in FCB format (no path/period, blank
                                   -padded). Here is the place, where you can
                                 test, is the file referred by handle is
                                 excutable or not. Just test 3 bytes starting at
                                 offset 28h for 'COM' or 'EXE'

    Offset:      31h - WORD - PSP segment of file's owner . The same value as you
                              get by reading the word at PSP:16 or code like

                                    mov ah,62h      ; mov ah,50h as well
                                    int 21h         ; BX is PSP
                                    push bx
                                    pop ds
                                    mov dx,ds:[16h] ; parent's PID = PSP of parent
                                                    ; process

    D. Conclusions

    - SFT stealth is powerfull if used correctly
    - SFT stealth on write can be used. Moreover it's possible to avoid the file
      corruption.
    - SFT allows to test if file referred by handle is excutable
    - SFT use can substitute some DOS INT 21H calls ( subfunctions 3dh,42h,43h,
      51h,62h and more )
    - SFT can be used correctly only if you have original INT 2F vector ( an this
      means e.g. to tunnel this vector )
    - SFT not necessary works under M$-DOS 7.0 ( other story )