反调试——5——检测调试对象
有一些内容采用的是WRK里面的定义。因为这个算是没有公开的文档,公开的不能这样使用。
查询父进程实现反调试
正常打开(双击运行)的程序的父进程是explorer.exe(资源管理器)(Windows的内置机制),通过查询父进程的ID是否是explorer.exe来判断程序是否被调试了。
这里我直接上代码了。
NTSTATUS
NtQueryInformationProcess(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out_bcount(ProcessInformationLength) PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
)
#include<iostream>
#include<Windows.h>
#include<winternl.h>
#include<tlhelp32.h>
#include<string.h>
using namespace std;
typedef struct _PROCESS_BASIC_INFORMATION_1 {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION_1;
//定义函数指针
typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out_bcount(ProcessInformationLength) PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
);
_NtQueryInformationProcess NtQueryInformationProcess_My;
int main()
{
//获得NtQueryInformation函数指针
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");//因为每个程序都会有ntdll,所以就不用动态loadlibrary加载了,直接获取dll的句柄
if (hNtdll == NULL)
{
cout << "获取ntdll句柄错误" << endl;
return 0;
}
NtQueryInformationProcess_My = (_NtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess");
//获取当前进程的信息
HANDLE hCurrentProcess = GetCurrentProcess();
PROCESS_BASIC_INFORMATION_1 CurrentInfo = { 0 };
NtQueryInformationProcess_My(hCurrentProcess, ProcessBasicInformation, &CurrentInfo,sizeof(CurrentInfo),NULL);
auto CurrentParentProcessId = CurrentInfo.InheritedFromUniqueProcessId;
//遍历所有进程信息,对比进程ID
PROCESSENTRY32 SnapshotInfo = { 0 };
SnapshotInfo.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
cout << "创建进程快照失败" << endl;
return 0;
}
auto Sucess = Process32First(hSnapshot, &SnapshotInfo);
if (Sucess == TRUE)
{
do {
if (wcscmp(SnapshotInfo.szExeFile, L"explorer.exe")==0)//这里使用宽字符
{
if (SnapshotInfo.th32ProcessID == CurrentParentProcessId)
{
cout << "程序通过父进程检测,程序的父进程正常" << endl;
}
else
{
cout << "检测到父进程不对,程序正在被调试" << endl;
}
break;
}
} while (Process32Next(hSnapshot, &SnapshotInfo));
}
else
{
cout << "获取第一个进程信息失败" << endl;
return 0;
}
system("pause");
return 0;
}
查询调试内核调试对象
当调试器调试某进程时,会创建一个内核的调试对象,通过检测它是否存在来实现反调试。(这里就采用WRK里面的内容了,官方文档的内容不好使。)
通过NtQueryObject可以检测是否存在调试对象,通过这个东西来看程序是否在被调试。
__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryObject(
HANDLE Handle,
OBJECT_INFORMATION_CLASS ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength
);//官方文档版本
NTSTATUS
NtQueryObject (
__in HANDLE Handle,//需要查询的句柄
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,
//查询对象枚举类
__out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
//输出结构缓冲区
__in ULONG ObjectInformationLength,
//缓冲区大小
__out_opt PULONG ReturnLength
//实际需要大小
);//wrk版本
NtQueryObject中的OBJECT_INFORMATION_CLASS参数:
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,//所有内核对象类型信息
ObjectHandleFlagInformation,
ObjectSessionInformation,
MaxObjectInfoClass // MaxObjectInfoClass should always be the last enum
} OBJECT_INFORMATION_CLASS;
//UNICODE_STRING结构体
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;
然后初步代码是这样:
#include"Anti-Debug.h"
int main()
{
//先获取ntdll的句柄
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
//获取函数指针
NtQueryObject = (_NtQueryObject)GetProcAddress(hNtdll, "NtQueryObject");
//来一个很大的缓冲区
char* buffer = (char*)malloc(0x4000);
DWORD realSize = 0;
auto ret = NtQueryObject(NULL, ObjectTypesInformation, buffer, 0x4000, &realSize);
if(ret != 0)
{
printf("NtQueryObject error\n");
}
CloseHandle(hNtdll);
return 0;
}
//Anti-Debug.h
#pragma once
#include<iostream>
#include<Windows.h>
using namespace std;
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,
ObjectHandleFlagInformation,
ObjectSessionInformation,
MaxObjectInfoClass // MaxObjectInfoClass should always be the last enum
} OBJECT_INFORMATION_CLASS;
typedef NTSTATUS(NTAPI* _NtQueryObject)(
__in HANDLE Handle,
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,
__out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
__in ULONG ObjectInformationLength,
__out_opt PULONG ReturnLength
);
_NtQueryObject NtQueryObject;
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;
然后来通过对这个buffer进行一个处理。
处理缓冲区
这个缓冲区是一个比较麻烦的结构,存放了所有内核对象的内容。
typedef struct _OBJECT_TYPES_INFORMATION
{
ULONG numberOfTypesInfo;
OBJECT_TYPE_INFORMATION typeInfo[1];
};
//也就是说TYPES包含了type在里面,因为TYPES是所有的type的内容
//缓冲区结构体
// __out_bcount_opt(ObjectInformationLength) PVOID //ObjectInformation,
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;
然后大致上是完成了:
#include"Anti-Debug.h"
int main()
{
//先获取ntdll的句柄
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
//获取函数指针
NtQueryObject = (_NtQueryObject)GetProcAddress(hNtdll, "NtQueryObject");
//来一个很大的缓冲区
char* buffer = (char*)malloc(0x4000);
DWORD realSize = 0;
auto ret = NtQueryObject(NULL, ObjectTypesInformation, buffer, 0x4000, &realSize);
if(ret != 0)
{
printf("NtQueryObject error\n");
return 0;
}
//定义TYPES对于的结构体来获取缓冲区的内容
POBJECT_TYPES_INFORMATION typesInfo = NULL;
typesInfo = (POBJECT_TYPES_INFORMATION)buffer;
//循环遍历typesInfo
POBJECT_TYPE_INFORMATION typeInfo = typesInfo->typeInfo;
//采用单个的type结构体来获取每个type的内容
for(int i = 0; i < typesInfo->numberOfTypesInfo; i++)
{
if (wcscmp(L"DebugObject", typeInfo->TypeName.Buffer)==0)//是调试类型的内核对象
{
if (typeInfo->TotalNumberOfObjects > 0)
{
cout << "有调试内核对象,正在进行调试" << endl;
}
else
{
cout << "没有检测到内核调试对象" << endl;
}
break;
typeInfo++;
}
}
return 0;
}
但是有一个问题出现了,由于typeinfo这个单type的内容的结构体里面有一个UNICODE_STRING字段这个字段里面又有个buffer,这个buffer根据内容的不同而大小不同,所以不能直接++
//修正指针偏移
DWORD temp = typeInfo->TypeName.MaximumLength;
temp +=temp % 4;
typeInfo = (POBJECT_TYPE_INFORMATION)((DWORD)typeInfo + temp);
typeInfo++;
最终版本:
#include"Anti-Debug.h"
int main()
{
//先获取ntdll的句柄
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
//获取函数指针
NtQueryObject = (_NtQueryObject)GetProcAddress(hNtdll, "NtQueryObject");
//来一个很大的缓冲区
char* buffer = (char*)malloc(0x4000);
DWORD realSize = 0;
auto ret = NtQueryObject(NULL, ObjectTypesInformation, buffer, 0x4000, &realSize);
if(ret != 0)
{
printf("NtQueryObject error\n");
return 0;
}
//定义TYPES对于的结构体来获取缓冲区的内容
POBJECT_TYPES_INFORMATION typesInfo = NULL;
typesInfo = (POBJECT_TYPES_INFORMATION)buffer;
//循环遍历typesInfo
POBJECT_TYPE_INFORMATION typeInfo = typesInfo->typeInfo;
//采用单个的type结构体来获取每个type的内容
for(int i = 0; i < typesInfo->numberOfTypesInfo; i++)
{
if (wcscmp(L"DebugObject", typeInfo->TypeName.Buffer)==0)//是调试类型的内核对象
{
if (typeInfo->TotalNumberOfObjects > 0)
{
cout << "有调试内核对象,正在进行调试" << endl;
}
else
{
cout << "没有检测到内核调试对象" << endl;
}
break;
}
DWORD temp = typeInfo->TypeName.MaximumLength;
temp +=temp % 4;
typeInfo = (POBJECT_TYPE_INFORMATION)((DWORD)typeInfo + temp);
typeInfo++;
}
return 0;
}
//头文件
#pragma once
#include<iostream>
#include<Windows.h>
using namespace std;
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,
ObjectHandleFlagInformation,
ObjectSessionInformation,
MaxObjectInfoClass // MaxObjectInfoClass should always be the last enum
} OBJECT_INFORMATION_CLASS;
typedef NTSTATUS(NTAPI* _NtQueryObject)(
__in HANDLE Handle,
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,
__out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
__in ULONG ObjectInformationLength,
__out_opt PULONG ReturnLength
);
_NtQueryObject NtQueryObject;
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;