Token story (CVE-2015-0002)
0x00 Preface
I like vulnerability research very much and sometimes find a significant difference between the difficulty of vulnerability mining and the difficulty of exploits. The Project Zero Blog contains many complex exploitation processes for seemingly trivial vulnerabilities. You may ask, why do we try to prove that the vulnerability is usable? I hope that at the end of this blog, you can better understand why we always spend a lot of effort to develop a poc to prove a security problem.
Our main objective of PoC is suppliers, but developing Poc also has other benefits. Customers using supplier systems can use PoC to test whether their systems have this vulnerability and ensure that all patches are installed. Even if the vendor is unwilling or unable to fix the vulnerability, the security vendor can use it to develop mitigation measures and vulnerability signatures. Without PoC, only people with reverse patching may understand it, and they may not consider your best interests.
I don't want this blog to involve too many technical details of this vulnerability (CVE-2015-0002. On the contrary, I will focus on the availability of relatively simple vulnerabilities and the PoC development process. This PoC is sufficient for suppliers to make a reasonable assessment of the vulnerability to reduce the workload. I will also explain the reasons for using shortcuts in PoC development and why.
0x01 reporting Vulnerabilities
The biggest problem with vulnerability research on closed or proprietary systems is the actual reporting process for fixing vulnerabilities. Especially in the case of complex or non-obvious vulnerabilities. If the system is open-source, you can develop a patch and submit it. This represents a chance to fix it. For a closed source system, the report process has to be passed. For better understanding, let's think about what a typical large supplier may need to do when accepting external security vulnerability reports.
This is a simple view of vulnerability response processing, but it is sufficient to explain the processing principles. For a company that develops most of their software internally, I cannot affect the repair cycle, but I can influence the delivery cycle. The more likely it will affect suppliers, the shorter the delivery cycle, and the faster the patch can be released. Everyone except those who are already using this vulnerability has the benefit. Don't forget, even if I didn't know the vulnerability before, it doesn't mean it's not known.
? In an ideal world of vulnerability Research (where I only need to do a few non-scientific jobs), if I find a bug, all I need to do is write some notes about it and send it to the supplier. They will understand the system and take immediate action to develop patches. The task is complete. Of course, this is not feasible. It is an important first step for suppliers to realize that this is a security issue. This may be a major obstacle from a delivery cycle to a patch cycle, especially when they are usually independent entities within the company. For the best possible, I may do two things:
Detailed details are provided in the report to help suppliers understand vulnerabilities.
Develop PoC to clearly indicate the impact of Security Vulnerabilities
0x02 Report Writing
Despite the fact that it is not enough in many cases, writing a report is critical for the vendor to fix security issues. As you can imagine, if something I wrote is similar to "the error is in ahcache. sys, fix it,LolThis does not really help the vendor. At least, I need to provide some background information, such as which systems are affected (not affected) by the vulnerability, what impact the vulnerability has (as far as I know), and where the problem exists.
Why is report alone not enough? Think about how large and modern software products are developed. It may be developed independently by team members in different modules. Based on the time when the vulnerability code exists, the original developer may have moved to another project, or all of them may have left the company. Even the relatively new code written by people who can communicate with each other does not mean that they remember how the code works. Anyone who is developing software of any size will encounter the code they wrote one month, one week, or even one day ago, but does not know how it works. There is a real possibility that security researchers who spend time analyzing software by command may be more familiar with the software than anyone in the world.
You can also consider the report in a scientific sense, that is, the vulnerability hypothesis. Some vulnerabilities can prove that, for example, buffer overflow can be proved by mathematical means. For example, it is impossible to put 10 items into a space that can only accommodate 5 items. But in many cases, there is nothing better than proof that development can be used. If it is completed correctly, the reporter and the supplier can be verified through experiments. This is the value of proof of concept. The proof of concept enables the vendor to observe the effects of the experiment and turn the hypothesis into a theory that no one can refute.
0x03 experiment-proven availability
The hypothesis assumes that a vulnerability has a real security impact and we will use PoC to objectively prove it. To do this, we not only need to provide suppliers with mechanisms to prove the authenticity of the vulnerability, but also need to be able to clearly understand why this constitutes a security problem.
The symptom depends on the vulnerability type. The memory corruption vulnerability may only need to prove that the application crashes when responding to certain inputs. But this is not always the case. Some memory damages do not provide attackers with any useful control. Therefore, it is necessary to prove that the current execution stream can be controlled, for example, the EIP register is ideally controlled.
Logical vulnerabilities may be more detailed. For example, you can write a file to a location that cannot be written or run the calculator program with elevated permissions. There is no method suitable for all situations, but at least ?? We need to show some security effects that can be observed objectively.
It should be noted that I did not develop the PoC into an available vulnerability exploitation Program (from the attacker's point of view), but it is sufficient to prove that this is a security problem to ensure that it can be repaired. Unfortunately, it is not easy to separate these two users. Sometimes the local permission escalation or remote code execution is not displayed, and its severity will not be taken seriously.
0x04 proof of concept
Now let's take a look at the challenges I encountered when developing the PoC of the ahcache Vulnerability I found. We should not forget to weigh the time spent on developing the PoC and the chance of fixing the vulnerability. If I don't spend enough time developing an available PoC, the vendor may not fix this vulnerability. On the other hand, the longer I spend, this vulnerability may be harmful to users.
0x05 technical details of Vulnerabilities
A little understanding of this vulnerability will help us discuss it later. Here you can see this vulnerability and the additional PoC I sent to Microsoft. The vulnerability exists in the ahcache. sys driver, which is introduced in Windows8.1, but is essentially implemented in the local Windows system that calls NtApphelpCacheControl. This system call is used to handle the application compatibility information locally cached for correcting the application behavior on a newer version of Windows.
Some operations called by this system require permissions, so that the driver checks the currently called applications to ensure that they have administrator permissions. These are completed in the AhcVerifyAdminContext function, which looks like the following code:
BOOLEAN AhcVerifyAdminContext(){ BOOLEAN CopyOnOpen;
BOOLEAN EffectiveOnly;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
PACCESS_TOKEN token = PsReferenceImpersonationToken( NtCurrentThread(),
&CopyOnOpen,
&EffectiveOnly,
&ImpersonationLevel);
if (token == NULL) { token = PsReferencePrimaryToken(NtCurrentProcess()); }
PSID user = GetTokenUser(token);
if(RtlEqualSid(user, LocalSystemSid) || SeTokenIsAdmin(token)) { return TRUE; }
return FALSE;}
This code checks whether the current thread simulates other users. Windows allows a thread to simulate other users on the system, so that the security operation can be correctly evaluated. If the thread is simulating, a pointer to the access token is returned. If the return value is NULL from PsReferenceImpersonationToken, the Code queries the access token of the current process. Finally, the Code checks whether the access token user is a local system user or whether the token is a member of the Administrators group. If the function returns TRUE, the privileged operation is allowed to continue.
All this seems to be correct. Where is the problem? Although full simulation is a special operation that can only be performed by users with simulated token privileges, normal users do not have the permission to simulate other users to perform non-security-related functions. During Simulated enabling, the kernel assigns a security level to the token to distinguish between privileged and non-privileged simulation. To understand this vulnerability, you only need to pay attention to two levels. SecurityImpersonation means that the simulation is privileged and SecurityIdentification is non-privileged.
If the token is assigned as SecurityIdentification, you can only query the token information, for example, the user who queries the token. If you try to open protected resources, such as files, the kernel will reject access. This is a potential vulnerability. If you look at the code, PsReferenceImpersonationToken returns a copy of the security level assigned to the token, but the Code does not verify whether it is SecurityImpersonation. This means that common users who can obtain the access token of the local system can simulate it on SecurityIdentification, and can still query the user through the check.
0x06 demonstrate basic vulnerability Exploitation
To exploit this vulnerability, You need to capture the access token of the local system, simulate it, and then call the system call through appropriate parameters. This must be achieved through the permissions of common users, otherwise it is not a security vulnerability. This system call has not been made public, so if we want to take a shortcut, we may only need to indicate that we can capture the token and can only do this?
This is not the case. This PoC will prove the possibility of being documented. That is, a common user can capture a token and simulate it as a simulated system design, which will not cause security issues. I already know that COM supports simulation. There are a lot of complicated system privileged services (such as BITS). We can interact with them as common users to simulate, it can communicate with our applications. This does not prove that we can reach the AhcVerifyAdminContext method with the Kernel Vulnerability, let alone successfully bypass the check.
? So we started a long process of reverse engineering to determine how system calls work. What parameters do you need to pass to make it useful. Some results from other researchers are also used here, but there is certainly no ready-to-use. This system call supports many different operations, not all operations require complex parameters. For example, the AppHelpNotifyStart and AppHelpNotifyStop operations can be easily called. They depend on the AhcVerifyAdminContext function. Now we can construct a PoC to verify the bypass of the check by observing the return code of the system call.
BOOL IsSecurityVulnerability() { ImpersonateLocalSystem();
NTSTATUS status = NtApphelpCacheControl(AppHelpNotifyStop, NULL);
return status != STATUS_ACCESS_DENIED;}
Does this prove that the vulnerability can be exploited? The history tells me no. For example, this problem involves almost identical operations, that is, you can simulate bypassing the Administrator check. In this case, except for information disclosure, I do not have enough evidence to prove whether it will cause other problems. So it is not fixed, even if it is indeed a security issue. To prove availability, we need to spend more time on PoC.
0x07 proof of concept for improvement
To improve the first PoC, I need to better understand what the system call is doing. The application compatibility cache is used to store query data in the application compatibility database. The rules contained in this database will tell the application program compatibility system what executable files require the application "shims" to implement custom behaviors, such as relying on the OS version number to avoid incorrect checks. When a process is created, it is queried. If a matching item is found, it is applied to a new process. The new process will query the shim data that it needs to apply from the database.
Since such processing is required each time a new process is created, each query of database files will bring significant performance overhead. Cache helps to reduce this impact. database queries can be added to the cache. If an executable file is created later, the cache query can quickly eliminate time-consuming database queries and apply or not apply a series of shims.
Therefore, we should be able to cache existing queries and apply them to any executable files. So I spent some time obtaining the format of system-called parameters to add my own query cache. For a 32-bit Windows 8.1, the structure looks like the following:
struct ApphelpCacheControlData { BYTE unk0[0x98]; DWORD query_flags; DWORD cache_flags; HANDLE file_handle; HANDLE process_handle; UNICODE_STRING file_name; UNICODE_STRING package_name; DWORD buf_len; LPVOID buffer; BYTE unkC0[0x2C]; UNICODE_STRING module_name; BYTE unkF4[0x14];};
You can see many unknown parts in the structure. If you want to apply it to Windows 7 (its structure is slightly different) or 64-bit (its structure size is different), this will cause problems, but it is not important for our purpose. We do not need to write exploitation code available on all versions of Windows. All we need to do is to prove to the vendor that this is a security problem. We only need to inform the supplier of the PoC restrictions (they will note the restrictions), which can be done. Suppliers should be able to determine whether the PoC can be used across operating system versions. After all, this is their product.
? So now we can add any cache entries. What are we actually adding? I can only add entries to the existing query results. You can modify the database to do a similar job of running the time code patch (the application compatibility system can also be used for patching), but this requires administrator privileges. Therefore, I need an existing shim to reuse it.
? I built a copy of the SDB Explorer tool so that I can dump any useful shim in an existing database query. I found that the 32-bit program has a shimwill pilot the executable regsvr32.exe and pass the original command line. This tool loads a DLL passed on the command line and executes a specific export method. If we can control the command line of the privileged process, we can redirect it to improve the permission.
This restricts the PoC to only be valid for 32-bit processes, but this is good. The last step is to select the process to redirect. I spent a lot of time studying the methods for starting a process and being able to control its command line parameters. I already know one way, that is, automatic UAC upgrade. Automatically upgrade to Windows 7 as a feature to reduce the number of UAC dialog boxes displayed by typical users. The operating system defines a fixed list of applications that can be automatically upgraded. When the UAC is set as the default setting and the user is the administrator, a dialog box is displayed when you request to upgrade these applications. I can easily use this feature to slow down the existing automatically promoted applications (in this case, I chose computerdefaults.exe) and asked the application to run the program to improve. The upgraded application is redirected to regsvr32, and the command line we fully control is passed. regsvr32 loads my DLL, and the code we get now is executed with elevated permissions.
In addition, PoC does not provide anything else and cannot be implemented through other mechanisms, but not always. This problem is fully demonstrated by providing an observed result (running any code as an administrator) so that Microsoft can reproduce and solve it.
0x08 interesting last point
Since it is easy to confuse whether it can only bypass UAC, I decided to spend a little time developing a new PoC, which can gain local system permissions without relying on UAC. Sometimes, I like to exploit the Write Vulnerability to prove that this can be done. To convert the original PoC to the PoC that grants local system permissions, I need a different application to redirect. In my opinion, the most likely goal is to register a scheduled task. Sometimes you can pass any parameter to the task processing process. Therefore, there are three restrictions for implementing this task. A common user must be able to start it, and it must start a process with local system permissions, the process must have any command line specified by the user. After some searches, I found the ideal target: Windows Store Maintenance Task (Windows Store Maintenance Task ). As we can see, it runs as a local system user.
By using the icacls tool to view the DACL of the task file, we can determine that normal users can start it. Note that the following items, nt authority \ Authenticated Users, have read and execute (RX) permissions.
Finally, by checking the XML task file, we can check whether common users can pass arbitrary parameters to the task. In WSTask, it uses a custom COM handler, but allows you to specify two command line parameters. This causes the executable file c: \ windows \ system32 \ taskhost.exe to be executed as a local system user and has any command line parameters.
Here, modify pocto use the "taskhost.exe" cache entry and use the DLL path as a parameter to start the task. There is also a fixed limit. Specifically, the worker can only work on 32-bit windows 8.1( No 32-bit taskhost.exe can be redirected on 64-bit platforms ). However, I am sure it can work on 64-bit through some efforts. Because the vulnerability has been fixed, I provide a new PoC, which is attached after the original issue.
0x09 conclusion
I hope I have proved that in order to ensure that the vulnerability is fixed, the vulnerability researchers will make some efforts. In the end, it is a compromise between the time spent on PoC development and vulnerability repair, especially when the vulnerability is complex or not obvious.
? In this case, I think I have made a correct balance. Although from the PoC I sent to Microsoft, on the surface, they simply bypassed UAC and combined the report to determine the true severity and develop patches. Of course, if they want to push back and claim that this is not usable, I will develop a more powerful PoC. Through further demonstration of the severity, I have also developed an available vulnerability that can be exploited to obtain local system permissions through a common user account.
? Disclosure of PoC is valuable in mitigating the public vulnerabilities developed by users or security companies. Without PoC, it is quite difficult to verify that security issues have been fixed or mitigated. It also helps inform researchers and developers about the types of issues they need to pay attention to when developing secure and sensitive applications. Vulnerability mining is not the only way for Project Zero to help the software improve security. Education is equally important.
Project Zero's mission is to solve software vulnerabilities. Developing Concept verification helps software vendors or open-source projects to take informed actions to fix vulnerabilities is also an important part of our responsibility.