/*
 * anti_debug.c
 *
 * Ten anti-debugger checks. Each populates one field in DebugCheckResult.
 * run_debug_checks() calls them all and returns the struct.
 *
 * Compile (MSVC):
 *   cl /W4 /O2 anti_debug.c
 *
 * Windows XP through Windows 11. x64 only (GS segment offset, PEB layout).
 */

#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <ntstatus.h>
#include <tlhelp32.h>
#include <stdio.h>

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif

/* ------------------------------------------------------------------ */
/* Types                                                                */
/* ------------------------------------------------------------------ */

typedef NTSTATUS (NTAPI *pfnNtQueryInformationProcess)(
    HANDLE           ProcessHandle,
    ULONG            ProcessInformationClass,
    PVOID            ProcessInformation,
    ULONG            ProcessInformationLength,
    PULONG           ReturnLength
);

/* Minimal PEB layout -- offsets stable since XP on x64. */
typedef struct _MYPEB {
    UCHAR  InheritedAddressSpace;
    UCHAR  ReadImageFileExecOptions;
    UCHAR  BeingDebugged;           /* +0x002 */
    UCHAR  BitField;
    UCHAR  Padding0[4];
    PVOID  Mutant;
    PVOID  ImageBaseAddress;
    PVOID  Ldr;
    PVOID  ProcessParameters;
    PVOID  SubSystemData;
    PVOID  ProcessHeap;
    PVOID  FastPebLock;
    PVOID  AtlThunkSListPtr;
    PVOID  IFEOKey;
    ULONG  CrossProcessFlags;
    UCHAR  Padding1[4];
    PVOID  KernelCallbackTable;
    ULONG  SystemReserved;
    ULONG  AtlThunkSListPtr32;
    PVOID  ApiSetMap;
    ULONG  TlsExpansionCounter;
    UCHAR  Padding2[4];
    PVOID  TlsBitmap;
    ULONG  TlsBitmapBits[2];
    PVOID  ReadOnlySharedMemoryBase;
    PVOID  SharedData;
    PVOID *ReadOnlyStaticServerData;
    PVOID  AnsiCodePageData;
    PVOID  OemCodePageData;
    PVOID  UnicodeCaseTableData;
    ULONG  NumberOfProcessors;
    ULONG  NtGlobalFlag;            /* +0x0bc on x64 */
} MYPEB, *PMYPEB;

#define PROCESSDEBUGPORT         7
#define PROCESSDEBUGOBJECTHANDLE 30

#define FLG_HEAP_TAIL_CHECK       0x10
#define FLG_HEAP_FREE_CHECK       0x20
#define FLG_HEAP_VALIDATE_PARAMS  0x40
#define DEBUGGER_HEAP_FLAGS       (FLG_HEAP_TAIL_CHECK | FLG_HEAP_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMS)

static const WCHAR *g_DebuggerNames[] = {
    L"x64dbg.exe",
    L"ida.exe",
    L"ida64.exe",
    L"VsDebugConsole.exe",
    L"msvsmon.exe",
    L"windbg.exe",
};
#define DEBUGGER_COUNT ((int)(sizeof(g_DebuggerNames)/sizeof(g_DebuggerNames[0])))

/* ------------------------------------------------------------------ */
/* Result struct                                                        */
/* ------------------------------------------------------------------ */

typedef struct {
    BOOL isDebuggerPresent;
    BOOL pebBeingDebugged;
    BOOL pebNtGlobalFlag;
    BOOL ntqipDebugPort;
    BOOL ntqipDebugObject;
    BOOL hardwareBreakpoints;
    BOOL debuggerProcess;
    BOOL tickCountAnomaly;
    BOOL perfCounterAnomaly;
    BOOL debugBreakConsumed;
    BOOL outputDbgStringTrick;

    ULONG_PTR dr0, dr1, dr2, dr3, dr6, dr7;
    ULONG     ntGlobalFlag;
    WCHAR     detectedProcess[MAX_PATH];
} DebugCheckResult;

/* ------------------------------------------------------------------ */
/* Check 1: IsDebuggerPresent                                          */
/* ------------------------------------------------------------------ */

static void check_is_debugger_present(DebugCheckResult *r)
{
    r->isDebuggerPresent = IsDebuggerPresent();
}

/* ------------------------------------------------------------------ */
/* Check 2: PEB.BeingDebugged direct read                              */
/* ------------------------------------------------------------------ */

static void check_peb_being_debugged(DebugCheckResult *r)
{
    PMYPEB peb = (PMYPEB)__readgsqword(0x60);
    r->pebBeingDebugged = (peb->BeingDebugged != 0);
}

/* ------------------------------------------------------------------ */
/* Check 3: PEB.NtGlobalFlag                                           */
/* ------------------------------------------------------------------ */

static void check_peb_nt_global_flag(DebugCheckResult *r)
{
    PMYPEB peb = (PMYPEB)__readgsqword(0x60);
    r->ntGlobalFlag    = peb->NtGlobalFlag;
    r->pebNtGlobalFlag = ((peb->NtGlobalFlag & DEBUGGER_HEAP_FLAGS) == DEBUGGER_HEAP_FLAGS);
}

/* ------------------------------------------------------------------ */
/* Check 4: NtQueryInformationProcess                                  */
/* ------------------------------------------------------------------ */

static void check_ntqip(DebugCheckResult *r)
{
    pfnNtQueryInformationProcess NtQIP = (pfnNtQueryInformationProcess)
        GetProcAddress(GetModuleHandleW(L"ntdll.dll"),
                       "NtQueryInformationProcess");
    if (!NtQIP) return;

    ULONG_PTR debugPort = 0;
    NTSTATUS  st = NtQIP(GetCurrentProcess(),
                          PROCESSDEBUGPORT,
                          &debugPort,
                          sizeof(debugPort),
                          NULL);
    if (NT_SUCCESS(st) && debugPort != 0)
        r->ntqipDebugPort = TRUE;

    HANDLE debugObj = NULL;
    st = NtQIP(GetCurrentProcess(),
               PROCESSDEBUGOBJECTHANDLE,
               &debugObj,
               sizeof(debugObj),
               NULL);
    if (NT_SUCCESS(st) && debugObj != NULL) {
        r->ntqipDebugObject = TRUE;
        CloseHandle(debugObj);
    }
}

/* ------------------------------------------------------------------ */
/* Check 5: hardware debug registers                                   */
/* ------------------------------------------------------------------ */

static void check_hardware_breakpoints(DebugCheckResult *r)
{
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

    if (!GetThreadContext(GetCurrentThread(), &ctx))
        return;

    r->dr0 = ctx.Dr0;
    r->dr1 = ctx.Dr1;
    r->dr2 = ctx.Dr2;
    r->dr3 = ctx.Dr3;
    r->dr6 = ctx.Dr6;
    r->dr7 = ctx.Dr7;

    r->hardwareBreakpoints = (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3);
}

/* ------------------------------------------------------------------ */
/* Check 6: process name blacklist                                     */
/* ------------------------------------------------------------------ */

static void check_process_blacklist(DebugCheckResult *r)
{
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) return;

    PROCESSENTRY32W pe;
    pe.dwSize = sizeof(pe);
    if (!Process32FirstW(snap, &pe)) {
        CloseHandle(snap);
        return;
    }

    do {
        for (int i = 0; i < DEBUGGER_COUNT; i++) {
            if (_wcsicmp(pe.szExeFile, g_DebuggerNames[i]) == 0) {
                r->debuggerProcess = TRUE;
                wcsncpy_s(r->detectedProcess,
                          MAX_PATH,
                          pe.szExeFile,
                          _TRUNCATE);
                goto done;
            }
        }
    } while (Process32NextW(snap, &pe));

done:
    CloseHandle(snap);
}

/* ------------------------------------------------------------------ */
/* Check 7: GetTickCount64 timing                                      */
/* ------------------------------------------------------------------ */

static void check_tick_count(DebugCheckResult *r)
{
    ULONGLONG t1 = GetTickCount64();
    Sleep(10);
    ULONGLONG t2 = GetTickCount64();
    r->tickCountAnomaly = ((t2 - t1) > 100);
}

/* ------------------------------------------------------------------ */
/* Check 8: QueryPerformanceCounter timing                             */
/* ------------------------------------------------------------------ */

static void check_qpc_timing(DebugCheckResult *r)
{
    LARGE_INTEGER freq, t1, t2;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&t1);
    Sleep(1);
    QueryPerformanceCounter(&t2);

    LONGLONG elapsed_us = (t2.QuadPart - t1.QuadPart) * 1000000LL / freq.QuadPart;
    r->perfCounterAnomaly = (elapsed_us > 50000);
}

/* ------------------------------------------------------------------ */
/* Check 9: DebugBreak exception trap                                  */
/* ------------------------------------------------------------------ */

static void check_debug_break(DebugCheckResult *r)
{
    r->debugBreakConsumed = TRUE;  /* assume debugger: it consumes the exception */
    __try {
        DebugBreak();
    }
    __except (GetExceptionCode() == EXCEPTION_BREAKPOINT
              ? EXCEPTION_EXECUTE_HANDLER
              : EXCEPTION_CONTINUE_SEARCH) {
        r->debugBreakConsumed = FALSE;  /* exception reached us: no debugger */
    }
}

/* ------------------------------------------------------------------ */
/* Check 10: OutputDebugString last-error trick                        */
/* ------------------------------------------------------------------ */

static void check_output_debug_string(DebugCheckResult *r)
{
    SetLastError(0xDEAD);
    OutputDebugStringW(L"probe");
    r->outputDbgStringTrick = (GetLastError() == 0);
}

/* ------------------------------------------------------------------ */
/* Entry point                                                          */
/* ------------------------------------------------------------------ */

DebugCheckResult run_debug_checks(void)
{
    DebugCheckResult r;
    memset(&r, 0, sizeof(r));
    check_is_debugger_present(&r);
    check_peb_being_debugged(&r);
    check_peb_nt_global_flag(&r);
    check_ntqip(&r);
    check_hardware_breakpoints(&r);
    check_process_blacklist(&r);
    check_tick_count(&r);
    check_qpc_timing(&r);
    check_debug_break(&r);
    check_output_debug_string(&r);
    return r;
}

int main(void)
{
    DebugCheckResult r = run_debug_checks();

    printf("1  IsDebuggerPresent       : %s\n", r.isDebuggerPresent    ? "DETECTED" : "clean");
    printf("2  PEB.BeingDebugged       : %s\n", r.pebBeingDebugged     ? "DETECTED" : "clean");
    printf("3  PEB.NtGlobalFlag        : %s  (0x%08lx)\n",
           r.pebNtGlobalFlag ? "DETECTED" : "clean", r.ntGlobalFlag);
    printf("4a NtQIP DebugPort         : %s\n", r.ntqipDebugPort       ? "DETECTED" : "clean");
    printf("4b NtQIP DebugObjectHandle : %s\n", r.ntqipDebugObject     ? "DETECTED" : "clean");
    printf("5  Hardware BPs (DR0-DR3)  : %s  DR0=%llx DR1=%llx DR2=%llx DR3=%llx\n",
           r.hardwareBreakpoints ? "DETECTED" : "clean",
           (ULONGLONG)r.dr0, (ULONGLONG)r.dr1,
           (ULONGLONG)r.dr2, (ULONGLONG)r.dr3);
    printf("6  Debugger process        : %s",   r.debuggerProcess      ? "DETECTED" : "clean");
    if (r.debuggerProcess) printf("  (%ls)", r.detectedProcess);
    printf("\n");
    printf("7  TickCount anomaly       : %s\n", r.tickCountAnomaly     ? "DETECTED" : "clean");
    printf("8  QPC anomaly             : %s\n", r.perfCounterAnomaly   ? "DETECTED" : "clean");
    printf("9  DebugBreak consumed     : %s\n", r.debugBreakConsumed   ? "DETECTED" : "clean");
    printf("10 OutputDbgString trick   : %s\n", r.outputDbgStringTrick ? "DETECTED" : "clean");

    return 0;
}
