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*** 哈哈哈!!
**上述如有有误,还请各位指证!**
**如有例外,还请各位大侠补充!**
您好,楼主,我想请问一下您代码的一个问题,希望得到您的回复
if( Status == STATUS_INFO_LENGTH_MISMATCH || !NT_SUCCESS(Status) )
continue;
请问这句代码是一个什么意思呢?作用是什么呢? 这个学习了{:1_893:}
之前也有接触过用CreateFileMapping的方式限制游戏多开,不过我是用挂起的标志创建进程,注入后hook ntCreatesection和ntopensection改名字;www 感谢楼主分享,原来那种强制删除文件,貌似用的也是这种方法 苏紫方璇 发表于 2018-4-7 13:24
感谢楼主分享,原来那种强制删除文件,貌似用的也是这种方法
嗯,就是文件句柄(File) ,理论关闭掉就可以删除了
:lol感谢支持 ~ 感谢分享,正需要这个,感谢! 佩服佩服 这么冷门的都有人分享{:1_893:} 学习了,谢谢分享 热血定点的工具求链接
页:
[1]
2