Exploring Acrobat’s DDE attack surface
Introduction
Adobe Acrobat have been our favorite target to poke at for bugs lately, knowing that it's one of the most popular and most versatile PDF readers available. In our previous research, we've been hammering Adobe Acrobat's JavaScript APIs by writing dharma grammars and testing them against Acrobat. As we continue investigating those APIs, we decided as a change of scenery to look into other features Adobe Acrobat has provided. Even though it has a rich attack surface yet we had to find which parts would be a good place to start looking for bugs.
While looking at the broker functions, we noticed that there’s a function that’s accessible through the renderer that triggers DDE calls. That by itself was a reason for us to start looking into the DDE component of Acrobat.
In this blog we'll dive into some of Adobe Acrobat attack surface starting with DDE within adobe using Adobe IAC.
DDE in Acrobat
To understand how DDE works let's first introduce the concept of inter-process communication (IPC).
So, what is IPC? It's a mechanism for processes to communicate with each other provided by the operating system. It could be that one process informs another about an event that has occurred, or it could be managing shared data between processes. In order for these processes to understand each other they have to agree on certain communication approach/protocol. There are several IPC mechanisms supported by windows such as: mailslots, pipes, DDE ... etc.
In Adobe Acrobat DDE is supported through Acrobat IAC which we will discuss later in this blog.
What is DDE?
In short DDE stands for Dynamic Data exchange which is a message-based protocol that is used for sending messages and transferring data between one process to another using shared memory.
In each inter-process communication with DDE, a client and a server engage in a conversation.
A DDE conversation is established using uniquely defined strings as follows:
Service name: a unique string defined by the application that implements the DDE server which will be used by both DDE Client and DDE server to initialize the communication.
Topic name: is a string that identifies a logical data context.
Item name: is a string that identifies a unit of data a server can pass to a client during a transaction.
DDE shares these strings by using it's Global Atom Table. For more details about Atoms. Also, DDE protocol defines how applications should use the wPram and lParam parameters to pass larger data pieces through shared memory handles and global atoms.
When is DDE used?
It is most appropriate for data exchanges that do not require ongoing user interaction. An application using DDE provides a way for the user to exchange data between the two applications. However, once the transfer is established, the applications continue to exchange data without further user intervention as in socket communication.
The ability to use DDE in an application running on windows can be added through DDMEL.
Introducing DDEML
The Dynamic Data Exchange Management Library DDEML by windows makes it easier to add DDE support to an application by providing an interface to simplify managing DDE conversations. Meaning that instead of sending, posting, and processing DDE messages directly, an application can use the DDEML functions to manage DDE conversations.
So, usually the following steps will happen when a DDE client wants to start conversation with the Server:
Initialization
Before calling a DDE functionwe need to register our application with DDEML and specify the transaction filter flags for the callback function, the following functions used for the initialization part:
DdeInitializeW()
DdeInitializeA()
Note: "A" used to indicate "ANSI" A Unicode version with the letter "W" used to indicate "wide"
2. Establishing a Connection
In order to connect our client to a DDE Server we must use the Service and Topic names associated with the application. The following function will return a handle to our connection which will be used later for data transactions and connection termination:
DdeConnect()
3. Data Transaction
In order to send data from DDE client to DDE server we need to call the following function:
DdeClientTransaction()
4. Connection Termination
DDEML provides a function for terminating any DDE conversations and freeing any DDEML resources related:
DdeUninitialize()
Acrobat IAC
As we discussed before about Acrobat, Inter Application Communication (IAC) allows an external application to control and manipulate a PDF file inside Adobe Acrobat using several methods such as OLE and DDE.
For example, let's say you want to merge two PDF documents into one and save that document with a different name, what do we need to achieve that ?
Obviously we need adobe acrobat DC pro .
The service, topic names for acrobat.
Topic name is "Control"
Service Name:
“AcroViewA21" here "A" means Acrobat and "21" refer to the version.
"AcroViewR21" here "R" for Reader.
So, to retrieve the service name for your installation based on the product and the version you can check the registry key:
What is the item we are going to use ?
When we attempt to send a DDE command to the server implemented in acrobat the item will be NULL.
Acrobat Adobe Reader DC supports several DDE messages, but some of these messages require Adobe Acrobat Adobe DC Pro version in order to work.
The format of the message should be between brackets and it's case sensitive. e.g:
Displaying document: such as "[FileOpen()]" and "[DocOpen()]".
Saving and printing documents: such as "[DocSave()]" and "[DocPrint()]".
Searching document: such as "[DocFind()]".
Manipulating document such as: "[DocInsertPage()]" and "[DocDeletePages()]".
Note: that in order to use Acrobat Adobe DDE messages that start with Doc, the file must be opened using [DocOpen()] message.
We started by defining Service and topic names for Adobe Acrobat and the DDE messages we want to send. In our case, we want to merge two Documents into one so we need three DDE methods "[DocOpen()]" , "[DocInsertPages()]" and "[DocSaveAs()]":
Next, as we discussed before, we first need to register our application to DDEML using DdeInitialize():
After the initialization step we have to connect to the DDE server using Service and Topic that we defined earlier:
Now we need to send our message using DdeClientTransaction() and as we can see we used XTYPE_EXECUTE with NULL Item, and our command is stored in HDDEDATA handle by calling DdeCreateDataHandle(). After executing this part of code, Adobe Acrobat will open the PDF document and append the other document to it, and save it as new file then exit Adobe Acrobat:
The last part is closing the connection and cleaning the opened handles:
So we decided to take a look at adobe plugins to see who else is implementing DDE Server by searching for DdeInitilaize() call:
Great 😈 it seems we got five plugins that implement a DDE service, before we analyzing these plugins we went to search for more info about them and we found that the search and catalog plug-ins are documented by Adobe... good what next!
Search Plug-in
We started to read about the search plug-in and we summarized it in the following:
Acrobat has a feature which allows the user to search for a text inside PDF document. But we already mentioned a DDE method called DocFind() right? well, DocFind() will search the PDF document page by page while the search plug-in will perform an indexed search that allows to search a word in the form of a query, so in other word we can search a cataloged PDF 🙂.
So basically the search plug-in allows the client to send search queries and manipulate indexes.
When implementing a client that communicates with the search plug-in the service name and topic's name will be "Acrobat Search" instead of "Acroview".
Remember when we send a DDE request to Adobe Acrobat, the item was NULL, but in search plugin there are two types of items the client can use to submit a query data and one item for manipulating the index:
SimpleQuery item: Allows the user to send a query that support Boolean operation e.g if we want to search for any occurrence of word "bye" or "hello" we can send "bye OR hello".
Query item: this allow different search query and we can specify the parser handling the query.
While the item name used to manipulate indexes is "Index” , the DDE transaction type will be "XTYPE_POKE" which is a single poke transaction.
So, we started by manipulating indexes. When we attempt to do an operation on indexes the data must be in the following form:
Where eAction represents the action to be made on the index:
Adding index
Deleting index
Enabling or Disabling index on the shelf.
The cbData[0] will store the index file path we want to do an action on - example: “C:\\XD\\test.pdx” and PDX file is an index file that is create by one or multiple IDX files.
CVE-2021-39860
So, we started analyzing the function responsible for handling the structure data sent by the client, and turned out there are no check on what data sent.
As we can see after calling DdeAccessData(), the EAX register will storea pointer to our data and we can see it access whatever data at offset 4 . So if we want to trigger an access violation at "movsx eax,word ptr [ecx+4]" simply send a two byte string which result in Out-Of-Bound Read 🙂 as demonstrated in the following crash:
Catalog Plug-in
Acrobat DC has a feature that allows the user to create a full-text index file for one or multiple PDF documents that will be searchable using the search command. The file extension is PDX. It will store the text of all specified PDF documents.
Catalog Plug-in support several DDE methods such as:
[FileOpen(full path)] : Used to open an index file and display the edit index dialog box, the file name must end with PDX extension.
[FilePurge(full path)]: Used to purge index definition file. The file name also must end with PDX extension.
The Topic name for Catalog is "Control" and the service name according to adobe documentation is "Acrobat", however if we check the registry key belonging to adobe catalog we can see that is "Acrocat" (meoww) instead of "Acrobat".
Using IDApro we can see the DDE methods that catalog plugin support along with Service and Topic names:
CVE-2021-39861
Since there are several DDE methods that we can send to the catalog plugin and these DDE methods accept one argument (except for "App related methods") which is a path to a file, we started analyzing the function responsible for handling this argument and turned out 🙂:
The function will check the start of the string (supplied argument) for \xFE\xFF, if it's there then call Bug() function which will read the string as Unicode string, otherwise it will call sub_22007210() which will read the string as ANSI string.
So, if we can send "\xFE\xFF" or byte order mask at the start of ASCII string then probably we will end up with Out-of-bound Read since it will look for Unicode NULL terminator which is "\x00\x00" instead of ASCII NULL terminator.
We can see here the function handling Unicode string :
And 😎:
Here we can see a snippet of the POC:
That’s it for today. Stay tuned for more new attack surfaces blogs!
Happy Hunting!