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 )