x64dbg / ScyllaHide

Advanced usermode anti-anti-debugger. Forked from https://bitbucket.org/NtQuery/scyllahide
GNU General Public License v3.0
3.46k stars 434 forks source link

Detecting x64dbg with SetWinEventHook #48

Open reliasn opened 7 years ago

reliasn commented 7 years ago

Sample source code using SetWinEventHook to detect x64dbg:

#include <Windows.h>

HWINEVENTHOOK hWinEventHook;

void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD dEvent, HWND hwnd, LONG idObject, LONG idChild, 
                             DWORD dwEventThread, DWORD dwmsEventTime)
{
    if (!hwnd) {
        return;
    }
    HWND hwndCopy = hwnd;
    while (1) {
        HWND parentHwnd = GetParent(hwndCopy);
        if (!parentHwnd) {
            break;
        }
        hwndCopy = parentHwnd;
    }
    // Just checking the window title, but you could also use GetClassName to detect it
    WCHAR windowTitle[MAX_PATH];
    if (InternalGetWindowText(hwndCopy, windowTitle, sizeof(windowTitle))) {
        const wchar_t* result = wcsstr(windowTitle, L"x64dbg");
        const wchar_t* result2 = wcsstr(windowTitle, L"x32dbg");
        if (result || result2) {
            MessageBox(NULL, "Debugger detected!", NULL, NULL);
        }
    }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) {
    hWinEventHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, HandleWinEvent, 0, 0, WINEVENT_SKIPOWNPROCESS);
    MSG msg;
    BOOL bRet; 
    WNDCLASS wc = {};
    UNREFERENCED_PARAMETER(lpszCmdLine);

    if (!hPrevInstance) 
    { 
        wc.style = 0; 
        wc.lpfnWndProc = (WNDPROC) WndProc;
        wc.cbClsExtra = 0; 
        wc.cbWndExtra = 0; 
        wc.hInstance = hInstance; 
        //wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION); 
        //wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); 
        //wc.hbrBackground = GetStockObject(WHITE_BRUSH); 
        wc.lpszMenuName =  "MainMenu"; 
        wc.lpszClassName = "MainWndClass"; 

        if (!RegisterClass(&wc)) 
            return FALSE; 
    }

    HWND hwndMain = CreateWindow("MainWndClass", "Sample", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
                                 CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL, hInstance, (LPVOID) NULL); 

    if (!hwndMain) 
        return FALSE; 

    ShowWindow(hwndMain, nCmdShow); 
    UpdateWindow(hwndMain); 

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    }

    return msg.wParam;
}

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.

reliasn commented 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.

Mattiwatti commented 6 years ago

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

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.

mrexodia commented 6 years ago

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 👍

Mattiwatti commented 6 years ago

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.

fenderluvr commented 6 years ago

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

mrexodia commented 6 years ago

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

fenderluvr commented 6 years ago

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.

Mattiwatti commented 6 years ago

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.