I am pleased to announce that the BrowserGather project now supports the extraction of Chrome cookie data. In the first part of the BrowserGather project, I used binary regular expressions in PowerShell to extract Chrome credentials in a novel, fileless manner. In this blog post, I will discuss how I was able to apply this technique to extract Chrome cookie data, and the issues that arose during development. Check out my GitHub for the updated code.
The cookie SQLite database in Chrome is significantly more complicated than the one used for storing credentials. There are several distinct fields present, including flags for the cookie (such as HTTPOnly), as well as properties like hostname, name, and path. In addition, it is not unusual for the cookie database to contain several hundred entries within it. While I was able to develop regular expressions that cover all scenarios I encountered during testing, the size of the database created an issue that could not be resolved.
First, let’s discuss the regular expressions used to extract the encrypted blob. The encrypted blob contains the actual value of the cookie, similar to how the password is stored in the credential database. It is encrypted using the Windows Data Protection API. The following regex was used to extract the blob from the database file:
The lookbehind portion was simple, and is consistent with all DPAPI-encrypted blobs. The lookaheads were considerably more complex, and encompass four different situations I found during testing. In order:
- Lookahead for the next cookie entry inside the database. This is very similar to the lookbehind used in the cookie properties extraction.
- Unique lookahead that occurred in a small minority of cases. This one is most at risk of causing a regex mismatch.
- A series of 20 null bytes in a row, indicating the end of the blob.
- An End of File (EOF) check.
An interesting note about scenario 3, this regex created a situation where a trailing null byte could be removed from the encrypted blob itself, leading to decryption failure. The function will automatically append a null byte to any decryption failures before proceeding, in order to correct for this situation.
Now let’s look at the regex used to extract the properties of each cookie. In its current form, only the Name, Hostname, and Path values are extracted. This should be sufficient for whatever cookie forging you are attempting, but please let me know if it is not. The regex is as follows:
The lookbehind used was quite tricky, and went through many updates before I found one that worked in all test cases. I had to restrict the characters returned by possible ASCII values, by using the Netscape cookie allowed character list as a baseline, along with other characters found during testing. Finally, the lookahead simply looks for the first terminating character encountered after the data is collected.
The returned properties string looks something like this:
The blue text is the hostname. It always ends with the Top Level Domain (TLD). The red text is the name of the cookie. The green text is the path, which always begins with the backslash character. While it would technically be possible to write a regex to separate these values, you would need to account for every TLD in existence, as well as future ones. I believe it to be easier to simply separate them by hand.
Now, let’s talk about the database size issue. Once the SQLite cookie database grows to a certain size (around 400 cookies in my testing), the encrypted blobs are stored non-contiguously. The entry remains at the same place, but the data gets moved around to a different location, usually within a different encrypted blob. This means that both the original entry, as well as the entry where the data gets moved to, gets corrupted. Luckily this only affects about 1% of cookies in large databases, with the other 99% of cookies being unaffected. When this error occurs, the encrypted blob will be replaced with the string “Unable to decrypt blob”, allowing for easy recognition. Hopefully the cookie you need is not one of the affected ones, or you don’t run into this situation at all!
Finally, I have implemented a simple regex mismatch check to help with troubleshooting. If the number of cookie property matches does not match the number of encrypted blob matches, then a non-terminating error is presented with the following text:
"The number of cookies is different than the number of encrypted blobs! This is most likely due to a regex mismatch."
Depending on the severity of the mismatch, this could lead to cookie properties being matched with the wrong decrypted blob contents, or even entirely missing. If you see this error, please let me know so that I can try to adjust the regular expressions. I have also included this check in the Get-ChromeCreds function.
An explanation of how to use the data extracted from this tool is a bit out scope for this writeup. However, I do highly recommend Empire’s Get-BrowserData function. It is able to retrieve the history of Chrome, in order to determine which cookies are recent, and therefore useful.
That’s it for the BrowserGather project for now, I may extend the current functionality to different browsers once I’m no longer sick of looking at regular expressions. Thanks for reading!
- wald0, tifkin_, and harmj0y for requesting this project.
- mattifestation for the many reasons listed above.
- et0x for his existing work on PowerShell-based Chrome credential extraction, located here.
- xorrior for his previous work on Empire and providing guidance.
- Coalfire/Veris Group, for allowing me to develop this project.