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:
- Because there are so many almost-but-not-quite-the-same patches, the function is huge for what it really does
- All of the repeated memcmp's are basically an unrolled loop that shouldn't have been unrolled. Is there a clean way to do this with the C-style (no STL) C++ you have to use in drivers? The problem is that the patches are of variable size, so you can't make a 2D array with constant dimensions
- The patch is one way - if you want the rat behaviour back, you need to reboot :)