New IDA Stealth - Improved anti-anti-debugging techniques
IDA Stealth v1.0 Beta 3
This time there aren't as many changes as there were when Beta 2 was released. The new version primarily increases the stealthiness of some techniques. For example the NtQueryObject hook mistakenly assumed that all object names are zero terminated strings, which means that it could miss the DebugObject chunk and consequently would fail to zero out the object and handle count.
Another mistake has slipped in the NtQuerySystemInformation hook which returned STATUS_ACCESS_VIOLATION when it should have returned STATUS_INFO_LENGTH_MISMATCH in case the supplied buffer length was smaller than expected. I guess these are rather subtle bugs so let's elaborate on two more interesting examples of detecting a debugger even if all stealth options are enabled.
NtQueryInformationProcess hook (Improved)
An interesting trick to detect a hook of the NtQueryInformationProcess API and therefore an existing stealth plugin was described by metr0 at the Tuts4You baord. The idea is pretty simple: NtQueryInformationProcess can be used to check if a given process has an associated DebugPort. The function takes a handle to the process which is to be inspected. Now consider the case, that our debuggee itslef acts as a debugger and calls NtQueryInformationProcess with its own process handle. If the hook function doesn't check if the supplied handle refers to the current process, it will return zero, i.e. pretend that no debug port exists. But since the debuggee itself actually acts as a debugger it should have returned one!
The new version of IDA Stealth is aware of this technique and won't unveil its existence in this case.
NtClose revised - Old technique, new approach
The CloseHandle API, or more precisely NtClose, is known to cause an exception when invoked with an invalid or protected handle, provided a debugger is attached to the respective process. In order to swallow the exception, IDA Stealth used to hook NtClose so it could wrap the call to the original API into SEH or VEH, whichever was available. The problem with this is, that the exception isn't actually triggered by NtClose itself, but by a function which serves as a kernel-to-user callback, namely KiRaiseUserExceptionDispatcher. This callback just creates an exception record and calls RtlRaiseException, whereas the exception code is taken from the TEB at offset 0x1A4 (0xC0000008 in this case).
Obviously this can be easily transformed into a new anti-debugging technique. For instance one could insert code into KiRaiseUserExceptionDispatcher which would crash the application or do some other fancy stuff whenever the kernel calls back into user mode in order to raise an exception.
On the other hand, defeating this technique is just as easy as putting a 0xC3 byte at the beginning of the callback, thus suppressing the INVALID_HANDLE exception in the first place. This technique is actually used by the new version of IDA Stealth to completely defeat the NtClose anti-debugging mechanism.
Here's some C code which demonstrates this anti-debugging technique:
HMODULE hNtDll = LoadLibrary(L"ntdll.dll"); char* ptr = (char*)GetProcAddress(hNtDll, "KiRaiseUserExceptionDispatcher"); DWORD oldProtect; VirtualProtect(ptr, 5, PAGE_EXECUTE_READWRITE, &oldProtect); // write unconditional jump ptr[0] = 0xE9; *(int*)&ptr[1] = (int)((int)ExitProcess - (int)ptr - 5); FlushInstructionCache(GetCurrentProcess(), ptr, 5); // this will trigger KiRaiseUserExceptionDispatcher and hence close the process CloseHandle((HANDLE)0x12345);
The code snippet replaces the first instruction in KiRaiseUserExceptionDispatcher with an unconditional jump to the ExitProcess function, so as soon as an invalid handle is closed, the process will exit.
You might wonder if we are breaking a fundamental core component of the OS since IDA Stealth writes a ret instruction to the beginning of the function, effectively rendering the whole callback mechanism useless. Well, according to Nynaeve, NtClose is the only API which employs this callback mechanism to trigger a user mode exception from kernel mode, so messing around with this function hopefully won't cause any additional side effects.
NtClose not the only API
I didn't notice this until now. It's not true that NtClose is the only API to use the mechanism. Any API that accepts a handle (and thus calls into ObReferenceObjectByHandle) can potentially raise a user-mode exception. I just don't know (yet) how to trigger it.
Invalid Handle Exception
Hi Peter,
you are right. I did a quick reverse engineering of NtosKrnl.exe and I found out that KeRaiseUserException is called from a few other places. It appears that if you set NtGlobalFlag to 0x100 (i.e. FLG_APPLICATION_VERIFIER) the kernel function ExMapHandleToPointerEx raises an invalid handle exception by means of KeRaiseUserException.
This could just as well be a new anti debugging technique, because as it seems that function is invoked for anything mapping a handle to a pointer.
I tested this WaitForSingleObject and ReadFile with an invalid handle, and both times an exception was raised :)
Invalid Handle Exception
Well, it seems that I was jumping to conclusions too quickly: If you enable the application verifier flag, an exception will be raised no matter if a debugger is attached or not. So it isn't that easy actually.
Invalid Handle Exception
However, if you disable the exception by patching the KiRaiseUserException, then the absence of the exception reveals the presence of the debugger.