Open reliasn opened 7 years ago
Just adding more info.
WINEVENTPROC
is called from:
user32.dll!___ClientCallWinEventProc@4()
ntdll.dll!_KiUserCallbackDispatcher@12()
user32.dll!PeekMessageW()
ClientCallWinEventProc
isn't exported, but KiUserCallbackDispatcher
is. So you could hook it, access the HWND
from the 3rd argument and use NtUserQueryWindow
with WindowProcess
to see if it belongs to the debugger:
NTSTATUS __stdcall __ClientCallWinEventProc(int (__stdcall **a1)(int, int, int, int, int, int, int))
{
int (__stdcall *v1)(int, int, int, int, int, int, int); // ST18_4@1
int (__stdcall *v2)(int, int, int, int, int, int, int); // ST14_4@1
int (__stdcall *v3)(int, int, int, int, int, int, int); // ST10_4@1
int (__stdcall *v4)(int, int, int, int, int, int, int); // ST0C_4@1
int (__stdcall *v5)(int, int, int, int, int, int, int); // ST08_4@1
int (__stdcall *v6)(int, int, int, int, int, int, int); // ST04_4@1
int (__stdcall *v7)(int, int, int, int, int, int, int); // ST00_4@1
int (__stdcall *v8)(int, int, int, int, int, int, int); // esi@1
int Result; // [sp+8h] [bp-1Ch]@1
int v11; // [sp+10h] [bp-14h]@1
int v12; // [sp+18h] [bp-Ch]@1
int v13; // [sp+1Ch] [bp-8h]@1
v11 = 0;
v12 = 0;
v13 = 0;
v1 = a1[7];
v2 = a1[6];
v3 = a1[5];
v4 = a1[4];
v5 = a1[3]; // this is the HWND
v6 = a1[2];
v7 = a1[1];
v8 = *a1;
__guard_check_icall_fptr(*a1);
Result = v8(v7, v6, v5, v4, v3, v2, v1);
return NtCallbackReturn(&Result, 0x18u, 0);
}
Since KiUserCallbackDispatcher
can also call other types of callbacks, you might need to filter the ApiNumber in the KiUserCallbackDispatcher_Hook
:
typedef
VOID
(NTAPI
*t_KiUserCallbackDispatcher)(
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength
);
t_KiUserCallbackDispatcher KiUserCallbackDispatcher_Trampoline;
VOID NTAPI KiUserCallbackDispatcher_Hook(ULONG ApiNumber, PVOID InputBuffer, ULONG InputLength) {
if (ApiNumber == 0x58 && InputBuffer) {
HWND &hwnd = *(HWND*)((UINT_PTR)InputBuffer + 0xC);
if (hwnd) {
HANDLE hHandle = NtUserQueryWindow(hwnd, WindowProcess);
if (isProtectedPid(hHandle)) {
hwnd = NULL;
}
}
}
return KiUserCallbackDispatcher_Trampoline(ApiNumber, InputBuffer, InputLength);
}
I'm not sure if this ApiNumber
is the same for other user32.dll
versions, nor if it's safe to simply ignore it and directly read the HWND, but I guess this option is better than messing with NtUserSetWinEventHook
like in my previous post.
I see some ways to approach this, but I'm not a great fan of any of them:
KiUserCallbackDispatcher
: I'm pretty sure this is the worst possible way to go about this. The first reason I have for saying this is just my gut feeling about hooking anything named Ki__
. Just look at our famously portable and reliable KiUserExceptionDispatcher
. The second is that this doesn't have any advantage over...__ClientCallWinEventProc
. This function is similarly ominous in that it starts with two underscores (or three if you're still stuck in the 32 bit world). However, it is actually present in XP through 10, and while as you say, it's not an exported function, it is indirectly accessible through the PEB. You see, when new people come to work at Microsoft, they are given an IQ test. If they score below 70, they are assigned to work on win32k.sys and user32.dll. This is why there is a KernelCallbackTable
in the PEB for convenient access to user callbacks from kernel mode. This array includes __ClientCallWinEventProc
. However, the issue is that the index of this function is not fixed and varies per Windows version. On my Windows 7 machine it is located at KernelCallbackTable[0x4E]
, and I can see that your 0x58
is correct for Windows 10. So we would need a reliable way to determine this offset.NtUserSetWinEventHook
(the ntdll-style syscall function in user32/win32u, not SetWinEventHook
which is only a wrapper for this). This is in my opinion the least hacky approach: we can keep a native equivalent of an std::unordered_map<HWINEVENTHOOK, WINEVENTPROC>
around in which we store the original procs (note that we can't assume SetWinEventHook
is only called once), to be ignored if the HWND
is the debugger's. Drawbacks:
NtUserUnhookWinEvent
as well for consistency, otherwise the hook is trivially detectable and will also be very brittle unless the target program only unregisters its hook(s) at exit.NtContinue
hook uses SAVE_DEBUG_REGISTERS ArrayDebugRegister[100]
. The benefit of using such an array is that it's statically allocated and can approximate an std::unordered_map
fairly easily. However, if we use a SAVE_WINEVENTHOOK EventHooks[N]
, the target process can just make N
bogus calls to SetWinEventHook
before we run out of storage. Heap storage is preferable, but this is much more complicated to implement correctly.I have some other musings w.r.t. the above that relate to ScyllaHide in general. For example, hooking NtUserSetWinEventHook
/NtUserUnhookWinEvent
would require adding two new RVAs to NtApiCollection.ini
(the one that is generated by PDBReader). To be honest I'd rather see this file and PDBReader disappear completely. It is a clumsy way to obtain some trivial function addresses, all of which as far as I can tell are directly called from other functions that are exported with the call instruction within a few bytes of the export. Something like HDE64 could probably easily achieve the same thing without the need to rely on dbghelp and the MS symbol servers (not to mention running PDBReader after each Windows update). Any thoughts re: this @mrexodia? This disassembler wouldn't need to be implemented in HookLibrary, in fact it would be easier to keep the existing method of having the injector supply the RVAs, but it could load and disassemble user32.dll inside its own (debugger/CLI) process before injection.
Similarly I noticed that the index of __ClientCallWinEventProc
in the PEB array is always +2 relative to that of __ClientNoMemoryPopup
. The latter should be easy to find with a basic disassembler as it is the only function in user32.dll that calls MessageBoxW
.
Personally I never actually run PDBReader because nobody uses unreliable methods like this to detect x64dbg, but adding the disassembly in the ScyllaHide plugin sounds like a good alternative approach.
Also you can just rename x64dbg.exe
to FUCKDAPOLICE.exe
and it will also kinda rename the relevant windows 👍
Yeah, likewise. The only commercial protector I know of that (I think) uses this kind of functionality is Obsidium. Not that I studied in great detail, I only noticed that my paid-for copy of NTLite wouldn't start if IDA was running. I sent the author an email to kindly tell him to go fuck himself and that was that.
Interestingly (or maybe not so), using Scyllahide with OllyDbg, the debugger window title is changed to match the profile being used by the plugin. And so when editing the code above to test for "OllyDbg", the debugger is not detected by the SetWinEventHook executable if the debugger is already running. ;)
Could you try if it detects x64dbg if you rename the executable to something random?
With the specific code used above (edited to check for "Ollydbg" as well), the detection "does not" occur if the debugger executable is is renamed to something random (123.exe as the example). I tested x64dbg and Ollydbg yielding identical results.
Heh. Yeah, checks like these are very frail and not that many protectors actually use them (because they are so easy to circumvent) unless you run the protector with the "paranoid" setting enabled. The issue is though that there are very many ways to query things about windows, desktops, the mouse, anything GUI related really, from win32k.sys (through user32.dll or win32u.dll nowadays in Win 10). The OP already listed several, but a more exhaustive list would be good. Also I'm not sure about how to go about adding this to SH since it could be some 10 or more hooks.
I looked at the various user32.dlls "through the ages" (from XP x86 to Win 10 x64) and unfortunately there are too many small differences to make a heuristic approach with a disassembler (like I talked about above) work I think. There is also the problem that if you want to add a new win32k syscall hook, you have to find a new heuristic and make it work on all OSes.
I still want to get rid of PDBReader and NtApiCollection eventually though, so I currently have a WIP project that uses a table of hardcoded syscall numbers (based on SyscallTables). This sounds like it is impossible to maintain, and it would be, except on builds >= 14393 of Win 10 we can get the syscall numbers from exported names in win32u.dll the same way you can already do it with ntdll. The remaining syscall numbers are fixed since the OSes have already been released. So in pseudocode the function to get an RVA looks something like this:
if os >= 14393:
return GetProcAddress(win32u, "NtUserQueryWindow") - win32uBase
else
syscallNum = GetSyscallIndexFromTable("NtUserQueryWindow")
return ScanForSyscallPattern(user32, syscallNum) - user32Base
I have a prototype app of this and it works on all OS/bitness combinations I've tested, but integrating it into ScyllaHide will take some time since there is a lot of code to replace.
Sample source code using SetWinEventHook to detect x64dbg:
The detection happens as soon as you move the mouse over other windows. This sends events to the application with the
HWND
and you can check if its title is flagged or not.You could counter this by hooking:
You could also hook
NtUserSetWinEventHook
to get the address of the WinEventProc callback and then hook it to erase the passed HWND. According to the documentation, it should be safe to erase it:Type: HWND Handle to the window that generates the event, or NULL if no window is associated with the event. For example, the mouse pointer is not associated with a window.