PPL保护
前言
windows vista版本引入了“受保护进程”的概念(PP),目的是为了保护媒体内容并符合DRM(数字版权管理)要求。Mircrosoft开发了此机制使受信任的媒体播放器可以访问高品质例如蓝光的文件,而防止其他程序访问和复制这些内容。在Windows 8.1(NT 6.3)中又对受保护的进程进行了扩展引入了Protected Process Light(PPL)和保护级别,这意味着受保护的进程PP(L)与其他进程相比可以受到更多的保护,例如受保护的进程可防止其他用户层进程打开,注入等操作。
PPL进程保护
kd> dt _EPROCESS -y Protection
nt!_EPROCESS
+0x87a Protection : _PS_PROTECTION
kd> dt _PS_PROTECTION
nt!_PS_PROTECTION
+0x000 Level : UChar
+0x000 Type : Pos 0, 3 Bits //type
+0x000 Audit : Pos 3, 1 Bit
+0x000 Signer : Pos 4, 4 Bits //Signer
windbg查看进程内核对象EPROCESS可以看到Protection字段,此字段用来描述受保护进程的类型和保护级别。
通过ZwQueryInformationProcess并传递ProcessProtectionInformation参数可以查看系统中所有受保护进程的Type和Signer信息,事实上此函数传递的很多参数就是直接利用EPROCESS来查询各种信息。https://learn.microsoft.com/zh-cn/windows/win32/procthread/zwqueryinformationprocess?redirectedfrom=MSDN
typedef enum _PS_PROTECTED_TYPE {
PsProtectedTypeNone = 0, //进程不受保护
PsProtectedTypeProtectedLight = 1, //PPL类型
PsProtectedTypeProtected = 2 //PP类型
} PS_PROTECTED_TYPE, *PPS_PROTECTED_TYPE;
typedef enum _PS_PROTECTED_SIGNER {
PsProtectedSignerNone = 0, // 0
PsProtectedSignerAuthenticode, // 1
PsProtectedSignerCodeGen, // 2
PsProtectedSignerAntimalware, // 3
PsProtectedSignerLsa, // 4
PsProtectedSignerWindows, // 5
PsProtectedSignerWinTcb, // 6
PsProtectedSignerWinSystem, // 7
PsProtectedSignerApp, // 8
PsProtectedSignerMax // 9
} PS_PROTECTED_SIGNER, *PPS_PROTECTED_SIGNER;
_PS_PROTECTION的Type如果是PsProtectedTypeNone表示此进程不受保护,PsProtectedTypeProtectedLight表示这是个PPL类型的保护进程,PsProtectedTypeProtected表示这是个PP类型的保护进程。Signer表示受保护的级别,目前一共有10个级别不同的级别允许不同的权限(通常这个权限都很小)。
使用process explorer查看进程的Protection属性,系统进程wininit.exe,csrss.exe,smss.exe都是受保护的进程,直接使用process explorer结束这些受保护的进程会出现拒绝访问。
PPL保护原理
系统是如何通过PPL保护来防止进程被打开和关闭的呢?正常来说打开一个进程需要通过OpenProcess
打开进程PID并获取一个句柄,根据之前分析的OpenProcess调用过程可知内核中在创建一个句柄之前会先调用这个句柄对应的内核对象的_OBJECT_TYPE->TypeInfo.OpenProcedure函数,对于进程内核对象而言这个函数就是PspProcessOpen
OpenProcess()
├─ NtOpenProcess(进入内核层)
│ └─ PsOpenProcess
│ └─ PsLookupProcessByProcessId(从全局句柄表PspCidTable获取对应的进程内核对象EPROCESS地址)
| ObOpenObjectByPointer
│ └─ ObpCreateHandle
│ └─ ObpIncrementHandleCountEx
│ └─ _OBJECT_TYPE->TypeInfo.OpenProcedure(PspProcessOpen)
│ ObpPreInterceptHandleCreate
| 创建句柄项并设置句柄的值
│____________________ObpPostInterceptHandleCreate
PspProcessOpen
IDA分析ntoskrnl.exe中的PspProcessOpen函数,
PspProcessOpen函数首先会从根据目标进程的EPROCESS.Protection的Signer从RtlProtectedAccess表中获取此保护级别限制的权限。
RTL_PROTECTED_ACCESS RtlProtectedAccess[] =
{
// Domination, Process, Thread,
// Mask, Restrictions, Restrictions,
{ 0, 0, 0}, //PsProtectedSignerNone
{ 2, 0x000fc7fe, 0x000fe3fd}, //PsProtectedSignerAuthenticode
{ 4, 0x000fc7fe, 0x000fe3fd}, //PsProtectedSignerCodeGen
{ 0x108, 0x000fc7ff, 0x000fe3ff}, //PsProtectedSignerAntimalware
{ 0x110, 0x000fc7ff, 0x000fe3ff}, //PsProtectedSignerLsa
{ 0x13e, 0x000fc7fe, 0x000fe3fd}, //PsProtectedSignerWindows
{ 0x17e, 0x000fc7ff, 0x000fe3ff}, //PsProtectedSignerTcb
{ 0x1fe, 0x000fefff, 0x000ff7ff}, //PsProtectedSignerWinSystem
{ 0, 0x000fc6fe, 0x000fe3fd}, //PsProtectedSignerApp
各个Signer等级对应的权限控制如下:
PspProcessOpen进程判断打开进程的权限不符合受保护进程级别的权限限制后会继续调用PsTestProtectedProcessIncompatibility做进一步的判断.
PsTestProtectedProcessIncompatibility
PsTestProtectedProcessIncompatibility判断当前进程是否等于目标进程。如果等于检查就通过,不等于就继续调用PspCheckForInvalidAccessByProtection函数检查
PspCheckForInvalidAccessByProtection
PspCheckForInvalidAccessByProtection函数首先会判断当前PreviousMode是否为0(内核模式)。如果是就检查通过,如果不是就会调用RtlTestProtectedAccess并传入当前进程和目标进程的EPROCESS.Protection进行最后的检查。
RtlTestProtectedAccess
RtlTestProtectedAccess进行最后的检查分为三步。
- 如果Protection.type == 0(PsProtectedSignerNone),表示目标进程不是PP/PPL保护进程检查通过
- 如果当前进程Protection.type > 目标进程的Protection.type,表示当前进程是PsProtectedTypeProtected(PP),目标进程是PsProtectedTypeProtectedLight(PPL)则检查通过
- 如果目标进程和当前进程都为PP/PPL,并且Protection.Signer级别是当前进程 > 目标进程则检查通过
结论
如果打开进程的权限不满足受保护进程的要求就调用PsTestProtectedProcessIncompatibility做进一步的检查,如果PsTestProtectedProcessIncompatibility中有检查不通过则返回1,最后PspOpenProcess就返回0xC0000005
无访问权限,如果PsTestProtectedProcessIncompatibility检查都通过可以正常继续执行代码打开进程。具体的检查规则如下:
- 如果访问的权限符合受保护进程的权限限制则正常执行(这里注意如果目标进程是当前进程的子进程则可以额外允许PROCESS_TERMINATE权限)
- 如果目标进程等于当前进程则正常执行
- 如果是内核中调用(PreviousMode = 0)则正常执行
- 如果目标进程不是PP(L)进程则正常执行
- 如果目标进程是PPL类型而当前进程是PP类型则正常执行
- 如果目标进程和当前进程都是PP/PPL类型,当前进程的Protection.Signer级别高于目标进程则正常执行
- 其他情况PspProcessOpen都会返回
0xC0000022
拒绝访问
测试
编写测试程序并利用驱动设置其EPROCESS.Protection
,设置Protection.Type = 2(PsProtectedTypeProtected)
,设置Protection.Signer = 6(PsProtectedSignerTcb)
。这样就通过PPL将此程序保护起来了,如果直接用process explorer结束此进程会返回拒绝访问。
然后再编写程序去OpenProcess
打开被保护的进程并获取PROCESS_ALL_ACCESS
权限的话就会失败,因为PsProtectedSignerTcb级别的保护进程对权限的限制如下0x000fc7ff
,即只允许PROCESS_SUSPEND_RESUME | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SET_LIMITED_INFORMATION(0x3800)
这三种权限,所以权限检查失败PspProcessOpen会返回0xC0000022
拒绝访问。
如果使用PROCESS_SUSPEND_RESUME | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SET_LIMITED_INFORMATION(0x3800)
权限去打开受保护的进程则会成功获取到对应权限的句柄。
结束PPL保护进程
内核层结束PPL进程不受限制,如果用户层结束PPL进程就需要获取一个拥有PROCESS_TERMINATE权限的进程句柄。
通过设置PPL保护进程的EPROCESS.Protected为0
如果将PPL保护进程的EPROCESS.Protected设置为0的话,此进程就不再受系统保护。参考开源项目PPLKiller的代码https://github.com/Mattiwatti/PPLKiller
复制csrss/system进程中的句柄
PPL + Object双重保护
因为通过OpenProcess
打开进程时系统点调用OpenProcedure对应的回调函数PspProcessOpen
函数进行权限的检查后会继续调用通过 ObRegisterCallback注册的对象回调,如果在Object回调函数中对句柄权限进行进一步的降权就可以实现进程的双重保护。绕过双重保护的话需要在绕过PPL的同时绕过Object对象回调。
参考:
https://rayanfam.com/topics/reversing-windows-internals-part1/#objecttypes-in-windows
https://github.com/SinaKarvandi/Process-Magics/tree/master/EnumAllHandles/EnumAllHandles
https://github.com/Mattiwatti/PPLKiller/tree/master/PPLKiller
https://wooyun.kieran.top/#!/drops/987.浅析Windows的访问权限检查机制
https://www.crowdstrike.com/blog/evolution-protected-processes-part-2-exploitjailbreak-mitigations-unkillable-processes-and/
https://learn.microsoft.com/zh-cn/windows/win32/procthread/zwqueryinformationprocess?redirectedfrom=MSDN
https://cloud.tencent.com/developer/article/2013602