前言
本文由笔者(论坛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获取自进程的进程句柄
求人不易,求己不难。对于自己已经有的东西,自当求己,同理,在笔者看来,获取自进程的进程句柄也是所有方法中最为简单的一个。只需要一个APIGetCurrentProcess
即可。
根据微软官方给出的文档,它的使用方法也是非常的简单,不需要任何参数,直接调用,它的返回值即是自进程的进程句柄。
HANDLE GetCurrentProcess();
甚至,如果你想偷懒,可以连这仅需的一个API都不用调用,在需要使用到自身进程句柄的地方直接填写-1
即可。
但是,值得注意的是,-1是一个伪句柄,即当前进程句柄,它拥有PROCESS_ALL_ACCESS访问权限
,它不会被子进程继承,也不能直接被其他进程使用。
相关文档:
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
通过OpenProcess获取进程句柄
上面已经说过了如何获取自进程的进程句柄,那么如何获取其他进程的进程句柄呢?伟大的微软同样也提供了一个APIOpenProcess
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
OpenProcess,相比GetCurrentProcess使用起来就复杂一些,它需要提供3个参数。
[in] dwDesiredAccess
你想要获取的句柄权限,具体权限可以参考前文,但是一般都是PROCESS_ALL_ACCESS访问权限
,即获取全部权限。虽然,严格说不太规范,但是是真的香。
[in] bInheritHandle
是否允许被继承,如果此值为 TRUE,则此进程创建的进程将继承句柄。否则,进程不会继承此句柄。
[in] 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,[ebp+10]
KERNELBASE.OpenProcess+B- 33 C9 - xor ecx,ecx
KERNELBASE.OpenProcess+D- 89 45 F4 - mov [ebp-0C],eax
KERNELBASE.OpenProcess+10- 8B 45 0C - mov eax,[ebp+0C]
KERNELBASE.OpenProcess+13- F7 D8 - neg eax
KERNELBASE.OpenProcess+15- 89 4D F8 - mov [ebp-08],ecx
KERNELBASE.OpenProcess+18- C7 45 DC 18000000 - mov [ebp-24],00000018 { 24 }
KERNELBASE.OpenProcess+1F- 1B C0 - sbb eax,eax
KERNELBASE.OpenProcess+21- 89 4D E0 - mov [ebp-20],ecx
KERNELBASE.OpenProcess+24- 83 E0 02 - and eax,02 { 2 }
KERNELBASE.OpenProcess+27- 89 4D E4 - mov [ebp-1C],ecx
KERNELBASE.OpenProcess+2A- 89 45 E8 - mov [ebp-18],eax
KERNELBASE.OpenProcess+2D- 8D 45 F4 - lea eax,[ebp-0C]
KERNELBASE.OpenProcess+30- 50 - push eax
KERNELBASE.OpenProcess+31- 8D 45 DC - lea eax,[ebp-24]
KERNELBASE.OpenProcess+34- 89 4D EC - mov [ebp-14],ecx
KERNELBASE.OpenProcess+37- 50 - push eax
KERNELBASE.OpenProcess+38- FF 75 08 - push [ebp+08]
KERNELBASE.OpenProcess+3B- 8D 45 FC - lea eax,[ebp-04]
KERNELBASE.OpenProcess+3E- 89 4D F0 - mov [ebp-10],ecx
KERNELBASE.OpenProcess+41- 50 - push eax
KERNELBASE.OpenProcess+42- FF 15 8C38BD77 - call dword ptr [KERNELBASE.WakeConditionVariable+5A11] { ->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(
[in] HANDLE SourceProcessHandle,
[in] HANDLE SourceHandle,
[in, optional] HANDLE TargetProcessHandle,
[out, optional] PHANDLE TargetHandle,
[in] ACCESS_MASK DesiredAccess,
[in] ULONG HandleAttributes,
[in] 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(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
在CreateProcess
API中的最后一个参数LPPROCESS_INFORMATION结构
中就包含了子进程的进程句柄
结构如下
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} 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破解论坛,如需转载请保留完整的作者信息以及出处。