Hello everyone, welcome to DEF CON Safe Mode. What a special year, glad to see you in this way. Our talk is Evil Printer, How to Hack Windows Machines with Printing Protocol. First, a self-introduction. Zhi Peng Huo is a senior security researcher, and he is a member of the EcoSec team at Dunson Security's Shell Lab. His research focuses on Windows and macOS platform security. He has found and reported many vulnerabilities to Microsoft and Apple. Chuanda Ding is a co-author of this presentation. He leads the EcoSec team at Dunson Security's Shell Lab. This is the agenda of our talk. First, an introduction of printing internals, components, and interactions. Then, we cover the attack surface of Windows printing. Finally, a critical bug that we found, a detailed walkthrough of its exploitation, and patch. We have published a demo video on Twitter about two months ago. First, the attacker listens for incoming connections. Next, the victim tries to connect to a attacker-controlled printer. The attacker immediately gets a reverse shell with system privilege. This vulnerability is what we call the evil printer. In this talk, we will show you what it is, how we found and exploit it, and what can it do. Most of you may have done some printing before, and many of you may have worked with a network printer. So how does network printing work? The system where users create the print job on is a print client. Print server connects to the physical printer and makes the printer available to other systems on the network. When client wants to print a document, it will go to the server and say, hey, server, print this document. The server gets the print job, then it will go to the real printer and say, hey, printer, print this. The printer would finally print a document for the client. This is the procedure when you want to print a document on a client. There are so many different printer products manufactured by different companies. Printers do not understand application data from print clients. So the application data needs to be converted to the data format that the printer could understand. This process is called rendering. In a network printing, there are two different types of rendering. Client-side and server-side. For client-side rendering, print client translates the application data to printer-specific data, then sends the data to the printer server. For server-side rendering, print client sends the unmodified data directly to the printer server. Then the printer server translates the application data to the printer-specific data. The rendering jobs are handled by print driver. Printer driver is an important component of printing. It is the interface component between the OS and the printers. Printer drivers include a rendering component and a configuration component. The rendering component converts application data into the data that printer could use. And the configuration component contains a user interface that enables users to control the printers. In order to support both client-side and server-side rendering, it is a requirement that the printer drivers are available to both print client and print server. There are two mechanisms for distributing printer driver. They are called point-and-print and package point-and-print. Point-and-print allows a print client to download printer driver directly from a print server. Package point-and-print allows a print client to download a printer support package. And the package would include a needed printer driver. According to Microsoft, the package approach to driver installation provides improved security for point-and-print by checking driver signing during the establishment of a point-and-print connection. Print spooler service is the core of Windows printing. Print spooler service manages print drivers. It is responsible for retrieving a correct print driver and dynamically loading it. It is an autostart service on Windows OS. It manages the entire printing process and explores the printing APIs for other printing components and the user applications to use. It implements both print client and print server roles. The design of the print spooler is extremely dangerous. For example, it runs at system privilege level, it has networking tasks, and it can dynamically load third-party binaries. Client-server printing model is a very common model that shares the printer through the network to many users. In this model, print server connects to the real printer and shares itself in the network. The print client connects to the shared print server. When applications on print client want to start print job, it calls the printing APIs to communicate with the local print spooler. Then print spooler will send the action to the remote print spooler on a print server through network protocols, such as SMB. The print spooler on the print server would communicate with the real printer to finish the printing job. The Windows printing is ancient. It has existed for more than 25 years. It's highly integrated with Windows OS, and it is one of the most important services. The architecture and implementation of it is extremely complex, even confusing. Many parts of it works at the highest privilege level to make it one of the most valuable targets for bug hunting. There are two types of attack surfaces of Windows printing. First, the local attack surface. In order to serve applications and other components running at different privilege levels, Windows printing made many of its interfaces available to applications running at lower privilege levels. Abusing them may cause local privilege escalation. Second, the remote attack surface. In client-server printing model, both the print server and print client would suffer from the potential attack. For the print server, it shares the printer to other users and exposes itself to potential attacks in the network. For a print client, it may be attacked by a malicious print server, also known as evil printer. What happens behind the scenes when Windows connects to a printer? If a user wants to do print jobs through a print server, they must actively connect to the print server. There are many methods for a print client to connect to a print server. If you are familiar with PowerShell, you could use add-printer command-line to add a new network printer connection by specifying the name of a print server and a shared printer on that server. If you want to program, Microsoft provides Win32 print spooler API, such as add-printer-connection and add-printer-connection-to. And you could also use the GUI provided by print-ui-im to select a shared printer by name. Although many methods could be used for connecting to a specified print server, they could be traced to the one same API add-printer-connection-to. This API adds a connection to the specified printer for the current user and specified connection details. The print server and shared printer name are passed in through parameter pszName. When adding printer connection, if the print client needs to download and install printer driver from a remote print server, it will show a warning dialog to ask a user to install the driver or cancel the request. What's the purpose of this warning dialog? About four years ago, security researchers from Vector AI found a remote code execution vulnerability in Windows Print Spooler servers. The root cause of this vulnerability is print client would load and install a malicious printer driver from attacker-controlled print server. To address this vulnerability, Microsoft simply added a warning dialog before installing untrusted printer drivers. It does protect the print client from installing printer drivers without the user concerned. But is this protection sufficient? In order to answer this question, let's dig into the implementation of add-printer-connection-to first. Any applications or print client that call add-printer-connection-to API will send an RPC call to print spooler service on printer client. The remote function called is RPC add-printer-connection-to. If the print client is trying to add a remote print server, the print spooler on print client would try to communicate with print spooler on print server and get information from it. When print server is fully controllable, it is obvious that the data back to the print spooler on print client is also fully controllable by print server. For print client, it is an input source that cannot be trusted. If the returned information is not fully verified, it would cause critical security issues. The information from the print server would be handled by print spooler on print client. After finishing, it would return to the caller of add-printer-connection-to. Add-printer-connection-to would get the return value from print spooler on print client directly. Error code 0xBB9, which is error printer driver download needed, is a special case. The error, just as the name described, means the print client does not have the required drivers and needs to be downloaded from the print server. Upon receiving the server, the add-printer-connection-to will immediately start to download and install the needed driver. It calls the function downloadAndInstallLegacyDriver. This function would load a new module, ntprint, and call the function psetupDownloadAndInstallLegacyDriver. But before the real download, you would call displayWarning for download driver to show a warning dialog for user to choose to install the driver or just cancel. You would call downloadAndInstallLegacyDriver function to finish the installation of remote printer only if the user confirms to install. This is the most common scenario of adding a new network printer. But as we have described, Windows printing has two different mechanisms, point-and-print and package-point-and-print, to download the correct printer drivers. So which mechanism does it use in a common case? In order to know the implementation of downloadAndInstallLegacyDriver, we do not want to do too much reverse engineering works. Instead, we prefer to observe the behavior of our operations. And we really recommend you doing this, especially when you are analyzing a complex application or want to find some logical bugs. Here we use process monitor to capture the driver download operations. This screenshot shows the file operation of print spooler on print client after we confirm to install the driver. We notice the print spooler on print client reads information through RPC from the print server and then write the print driver files to a temporary directory directly. This is the whole procedure of downloading printer driver from print server. After the printer driver is downloaded, it should be installed so the print client could use it to do client-side rendering. And we could notice from file operations that the drivers in temporary directory are moved to the system driver directory. This is the procedure of printer driver installation. Now the printer driver is successfully downloaded and installed. So which mechanism is this? We did not see any package here. It seems all printer drivers are directly downloaded from the print server. So it seems to be point-and-print. So why does the current print client only use the point-and-print mechanism? How does the print client and server negotiate which one to use? How to enable the package point-and-print? To answer this question, we dig into the RPC addPrinterConnection2 function in print spooler. The procedure of addPrinterConnection2 is very complex. We found the adding connection operation of the remote printer is handled by the Win32 SPL module. You will try to call the createLocalPrinter function to create a local printer. Then it uses acquireV3Driver and addPrinter function to get printer driver. Before getting the driver, it needs to determine install type. In this function, it tries to call checkPackagePointAndPrint. This function name seems to indicate it would determine to use the point-and-print or package point-and-print mechanism. But how does the function checkPackagePointAndPrint determines the mechanisms? Let's see the implementation. Here is its pseudocode. If there is bit 1 at 0x88 offset of an object, the print spooler would download and import driver packages. But what is this object? Where it comes from? And can we control it from the print server? We found the print client gets that object by calling function remoteGetPrinterDriver. The function, in fact, is just an RPC stub. The real implementation is RPC asyncGetPrinterDriver in print spooler of print server. The object is a returned data from a remote print server. So it is fully controllable by print server. The structure of the returned object is a DriverInfo8. And we could know that at the 0x88 offset is the DWPrinterDriver attributes. The object has many other interesting items such as driver path, config path, inf path, and so on. The definitions of printer driver attributes could be found in the Windows 10 software development kit. What we care about is just a value 1, which is PrinterDriverPackageAware. It means the printer driver is part of a driver package. Now things get clearer. If the printer driver attributes of the print server is PrinterDriverPackageAware, the print client would use the package point-and-print mechanisms to download and import package drivers. The mechanism used to download print drivers are solely determined by the remote print server. That seems very interesting and may be dangerous. What is a driver package in package point-and-print? A driver package is just a collection of files that are needed to successfully load the printer driver. It may contain a device information file, a catalog file, and odd files copied by information file. Now we know how to force the print client to use the package point-and-print mechanism, and we know the print client would try to download the package driver from the print server. But where does the print client get the package from? Can we control the package content? After some research, we found that all printer driver packages are stored in a PCC directory, and the filename of package driver is very interesting. It is the final directory name of the inf path in print driver info. If the print client knows the print server wants to use package point-and-print mechanism, it would download and import driver packages. But before import, the print client would first download the driver package and then extract it. The print client would use tdriver install internal copy file to copy driver packages from print server. Then you would use ncabbing library legacy cab unpack to extract the package. And the most important thing is the download and extract operations are all happened before showing the warning dialog. It seems this could be abused. We know that the driver package is a cabinet file. A cabinet file is an archive file format for Microsoft Windows. It is ancient and has existed for many years. It has the suffix .cab and acts like a container for other files. In order to extract the cabinet file, Microsoft provides a lot of file decompression interface APIs. Here are some examples. First, fdi create is used to create a fdi context for cabinet file operations. Next, fdi copy is the most important one. It is used to extract files from cabinet. fdi destroy deletes and opens the fdi context after the cabinet operations. fdi copy is the primary API for extracting files from the cabinet. This API is designed to be very flexible. It provides application-defined callback notification functions to update the application on the status of a decompressor. So the behavior of cabinet extraction is highly dependent on the implementation of users of the API. As we have described, the print client would use nCabinLibraryLegacyCabOnPack to extract files from the downloaded cab files. This function uses fdi copy to extract files with a notification callback. nCabinLibraryFdicabNotify to control the extraction operation of files. In the callback notification, it would receive the fdint copy file notification to handle copy file operation. This is part of a pseudocode of copy file processing. The copy file operation would finally call the nCabinLibrary process copy file. The process copy file first call nCabinLibrary createFullPath to create a directory if the specified directory does not exist. When creating the full path, it would check if the path has dot dot backslash. If it has, the function would just return with an error. But it forgot to check the dot dot slash, which also works on Windows OS and could be used to do path traversal. After creating the full path, it would then call the open function to open the file. If the file does not exist, it will create the file via the ocreate flag. Now it is obvious that there is a path traversal issue when print client extract the downloaded cabinet file. And the cabinet file is provided by a print server. Let's try to make a malformed cab file. Windows has provided the command make cab for cabinet file creation. However, make cab could not create cabinet file with the file name contain slashes. So we decided to create a simple cab and then change the name of the file with another program. Here we used make cab to create the test.cab. After we have a cabinet file, we try to change the file name inside the cabinet file. We used programs such as 7-zip to try to edit the name of the file. But the file name is not editable. That's very sad. Are we doomed? No, that could not stop us. We could still modify the file names directly in binary. Let's edit the file name with an hex editor. Here we use a small trick using ones and twos in file names as placeholders. We just modify all ones in file name to dot and twos to slash. After the modification, we use lzip program to open the cabinet file again. It can be successfully opened and the file name contains dot dot slash that could be used to do path traversal. We now have a malformed cabinet. Replace the cabinet file in the print server with this malformed cab to make it an evil printer. Let's make an evil printer. First, we should have a fully controllable print server. To prepare a print server, we prefer to use a virtual PDF printer to simulate the printer instead of getting a real physical printer. There are many virtual printer products available. Here we randomly choose one, the Qt PDF Writer. Or other virtual printers should also work. To prepare a workable print server is very easy. Just install the printer and then share this printer so that any user on your network can print to it. Here we just share the printer with the share name test. Now we have got a fully controllable print server. We will wait for a print client to connect to it. Because the print server is fully controllable, we have many ways to change the return data before it sends to the print client. The easiest way is to change the real printer driver info of the shared printer. We found the print server stores all the definitions of printer driver information in the registry. As shown in this screenshot, we could know the default print driver attribute of Qt PDF Writer is 0. Now, let's make a package point-and-print print server. First, we need to make sure the printer driver attributes of the shared printer is 1, so that print client would use the package point-and-print mechanism. Next, we need to configure the driver package that would be downloaded. As we have described, the package name comes from the directory name of the nth pass. Here, we configure it to ccolumn backslash test backslash test.int, and make sure the int file exists. The package name that will be downloaded should be test.cap. We just needed to place a prepared test.cap to the PCC directory. Now we finally have a workable Evo printer. This screenshot shows how to configure the prepared Qt PDF Writer printer to use the package point-and-print mechanism and convert it to Evo printer. What would happen if a print client tries to connect to this Evo printer? We could see the print spooler in print client download the test.cap file from the print server and then extract the cabinet file. Because of the path traversal bug, ccolumn backslash windows backslash system32 backslash diagnostic services backslash userenvironment.do is created. Because the print spooler service is running with system privilege, the Evo printer could write arbitrary file to print client with system privilege. It is a logical bug and works very stably. Okay, now we have covered the remote code execution part of the Evo printer. This attack only needs the print client to make a connection attempt to the Evo printer. But can this vulnerability do more? Three years ago, James Horshow did an excellent job on COM internals and Microsoft Edge Sandbox. During his talk, he demoed a scan of COM servers reachable from inside the Microsoft Edge Sandbox. He must be hinting something by highlighting that specific COM server during his talk. Sandbox is an essential security mechanism in modern security architectures. Sandbox escapes are important security research target. So how does Evo printer affect sandboxes? Microsoft Edge vendor process is the most restricted app container sandbox on Windows. Microsoft has kept reduced attack surfaces of it for many years. The interfaces that could be accessed from inside the sandbox is fewer and fewer. But the printing service has always been one of them. Until now, the process token of render process still has the capability LPAC printing. It means the render process could access the printing service. Using oleview.net, we found the COM service CPrintTicketWW services can be launched and accessed with capability LPAC printing. So it could be called by the Microsoft Edge render process. The CPrintTicket server provides an interface bind. This is used to bind to a specific printer. If we bind to an Evo printer, it will also trigger the vulnerability so we could use it to escape from the sandbox. App container processes are not allowed to create files. But we could use this vulnerability to create arbitrary files. The app container process can launch and access CPrintTicket.com server. The server is implemented in print ticket services module and launched by deal host with medium integrity level. The bind interface would call CPrintTicket service base bind. In this function, it would open a remote print server and get the print driver from print server. This would also trigger the bug if it's connecting to the Evo printer. So we could escape the Edge render sandbox and execute arbitrary code with system privilege. Next, let's do a real demo. In this demo, we have two computers, the victim and the attacker. The victim is a print client and the attacker is the Evo printer. First, let's see the Evo printer. We have modified the driver info of a shared printer. We modify the printer driver attributes to one and set the info path item to c column backslash test backslash test.inf. And we have placed the test.cap at the PCC directory. Next, let's go to the victim's computer. You can see there's no user environment.deal in diagnostic services directory. Now, we try to connect to the Evo printer from Microsoft Edge render sandbox. Here, we simulate a code execution inside the sandbox by injecting our exploit deal into the render process. We could see the warning dialog and we could find a user environment.deal present in the diagnostic services directory. In fact, the Evo printer could write arbitrary file with system privilege. The patch of the vulnerability is extremely simple. We have known the root cause of the vulnerability is because the Win32 SPL uncapping library process copy file does not properly handle the dot dot slash. So the patch just check both slashes before process copy file to prevent past traversal issues. Finally, we want to talk about the possible attack scenarios of this vulnerability. For lateral movement, the attacker can modify a printer trusted by the victim to Evo printer. For remote code execution, the attacker could convince the victim to connect to a Evo printer. For privileged escalation, the attacker could make a connection attempt to an Evo printer either as an unprivileged user or from inside the sandbox. For all the above scenarios, the attacker immediately gets code execution as system with complete control of the victim's computer. This vulnerability has been reported and fixed. The CVE ID is CVE-2020-1300. This is Microsoft's description about this vulnerability. Although it's technically correct about the final exploitation vector, it's kind of misleading. First, this is not a cabinet API vulnerability. The cabinet API is sophisticated and flexible. But it is too flexible, many parts of the decompression were handled by user-supplied callbacks. If they don't handle edge cases properly, it could lead to security issues. In this case, it is the Windows printing which did not implement the decompression callbacks correctly. Second, this vulnerability does not need a user confirmation to install the printer driver. Just as we described and demoed, this entire attack finishes before the warning dialogue. In fact, the attacker could also configure the print server to prevent the print client from showing the warning dialogue to make the attack completely unnoticeable. However, there's no need to be panicked about cabinet files. The cabinet API is old, but its design is okay. If the users of cabinet API handled callbacks with care, there's no security issues to be worried about. For example, don't be afraid to open the malformed cabinet we created in Explorer. The cabview module in Explorer.exe correctly handles special characters in cabinet API callbacks, so it is safe to open a cabinet file in Explorer and copy files. In this presentation, we talked about the implementation of Windows printing. It is complex and privileged and can be one of the best targets for attackers. And we walked you through a bug we found in Windows printing. It could be exploited both locally and remotely. It could be used to execute arbitrary code with system privilege and escape sandboxes. For developers, don't be panicked about the cabinet files, but you should be careful when writing the cabinet API callbacks. Logical bugs like this are always found to be harmful. We think you will love it just as we do. We would like to thank James Forshaw for his excellent tools and hints. Big draw AI for their previous works and shares. And thank you for listening.