(This mini-entry is dedicated to Mr. eXoDia. He wanted me to write about kernelmode, so here goes..)
Remember Themida 1.0.0.5 that came with a driver? It caused vast majority of BSODs on my PC. Remember the Sony DRM rootkit that came with some CDs? Remember adware that was impossible to remove, because driver was protecting it?
Apparently, even Microsoft noticed the bad quality drivers and has done a lot in recent years to stop the plague. Drivers now need to be signed - and before Microsoft signs them, they must pass plenty of automatic tests.
Big companies still manage to write crap drivers - but that's a topic for another post..
Of course, sometimes reversers need drivers, too. For example, to hide a debugger from the target. Enter TitanHide, an open-source project by Mr. eXoDia:
TitanHide is a driver intended to hide debuggers from certain processes. The driver hooks various Nt* kernel functions (using SSDT table hooks) and modifies the return values of the original functions
It all sounds great and peachy, but how good this driver really is?
Crash, boom, bang
After few minutes of browsing the code, I was already having fun. How about this?
Cause of the problem
Take a look at this code:
static NTSTATUS NTAPI HookNtSetContextThread( IN HANDLE ThreadHandle, IN PCONTEXT Context) { ULONG pid = (ULONG)PsGetCurrentProcessId(); bool IsHidden = Hider::IsHidden(pid, HideNtSetContextThread); ULONG OriginalContextFlags = 0; if (Context && IsHidden) { Log("[TITANHIDE] NtSetContextThread by %d\n", pid); OriginalContextFlags = Context->ContextFlags; Context->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS; } NTSTATUS ret = Undocumented::NtSetContextThread(ThreadHandle, Context); if (Context && IsHidden) Context->ContextFlags = OriginalContextFlags; return ret; }
This is a great example how not to implement hooks.
Attacker has full control over the value passed as Context.
- Lines 1-4 check if we want to hide debugger from this process.
- Lines 7-8 take the unchecked Context value and stuff some data into it.
- And only then the data are passed to the original NtSetContextThread function which does proper validation.
Ouch! I'm pretty sure I could turn it into full-featured ring-0 code execution, but that's not the point of the exercise.
But what if the source is not available?
In this case it was really easy to find a bug, because full source was available. When the source is not available, you can use specialized fuzzing tools, like BSODHook or IOCTLBF.
Here's BSODHook output showing the same bug:
We can take a detailed look at these tools later, in a different blog post.
Sample executable showing the bug: https://www.mediafire.com/?1191p9mcfbks9sq - try running it in Olly with TitanHide enabled.
P.S. ZIP filesize=666 is accidental, I swear!
P.P.S. One more issue with TitanHide - build 0012 doesn't work on XP anymore. đ