标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-3185] 作者: 大猪 发表于: [2022-10-02]
本文共 [536] 位读者顶过
IntroductionMicrosoft introduced recently a new mitigation in the Windows kernel, dubbed “Component Filter”. The following blog post will explains what this mitigation is about, and how it actually works. Let’s dive in. [出自:jiwo.org] The big pictureBefore going into the details of this new mitigation, I thought it would be better to present my own view of the aims of this new mitigation, and how it integrates with the current state of Windows’s kernel security policy. Hopefully, this is accurate enough, even if I’ll make some simplifications. People who just want technical details could go to the next paragraph. This new mitigation is a new syscall disabling mechanism, but only for “components” of the windows kernel. Let’s see what that means: Historically, Windows promoted a programming paradigm for userland programs based on APIs, and not on syscalls. What this means is that Microsoft considered that syscalls are not something a standard userland program should do directly, and instead, those programs should use the programmatic Windows API. It implies that syscalls can be changed between multiple Windows releases (it’s still the case), and that Microsoft is entirely in control of this mechanism. It also implies that the Windows API is fixed, otherwise all the code relying on it has to change between Windows releases. That’s why we are seeing differently numbered APIs when changes are introduced (have a look for example at GetTempPathA and GetTempPath2A). On the contrary, Linux has a programming paradigm based on syscalls. This means syscalls are fixed, but the API is not. This explains the existence of the dup2 syscall to replace the dup one. This programming paradigm explains partly why Microsoft has not implemented any correct syscall filtering mechanism : those are not fixed! So, to provide some protection against malicious syscalls, Microsoft decided to implement a mechanism based on the disablement of syscalls given their underlying treating component. But what’s a treating component? It’s the module responsible to handle a given syscall in kernel land. For example, one could see Windows syscalls as divided in 4 big categories:
Here is a little schema resuming that: Microsoft first provided the win32k lockdown mechanism. Without going into details, based on a flag defined in the EPROCESS structure, a win32k syscall is not handled by the same function. This new component filter mitigation follows the same principle, but with more details : it will prevent transaction manager related syscalls that are dispatched towards the kernel transaction manager(KTM) component. How it works internallyFor this new mitigation to be operational, Microsoft implemented a new DisabledComponentFlags field in the EPROCESS structure. This field is an unsigned long and gets filled by the PspApplyComponentFilterOptions function. This function is called by the internal function PspAllocateProcess, and as such this new field can only get set when a process is spawned. The only way I found to define this field is through the usage of the STARTUPINFO attributes, through the UpdateProcThreadAttribute function. This DisabledComponentFlags gets checked by a new introduced function dubbed PsIsComponentEnabled in ntoskrnl. Here is its code:
The only component using this function is the tm.sys driver, which is the implementation of KTM. Inside this driver, the following functions are now using this PsIsComponentEnabled function :
For all those functions the check related to the component is done at the beginning and is used like the following:
Using this new mitigationAs said before, you have to use the UpdateProcThreadAttribute function to actually spawn a process with this new mitigation. Here is a code to do so:
Once one of your process is spawned with this mitigation, you could try to create a volatile transaction manager with the following snippet, and see it fails:
ConclusionThis new mitigation is actually a way to prevent 7 syscalls to be called within a process. Because there is no way to activate this feature outside of spawning a process with an additionnal attribute, this feature seems reserved for sandbox developers for now. As such, this mitigation is clearly niche. However, because the field associated with this mitigation is not fully used, I’m pretty sure we will see additional components to be filtered in the same way, especially file systems components like ntfs.sys or nfs.sys. Hopes for the futureAs a defender, I’m baffled by the level of syscall filtering achieved by Linux with seccomp and seccomp-bpf, and the current level of the same feature on Windows. Especially, without going into a complete filtering mechanism capable of inspecting arguments passed to syscalls, Microsoft already has all the mechanisms to provide a correct syscall disabling feature inside the kernel, named “token privileges”. Indeed, when you look at what those privileges do, they are in fact preventing the usage of given syscalls! So, I would really appreciate if someone from Microsoft could state on the following system and if it’s planned to implement something similar or not in future. Here is how I imagine a global syscall disabling mechanism can take place (based on how token privileges work): At the spawn of a process, you create two bitmaps inside the EPROCESS structure in place of current token privileges:
Basically, the first bitmap serves as the same field of the Present field of the _SEP_TOKEN_PRIVILEGES while the second one serves as the Enabled field. The first bitmap can be created based on the following factors:
The second bitmap is created by copying the first one and updated with:
Like token privileges, you then offer the ability to disable additional syscalls, but not the ability to activate ones that are not available inside the first bitmap. You finally just need a little checker inside the KiServiceInternal function which is the following:
With such a system, you have so :
Of course, this system poses a question about performance. Microsoft certainly also chose the current implementation to support corner cases that I’m not aware about. This would also require to deprecate functions reasoning about token privileges. To finish, I would say this system could not be complete without transforming all the “enum-based” syscalls (like the NtQuery and NtSet syscalls) into unitary syscalls. A simple example are the kernel leaks offered by the NtQuerySystemInformation function : those are actively used to achieve local privilege escalations from medium integrity, but they are all now under the same syscall. |