反调试——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;