Open zeze-zeze opened 1 month ago
But this situation is that the signature can't be cryptographically verified, which I have no idea why it would happen.
We've had some bugs in the past around uthenticode's section/data hashing, producing false negatives where other implementations (like signcode
) don't. So that's possibly what's happening here, although in your case it looks like the calculated SHA-1 and embedded SHA-1 do indeed match (612E98A6DABA999F46EE8CE82176E10B77B60C87
).
If you have the ability, I suggest stepping through verify_signature
and seeing if any of the pre-signature checks fail:
If not, I can attempt to debug this in a few days.
Thanks for the recommendation. After stepping through verify_signature
, I found that checking the xku_flags
from the second X509_get_extended_key_usage
(https://github.com/trailofbits/uthenticode/blob/master/src/uthenticode.cpp#L244) makes the verification fails.
Possible xdu_flags
are as follows:
XKU_SSL_SERVER: 1, XKU_SSL_CLIENT: 2, XKU_SMIME: 4, XKU_CODE_SIGN: 8, XKU_OCSP_SIGN: 32, XKU_TIMESTAMP: 64, XKU_DVCS : 128, XKU_ANYEKU: 256
Currently, uthenticode returns false if it doesn't contain XKU_CODE_SIGN (8)
. However, for all of my false negative test cases, they get only XKU_TIMESTAMP (64)
from the second X509_get_extended_key_usage
, and that's why verify_signature
fails.
From my understanding, XKU_TIMESTAMP
is used to verify a file exists and is not modified at a specified timestamp. I'm not quite sure if there is any side effect if we change the line if (!(xku_flags & XKU_CODE_SIGN))
to if (!(xku_flags & (XKU_CODE_SIGN | XKU_TIMESTAMP)))
.
Thanks for debugging that!
Hmm -- I don't think we can unconditionally widen this to XKU_CODE_SIGN | XKU_TIMESTAMP
-- Authenticode follows the rule that all members of the signing chain must either have the codeSigning
EKU set or no EKU at all. We currently don't support the "no EKU" case though, since in practice no Authenticode-issuing CAs appear to do that.
That being said, I think I see the underlying problem here: it looks like nc.exe
's PKCS#7 blob contains not just normal Authenticode code-signing certs, but also timestamping certs that aren't part of the signing chain. So we should just be ignoring those, since they're not actually part of our verified chain.
To do that, I think uthenticode needs to do one of two things:
codeSigning
EKU (or no EKUs at all)(1) is preferred since it's strictly more correct from a path validation perspective, but (2) might be easier.
TL;DR: You've hit a bug in uthenticode, but unfortunately I think the patch in #103 is too broad -- we'll need to approach this in a way that doesn't involve uthenticode handling any EKUs besides codeSigning
. But thank you for triaging this!
What if we check whether there is at least one embedded intermediate cert whose xku_flags
is XKU_CODE_SIGN
? As the actual verification is at the last part in verify_signature
, we just make sure there is at least one XKU_CODE_SIGN
.
By the way, contig.exe and ctrl2cap.exe from Sysinternals are also false negative cases of the original method.
Edit: We can't do it, ctrl2cap.exe doesn't contain any XKU_CODE_SIGN
cert.
nc.exe from int0x33/nc.exe is signed and it is verified by signtool.
But it is not verified by uthenticode.
Some other software has the same problem, while some of them are normal. I understand there are caveats that uthenticode may behave differently to Wintrust API because of not accessing Trusted Publishers store which causes the uthenticode-verified software can't run on some Windows environments. But this situation is that the signature can't be cryptographically verified, which I have no idea why it would happen.
The following are the versions of the dependencies I used: