- Регистрация
- 20.01.2011
- Сообщения
- 7,665
- Розыгрыши
- 0
- Реакции
- 135
In this series of blog posts, I will explore yet another avenue for bypassing LSA Protection in Userland. I will also detail the biggest challenges I faced while developing a proof-of-concept, and discuss some novel techniques and tricks to load an arbitrary DLL in LSASS, or even dump its memory.
или Зарегистрируйся and Для просмотра ссылки Войди или Зарегистрируйся a few years prior, allowing this protection to be easily bypassed without the need to execute code in the Kernel.
This change effectively broke my Для просмотра ссылки Войдиили Зарегистрируйся proof-of-concept (PoC) but, in October 2022, Для просмотра ссылки Войди или Зарегистрируйся posted a message on Twitter in which he alluded to the fact that this wasn’t completely true. To prove his point, he attached a screenshot showing how he used a technique called “Bring Your Own Vulnerable DLL” to bring the original vulnerability back from the dead, and run Для просмотра ссылки Войди или Зарегистрируйся again without any modification.
Для просмотра ссылки Войдиили Зарегистрируйся
Since then, I kept thinking about this concept, and how I could use it to execute arbitrary code within a protected process using other DLLs, and most importantly, without having to reboot.
или Зарегистрируйся is enabled. Besides, LSASS is also a more appealing target when it comes to extracting in-memory credentials during post-exploitation. To illustrate what I mean by that, I listed all the services that may run within this process, as shown below.
Для просмотра ссылки Войдиили Зарегистрируйся
PowerShell – List of services that may run in LSASS
Alternatively, Для просмотра ссылки Войдиили Зарегистрируйся can be used to list services that are actually running within LSASS.
Для просмотра ссылки Войдиили Зарегистрируйся
System Informer – List of services running in LSASS
Because I’m constantly monitoring for public documentation, PoCs and exploits for Elevation of Privilege (EoP) bugs, I knew that the CNG Key Isolation service, a.k.a. “KeyIso”, was a good target. More specifically, I knew that I wanted to target this service when I saw the blog post Для просмотра ссылки Войдиили Зарегистрируйся by Для просмотра ссылки Войди или Зарегистрируйся, and the PoC exploit published by Для просмотра ссылки Войди или Зарегистрируйся on GitHub Для просмотра ссылки Войди или Зарегистрируйся, as they would offer the initial building blocks I needed for what I had in mind.
In their blog post, Для просмотра ссылки Войдиили Зарегистрируйся actually discusses two separate bugs: an out-of-bound (OOB) read (Для просмотра ссылки Войди или Зарегистрируйся), which serves as an information disclosure primitive to then exploit a use-after-free (UAF) flaw (Для просмотра ссылки Войди или Зарегистрируйся). I won’t cover the details of these two issues, nor the implementation of the PoC exploit, as it goes way beyond my knowledge and skills. The only thing you need to know for now is that these bugs can be abused through a subset of RPC procedures exposed by the KeyIso service, and that their successful exploitation eventually leads to the control of a CALL instruction’s target (RAX register), and the first argument (RCX register).
Для просмотра ссылки Войдиили Зарегистрируйся
Registry – Configuration of the KeyIso service
The actual path of the module containing the implementation of the service is set in the Parameters key, and has the value %SystemRoot%\system32\keyiso.dll.
Для просмотра ссылки Войдиили Зарегистрируйся
Registry – Parameters of the KeyIso service
Lastly, the default DACL of this key grants Full Control to the Administrators group, so we don’t even need to impersonate Trusted Installer to modify it. If we want to load a vulnerable version of this DLL in LSASS, we can just stop the service, change the path of the DLL in the Registry, and restart it.
Для просмотра ссылки Войдиили Зарегистрируйся
Registry – Permissions of the Parameters key
I did just that, and got the system error code 577 (ERROR_INVALID_IMAGE_HASH) – “Windows cannot verify the digital signature for this file” – when trying to start the service. This is the error code you are supposed to get when attempting to load a non Microsoft-signed DLL in a PP(L). In my case though, I’m using a legitimate Windows DLL, so what’s causing this issue?
Для просмотра ссылки Войдиили Зарегистрируйся
Attempting to start the KeyIso service with net.exe
To find out, we should first compare the signatures of the built-in keyiso.dll, and the imported one, using the PowerShell command Get-AuthenticodeSignature. In the case of the imported DLL, the status is just NotSigned, which is consistent with the previous error message at least…
Для просмотра ссылки Войдиили Зарегистрируйся
PowerShell – Comparison of Authenticode signatures
The reason why Windows can’t find the DLL’s signature is simply because it isn’t stored in the file. For a binary such as lsass.exe, the signature is directly embedded into the file, but for most DLLs, this is not the case. We can see that by comparing the properties of lsass.exe and keyiso.dll for instance. One has a “Digital Signatures” tab, but not the other. So, where is the signature stored?
Для просмотра ссылки Войдиили Зарегистрируйся
Properties of lsass.exe and keyiso.dll
A more common way to store file signatures on Windows consists in using Для просмотра ссылки Войдиили Зарегистрируйся. As explained in the documentation, “A catalog file contains a collection of cryptographic hashes, or thumbprints.”, and “Each thumbprint corresponds to a file that is included in the collection.”. One way to see which catalog file is associated to a given binary is to use Для просмотра ссылки Войди или Зарегистрируйся with the option -i.
Для просмотра ссылки Войдиили Зарегистрируйся
Checking the signature of keyiso.dll with SigCheck.exe
The screenshot above was taken on a Windows 11 machine manually updated with the package KB5023778 to get the version 10.0.22621.1485 of keyiso.dll, the version prior to the security patch for CVE-2023-28229 and CVE-2023-36906.
Для просмотра ссылки Войдиили Зарегистрируйся
Applying the update package KB5023778 on Windows 11
We can thus extract both the vulnerable DLL and the catalog file containing its signature. After copying the catalog file to the CatRoot folder of a fully updated Windows 11 machine, we can confirm that the signature of the imported keyiso.dll file is now recognized by the OS.
Для просмотра ссылки Войдиили Зарегистрируйся
Checking the signature of an imported keyiso.dll file
And there we have it, a vulnerable version of keyiso.dll loaded in our protected LSASS process!
Для просмотра ссылки Войдиили Зарегистрируйся
Starting the KeyIso service using a vulnerable DLL
или Зарегистрируйся worked as intended. However, even after running the exploit a few times, it still failed to go past the information disclosure step.
Для просмотра ссылки Войдиили Зарегистрируйся
Running the original Proof-of-Concept exploit
The information disclosure vulnerability is due to an improper bound check in the function SPCryptGetProviderProperty, which can be abused by first calling SPCryptSetProviderProperty with a specially crafted input buffer. What I didn’t realize initially was that these two functions are not implemented in keyiso.dll, but in ncryptprov.dll.
The DLL ncryptprov.dll contains the implementation of the Для просмотра ссылки Войдиили Зарегистрируйся. We can see that by opening the Registry editor, and checking the content of the Image value in its properties, as highlighted on the screenshot below.
Для просмотра ссылки Войдиили Зарегистрируйся
Registry – Settings of the “Microsoft Software Key Storage Provider”
This is a problem because ncryptprov.dll is automatically loaded by LSASS when it starts. We could modify the value of the Image property in the registry to specify the name of a vulnerable version of this DLL instead, but then we would still have to restart the machine.
Для просмотра ссылки Войдиили Зарегистрируйся
System Informer – DLL ncryptprov.dll loaded in LSASS
Therefore, for this exploit to work, we also need to figure out a way to load a vulnerable version of ncryptprov.dll.
[/QUOTE]
Bring Your Own Vulnerable DLL (BYOVDLL)
In July 2022, Microsoft brought some changes to their Protected Process Light (PPL) implementation to mitigate a well-known flaw, originally discovered by Для просмотра ссылки ВойдиThis change effectively broke my Для просмотра ссылки Войди
Для просмотра ссылки Войди
Since then, I kept thinking about this concept, and how I could use it to execute arbitrary code within a protected process using other DLLs, and most importantly, without having to reboot.
Choosing our Target
As a reminder, there are currently two “protection levels”: Protected Process (PP) and Protected Process Light (PPL). Each protection level has its own set of “signer types”, such as “Windows”, “WinTcb”, or even “Lsa” in the case of LSASS. The combination of these two values defines a hierarchy, thereby making some processes “more protected” than others. Thus, we want to target a PP with the highest signer type available, but those processes usually present a smaller attack surface than PPLs, such as LSASS when Для просмотра ссылки ВойдиДля просмотра ссылки Войди
PowerShell – List of services that may run in LSASS
Alternatively, Для просмотра ссылки Войди
Для просмотра ссылки Войди
System Informer – List of services running in LSASS
Because I’m constantly monitoring for public documentation, PoCs and exploits for Elevation of Privilege (EoP) bugs, I knew that the CNG Key Isolation service, a.k.a. “KeyIso”, was a good target. More specifically, I knew that I wanted to target this service when I saw the blog post Для просмотра ссылки Войди
In their blog post, Для просмотра ссылки Войди
Loading a Vulnerable Version of the KeyIso DLL
The ImagePath configured for the KeyIso service is the path of lsass.exe. This is because its type is Win32ShareProcess (32), which means it shares the same process as other services, such as EFS or VaultSvc, as we saw earlier.Для просмотра ссылки Войди
Registry – Configuration of the KeyIso service
The actual path of the module containing the implementation of the service is set in the Parameters key, and has the value %SystemRoot%\system32\keyiso.dll.
Для просмотра ссылки Войди
Registry – Parameters of the KeyIso service
Lastly, the default DACL of this key grants Full Control to the Administrators group, so we don’t even need to impersonate Trusted Installer to modify it. If we want to load a vulnerable version of this DLL in LSASS, we can just stop the service, change the path of the DLL in the Registry, and restart it.
Для просмотра ссылки Войди
Registry – Permissions of the Parameters key
I did just that, and got the system error code 577 (ERROR_INVALID_IMAGE_HASH) – “Windows cannot verify the digital signature for this file” – when trying to start the service. This is the error code you are supposed to get when attempting to load a non Microsoft-signed DLL in a PP(L). In my case though, I’m using a legitimate Windows DLL, so what’s causing this issue?
Для просмотра ссылки Войди
Attempting to start the KeyIso service with net.exe
To find out, we should first compare the signatures of the built-in keyiso.dll, and the imported one, using the PowerShell command Get-AuthenticodeSignature. In the case of the imported DLL, the status is just NotSigned, which is consistent with the previous error message at least…
Для просмотра ссылки Войди
PowerShell – Comparison of Authenticode signatures
The reason why Windows can’t find the DLL’s signature is simply because it isn’t stored in the file. For a binary such as lsass.exe, the signature is directly embedded into the file, but for most DLLs, this is not the case. We can see that by comparing the properties of lsass.exe and keyiso.dll for instance. One has a “Digital Signatures” tab, but not the other. So, where is the signature stored?
Для просмотра ссылки Войди
Properties of lsass.exe and keyiso.dll
A more common way to store file signatures on Windows consists in using Для просмотра ссылки Войди
Для просмотра ссылки Войди
Checking the signature of keyiso.dll with SigCheck.exe
The screenshot above was taken on a Windows 11 machine manually updated with the package KB5023778 to get the version 10.0.22621.1485 of keyiso.dll, the version prior to the security patch for CVE-2023-28229 and CVE-2023-36906.
Для просмотра ссылки Войди
Applying the update package KB5023778 on Windows 11
We can thus extract both the vulnerable DLL and the catalog file containing its signature. After copying the catalog file to the CatRoot folder of a fully updated Windows 11 machine, we can confirm that the signature of the imported keyiso.dll file is now recognized by the OS.
Для просмотра ссылки Войди
Checking the signature of an imported keyiso.dll file
And there we have it, a vulnerable version of keyiso.dll loaded in our protected LSASS process!
Для просмотра ссылки Войди
Starting the KeyIso service using a vulnerable DLL
Testing the Information Disclosure (CVE-2023-36906)
Before going any further, I wanted to make sure that the initial Для просмотра ссылки ВойдиДля просмотра ссылки Войди
Running the original Proof-of-Concept exploit
The information disclosure vulnerability is due to an improper bound check in the function SPCryptGetProviderProperty, which can be abused by first calling SPCryptSetProviderProperty with a specially crafted input buffer. What I didn’t realize initially was that these two functions are not implemented in keyiso.dll, but in ncryptprov.dll.
The DLL ncryptprov.dll contains the implementation of the Для просмотра ссылки Войди
Для просмотра ссылки Войди
Registry – Settings of the “Microsoft Software Key Storage Provider”
This is a problem because ncryptprov.dll is automatically loaded by LSASS when it starts. We could modify the value of the Image property in the registry to specify the name of a vulnerable version of this DLL instead, but then we would still have to restart the machine.
Для просмотра ссылки Войди
System Informer – DLL ncryptprov.dll loaded in LSASS
Therefore, for this exploit to work, we also need to figure out a way to load a vulnerable version of ncryptprov.dll.
[/QUOTE]
Registering a Key Storage Provider
Fortunately, we don’t need to change the configuration of the built-in Microsoft Software Key Storage Provider (KSP) to load a vulnerable version of ncryptprov.dll. Instead, we should theoretically be able to register a new KSP. My only worry was whether it could be done without a machine reboot.
I couldn’t find any official documentation explaining how to register a Key Storage Provider, so my idea was to find a third-party provider and analyze its installation process to find out how to do it through reverse engineering. I quickly came across the documentation of Для просмотра ссылки Войдиили Зарегистрируйся, and more specifically its Windows installation and configuration. After installing it, I observed that a new provider named “YubiHSM Key Storage Provider” was indeed available, and I was also able to instantiate it with a call to the documented Win32 API NCryptOpenStorageProvider.
Для просмотра ссылки ВойдиКод:NCRYPT_PROV_HANDLE hProvider = NULL; SECURITY_STATUS status; status = NCryptOpenStorageProvider(&hProvider, argv[1], 0); wprintf(L"NCryptOpenStorageProvider: 0x%08x\n", status); if (status == ERROR_SUCCESS) { status = NCryptFreeObject(hProvider); wprintf(L"NCryptFreeObject: 0x%08x\n", status); }
или Зарегистрируйся
Opening a third-party KSP with NCryptOpenStorageProvider
This is the confirmation that it is possible to register a KSP without a reboot. The question is how to do that programmatically? My initial idea was to naively replicate the registry structure, but without great surprise, this did not work. So, instead, I monitored the installation process of the YubiHSM MSI package with Для просмотра ссылки Войдиили Зарегистрируйся.
Для просмотра ссылки Войдиили Зарегистрируйся
Process Monitor – Analyzing the installation process of YubiHSM
This is how I found that the KSP is registered using the API BCryptRegisterProvider. The name sounded familiar to some BCrypt* functions I already knew about, so why didn’t I find it in the public Microsoft documentation, you might ask. As it turns out, the header file bcrypt.h is largely Для просмотра ссылки Войдиили Зарегистрируйся, but there is no reference to BCryptRegisterProvider in there.
Для просмотра ссылки Войдиили Зарегистрируйся
Online documentation of the header file bcrypt.h
Part of the answer came from a GitHub Для просмотра ссылки Войдиили Зарегистрируйся on the Для просмотра ссылки Войдиили Зарегистрируйся project, which provides Windows drivers for KVM guest virtual machines. From the thread of messages, I understood that BCryptRegisterProvider is defined in the header file bcrypt_provider.h, and that this file is provided through the Для просмотра ссылки Войдиили Зарегистрируйся (CPDK), which needs to be installed on top of the Windows SDK.
Для просмотра ссылки Войдиили ЗарегистрируйсяI
nstallation of the Cryptographic Provider Development Kit
Tu use it, you have to update the include path of your C/C++ project and add the entry $(WindowsSdkDir)Cryptographic Provider Development Kit\Include.
Для просмотра ссылки Войдиили Зарегистрируйся
Visual Studio – Adding the CPDK to the include path of a project
Just knowing the name of the API, without official documentation, is not ideal though. We can get some information about its usage from the header file, but we can also search for sample code on the Internet. One instance I found is in the Для просмотра ссылки Войдиили Зарегистрируйся (GCP)’s project Для просмотра ссылки Войдиили Зарегистрируйся on GitHub, in the file Для просмотра ссылки Войдиили Зарегистрируйся.
Для просмотра ссылки Войдиили Зарегистрируйся
Sample code from GCP showing how to register a KSP
Below is a slightly simplified version of the code I used to register and unregister my own Key Storage Provider, based on the code of the GCP project.
The screenshot below shows the result. First, a KSP named “foo123” is successfully registered using a non-existent DLL named foo123.dll. Then, the program tries to instantiate it, but fails, which is expected since the supporting DLL doesn’t exist. However, thanks to Process Monitor, we can see that LSASS tries to load it, which tends to confirm that the KSP registration worked.Код:NTSTATUS WINAPI RegisterKeyStorageProvider(LPCWSTR ProviderName, LPCWSTR ImageName) { NTSTATUS status = 0; CRYPT_PROVIDER_REG provider_reg; CRYPT_IMAGE_REG image_reg; CRYPT_INTERFACE_REG interface_reg; PCRYPT_INTERFACE_REG interfaces[1]; PWSTR pwszFunctions[1]; pwszFunctions[0] = const_cast<wchar_t*>(NCRYPT_KEY_STORAGE_ALGORITHM); interface_reg.dwInterface = NCRYPT_KEY_STORAGE_INTERFACE; interface_reg.dwFlags = CRYPT_LOCAL; interface_reg.cFunctions = 1; interface_reg.rgpszFunctions = pwszFunctions; interfaces[0] = &interface_reg; image_reg.pszImage = const_cast<wchar_t*>(ImageName); image_reg.cInterfaces = 1; image_reg.rgpInterfaces = interfaces; provider_reg.cAliases = 0; provider_reg.rgpszAliases = NULL; provider_reg.pUM = &image_reg; // User mode only provider_reg.pKM = NULL; // User mode only status = BCryptRegisterProvider(ProviderName, CRYPT_OVERWRITE, &provider_reg); // ... status = BCryptAddContextFunctionProvider( CRYPT_LOCAL, NULL, NCRYPT_KEY_STORAGE_INTERFACE, NCRYPT_KEY_STORAGE_ALGORITHM, ProviderName, CRYPT_PRIORITY_BOTTOM ); // ... return status; } NTSTATUS WINAPI UnregisterKeyStorageProvider(LPCWSTR ProviderName) { NTSTATUS status; status = BCryptRemoveContextFunctionProvider( CRYPT_LOCAL, NULL, NCRYPT_KEY_STORAGE_INTERFACE, NCRYPT_KEY_STORAGE_ALGORITHM, ProviderName ); // ... status = BCryptUnregisterProvider(ProviderName); // ... return status; }
Для просмотра ссылки Войдиили Зарегистрируйся
Registering a fake Key Storage Provider named “foo123”
My original goal was to load a vulnerable version of ncryptprov.dll in LSASS though, so I used this proof-of-concept to register a KSP named “Vulnerable Key Storage Provider” with the path of an older version of this DLL. After that, I used the PoC again to try and open the provider, and it worked! Thanks to Для просмотра ссылки Войдиили Зарегистрируйся, I could also confirm that the vulnerable DLL was loaded, alongside the original one.
Для просмотра ссылки Войдиили Зарегистрируйся
Loading a vulnerable version of ncryptprov.dll in LSASS
After that, I just had to update the exploit code, so that it opens the “Vulnerable Key Storage Provider” instead of the default “Microsoft Software Key Storage Provider”, and run it again to confirm that the memory leak worked as intended.
Для просмотра ссылки Войдиили Зарегистрируйся
Testing the information leak exploit again after loading a vulnerable version of ncryptprov.dll
Testing the Full Exploit Chain
With the ability to load vulnerable versions of both keyiso.dll and ncryptprov.dll, it was time to test the full exploit chain. The original PoC uses LoadLibraryW, with the absolute path of a DLL on disk. This is perfectly suitable for a privilege escalation scenario where LSASS is not protected. In our case though, this would not work because a PPL won’t load a DLL which is not signed by Microsoft. So, I just replaced the address of LoadLibraryW with the address of OutputDebugStringW, and the DLL path with a custom message. This way, I can see when the execution is triggered using Для просмотра ссылки Войдиили Зарегистрируйся, instead of monitoring filesystem events resulting from a call to LoadLibraryW with Process Monitor.
After restarting the KeyIso service and registering a custom Key Storage Provider, it was time to run the PoC again. And a few seconds later, I finally saw the message “I’m in LSASS!!!”, thus confirming the exploit worked as intended!
Для просмотра ссылки Войдиили Зарегистрируйся
Executing OutputDebugStringW from within LSASS
Conclusion
In this first part, we saw that it is possible to use the technique called “Bring Your Own Vulnerable DLL” to reintroduce two vulnerabilities, and then exploit them to gain arbitrary code execution within a protected LSASS process.
Для просмотра ссылки Войдиили Зарегистрируйся
From BYOVDLL to arbitrary code execution within a protected LSASS process
The term “arbitrary” may sound a bit exaggerated at this point though, and rightfully so. We’ve only proven that we can print a debug message. In the next part, I will go over the exploitation strategies I considered and explored, what failed, and what worked. In the meantime, and if you want to learn more about the OOB read and UAF vulnerabilities, I would suggest that you read the blog post Для просмотра ссылки Войдиили Зарегистрируйся.
Для просмотра ссылки Войдиили Зарегистрируйся