The Spooler is a special process that manages access to printers by multiple users. For most users, the function of the Spooler is transparent. They generate a job for a printer and go to the printer to pick up the output. The Spooler permits users to continue working without waiting for a print job to finish printing.
This is a windows system file. This program runs in the background and is created by Microsoft. The task manager lists it as a printer driver host for 32bit applications. And it allows the 64-bit printer spooler service on x64 windows builds.
At that time, researchers from Kaspersky Lab discovered that attackers could combine the 0day CVE-2020-0986 with the 0day in IE browser to achieve privilege escalation and then execute code remotely. Now, Maddie Stone, a security researcher on Google’s Project Zero team, found that an attacker can still trigger CVE-2020-0986 and elevate kernel privileges by sending an offset instead of a pointer.
Stone pointed out that CVE-2020-0986 is an arbitrary pointer dereference vulnerability that allows an attacker to control the “src” and “dest” pointers of the memcpy function. The reason Microsoft’s patch is incorrect is because it modifies the pointer to an offset, so the parameters of the function can still be controlled.
In a blog post, Stone noted, “A low-integrity process can send LPC information to splwow64.exe (medium integrity) and get a write-what-where primitive in the splwow64 memory space. An attacker takes control of this target, replicates and the number of bytes copied by the memcpy call.
Splwow64 passes LPC information to GdiPrinterThunk. The vulnerable memcpy is located in message 0x6D.
The only difference between CVE-2020-0986 and CVE-2021-1648 is that for the former, the attacker sends a pointer, while for the latter, the attacker sends an offset!!!
The main difference was in the CVE-2020 version the Message Send, reply parameters are there with the reply. In the 2021 version these are not required. And, the rest whole code exploit remains the same. I have managed to rewrite the CVE-2020-0986 and the CVE-2021-1648. Then I reduced and removed the code and decreased a few lines to notice the crash in my debugger and check for the crashed of the splwow64 driver. I managed to make them to be just 4 lines different. The above code is totally reduced and modified from the original PoC from the existing blogs from Maddie Stone and Kaspersky. These code reductions and modifications allowed me to further understand what was the exact difference between the two and how they both were exploited.
Once the POC is built, run the following command to set its integrity level to low:
icacls splwow64_poc.exe /setintegritylevel L
Move the CreateDC.exe into the same directory as splwow64_poc.exe. CreateDC.exe is simply an x86 application that calls CreateDCA in order to start splwow64.exe.
ruby -e '`icacls exploit.exe /setintegritylevel L`; p `exploit.exe` while true'
You can also create a batch file and write these commands in a .bat extension –
save that as a .bat
This communication objects include process to process, process to driver, and driver to driver which can communicate between different CPU rings. A low-privilege can communicate with high-privilege. These are whole client-server models. The whole communication data has two parts. One is LPC Message. This is a simple LPC communication process. First, the Server needs to specify a port name, create a port, and then monitor the port. Client wants to connect to the server port, Client will send the request synchronously and wait for the server’s response, and then complete the entire communication after processing. This is a simple LPC communication process.
The GdiPrinterThunk function
The GdiPrinterThunk is a pretty complex function, whose workflow will be determined by a byte located at the offset 0x4 of the address specified in the first parameter. As already stated before, we can actually control the three parameters passed at the GdiPrinterThunk function by crafting a specific LPC message. In other words, this means we are able to control the GdiPrinterThunk workflow.
IDA analysis of this exploit –
The above code mainly does three things: first, judges whether LpcRequest.MessageHeader.DataSize is 0x20, and then judges whether the GDI32!GdiPrinterThunk function pointer exists, if it exists, it takes the value of LpcRequest.MsgSendLen 0x88 to EBX, and then calls splwow64!operator new allocates a 0x88 memory space in the splwow64.exe process space, and then we call this space InputBuffer.
I understood that there is a function in splwow64.ese called GdiPrinterThunk , which is specified by the pointer offset of 0x30 which is this statement –
ClientView.Length = 0x30;
And in another exploit it is at 0x38 –
NtOpenProcessToken(GetCurrentProcess(), 0x20008u, &tokenHandle) || ZwQueryInformationToken(tokenHandle, TokenStatistics, tokenInformation, 0x38u, &length);
So, this GdiPrinterThunk function is what enables us writing into the memory in a shared space as writing directly into memory is not easy, then a VirtualProtect function is called further.
But, to bypass the ASLR – there is another winspool.drv DLL which is used and every time I see the crash and load the modules in the debugger, I am able to witness this winspool.drv dll also.
While the initial part is just connecting to the printer port – and finding the name of the printer which is the 2nd parameter of the memcpy function.
The LPC messages are just like the payloads which are required and enable us to write this very exact message in the higher memory address of the printer driver.