DebugObject 对象
一、检测 DebugObject 对象
当调试器附加或打开一个程序建立调试的时候,会先创建一个调试对象 DebugObject
,通过这个调试对象与被调试进程进行交互,因此我们可以对该对象进行检测来判断是否有调试器正在调试。
1 通过 NtQueryObject 函数来获取调试对象
我们可以通过 NtQueryObject
函数指定枚举常量 ObjectTypesInformation(3)
来对所有的内核对象进行查询,看是否存在调试对象 DebugOBject
:
#include <stdio.h>
#include <Windows.h>
// 查询的类型枚举常量
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,
ObjectHandleFlagInformation,
ObjectSessionInformation,
MaxObjectInfoClass // MaxObjectInfoClass should always be the last enum
} OBJECT_INFORMATION_CLASS;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT* Buffer;
#else // MIDL_PASS
PWSTR Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
// 单个内核对象结构体
typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;
// 接收所有查询到的内核对象的结构体
typedef struct _OBJECT_TYPES_INFORMATION
{
ULONG numberOfTypesInfo;
OBJECT_TYPE_INFORMATION typeInfo[1];
}OBJECT_TYPES_INFORMATION, * POBJECT_TYPES_INFORMATION;
typedef NTSTATUS(NTAPI* PFN_NtQueryObject)(
__in HANDLE Handle,
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,
__out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
__in ULONG ObjectInformationLength,
__out_opt PULONG ReturnLength
);
PFN_NtQueryObject NtQueryObject;
int main()
{
// 获取 NtQueryObject 函数
NtQueryObject = (PFN_NtQueryObject)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryObject");
// 查询内核对象
POBJECT_TYPES_INFORMATION typesInfo = (POBJECT_TYPES_INFORMATION)malloc(0x4000);
DWORD realSize = 0;
NTSTATUS ret = NtQueryObject(NULL, ObjectTypesInformation, typesInfo, 0x4000, &realSize);
if (ret != 0)
{
printf("NtQueryObject error\r\n");
return -1;
}
// 遍历所有内核对象
BOOL isDebugged = FALSE;
POBJECT_TYPE_INFORMATION typeInfo = typesInfo->typeInfo;
printf("遍历所有内核对象:\r\n");
for (ULONG i = 0; i < typesInfo->numberOfTypesInfo; i++)
{
wprintf(L"%ls\r\n", typeInfo->TypeName.Buffer);
if (wcscmp(L"DebugObject", typeInfo->TypeName.Buffer) == 0)
{
if (typeInfo->TotalNumberOfObjects > 0)
{
isDebugged = TRUE;
printf("检测到了调试对象 DebugObject,数量为:%d 个\r\n", typeInfo->TotalNumberOfObjects);
}
//break;
}
// 指向下一个内核对象结构体
// 先计算结构体对齐后的长度
ULONG_PTR structLen =sizeof(OBJECT_TYPE_INFORMATION) + typeInfo->TypeName.MaximumLength;
#ifdef _WIN64
// 64 位系统下上面的结构体按照 8 字节对齐
if (structLen % 8 != 0)
{
structLen = int(structLen / 8) * 8 + 8;
}
typeInfo = (POBJECT_TYPE_INFORMATION)((ULONG_PTR)typeInfo + structLen);
#else
// 32 位系统下上面的结构体按照 4 字节对齐
if (structLen % 4 != 0)
{
structLen = int(structLen / 4) * 4 + 4;
}
typeInfo = (POBJECT_TYPE_INFORMATION)((ULONG_PTR)typeInfo + structLen);
#endif
}
if (!isDebugged)
{
printf("未检测到调试对象\r\n");
}
system("pause");
}
需要注意的是,我们查询返回的结构体数组里面的结构体是经过结构体对齐的,我们需要根据不同系统下结构体的对齐长度来对其进行正确解析。
当我们用 VS 编译器进行调试的时候,查询的结果如下: