yara: PE signature validation doesn't detect malware signed with sigthief

PE signature validation doesn’t detect malware signed with Sigthief.

YARA Rule:

import "pe"

rule fea_pe_improperly_signed {
  condition:
    uint16(0) == 0x5A4D and pe.number_of_signatures > 0

    and not for all i in (0..pe.number_of_signatures - 1):
    (
      pe.signatures[i].valid_on(pe.timestamp)
    )
}

How to reproduce:

1 - Sign Mimikatz using Sigthief 2 - Run the rule provided above against the binary

Expected Results:

YARA reports the binary to be improperly signed.

Current Results:

YARA reports the signature as valid. Probably because the timestamp is valid.

Risk:

It’s very likely that dozens of security professionals are using the valid_on() function to try and validate whether a PE is properly signed or not.

Recommendations:

We recommend that the documentation should specify any of the limitations for valid_on(). Also, if YARA could have a way to formally validate whether a binary is properly signed or not, that would be very handy.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Comments: 16 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Unless I’m not understanding this properly, there is no bug here. The timestamp on the file is 1544396227, which is clearly within the not_before and not_after range, so the loop condition evaluates to true, but then you are negating the whole thing and your rule ends up not matching.

Is your issue that the signature doesn’t match the binary? If so, that is entirely out of the scope of YARA signature parsing. YARA is currently extracting signature data and exposing it so you can write more precise and expressive rules. At no point has YARA claimed to validate any signatures, which might be where your confusion is coming from. The valid_on() function is only checking if the argument provided is within the range specified on the signature (https://yara.readthedocs.io/en/v3.8.1/modules/pe.html#c.signatures.valid_on) and nothing else.

You’re not the first to bring up the point of validation of signatures though. It’s something that could be added but I’ve not done it yet because if YARA tells you the signature is valid I’m concerned people will take it to mean “this is trustworthy”, which is clearly not the case. The question of trustworthiness is up to the user and their OS, or whatever is providing the trusted certificate store and it also changes over time as certificates are added or removed from the trusted certificate store.

There is an argument to be made that we could maybe have a pe.signatures[i].valid() which just verifies the signature matches what YARA calculates it should be, but that we should make it very clear that it doesn’t imply any validation up the chain of trust. I’m open to considering that, and possibly writing it, but I think @plusvic should chime in here.

Thanks, yes I meant #1623. Sorry. Great to hear.

I am not familiar with the PR you linked, maybe you meant #1623? That would offer this kind of validation.

What, how? A different hash invalidates the signature. And to create a new signature you need the private key matching your certificate. So catch 22, right?

The point was, that without validating the chain, you don’t have any trust in the owner of the keys, meaning that you can modify the file, compute a new signature and sign it yourself with your made-up certificates.

Of course, I agree that checking for signature validity - if everything matches - is a good thing, that is why the PR exists. But I think the original point was, that a valid signature doesn’t mean anything without a trust anchor. But it is of course better than being invalid in the first place.

So as you mentioned in the end - valid != trusted. That’s why I said you can create a valid signature because you can create corresponding key pairs and certificates such that the certificate is valid, but not trustworthy.

No, trust can equally be gained from knowing the public key of the signer. Or really whatever trait (or better yet: traits) you decide. There is no need to involve any CA, even. Just because WinVerifyTrust() does it one way and Microsoft only lets particular CAs into that club, doesn’t mean that this Microsoft-y concept of trust is the only permissible one.

Yes, the root certificate was just an example, you can obviously have different points of trust if you want.

Essentially your claim seems to be that you can transplant a signature from a Microsoft-signed file to another file, re-compute the hash … switch it out in the signature, and be done with it. Well, I claim that the signature will not validate after such tampering.

No, I claim I can switch out the Authenticode, and modify the hashes, the signatures, and the corresponding certificate chain. Then it would validate as the signatures are verified with the public key of the signer and its trust is determined with a chain of trust. But again, manipulating the chain would probably result in not a very trustworthy signature, but a valid one.

The actual verification of PE signatures, going up the signing chain up to a root certificate trusted by the operating system is a major undertake. In Windows you can simply use the WinVerifyTrust API, but in other operating systems you need to implement all that logic by yourself, far from easy. Also, as @wxsBSD mentioned the trustworthiness of a file is ultimately determined by the certificate store in your OS, so the same PE file could be “trusted” by one system and “not trusted” by some other, with the same YARA rule yielding different results. So, I wouldn’t implement a full verification process like sigcheck.exe does (in fact sigcheck.exe uses WinVerifyTrust under the hood, that’s why porting sigcheck.exe to some other OS is not straightfoward).

Having said that, we still can consider the pe.signatures[i].valid() function that @wxsBSD said, as in “lets verify that the hash in this signature matches the actual file”, with no further attempt to guarantee that the signer is actually who it claims to be. However, my impression is that without actually validating the certificate chain this is like a glorified checksum. Anyone wanting to pass a file as signed by Microsoft could generate a “valid” signature where the hashes are correct, and you won’t know that it’s a fake signature until you try to validate the chain.

Thanks! I’ll take a look in a couple of hours.