为 “DebugObject”的数目不为0,那么系统中就存在调试器。 但有个缺点是不能确定调试器在调试当前的程序。 2)切断调试器与调试目标的联系 调试器在调试程序的时候,调试器实际上是调试程序的 父进程,操作系统会将被调试程序产生的调试事件都发给调 试程序。调试器只有在获得调试事件的前提下,才可以对程 序进行调试。 Ntdll.dll中的函数ZwSetInformationThread来设置标志 位ThreadHideFromDebugger。ThreadHideFromDebugger可 以作为一个参数传入函数,告诉系统不要产生线程的调 试事件。如果当前进程成功映射了一个映像,会调用函数 DbgMapViewOfSection。这个函数会在当前进程成功的加载到 内存映像后被调用,在这个函数中会判断HideFromDebugger, 如果为True,则代码返回,调试器不会获得任何消息。否 则,进程会跟一个调试端口关联起来,通知调试器发生了 LOAD_DLL_DEBUG_EVENT事件。 3)利用Windows进程管理 程序运行时,利用Windows的进程管理的方法去检测调 试器也是不错的方法。函数Process32First和Process32Next 通过遍历所有进程, 得到存放进程信息的结构体, 其定义如下: typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; // this process ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; // associated exe DWORD cntThreads; DWORD th32ParentProcessID; // this process's parent process LONG pcPriClassBase; // Base priority of process's threads DWORD dwFlags; CHAR szExeFile[MAX_PATH]; // Path } PROCESSENTRY32; 通过查看结构的szExeFile成员来判断该进程是否是调 试器的相关进程,如果是,则自身程序直接退出。通过函数 EnumWindows来枚举当前系统中运行所有程序的窗口,函数 原型如下: BOOL EnumWindows( WNDENUMPROC lpEnumFunc, LPARAM lParam ); 其中,回调函数的定义为: BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam ); 在第一个回调函数中,调用函数GetWindowText来获得窗 口的标题,函数原型如下: int GetWindowText( HWND hWnd, LPTSTR lpString, int nMaxCount ); 通过成员变量lpString来判断当前系统中是否运行着调试 器。如果存在调试器,可利用函数EnumWindows得到的句柄, 使用函数PostMessage来关闭调试器, 函数PostMessage定义如下: BOOL PostMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam ); 其中第二个参数是WM_CLOSE, 这样就可以把调试器关 闭。使用函数EnableWindow来使调试器无法获得来自键盘和 鼠标的操作。函数定义如下: BOOL EnableWindow( HWND hWnd, BOOL bEnable ); 第一个参数是目标的句柄,因此在函数调用前需要得到 调试器进程的句柄。第二个参数为true的时候,输入被阻止, 为false的时候,输入回复正常。 使用函数BlockInput也可以得到类似的效果,函数定义 如下: BOOL BlockInput( BOOL fBlockIt); 利用EnableWindow和BlockInput这两个函数,程序可在 进行授权验证之前阻止鼠标和键盘的输入,防止此时程序被 调试,验证过程被更改,验证完之后在回复鼠标键盘的正常 输入。由于计算机执行验证的速度很快,用户也觉察不到鼠 标和键盘的暂时失效。 另外,还有一些常用的反调试的方法: 1)父进程: 得到当前进程的PROCESSENTRY32结构,判 断其成员变量th32ParentProcessID的所有者是否是Explorer.exe, 或者是cmd.exe,如果不是,则证明存在调试器,退出程序。 |