__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);
第二个参数可以是ProcessBasicInformation(获取PEB信息)或ProcessDebugPort/ProcessDebugObjectHandle
当调试状态时,调试端口的值为0xFFFFFFFF
还可以获取DebugHandle(正常情况下为0)
DWORD dbgHandle=0;
NtQueryInformationProcess(GetCurrentProcess(),(PROCESSINFOCLASS)0x1E,&dbgHandle,4,0);
实际底层调用了ZwQueryInformationProcess
可以通过Hook相关API绕过
一般程序正常启动不具备调试权限,但是调试器具有调试权限,并且被调试程序会继承权限
,,状XSTuetrrocess 1d尘女X-山+
HIMODULE hMod = GetModuleHandle(L"ntd1l. d11");
typedef int (*CSRGETPROCESSID) ();
CSRGETPROCESSID CsrGetProcessId = (CSRGETPROCESSID) GetProcAddress(hMod,"CsrGetProcessId");
//获取csrss. exe的PID4
DIWORD pid = CsrGetProcessId();
//打开成功说明管理员+调试权限。
HANDLE hCsr = OpenProcess(PROCESS_QUERY_INFORIMATITON, FALSE, pid) ;
BOOL isDebugged=hCsr;
破解即要么置csrss.exe
字符串为0,要么hook API
程序异常结束后,如果设置了JIT调试器,会展示一个调试程序对话框。
设置JIT调试器需要两个注册表值
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug(32位系统)
HKLM\SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\AeDebug(64位系统)
就可以通过这两项注册表值检测调试器
//判断当前系统是32还是64位。
BOOL b64 = FALSE;
IsWow64Process(GetCurrentProcess(),&b64) ;
HKEY hkey = NLL;
TCHAR *reg = b64?TEXT("SOFTWARE\\Wow6432Node\\Microsoft\\WindowsNT\\CurrentVersion\\AeDebug"):TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug");
//打开注册表键v
DIWIORD ret = RegCreateKey(HKEY_LOCAL_MACHINE, reg, &hkey);
if (ret != ERROR_SUCCESS) return FALSE;
TCHAR *subkey = TEXT("Debugger") ;
TCHAR value[256] = {};
DIWORD len = 256;
//查询对应项的值。
ret = RegQueryValueEx(hkey, subkey, NULL, NULL, ((LPBYTE)value, &len);
RegCloseKey(hkey);
// 其中value就是调试器名称
窗口名可以通过Spy++查看
BOOL isDebugged=FindWindow(TEXT("OLLYDBG"),NULL);
枚举窗口方法
BOOL CALLBACK EnumWindowProc (HIMND hWnd, LPARAM lParam) {
TCHAR winTitle[0x100] = 0;
GetiWindowText(hWnd, winTitle, 0x100) ;
if ( tcsstr(winTitle,TEXT("0llyDbg")))
{
*((int*)IParam) = true;
//找到目标窗口停止遍历+
return false;+
}。
//继续遍历下一个窗口。
return true;
}
bool CheckDebugEnumWindow() {
int nFind = false;
EnumWindows(EnumWindowProc,((LPARAMD)&nFind) ;
return nFind;
}
#ifndef UNICODE
#define UNICODE
#endif // UNICODE
#ifndef _UNICODE
#define _UNICODE
#endif // _UNICODE
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <winternl.h>
#include <Shlwapi.h>
/*
/// GetProcessImageFileName 函数
//windows7: kernel32.dll|.lib
// windows r8+: psapi.dll|.lib
#include <psapi.h>
*/
#pragma comment(lib, "shlwapi.lib")
#define SETVALUEFROMPOINTER(p, v) (*p=v)
#if defined(UNICODE) || defined(_UNICODE)
#define OutPutStr(f, v) wprintf_s(L##f, v)
#else
#define OutPutStr(f, v) printf_s(f, v)
#endif
DWORD GetParentPIDAndName( DWORD ProcessID, LPTSTR lpszBuffer_Parent_Name, PDWORD ErrCodeForBuffer );
int main(int argc, const char* argv[]) {
DWORD pid;
TCHAR buf[BUFSIZ] = {0};
DWORD err_code;
pid = GetParentPIDAndName(GetCurrentProcessId(), buf, &err_code);
if ( err_code ) {
fprintf(stderr, "GetProcessName--> err code: %lu\n", err_code);
}
OutPutStr("ParentProcessPID: %lu\n", pid);
OutPutStr("ParentProcessFullName: %s\n", buf);
PathStripPath(buf);
OutPutStr("ParentProcessName: %s\n", buf);
return 0;
}
typedef
__kernel_entry NTSTATUS
(NTAPI*NQIP)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
DWORD GetParentPIDAndName( DWORD ProcessID, LPTSTR lpszBuffer_Parent_Name, PDWORD ErrCodeForBuffer ) {
/// 打开给定进程PID
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessID);
if ( !ProcessID ) {
DWORD err_code = GetLastError();
fprintf_s(stderr, "[OpenProcess]err code: %lu\n", err_code);
return 0;
}
/// 下面是获取函数 NtQueryInformationProcess 的函数指针
HMODULE hNtdll = GetModuleHandle(_T("ntdll.dll"));
if ( !hNtdll ) {
DWORD err_code = GetLastError();
fprintf_s(stderr, "[GetModuleHandle]err code: %lu\n", err_code);
CloseHandle(hProcess);
return 0;
}
NQIP _NtQueryInformationProcess = (NQIP)GetProcAddress(hNtdll, "NtQueryInformationProcess");
if ( !_NtQueryInformationProcess ) {
DWORD err_code = GetLastError();
fprintf_s(stderr, "[GetProcAddress]err code: %lu\n", err_code);
CloseHandle(hProcess);
return 0;
}
//***
/// 获取打开的进程的进程进程信息
PROCESS_BASIC_INFORMATION pbi;
NTSTATUS status = _NtQueryInformationProcess(
hProcess,
ProcessBasicInformation,
(LPVOID)&pbi, sizeof(PROCESS_BASIC_INFORMATION),
NULL);
DWORD dwParentID = 0;
if ( NT_SUCCESS(status) ) {
/// 结构体 PROCESS_BASIC_INFORMATION 的 "Reserved3"字段 是父进程的PID
dwParentID = (LONG_PTR)pbi.Reserved3;
if ( NULL != lpszBuffer_Parent_Name ) {
HANDLE hParentProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwParentID);
if ( hParentProcess ) {
/// 用来接收进程文件名和路径的长度(必须!)
DWORD bufs;
/// 获取进程路径
BOOL ret = QueryFullProcessImageName(hParentProcess, 0, lpszBuffer_Parent_Name, &bufs);
if ( TRUE == ret )
SETVALUEFROMPOINTER(ErrCodeForBuffer, 0);
else
SETVALUEFROMPOINTER(ErrCodeForBuffer, GetLastError());
// 结果是DOS路径+文件名
}
else {
SETVALUEFROMPOINTER(ErrCodeForBuffer, GetLastError());
}
if ( hParentProcess )
CloseHandle(hParentProcess);
}
}
else {
DWORD err_code = GetLastError();
fprintf_s(stderr, "[NtQueryInformationProcess]err code: %lu\n", err_code);
}
CloseHandle(hProcess);
return dwParentID;
}
其中PID可以使用GetProcessId(PROCESS_QUERY_INFORMATION)获取,之后就可以比对父进程是否是explorer.exe了
PROCESSENTRY32 pe32 = { sizeof(pe32) } ;
HANDLE hProcessSnap = CreateToolhelp32Snapshot( TH32CS_ SNAPPROCESS, 0) ;
if (hProcessSnap == INTALID _HANDLE_ VALUE)
{
return FALSE;
}。
Process32First(hProcessSnap,&pe32) ;
//这里只比较了0l1yDbg,也可以添加其他的调试分析工具名。
do{
if ( tcsicmp(pe32. szExeFile, TEXT("0l1yDbg.exe ")) == 0)
{
CloseHlandle(hProcessSnap);
return TRUE;
}
} while (Process32Next(hProcessSnap, &pe32)) ;
CloseHandle(hProcessSnap);
通过查找调试对象来实现反调试
boo1 CheckDebug_QueryObject0 {
typedef struct _OBJECT_TYPE_INFORMATION{
INICODE_ STRING TypeNames;
ULONG TotalNumber0fHandles;
ULCNG Tota1Number0fObjiects;
}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION{
UZONG NumberOfObjectsTypes;
OBJBCT_TYPE_INFORMATION ObjectTypeInfo[1];
}OBJBCT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
//1.获取欲查询信息大小,
ULONG uSize = 0;
NtQueryObject (NULL,(OBJECT_INFORMATION_CLASS)0x03,&uSize,sizeof (uSize),&uSize);
//2.获取对象大信息..
POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION) new BYTE[uSize+4];
NtQueryObject(NULL,(OBJECI_INFORMATION_CLASS) 0x03, pObjectAllInfo,uSize,&uSize);
//3.循环遍历并处理对象信息..
POBJECT_TYPE_INFORMATION pObjectTypeInfo = pObjectAllInfo->ObjectTypeInfo;
for (int i = 0: i < pObjectAllInfo->Number0fObjectsTypes; i++){
//3. 1查看此对象的类型是否为DebugObject.
if(!wcscmp(L"DebugObject",pObjectTypeInfo->TypeNames.buffer)){
delete[] pObjectA11Info;
return true;
}
//3. 2获取对象名占用空间大小(考虑到了结构体对齐问题)
ULONG uNameLength = pObjectTypeInfo->TypeNames. Length;
UZONG uDataLength = uNameLength - uNameLength % sizeof(ULONG)+sizeof (ULONG);
//3. 3指向下一个对象信息,
pObjectTypeInfo =(POBJECT_TYPB_INFORMATION) pObjectTypeInfo->TypeNames.Buffer;
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION) ((PBYTE) pObjectTypeInfo+uDataLength);
}
de1ete[] pObjectA11Info;
return false;
}
当系统处于被调试状态时,可以通过检测该状态来实现反调试
struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
BOOLEAN DebuggerEnabled;
BOOLEAN DebuggerNotPresent;
}DebuggerInfo = { 0 };
NtQuerySystemInformation((SYSTEML INFORILATIOV CLASS) 0x23,&DebuggerInfo,sizeof(DebuggerInfo),NULL);
BOOL isDebugged=DebuggerInfo.DebuggerEnabled;
ZwSetInformationThread传参ThreadHideFromDebugger时,可以使线程脱离调试器,效果就像崩溃
enum THREAD_INFO_CLASS4{
ThreadHideFromDebugger = 17
}
typedef NTSTATUS(NTAPI *ZW_SET_INFORMATION_THREAD) (
IN HANDLE ThreadHandle,
IN THREAD_INFO_CLASS ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);
ZW_SET_INFORMATION_THREAD ZwSetInformationThread;
ZwSetInformationThread =(ZW_SET_INFORMATION_THREAD)GetProcAddress(LoadLibrary(L"ntdll.d11"),"ZwSetInformationThread");
ZwSetInformationThread (GetCurrentThread(),ThreadHideFromDebugger, NULL, NULL);
可以通过Hook代码绕过
PE文件中数据目录表项大于0x10时系统会忽略,但是OD会认为PE格式错误
软件断点会将目标位置修改为0xcc(int 3)
,可以通过代码查询或更强的内存校验进行检测,笔者利用这个特性出过一道CTF题目
这种使用硬件断点绕过即可。
CPU有8个硬件断点寄存器Dr0~Dr7,其中Dr0~Dr3保存断点地址,Dr4~Dr5保留,Dr6~Dr7说明哪个硬件断点触发的相关属性
可以检查Dr0~Dr3的值是否为0确认硬件断点
通过API方式
CONTEXT context;
HANDLE hThread = GetCurrentThread();
context.ContextFlags = CONTEXT_DEBU_REGISTERS;
GetThreadContext(hThread, &context) ;中
BOOL isDebugged=context.Dr0 != 0|| context.Dr1 != 0 || context.Dr2 != 0|l context.Dr3 !=0
可以通过Hook GetCurrentThread破解
异常方式
BOOL bDebugging = FALSE;
__asm{
// install SEH
push handler+
push DWORD ptr fs:[0]
mov DWORD ptr fs:[0], esp
__emit(0xcc)
mov bDebugging;
jmp normal
handler:
mov eax, dword ptr ss : [esp + 0xc];// ContextRecord
mov dword ptr ds : [eax + 0xb8],offset normal
mov ecx, [eax + 4]; // Dr0
or ecx,[eax + 8]; // Dr1
or ecx, [eax + 0x0C];// Dr2
or ecx, [eax + 0x10];// Dr3
je NoDebugger;
mov eCx,
[eax + 0xb4];// ebp
// vs2015 debug 下bDebugging的地址为ebp-c
mov [ecx-0x0c],1 // bDebugging
NoDebugger:
xor eax, eax
retn
normal:
//remove SEH
pop dword ptr fs : [0]μ
add esp,4
}
return bDebugging;
单步步过时,call/rep指令下一条指令会设置为int 3
BOOL bDebugging = FALSE;
__asm{
xor eax, eax
xor ecx, ecx
inc ecx
lea esi, key
//此处步过时key处会被下0xCC断点。
//将key处的首字节给AL
rep lodsb
key:
cmp al, 0xcc
je debuging
jmp over
debuging:
mov bDebugging, 1
over :
}
return bDebugging;
当EFLAGS的TF位被设置时,CPU将进入单步执行模式,CPU执行一条指令触发EXCEPTION_SINGLE_STEP异常,并且TF清零。
检测方式1:主动触发TF异常,与SEH结合使用探测调试器。先pushfd修改再popfd
bool bDebugged = false;
__asm
{
// install SEH
push handler
push DIWIORD ptr fs : [0]
mov DIWORD ptr fs : [0], esp
pushfd
or dword ptr ss : [esp], 0x100
popfd
nop
mov bDebugged, 1
jmp normal
handler:
mov bDebugged, 1
mov eax, dword ptr ss : [esp + 0xc]
mov ebx,normal
mov dword ptr ds:[eax+0xb8], ebx
xor eax, eax
retn
normal:
remove SEH
pop dword ptr fs : [0]
add esp,4
}
return bDebugged;
检测方式2:检测调试器是否设置TF位。当执行pushfd时,TF会自动清零,所以test结果为0,这时候使用pop ss
将异常和终端挂起,知道下一条指令执行完毕
bool bDebugged = false;
__asm
{
push ss
pop ss
pushfd
test BYTE PIR SS : [ESP + 1], 10
jne debugged
jmp over
debugged:
mov bDebugged, 1
over:
popfd
}
return bDebugged;
PEB位于fs段0x30偏移处
PEB中0x2偏移处为BeingDebugged表示进程是否被调试,0x68为NtGlobalFlag表示进程的堆特性
mov eax,fs:[0x30]
mov al,byte ptr DS:[eax+2]
test al,al ; 1为被调试,0为没有被调试
IsDebuggerPresent
就是使用的这个原理,但是首先使用mov eax,fs:[0x18]
获取TEB的地址,再mov eax,[eax+0x30]
获取PEB地址
可以通过手动置零绕过
对于NtGlobalFlag
mov eax,fs:[0x30]
mov eax,dword ptr ds:[eax+0x68]
test eax,0x70 ; 为0x70时有调试器
手动置0绕过
explorer启动时会设置为0,调试器启动时会设置非0
STARTUPINFO si={};
GetStartupInfoW(&si);
BOOL isDebugged=si.dwX||si.dwY||si.dwXSize||dw.YSize;
实际上这些数据位于PEB中0x10中的_RTL_USER_PROCESS_PARAMETERS结构体,只需要将_RTL_USER_PROCESS_PARAMETERS结构体中0x4c 0x50 0x54 0x58
位置的数据设置为0即可
rdtsc
命令将TSC(Time Stamp Counter)读取到EDX:EAX中
使用时间API也可以
只需要判断两次执行时间大于某个值就认为有调试器
异常发生时,SEH机制下OS接收异常,使用进程中注册的SEH处理。但是如果被调试,则先传给调试器,利用这个特征可以判断是否调试运行。
以int 3
为例,反调试程序可以在SEH中更改执行流程,如果控制权交给调试器,调试器没执行SEH代码,程序流程就会未知
; install SEH
push handler
push DWORD PTR fs:[0]
mov DWORD PTR fs:[0],esp
__emit(0xcc)
mov isDebugged,1
jmp normal
handler:
mov eax,dword ptr ss:[esp+0xc] ; exception record
mov dword ptr ds:[eax+0xb8],offset normal ; 修改EIP
xor eax,eax
retn
normal:
pop dword ptr fs:[0]
add esp,4
可以通过调试器中的忽略异常绕过
当SEH没法处理异常时,系统会调用UnhandledExceptionFilter
,即停止执行弹窗。SetUnhandledExceptionFilter
可以替换弹窗行为
LONG WINAPI Fun(_In_ struct_ EXCEPTION POINTERS *ExceptionInfo+) {
//跳过mov bDebug, 1这条指令
// int 3异常时,eip会被回拨到cc处
ExceptionInfo-> ContextRecord->Eip += 5;
return EXCEPTION_CONTINUE_EXECUTION;
}
bool CheckDebug _SetUnhandledExceptionFilter() {
bool bDebug = false;
_asm{
__emit(0xCC) ;
//正常运行时, Fun函数会跳过这条指令。
//调试时,调试器会不停收到int 3异常,程序崩溃
mov bDebug,1
}
return bDebug;
}
int main(){
SetUnhandledExceptionFilter(Fun);
if (CheckDebug_ SetUnhandledExceptionFilter()){
printf("发现调试器\n");
}
else{
printf("正常运行\n");
}
getchar();
return 0;
}
忽略异常即可
int 2d
时内核模式下触发异常的,用户模式下正常运行时触发异常,OD有如下特点
int 2d
被忽略int 2d
下一条指令的第一个字节被忽略int 2d
时,不会停在下条指令开始的地方,而是运行到断点; install SEH
push handler
push DWORD PTR fs:[0]
mov DWORD PTR fs:[0],esp
int 0x2d
nop
mov isDebugged,1
jmp normal
handler:
mov eax,dword ptr ss:[esp+0xc] ; exception record
mov dword ptr ds:[eax+0xb8],offset normal ; 修改EIP
xor eax,eax
retn
normal:
pop dword ptr fs:[0]
add esp,4
破解:把int 2d
改为int 3
这种OD不会忽略的异常
同一个进程不允许同时被两个进程调试,可以先调试自己,防止被调试器调试。
通过创建共享对象来确定是否第一次运行
int main()
{
//尝试打开互斥体。确定是否首次运行程序.
HHANDLE hMutex = OpenMutex(MUTEX_MODIFY_STATE, FALSE, L"Globa1\\MyMutex");
if (hMutex){
//打开成功说明第2次运行,执行正常代码.
printf("正被调试运行!\n");
getchar();
}else{
//打开失败说明第1次运行。创建互斥体,并调试创建自身进程
Createluter(NULL,FALSE,L"G1oba1\\MyMutex");
TCHAR szPath[MAX _PATH] = 0;
GetModuleFileName(NULL,szPath,MAX_PATH);
//调试方式打开程序.
STARTUPINFO si = { sizeof (STARTUPINFO) }:.
PROCESS_INFORMATION pi = 0;
BOOL bStatus = CreateProcess(szPath, NULL, NULL,NULL, FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS| CREATE_NEW_CONSOLE,NULL,NULL,&si, &pi);
if(!bStatus) {
printf("创建调试进程失败!\n");
return 0;
}
//初始化调试事件结构体,
DEBUG_ EVENT DbgEvent = { 0 };
DWORD dwState = DBG_EXCEPTION_NOT_HANDLED;
//等待目标exe产生调试事件.
BOOL bExit = FALSE;
while(!bExit) {.
WaitForDebugEvent (&DbEvent,INFINITE);
if (DbgEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT){
//被调试进程退出.
bExit = TRUE;
}
ContinueDebugEvent (DbgEvent.dwProcessId, DbgEvent.dwThreadId,dwState)
}
}
还可以选择正常创建自身后附加到进程DebugActiveProcess
int main(){
HANDLE hMutex = OpenMutex(MUTEX _MODIFY_ STATE, FALSE, L"G1oba1\\MyMutex"){
if (hMutex){
printf("正被调试运行!\n");
setchar();
}else{
//打开失败说明第1次运行,创建互斥体,并调试创建自身进程
CreatelMutex(NULL,FALSE, L"Global\\MyMutex");
TCHAR szPath[MAX PATH]={};
GetMloduleFi1eName(NULL,szPath, MAX_PATH);
STARTUPINFO si = { sizeof (STARTUPINFO) };
PROCESS_INFORMATION pi = {};
//正常创建。后面附加调试
BOOL bStatus = CreateProcess(szPath,NULL,NULL, NULL, FALSE,CREATE_NEW_CONSOLE,NULL,NULL, &si, &pi);
if (!bStatus){
printf("创建进程失败!\n" );
return 0;
}
if (!DebuzActiveProcess(pi.dwProcessId) {
printf("附加进程失败!\n");
return 0;
}
DEBUG_EVENT DbgEvent = { 0 };
DWORD dwState = DBG_EXCEPTION_NOT_HANDLED;
//等待目标exe产生调试事件
BOOL bExit = FALSE;
while (!bExit) {
WaitForDebuzEvent(&DbEvent,INFINITE);.
if (DbgEvent.dwDebugEventCode == EXII_PROCESS_DEBUG_EVENT){
bExit = TRUE;
}
ContinueDebuzEvent (DbgEvent.dwProcessId, DbgEvent.dwThreadId, dwState)
}
return 0;
}
return 0;
}
依然是同一个进程不允许同时被两个进程调试,所以父进程创建一个子进程并运行可以避免子进程被调试,绕过方法略微麻烦,但是依然可以绕过。
https://www.freebuf.com/articles/others-articles/181085.html