IsDebuggerPresent

函数体

检测原理
TEB结构体
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
fs:[30]对应的是PEB
PEB结构体
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
偏移0x2处即为BeingDebugged字段,为1则表示正在被调试
对策
- 打断点然后更改eax寄存器的值(不推荐)
- 由于IsDebuggerPresent后面往往会跟随关键je跳转,直接把je改为jmp,
- 把BeingDebugged字段处的值置0
进程名检测
CreateToolHelp32Snapshot
实现方法
bool CheckandKillDebugger(const wchar_t* name)
{
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(snap == INVALID_HANDLE_VALUE) return false;
PROCESSENTRY32W pe;
pe.dwSize = sizeof(pe);
BOOL killed = FALSE;
if(Process32FirstW(snap,&pe))
{
do
{
if(_wcsicmp(pe.szExeFile,name) == 0)
{
killed = true;
}
} while (Process32NextW(snap,&pe));
}
CloseHandle(snap);
return killed;
}
用CreateToolhelp32Snapshot创建一个所有进程的快照,然后用Process32FristW配合Process32NextW遍历快照中的进程,存到PROCESSENTRY32W结构体。再使用针对wchar_t的字符串比较函数_wcsicmp,检测xdbg进程名

对策
pe.szExeFile是用宽字符存储的,所以对应的字符串比较函数也必须跟宽字符有关,因此可以找到_wcsicmp函数。
这个函数在字符串相等时返回0,我尝试给下面的eax设断点,但还没看到eax是0的时候就已经被检测到了。
所以将目光转向返回值所在内存地址,我这里是rbp + 1EC,而它后面跟的正好是1,所以把1改成0就可以了。
或者把下面的setne改成sete
逻辑:非目标进程 =》rax != 0 =》killed是0 =》cmp比较结果为0 =》zf = 1 =》al被置0 (不写出来真的很乱)
GetProcAddress
实现方法
bool CheckandKillDebugger(const wchar_t* name)
{
HMODULE hPsapi = LoadLibraryW(L"psapi.dll");
if (!hPsapi) return false;
typedef BOOL (WINAPI* pEnumProcesses)(DWORD*,DWORD,DWORD*);
typedef BOOL (WINAPI* pEnumProcessModules)(HANDLE,HMODULE*,DWORD,LPDWORD);
typedef DWORD (WINAPI* pGetModuleBaseNameW)(HANDLE,HMODULE,LPWSTR,DWORD);
pEnumProcesses mEnumProcesses = (pEnumProcesses)GetProcAddress(hPsapi,"EnumProcesses");
pEnumProcessModules mEnumProcessModules = (pEnumProcessModules)GetProcAddress(hPsapi,"EnumProcessModules");
pGetModuleBaseNameW mGetModuleBaseNameW = (pGetModuleBaseNameW)GetProcAddress(hPsapi,"GetModuleBaseNameW");
if(!mEnumProcesses || !mEnumProcessModules || !mGetModuleBaseNameW)
{
FreeLibrary(hPsapi);
return false;
}
DWORD pids[1024],cbNeeded;
if(!mEnumProcesses(pids,sizeof(pids),&cbNeeded))
{
FreeLibrary(hPsapi);
return false;
}
int cnt = cbNeeded / sizeof(DWORD);
for(int i = 0; i < cnt; ++i)
{
DWORD pid = pids[i];
if (pid == 0) continue;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,pid);
if (!hProcess) continue;
HMODULE hMod;
DWORD cbMod;
if(mEnumProcessModules(hProcess,&hMod,sizeof(hMod),&cbMod))
{
wchar_t modName[MAX_PATH] = {0};
if (mGetModuleBaseNameW(hProcess,hMod,modName,MAX_PATH))
{
if (_wcsicmp(modName, name) == 0)
{
CloseHandle(hProcess);
FreeLibrary(hPsapi);
return true;
}
}
}
CloseHandle(hProcess);
}
FreeLibrary(hPsapi);
return false;
}
用LoadLibrary函数动态加载psapi.dll,提高检测api隐蔽性。然后需要用GetProcAddress获取EnumProcess、EnumProcessModules、GetModuleBaseNameW这3个api的地址,并保存在各自的指针中。先用mEnumProcess枚举进程,再用OpenProcess打开相应进程。然后通过mEnumProcessModules获取进程的exe模块句柄(大部分进程第一个是exe,后面是一堆dll,所以这里没用数组接收),把句柄传入GetModuleBaseNameW就可以得到进程名,最后与x64dbg.exe比对一下就行了
动态加载api的效果
xdbg无法直接查到相关api,只能看到GetProcAddress。即使通过GetProcAddress打断点,有些api也会失效,比如上述这三个。
对策
给GetProcAddress返回值改成0,改不了就在调用处把rax改成0,异常就nop,这样就不会触发检测了

NtQueryInformationProcess
实现方法
typedef NTSTATUS (NTAPI* _NtQueryInformationProcess)(
HANDLE ProcessHandle,
DWORD ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
BOOL testNtPort(_NtQueryInformationProcess NtQueryInformationProcess)
{
HANDLE hProcess = GetCurrentProcess();
DWORD DebugPort;
NtQueryInformationProcess(hProcess,7,&DebugPort,sizeof(DWORD),NULL);
if (DebugPort != 0)
{
MessageBoxW(NULL,L"NtPort",NULL,MB_OK);
return TRUE;
}
return FALSE;
}
//VScode gdb xdbg
BOOL testNtObjectHandle(_NtQueryInformationProcess NtQueryInformationProcess)
{
HANDLE hProcess = GetCurrentProcess();
HANDLE ObjectHandle;
NtQueryInformationProcess(hProcess, 30, &ObjectHandle, sizeof(ObjectHandle), NULL);
if (ObjectHandle != NULL)
{
MessageBoxW(NULL,L"NtObjectHandle",NULL,MB_OK);
return TRUE;
}
return FALSE;
}
BOOL testFlag(_NtQueryInformationProcess NtQueryInformationProcess)
{
HANDLE hProcess = GetCurrentProcess();
BOOL Flags;
NtQueryInformationProcess(hProcess, 31, &Flags, sizeof(Flags), NULL);
if (Flags != 1)
{
MessageBoxW(NULL,L"Flag",NULL,MB_OK);
return TRUE;
}
return FALSE;
}
BOOL CheckandKillDebugger()
{
HMODULE hDll = LoadLibraryW(L"Ntdll.dll");
_NtQueryInformationProcess NtQueryInformationProcess =
(_NtQueryInformationProcess)GetProcAddress(hDll, "NtQueryInformationProcess");
if( testNtPort(NtQueryInformationProcess) == TRUE
|| testNtObjectHandle(NtQueryInformationProcess) ==TRUE
|| testFlag(NtQueryInformationProcess) == TRUE)
{
return TRUE;
}
return FALSE;
}
对策:给函数下断点,找第二个参数(缓冲区数据的含义)和缓冲区地址,修改缓冲区数据,比如0、NULL以及Flags对应的1。
此外NtQueryInformationProcess可配合SetUnhandledExceptionFilter使用。
SetUnhandledExceptionFilter
这个函数是用来注册UnhandledExceptionFilter API的。该API会先调用上边的NtQueryInformationProcess检查是否存在调试器,如果不存在,就执行程序自己的异常处理API。存在调试器的情况下,异常会被移交给调试器,如果调试器也不处理,就调用系统的UnhandledExceptionFilter去处理
RtlGetNtGlobalFlags
实现方法
#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
#define FLG_HEAP_ENABLE_FREE_CHECK 0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
typedef ULONG (NTAPI* RtlGetNtGlobalFlags_t)(void);
BOOL CheckandKillDebugger()
{
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if(!hNtdll) return FALSE;
RtlGetNtGlobalFlags_t RtlGetNtGlobalFlags =
(RtlGetNtGlobalFlags_t)GetProcAddress(hNtdll,"RtlGetNtGlobalFlags");
if(!RtlGetNtGlobalFlags) return FALSE;
ULONG flags = RtlGetNtGlobalFlags();
ULONG heapFlags = flags & (FLG_HEAP_ENABLE_FREE_CHECK
| FLG_HEAP_ENABLE_TAIL_CHECK
| FLG_HEAP_VALIDATE_PARAMETERS);
//表达式 flags & (A | B | C) 用位运算把 flags 中除了 A、B、C 所代表的那几位以外的位都清零,结果 heapFlags 只保留这三个位的状态。
return (heapFlags != 0);
}
浙公网安备 33010602011771号