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
- Adding per certificate file hash validation on PE file. — committed to soumy/yara by soumy 4 years ago
Unless I’m not understanding this properly, there is no bug here. The timestamp on the file is
1544396227
, which is clearly within thenot_before
andnot_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.
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.
Yes, the root certificate was just an example, you can obviously have different points of trust if you want.
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 likesigcheck.exe
does (in factsigcheck.exe
usesWinVerifyTrust
under the hood, that’s why portingsigcheck.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.