-> FILE: ESrc:Own/kbvirus.e          REV: 545 --- Kill memory/clean files infected by BOKOR 1.x virus
/* History
    0     Started to resource BOKOR 1.05  25th June 1997 18:00.
   62     Quite finished version. To be released about 23:00. :)
   77     Hopefully this'll detect all variations of the virus...
          It should though... :-\
   89     Ouch didn't recognize all variations. Sometimes BOKOR
          doesn't use cryping at all.
   107    The polymorphism code is a bit more advanced than I first
          thought. Fixed virus detection. Now kbvirus uses heuristic
          kind of search to find virus from file. Added OpenLibrary()
          -patch to prevent virus from activating
   109    Now automagically INSTALLs pacthes unless NOINSTALL is
          specified. This should help some people... :)
   113    21:10... Some testing... :)
   121    Huh... I have really underestimated that polymorphic engine... :P
   130    Should work now. BOKOR has add.l/eor.l cryptmodes.
   133    Uhhh... and more stuff... I have to admit: the engine is
          quite good. =)
   137    Now it should be finally finished. 23:15
   152    Improved killmem_BOKOR(). Now it'll kill virus more often. Fixed
          funny bug that caused killmem_BOKOR() to always tell that virus
          wasn't found.
   248    Added file scanning and such. Now detects and reports
          infected files.
   278    It seems to work. Succesfully cleaned CEd and SetPatch (!).
   325    V1.2 finished 1st July 13:15.
   327    Added support for SLOS patch modification. Thanks Dave! :)
   343    Added support for XFD system. Dave's one has it too...
          I need to keep up to competition... ;)
   352    Fixed some minor bugs.
   408    Added support for single files (as arguments). Overlay
          executables are now skipped.
   453    Added patternmatching.
   455    Ouch. v1.2b was broken. It didn't strip upper bits from hunkids.
   468    Added support for BOKOR V1.01.
   469    Added support for BOKOR V1.06.
   475    Fixed _STUPID_ mistake with FFS filesystems. Release v1.3.
   482    Found "Bokor V1.1!!" at Assembly'97. Added support for it.
   483    Hm, Ehh. "Bokor V1.1!!" has varying reloc offset. Fixed.
          Release v1.4.
   510    Uhhuh. Fiddled with 1.1 support much too long. It should now work.
   524    Huh. 1.1 code works now. :)
   545    Added SelfCheck. Added new aliases for keywords. Disabled hard/soft
          link following.
          Release v1.4b.
*/

MODULE 'dos/dos','dos/dosextens','exec/libraries','exec/memory','dos/doshunks',
       'workbench/startup'
MODULE '*dirwalker'
MODULE 'libraries/xfdmaster','xfdmaster','utility'

/*
      Flags:
      DWF_FOLLOWHARD  If set will follow hardlinks.
      DWF_FOLLOWSOFT  If set will follow softlinks.

      success:=dw_WalkName(dirname,flags,funcarray)
      success:=dw_Walk(dirlock,flags,funcarray)

      File types from dos/dosextens.i (fib.direntrytype)

      ST_ROOT      dir   normal dir (rootdirectory of boot disk)
      ST_USERDIR   dir   normal dir
      ST_LINKDIR   link  hard link to directory

      ST_SOFTLINK  link  soft link to file or dir (looks like dir!)

      ST_FILE      file  normal file
      ST_LINKFILE  link  hard link to file
      ST_PIPEFILE  file  for pipes that support ExamineFH
*/

DEF hookg1,hookg2         -> These two must be global!


DEF array:PTR TO LONG,q,dirs:PTR TO LONG,f
DEF count,vcount,fcount,tcount,tvcount,tfcount

DEF xa[128]:ARRAY OF LONG,n1[256]:STRING,n2[256]:STRING
DEF buffer:PTR TO LONG, end, ofs=0, first, last
DEF xfd:PTR TO lib, xprint
DEF pat:PTR TO CHAR, patf

ENUM ER_BREAK=10000, ER_NONE=0, ER_ARGS, ER_MEM, ER_PAT
ENUM ARG_D=0, ARG_P, ARG_FIX, ARG_I, ARG_NI, ARG_NOXFD, ARG_HELP, ARG_QUIET

PROC main() HANDLE
  DEF cop,ver,rdargs=NIL,ioerr,r,s:PTR TO CHAR,len

  IF KickVersion(37)=FALSE
    WriteF('This program requires OS 2.04+ (as does the BOKOR virus :)\n')
    RETURN 20
  ENDIF

  ver:='$VER: kbVirus 1.4b (19.8.97)'
  cop:='$COPYRIGHT: Copyright © 1997 Harry "Piru" Sintonen'

  array:=[0,0,0,0,0,0,0,0]
  IF (rdargs:=ReadArgs('D=DIRS=FILES/M,P=PAT=PATTERN/K,F=FIX=FIXFILES/S,I=INSTALL/S,NI=NOINSTALL/S,NOXFD/S,H=HELP/S,Q=QUIET/S',array,NIL))=NIL THEN Raise(ER_ARGS)
  q:=array[ARG_QUIET]
  IF array[ARG_HELP] THEN q:=FALSE
  IF Not(q)
    PrintF('\e[1m\s \e[32;3m\s.\e[22;39;23m\n\n',ver+6,cop+12)
  ENDIF

  selfcheck()

  IF array[ARG_I] AND array[ARG_NI]
    IF Not(q)
      PrintF('\nYou can\at specify both INSTALL and NOINSTALL!\n')
    ENDIF
    Raise(ER_ARGS)
  ENDIF

  IF array[ARG_HELP]
    IF Not(q)
      PrintF('\nThis program kills BOKOR 1.x virus from memory and additionally prevents\nit from being activated. This program allows you to run infected files\nwithout any harm. Note that this program is intended to be used only until\nvirus killers are updated. DISCLAIMER: I take no responsibility for any\ndamages caused by these 1\an\a0s!\n\n')
      PrintF('Usage:\n D=DIRS=FILES/M   -- Here you can give directories/files to scan for virus\n P=PAT=PATTERN/K  -- Matchpattern for files (#? as default)\n F=FIX=FIXFILES/S -- kbVirus tries to fix infected executables\n I=INSTALL/S      -- Install patch to allow running of infected executables\n NI=NOINSTALL/S   -- Do \e[1mnot\e[22m install any patches\n NOXFD/S          -- Do \e[1mnot\e[22m use XFD to decrunch executables\n H=HELP           -- Display this help\n Q=QUIET/S        -- Guess... :)\n\nTo be able to run your infected system get this executable (you have\nit already by now! :) and put it into C:. Then reboot your system without\ns:startup-sequence and run kbVirus once (kbVirus INSTALL) thus enabling\n')
      PrintF('you to run infected programs. Then edit your s:startup-sequence and add\nfollowing line as the very \e[4mfirst\e[24m line: \e[1m"C:kbVirus INSTALL QUIET"\e[22m\n\nFrom version 1.2 and up it is possible to scan disk for infected files and\neven fix them. Use D=DIRS keyword to specify directories to scan (multiple\ndirectories can be given). Use FIX if you want to salvage infected files.\n')
      PrintF('WARNING: Fixing some executable files may be impossible due some\nprogramming failures in BOKOR. If infected file can\at be fixed its\nexecute protection bit will be taken off, file comment will be changed to\nremind you about the infection and message will be displayed on console.\nEven after cleaning your hard disks you\are still advised to reinstall\nevery software you can. Note that both crunched \e[1mand\e[22m infected files will be\nsaved back uncrunched if virus is fixed from them.\n')
    ENDIF
    Raise(ER_NONE)
  ENDIF

  IF (patf:=array[ARG_P])
    pat:=String(len:=StrLen(patf)*2+2)
    IF Not(KickVersion(39))
      IF (utilitybase:=OpenLibrary('utility.library',37))
        s:=patf
        FOR r:=0 TO StrLen(s)-1; s[]++:=ToUpper(s[]); ENDFOR
        CloseLibrary(utilitybase)
      ENDIF
    ENDIF
    IF ParsePatternNoCase(patf,pat,len)<0 THEN Raise(ER_PAT)
  ENDIF

  IF Not(array[ARG_NI])
    installpatch_BOKOR()
  ELSE
    IF Not(q)
      PrintF('No LoadSeg() or OpenLibrary() -patch added!\n')
    ENDIF
  ENDIF

  IF Not(array[ARG_NOXFD])
    xfdmasterbase:=OpenLibrary('xfdmaster.library',34)
  ELSE
    xfdmasterbase:=NIL
  ENDIF
  xfd:=xfdmasterbase

  IF (dirs:=array[ARG_D])
    IF (f:=array[ARG_FIX])
      IF Not(q)
        PrintF('FIX MODE SET - Infected files will be fixed, if possible.\n')
      ENDIF
    ENDIF
    WHILE dirs[]
      scan(dirs[])
      ADDQ.L #4,dirs
    ENDWHILE
    IF Not(q) THEN PrintF('\nTotal \d files scanned, \d viruses found, \d files fixed (\d%).\n',tcount,tvcount,tfcount,IF tvcount=0 THEN 0 ELSE tfcount*100/tvcount)
  ENDIF

EXCEPT DO
  ioerr:=IoErr()

  IF xfdmasterbase THEN CloseLibrary(xfdmasterbase)
  IF rdargs THEN FreeArgs(rdargs)

  IF exception
    IF exception=ER_BREAK; IF Not(q); PrintF('***Break\n'); ENDIF; RETURN 5; ENDIF
    IF (exception="NEW") OR (exception="MEM") THEN exception:=ER_MEM
    IF ioerr AND Not(q) THEN PrintFault(ioerr,'kbVirus')
    IF Not(q) THEN PrintF('Error: Could not \s!\n',
                          ListItem(['','get arguments','allocate enough memory','parse pattern'],exception))
    RETURN 10
  ENDIF
ENDPROC

PROC selfcheck()
  DEF wb:PTR TO wbstartup, arg:PTR TO wbarg, lock=NIL, name=NIL,
      fullpath[512]:STRING, temps[40]:STRING, oq

  killmem_BOKOR(q)

  IF wbmessage=NIL
    lock:=GetProgramDir()
  ELSE
    wb:=wbmessage
    IF wb.numargs
      arg:=wb.arglist
      lock:=arg[].lock
      name:=arg[].name
    ENDIF
  ENDIF
  NameFromLock(lock,fullpath,512)
  IF name=NIL
    GetProgramName(temps,40); name:=temps
  ENDIF
  AddPart(fullpath,name,512); SetStr(fullpath,StrLen(fullpath))

  oq:=q; q:=TRUE
  scan(fullpath)
  q:=oq

  IF tvcount
    IF tfcount
      IF Not(q) THEN PrintF('NOTE: Main executable was selfdisinfected.\n')
    ELSE
      IF Not(q) THEN PrintF('\nALERT!! Main executable is infected and could\at be disinfected!!\n')
    ENDIF
  ENDIF
  tcount:=0; tvcount:=0; tfcount:=0

  killmem_BOKOR(TRUE)  -> kill quietly

ENDPROC

PROC decrunch_buf(sbuf,slen,noext,filename)
  DEF bufinfo:PTR TO xfdbufferinfo,ret=NIL,herr=0
  DEF buf,buflen,savelen

  buf:=sbuf; buflen:=slen; savelen:=slen

  IF xfd
    IF (bufinfo:=XfdAllocObject(XFDOBJ_BUFFERINFO))
      IF Not(noext) AND (xfd.version>=37) THEN PutInt(bufinfo+48,1) ->XFDFF_RECOGEXTERN
      bufinfo.sourcebuffer:=sbuf
      bufinfo.sourcebuflen:=slen
      IF XfdRecogBuffer(bufinfo)
        IF bufinfo.packerflags AND XFDPFF_RELOC
          bufinfo.targetbufmemtype:=MEMF_ANY

          IF Not(testexestruct(sbuf,slen,filename))
            IF buf
              FreeMem(buf,buflen)
              buf:=0; buflen:=0; savelen:=0
            ENDIF
          ELSE
            xprint:=TRUE
            IF Not(q)
              PrintF('\s is \e[33m\s\e[39m crunched...',filename,bufinfo.packername); Flush(stdout)
            ENDIF
            IF XfdDecrunchBuffer(bufinfo)
              FreeMem(sbuf,slen); sbuf:=0; slen:=0

              buf:=bufinfo.targetbuffer
              buflen:=bufinfo.targetbuflen
              savelen:=bufinfo.targetbufsavelen

              IF Not(q); PrintF(' decrunched OK '); Flush(stdout); ENDIF

              IF Not(testexestruct(buf,savelen,filename))
                IF buf
                  FreeMem(buf,buflen)
                  buf:=0; buflen:=0; savelen:=0
                ENDIF
              ELSE
                IF Not(q) THEN PrintF('\n')
              ENDIF
            ELSE
              PrintF(' \e[32mdecrunching error!\e[39m\n')
              xfdprinterr('XFD error: ',bufinfo.error)
              IF bufinfo.error=XFDERR_CORRUPTEDDATA
                PrintF('\e[1mCrunched executable is corrupted! This file should be deleted.\e[22m\n')
              ENDIF
            ENDIF
          ENDIF
        ENDIF
      ENDIF
      XfdFreeObject(bufinfo)
    ENDIF
  ENDIF
ENDPROC buf,buflen,savelen

PROC xfdprinterr(head,ernum)
  IF xfd AND (ernum<>XFDERR_OK) AND Not(q) THEN PrintF('\s\s\n',head,XfdGetErrorText(ernum))
ENDPROC

PROC installpatch_BOKOR()
  DEF pmem,mseg,x,mes

  mes:='Installing LoadSeg() and OpenLibrary() -patches for detecting/\ndisabling virus... '

  killmem_BOKOR(TRUE)  -> kill quietly

  IF (mseg:=LoadSeg({magicname}))
    MOVE.L  mseg,A1
    ADD.L   A1,A1
    ADD.L   A1,A1
    ADDQ.L  #4,A1
    MOVEQ   #0,D0
    CMP.L   #"kbvk",2(A1)
    BNE     ipts
    JSR     (A1)
    ipts:
    MOVE.L  D0,x
    UnLoadSeg(mseg)
    IF x="kbvk"
      IF Not(q) AND array[ARG_I]
        PrintF('\sPatches already installed.\n',mes)
      ENDIF
      RETURN TRUE
    ENDIF
  ENDIF

  IF (pmem:=AllocMem({pend}-{pstrt}+32,MEMF_PUBLIC OR MEMF_CLEAR))=NIL THEN Raise(ER_MEM)
  CopyMem({pstrt},pmem,{pend}-{pstrt})
  Forbid()
  PutLong(pmem+{lspatch}-{pstrt},SetFunction(dosbase,-$96,pmem+{lspatchcode}-{pstrt}))
  PutLong(pmem+{olpatch}-{pstrt},SetFunction(execbase,-$228,pmem+{olpatchcode}-{pstrt}))
  CacheClearU()
  UnLoadSeg(LoadSeg('kbvirusfakeloadsegcalla'))  -> Kill it by OpenLibrary patch
  Permit()


  IF Not(q) THEN PrintF('\sPatches installed OK.\n',mes)

  killmem_BOKOR(TRUE)  -> kill quietly
  RETURN TRUE

  magicname:
  CHAR 0,'kbvk',0

  -> patch
  pstrt:
  lspatch:
  LONG  0
  olpatch:
  LONG  0

  magicseg:     -> longword aligned!!
  LONG   0,0    -> really faked seglist
  MOVE.L #"kbvk",D0
  RTS

  -> LoadSeg patch
  LONG  "kbvk"
  lspatchcode:
  TST.L   D1
  BEQ     nottest
  MOVE.L  D1,A0
  TST.B   (A0)+
  BNE     nottest
  CMP.B   #"k",(A0)+
  BNE     nottest
  CMP.B   #"b",(A0)+
  BNE     nottest
  CMP.B   #"v",(A0)+
  BNE     nottest
  CMP.B   #"k",(A0)+
  BNE     nottest
  TST.B   (A0)
  BNE     nottest
  LEA     magicseg(PC),A0
  MOVE.L  A0,D0
  ADDQ.L  #4,D0
  LSR.L   #2,D0
  RTS

  nottest:
  MOVE.L  lspatch(PC),A0
  JSR     (A0)
  TST.L   D0
  BEQ     exitx
  BMI     exitx
  MOVEM.L D0/D2-D7/A2-A6,-(A7)

  testnextseg:
  LSL.L   #2,D0
  ADDQ.L  #4,D0
  MOVE.L  D0,A5

  MOVE.L  A5,A0
  BSR     detectvirseg
  BEQ     exit2

  MOVE.L  (A7),D1
  ->CLR.L   (A7)
  MOVE.L  -4(A5),(A7)
  CLR.L   -4(A5)
  JSR     UnLoadSeg(A6)
  MOVE.L  (A7),D0
  BRA     testnextseg

  exit2:
  MOVE.L  4,A6
  JSR     CacheClearU(A6)
  MOVEM.L (A7)+,D0/D2-D7/A2-A6
  exitx:
  RTS



  -> OpenLibrary patch:
  LONG  "kbvk"
  olpatchcode:
  MOVEM.L D0/A1,-(A7)
  TST.L   D0
  BNE     exitol
  MOVE.L  A1,D0
  BEQ     exitol
  LSR.L   #1,D0             -> word aligned?
  BCS     exitol

  CMP.L   #"dos.",(A1)
  BNE     exitol
  CMP.L   #"libr",4(A1)
  BNE     exitol
  CMP.L   #$61727900,8(A1)  -> "ary"<<8
  BNE     exitol

  CMP.L   #"RTUP",-4(A1)
  BNE     exitol
  CMP.L   #"rtup",-8(A1)
  BNE     try_b11
  CMP.L   #"Devs",-12(A1)
  BNE     exitol
  CMP.L   #"DEVS",-16(A1)
  BNE     exitol
  CMP.L   #"devs",-20(A1)
  BNE     exitol
  CMP.L   #"IRUS",-24(A1)
  BNE     exitol
  CMP.L   #"irus",-28(A1)
  BNE     exitol
  CMP.L   #"DLER",-32(A1)
  BNE     exitol
  CMP.L   #"dler",-36(A1)
  BNE     exitol

  CMP.W   #$60EA,-$26(A1)
  BNE     exitol
  CMP.L   #$4EAEFFDC,-$2A(A1)
  BNE     exitol
  CMP.L   #$222C0014,-$2E(A1)
  BNE     exitol

  CLR.B   (A1)+           -> prevent virus from getting doslib -> will quit quietly
  LEA     kmsg(PC),A0     -> and run the original... (nice :) Also kills memoryresident part!
  kcopy:
  MOVE.B  (A0)+,(A1)+
  BNE.B   kcopy

  MOVE.L  4(A7),A1        -> restore a1
  MOVE.L  A4,D0
  CMP.L   A1,D0
  BNE     ol_notmagic
  MOVE.L  A2,A1
  LEA     -$40(A1),A1
  JSR     TypeOfMem(A6)
  TST.L   D0
  BEQ     ol_notmagic
  CMP.L   #$42E748E7,4(A2)
  BNE.B   ol_notmagic
  CMP.L   #$FFFE2041,8(A2)
  BNE.B   ol_notmagic
  CMP.L   #$4280247C,12(A2)
  BNE.B   ol_notmagic
  CMP.L   #"ATCH",-4(A2)
  BNE.B   ol_notmagic
  CMP.L   #"SYSP",-8(A2)
  BNE.B   ol_notmagic
  CMP.L   #$4E75600C,-12(A2)
  BNE.B   ol_notmagic

  -> kill it!
  JSR     Forbid(A6)
  LEA     $2AA(A2),A0
  MOVE.L  -10(A2),A1
  MOVE.W  (A0)+,(A1)+
  MOVE.L  (A0),(A1)
  JSR     CacheClearU(A6)
  JSR     Permit(A6)
  BRA.B   exitol

  ol_notmagic:
  JSR     CacheClearU(A6)

  exitol:
  MOVEM.L (A7)+,D0/A1
  MOVE.L  olpatch(PC),A0
  JMP     (A0)

  kmsg:
    -> dos.library
  CHAR 'kbVirus.',0
  -> is automagically aligned!



  -> "Bokor 1.1!!" code:

  try_b11:
  CMP.L   #"DEVS",-8(A1)
  BNE     exitol
  CMP.L   #"Viru",-12(A1)
  BNE     exitol
  CMP.L   #"Devs",-16(A1)
  BNE     exitol
  CMP.L   #"DLER",-20(A1)
  BNE     exitol
  CMP.L   #"devs",-24(A1)
  BNE     exitol
  CMP.L   #"IRUS",-28(A1)
  BNE     exitol
  CMP.L   #"rtup",-32(A1)
  BNE     exitol
  CMP.L   #"dler",-36(A1)
  BNE     exitol

  MOVEQ   #12,D0          -> Test for first OpenLibrary call
  ADD.L   A1,D0
  CMP.L   A4,D0
  BNE.B   ol_not_firstol

  -> kill it!
  MOVE.L  A1,A0           -> Find "next hunk jmp" and openlibrary pos
  MOVE.W  #799,D1         -> 1600/2-1
  ls_1lfind:
  CMP.L   #$7F7F4EF9,(A0)
  BEQ.B   ls_1lscopyjump
  SUBQ.L  #2,A0
  DBF     D1,ls_1lfind
  BRA.B   ol_not_firstol
  ls_1lscopyjump:
  LEA     -2(A0),A1       -> a1=movempos
  ls_1lfind2:
  CMP.W   #$43FA,-8(A0)
  BNE.B   ls_1lscopyskip
  CMP.L   #$4EAEFDD8,-4(A0)
  BNE.B   ls_1lscopyskip
  CMP.W   #$2880,0(A0)
  BNE.B   ls_1lscopyskip
  CMP.W   #$41FA,4(A0)
  BEQ.B   ls_1lscopyjump2
  ls_1lscopyskip:
  SUBQ.L  #2,A0
  DBF     D1,ls_1lfind2
  BRA.B   ol_not_firstol
  ls_1lscopyjump2:
  MOVE.W  #$4EF9,(A0)+    -> create jump that goes to movem & "next hunk jmp"
  MOVE.L  A1,(A0)+

  MOVE.L  4(A7),A1        -> restore libname ptr from stack

  ol_not_firstol:
  -> Actually this doesn't prevent bokor 1.1 from crashing.

  CLR.B   (A1)+
  LEA     kmsg(PC),A0
  kcopy:
  MOVE.B  (A0)+,(A1)+
  BNE.B   kcopy

  BRA     ol_notmagic     -> do CecheClear & call orig


  skippol_l:
  MOVE.L  A0,-(A7)
  spllloop:
  CMP.L   (A0),D0
  BEQ.B   spllfound
  ADDQ.L  #2,A0
  CMP.L   A0,D7
  BLS.B   spllnfound
  DBF     D1,spllloop
  spllnfound:
  MOVE.L  (A7)+,A0
  MOVEQ   #1,D0
  RTS
  spllfound:
  ADDQ.L  #4,A7
  ADDQ.L  #4,A0
  MOVEQ   #0,D0
  RTS

  skippol_w:
  MOVE.L  A0,-(A7)
  SUBQ.L  #4,A7
  splwloop:
  MOVE.W  (A0)+,(A7)
  AND.L   D0,(A7)
  CMP.W   (A7),D0
  BEQ.B    splwfound
  CMP.L   A0,D7
  BLS.B   splwnfound
  DBF     D1,splwloop
  splwnfound:
  ADDQ.L  #4,A7
  MOVE.L  (A7)+,A0
  MOVEQ   #1,D0
  RTS
  splwfound:
  ADDQ.L  #8,A7
  MOVEQ   #0,D0
  RTS


  -> "Bokor V1.1!!"

  dvs_try_bokor11:
  CMP.L   #$48E7FEFE,(A0)+
  BNE     dvs_exit

  MOVE.L  #$F1FF303C,D0     -> move.w #xxx,dn
  MOVEQ   #127,D1
  BSR     skippol_w
  BNE     dvs_try_nocrypt11
  MOVE.W  (A0)+,D0
  AND.W   #$FE00,D0
  BNE     dvs_try_nocrypt11
  MOVEQ   #$E,D0
  AND.B   -4(A0),D0
  LSR.L   #1,D0
  OR.L    #$FFFF51C8,D0     -> d0=dbf to scan
  MOVEQ   #63,D1
  BSR     skippol_w

  BNE     dvs_try_nocrypt11
  ADDQ.L  #2,A0

  MOVE.L  A1,A0
  MOVE.L  #$FFF82428,D0
  MOVEQ   #127,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVEQ   #7,D4
  AND.B   -1(A0),D4     -> d4=keyareg
  CMP.B   #7,D4         -> don't allow a7
  BCC     dvs_exit
  MOVE.W  (A0)+,D2
  EXT.L   D2

  MOVE.L  A1,A0
  MOVE.L  D4,D0
  ADD.L   D0,D0         -> keyareg*2
  OR.L    #$FAFFFF41,D0
  ROL.L   #8,D0
  MOVEQ   #127,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVE.W  (A0)+,D0
  EXT.L   D0
  ADD.L   D0,D2
  ADD.L   A0,D2
  SUBQ.L  #2,D2
  MOVE.L  A0,D0
  MOVE.L  D2,A0
  MOVE.L  (A0),D2       -> d2=cryptkey
  MOVE.L  D0,A0
  MOVEQ   #0,D6
  MOVE.L  #$FFF8B598,D0 -> eor.l d2,(an)+
  MOVEQ   #63,D1
  BSR     skippol_w
  BEQ.B   is_eorl11
  MOVEQ   #4,D6
  MOVE.L  #$FFF8D598,D0 -> add.l d2,(an)+
  MOVEQ   #63,D1
  BSR     skippol_w
  BNE     dvs_exit
  is_eorl11:
  MOVEQ   #7,D3
  AND.B   -1(A0),D3     -> d3=cryptareg
  CMP.B   #7,D3         -> don't allow a7
  BCC     dvs_exit
  

  MOVE.L  #$2C780004,D0  -> execbase loading
  MOVEQ   #127,D1
  BSR     skippol_l
  BEQ.B   dvs_isexecl11
  MOVE.L  #$2C790000,D0
  MOVEQ   #63,D1
  BSR     skippol_l
  BNE     dvs_exit
  CMP.W   #4,(A0)+
  BNE     dvs_exit
  dvs_isexecl11:

  MOVE.L  #$FFFFFD84,D0
  MOVEQ   #63,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVE.W  -4(A0),D0
  AND.W   #$FFF8,D0
  CMP.W   #$4EA8,D0
  BNE     dvs_exit

  MOVE.L  A1,A0
  MOVE.L  D3,D0
  ADD.L   D0,D0          -> cryptareg*2
  OR.L    #$FAFFFF41,D0
  ROL.L   #8,D0
  MOVEQ   #63,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVE.W  (A0)+,D0
  CMP.W   #$80,D0
  BHI     dvs_exit
  LEA     -2(A0,D0.W),A0

  MOVE.L  -4(A1),D0
  LSL.L   #2,D0
  ADDQ.L  #4,D0
  MOVE.W  #$1FF,D1    -> $400/2-1
  hptrfind:
  ADDQ.L  #2,A0
  CMP.L   (A0),D0
  BEQ.B   hptrfound
  DBF     D1,hptrfind
  BRA     dvs_exit
  hptrfound:
  MOVE.L  -2(A0),D1     -> read long at jmp inst
  JSR     0(A4,D6.L)
  SWAP    D1
  CMP.W   #$4EF9,D1
  BEQ     dvs_ok
  MOVE.W  -2(A0),D1     -> read long at jmp inst-2
  JSR     0(A4,D6.L)
  CMP.W   #$4EF9,D1
  BEQ     dvs_retok
  BRA     dvs_exit


  dvs_try_nocrypt11:
  MOVE.L  A1,A0
  MOVE.L  #$70002C78,D0
  MOVEQ   #127,D1
  BSR     skippol_l
  BNE     dvs_exit
  CMP.L   #$000441FA,(A0)+
  BNE     dvs_exit
  CMP.L   #$02904E71,(A0)+
  BNE     dvs_exit
  CMP.L   #$49FA05AA,(A0)+
  BNE     dvs_exit
  CMP.L   #$43FA059A,(A0)+
  BNE     dvs_exit
  CMP.L   #$4EAEFDD8,(A0)+
  BNE     dvs_exit
  CMP.L   #$288067F4,(A0)+
  BNE     dvs_exit
  CMP.L   #$41FA02D8,(A0)+
  BNE     dvs_exit
  CMP.L   #$43FA05B2,(A0)+
  BNE     dvs_exit
  CMP.W   #$2089,(A0)+
  BNE     dvs_exit
  BRA     dvs_retok


  -> code

  ->LONG    "0XYZ"
  detectvirseg:
  MOVEM.L D2-D4/D6-D7/A4-A5,-(A7)
  LEA     decr_codes(PC),A4
  MOVE.L  A0,A1
  MOVE.L  A1,D7
  ADD.L   -8(A1),D7         -> D7=segend
  CMP.L   #$48E7FFFE,(A0)
  BNE     dvs_try_bokor11
  ADDQ.L  #4,A0

  MOVE.L  #$FFFF323C,D0
  MOVEQ   #127,D1
  BSR     skippol_w
  BNE     dvs_try_nocrypt
  MOVE.W  (A0)+,D0
  AND.W   #$FE00,D0
  BNE     dvs_try_nocrypt

  MOVE.L  A1,A0
  MOVE.L  #$FFF82428,D0
  MOVEQ   #127,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVEQ   #7,D4
  AND.B   -1(A0),D4     -> d4=keyareg
  CMP.B   #6,D4         -> don't allow a6 or a7
  BCC     dvs_exit
  MOVE.W  (A0)+,D2
  EXT.L   D2

  MOVE.L  A1,A0
  MOVE.L  D4,D0
  ADD.L   D0,D0         -> keyareg*2
  OR.L    #$FAFFFF41,D0
  ROL.L   #8,D0
  MOVEQ   #127,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVE.W  (A0)+,D0
  EXT.L   D0
  ADD.L   D0,D2
  ADD.L   A0,D2
  SUBQ.L  #2,D2
  MOVE.L  A0,D0
  MOVE.L  D2,A0
  MOVE.L  (A0),D2       -> d2=cryptkey
  MOVE.L  D0,A0
  MOVEQ   #0,D6
  MOVE.L  #$FFF8B598,D0 -> eor.l d2,(an)+
  MOVEQ   #63,D1
  BSR     skippol_w
  BEQ.B   is_eorl
  MOVEQ   #4,D6
  MOVE.L  #$FFF8D598,D0 -> add.l d2,(an)+
  MOVEQ   #63,D1
  BSR     skippol_w
  BNE     dvs_exit
  is_eorl:
  MOVEQ   #7,D3
  AND.B   -1(A0),D3     -> d3=cryptareg
  CMP.B   #6,D3         -> don't allow a6 or a7
  BCC     dvs_exit
  

  MOVE.L  #$FFFF51C9,D0 -> dbf
  MOVEQ   #63,D1
  BSR     skippol_w
  BNE     dvs_exit
  ADDQ.L  #2,A0

  MOVE.L  #$2C780004,D0  -> execbase loading
  MOVEQ   #63,D1
  BSR     skippol_l
  BEQ.B   dvs_isexecl
  MOVE.L  #$2C790000,D0
  MOVEQ   #63,D1
  BSR     skippol_l
  BNE     dvs_exit
  CMP.W   #4,(A0)+
  BNE     dvs_exit
  dvs_isexecl:

  MOVE.L  #$4EAEFD84,D0  -> jsr (_LVOCacheClearU,a6)
  MOVEQ   #63,D1
  BSR     skippol_l
  BNE     dvs_exit

  MOVE.L  A1,A0
  MOVE.L  D3,D0
  ADD.L   D0,D0          -> cryptareg*2
  OR.L    #$FAFFFF41,D0
  ROL.L   #8,D0
  MOVEQ   #63,D1
  BSR     skippol_w
  BNE     dvs_exit
  MOVE.W  (A0)+,D0
  CMP.W   #$80,D0
  BHI     dvs_exit
  MOVE.L  -2(A0,D0.W),D1 -> first crypted lw
  JSR     0(A4,D6.L)
  LEA     0(A0,D0.W),A5
  ADD.W   D1,A5          -> a5=fixjump pos
  AND.L   #$F9FFF000,D1
  CMP.L   #$41FA0000,D1  -> lea (xxxx,pc),an
  BNE.B   dvs_exit
  MOVE.L  2(A0,D0.W),D1  -> 2nd crypted lw
  JSR     0(A4,D6.L)
  MOVE.W  D1,D0
  AND.L   #$FFF80000,D1
  CMP.L   #$B5900000,D1  -> eor.l d2,(an)
  BEQ     dvs_iseorok
  CMP.L   #$95900000,D1  -> sub.l d2,(an)
  BEQ     dvs_iseorok
  CMP.L   #$B5A80000,D1  -> eor.l d2,(yyyy,an)
  BEQ     dvs_iseorok2
  CMP.L   #$95A80000,D1  -> sub.l d2,(yyyy,an)
  BNE     dvs_exit
  dvs_iseorok2:
  ADD.W   D0,A5          -> a5=fixjump pos
  dvs_iseorok:

  dvs_ok:
  MOVE.L  -4(A1),D0
  LSL.L   #2,D0
  ADDQ.L  #4,D0
  CMP.L   (A5),D0
  BNE     dvs_exit

  dvs_retok:
  MOVEM.L (A7)+,D2-D4/D6-D7/A4-A5
  MOVEQ   #1,D0
  RTS
  dvs_exit:
  MOVEM.L (A7)+,D2-D4/D6-D7/A4-A5
  MOVEQ   #0,D0
  RTS


  dvs_try_nocrypt:
  MOVE.L  #$41FA01FA,D0
  MOVEQ   #63,D1
  BSR     skippol_l
  BNE     dvs_exit
  CMP.W   #$4E71,(A0)+
  BNE     dvs_exit
  CMP.L   #$49FA0538,(A0)+
  BNE     dvs_exit
  CMP.L   #$70002C78,(A0)+
  BNE     dvs_exit
  CMP.L   #$000443FA,(A0)+
  BNE     dvs_exit
  CMP.L   #$05224EAE,(A0)+
  BNE     dvs_exit
  CMP.L   #$FDD82880,(A0)+
  BNE     dvs_exit
  CMP.L   #$670001DA,(A0)+
  BNE     dvs_exit
  CMP.L   #$4EAEFD84,(A0)+
  BNE     dvs_exit
  BRA     dvs_retok


  decr_codes:
  EOR.L   D2,D1
  RTS
  ADD.L   D2,D1
  RTS


  CHAR    'kbVirus (C) 1997 Harry Sintonen',0

  pend:
ENDPROC


PROC killmem_BOKOR(q)
  DEF db:PTR TO lib
  DEF loadseg:PTR TO LONG,ls:PTR TO LONG,sc:PTR TO LONG,flg=FALSE

  IF Not(q)
    PrintF('Scanning memory (LoadSeg) for BOKOR 1.x virus... '); Flush(stdout)
  ENDIF

  -> method 1:

  Forbid()
  loadseg:=Long(dosbase-$96+2)
  IF Int(loadseg)=$4E71 THEN ADDQ.L #2,loadseg

  -> support for StreamLineOS modification, thanks Dave! :)
  IF (Int(loadseg)<>$600C) AND (Int(loadseg-14)=$600C)
    ls:=loadseg-12
    IF (ls[]="SYSP") AND (ls[1]="ATCH")
      IF (ls[3]=$42E748E7) AND (ls[4]=$FFFE2041) AND (ls[5]=$4280247C)
        loadseg:=loadseg-14
      ELSEIF (ls[3]=$42E748E7) AND (ls[4]=$FFFE49FA) AND (ls[5]=$02AA2941)
        loadseg:=loadseg-14
      ENDIF
    ENDIF
  ENDIF
  IF Int(loadseg)=$600C
    ls:=loadseg+2
    IF (ls[]="SYSP") AND (ls[1]="ATCH")
      IF (ls[3]=$42E748E7) AND (ls[4]=$FFFE2041) AND (ls[5]=$4280247C)
        flg:=TRUE
      ELSEIF (ls[3]=$42E748E7) AND (ls[4]=$FFFE49FA) AND (ls[5]=$02AA2941)
        flg:=TRUE
      ENDIF
      IF flg
        sc:=ls+$544-$214
        WHILE ((sc[]<>$44DF4EF9) AND (sc>ls)); SUBQ.L #2,sc;  ENDWHILE
        IF sc[]=$44DF4EF9
          PutLong(dosbase-$96+2,Long(sc+4))
          db.flags OR LIBF_CHANGED; SumLibrary(db)
          CacheClearU()
          Permit()
          IF Not(q)
            PrintF('found and killed! [1]\n')
          ENDIF
          RETURN TRUE
        ENDIF
      ENDIF
    ENDIF
  ENDIF

  -> method 2:

  PutLong({km_killed},0)
  PutLong({km_olpatch},SetFunction(execbase,-$228,{km_olpatchcode}))
  CacheClearU()
  UnLoadSeg(LoadSeg({km_lsfile}))
  SetFunction(execbase,-$228,Long({km_olpatch}))
  CacheClearU()

  Permit()

  IF Long({km_killed})
    IF Not(q)
      PrintF('found and killed! [2]\n')
    ENDIF
    RETURN TRUE
  ENDIF

  IF Not(q)
    PrintF('not found.\n')
    IF array[ARG_NI]
      PrintF('This doesn\at mean it couldn\at be active! Use INSTALL to really\nget rid of it. INSTALL-killing should always work.\n')
    ENDIF
  ENDIF

  RETURN FALSE

  km_olpatch:
  LONG    0
  km_killed:
  LONG    0

  km_olpatchcode:
  MOVE.L  A1,D1
  CMP.L   A4,D1
  BNE     km_notmagic
  LEA     km_lsfile(PC),A0
  MOVE.L  A0,D1
  CMP.L   $10(A4),D1
  BNE     km_notmagic

  MOVE.L  (A7),A0
  CMP.L   #$2940000C,(A0)+
  BNE     km_doa2
  CMP.L   #$2C40222C,(A0)+
  BNE     km_doa2
  CMP.L   #$001074FE,(A0)+
  BNE     km_doa2

  MOVEQ   #31,D1        -> Disable Lock() to produce error
  km_killlock:
  CMP.L   #$4EAEFFAC,(A0)
  BNE.B   km_kl_notcmp
  MOVE.L  #$4E717000,(A0)
  km_kl_notcmp:
  ADDQ.L  #2,A0
  DBF     D1,km_killlock

  LEA     620(A0),A0     ->$660-$3B4-32*2
  MOVE.W  #511,D1        -> 1024/2-1
  km_find11:
  CMP.L   #$44DF4EF9,(A0)
  BEQ.B   km_found11
  SUBQ.L  #2,A0
  DBF     D1,km_find11
  BRA     km_notmagic
  km_found11:
  ADDQ.L  #2,A0

  MOVEM.L D0/A1-A2,-(A7)
  MOVE.L  A0,A2
  MOVE.W  #511,D1        -> 1024/2-1
  km_scan11:
  CMP.L   #$42E748E7,(A2)
  BNE.B   km_goscan11
  CMP.L   #$FFFE49FA,4(A2)
  BNE.B   km_goscan11
  CMP.L   #$02AA2941,8(A2)
  BNE.B   km_goscan11
  SUBQ.L  #4,A2
  BRA     km_kill
  km_goscan11:
  SUBQ.L  #2,A2
  DBF     D1,km_scan11
  MOVEM.L (A7)+,D0/A1-A2
  BRA     km_notmagic

  km_doa2:
  MOVEM.L D0/A1,-(A7)
  MOVE.L  A2,A1
  LEA     -$40(A1),A1
  JSR     TypeOfMem(A6)
  TST.L   D0
  MOVEM.L (A7)+,D0/A1
  BEQ     km_notmagic
  CMP.L   #$42E748E7,4(A2)
  BNE     km_notmagic
  CMP.L   #$FFFE2041,8(A2)
  BNE     km_notmagic
  CMP.L   #$4280247C,12(A2)
  BNE     km_notmagic
  CMP.L   #"ATCH",-4(A2)
  BNE     km_notmagic
  CMP.L   #"SYSP",-8(A2)
  BNE.B   km_notmagic
  CMP.L   #$4E75600C,-12(A2)
  BNE.B   km_notmagic

  LEA     $330(A2),A0    -> $544-$214
  MOVE.W  #407,D1        -> ($544-$214)/2-1
  km_find:
  CMP.L   #$44DF4EF9,(A0)
  BEQ.B   km_found
  SUBQ.L  #2,A0
  DBF     D1,km_find
  BRA.B   km_notmagic
  km_found:
  ADDQ.L  #2,A0

  -> kill it!
  MOVEM.L D0/A1-A2,-(A7)
  km_kill:
  JSR     Forbid(A6)
  LEA     -10(A2),A1
  MOVE.W  (A0),(A1)+
  MOVE.L  2(A0),(A1)+
  MOVE.L  #"KILL",D0
  MOVE.L  D0,(A1)+
  MOVE.L  D0,(A1)+
  LEA     4(A2),A1
  MOVE.W  (A0),(A1)+
  MOVE.L  2(A0),(A1)+
  MOVE.L  D0,(A1)+
  MOVE.L  D0,(A1)+
  JSR     CacheClearU(A6)
  JSR     Permit(A6)
  LEA     km_killed(PC),A0
  ST      (A0)
  MOVEM.L (A7)+,D0/A1-A2
  MOVEQ   #0,D0
  RTS

  km_notmagic:
  MOVE.L  km_olpatch(PC),A0
  JMP     (A0)

  km_lsfile:
  CHAR    'kbvirusfakeloadsegcallb',0

ENDPROC


PROC rereloc(buf:PTR TO LONG)
  DEF nhunks, id

  nhunks:=last-first+1

  buffer:=buf
  REPEAT
    id:=buffer[] AND $1FFFFFFF
    ->PrintF('buf: $\h[08], HUNK: $\h[08]:\d\n',buffer,id,buffer[1])
    SELECT id
      CASE HUNK_CODE;         handle(0)
      CASE HUNK_DATA;         handle(0)
      CASE HUNK_BSS;          handle(1)
      CASE HUNK_DEBUG;        handle(0)
      CASE HUNK_NAME;         handle(0)
      CASE HUNK_RELOC32;      handle(2)
      CASE HUNK_SYMBOL;       handle(5)  -> not tested
      CASE HUNK_RELOC32SHORT; handle(3)  -> not tested
      CASE HUNK_DREL32;       handle(3)  -> not tested
      CASE HUNK_RELRELOC32;   handle(4)  -> not tested
      CASE HUNK_END;
        ADDQ.L #4,buffer
        ofs:=1
      CASE HUNK_UNIT; printrr('UNIT')
      CASE HUNK_RELOC16; printrr('RELOC16')
      CASE HUNK_RELOC8; printrr('RELOC8')
      CASE HUNK_EXT; printrr('EXT')
      CASE HUNK_OVERLAY; printrr('OVERLAY')
      CASE HUNK_BREAK; printrr('BREAK')
      CASE HUNK_DREL16; printrr('DREL16')
      CASE HUNK_DREL8; printrr('DREL8')
      CASE HUNK_LIB; printrr('LIB')
      CASE HUNK_INDEX; printrr('INDEX')
      CASE HUNK_ABSRELOC16; printrr('ABSRELOC16')

      DEFAULT;
        IF Not(q)
          PrintF('Unknown hunk $\h[08]!',id); badprint('')
        ENDIF
    ENDSELECT
  UNTIL (buffer>=end) OR CtrlC()
  IF buffer=end THEN RETURN TRUE
ENDPROC FALSE

PROC handle(m)
  SELECT m
    CASE 0;
      IF buffer[1]>$10000000 THEN RETURN
      buffer:=buffer[1]*4+buffer+8
      IF buffer>=end
        IF Not(q) THEN badprint('Hunk too big!')
        RETURN
      ENDIF
    CASE 1; ADDQ.L #8,buffer
    CASE 2; dorel(4)
    CASE 3; dorel(2)
    CASE 4; dorel(4)
    CASE 5; symskip()
  ENDSELECT
ENDPROC

PROC symskip()
  ADDQ.L #4,buffer

    WHILE buffer[]
      buffer:=buffer+buffer[]*4+8
      IF buffer>=end
        IF Not(q) THEN badprint('Symboltable out of executable!')
        RETURN
      ENDIF
    ENDWHILE
    ADDQ.L  #4,buffer
ENDPROC

PROC badprint(str)
  IF Not(q) THEN PrintF('\s\nThe executable may be unrepairable.\n',str)
ENDPROC
PROC printrr(hunk)
  IF Not(q) THEN PrintF('Can\at handle HUNK_\s\n',hunk)
ENDPROC

PROC dorel(m)
  ADDQ.L #4,buffer
  IF m=4
    WHILE buffer[]
      ->PrintF(': buf $\h[08]\n',buffer)
      ->PrintF(': \d 32bitrelocs to hunk \d.\n',buffer[],buffer[1])
      IF (buffer[1]<(first+ofs)) OR (buffer[1]>last)
        IF Not(q) THEN badprint('Reloc to unknown hunk!')
        RETURN
      ENDIF
      IF buffer[1]>=(first+ofs) THEN buffer[1]:=buffer[1]-1
      buffer:=buffer[]*4+buffer+8

      IF buffer>=end
        IF Not(q) THEN badprint('Reloctable out of executable!')
        RETURN
      ENDIF

    ENDWHILE
    ADDQ.L  #4,buffer
  ELSE
    WHILE Int(buffer)

      IF Not(q) THEN PrintF(': \d 16bitrelocs to hunk \d.\n',Int(buffer),Int(buffer+2))

      IF (Int(buffer+2)<(first+ofs)) OR (Int(buffer+2)>last)
        IF Not(q) THEN badprint('Reloc to unknown hunk!')
        RETURN
      ENDIF
      IF Int(buffer+2)>=(first+ofs) THEN PutInt(buffer+2,Int(buffer+2)-1)
      buffer:=Int(buffer)*2+buffer+4
      IF buffer>=end
        IF Not(q) THEN badprint('Reloctable out of executable!')
        RETURN
      ENDIF
    ENDWHILE
    buffer:=(buffer+3) AND -4

    IF Not(q) THEN PrintF('Relocs done at $\h[08]!\n',buffer)
  ENDIF
ENDPROC


PROC fixexe(buf:PTR TO LONG,len,filename)
  DEF nhunks, b:PTR TO LONG, c:PTR TO LONG,
      r,x:PTR TO LONG,y:PTR TO LONG,strip,herr

  IF Not(testexestruct(buf,len,filename)) THEN RETURN NIL

  end:=buf+len

  nhunks:=(last:=buf[4])-(first:=buf[3])+1
  b:=buf+20+(nhunks*4)
  c:=b+(strip:=32+(b[1]*4))

  IF rereloc(b)
    buf[2]:=buf[2]-1; buf[4]:=buf[4]-1
    nhunks:=(last:=buf[4])-(first:=buf[3])+1
    x:=buf+20; y:=buf+24
    MOVE.L nhunks,D0
    ->SUBQ.L #1,D0
    MOVE.L y,A0
    MOVE.L x,A1
    cloop1:
    MOVE.L (A0)+,(A1)+
    SUBQ.L #1,D0
    BNE.B  cloop1

    b:=buf+20+(nhunks*4)
    MOVE.L end,D0
    MOVE.L c,A0
    MOVE.L b,A1
    SUB.L  A0,D0
    LSR.L  #2,D0
    cloop2:
    MOVE.L (A0)+,(A1)+
    SUBQ.L #1,D0
    BNE.B  cloop2

    RETURN len-strip-4

  ELSE
    IF Not(q) THEN PrintF('Error removing hunk 0.\n')
  ENDIF
ENDPROC NIL

PROC testexestruct(buf,len,filename)
  DEF herr
  IF xfd
    IF (herr:=XfdTestHunkStructureFlags(buf,len,XFDTHF_NOOVERLAYS))<>XFDERR_OK
      IF herr=XFDERR_NOOVERLAYS THEN RETURN TRUE
      IF (herr=$2000) OR (herr AND $1000)
        IF Not(q) THEN PrintF('Hunk structure of \e[1m\s\e[22m executable is damaged!\nPlease delete this file.\n',filename)
      ELSE
        xfdprinterr('XFD error: ',herr)
      ENDIF
      xprint:=TRUE
      RETURN FALSE
    ENDIF
  ENDIF
ENDPROC TRUE

PROC infectedexe(buf:PTR TO LONG,len,filename)
  DEF nhunks,b:PTR TO LONG, c:PTR TO LONG, hid

  IF Not(testexestruct(buf,len,filename)) THEN RETURN NIL

  IF (buf[]=HUNK_HEADER) AND (buf[1]=0)
    nhunks:=buf[4]-buf[3]+1
    IF (nhunks>1) AND (nhunks<512)
      b:=buf+20+(nhunks*4)
      hid:=b[] AND $1FFFFFFF
      IF ((hid=HUNK_CODE) OR (hid=HUNK_DATA)) AND ((b[2] AND $FFFFFEFF)=$48E7FEFE)
        c:=b+8+(b[1]*4)
        IF (c[]=HUNK_RELOC32) AND (c[1]=1) AND (c[2]=1) AND (c[4]=0) AND (c[5]=HUNK_END)
          IF (b[2]=$48E7FFFE)
            -> BOKOR 1.01
            IF (b[1]>300) AND (b[1]<367) AND (c[3]=$1C0)
              RETURN 'BOKOR V1.01'
            ENDIF
            -> BOKOR 1.05
            IF (b[1]>=367) AND (b[1]<=368+8) AND (c[3]=$22C)
              RETURN 'BOKOR V1.05'
            ENDIF
            -> BOKOR 1.06
            IF (b[1]>=367) AND (b[1]<=372+8) AND (c[3]=$230)
              RETURN 'BOKOR V1.06'
            ENDIF
            -> BOKOR 1.0?
            IF ((b[1]>300) AND (b[1]<400))
              IF (c[3]>$1A0) AND (c[3]<$280)
                IF Not(q)
                  PrintF('\nUnknown BOKOR v1.0x clone detected! Please send this (\s) file to me!\n\n',filename)
                ENDIF
                RETURN 'BOKOR V1.0x'
              ENDIF
            ENDIF
          ELSEIF (b[2]=$48E7FEFE)
            -> "Bokor V1.1!!"
            IF (b[1]>=400) AND (b[1]<=432) AND ((c[3]>=$2D0) AND (c[3]<=$340))
              RETURN 'Bokor V1.1!!'
            ENDIF

            -> BOKOR 1.0?
            IF ((b[1]>380) AND (b[1]<500))
              IF (c[3]>$280) AND (c[3]<$380)
                IF Not(q)
                  PrintF('\nUnknown BOKOR v1.x clone detected! Please send this (\s) file to me!\n\n',filename)
                ENDIF
                RETURN 'BOKOR V1.x'
              ENDIF
            ENDIF

          ENDIF
        ENDIF
      ENDIF
    ENDIF
  ENDIF
ENDPROC NIL

PROC validexe(buf:PTR TO LONG)
  DEF nhunks,b:PTR TO LONG, hid
  IF (buf[]=HUNK_HEADER) AND (buf[1]=0)
    nhunks:=buf[4]-buf[3]+1
    IF (nhunks>0) AND (nhunks<100)
      b:=buf+20+(nhunks*4)
      hid:=b[] AND $1FFFFFFF
      IF (hid=HUNK_CODE) OR (hid=HUNK_DATA)
        RETURN TRUE
      ENDIF
    ENDIF
  ENDIF
ENDPROC FALSE

PROC scanfile(fib:PTR TO fileinfoblock)
  DEF rfh=NIL,wfh=NIL, buf:PTR TO LONG,flen,nlen,succ=FALSE
  DEF filename,prot,comment,owner,slen,vname,comm[60]:STRING

  filename:=fib.filename; prot:=fib.protection; comment:=fib.comment
  owner:=Shl(fib.owneruid,16) OR fib.ownergid
  IF ((flen:=FileLength(filename))>524288) OR (flen<1024) THEN RETURN
  IF (rfh:=Open(filename,OLDFILE))
    xprint:=FALSE
    count++
    IF Read(rfh,xa,512)=512
      Seek(rfh,0,OFFSET_BEGINNING)
      IF validexe(xa)
        slen:=flen
        IF (buf:=AllocMem(flen,MEMF_ANY))
          IF Read(rfh,buf,flen)=flen
            Close(rfh); rfh:=NIL

            buf,flen,slen:=decrunch_buf(buf,flen,FALSE,filename)

            IF buf
              IF vname:=infectedexe(buf,slen,filename)
                vcount++
                xprint:=TRUE
                IF Not(q) THEN PrintF('\e[1m\s\e[22m infected by \e[32m\s\e[39m ',filename,vname)
                IF f
                  StringF(n1,'\skbva',filename)
                  IF (wfh:=Open(n1,NEWFILE))
                    IF (nlen:=fixexe(buf,slen,filename))
                      IF Write(wfh,buf,nlen)=nlen
                        Close(wfh); wfh:=NIL
                        SetProtection(filename,prot)
                        SetComment(filename,comment)
                        SetOwner(filename,owner)   -> V37 ignores this
                        StringF(n2,'\skbvb',filename)
                        IF Rename(filename,n2)
                          IF Rename(n1,filename)
                            DeleteFile(n2)
                            fcount++
                            succ:=TRUE
                            IF Not(q) THEN PrintF('Fixed!')
                          ELSE
                            Rename(filename,n1)
                            Rename(n2,filename)
                          ENDIF
                        ELSE
                          Rename(n2,filename)
                        ENDIF
                      ELSE
                        IF Not(q) THEN PrintF('Can\at write!')
                      ENDIF
                    ENDIF
                    IF Not(succ)
                      SetProtection(filename,prot OR FIBF_EXECUTE)
                      StringF(comm,'\s virus infected file!',vname)
                      SetComment(filename,comm)
                    ENDIF
                    IF wfh THEN Close(wfh)
                    IF Not(succ)
                      DeleteFile(n1)
                    ENDIF
                  ELSE
                    IF Not(q) THEN PrintF('Can\at open new file!')
                  ENDIF
                ELSE
                  PrintF('Use FIXFILES switch to fix!')
                ENDIF
                IF Not(q) THEN PrintF('\n')
              ENDIF
            ENDIF
          ELSE
            IF Not(q) THEN PrintF('Can\at read!')
          ENDIF

          FreeMem(buf,slen)

        ELSE
          IF Not(q) THEN PrintF('No memory!')
        ENDIF
      ENDIF
    ELSE
      IF Not(q) THEN PrintF('Can\at read!')
    ENDIF
    IF rfh THEN Close(rfh)

    IF xprint AND Not(q) THEN PrintF('\n')

  ELSE
    IF Not(q) THEN PrintF('Can\at open file!\n')
  ENDIF
ENDPROC


-> dirwalker stuff

PROC scan(dir)
  DEF lock, dlock, fib:fileinfoblock, notfile=TRUE

  IF (lock:=Lock(dir,ACCESS_READ))
    IF Examine(lock,fib)
      IF fib.direntrytype<0
        IF Not(q) THEN PrintF('\nChecking file \a\s\a...\n',dir)
        dlock:=ParentDir(lock)
        UnLock(lock);
        lock:=CurrentDir(dlock)

        hook(0,fib)

        CurrentDir(lock); lock:=0
        UnLock(dlock)
        tcount:=tcount+count
        tvcount:=tvcount+vcount
        tfcount:=tfcount+fcount

        notfile:=FALSE
      ENDIF
    ELSE
      UnLock(lock)
      IF Not(q)
        PrintF('Couldn\at exemine \a\s\a!\n',dir)
        PrintFault(IoErr(),'kbVirus')
      ENDIF
      RETURN FALSE
    ENDIF
    IF lock THEN UnLock(lock)
  ELSE
    IF Not(q)
      PrintF('Couldn\at lock \a\s\a!\n',dir)
      PrintFault(IoErr(),'kbVirus')
    ENDIF
    RETURN FALSE
  ENDIF
  IF notfile
    IF Not(q) THEN PrintF('\nScanning directory \a\s\a...\n',dir)
    IF dw_WalkName(dir,0 /*DWF_FOLLOWHARD OR DWF_FOLLOWSOFT*/,initHook())
      IF Not(q) THEN PrintF('\s[50]\bDirectory done, \d files, \d viruses found, \d files fixed (\d%).\n','',count,vcount,fcount,IF vcount=0 THEN 0 ELSE fcount*100/vcount)
      tcount:=tcount+count
      tvcount:=tvcount+vcount
      tfcount:=tfcount+fcount
      count:=0; vcount:=0; fcount:=0
    ELSE
      IF Not(q)
        PrintF('\nCouldn\at scan directory!\n')
        PrintFault(IoErr(),'kbVirus')
      ENDIF
      RETURN FALSE
    ENDIF
  ENDIF
ENDPROC TRUE

/*
;      IN: d0=level (0 to n), a0=fib
;     OUT: d0=return <0 if you want to break walking, 0 if you don't want to
;          enter more levels, or >0 if you want to proceed normally.
*/
PROC hook(level,fib:PTR TO fileinfoblock)
  DEF et,r,typ

  IF CtrlC()
    IF Not(q); PrintF('\n***Break\n'); ENDIF
    RETURN -1
  ENDIF

  et:=fib.direntrytype

  IF (et<0) AND (patf<>0)
    IF MatchPatternNoCase(pat,fib.filename)=FALSE
      RETURN 1
    ENDIF
  ENDIF

  IF Not(q) AND (level>0) AND (et>0)
    FOR r:=0 TO level; PrintF('  '); ENDFOR
  ENDIF
  SELECT et
    CASE ST_ROOT
      typ:='(rootdir)\n'
    CASE ST_USERDIR
      typ:='(dir)\n'
    CASE ST_LINKDIR
      typ:='(dir) <hl>\n'
    CASE ST_SOFTLINK
      typ:='(dir/file) <sl>\n'
      ->scanfile(fib)
    CASE ST_FILE
      ->file
      typ:=''
      scanfile(fib)
    CASE ST_LINKFILE
      typ:=' <hl> '
      scanfile(fib)
    DEFAULT
      typ:='\n'
  ENDSELECT
  IF Not(q) AND (et>0) THEN PrintF('\l\s \s\s[50]',fib.filename,typ,'')

  IF Not(q) AND (et>0) THEN PrintF('\b')
ENDPROC 1


-> Internal stuff follows:

PROC initHook()
  DEF ret
  LEA    hookdata(PC),A2
  MOVE.L A4,(A2)+
  MOVE.L A5,(A2)+
  LEA    funcarraydata(PC),A2
  MOVE.L A2,ret
  LEA    hookcode(PC),A0
  MOVE.L A0,(A2)+
  LEA    hookdata(PC),A0
  MOVE.L A0,(A2)+
ENDPROC ret

/*
;     hookcode:
;      IN: d0=level (0 to n), a0=fib, a1=HookData, a6=DosBase
;     OUT: d0=return <0 if you want to break walking, 0 if you don't want to
;          enter more levels, or >0 if you want to proceed normally.
;     d0/d1/a0/a1 can be trashed.
*/
hookcode:
  MOVEM.L D2-D7/A2-A6,-(A7)
  MOVEM.L (A1)+,A4/A5
  MOVE.L  D0,hookg1
  MOVE.L  A0,hookg2
  hookg1:=hook(hookg1,hookg2)
  MOVE.L  hookg1,D0
  MOVEM.L (A7)+,D2-D7/A2-A6
  RTS

hookdata:
  LONG 0,0    -> A4/A5

funcarraydata:
  LONG 0,0    -> hookcode, hookdata
