文章目录
NtQueryInformationProcess()
下面介绍一种利用NtQuertInformationProcess()API探测调试器的技术。通过NtQuertInformationProcess()API可以获取各种与进程相关的信息。函数定义如下
NtQueryInformationProcess (
IN HANDLE ProcessHandle, // 进程句柄
IN PROCESSINFOCLASS InformationClass, // 信息类型
OUT PVOID ProcessInformation, // 缓冲指针
IN ULONG ProcessInformationLength, // 以字节为单位的缓冲大小
OUT PULONG ReturnLength OPTIONAL // 写入缓冲的字节数
);
第一个参数是希望操作的进程句柄,这个句柄必须以PROCESS_QUERY_INFORMATION
模式存取。为了取得一个句柄,我们必须用OpenProcess
函数:
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,dwProcessID);
第二个参数是请求信息的类型,这个参数可以有许多个值,为这个参数指定特定值并去调用函数,相关信息就会设置到第三个参数PVOID ProcessInformation
。
因此,如果第二个参数是ProcessBasicInformation
的话,则第三个参数必须为一个指针指向结构PROCESS_BASIC_INFORMATION
。
typedef struct
{
DWORD ExitStatus; // 接收进程终止状态
DWORD PebBaseAddress; // 接收进程环境块地址
DWORD AffinityMask; // 接收进程关联掩码
DWORD BasePriority; // 接收进程的优先级类
ULONG UniqueProcessId; // 接收进程ID
ULONG InheritedFromUniqueProcessId; //接收父进程ID
} PROCESS_BASIC_INFORMATION;
PROCESSINFOCLASS
是枚举类型,拥有的值如下所示:
enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort = 7, //0x7
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
MaxProcessInfoClass,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessDebugObjectHandle = 30, //0x1E
ProcessDebugFlags = 31, //0x1F
SystemKernelDebuggerInformation = 35
};
以上代码中与调试器探测相关的成员为ProcessDebugPort
(0x7),ProcessDebugObjectHandle
(0x1E),ProcessDebugFlags
(0x1F)
ProcessDebugPort
(0x7)
进程处于调试状态时,系统就会为它分配一个调试端口(Debug Port)。PROCESSINFOCLASS
参数的值设置为 ProcessDebugPort
(0x7)时,调用NtQueryInformationProcess()
函数就能获取调试端口。若进程处于非调试状态,则变量 ProcessDebugPort
的值设置为0;若进程处于调试状态,则变量 ProcessDebugPort
的值设置为0xFFFFFFFF
ProcessDebugPort=0x7;
DWORD dwDebugPort = 0;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugPort,
&dwDebugPort,
sizeof(dwDebugPort),
NULL);
printf("NtQueryInformationProcess(ProcessDebugPort) = 0x%X\n", dwDebugPort);
if( dwDebugPort != 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
CheckRemoteDebuggerPresent()
CheckRemoteDebuggerPresent()
API与IsdebuggerPresent()
API类似,用来检测进程是否处于调试状态。CheckRemoteDebuggerPresent()
函数不仅可以用来检测当前进程,还可以用来检测其它进程是否处于被调试状态。
CheckRemoteDebuggerPresent()
内部代码如下:
这个参数7即 ProcessDebugPort
ProcessDebugObjectHandle(0x1E)
调试进程时会生成调试对象(Debug Object)。函数的第二个参数值为 ProcessDebugObjectHandle
(0x1E)时,调用函数后通过第三个参数就能获取调用对象句柄。进程属于调试状态时,调试对象句柄就存在;若进程处于非调试状态,则调试对象句柄值为NULL
。
ProcessDebugObjectHandle=0x1E;
HANDLE hDebugObject = NULL;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugObjectHandle,
&hDebugObject,
sizeof(hDebugObject),
NULL);
printf("NtQueryInformationProcess(ProcessDebugObjectHandle) = 0x%X\n", hDebugObject);
if( hDebugObject != 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
ProcessDebugFlags(0x1F)
检测Debug Flags(调试标志)的值也可以判断进程是否处于被调试状态,函数的第二个参数设置为ProcessDebugFlags
(0x1F)时,调用函数后通过第三个参数即可获取调试标志的值:若为,则进程处于被调试状态;若为1,则进程处于非调试状态。
ProcessDebugFlags=0x1F;
BOOL bDebugFlag = TRUE;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugFlags,
&bDebugFlag,
sizeof(bDebugFlag),
NULL);
printf("NtQueryInformationProcess(ProcessDebugFlags) = 0x%X\n", bDebugFlag);
if( bDebugFlag == 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
}
调试程序代码
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort = 7,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
MaxProcessInfoClass,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessDebugObjectHandle = 30,
ProcessDebugFlags = 31,
SystemKernelDebuggerInformation = 35
};
void MyNtQueryInformationProcess()
{
typedef NTSTATUS (WINAPI *NTQUERYINFORMATIONPROCESS)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
NTQUERYINFORMATIONPROCESS pNtQueryInformationProcess = NULL;
pNtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS)
GetProcAddress(GetModuleHandle(L"ntdll.dll"),
"NtQueryInformationProcess");
// ProcessDebugPort (0x7)
DWORD dwDebugPort = 0;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugPort,
&dwDebugPort,
sizeof(dwDebugPort),
NULL);
printf("NtQueryInformationProcess(ProcessDebugPort) = 0x%X\n", dwDebugPort);
if( dwDebugPort != 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
// ProcessDebugObjectHandle (0x1E)
HANDLE hDebugObject = NULL;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugObjectHandle,
&hDebugObject,
sizeof(hDebugObject),
NULL);
printf("NtQueryInformationProcess(ProcessDebugObjectHandle) = 0x%X\n", hDebugObject);
if( hDebugObject != 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
// ProcessDebugFlags (0x1F)
BOOL bDebugFlag = TRUE;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugFlags,
&bDebugFlag,
sizeof(bDebugFlag),
NULL);
printf("NtQueryInformationProcess(ProcessDebugFlags) = 0x%X\n", bDebugFlag);
if( bDebugFlag == 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
MyNtQueryInformationProcess();
printf("\npress any key to quit...\n");
_gettch();
return 0;
}
strongOD->Options中的*KernelMode可以绕过。
NtQuerySystemInformation()
这是基于调试环境检测的反调试技术。
注意:
前面所介绍的反调试技术,我们通过探测调试器来判断自己的进程是否处于被调试状态,这是一种非常直接的调试器探测方法。除此之外,还有间接探测调试器的方法,借助该方法可以检测调试环境,若显露出调试器的端倪,则立刻停止执行程序。
运用这种反调试技术可以检测当前OS是否在调试模式下运行。
OS的调试模式:
为了使用winDbg工具调试系统内核(Kernel Debugging),需要先准备2个系统(Host Target)并连接(Serial,1394,USB,Direct Cable)。其中,Target的OS以调试模式运行,连接到Host系统的WinDbg上台后即可调试。
设置调试模式方法:
windows XP:编辑"C:\boot.ini"
windows 7:使用bcdedit.exe实用程序
ntdll!NtQuerySystemInformation()
API是一个系统函数,用来获取当前运行的多种OS信息。
typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)
(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL);
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
第一个参数是dwRecordType,这个参数指定了我们所查询的系统信息类型,为了查询系统HANDLE列表,我们定义一个常量#define NT_HANDLE_LIST 16(这个数值我是查资料得到的,如果谁有更详细的资料,也请让我共享一下)。
第二个参数是一个指针,这个指针用来返回系统句柄列表,在调用NtQuerySystemInformation函数之前,必须为这个指针分配足够的内存空间,否则函数调用会出错。
第三个参数是指定你为HandleList所分配的内存空间大小,单位是byte。
第四个参数是NtQuerySystemInformation返回的HandleList的大小;如果NtQuerySystemInformation函数调用成功,返回值将是0,否则可以使用GetLastError()获得详细的错误代码。
SYSTEM_INFORMATION_CLASS 是枚举类型,拥有的值如下:
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,// 0 Y N
SystemProcessorInformation,// 1 Y N
SystemPerformanceInformation,// 2 Y N
SystemTimeOfDayInformation,// 3 Y N
SystemNotImplemented1,// 4 Y N // SystemPathInformation
SystemProcessesAndThreadsInformation,// 5 Y N
SystemCallCounts,// 6 Y N
SystemConfigurationInformation,// 7 Y N
SystemProcessorTimes,// 8 Y N
SystemGlobalFlag,// 9 Y Y
SystemNotImplemented2,// 10 YN // SystemCallTimeInformation
SystemModuleInformation,// 11 YN
SystemLockInformation,// 12 YN
SystemNotImplemented3,// 13 YN // SystemStackTraceInformation
SystemNotImplemented4,// 14 YN // SystemPagedPoolInformation
SystemNotImplemented5,// 15 YN // SystemNonPagedPoolInformation
SystemHandleInformation,// 16 YN
SystemObjectInformation,// 17 YN
SystemPagefileInformation,// 18 YN
SystemInstructionEmulationCounts,// 19 YN
SystemInvalidInfoClass1,// 20
SystemCacheInformation,// 21 YY
SystemPoolTagInformation,// 22 YN
SystemProcessorStatistics,// 23 YN
SystemDpcInformation,// 24 YY
SystemNotImplemented6,// 25 YN // SystemFullMemoryInformation
SystemLoadImage,// 26 NY // SystemLoadGdiDriverInformation
SystemUnloadImage,// 27 NY
SystemTimeAdjustment,// 28 YY
SystemNotImplemented7,// 29 YN // SystemSummaryMemoryInformation
SystemNotImplemented8,// 30 YN // SystemNextEventIdInformation
SystemNotImplemented9,// 31 YN // SystemEventIdsInformation
SystemCrashDumpInformation,// 32 YN
SystemExceptionInformation,// 33 YN
SystemCrashDumpStateInformation,// 34 YY/N
SystemKernelDebuggerInformation,// 35 YN //0x23
SystemContextSwitchInformation,// 36 YN
SystemRegistryQuotaInformation,// 37 YY
SystemLoadAndCallImage,// 38 NY // SystemExtendServiceTableInformation
SystemPrioritySeparation,// 39 NY
SystemNotImplemented10,// 40 YN // SystemPlugPlayBusInformation
SystemNotImplemented11,// 41 YN // SystemDockInformation
SystemInvalidInfoClass2,// 42 // SystemPowerInformation
SystemInvalidInfoClass3,// 43 // SystemProcessorSpeedInformation
SystemTimeZoneInformation,// 44 YN
SystemLookasideInformation,// 45 YN
SystemSetTimeSlipEvent,// 46 NY
SystemCreateSession,// 47 NY
SystemDeleteSession,// 48 NY
SystemInvalidInfoClass4,// 49
SystemRangeStartInformation,// 50 YN
SystemVerifierInformation,// 51 YY
SystemAddVerifier,// 52 NY
SystemSessionProcessesInformation// 53 YN
} SYSTEM_INFORMATION_CLASS;
向SystemInformationClass
参数传入SystemKernelDebuggerInformation
值(0x23
)即可判断当前OS在调试模式下运行
SystemKernelDebuggerInformation
(0x23
)
void MyNtQuerySystemInformation()
{
//检测当前OS是否运行在调试模式下,WinDbg
typedef NTSTATUS (WINAPI *NTQUERYSYSTEMINFORMATION)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
BOOLEAN DebuggerEnabled;
BOOLEAN DebuggerNotPresent;
} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION;
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)
GetProcAddress(GetModuleHandle(L"ntdll"),
"NtQuerySystemInformation");
ULONG SystemKernelDebuggerInformation = 0x23;
ULONG ulReturnedLength = 0;
SYSTEM_KERNEL_DEBUGGER_INFORMATION DebuggerInfo = {
0,};
NtQuerySystemInformation(SystemKernelDebuggerInformation,
(PVOID) &DebuggerInfo,
sizeof(DebuggerInfo), // 2 bytes
&ulReturnedLength);
printf("NtQuerySystemInformation(SystemKernelDebuggerInformation) = 0x%X 0x%X\n",
DebuggerInfo.DebuggerEnabled, DebuggerInfo.DebuggerNotPresent);
if( DebuggerInfo.DebuggerEnabled ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
}
在上述代码中调用NtQuerySystemInformation()
API时,第一个参数(SystemInformaticationClass)的值设置为SystemKernelDebuggerInformation(0x23),第二个参数(SystemInformation)为SYSTEM_KERNEL_DEBUGGER_INFORMATION结构体的地址。当API返回时,若系统处在调试模式下,则SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnable的值设置为1(SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnable的值恒为1)
破解之法
在Windows XP 系统中编辑boot.ini文件,删除“/debugport=coml /baudrate=115200 /debug”值。在windows 7系统的命令行窗口中执行“bcdedit /debug off”命令即可。并且,若重启系统则要以正常模式启动。
NTQueryObject()
系统中的某个调试器调试进程时,会创建1个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试。
ntdll!NTQueryObject()
API用来获取系统各种内核对象的信息,NTQueryObject()函数的定义如下
NTSTATUS NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS objectInformationClass,
_Out_opt_ PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength
);
调用NTQueryObject()
函数前,先向第二个参数 OBJECT_INFORMATION_CLASS objectInformationClass
赋于某个特定的值,调用API后,包含相关信息的结构指针就被返回到第三个参数PVOID ObjectInformation
。
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllInformation,//3
ObjectDataInformation
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
首先使用ObjectAllTypeInformation
值获取系统所有对象的信息,然后从中检测是否存在调试对象。NtQueryObject()
API使用方法复杂。
1.获取内核对象信息链表的大小
ULONG lSize = 0;
pNtQueryObject(NULL, ObjectAllTypesInformation, &lSize, sizeof(lSize), &lSize);
2.分配内存
void *pBuf = NULL;
pBuf = VirtualAlloc(NULL, lSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
3.获取内核对象信息链表
typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfHandles;
ULONG TotalNumberOfObjects;
}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION {
ULONG NumberOfObjectsTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
pNtQueryObject((HANDLE)0xFFFFFFFF, ObjectAllTypesInformation, pBuf, lSize, NULL);
POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION)pBuf;
调用NtQueryObject()
函数后,系统的所有对象的信息代码就被存入pBuf
,然后将pBuf
转换(casing
)为POBJECT_ALL_INFORMATION
类型。POBJECT_ALL_INFORMATION
结构体_OBJECT_TYPE_INFORMATION
结构体数组构成。实际内核对象类型的信息就被存储在_OBJECT_TYPE_INFORMATION
结构体数组中,然后通过循环检索即可查看是否存在“调试对象”对象类型。
完整代码:
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllTypesInformation,
ObjectHandleInformation
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
void MyNtQueryObject()
{
//基于检测调试环境
//系统中的某个调试器调试进程时,会创建1个调试对象类型的内核对象。检测该对象
//是否存在即可判断是否有进程正在被调试(注意不是当前进程)。
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
typedef NTSTATUS (WINAPI *NTQUERYOBJECT)(
HANDLE Handle,
OBJECT_INFORMATION_CLASS ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength
);
#pragma pack(1)
typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfHandles;
ULONG TotalNumberOfObjects;
}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION {
ULONG NumberOfObjectsTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
#pragma pack()
POBJECT_ALL_INFORMATION pObjectAllInfo = NULL;
void *pBuf = NULL;
ULONG lSize = 0;
BOOL bDebugging = FALSE;
NTQUERYOBJECT pNtQueryObject = (NTQUERYOBJECT)
GetProcAddress(GetModuleHandle(L"ntdll.dll"),
"NtQueryObject");
// Get the size of the list
pNtQueryObject(NULL, ObjectAllTypesInformation, &lSize, sizeof(lSize), &lSize);
// Allocate list buffer
pBuf = VirtualAlloc(NULL, lSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
// Get the actual list
pNtQueryObject((HANDLE)0xFFFFFFFF, ObjectAllTypesInformation, pBuf, lSize, NULL);
pObjectAllInfo = (POBJECT_ALL_INFORMATION)pBuf;
UCHAR *pObjInfoLocation = (UCHAR *)pObjectAllInfo->ObjectTypeInformation;
POBJECT_TYPE_INFORMATION pObjectTypeInfo = NULL;
for( UINT i = 0; i < pObjectAllInfo->NumberOfObjectsTypes; i++ )
{
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjInfoLocation;
if( wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0 )
{
bDebugging = (pObjectTypeInfo->TotalNumberOfObjects > 0) ? TRUE : FALSE;
break;
}
// calculate next struct
pObjInfoLocation = (UCHAR*)pObjectTypeInfo->TypeName.Buffer;
pObjInfoLocation += pObjectTypeInfo->TypeName.Length;
pObjInfoLocation = (UCHAR*)(((ULONG)pObjInfoLocation & 0xFFFFFFFC) + sizeof(ULONG));
}
if( pBuf )
VirtualFree(pBuf, 0, MEM_RELEASE);
printf("NtQueryObject(ObjectAllTypesInformation)\n");
if( bDebugging ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
MyNtQueryObject();
printf("\npress any key to quit...\n");
_gettch();
return 0;
}
strongOD->Options中的*KernelMode可以绕过。
破解之法
位于0xCB184A
地址处的call dword ptr ss:[ebp-0x3C]
指令是用来调用ntdll.ZwQueryObject()
API的,
此时查看栈可以发现,第二个参数值为ObjectAllTypesInformation(3)
,将该值修改为0后再执行0xCB184A
地址处的指令,这样就无法探测到调试器的存在了
或者直接把这个API给勾取,输入ObjectAllTypesInformation(3)
值或者直接操作结果值,也不会被探测到
反调试技术系列:
静态反调试技术(1)https://blog.csdn.net/CSNN2019/article/details/113105292
静态反调试技术(2)https://blog.csdn.net/CSNN2019/article/details/113147820
静态反调试技术(3)https://blog.csdn.net/CSNN2019/article/details/113178232
动态反调试技术 https://blog.csdn.net/CSNN2019/article/details/113181558
高级反调试技术 https://blog.csdn.net/CSNN2019/article/details/113263215