CERT Gouvernemental of Luxembourg, we refactored the design of the authenticode parser [2] and we implemented functions to verify the signature. LIEF API tries to expose most of the internal components of the PKCS #7 container associated with the Authenticode. First, we can access the PE’s signature through the Although we usually find only one signature, PE executables can embed multiple signatures thanks to the The Within this object, we can access the following attributes: The The Note While the PKCS #7 standard enables multiple signers, Microsoft specifications require one and only one signer. The Regarding the PE files, the authentihash is computed through the function For instance, to compute the SHA-256 value of the authenticode, we just have to pass Note To compare the We also expose shortcut attributes in the Python API to compute the authentihash values for: Hash Algorithm Binary’s Attribute MD5 SHA1 SHA-256 SHA-512 LIEF also exposes the original raw signature blob through the property Then, we can use The authenticode_reader.py script located in the examples/ directory can also be used to inspect the signature: Besides the fact that LIEF can parse PE’s authenticode signature, LIEF can also verify the integrity of the authentihash thanks to the method: We can also verify a PE binary with a detached signature by providing a The verification process does not rely on an external component (i.e. neither openssl nor WinTrust API) but we try to reproduce the same checks as described in the RFC(s) and the official documentation of the Authenticode [4]. These checks include: Check the integrity of the signature ( There is ONE and only ONE Digest algorithms are consistent ( If the If there are authenticated attributes, check that there is a If there is a counter signature in the un-authenticated attributes, verify its integrity and check that it wraps a valid timestamping. Check the expiration of the certificates according to the potential timestamping If the signature is valid, check that These checks are the default behavior of the By using By using By using Note To verify the integrity of a Last but not least, we can also verify the certificates chain thanks to: On the other hand, Regarding the PKCS #7 structure itself, LIEF is able to parse and process most of its elements. Nevertheless, the OID Description 1.3.6.1.4.1.311.3.3.1 Ms-CounterSign (undocumented, supported in LIEF 0.15.0) 1.2.840.113549.1.9.16.2.12 S/MIME Signing certificate (id-aa-signingCertificate) 1.3.6.1.4.1.311.2.6.1 SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID 1.3.6.1.4.1.311.10.3.28 szOID_PLATFORM_MANIFEST_BINARY_ID (supported in LIEF 0.15.0) These not-supported attributes are wrapped within the Under the hood, most of the work is done by mbedtls which provides the following primitive used by LIEF: ASN.1 decoder x509 certificate processing (parsing AND verification) Hash algorithms Public key algorithms We can also cross-compile a small C++ snippet for iOS: So that we can verify the integrity of a PE executable on an iPhone: Whilst this example is quite useless, it emphasizes the purpose of this project: Provide a cross-platform and cross-format library Expose a high-level API (Python) as well as a (more or less) low-level API (C++) Few dependencies so that the static version of LIEF does not need external libraries [5]. To complete these functionalities of LIEF, you might also be interested in the following projects that deal with Authenticode: Project URL signify winsign uthenticode AuthenticodeLint osslsigncode yara-x https://github.com/VirusTotal/yara-x (which has support for PE Authenticode) Finally, you can find additional information about the Authenticode in Trail of Bits blog post [6]. If you are interested in Authenticode tricks used by Dropbox, you can take a look at Microsoft website [7] and if you are interested in understanding how the integrity of the PKCS #7 works, you can look at Manual verify PKCS#7 signed data with OpenSSL [8] References APIExploring PKCS #7 Signature¶
lief.PE.Binary.signatures
attribute [3]:import lief
pe = lief.parse("avast_free_antivirus_setup_online.exe")
print(len(pe.signatures))
signature = pe.signatures[0]
/as
command of signtool.exe
. This is why the signatures
attribute returns an iterator over the signatures parsed by LIEF.signature
variable is actually a lief.PE.Signature
object which basically mirrors the PKCS #7 container plus some method to verify its integrity.x509
certificates used to sign the executable: lief.PE.Signature.certificates
ContentInfo
object that contains the authentihash value: lief.PE.ContentInfo.digest
SignerInfo
structure: lief.PE.Signature.signers
__str__()
functions of these objects are overloaded so that we can pretty-print the content of these objects easily:# Print certificates information
for crt in signature.certificates:
print(crt)
# Print the authentihash value embedded in the signature
print(signature.content_info.digest.hex())
# Print signer information
print(signature.signers[0])
cert. version : 3
serial number : 04:09:18:1B:5F:D5:BB:66:75:53:43:B5:6F:95:50:08
issuer name : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA
subject name : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA
issued on : 2013-10-22 12:00:00
expires on : 2028-10-22 12:00:00
signed using : RSA with SHA-256
RSA key size : 2048 bits
basic constraints : CA=true, max_pathlen=0
key usage : Digital Signature, Key Cert Sign, CRL Sign
ext key usage : Code Signing
cert. version : 3
serial number : 09:70:EF:4B:AD:5C:C4:4A:1C:2B:C3:D9:64:01:67:4C
issuer name : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA
subject name : C=CZ, L=Praha, O=Avast Software s.r.o., OU=RE stapler cistodc, CN=Avast Software s.r.o.
issued on : 2020-04-02 00:00:00
expires on : 2023-03-09 12:00:00
signed using : RSA with SHA-256
RSA key size : 2048 bits
basic constraints : CA=false
key usage : Digital Signature
ext key usage : Code Signing
a738da4446a4e78ab647db7e53427eb07961c994317f4c59d7edbea5cc786d80
SHA_256/RSA - C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA - 4 auth attr - 1 unauth attr
lief.PE.Binary.authentihash()
which takes a lief.PE.ALGORITHMS
enum as parameter to define which hash algorithm must be used to compute the digest.lief.PE.ALGORITHMS.SHA_256
:print(pe.authentihash(lief.PE.ALGORITHMS.SHA_256).hex())
a738da4446a4e78ab647db7e53427eb07961c994317f4c59d7edbea5cc786d80
lief.PE.Binary.authentihash()
value with the signed one (i.e. lief.PE.ContentInfo.digest
) we must use the same hash algorithm as defined by lief.PE.Signature.digest_algorithm
lief.PE.Signature.raw_der
which enables to export the signature:from pathlib import Path
Path("/tmp/extracted.p7b").write_bytes(signature.raw_der)
openssl
to process its content:$ openssl pkcs7 -inform der -print -in /tmp/extracted.p7b -noout -text
...
sig_alg:
algorithm: sha256WithRSAEncryption (1.2.840.113549.1.1.11)
parameter: NULL
signature: (0 unused bits)
0000 - 31 c3 a7 f3 70 e3 2c 49-15 bd f4 09 6c 27 4e 1...p.,I....l'N
000f - 00 a9 23 df cb ea 7f 99-55 cb 24 88 75 e8 c4 ..#.....U.$.u..
001e - de 48 4f 70 dd 2a 27 5c-df be 36 f6 84 0d ad .HOp.*'\..6....
002d - 35 5e 65 f7 af 55 01 7a-2d 01 18 a0 d6 98 a4 5^e..U.z-......
003c - d1 bd 19 e9 a4 03 f4 a3-4d 12 6e 72 5f 6b 3a ........M.nr_k:
004b - b8 de 45 f1 63 80 b0 47-42 f6 38 b8 e7 5b dd ..E.c..GB.8..[.
005a - cf f2 f8 c2 61 4b 2c 19-b7 7d 78 8f 2e 0c b0 ....aK,..}x....
0069 - 7c f2 d9 8e 9f 65 4e 21-63 19 6a 5b 0c 91 12 |....eN!c.j[...
0078 - 44 29 fe 91 d5 6f 5d 9c-4d 7b a1 74 c6 69 d9 D)...o].M{.t.i.
0087 - e7 23 26 54 35 5c 38 33-c5 a7 92 0d 70 a5 2a .#&T5\83....p.*
0096 - 33 77 4a fc 86 b0 fa 59-2f 24 f6 a1 45 b2 09 3wJ....Y/$..E..
00a5 - 75 2d a1 81 68 e4 67 11-46 e3 fb bf 0c c5 d5 u-..h.g.F......
00b4 - d7 7b 7b 35 fb d6 e8 4a-c9 13 82 82 a7 0c 3e .{{5...J......>
00c3 - 6f 61 e0 37 15 e0 37 5d-b8 22 14 ad 54 58 0e oa.7..7]."..TX.
00d2 - 95 6c 2b b1 d2 c7 6c 86-a1 9f fa d8 37 ca f7 .l+...l.....7..
00e1 - 56 75 b0 9d df 7c 46 43-20 87 8a a3 81 47 82 Vu...|FC ....G.
00f0 - 99 57 87 12 46 96 02 7c-a7 77 b9 42 4d c8 05 .W..F..|.w.BM..
00ff - 0a .
crl:
<ABSENT>
signer_info:
version: 1
issuer_and_serial:
issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA
serial: 12549442701880659695003200114191853388
digest_alg:
algorithm: sha256 (2.16.840.1.101.3.4.2.1)
parameter: NULL
auth_attr:
object: contentType (1.2.840.113549.1.9.3)
set:
OBJECT:undefined (1.3.6.1.4.1.311.2.1.4)
object: undefined (1.3.6.1.4.1.311.2.1.11)
$ python authenticode_reader.py --all avast_free_antivirus_setup_online.exe
Signature version : 1
Digest Algorithm : ALGORITHMS.SHA_256
Content Info:
Content Type : 1.3.6.1.4.1.311.2.1.4 (SPC_INDIRECT_DATA_CONTENT)
Digest Algorithm: ALGORITHMS.SHA_256
Digest : a738da4446a4e78ab647db7e53427eb07961c994317f4c59d7edbea5cc786d80
Certificates
Version : 3
Issuer : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA
Subject : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA
Serial Number : 0409181b5fd5bb66755343b56f955008
Signature Algorithm: SHA256_WITH_RSA_ENCRYPTION
Valid from : 2013/10/22 - 12:00:00
Valid to : 2028/10/22 - 12:00:00
Key usage : CRL_SIGN - KEY_CERT_SIGN - DIGITAL_SIGNATURE
Ext key usage : CODE_SIGNING
RSA key size : 2048
===========================================
Version : 3
Issuer : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA
Subject : C=CZ, L=Praha, O=Avast Software s.r.o., OU=RE stapler cistodc, CN=Avast Software s.r.o.
Serial Number : 0970ef4bad5cc44a1c2bc3d96401674c
Signature Algorithm: SHA256_WITH_RSA_ENCRYPTION
Valid from : 2020/04/02 - 00:00:00
Valid to : 2023/03/09 - 12:00:00
Key usage : DIGITAL_SIGNATURE
Ext key usage : CODE_SIGNING
RSA key size : 2048
===========================================
Signer(s)
Version : 1
Serial Number : 0970ef4bad5cc44a1c2bc3d96401674c
Issuer : C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Assured ID Code Signing CA
Digest Algorithm : ALGORITHMS.SHA_256
Encryption Algorithm: ALGORITHMS.RSA
Encrypted Digest : 758db1f480eb25bada6c ...
Authenticated attributes:
Content Type OID: 1.3.6.1.4.1.311.2.1.4 (SPC_INDIRECT_DATA_CONTENT)
MS Statement type OID: 1.3.6.1.4.1.311.2.1.21 (INDIVIDUAL_CODE_SIGNING)
Info: http://www.avast.com
PKCS9 Message Digest: 3983816a7d1c62962540ec66fa8790fa45d1063cb23e933677de459f0b73c577
Un-authenticated attributes:
Generic Type 1.3.6.1.4.1.311.3.3.1 (MS_COUNTER_SIGN)
Verifying the Signature¶
lief.PE.Binary.verify_signature()
which outputs lief.PE.Signature.VERIFICATION_FLAGS.OK
if the signature is valid or another enum (see: lief.PE.Signature.VERIFICATION_FLAGS
) when it is invalid:pe = lief.parse("avast_free_antivirus_setup_online.exe")
print(pe.verify_signature()) # lief.PE.Signature.VERIFICATION_FLAGS.OK
signature
object to verify_signature()
:pe = lief.parse("avast_free_antivirus_setup_online.exe")
detached_sig = lief.PE.Signature.parse("/tmp/detached.p7b")
print(pe.verify_signature(detached_sig))
lief.PE.Signature.check()
):SignerInfo
Signature.digest_algorithm
==
ContentInfo.digest_algorithm
==
SignerInfo.digest_algorithm
)SignerInfo
has authenticated attributes, check their integrity. Otherwise, check the integrity of the ContentInfo
against the Signer’s certificate.lief.PE.PKCS9MessageDigest
attribute for which the digest
matches the hash of the ContentInfo
lief.PE.ContentInfo.digest
matches the computed authentihash()
verify_signature()
. Nevertheless, you could pass lief.PE.Signature.VERIFICATION_CHECKS
flags to customize its behavior:VERIFICATION_CHECKS.HASH_ONLY
, it only performs step B)
(i.e. check the authentihash values regardless of the signature integrity)pe.verify_signature(lief.PE.Signature.VERIFICATION_CHECKS.HASH_ONLY)
VERIFICATION_CHECKS.LIFETIME_SIGNING
, timestamped signatures can expire if their certificate expired. It has the same meaning as WTD_LIFETIME_SIGNING_FLAGpe.verify_signature(lief.PE.Signature.VERIFICATION_CHECKS.LIFETIME_SIGNING)
signature.check(lief.PE.Signature.VERIFICATION_CHECKS.LIFETIME_SIGNING)
VERIFICATION_CHECKS.SKIP_CERT_TIME
, LIEF doesn’t raise an error if the certificate(s) expired.# Returns lief.PE.Signature.VERIFICATION_FLAGS.OK even though
# the certificates expired
pe.verify_signature(lief.PE.Signature.VERIFICATION_CHECKS.SKIP_CERT_TIME)
signature.check(lief.PE.Signature.VERIFICATION_CHECKS.SKIP_CERT_TIME)
Signature
object, you can use lief.PE.Signature.check()
Certificate Chain of Trust¶
verify()
aims to verify a signed certificate from its CA. Given a CA x509
certificate, CA.verify(signed)
verifies that the signed
parameter has been signed by CA
.is_trusted_by()
can be used to check that a given x509
certificate is verified against a list of certificates:CA_BUNDLE = lief.PE.x509.parse("ms_bundle.pem")
signer = signature.signers[0]
print(signer.cert.is_trusted_by(CA_BUNDLE))
cert1 = lief.PE.x509.parse("ca1.crt")
cert2 = lief.PE.x509.parse("ca2.crt")
print(signer.cert.is_trusted_by([cert1, cert2]))
Limitations¶
lief.PE.SignerInfo
structure can embed attributes (authenticated or not) for which the ASN.1 structure can be public or not. As of LIEF v0.11.0 we do not support yet the following OIDs:lief.PE.GenericType
that exposes the raw ASN.1 blob with the property raw_content
.Conclusion¶
#include <LIEF/PE.hpp>
int main(int argc, char** argv) {
std::unique_ptr<LIEF::PE::Binary> pe = LIEF::PE::Parser::parse(argv[1])
if (pe->verify_signature() == LIEF::PE::Signature::VERIFICATION_FLAGS.OK) {
std::cout << "Signature ok!" << "\n";
return 0;
}
std::cout << "Error!" << "\n";
return 1;
}
iPhone:~ root# file PE32_x86-64_binary_avast-free-antivirus-setup-online.exe
PE32_x86-64_binary_avast-free-antivirus-setup-online.exe: PE32 executable (GUI) Intel 80386, for MS Windows
iPhone:~ root# file ./pe_authenticode_check
./pe_authenticode_check: Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|WEAK_DEFINES|BINDS_TO_WEAK|PIE|HAS_TLV_DESCRIPTORS>
iPhone:~ root# ./pe_authenticode_check PE32_x86-64_binary_avast-free-antivirus-setup-online.exe
Signature ok!
iPhone:~ root#
$ otool -L pe_authenticode_check
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1770.255.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 904.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)