Tracking a stolen code-signing certificate with osquery

Recently, 2.27 million computers running Windows were infected with malware signed with a stolen certificate from the creators of a popular app called CCleaner, and inserted into its software update mechanism. Fortunately, signed malware is now simple to detect with osquery thanks to a pull request submitted by our colleague Alessandro Gario that adds Windows executable code signature verification (also known as Authenticode). This post explains the importance of code signatures in incident response, and demonstrates a use case for this new osquery feature by using it to detect the recent CCleaner malware.

If you are unfamiliar with osquery, take a moment to read our previous blog post in which we explain why we are osquery evangelists, and how we extended it to run on the Windows platform. Part of osquery’s appeal is its flexibility and open-source model – if there’s another feature you need built, let us know!

Code-signed malware

Code signing was intended to be an effective deterrent against maliciously modified executables, and to allow a user (or platform owner) to choose whether to run executables from untrusted sources. Unfortunately, on general-purpose computing platforms like Windows, third-party software vendors are individually responsible for protecting their code-signing certificates. Malicious actors realized that they only needed to steal one of these certificates in order to sign malware and make it appear to be from a legitimate software vendor. This realization (and the high-profile Stuxnet incident) began a trend of malware signed with stolen code-signing certificates. It has become a routine feature of criminal and nation-state malware attacks in the past few years, and most recently happened again with an infected software update to the popular app CCleaner.

So, defenders already know that a trust model based on an assumption that all third-party software vendors can protect their code-signing certificates is untenable, and that on platforms like Windows, code-signing is only a weak trust marker or application whitelisting mechanism. But, there’s another use for code signatures: incident response. Once a particular signing certificate is known to be stolen, it also works as a telltale indicator of compromise. As the defender you can make lemonade out of these lemons: search for other systems on your network with executables that were also signed with this stolen certificate. The malware might have successfully evaded antivirus-type protections, but any code signed with a known-stolen certificate is an easy red flag: signing can be checked with a 0% chance of any false-positives. osquery offers an ideal method for performing such a search.

Verifying Authenticode signatures with osquery

New sensors are added to osquery with the addition of “tables,” maintaining the abstraction of all system information as SQL tables.

To add a table to osquery, you first define its spec, or schema. An osquery table spec is just a short description of the table’s columns, their data types, and short descriptions, as well as a reference to the implementation. In Alessandro’s pull request, he added an ‘authenticode’ virtual table for Windows, containing the following columns: path, original_program_name (from the publisher), serial_number, issuer_name, subject_name, and result.

Alessandro implemented the code to read code signature and certificate information from the system in osquery/tables/system/windows/authenticode.cpp. The verification of signatures is done using a call to the system API, WinVerifyTrust().

Here’s a simplified example of using osquery to check a Windows executable’s code signature:

osquery> SELECT serial_number, issuer_name, subject_name,
    ...> result FROM authenticode
    ...> WHERE path = 'C:\Windows\explorer.exe';


Most of the columns are self-explanatory. The result values aren’t. “Result” could mean:

State Explanation
missing Missing signature.
invalid Invalid signature, caused by missing or broken files.
untrusted Signature that could not be validated.
distrusted Valid signature, explicitly distrusted by the user.
valid Valid signature, but which is not explicitly trusted by the user.
trusted Valid signature, trusted by the user.

Getting focused results with SQL in osquery

To make the most out of this new functionality, perform JOIN queries with other system tables within osquery. We will demonstrate how using SQL queries enhances system monitoring by reducing the amount of noise when listing processes:

osquery> SELECT, process.path, authenticode.result
    ...> FROM processes as process
    ...> LEFT JOIN authenticode
    ...> ON process.path = authenticode.path
    ...> WHERE result = 'missing';

| pid  | path                                                      | result  |
| 3752 | c:\windows\system32\sihost.exe                            | missing |
| 3872 | C:\Windows\system32\notepad.exe                           | missing |
| 4860 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe | missing |
| 5200 | C:\Windows\system32\conhost.exe                           | missing |
| 6040 | C:\Windows\osqueryi.exe                                   | missing |

Tracking a stolen signing certificate

Assume that you have just learned of a malware campaign. The malware authors code-signed their executables using a code-signing certificate that they stole from a legitimate software vendor. The vendor has responded to the incident by acquiring a new code-signing certificate and redistributing their application signed with the new certificate. In this example, we will use CCleaner. How can you search a machine for any software signed with this stolen certificate, but filter out software signed with the vendor’s new certificate?

Example 1: Find executables signed with the stolen certificate

osquery> SELECT files.path, authenticode.subject_name,
    ...>        authenticode.serial_number,
    ...>        authenticode.result AS status
    ...> FROM (
    ...>   SELECT * FROM file
    ...>   WHERE directory = "C:\Program Files\CCleaner"
    ...> ) AS files
    ...> LEFT JOIN authenticode
    ...> ON authenticode.path = files.path
    ...> WHERE authenticode.serial_number == "4b48b27c8224fe37b17a6a2ed7a81c9f";


Example 2: Find executables signed by the affected vendor, but not with their new certificate

osquery> SELECT files.path, authenticode.subject_name,
    ...>        authenticode.serial_number,
    ...>        authenticode.result AS status
    ...> FROM (
    ...>   SELECT * FROM file
    ...>   WHERE directory = "C:\Program Files\CCleaner"
    ...> ) AS files
    ...> LEFT JOIN authenticode
    ...> ON authenticode.path = files.path
    ...> WHERE authenticode.subject_name LIKE "%Piriform%"
    ...> AND authenticode.serial_number != "52b6a81474e8048920f1909e454d7fc0";


Example 3: Code signatures and file hashing

Perhaps you would also like to keep a log of hashes, to keep track of what has been installed:

SELECT files.path AS path,
    ...>        authenticode.subject_name AS subject_name,
    ...>        authenticode.serial_number AS serial_number,
    ...>        authenticode.result AS status,
    ...>        hashes.sha256 AS sha256
    ...> FROM (
    ...>   SELECT * FROM file
    ...>   WHERE directory = "C:\Program Files\CCleaner"
    ...> ) AS files
    ...> LEFT JOIN authenticode
    ...> ON authenticode.path = files.path
    ...> LEFT JOIN hash AS hashes
    ...> ON hashes.path = files.path
    ...> WHERE authenticode.subject_name LIKE "%Piriform%"
    ...> AND authenticode.serial_number != "52b6a81474e8048920f1909e454d7fc0"


For the purposes of our examples here, notice that we have restricted the searches to “C:\Program Files\CCleaner”. You could tailor the scope of your search as desired.

The queries we’ve shown have been run in osquery’s interactive shell mode, which is more appropriate for incident response. You could run any of these queries on a schedule – using osquery for detection rather than response. For this, you would install osqueryd (the osquery daemon) on the hosts you wish to monitor, and configure logging infrastructure to collect the output of these queries (feeding the osquery output to, for example, LogStash / ElasticSearch for later analysis).

Future osquery Work

In this post we demonstrated the flexibility of osquery as a system information retrieval tool: using familiar SQL syntax, you can quickly craft custom queries that return only the information relevant to your current objective. The ability to check Authenticode signatures is just one use of osquery as a response tool to search for potential indicators of compromise. Many IT and security teams are using osquery for just-in-time incidence response including initial malware detection and identifying propagation.

Trail of Bits was early to recognize osquery’s potential. For over a year we have been adding various features like this one in response to requests from our clients. If you are already using osquery or considering using it and there’s a feature you need built, let us know! We’re ready to help you tailor osquery to your needs.

8 thoughts on “Tracking a stolen code-signing certificate with osquery

    • Thanks for the note!

      I’m familiar with that research but I don’t think it plays a role here. Yes, if you’re already an admin and you register a new trust provider then you can trick WinVerifyTrust to return incorrect results. osquery is not meant to withstand rootkits that hook or modify functionality out from underneath it (at least not today).

      That said, I think there is probably room to implement a few trust provider consistency checks in osquery. However, even those can’t be trusted in the presence of an active, highly permissioned attacker that is already on the box. This problem is not unique to WinVerifyTrust.

    • Hi DK. The verification of signed code on-host is dependent on system integrity, that is true. If using osquery, one could also perform the detections/mitigations listed in Matt Graeber’s whitepaper starting on page 38.

      And if Matt is reading this, hi Matt!

    • As this blog post went live, we noticed a rising interest in the use of invalid signatures, because it’s apparently common for security products to check that a signature is present without checking that it is valid.

      We will leave it as an exercise for the reader, but you could use osquery and SQL syntax to quickly get a list of all executables with signatures WHERE authenticode.subject_name LIKE “%Microsoft%” AND authenticode.result == “invalid”.

  1. That’s really cool! Thanks for sharing!
    Any plan to extend to powershell script signatures or MsOffice files with macro signed or not? :-)

    • Sure, we’ll look into it! It appears to be done by modifying the call to WinVerifyTrust and specifying other providers (SIPs) for each type of signed file.

  2. Pingback: What are the current pain points of osquery? | Trail of Bits Blog

  3. Pingback: 2017 in review | Trail of Bits Blog

Leave a Reply