Git Product home page Git Product logo

Comments (8)

Mattiwatti avatar Mattiwatti commented on May 18, 2024 3

I see some ways to approach this, but I'm not a great fan of any of them:

  • Hooking 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...
  • Hooking __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.
  • Hooking 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:
    • This requires hooking 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.
    • Persistent storage of any kind is problematic in HookLibrary. There is one other hook that does something similar: the 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.

from scyllahide.

Mattiwatti avatar Mattiwatti commented on May 18, 2024 1

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.

from scyllahide.

fenderluvr avatar fenderluvr commented on May 18, 2024 1

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.

from scyllahide.

reliasn avatar reliasn commented on May 18, 2024

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.

from scyllahide.

mrexodia avatar mrexodia commented on May 18, 2024

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 👍

from scyllahide.

fenderluvr avatar fenderluvr commented on May 18, 2024

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. ;)

from scyllahide.

mrexodia avatar mrexodia commented on May 18, 2024

Could you try if it detects x64dbg if you rename the executable to something random?

from scyllahide.

Mattiwatti avatar Mattiwatti commented on May 18, 2024

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.

from scyllahide.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.