trailofbits / uthenticode

A cross-platform library for verifying Authenticode signatures
https://trailofbits.github.io/uthenticode/
MIT License
136 stars 33 forks source link

Different result from Signtool #102

Open zeze-zeze opened 1 month ago

zeze-zeze commented 1 month ago

nc.exe from int0x33/nc.exe is signed and it is verified by signtool.

>signtool verify /pa nc.exe
File: nc.exe
Index  Algorithm  Timestamp
========================================
0      sha1       Authenticode

Successfully verified: nc.exe

But it is not verified by uthenticode.

>svcli nc.exe
This PE is NOT verified!

nc.exe has 1 certificate entries

Calculated checksums:
   MD5: 93013015944D906D98AC97C32274D8E7
  SHA1: 612E98A6DABA999F46EE8CE82176E10B77B60C87
SHA256: F2CB0E58C668B2C289D7CBAD47030E3FB82126180270EE99E69076AFDE997F8E

SignedData entry:
        Embedded checksum: 612E98A6DABA999F46EE8CE82176E10B77B60C87

        Signers:
                Subject: /C=SI/CN=Jernej Simoncic
                Issuer: /C=BE/O=GlobalSign nv-sa/OU=ObjectSign CA/CN=GlobalSign ObjectSign CA
                Serial: 010000000001307A27872D

        Certificates:
                Subject: /C=BE/O=GlobalSign nv-sa/OU=Primary Object Publishing CA/CN=GlobalSign Primary Object Publishing CA
                Issuer: /C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
                Serial: 040000000001239E0FACB3

                Subject: /OU=Timestamping CA/O=GlobalSign/CN=GlobalSign Timestamping CA
                Issuer: /C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
                Serial: 0400000000012019C19066

                Subject: /C=BE/O=GlobalSign NV/CN=GlobalSign Time Stamping Authority
                Issuer: /OU=Timestamping CA/O=GlobalSign/CN=GlobalSign Timestamping CA
                Serial: 01000000000125B0B4CC01

                Subject: /C=SI/CN=Jernej Simoncic
                Issuer: /C=BE/O=GlobalSign nv-sa/OU=ObjectSign CA/CN=GlobalSign ObjectSign CA
                Serial: 010000000001307A27872D

                Subject: /C=BE/O=GlobalSign nv-sa/OU=ObjectSign CA/CN=GlobalSign ObjectSign CA
                Issuer: /C=BE/O=GlobalSign nv-sa/OU=Primary Object Publishing CA/CN=GlobalSign Primary Object Publishing CA
                Serial: 040000000001239E0FAF24

        This SignedData is invalid!

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:

woodruffw commented 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:

https://github.com/trailofbits/uthenticode/blob/cad1bfc713dd31a00de1c3fcdb887dfb812f70ad/src/uthenticode.cpp#L193-L288

If not, I can attempt to debug this in a few days.

zeze-zeze commented 1 month ago

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))).

woodruffw commented 1 month ago

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:

  1. Use the correct OpenSSL API to specify EKU behavior during chain verification, rather than checking the certs before actually verifying them, or;
  2. Check the verified chain after building to confirm that every member has the 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!

zeze-zeze commented 1 month ago

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.