LegendSaber 发表于 2021-10-8 11:35

通过句柄表实现反调试

本帖最后由 LegendSaber 于 2021-10-21 09:29 编辑

一.调试原理
大家应该都知道,所有创建出来的对象,比如进程,线程,互斥体等等其实都是内核对象,在内核中的特定地址保存着这些对象的结构体。而在对这些内核对象进行操作的时候,只要稍微操作不当,那就非常容易造成蓝屏。所以为了程序员的开发方便,Windows提供了一系列API供大家使用以打开这些内核对象获取句柄对象,这些句柄用特定的数字来代表,这就避免了程序员直接修改内核对象。
为了可以通过这些句柄对象找到内核中的具体的句柄对象的地址,在内核层,操作系统为每个进程都分配了一张句柄表,这张句柄表里面保存的就是这个进程有拥有的所有句柄对象的真实地址。
调试器调试一个程序的时候,调试器就会获得进程的句柄以供后面的调试使用。所以我们就可以通过遍历所有进程,查找进程中的句柄表中是否有我们运行的程序的句柄依次来判断程序是否正在被调试器以附加的方式调试。
二.句柄表查找方式
在内核中,每一个进程都有它自己特定的EPROCESS结构,EPROCESS结构体的定义如下

kd> dt _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb            : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId: Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY
   +0x090 QuotaUsage       : Uint4B
   +0x09c QuotaPeak      : Uint4B
   +0x0a8 CommitCharge   : Uint4B
   +0x0ac PeakVirtualSize: Uint4B
   +0x0b0 VirtualSize      : Uint4B
   +0x0b4 SessionProcessLinks : _LIST_ENTRY
   +0x0bc DebugPort      : Ptr32 Void
   +0x0c0 ExceptionPort    : Ptr32 Void
   +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE//指向_HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : Uint4B
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : Uint4B
   +0x114 ForkInProgress   : Ptr32 _ETHREAD
   +0x118 HardwareTrigger: Uint4B
   +0x11c VadRoot          : Ptr32 Void
   +0x120 VadHint          : Ptr32 Void
   +0x124 CloneRoot      : Ptr32 Void
   +0x128 NumberOfPrivatePages : Uint4B
   +0x12c NumberOfLockedPages : Uint4B
   +0x130 Win32Process   : Ptr32 Void
   +0x134 Job            : Ptr32 _EJOB
   +0x138 SectionObject    : Ptr32 Void
   +0x13c SectionBaseAddress : Ptr32 Void
   +0x140 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch: Ptr32 _PAGEFAULT_HISTORY
   +0x148 Win32WindowStation : Ptr32 Void
   +0x14c InheritedFromUniqueProcessId : Ptr32 Void
   +0x150 LdtInformation   : Ptr32 Void
   +0x154 VadFreeHint      : Ptr32 Void
   +0x158 VdmObjects       : Ptr32 Void
   +0x15c DeviceMap      : Ptr32 Void
   +0x160 PhysicalVadList: _LIST_ENTRY
   +0x168 PageDirectoryPte : _HARDWARE_PTE_X86
   +0x168 Filler         : Uint8B
   +0x170 Session          : Ptr32 Void
   +0x174 ImageFileName    : UChar
   +0x184 JobLinks         : _LIST_ENTRY
   +0x18c LockedPagesList: Ptr32 Void
   +0x190 ThreadListHead   : _LIST_ENTRY
   +0x198 SecurityPort   : Ptr32 Void
   +0x19c PaeTop         : Ptr32 Void
   +0x1a0 ActiveThreads    : Uint4B
   +0x1a4 GrantedAccess    : Uint4B
   +0x1a8 DefaultHardErrorProcessing : Uint4B
   +0x1ac LastThreadExitStatus : Int4B
   +0x1b0 Peb            : Ptr32 _PEB
   +0x1b4 PrefetchTrace    : _EX_FAST_REF
   +0x1b8 ReadOperationCount : _LARGE_INTEGER
   +0x1c0 WriteOperationCount : _LARGE_INTEGER
   +0x1c8 OtherOperationCount : _LARGE_INTEGER
   +0x1d0 ReadTransferCount : _LARGE_INTEGER
   +0x1d8 WriteTransferCount : _LARGE_INTEGER
   +0x1e0 OtherTransferCount : _LARGE_INTEGER
   +0x1e8 CommitChargeLimit : Uint4B
   +0x1ec CommitChargePeak : Uint4B
   +0x1f0 AweInfo          : Ptr32 Void
   +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
   +0x1f8 Vm               : _MMSUPPORT
   +0x238 LastFaultCount   : Uint4B
   +0x23c ModifiedPageCount : Uint4B
   +0x240 NumberOfVads   : Uint4B
   +0x244 JobStatus      : Uint4B
   +0x248 Flags            : Uint4B
   +0x248 CreateReported   : Pos 0, 1 Bit
   +0x248 NoDebugInherit   : Pos 1, 1 Bit
   +0x248 ProcessExiting   : Pos 2, 1 Bit
   +0x248 ProcessDelete    : Pos 3, 1 Bit
   +0x248 Wow64SplitPages: Pos 4, 1 Bit
   +0x248 VmDeleted      : Pos 5, 1 Bit
   +0x248 OutswapEnabled   : Pos 6, 1 Bit
   +0x248 Outswapped       : Pos 7, 1 Bit
   +0x248 ForkFailed       : Pos 8, 1 Bit
   +0x248 HasPhysicalVad   : Pos 9, 1 Bit
   +0x248 AddressSpaceInitialized : Pos 10, 2 Bits
   +0x248 SetTimerResolution : Pos 12, 1 Bit
   +0x248 BreakOnTermination : Pos 13, 1 Bit
   +0x248 SessionCreationUnderway : Pos 14, 1 Bit
   +0x248 WriteWatch       : Pos 15, 1 Bit
   +0x248 ProcessInSession : Pos 16, 1 Bit
   +0x248 OverrideAddressSpace : Pos 17, 1 Bit
   +0x248 HasAddressSpace: Pos 18, 1 Bit
   +0x248 LaunchPrefetched : Pos 19, 1 Bit
   +0x248 InjectInpageErrors : Pos 20, 1 Bit
   +0x248 VmTopDown      : Pos 21, 1 Bit
   +0x248 Unused3          : Pos 22, 1 Bit
   +0x248 Unused4          : Pos 23, 1 Bit
   +0x248 VdmAllowed       : Pos 24, 1 Bit
   +0x248 Unused         : Pos 25, 5 Bits
   +0x248 Unused1          : Pos 30, 1 Bit
   +0x248 Unused2          : Pos 31, 1 Bit
   +0x24c ExitStatus       : Int4B
   +0x250 NextPageColor    : Uint2B
   +0x252 SubSystemMinorVersion : UChar
   +0x253 SubSystemMajorVersion : UChar
   +0x252 SubSystemVersion : Uint2B
   +0x254 PriorityClass    : UChar
   +0x255 WorkingSetAcquiredUnsafe : UChar
   +0x258 Cookie         : Uint4B
   
其中,在EPROCCESS结构体0xC4处保存了一个_HANDLE_TABLE的指针ObjectTable。这个ObjectTable中保存的就是这个进程对应的_HANDLE_TABLE的地址,而_HANDLE_TABLE结构体定义如下

kd> dt _HANDLE_TABLE
ntdll!_HANDLE_TABLE
   +0x000 TableCode      : Uint4B
   +0x004 QuotaProcess   : Ptr32 _EPROCESS
   +0x008 UniqueProcessId: Ptr32 Void
   +0x00c HandleTableLock: _EX_PUSH_LOCK
   +0x01c HandleTableList: _LIST_ENTRY
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo      : Ptr32 _HANDLE_TRACE_DEBUG_INFO
   +0x02c ExtraInfoPages   : Int4B
   +0x030 FirstFree      : Uint4B
   +0x034 LastFree         : Uint4B
   +0x038 NextHandleNeedingPool : Uint4B
   +0x03c HandleCount      : Int4B
   +0x040 Flags            : Uint4B
   +0x040 StrictFIFO       : Pos 0, 1 Bit

_HANDLE_TABLE偏移为0的地方保存着TableCode,这个TableCode保存的就是程序句柄表的位置。但是要注意,这个值的最后两位是用来表示程序句柄表的结构的。具体的表现形式可以参考下图

      
可以看到这些句柄表其实是八字节的数组,根据情况找到相应的句柄表以后,取出里面的八字节数据就可以得到相应的对象在内核中的位置。但是取出来的数据只有低31-3位用来保存地址,高32位以及最后0-2位保存的都是内核对象的各种属性。         
接下来用一段代码并结合WinDbg来查看内核对象来体会上述过程,代码如下
#include <cstdio>
#include <windows.h>

int main()
{
      HANDLE handle = NULL;
      int i = 0;


      for (i = 0; i < 50; i++)
      {
                handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 420);
                printf("%X\n", handle);
      }
      system("pause");

      return 0;
}         
这段代码很简单,就是获得50个进程句柄并输出句柄值,这个PID是系统中的记事本的PID。

查找的过程,首先是要在WinDbg中找到编写的这个程序的EPROCESS地址。



根据这个地址找到EPROCESS中的ObjectTable中保存的值。      



在根据这个值找到_HANDLE_TABLE结构体并查看TableCode。





可以看到,由于程序只获得了50个句柄,少于512个,所以TableCode的最后两位是0,那么这个TableCode的值就是句柄表的值。那么怎么知道程序的句柄对象是在句柄表中的哪个位置呢?首先看看程序输出的句柄值



   
以句柄值0xE4,将它除以4得到0x39。这个值其实就是句柄对象在句柄表中的索引,由于句柄表中每个值都是八个字节,所以我们应该在WinDbg中这样查看相应的值




取出低32位并将低3位清0就得到了一个地址。但是,此时这个地址其实是OBJECT_HEADER结构体地址,结构体的定义如下

kd>dt _OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount   : Int4B
   +0x004 HandleCount      : Int4B
   +0x004 NextToFree       : Ptr32 Void
   +0x008 Type             : Ptr32 _OBJECT_TYPE
   +0x00c NameInfoOffset   : UChar
   +0x00d HandleInfoOffset : UChar
   +0x00e QuotaInfoOffset: UChar
   +0x00f Flags            : UChar
   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD
   
其中,0x18的位置才是内核对象的具体内容,所以得到地址以后要+0x18才可以查看内核对象的内容如下

kd>dt 81a30bf0 + 0x18 _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb            : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x1d7bbe9`9e3e9b9a
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId: 0x000001a4 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x81c940a8 - 0x81d6b478 ]
   +0x090 QuotaUsage       : 0xa78
   +0x09c QuotaPeak      : 0xc20
   +0x0a8 CommitCharge   : 0x192
   +0x0ac PeakVirtualSize: 0x2451000
   +0x0b0 VirtualSize      : 0x1fbe000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x81c940d4 - 0x81d6b4a4 ]
   +0x0bc DebugPort      : (null)
   +0x0c0 ExceptionPort    : 0xe15bec38 Void
   +0x0c4 ObjectTable      : 0xe1a3a378 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : 0x16298
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : 0
   +0x114 ForkInProgress   : (null)
   +0x118 HardwareTrigger: 0
   +0x11c VadRoot          : 0x81d60ba0 Void
   +0x120 VadHint          : 0x81da73b0 Void
   +0x124 CloneRoot      : (null)
   +0x128 NumberOfPrivatePages : 0xce
   +0x12c NumberOfLockedPages : 0
   +0x130 Win32Process   : 0xe2ad2e68 Void
   +0x134 Job            : (null)
   +0x138 SectionObject    : 0xe2a4ca58 Void
   +0x13c SectionBaseAddress : 0x01000000 Void
   +0x140 QuotaBlock       : 0x81dab448 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch: (null)
   +0x148 Win32WindowStation : 0x0000003c Void
   +0x14c InheritedFromUniqueProcessId : 0x000005a8 Void
   +0x150 LdtInformation   : (null)
   +0x154 VadFreeHint      : (null)
   +0x158 VdmObjects       : (null)
   +0x15c DeviceMap      : 0xe159c150 Void
   +0x160 PhysicalVadList: _LIST_ENTRY [ 0x81a30d68 - 0x81a30d68 ]
   +0x168 PageDirectoryPte : _HARDWARE_PTE_X86
   +0x168 Filler         : 0
   +0x170 Session          : 0xf89cf000 Void
   +0x174 ImageFileName    : "notepad.exe"
      
可以看到ImageFileName是notepad.exe,说明找到的内核对象就是记事本的EPROCESS。
三.反调试的实现         
根据上面的内容可以得知,我们可以通过查找所有进程中的句柄表来查看是否有我们的进程,如果有就说明程序正在被调试,剩下的一个问题就是如何遍历所有的进程。         
在EPROCESS结构体0x88的位置保存的ActiveProcessLinks是_LIST_ENTRY结构体的,它是一个双向链表,定义如下。通过遍历这个双向链表就可以得到所有的进程,所以从EPROCESS的0x88的位置开始,我们就可以遍历所有进程。typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;   //指向下一个EPROCESS的ActiveProcessLinks
   struct _LIST_ENTRY *Blink;//指向上一个EPROCESS的ActiveProcessLinks
} LIST_ENTRY, *PLIST_ENTRY;         
需要注意的是,这里两个链表指向的都是另一个EPROCESS的ActiveProcessLinks,如下图所示。所以要获得另一个进程的EPROCESS需要再次减去0x88。


            

根据上面的内容就可以写出如下的检测调试器的代码

BOOLEAN IsDebugger()
{
      PEPROCESS pCurEPro = NULL, pTargetEPro = NULL;
      PCHAR pImageFileName = NULL;
      ULONG uTableCode = 0, uObjTable = 0, uEProAddr = 0, i = 0;
      BOOLEAN bIsDebug = FALSE;

      pTargetEPro = PsGetCurrentProcess();      //获取当前进程EPROCESS
      pCurEPro = (PEPROCESS)(*(PULONG)((ULONG)pTargetEPro + 0x88) - 0x88); //获取下一进程的EPROCESS2
      do
      {
                uObjTable = *(PULONG)((ULONG)pCurEPro + 0xC4);      //获取_HANDLE_TABLE的地址
                if (uObjTable)
                {
                        uTableCode = *(PULONG)uObjTable;      //获取TableCode
                        switch(uTableCode & 0x3)                        //判断低两位是多少
                        {
                        case 0:
                              pImageFileName = (PCHAR)((ULONG)pCurEPro + 0x174);                //获取EPROCESS对象的ImageFileName
                              uTableCode &= ~0x3;                              //将低两位清0
                              for (i = 0; i < 512; i += 2)
                              {
                                        uEProAddr = (*((PULONG)uTableCode + i) & ~0x7) + 0x18;      //获取句柄表中低32位的值,并将低3位清0后加上0x18得到对象的内核地址
                                       
                                        if (uEProAddr == (ULONG)pTargetEPro)                                        //判断这个内核地址是不是就是本进程的内核地址,如果是说明在被调试
                                        {
                                                DbgPrint("进程:%s正在调试你的进程\r\n", pImageFileName);
                                                bIsDebug = TRUE;
                                                break;
                                        }
                              }
                              break;
                        }
                }

                if (bIsDebug) break;
                pCurEPro = (PEPROCESS)(*(PULONG)((ULONG)pCurEPro + 0x88) - 0x88);
      } while (pCurEPro != pTargetEPro);

      return bIsDebug;
}                  
为了可以随时开启检测,就需要通过编写代码来安装驱动,实现从用户层发送控制码给驱动然后开始检测。下面给出完整的用户层和内核从的代码,必要的地方已经增加了注释。不过检测句柄表的时候,我只实现了TableCode低两位为0,也就是第一种情况,那会发现只要检测它就可以检测出调试器就懒了没实现其他两种情况,大家感兴趣可以自己实现一下   
用户层代码如下

#include <cstdio>
#include <Windows.h>

#define IOCTRL_BASE 0x800
#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CTL_BEGIN MYIOCTRL_CODE(0)
#define LINK_NAME "\\\\.\\TestDeviceLink"
#define DRIVER_NAME "HelloDriver"
#define DRIVER_PATH "C:\\Documents and Settings\\Administrator\\桌面\\HelloDriver.sys"
#define OUT_BUFFER_LENGTH 4      //输出缓冲区的长度

BOOL InstallService();
BOOL UnInstallService();

int main()
{
      char szAns = { 0 };      //用户输入来判断是否退出程序
      HANDLE hDevice = NULL;
      DWORD dwOutBuffer = 0;      //输出缓冲区
      DWORD dwRetLength = 0;      //接受缓冲区长度

      if (InstallService())
      {
                // 获取设备句柄
                hDevice = CreateFile(LINK_NAME, GENERIC_READ | GENERIC_WRITE,
                                                         0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
                if (hDevice == INVALID_HANDLE_VALUE)
                {
                        printf("CreateFile Error %d.\n", GetLastError());
                        goto exit;
                }

                while (TRUE)
                {
                        memset(szAns, 0, strlen(szAns));
                        printf("是否开始继续检测?(yes)\n");
                        scanf("%s", szAns);
                        if (strcmp(szAns, "yes") != 0)      break;

                        if (!DeviceIoControl(hDevice, CTL_BEGIN, NULL, 0,
                                                               &dwOutBuffer, OUT_BUFFER_LENGTH, &dwRetLength, NULL))
                        {
                              printf("DeviceIoControl Error\n");
                              break;
                        }
                        if (dwOutBuffer) printf("程序正在被调试\n");
                        else printf("程序没被调试\n");
                }
      exit:
                if (hDevice) CloseHandle(hDevice);
                if (!UnInstallService())
                {
                        printf("卸载驱动失败\n");
                }
      }
      else
      {
                printf("驱动加载失败\n");
      }
      system("pause");

      return 0;
}

BOOL UnInstallService()
{
      BOOL bRet = TRUE;
      SC_HANDLE hService = NULL, hSCMHandle = NULL;
      SERVICE_STATUS ServiceStatus = { 0 };

      hSCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);      //建立服务控制管理器连接
      if (hSCMHandle == NULL)
      {
                printf("OpenSCManager error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      hService = OpenService(hSCMHandle, "HelloDriver", SERVICE_ALL_ACCESS);
      if (hService == NULL)
      {
                printf("CreateService error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      if (!ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus))      //停止驱动服务
      {
                printf("ControlService error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      if (!DeleteService(hService))      //删除驱动
      {
                printf("DeleteService error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      printf("驱动卸载成功\n");
exit:
      if (hSCMHandle)      CloseServiceHandle(hSCMHandle);
      if (hService) CloseServiceHandle(hService);

      return bRet;
}

BOOL InstallService()
{
      SC_HANDLE hSCMHandle = NULL, hService = NULL;
      BOOL bRet = TRUE;

      hSCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);      //建立服务控制管理器连接
      if (hSCMHandle == NULL)
      {
                printf("OpenSCManager error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      //创建一个服务对象
      hService = CreateService( hSCMHandle,
                                  DRIVER_NAME,                        //驱动名称
                                  DRIVER_NAME,                        //显示的名称
                                  SERVICE_ALL_ACCESS,      
                                  SERVICE_KERNEL_DRIVER,      //指定内核驱动程序
                                  SERVICE_DEMAND_START,            //需要时候开启
                                  SERVICE_ERROR_NORMAL,
                                  DRIVER_PATH,      //驱动的路径
                                  NULL, NULL, NULL, NULL, NULL);      
      if (hService == NULL)
      {
                printf("CreateService error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      if (!StartService(hService, 0, NULL))      //启动服务
      {
                printf("StartService error %d\n", GetLastError());
                bRet = FALSE;
                goto exit;
      }

      printf("驱动安装成功\n");
exit:
      if (hSCMHandle)      CloseServiceHandle(hSCMHandle);
      if (hService) CloseServiceHandle(hService);

      return bRet;
}
         
   驱动的代码如下

#include <ntifs.h>

#define IOCTRL_BASE 0x800
#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CTL_BEGIN MYIOCTRL_CODE(0)
#define DEVICE_NAME L"\\device\\TestDevice"
#define SYMBOL_LINE L"\\dosdevices\\TestDeviceLink"

BOOLEAN IsDebugger();
VOID DriverUnload(IN PDRIVER_OBJECT driverObject);
NTSTATUS DispatchCommon(PDEVICE_OBJECT pObj, PIRP pIrp);
NTSTATUS DispatchIoctrl(PDEVICE_OBJECT pObj, PIRP pIrp);


NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
{
      NTSTATUS status = STATUS_SUCCESS;
      UNICODE_STRING uDeviceName, uSymbolLinkName;
      ULONG i = 0;
      PDEVICE_OBJECT pDeviceOjb = NULL;

      //创建设备
      RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
      status = IoCreateDevice(driverObject, NULL, &uDeviceName, FILE_DEVICE_UNKNOWN,
                                                                FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceOjb);
      if (!NT_SUCCESS(status))
      {
                DbgPrint("IoCreateDevice %X\r\n", status);
                goto exit;
      }

      //设置数据交互方式
      pDeviceOjb->Flags |= DO_BUFFERED_IO;

      //创建符号链接
      RtlInitUnicodeString(&uSymbolLinkName, SYMBOL_LINE);
      status = IoCreateSymbolicLink(&uSymbolLinkName, &uDeviceName);
      if (!NT_SUCCESS(status))
      {
                DbgPrint("IoCreateSymbolicLink %X\r\n", status);
                goto exit;
      }

      for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
      {
                driverObject->MajorFunction = DispatchCommon;
      }
      driverObject->MajorFunction = DispatchIoctrl;
      DbgPrint("驱动成功加载\r\n");
exit:
      driverObject->DriverUnload = DriverUnload;
      return STATUS_SUCCESS;
}

NTSTATUS DispatchIoctrl(PDEVICE_OBJECT pObj, PIRP pIrp)
{
      NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
      PIO_STACK_LOCATION pIrpStack;
      ULONG uIoControlCode = 0, uInformation = 0, uRes = 0;
      PVOID pIoBuffer = NULL;

      //获取设备栈
      pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
      //获取控制码
      uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
      //获取输入输出缓冲区
      pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;

      switch(uIoControlCode)
      {
      case CTL_BEGIN:
                status = STATUS_SUCCESS;
                if (IsDebugger()) uRes = 1;
                memcpy(pIoBuffer, &uRes, sizeof(uRes));
                uInformation = sizeof(uRes);
                break;
      default:
                DbgPrint("Unknown IoControlCode\r\n");
                break;
      }

      pIrp->IoStatus.Status = status;
      pIrp->IoStatus.Information = uInformation;
      IoCompleteRequest(pIrp, IO_NO_INCREMENT);

      return STATUS_SUCCESS;
}

BOOLEAN IsDebugger()
{
      PEPROCESS pCurEPro = NULL, pTargetEPro = NULL;
      PCHAR pImageFileName = NULL;
      ULONG uTableCode = 0, uObjTable = 0, uEProAddr = 0, i = 0;
      BOOLEAN bIsDebug = FALSE;

      pTargetEPro = PsGetCurrentProcess();      //获取当前进程EPROCESS
      pCurEPro = (PEPROCESS)(*(PULONG)((ULONG)pTargetEPro + 0x88) - 0x88); //获取下一进程的EPROCESS2
      do
      {
                uObjTable = *(PULONG)((ULONG)pCurEPro + 0xC4);      //获取_HANDLE_TABLE的地址
                if (uObjTable)
                {
                        uTableCode = *(PULONG)uObjTable;      //获取TableCode
                        switch(uTableCode & 0x3)                        //判断低两位是多少
                        {
                        case 0:
                              uTableCode &= ~0x3;                              //将低两位清0
                              pImageFileName = (PCHAR)((ULONG)pCurEPro + 0x174);                //获取EPROCESS对象的ImageFileName
                              for (i = 0; i < 512; i += 2)
                              {
                                        uEProAddr = (*((PULONG)uTableCode + i) & ~0x7) + 0x18;      //获取句柄表中低32位的值,并将低3位清0后加上0x18得到对象的内核地址
                                        if (uEProAddr == (ULONG)pTargetEPro)                                        //判断这个内核地址是不是就是本进程的内核地址,如果是说明在被调试
                                        {
                                                DbgPrint("进程:%s正在调试你的进程\r\n", pImageFileName);
                                                bIsDebug = TRUE;
                                                break;
                                        }
                              }
                              break;
                        }
                }

                if (bIsDebug) break;
                pCurEPro = (PEPROCESS)(*(PULONG)((ULONG)pCurEPro + 0x88) - 0x88);
      } while (pCurEPro != pTargetEPro);

      return bIsDebug;
}

NTSTATUS DispatchCommon(PDEVICE_OBJECT pObj, PIRP pIrp)
{
      pIrp->IoStatus.Status = STATUS_SUCCESS;
      pIrp->IoStatus.Information = 0;

      IoCompleteRequest(pIrp, IO_NO_INCREMENT);

      return STATUS_SUCCESS;
}

VOID DriverUnload(IN PDRIVER_OBJECT driverObject)
{
      UNICODE_STRING uSymbolLinkName;

      if (driverObject->DeviceObject)
      {
                IoDeleteDevice(driverObject->DeviceObject);
                RtlInitUnicodeString(&uSymbolLinkName, SYMBOL_LINE);
                IoDeleteSymbolicLink(&uSymbolLinkName);
      }
      DbgPrint("驱动卸载完成\r\n");
}


       最终,运行结果如下,首先是程序启动没附加调试器的时候,结果如下




       接着打开调试器,附加到进程上面以后,运行结果如下


LegendSaber 发表于 2021-10-8 19:05

咬字分开念 发表于 2021-10-8 18:08
用c的指针直接获取内存地址修改内容不是更快吗

那雀实。写的时候没想那么多

LegendSaber 发表于 2021-10-9 19:34

92013 发表于 2021-10-9 19:29
断链~就解决

这只是一种思路罢了。你可以断链我也用其他办法找到你。

htpidk 发表于 2021-10-8 13:58

还要上驱动,麻烦了点

zhuyong770 发表于 2021-10-8 14:07

思路不错, 问题是内核上半年在科锐没学懂

54mj 发表于 2021-10-8 15:52

没看懂,我觉得往后肯定是个好办法。

LegendSaber 发表于 2021-10-8 16:00

54mj 发表于 2021-10-8 15:52
没看懂,我觉得往后肯定是个好办法。

这个办法挺早就有了啊。。估计早就被过了吧

是荞麦呀 发表于 2021-10-8 16:22

感谢大佬分享

sieving 发表于 2021-10-8 17:20

资料不错值得学习,谢谢分享

咬字分开念 发表于 2021-10-8 18:08

用c的指针直接获取内存地址修改内容不是更快吗

luckysky 发表于 2021-10-8 19:07

句柄检测也可以ring3层调用NtQuery系列API
干掉驱动层句柄检测,挂线程 + 跳出死循环
页: [1] 2
查看完整版本: 通过句柄表实现反调试