Aperodry 发表于 2018-4-7 12:57

R3下取伪句柄表的几种方式

*R3下取伪句柄表的几种方式*
[目录]( "目录")
1. 句柄(HANDLE)是什么?
2.R3下获取句柄的几种方式
      - 通过 `ZwQuerySystemInformation` 函数获取      
      - 通过 `ZwQueryInformationProcess` 函数获取
3. 关闭句柄的两种方式
      - 本地进程句柄的关闭
      - 远程进程句柄的关闭
---

*最近碰到一个简单的老游戏,看了一下它的防多开就是通过 CreateFileMapping 函数以只读 方式创建了个文件映射对象 就和互斥体一样,本来劫持个DLL hook下就完事了,但是发现只有几个陌生的DLL可劫持 重点在于这个DLL加载时候对多开的检测已经完成,这就十分尴尬了。。。只好通过远程关闭文件映射句柄来实现多开 然后发现既然DLL可以劫持,又再句柄创建完成后,何不用DLL劫持来干掉句柄,这就滋生出来了几种操作了...*

**好了废话不多谈,开始写了,只适合小白看的基础性文章~~**

---

# 一、句柄(HANDLE)是什么?

句柄((https://baike.baidu.com/item/handle/2971688?fr=aladdin "句柄"))是Windows表示对象的(不是C++对象)
HWND就是其中一种,表示为窗口句柄。还有其他很多如图标句柄(hIco)、光标句柄(hCursor)、线程句柄(hThread)等等..

> https://blog.csdn.net/shuyong1999/article/details/7171683
> https://blog.csdn.net/ustbkuang/article/details/77862720
> https://blog.csdn.net/maowei117/article/details/55254855
上面的为各大牛们对句柄的一些理解

对于句柄来说,我们只要获得了目标的句柄就可以为所欲为了,比如获得有权限的进程句柄就可以读写目标进程数据了(有保护的当我没说...),窗口句柄可以向指定窗口发送消息,线程句柄可以**结束(Terminate)**或是**挂起Suspend**指定线程(不是本地进程当我没说...)
当然对于**互斥体句柄(Mutant)**或是**Section**类型的句柄还是可以给它关闭为所欲为的。


# 二、R3下获取句柄的几种方式

我们知道系统层有句柄表,而用户层也能靠3环的几个函数获取:

## 通过 `ZwQuerySystemInformation` 函数获取


此为查询系统信息的函数,当 ***SystemInformationClass 参数为 SystemHandleInformation(0x10)***时查询系统句柄信息,
**SystemInformationClass** 如下定义:

```
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
      SystemProcessesAndThreadsInformation, // 5       Y      N
      SystemCallCounts,                  // 6      Y      N
      SystemConfigurationInformation,      // 7      Y      N
      SystemProcessorTimes,                // 8      Y      N
      SystemGlobalFlag,                  // 9      Y      Y
      SystemNotImplemented2,               // 10       Y      N
      SystemModuleInformation,             // 11       Y      N
      SystemLockInformation,               // 12       Y      N
      SystemNotImplemented3,               // 13       Y      N
      SystemNotImplemented4,               // 14       Y      N
      SystemNotImplemented5,               // 15       Y      N
      SystemHandleInformation,             // 16       Y      N
      SystemObjectInformation,             // 17       Y      N
      SystemPagefileInformation,         // 18       Y      N
      SystemInstructionEmulationCounts,    // 19       Y      N
      SystemInvalidInfoClass1,             // 20
      SystemCacheInformation,            // 21       Y      Y
      SystemPoolTagInformation,            // 22       Y      N
      SystemProcessorStatistics,         // 23       Y      N
      SystemDpcInformation,                // 24       Y      Y
      SystemNotImplemented6,               // 25       Y      N
      SystemLoadImage,                     // 26       N      Y
      SystemUnloadImage,                   // 27       N      Y
      SystemTimeAdjustment,                // 28       Y      Y
      SystemNotImplemented7,               // 29       Y      N
      SystemNotImplemented8,               // 30       Y      N
      SystemNotImplemented9,               // 31       Y      N
      SystemCrashDumpInformation,          // 32       Y      N
      SystemExceptionInformation,          // 33       Y      N
      SystemCrashDumpStateInformation,   // 34       Y      Y/N
      SystemKernelDebuggerInformation,   // 35       Y      N
      SystemContextSwitchInformation,      // 36       Y      N
      SystemRegistryQuotaInformation,      // 37       Y      Y
      SystemLoadAndCallImage,            // 38       N      Y
      SystemPrioritySeparation,            // 39       N      Y
      SystemNotImplemented10,            // 40       Y      N
      SystemNotImplemented11,            // 41       Y      N
      SystemInvalidInfoClass2,             // 42
      SystemInvalidInfoClass3,             // 43
      SystemTimeZoneInformation,         // 44       Y      N
      SystemLookasideInformation,          // 45       Y      N
      SystemSetTimeSlipEvent,            // 46       N      Y
      SystemCreateSession,               // 47       N      Y
      SystemDeleteSession,               // 48       N      Y
      SystemInvalidInfoClass4,             // 49
      SystemRangeStartInformation,         // 50       Y      N
      SystemVerifierInformation,         // 51       Y      Y
      SystemAddVerifier,                   // 52       N      Y
      SystemSessionProcessesInformation    // 53       Y      N
} SYSTEM_INFORMATION_CLASS;
```

每一个enum元素对应着查询信息,而 **SystemHandleInformation(0x10)** 查询的为 **SYSTEM\_HANDLE\_INFORMATIO** 结构体指针的信息:

```
typedef struct _SYSTEM_HANDLE_INFORMATION
{
      ULONG            ProcessId;      //进程ID
      UCHAR            ObjectTypeNumber;
      UCHAR            Flags;
      USHORT            Handle;      //句柄
      PVOID            Object;      //句柄对象
      ACCESS_MASK      GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
      ULONG NumberOfHandles;    //数组数量
      SYSTEM_HANDLE Information;    //数组指针
}SYSTEM_HANDLE_INFORMATIO, *PSYSTEM_HANDLE_INFORMATION;
```

到了这里思路就很清晰了,那么怎么才能区分句柄获得我们想要的句柄呢 ?这里就用到另外一个函数 **NtQueryObject** :

**NtQueryObject** 函数用来查询对象句柄信息,当 ***OBJECT\_INFORMATION\_CLASS参数为ObjectNameInformation(1)和ObjectTypeInformation(2)时分别查询句柄的名称和句柄类型 它们的定义和查询的结构体如下:***

```
typedef enum _OBJECT_INFORMATION_CLASS {
      ObjectBasicInformation,
      ObjectNameInformation,
      ObjectTypeInformation,
      ObjectAllInformation,
      ObjectDataInformation
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
//注释
//ObjectBasicInformation 对应结构为:OBJECT_BASIC_INFORMATION
//ObjectNameInformation 对应结构为:OBJECT_NAME_INFORMATION
//ObjectTypeInformation对应结构为:OBJECT_TYPE_INFORMATION
//ObjectAllInformation对应结构为:   OBJECT_ALL_INFORMATION
//ObjectDataInformation对应结构为:OBJECT_DATA_INFORMATION

typedef struct
{
      USHORT Length;      //当前名称长度
      USHORT MaxLen;      //缓冲区最大长度
      USHORT *Buffer;      //Unicode 名称指针
}UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_NAME_INFORMATION {
      UNICODE_STRING          Name;
      WCHAR                   NameBuffer;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

typedef struct _OBJECT_TYPE_INFORMATION {
      UNICODE_STRING          TypeName;
      ULONG                   TotalNumberOfHandles;
      ULONG                   TotalNumberOfObjects;
      WCHAR                   Unused1;
      ULONG                   HighWaterNumberOfHandles;
      ULONG                   HighWaterNumberOfObjects;
      WCHAR                   Unused2;
      ACCESS_MASK             InvalidAttributes;
      GENERIC_MAPPING         GenericMapping;
      ACCESS_MASK             ValidAttributes;
      BOOLEAN               SecurityRequired;
      BOOLEAN               MaintainHandleCount;
      USHORT                  MaintainTypeList;
      POOL_TYPE               PoolType;
      ULONG                   DefaultPagedPoolCharge;
      ULONG                   DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
```

到了这里就明了很多了,下面写个简单的Demo实现获取目的进程的句柄:

```
// 进程提权
bool EnableDebugPrivilege()
{
      HANDLE hToken;
      LUID sedebugnameValue;
      TOKEN_PRIVILEGES tkp;
      if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
      {
                return   FALSE;
      }
      if( !LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue) )
      {
                CloseHandle(hToken);
                return false;
      }
      tkp.PrivilegeCount = 1;
      tkp.Privileges.Luid = sedebugnameValue;
      tkp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
      if( !AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL) )
      {
                CloseHandle(hToken);
                return false;
      }
      return true;
}
void LogOut(char *Format, ...)
{
      char szBuffer;
      va_list pArgList;
      va_start(pArgList, Format);//参数列表初始化
      vsprintf_s(szBuffer, Format, pArgList);
      va_end(pArgList);
      OutputDebugString(szBuffer);
}

//初始化未文档化函数
BOOL InitUnDocumentProc()
{
      HMODULE hNtdll = GetModuleHandle("Ntdll.dll");
      if( hNtdll == NULL )      return FALSE;

      ZwQuerySystemInformation = \
                (pfnNtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation");
      ZwQueryObject = \
                (pfnNtQueryObject)GetProcAddress(hNtdll, "NtQueryObject");
      ZwQueryInformationProcess = \
                (pfnNtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess");

      if( (ZwQuerySystemInformation == NULL) || \
                (ZwQueryObject == NULL) || \
                (ZwQueryInformationProcess == NULL) )
                return FALSE;
      return TRUE;
}

SYSTEM_HANDLE_INFORMATIO_EX *GetSystemProcessHandleInfo()
{
      DWORD buffLen = 0x1000;
      NTSTATUS status;
      BYTE* buff = new BYTE;
      do{
                status = ZwQuerySystemInformation(SystemHandleInformation, buff, buffLen, &buffLen);
                if( status == STATUS_INFO_LENGTH_MISMATCH )
                {
                        delete[] buff;
                        buff = new BYTE;
                } else
                        break;

      } while( TRUE );
      return (SYSTEM_HANDLE_INFORMATIO_EX*)buff;
}
int _tmain(int argc, _TCHAR* argv[])
{
      EnableDebugPrivilege();
      InitUnDocumentProc();
      NTSTATUS Status;
      SYSTEM_HANDLE* CurHandle;
      OBJECT_NAME_INFORMATION *ObjectName;
      OBJECT_TYPE_INFORMATION *ObjectType;
      char BufferForObjectName;
      char BufferForObjectType;
      SYSTEM_HANDLE_INFORMATIO_EX *pInfo = GetSystemProcessHandleInfo();
      if( pInfo )
      {
                for( DWORD i = 0; i < pInfo->NumberOfHandles; i++ )
                {
                        CurHandle = &(pInfo->Information);
                        if( CurHandle->ProcessId==GetCurrentProcessId())//自进程
                        {
                              ZeroMemory(BufferForObjectName, 1024);
                              ZeroMemory(BufferForObjectType, 1024);
                              //获取句柄类型
                              Status = ZwQueryObject((HANDLE)CurHandle->Handle,
                                        ObjectTypeInformation,
                                        BufferForObjectType,
                                        sizeof(BufferForObjectType),
                                        NULL);
                              
                              ObjectType = (OBJECT_TYPE_INFORMATION*)BufferForObjectType;
                              //if( Status == STATUS_INFO_LENGTH_MISMATCH || !NT_SUCCESS(Status) )
                              //      continue;

                              //获取句柄名
                              Status=ZwQueryObject((HANDLE)CurHandle->Handle,
                                        ObjectNameInformation,
                                        BufferForObjectName,
                                        sizeof(BufferForObjectName),
                                        NULL);

                              ObjectName = (POBJECT_NAME_INFORMATION)BufferForObjectName;
                              //if( Status == STATUS_INFO_LENGTH_MISMATCH || !NT_SUCCESS(Status) )
                              //      continue;
                              LogOut("Type:%S Name:%S Handle=%X", ObjectType->TypeName.Buffer,
                                        ObjectName->Name.Buffer,
                                        CurHandle->Handle);
                        }
                }
                delete[] pInfo;
      }
      return 0;
}
```

上面写个获取自身进程句柄的整个逻辑,下面还有一种获取指定进程伪句柄表的方法

* * *

## 通过 `ZwQueryInformationProcess` 函数获取


这个为查询指定进程信息的函数,当 ***ProcessInformationClass参数为ProcessHandleCount(20)*** 时候查询进程的句柄引用计数**(Count)**

```
typedef enum _PROCESS_INFORMATION_CLASS {
      ProcessBasicInformation,
      ProcessQuotaLimits,
      ProcessIoCounters,
      ProcessVmCounters,
      ProcessTimes,
      ProcessBasePriority,
      ProcessRaisePriority,
      ProcessDebugPort,
      ProcessExceptionPort,
      ProcessAccessToken,
      ProcessLdtInformation,
      ProcessLdtSize,
      ProcessDefaultHardErrorMode,
      ProcessIoPortHandlers,
      ProcessPooledUsageAndLimits,
      ProcessWorkingSetWatch,
      ProcessUserModeIOPL,
      ProcessEnableAlignmentFaultFixup,
      ProcessPriorityClass,
      ProcessWx86Information,
      ProcessHandleCount,
      ProcessAffinityMask,
      ProcessPriorityBoost,
      MaxProcessInfoClass
} PROCESS_INFORMATION_CLASS, *PPROCESS_INFORMATION_CLASS;
```

查询这个干嘛呢?我们接下来穷举目标进程句柄。没错,是穷举...刚开始是在博客里看到一位大牛写的,发现他思路很清晰,但是代码有点问题,都把逻辑给混淆了。
**句柄是以4开始,以4为单位递增...**:

从上图不难发现都是句柄值以**4**递增的,我们穷举的话判断句柄的**有效性**就可以用函数***DuplicateHandle***函数实现拷贝有效句柄,失败返回**FALSE** 从而就可以判断句柄的有效性了。

```
int _tmain(int argc, _TCHAR* argv[])
{
      EnableDebugPrivilege();
      InitUnDocumentProc();
      NTSTATUS Status;
      HANDLE hSource = NULL;
      HANDLE hDuplicate = NULL;
      DWORD HandleCount;
      OBJECT_NAME_INFORMATION *ObjectName;
      OBJECT_TYPE_INFORMATION *ObjectType;
      char BufferForObjectName;
      char BufferForObjectType;


      hSource = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME, FALSE, GetCurrentProcessId());
      if( hSource != NULL )
      {
                DWORD dwHandle;
                Status = ZwQueryInformationProcess(hSource, ProcessHandleCount, &HandleCount, sizeof(HandleCount), NULL);

                for( DWORD i = 1; i <= HandleCount; i++ )//穷举句柄
                {
                        dwHandle = i * 4;
                        if( DuplicateHandle(hSource, //复制一个句柄对象 && 判断此句柄是否有效
                              (HANDLE)dwHandle,
                              GetCurrentProcess(),
                              &hDuplicate,
                              0, FALSE, DUPLICATE_SAME_ACCESS) )
                        {
                              ZeroMemory(BufferForObjectName, 1024);
                              ZeroMemory(BufferForObjectType, 1024);

                              //获取句柄类型
                              Status = ZwQueryObject(hDuplicate,
                                        ObjectTypeInformation,
                                        BufferForObjectType,
                                        sizeof(BufferForObjectType),
                                        NULL);

                              ObjectType = (OBJECT_TYPE_INFORMATION*)BufferForObjectType;
                              if( Status == STATUS_INFO_LENGTH_MISMATCH || !NT_SUCCESS(Status) )
                                        continue;

                              //获取句柄名
                              Status = ZwQueryObject((HANDLE)hDuplicate,
                                        ObjectNameInformation,
                                        BufferForObjectName,
                                        sizeof(BufferForObjectName),
                                        NULL);

                              //关闭复制的句柄
                              CloseHandle(hDuplicate);
                              ObjectName = (POBJECT_NAME_INFORMATION)BufferForObjectName;
                              if( Status == STATUS_INFO_LENGTH_MISMATCH || !NT_SUCCESS(Status) )
                                        continue;

                              printf("Type:%S|Name:%S|Handle:%X\n", ObjectType->TypeName.Buffer,
                                        ObjectName->Name.Buffer,hDuplicate);
                              
                        }
                }
                CloseHandle(hSource);
      }
      return FALSE;
      return 0;
```

* * *

# 三、关闭句柄的两种方式
到这里就很简单了,上述已经获得了想到的句柄,但是关闭句柄也有两种不同的情况,本地句柄调用函数**CloseHandle**即可,而远程的句柄如何关闭呢 ?(就像知道目标的***Patch***地址而无法在本地执行操作一样),实际调用函数**DuplicateHandle**即可。

* 本地进程句柄的关闭

* 远程进程句柄的关闭

这里我们关注的是函数**DuplicateHandle**的最后一个参数,我们查下msdn的定义:
> **DUPLICATE_CLOSE_SOURCE(0x00000001)** Closes the source handle. This occurs regardless of any error status returned.
> **DUPLICATE_SAME_ACCESS(0x00000002)**Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.

* * *

# 小结:

对于远程和本地两种情况都有两种取的句柄的方法,从而针对不同的情况有**4**种方法,还有通过**DuplicateHandle**复制的句柄记得**CloseHandle**,这样才可以实现跨进程关闭句柄,本地的进程不用**DuplicateHandle**直接可以关闭,有点啰嗦了...

***这里就不贴代码了,都打包在附件上。不然显得有点...占空间了...***

**Ps: **

*这玩意不是网上有很多资料吗 ?为什么你还要发帖?

大牛们对于这种小问题都是一笔带过的,只给了我们这些小白点关键代码和思路,好让我这些 “跑龙套” 方便整理。。。
好了不瞎扯了,上面是一方面,另一方面是还是自己的代码看的习惯.....主要是在学 ***Markdown*** 哈哈哈!!


**上述如有有误,还请各位指证!**
**如有例外,还请各位大侠补充!**

bester 发表于 2019-10-31 19:50

您好,楼主,我想请问一下您代码的一个问题,希望得到您的回复
if( Status == STATUS_INFO_LENGTH_MISMATCH || !NT_SUCCESS(Status) )
                                        continue;
请问这句代码是一个什么意思呢?作用是什么呢?

失去灯塔的孤帆 发表于 2018-4-7 14:59

这个学习了{:1_893:}
之前也有接触过用CreateFileMapping的方式限制游戏多开,不过我是用挂起的标志创建进程,注入后hook ntCreatesection和ntopensection改名字;www

苏紫方璇 发表于 2018-4-7 13:24

感谢楼主分享,原来那种强制删除文件,貌似用的也是这种方法

Aperodry 发表于 2018-4-7 13:33

苏紫方璇 发表于 2018-4-7 13:24
感谢楼主分享,原来那种强制删除文件,貌似用的也是这种方法

嗯,就是文件句柄(File) ,理论关闭掉就可以删除了
:lol感谢支持 ~

gunxsword 发表于 2018-4-7 19:03

感谢分享,正需要这个,感谢!

廖道军 发表于 2018-4-9 10:04

佩服佩服   这么冷门的都有人分享{:1_893:}

zd326379 发表于 2018-4-13 21:56

xiao135140 发表于 2018-5-27 12:09

ppyyll7788 发表于 2018-6-20 09:32

学习了,谢谢分享

游戏丶死忠 发表于 2018-7-6 09:27

热血定点的工具求链接
页: [1] 2
查看完整版本: R3下取伪句柄表的几种方式