Anti-reversing tricks have been around for a long time. They most commonly occur in malware or spyware applications. However, in recent times more applications are incorporating them into their code. This might be to thwart reversing of intellectual property, or perhaps modification of the binary at run time. So today we take a quick look at some of the most common categories anti-reversing techniques fall into, and a few examples from each type.
MindshaRE is our weekly look at some simple reverse engineering tips and tricks. The goal is to keep things small and discuss every day aspects of reversing. You can view previous entries here by going through our blog history.
Anti-reversing tricks are pieces of code added to a binary that deter a disassembler or debugger. In general they serve no other purpose to the execution of the program. These tricks generally fall into several categories that we will cover. It is a good idea to have a cursory knowledge of several anti-reversing tricks in case you come into contact with them. If you are doing malware reversing then knowing these is imperative. Otherwise you could find yourself wasting hours going in circles.
I have come up with the following categories of anti reversing tricks. I am aware that there are many more, but in general these are the ones I see most often. Look for a more complete list and discussion in the links following this post.
- Debugger Presence Detection -
Detecting the presence of the debugger is probably the number one method of anti-reversing. This is done via Microsoft APIs, internal structure flags, window titles, and anything that might tell an application it is being debugged. Lets first look at some of the Windows API calls that will give us up quickly.
IsDebuggerPresent() is the most common API that is used to detect the presence of a debugger. It is the easiest to use, returning true if we have a debugger connected, or false if we do not. Here is an example.
004012E1 call IsDebuggerPresent
004012E6 test eax, eax
004012E8 jnz DebuggerPresent
An obvious way to get around this is to patch any jump that occurs when this returns true. The problem with patching in this manner is you may be doing it hundreds of times. Lets look at what the function IsDebuggerPresent in kernel32.dll does.
7C813093 IsDebuggerPresent proc near
7C813093 mov eax, large fs:18h
7C813099 mov eax, [eax+30h]
7C81309C movzx eax, byte ptr [eax+2]
7C8130A0 retn
7C8130A0 IsDebuggerPresent endp
A little understanding of the windows internals tells us that fs:18h is a pointer to our current threads TEB data structure. Knowing this we can see that TEB+0x30 is a pointer to our process PEB. I know this is a hassle, but at PEB+0x2 we can see what is being checked by IsDebuggerPresent.
0:002> dt _TEB 7ffdd000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x7ffdb000 _PEB
0:002> dt _PEB 0x7ffdb000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
We can easily verify this like so
0:002> db poi(7ffdd000+0x30)+0x2 L1
7ffdb002 01
BeingDebugged is a flag that is set when a debugger loads, or attaches, to a process. This is much easier to fix on a global scale. In our debugger we can simply load/attach to a process and at initial breakpoint do the following.
0:002> eb poi(7ffdd000+0x30)+0x2 0x0
0:002> dt _PEB 0x7ffdb000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
That will fix any calls to IsDebuggerPresent, or any checking of the internal PEB structure.
CheckRemoteDebuggerPresent is another API that is frequently used to check if a debugger is attached. This one works slightly different than IsDebuggerPresent. It takes a handle to a process (can be current process or remote process) and returns the value in the pointer passed to the function. The way it works is by gathering process information via NtQueryInformationProcess.
7C859B35 lea eax, [ebp+arg_0]
7C859B38 push eax
7C859B39 push 7
7C859B3B push [ebp+arg_0]
7C859B3E call ds:_imp__NtQueryInformationProcess
This call will request the PROCESSINFOCLASS.ProcessDebugPort dword from the kernel. If we have a debugger attached this will obviously not be zero. Since this is handled by the kernel defeating it may be a little harder and might include patching, or running with a modified kernel32.dll (Which is what I have done before).
Another, more novel, way debuggers are detected is by checking for the presence of the debugger applications window name. This sounds cheesy but it happens sometimes. Calls to FindWindow() with the target debuggers window class name can tell the application if the specified window exists. This is not as efficient as the others, since you have to specify the proper window name for each debugger you want to detect, but it is something to look out for.
- Timing checks -
Timing checks involve the checking of a time at a certain point in the binary with another later in execution. The point of this is to utilize micro timers like clock cycles to detect that the process has paused for a fraction of time. This can be very useful when dealing with clock cycles. Breaking into the debugger alone will cause these values to be off quite a bit. This can be done at say startup, and then each time a function is called it can check the delta for a possible debugger. One example is the use of the Windows API GetTickCount() to grab the time since startup in milliseconds. Pausing in execution will cause this value to skew since the tick count continues even when the debugger is broken in.
The one I see more often is the Intel instruction rdtsc (read double time stamp counter). This instruction will pull off the clock cycles that have occurred since boot. The low dword is stored in EAX and the high dword of this 64 bit number is stored in EDX. The precision of this lends itself well to detecting how long execution has taken. Lets look at an example of this.
Beginning of process execution.
xor eax, eax
xor edx, edx
rdtsc
mov dword_High, edx
mov dword_Low, eax
Later in process execution
xor eax, eax
xor edx, edx
rdtsc
sub edx, dword_High
cmp edx, dword_Delta
jnb DebuggerPresent
This example is obviously contrived, but in general this is how the timer works when detecting a debugger.
- Binary Modification Checks -
Binary modification happens all the time in a binary. For instance when setting a break point in a function. In a debugger you cannot tell that anything is different about the function, but in memory we are in fact modifying the instructions in the program. As you may know break points are instructions inserted at the desired address (int3). When execution hits this instruction it tells the process to break into a debugger. So behind the scenes we have modified the code portion of a binary by inserting an interrupt instruction.
This is good for people trying to detect the presence of someone debugging the program, and people that may have patched the binary. This is done by having a checksum for the code section of a binary and an associated routine for calculating and detecting if a function has been modified (via patching or a break point).
Another form of detecting writes or modifications of data is through guard pages, or page permissions. Often times the application will set a guard page on the code section and any writes to that page will call through the SEH chain. In most cases this is a minor nuisance, but an effective way of determining code section writing.
- Anti-Disassembling -
While most anti-reversing tricks are focused on live analysis they may also try and trick a static disassembler such as IDA. A good example of this is inserting a jump in a function to a location that contains a valid instruction. The problem occurs when IDA does its analysis. When IDA finds a jump, and a valid instruction at that jump, it will create a cross reference and disassemble the destination as code. The problem is that during execution the jump will not be taken, and in fact the instruction being executed is at a different address. This is easier to see in an example.
004010C0 sub_4010C0 proc near
004010C0 push eax
004010C1 push edx
004010C2 xor eax, eax
004010C4 jnz short loc_4010DB
004010C6 push 2DC431h
004010CB pop eax
004010CC sub eax, 0DB353h
004010D1 ror eax, 10h
004010D4 xor al, 60h
004010D6 ror eax, 10h
004010D9 jmp eax ; 004010de
004010DB
004010DB loc_4010DB:
004010DB mov eax, 310FD3FFh
004010E0 and edx, 0FFh
To IDA, this makes perfect sense. We have a cross reference to loc_4010db and it disassembles to a valid instruction. That has to be right, right? No not really. The jump will never be taken because the xor will always be 0. This should raise a red flag when you encounter an impossible branch. If we follow it to the next branch (jmp eax) we can see what is really happening when the function is being executed. I have added the comment above after calculating eax which takes us to the proper branch at 0x004010de. As we can see this doesn't exists in IDA so we need to fix it. Put your cursor over the invalid instructions at 0x004010db and hit "U" (Undefine). This will turn all the code back into their byte representations. Lets move to the real branch destination at 0x004010de and hit "C" (Make Code). We should see the proper instructions appear like this.
004010D1 ror eax, 10h
004010D4 xor al, 60h
004010D6 ror eax, 10h
004010D9 jmp eax ; 004010de
004010D9 sub_4010C0 endp
004010D9
004010D9 ; -----------------------------------
004010DB unk_4010DB db 0B8h ;
004010DC db 0FFh
004010DD db 0D3h ;
004010DE ; -----------------------------------
004010DE rdtsc
004010E0 and edx, 0FFh
004010E6 mov eax, edx
004010E8 shr edx, 8
If you want you can go and fix up the function and make it all pretty, but in this case I didn't bother. This is a very typical anti-disassembler trick, and can occur in many different forms.
We have covered just a small snippet of anti-reversing techniques. This is always a fun area of reverse engineering because its an arms race. New tricks are invented every day, and new anti-tricks are discovered in response. I certainly had to cut this discussion short as it is a much larger topic than I can handle in a single post. I suggest reading through the following links to get a more in depth dissection of the most common anti-reversing techniques.
http://www.securityfocus.com/infocus/1893
http://pferrie.tripod.com/papers/unpackers.pdf
I hope you enjoyed this weeks MindshaRE.
-Cody
