试谈一下获取进程句柄的那些事
本帖最后由 忆魂丶天雷 于 2023-3-30 16:57 编辑#前言
**本文由笔者(论坛UID: 666926)首发于52破解论坛,如需转载请保留完整的作者信息以及出处。**
本文仅作为记录之用,所写内容皆源自于笔者所接触、了解、总结以及网络有关知识,因为碍于自身知识储备有限,所以实在是不敢也没有能力展开深入讨论,内容可能比较肤浅,如有错误还望各位大牛教正。
---
#正文
---
##什么是进程句柄
以下引用bing搜索给出的答案
>句柄实际是一个指针,他指向一块包含具体信息数据的内存,可以当做索引 ,所以进程句柄是当你要访问该进程时取得的,使用完毕必须释放。
>进程的句柄则是基于特定进程的,对于同一个进程对象,在不同的进程中可能有不同的句柄值。因为句柄实际上是进程空间中的句柄表的偏移,由于在不同的进程空间中句柄表是不同的,针对相同的对象在句柄表的偏移也就不一样了!
>一个进程在不同的调用时间中有可能句柄的值是不一样的,但是ID只有也只能有一个
>在Windows有一张指针表(就是指针数组),这些指针指向Windows内的各种对象(Windows概念的对象),其中就包含进行对象。句柄就就是指针数组的编号。
>进程句柄,每次打开这个进程(OpenProcess),返回给你的句柄是变化的
综上所述,我们可以对进程句柄有一个简单的了解。
##进程句柄有什么用?
在Ring3环境中,我们对进程的操作基本上都离不开进程句柄,例如通过GetProcessId使用进程句柄获取进程的PID,又例如对目标进程内存的读写,获取目标进程的一些信息,挂起或者终止目标进程,这等等一大串操作都需要进程句柄。
##什么是进程句柄的权限?
每一个句柄都有属于自己的权限,只有当拥有对应的权限才能做对应的事情,如果拿一个QQ群举例,有权限那么你就可能是`权限🐶管理`,或者是`权限🐶群主`,在群内可以为所欲为,因为一切敢反抗你的人你就可以权限它,比如禁言或者移除。
当然权限也不是固定的,比如你可以由普通群员通过~~PY交易~~努力成为管理,甚至成为群主。
那么对于进程句柄来说,你想获取对应进程的信息、挂起或者终止一个进程,那么你也就需要一个拥有对应权限的进程句柄,当然同理,进程句柄也有提权和降权一说。
如果你想详细的知道有哪些权限,可以阅读一下微软的文档。
>https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
##如何获取进程句柄?
既然进程句柄有这么多作用,那么我们怎么去得到它?这也是本文的主要内容。
###通过GetCurrentProcess获取自进程的进程句柄
求人不易,求己不难。对于自己已经有的东西,自当求己,同理,在笔者看来,获取自进程的进程句柄也是所有方法中最为简单的一个。只需要一个API`GetCurrentProcess`即可。
根据微软官方给出的文档,它的使用方法也是非常的简单,不需要任何参数,直接调用,它的返回值即是自进程的进程句柄。
```
HANDLE GetCurrentProcess();
```
甚至,如果你想偷懒,可以连这仅需的一个API都不用调用,在需要使用到自身进程句柄的地方直接填写`-1`即可。
但是,值得注意的是,-1是一个伪句柄,即当前进程句柄,它拥有`PROCESS_ALL_ACCESS访问权限`,它不会被子进程继承,也不能直接被其他进程使用。
相关文档:
>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
###通过OpenProcess获取进程句柄
上面已经说过了如何获取自进程的进程句柄,那么如何获取其他进程的进程句柄呢?伟大的微软同样也提供了一个API`OpenProcess`
```
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOLbInheritHandle,
DWORD dwProcessId
);
```
OpenProcess,相比GetCurrentProcess使用起来就复杂一些,它需要提供3个参数。
` dwDesiredAccess`你想要获取的句柄权限,具体权限可以参考前文,但是一般都是`PROCESS_ALL_ACCESS访问权限`,即获取全部权限。虽然,严格说不太规范,但是是真的香。
` bInheritHandle`是否允许被继承,如果此值为 TRUE,则此进程创建的进程将继承句柄。否则,进程不会继承此句柄。
` dwProcessId`目标进程的PID
当你填充以上3个参数,就可以尝试通过OpenProcess去获取你想获取的目标进程的进程句柄了。如果没发生什么意外情况,那么一般来说你都可以如愿以偿的得到。
相关文档:
>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
###OpenProcess失效的一些情况
人生不如意事常八九,在`实际运用`OpenProcess时其实经常会遇见它失效的情况。在抛开一些因为语法错误导致的失效后,其实更多的时候是你想获取的目标进程存在保护。
虽然的确值得反思,为什么大家要尝试对一个有保护的进程存在一些想法,但是作为一篇~~水贴~~笔记来说,的确有必要记录一些处理这种异常情况的方法,但是由于逐渐进入深水区~~因为我也不会~~,接下来的内容可能不会有详细的文档,甚至只会提供一种解决思路。
####OpenProcess/ZwOpenProcess被HOOK
举个简单的例子,你对一个心爱的男生写了一封情书放在他课桌里,但是你的情敌早有准备,对你无时无刻进行了监视,趁你不注意直接拿走了情书,或者对里面的内容进行了修改,那么这个过程就可以被称为被情敌HOOK了。
在十多年前XP系统流行的年代,这种直接HOOK SSDT表中的OpenProcess达到保护进程的目的手段还是很常见的,虽然过时,但是也还是存在(以另外一种方式......)。
为了解决这个问题,我们首先得了解一下OpenProcess的实现过程
以一个32位程序在64位系统上运行为例,通过CE我们可以发现,OpenProcess本质上对ZwOpenProcess的封装
```
KERNELBASE.OpenProcess - 8B FF - mov edi,edi
KERNELBASE.OpenProcess+2- 55 - push ebp
KERNELBASE.OpenProcess+3- 8B EC - mov ebp,esp
KERNELBASE.OpenProcess+5- 83 EC 24 - sub esp,24 { 36 }
KERNELBASE.OpenProcess+8- 8B 45 10 - mov eax,
KERNELBASE.OpenProcess+B- 33 C9 - xor ecx,ecx
KERNELBASE.OpenProcess+D- 89 45 F4 - mov ,eax
KERNELBASE.OpenProcess+10- 8B 45 0C - mov eax,
KERNELBASE.OpenProcess+13- F7 D8 - neg eax
KERNELBASE.OpenProcess+15- 89 4D F8 - mov ,ecx
KERNELBASE.OpenProcess+18- C7 45 DC 18000000 - mov ,00000018 { 24 }
KERNELBASE.OpenProcess+1F- 1B C0 - sbb eax,eax
KERNELBASE.OpenProcess+21- 89 4D E0 - mov ,ecx
KERNELBASE.OpenProcess+24- 83 E0 02 - and eax,02 { 2 }
KERNELBASE.OpenProcess+27- 89 4D E4 - mov ,ecx
KERNELBASE.OpenProcess+2A- 89 45 E8 - mov ,eax
KERNELBASE.OpenProcess+2D- 8D 45 F4 - lea eax,
KERNELBASE.OpenProcess+30- 50 - push eax
KERNELBASE.OpenProcess+31- 8D 45 DC - lea eax,
KERNELBASE.OpenProcess+34- 89 4D EC - mov ,ecx
KERNELBASE.OpenProcess+37- 50 - push eax
KERNELBASE.OpenProcess+38- FF 75 08 - push
KERNELBASE.OpenProcess+3B- 8D 45 FC - lea eax,
KERNELBASE.OpenProcess+3E- 89 4D F0 - mov ,ecx
KERNELBASE.OpenProcess+41- 50 - push eax
KERNELBASE.OpenProcess+42- FF 15 8C38BD77 - call dword ptr { ->ntdll.ZwOpenProcess }
......
```
```
ntdll.ZwOpenProcess - B8 26000000 - mov eax,00000026 { 38 }
ntdll.NtOpenProcess+5- BA 408BD577 - mov edx,ntdll.RtlInterlockedCompareExchange64+170 { (-1842862593) }
ntdll.NtOpenProcess+A- FF D2 - call edx
ntdll.NtOpenProcess+C- C2 1000 - ret 0010 { 16 }
ntdll.NtOpenProcess+F- 90 - nop
```
所以如果他的上层OpenProcess被HOOK那么我们直接调用ZwOpenProcess就可以绕过HOOK了
当然,这只是一个`非常理想`的情况,实际上,也不可能HOOK这么浅层,可能直接就HOOK了ZwOpenProcess那么你就需要再主动深入一层(32位应用程序在64位系统上运行会存在更多的HOOK点,比如被称为天堂之门的WOW64,或者更进一步直接到达64位的Ntdll)或者手动重写OpenProcess。
天堂之门相关资料:
>https://bbs.kanxue.com/thread-270153.htm
重写ZwOpenProcess相关资料:
>https://blog.csdn.net/weixin_44286745/article/details/104585713
当然,需要说明的是上面采取的是相对温和的战略,实际上我们也可以直接想办法恢复HOOK然后正常调用,不过这可能就会触发一些其他保护,比如CRC检测。
那么,我们来总结一下,在OpenProcess/ZwOpenProcess被HOOK的情况下,我们可以尝试以下思路解决。
```
1.直接调用比被HOOK点的更深入一层的API函数
2.重写ZwOpenProcess的实现流程
3.32位应用在64位系统的运行环境下可以考虑尝试直接调用X64Ntdll中的NtOpenProcess,在Ring3环境中这是相对而言比较底层的了,比它更底层的就是直接用syscall调用了。
4.直接恢复目标进程的HOOK,这里我们可以考虑使用一些工具,比如YDark,PCHunter,火绒剑。
```
相关工具:
YDark:
>https://github.com/ClownQq/YDArk
PCHunter:
>https://www.anxinsec.com/
火绒剑:
>https://www.huorong.cn/
###另辟蹊径
天下同归而殊途,一致而百剃糠提虑。
前文所说,基本上都是对于OpenProcessAPI的应用和拓展。但是不要忘了我们求取的始终是进程句柄,从另外一些道路,虽有曲折但我们依旧可以得到它。
####通过`DuplicateObject`拷贝句柄
~~你配吗?~~咳咳,我是说配钥匙,有一些孩子打小就丢三落四,把钥匙丢了,这个时候就需要,大喊:“妈,我钥匙丢了”。这个时候就会带着你去配一把一模一样的钥匙。
同理,我们可以通过DuplicateObject去拷贝/复制一份句柄给自己使用。
```
NTSYSAPI NTSTATUS ZwDuplicateObject(
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG HandleAttributes,
ULONG Options
);
```
因为这个函数参数比较多,建议读者直接阅读微软的官方文档
相关文档:
>https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/ntifs/nf-ntifs-zwduplicateobject
可能有读者想到了,既然要~~配钥匙~~拷贝句柄,那么肯定得原本就有一把。
~~哦,对了,我们没有,那告辞。~~
是的,使用`DuplicateObject`的前提就是已有一份才能拷贝,那么面对被保护的进程我们如何去拥有一个句柄呢?
还记得前文使说的伪句柄吗,我们可以结合一些其他技术,比如dll劫持,那么我们就可以在dll中拷贝一份目标进程的-1伪句柄出来。
当然,不可否认的是这个方法是很鸡肋的,都已经注入dll进被保护的进程了,重新拷贝一份就显得多余,但这也是一种思路,有一些环境下的确也需要这种多此一举的操作。毕竟你可能不能把所有的运行逻辑写入一个dll中,而暴露一个句柄出来就可以更方便。
####通过`CreateProcess`创建一个进程得到进程句柄
~~众所周知~~,进程关系中存在父子进程的关系,那么儿子的东西,就是老子的东西应该合乎情理吧。
```
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
```
在`CreateProcess`API中的最后一个参数`LPPROCESS_INFORMATION结构`中就包含了子进程的进程句柄
结构如下
```
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORDdwProcessId;
DWORDdwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
```
那么这就好办了,既然打不过你,我就当你爸爸。
面对一个被保护的严严实实的进程,我们可以尝试一下这个办法,比如自己去调用`CreateProcess`创建它,这样作为父级,我们就可以直接得到子进程的进程句柄。
相关文档:
>https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
>https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information
##关于本文中涉及方法的可用性
上述所有方法,笔者均有进行亲测,但是我依旧不能保证,因系统差异、目标进程状态等各种不可控的情况下,各位读者进行尝试是否能与我得到一致的结果。
另外,面对具有保护的进程,希望各位读者三思而后行,如果有成功利用本文所记录的方案获取目标进程句柄,或者因尝试使用本文所记录的方案过程中导致的其他损失,以及其他可能发生的一切已知或未知后果,均应由各位读者自身承担。
#后记
本文,到这里就结束了,仓促编辑或有疏落,可能存在错字,别字;或者是因笔者自身认知有限,某些方法可能存在错误、不足、使用不规范的情况,望各位读者海涵。
最后,再次强调一下版权问题
**本文由笔者(论坛UID: 666926)首发于52破解论坛,如需转载请保留完整的作者信息以及出处。**
大佬,OpenProcess这个函数,如果在java或者C#这种面向对象的语言里,没有办法直接处理window内核函数,有其他获取句柄内存的途径吗 {:301_999:}如有帮助,请给个免费评分。 感谢分享 感谢分享,很有帮助! 感谢分享,有用的知识 学到了,谢谢分享 感谢分享,学到了 为小白科普。有一定的用处 感谢分享,收藏一下 非常感谢,很早就用过OpenProcess了,没想到有这么多门道在里面。