 Hello everyone, welcome to DevCon Safe Mode, what a special year, glad to see you in this way. Word Talk is evil printer, how to hack Windows machines with printing protocol. First, a self-introduction. Trichon Ho is a senior security researcher and he's a member of EcoSec Team at Tencent Security Shamp Lab. His research focuses on Windows and macOS platform security. He has found and reported many vulnerabilities to Microsoft and Apple. Tronder Ding is a co-author of this presentation. He leads the EcoSec Team at Tencent Security Shamp 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 2 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 exploited and what can you 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 works? The system where user create the print job on is the 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, you will go to the server and say, Hey, server, print this document. The server gets the print job, then you 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 does 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 print server. For server-side rendering, print client sends the all-modified data directly to the print server. Then print server translates the application data to printer-specific data. The rendering job 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 the correct print driver and dynamically loading it. It is an auto-stout service on Windows OS. It manages the entire printing process and explores the printing APIs for other printing component 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 AdPrinter command light 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 AdPrinter Connection and AdPrinter Connection 2. And you could also use the GUI provided by printUI slash 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 AdPrinter Connection 2. 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 Victoria AI found a remote code execution vulnerability in Win32's print spooler service. 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 on trusted 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 AdPrinter Connection 2 first. Any applications on a print client that call AdPrinter Connection 2 API would send an RPC call to print spooler service on printer client. The remote function code is RPC AdPrinter Connection 2. 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 AdPrinter Connection 2. AdPrinter Connection 2 would get the return value from print spooler on print client directly. Error code 0xbbni, 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 this error, the AdPrinter Connection 2 will immediately start to download and install the needed driver. It calls the function download and install legacy driver. This function would load a new module, ntprint, and call the function psetupdownload and install legacy driver. But before the real download, you would call display warning for download driver to show a warning dialog for user to choose to install the driver or just cancel. You would call download and install legacy driver 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 a download and install legacy driver, we do not want to do too much reverse engineering works. Instead, we prefer to observe the behavior of all 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 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 add-printer connection 2 function in print spooler. The procedure of add-printer connection 2 is very complex. We found the add-in connection operation of the remote printer is handled by the Win32 SPL module. You will try to call the create local printer function to create a local printer. Then it uses acquire v3 driver and add printer function to get printer driver. Before getting the driver, it needs to determine install type. In this function, it tries to call check package point and print. This function name seems to indicate you would determine to use the point and print or package point and print mechanism. But how does the function check package point and print 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 remote get printer driver. The function in fact is just an RPC stop. The real implementation is RPC async get printer driver in print spooler of print server. The object is a return data from a remote print server. So it is fully controllable by print server. The structure of the returned object is a driver info 8. And we could know that at the 0x88 offset is the DW printer driver attributes. The object has many other interesting items such as driver pass, config pass, inf pass 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 the value 1, which is printer driver package aware. It means the printer driver is part of a driver package. Now since get clear, if the printer driver attributes of the print server is printer driver package aware, 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 maybe 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 alt 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 a package driver from the print server. But where does 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 file name of the package driver is very interesting. It is the final directory name of the inf pass in print driver info. If the print client knows the print server wants to use package point and print mechanism, you would download and import driver packages. But before the 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 opac 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 dog 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 encabbing library legacy cab unpack to extract files from the downloaded cab files. This function used fdi copy to extract files with a notification callback. Encaping library fdi cab notify to control the extraction operational files. In the callback notification, it would receive the fdint copy file notification to handle copy file operation. This is part of pseudocode of copy file processing. The copy file operation would finally call the encabbing library process copy file. The process copy file first called encabbing library create full path to create a directory if the specified directory does not exist. When creating the full path, it would check if the path has ..backslash. If it has, the function would just return with an error. But it forgot to check the ..slash which also works on Windows OS. It 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 makecab for cabinet file creation. However, makecab could not create a cabinet file with the file name contains slashes. So we decided to create a simple cab and then change the name of the file with another program. Here we used makecab 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 7zip 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 cute 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. You 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 store all the definitions of printer driver information in the registry. As shown in this screenshot, we could know the default print driver attribute of a cute 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 int pass. Here we configure it to C column 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 evil printer. This screenshot shows how to configure the prepared cute PDF writer printer to use the package point and print mechanism and convert it to evil printer. What would happen if a print client tries to connect to this evil 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 pass traversal bug, C column backslash windows backslash system32 backslash diagnostic services backslash user environment.do is created. Because the print spooler service is running with system privilege, the evil 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 evil printer. This attack only needs the print client to make a connection attempt to the evil printer. But can this vulnerability do more? Three years ago, James Osho did an excellent talk on COM internals and the 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 our important security research target. So how does evil printer affect sandboxes? Microsoft Edge rendering process is the most restricted app container sandbox on Windows. Microsoft has kept reduced attack services of it for many years. The interfaces that could be accessed from inside 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 server's c-print ticket ww services can be launched and accessed with capability LPAC printing. So it could be called by the Microsoft Edge rendering process. The c-print ticket server provides an interface bind. This is used to bind to a specific printer. If we bind to an evil printer, it will also trigger the availability 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 c-print ticket COM server. The server is implemented in print ticket services module and launched by deal host with median integrity level. The bind interface would call c-print ticket service base bind. In this function, it would open the remote print server and get the print driver from print server. This would also trigger the bug if it's connecting to the evil printer. So we could escape the Edge renderer 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 a evil printer. First, let's see the evil printer. We have modified the driver info of a shared printer. We modify the printer driver attributes to one and set the info pass item to c-column backslash test, backslash test.inf. And we have placed the test doc 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 evil printer from Microsoft Edge renderer sandbox. Here, we simulate a code execution inside the sandbox by injecting or exploiting into the renderer process. We could see the warning dialog and we could find the user environment.deal present in the diagnostic services directory. In fact, the evil 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 and Cabin library process copy file does not properly handle the .doc slash. So, the patch just check both slashes before process copy file to prevent past troubles or 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 evil printer. For remote code execution, the attacker could convince the victim to connect to the evil printer. For privileged escalation, the attacker could make a connection attempt to the evil 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 CVE20201300. 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 user confirmation to install the printer driver. Just as we described and demoed, this entire attack finishes before the warning dialog. In fact, the attacker could also configure the print server to prevent the print client from showing the warning dialog to make the attack completely unnoticeable. However, there's no need to be panic about cabinet files. The cabinet API is old, but its design is okay. If the users of a 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 that were created in Explorer. The cap view 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 panic about the cabinet files, but you should be careful when writing the cabinet API callbacks. Logical bugs like this are always found to hunt for. We think you will love it, just as we do. We would like to thank James for his excellent tools and hints. Big draw AI for their previous works and shares. And thank you for listening.