mrexodia / TitanHide

Hiding kernel-driver for x86/x64.
MIT License
2.11k stars 420 forks source link

Kernel debugger presence leakage through KUSER_SHARED_DATA #18

Open Mattiwatti opened 7 years ago

Mattiwatti commented 7 years ago

The following will detect a kernel debugger on any system running Windows 2000 or later:

#include <stdio.h>
#include <conio.h>
int main(int argc, char* argv[])
{
    unsigned char b = *(unsigned char*)0x7ffe02d4;
    if ((b & 0x01) || (b & 0x02))
        printf("Kernel debugger detected!\n");
    else
        printf("No kernel debugger detected\n");
    _getch();
    return 0;
}

The headers and CRT functions are only there to make the output readable; if you want you can turn this into a one liner that doesn't use any syscall or even a single DLL import!

So how does it work? Well, 0x7ffe02d4 is actually 0x7ffe0000 + 0x2d4. 0x7ffe0000 is the fixed user mode address of the KUSER_SHARED_DATA structure that contains data that is shared between user mode and the kernel (though user mode doesn't have write access to it). I stumbled upon this struct in the Windows Research Kernel sources, but it's actually part of the DDK and even mostly-commented (some fields better than others). The struct has some interesting properties: (a) its address is fixed and has been in all Windows versions since it was introduced. (b) its user mode address is the same in 32 bit and 64 bit mode. (c) all offsets and sizes are strictly fixed, and new fields are only ever appended or added in place of unused padding space. Hence why this program will work in 32 bit Windows 2000 and 64 bit Windows 10 without recompiling.

I found some much more detailed documentation of the struct at http://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kuser_shared_data.htm. Other than that the struct seems to be relatively unknown (at least for anti-debug purposes), I didn't find many references to it other than people trying to use the time fields as jump pads for malware... The 'b' in the program at +0x2d4 is the 'BOOL KdDebuggerEnabled' field. When I found the struct I tested it with

BOOL KdDebuggerEnabled = *(BOOL*)SharedUserData->KdDebuggerEnabled; // exact same code as above

Since I am not a filthy Javascript programmer, I do all my boolean checks with strict type equality to avoid issues with MS's ever-changing giant forest of typedefs that change your program depending on which headers you include. I found that if (KdDebuggerEnabled == TRUE) evaluated to false on both my own machine and a debugged VM, so at first I thought it was just an outdated field kept for compatibility reasons. But closer inspection revealed that the value is actually a bitfield and not a boolean, of which the first two bits are set to 1 if a debugger is attached! So if you do if (KdDebuggerEnabled) like a pleb, the check will work! Microsoft rewarding laziness and bad form again.

After I found out about this (and then afterwards of course found the above link, where the bitfield factoid was already documented...) I tried to nuke the field at both its KM and UM address from a kernel module - only to find that it was immediately overwritten with 0x3 again. Then I remembered I had a kernel debugger and set a HWBP:

> 0: kd> ba w 1 0xFFFFF780000002D4
> 0: kd> g
> Breakpoint 0 hit
> kdbazis!KdReceivePacket+0x75f:
> fffff80000ba2bff 8b45e7          mov     eax,dword ptr [rbp-19h]
> 1: kd> kb
>  # RetAddr           : Args to Child                                                           : Call Site
> 00 fffff80000ba24bc : 0000000000000008 0000000000000001 0000000000000000 0000000000000000 : kdbazis!KdReceivePacket+0x75f
> 01 fffff80002885e1d : fffffa800410c280 fffff8a0003cf890 0000000000000000 fffffa8004284c30 : kdbazis!KdReceivePacket+0x1c
> 02 fffff8000288873f : 0000000000000001 fffff880009f1180 0000000000000002 000000000002625a : nt!KdPollBreakIn+0xec`

(kdbazis.dll is VirtualKD's replacement for kdcom.dll, I recommend it if you want to do kernel debugging in a VM without wanting to kill yourself) Trying this with kdcom.dll won't work, it seems to go into an infinite loop where it keeps sending itself messages that the breakpoint has been hit, which cause it to write to the breakpoint address, which triggers the breakpoint... etc. But the basic idea is the same regardless of which DLL you use: the debugger interface writes to the address on every KdReceivePacket, and kdcom.dll additionally writes to it in KdSendPacket as well.

The good news is that both DLLs can be patched not to do this with no negative consequences. Since VirtualKD is open source, patching it is left as an exercise for the reader... Patching kdcom.dll is more of a challenge, since it is one of the first modules to be loaded at boot and seems to have stricter code signing requirements than other drivers (test signing is not enough). So I found runtime patching to work best. This is possible from TitanHide and I've written a 'PatchKdCom()' to do this, but no PR yet as the code is quite messy and could use a review at least. Or maybe it's not even interesting enough to warrant adding; I've never seen anyone use KUSER_SHARED_DATA for anti-debug (as far as I'm aware anyway, but I'm definitely going to be setting more HWBPs in the future ;)).

Gist here: https://gist.github.com/anonymous/b5024c25634fc36e699cd9d041224531 Some issues:

mrexodia commented 7 years ago

Definitely very interesting! I pretty sure this isn't used because most people don't use a kernel debugger to do debug protections anyway but perhaps its an interesting trick to use...

Mattiwatti commented 7 years ago

That's true. I figured this might be of interest since TitanHide users are probably more likely to have a kernel debugger attached than most. It is possible to debug user mode applications using a kernel debugger, it's just very cumbersome and rarely gives any advantage over regular ring 3 debugging. But if I am making even minor changes to TitanHide for example, I will always have a debugger attached to the VM, if only to catch the BSODs and to have a usable log window.

Btw, I found that someone else had previously also found out about the magic 0x7ffe02d4, and found that it is actually used in ntdll!RtlIsAnyDebuggerPresent (which I had also never heard of...) RtlIsAnyDebuggerPresent checks PEB->IsBeingDebugged for the user mode process, and KUSER_SHARED_DATA->KdDebuggerEnabled for kernel mode, and returns true if either one is set.

mrexodia commented 7 years ago

As for the patching, probably some nifty macros could make the code easier to maintain or maybe even std::array? I think it would be a good addition but at the moment I don't really feel like updating TitanHide (mostly because I stopped using it myself 😄)