diff --git a/AUTHORS b/AUTHORS index 498997024e..bcc9c412cd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,6 +8,7 @@ # Please keep the list sorted. +Avast Google Inc. Hilko Bengen Joachim Metz diff --git a/CONTRIBUTORS b/CONTRIBUTORS index e8a69cb213..7cc5e0a708 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -31,6 +31,7 @@ Antonio Vargas Gonzalez Christian Blichmann Hilko Bengen Joachim Metz +Karel Hajek Karl Hiramoto Mike Wiacek Shane Huntley diff --git a/bazel/yara.bzl b/bazel/yara.bzl index d7ba0077ee..c4a02eb322 100644 --- a/bazel/yara.bzl +++ b/bazel/yara.bzl @@ -194,6 +194,16 @@ def yara_library( "libyara/stream.c", "libyara/strutils.c", "libyara/threading.c", + "libyara/include/authenticode-parser/authenticode.h", + "libyara/modules/pe/authenticode-parser/authenticode.c", + "libyara/modules/pe/authenticode-parser/certificate.c", + "libyara/modules/pe/authenticode-parser/certificate.h", + "libyara/modules/pe/authenticode-parser/countersignature.c", + "libyara/modules/pe/authenticode-parser/countersignature.h", + "libyara/modules/pe/authenticode-parser/helper.c", + "libyara/modules/pe/authenticode-parser/helper.h", + "libyara/modules/pe/authenticode-parser/structs.c", + "libyara/modules/pe/authenticode-parser/structs.h", "libyara/tlshc/tlsh.c", "libyara/tlshc/tlsh_impl.c", "libyara/tlshc/tlsh_impl.h", diff --git a/configure.ac b/configure.ac index c2a5263498..a98225f758 100644 --- a/configure.ac +++ b/configure.ac @@ -332,6 +332,7 @@ AS_IF([test "x$have_crypto" = "xno"], ]) ], [ + build_authenticode_module=true # Authenticode relies on openssl build_hash_module=true CFLAGS="$CFLAGS -DHASH_MODULE" PC_REQUIRES_PRIVATE="$PC_REQUIRES_PRIVATE libcrypto" @@ -351,6 +352,7 @@ AM_CONDITIONAL([UB_SANITIZER], [test x$undefined_behaviour_sanitizer = xtrue]) AM_CONDITIONAL([CUCKOO_MODULE], [test x$build_cuckoo_module = xtrue]) AM_CONDITIONAL([MAGIC_MODULE], [test x$build_magic_module = xtrue]) AM_CONDITIONAL([HASH_MODULE], [test x$build_hash_module = xtrue]) +AM_CONDITIONAL([AUTHENTICODE_MODULE], [test x$build_authenticode_module = xtrue]) AM_CONDITIONAL([DOTNET_MODULE], [test x$build_dotnet_module = xtrue]) AM_CONDITIONAL([MACHO_MODULE], [test x$build_macho_module = xtrue]) AM_CONDITIONAL([PB_TESTS_MODULE], [test x$build_pb_tests_module = xtrue]) diff --git a/docs/modules/pe.rst b/docs/modules/pe.rst index 14fb8bfc0e..ba9fe9b878 100644 --- a/docs/modules/pe.rst +++ b/docs/modules/pe.rst @@ -820,6 +820,10 @@ Reference Number of authenticode signatures in the PE. +.. c:type:: is_signed + + True if any of the PE signatures is valid. + .. c:type:: signatures A zero-based array of signature objects, one for each authenticode @@ -898,6 +902,117 @@ Reference timestamp >= pe.signatures[n].not_before and timestamp <= pe.signatures[n].not_after + .. c:member:: verified + + Boolean, true if signature was sucessfully verified. + + .. c:member:: digest_alg + + Name of the algorithm used for file digest. Usually "sha1" or "sha256" + + .. c:member:: digest + + Digest of the file signed in the signature. + + .. c:member:: file_digest + + Calculated digest using digest_alg of the analysed file. + + .. c:member:: number_of_certificates + + Number of the certificates stored in the signature, including the ones in countersignatures. + + .. c:type:: certificates + + A zero-based array of certificates stored in the signature, including the ones in countersignatures. + The members of the certificates are identical to those already explained before, with the same name. + + .. c:member:: thumbprint + .. c:member:: issuer + .. c:member:: subject + .. c:member:: version + .. c:member:: algorithm + .. c:member:: serial + .. c:member:: not_before + .. c:member:: not_after + + .. c:type:: signer_info + + Information about the signature signer. + + .. c:member:: program_name + + Optional program name stored in the signature. + + .. c:member:: digest + + Signed digest of the signature. + + .. c:member:: digest_alg + + Algorithm used for the digest of the signature. Usually "sha1" or "sha256" + + .. c:member:: length_of_chain + + Number of certificates in the signers chain. + + .. c:type:: chain + + A zero-based array of certificates in the signers chain. The members of the certificates are + identical to those already explained before, with the same name. + + .. c:member:: thumbprint + .. c:member:: issuer + .. c:member:: subject + .. c:member:: version + .. c:member:: algorithm + .. c:member:: serial + .. c:member:: not_before + .. c:member:: not_after + + .. c:member:: number_of_countersignatures + + Number of the countersignatures of the signature. + + .. c:type:: countersignatures + + A zero-based array of the countersignatures of the signature. + Almost always it's just single timestamp one. + + .. c:member:: verified + + Boolean, true if countersignature was sucessfully verified. + + .. c:member:: sign_time + + Integer - unix time of the timestamp signing time. + + .. c:member:: digest + + Signed digest of the countersignature. + + .. c:member:: digest_alg + + Algorithm used for the digest of the countersignature. Usually "sha1" or "sha256" + + .. c:member:: length_of_chain + + Number of certificates in the countersigners chain. + + .. c:type:: chain + + A zero-based array of certificates in the countersigners chain. The members of the certificates are + identical to those already explained before, with the same name. + + .. c:member:: thumbprint + .. c:member:: issuer + .. c:member:: subject + .. c:member:: version + .. c:member:: algorithm + .. c:member:: serial + .. c:member:: not_before + .. c:member:: not_after + .. c:type:: rich_signature Structure containing information about the PE's rich signature as diff --git a/libyara/Makefile.am b/libyara/Makefile.am index 677d1fbbfe..628194a02d 100644 --- a/libyara/Makefile.am +++ b/libyara/Makefile.am @@ -77,6 +77,14 @@ MODULES += modules/pb_tests/pb_tests.c MODULES += modules/pb_tests/pb_tests.pb-c.c endif +if AUTHENTICODE_MODULE +MODULES += modules/pe/authenticode-parser/authenticode.c +MODULES += modules/pe/authenticode-parser/certificate.c +MODULES += modules/pe/authenticode-parser/helper.c +MODULES += modules/pe/authenticode-parser/countersignature.c +MODULES += modules/pe/authenticode-parser/structs.c +endif + # # Add your modules here: # diff --git a/libyara/include/authenticode-parser/authenticode.h b/libyara/include/authenticode-parser/authenticode.h new file mode 100644 index 0000000000..4dfe8a2d20 --- /dev/null +++ b/libyara/include/authenticode-parser/authenticode.h @@ -0,0 +1,206 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_AUTHENTICODE_H +#define AUTHENTICODE_PARSER_AUTHENTICODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* Signature is valid */ +#define AUTHENTICODE_VFY_VALID 0 +/* Parsing error (from OpenSSL functions) */ +#define AUTHENTICODE_VFY_CANT_PARSE 1 +/* Signers certificate is missing */ +#define AUTHENTICODE_VFY_NO_SIGNER_CERT 2 +/* No digest saved inside the signature */ +#define AUTHENTICODE_VFY_DIGEST_MISSING 3 +/* Non verification errors - allocations etc. */ +#define AUTHENTICODE_VFY_INTERNAL_ERROR 4 +/* SignerInfo part of PKCS7 is missing */ +#define AUTHENTICODE_VFY_NO_SIGNER_INFO 5 +/* PKCS7 doesn't have type of SignedData, can't proceed */ +#define AUTHENTICODE_VFY_WRONG_PKCS7_TYPE 6 +/* PKCS7 doesn't have corrent content, can't proceed */ +#define AUTHENTICODE_VFY_BAD_CONTENT 7 +/* Contained and calculated digest don't match */ +#define AUTHENTICODE_VFY_INVALID 8 +/* Signature hash and file hash doesn't match */ +#define AUTHENTICODE_VFY_WRONG_FILE_DIGEST 9 +/* Unknown algorithm, can't proceed with verification */ +#define AUTHENTICODE_VFY_UNKNOWN_ALGORITHM 10 + +/* Countersignature is valid */ +#define COUNTERSIGNATURE_VFY_VALID 0 +/* Parsing error (from OpenSSL functions) */ +#define COUNTERSIGNATURE_VFY_CANT_PARSE 1 +/* Signers certificate is missing */ +#define COUNTERSIGNATURE_VFY_NO_SIGNER_CERT 2 +/* Unknown algorithm, can't proceed with verification */ +#define COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM 3 +/* Verification failed, digest mismatch */ +#define COUNTERSIGNATURE_VFY_INVALID 4 +/* Failed to decrypt countersignature enc_digest for verification */ +#define COUNTERSIGNATURE_VFY_CANT_DECRYPT_DIGEST 5 +/* No digest saved inside the countersignature */ +#define COUNTERSIGNATURE_VFY_DIGEST_MISSING 6 +/* Message digest inside countersignature doesn't match signature it countersigns */ +#define COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE 7 +/* Non verification errors - allocations etc. */ +#define COUNTERSIGNATURE_VFY_INTERNAL_ERROR 8 +/* Time is missing in the timestamp signature */ +#define COUNTERSIGNATURE_VFY_TIME_MISSING 9 + +typedef struct { + uint8_t* data; + int len; +} ByteArray; + +typedef struct { /* Various X509 attributes parsed out in raw bytes*/ + ByteArray country; + ByteArray organization; + ByteArray organizationalUnit; + ByteArray nameQualifier; + ByteArray state; + ByteArray commonName; + ByteArray serialNumber; + ByteArray locality; + ByteArray title; + ByteArray surname; + ByteArray givenName; + ByteArray initials; + ByteArray pseudonym; + ByteArray generationQualifier; + ByteArray emailAddress; +} Attributes; + +typedef struct { + long version; /* Raw version of X509 */ + char* issuer; /* Oneline name of Issuer */ + char* subject; /* Oneline name of Subject */ + char* serial; /* Serial number in format 00:01:02:03:04... */ + ByteArray sha1; /* SHA1 of the DER representation of the cert */ + ByteArray sha256; /* SHA256 of the DER representation of the cert */ + char* key_alg; /* Name of the key algorithm */ + char* sig_alg; /* Name of the signature algorithm */ + char* sig_alg_oid; /* OID of the signature algorithm */ + time_t not_before; /* NotBefore validity */ + time_t not_after; /* NotAfter validity */ + char* key; /* PEM encoded public key */ + Attributes issuer_attrs; /* Parsed X509 Attributes of Issuer */ + Attributes subject_attrs; /* Parsed X509 Attributes of Subject */ +} Certificate; + +typedef struct { + Certificate** certs; + size_t count; +} CertificateArray; + +typedef struct { + int verify_flags; /* COUNTERISGNATURE_VFY_ flag */ + time_t sign_time; /* Signing time of the timestamp countersignature */ + char* digest_alg; /* Name of the digest algorithm used */ + ByteArray digest; /* Stored message digest */ + CertificateArray* chain; /* Certificate chain of the signer */ +} Countersignature; + +typedef struct { + Countersignature** counters; + size_t count; +} CountersignatureArray; + +typedef struct { /* Represents SignerInfo structure */ + ByteArray digest; /* Message Digest of the SignerInfo */ + char* digest_alg; /* name of the digest algorithm */ + char* program_name; /* Program name stored in SpcOpusInfo structure of Authenticode */ + CertificateArray* chain; /* Certificate chain of the signer */ +} Signer; + +typedef struct { + int verify_flags; /* AUTHENTICODE_VFY_ flag */ + int version; /* Raw PKCS7 version */ + char* digest_alg; /* name of the digest algorithm */ + ByteArray digest; /* File Digest stored in the Signature */ + ByteArray file_digest; /* Actual calculated file digest */ + Signer* signer; /* SignerInfo information of the Authenticode */ + CertificateArray* certs; /* All certificates in the Signature including the ones in timestamp + countersignatures */ + CountersignatureArray* countersigs; /* Array of timestamp countersignatures */ +} Authenticode; + +typedef struct { + Authenticode** signatures; + size_t count; +} AuthenticodeArray; + +/** + * @brief Initializes all globals OpenSSl objects we need for parsing, this is not thread-safe and + * needs to be called only once, before any multithreading environment + * https://github.com/openssl/openssl/issues/13524 + */ +void initialize_authenticode_parser(); + +/** + * @brief Constructs AuthenticodeArray from PE file data. Authenticode can + * contains nested Authenticode signatures as its unsigned attribute, + * which can also contain nested signatures. For this reason the function returns + * an Array of parsed Authenticode signatures. Any field of the parsed out + * structures can be NULL, depending on the input data. + * Verification result is stored in verify_flags with the first verification error. + * + * @param pe_data PE binary data + * @param pe_len + * @return AuthenticodeArray* + */ +AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, uint64_t pe_len); + +/** + * @brief Constructs AuthenticodeArray from binary data containing Authenticode + * signature. Authenticode can contains nested Authenticode signatures + * as its unsigned attribute, which can also contain nested signatures. + * For this reason the function return an Array of parsed Authenticode signatures. + * Any field of the parsed out structures can be NULL, depending on the input data. + * WARNING: in case of this interface, the file and signature digest comparison is + * up to the library user, as there is no pe data to calculate file digest from. + * Verification result is stored in verify_flags with the first verification error + * + * @param data Binary data containing Authenticode signature + * @param len + * @return AuthenticodeArray* + */ +AuthenticodeArray* authenticode_new(const uint8_t* data, long len); + +/** + * @brief Deallocates AuthenticodeArray and all it's allocated members + * + * @param auth + */ +void authenticode_array_free(AuthenticodeArray* auth); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libyara/include/yara/pe_utils.h b/libyara/include/yara/pe_utils.h index 2e017a5308..e36e9db911 100644 --- a/libyara/include/yara/pe_utils.h +++ b/libyara/include/yara/pe_utils.h @@ -83,9 +83,4 @@ int64_t pe_rva_to_offset(PE* pe, uint64_t rva); char* ord_lookup(char* dll, uint16_t ord); -#if HAVE_LIBCRYPTO -#include -time_t ASN1_get_time_t(const ASN1_TIME* time); -#endif - #endif diff --git a/libyara/modules/pe/authenticode-parser/authenticode.c b/libyara/modules/pe/authenticode-parser/authenticode.c new file mode 100644 index 0000000000..7515102464 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/authenticode.c @@ -0,0 +1,648 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "certificate.h" +#include "countersignature.h" +#include "helper.h" +#include "structs.h" + +#define MAX_NESTED_COUNT 16 + +/* Moves signatures from src to dst, returns 0 on success, + * else 1. If error occurs, arguments are unchanged */ +static int authenticode_array_move(AuthenticodeArray* dst, AuthenticodeArray* src) +{ + size_t newCount = dst->count + src->count; + + Authenticode** tmp = (Authenticode**)realloc(dst->signatures, newCount * sizeof(Authenticode*)); + if (!tmp) + return 1; + + dst->signatures = tmp; + + for (size_t i = 0; i < src->count; ++i) + dst->signatures[i + dst->count] = src->signatures[i]; + + dst->count = newCount; + + free(src->signatures); + src->signatures = NULL; + src->count = 0; + + return 0; +} + +static SpcIndirectDataContent* get_content(PKCS7* content) +{ + if (!content) + return NULL; + + if (OBJ_obj2nid(content->type) != OBJ_txt2nid(NID_spc_indirect_data)) + return NULL; + + SpcIndirectDataContent* spcContent = SpcIndirectDataContent_new(); + if (!spcContent) + return NULL; + + int len = content->d.other->value.sequence->length; + const uint8_t* data = content->d.other->value.sequence->data; + + d2i_SpcIndirectDataContent(&spcContent, &data, len); + + return spcContent; +} + +static char* parse_program_name(ASN1_TYPE* spcAttr) +{ + const uint8_t* spcData = spcAttr->value.sequence->data; + int spcLen = spcAttr->value.sequence->length; + SpcSpOpusInfo* spcInfo = d2i_SpcSpOpusInfo(NULL, &spcData, spcLen); + if (!spcInfo) + return NULL; + + char* result = NULL; + + if (spcInfo->programName) { + uint8_t* data = NULL; + /* Should be Windows UTF16..., try to convert it to UTF8 */ + int nameLen = ASN1_STRING_to_UTF8(&data, spcInfo->programName->value.unicode); + if (nameLen >= 0 && nameLen < spcLen) { + result = (char*)malloc(nameLen + 1); + if (result) { + memcpy(result, data, nameLen); + result[nameLen] = 0; + } + OPENSSL_free(data); + } + } + + SpcSpOpusInfo_free(spcInfo); + return result; +} + +/* Parses X509* certs into internal representation and inserts into CertificateArray + * Array is assumed to have enough space to hold all certificates storted in the STACK */ +static void parse_certificates(const STACK_OF(X509) * certs, CertificateArray* result) +{ + int certCount = sk_X509_num(certs); + int i = 0; + for (; i < certCount; ++i) { + Certificate* cert = certificate_new(sk_X509_value(certs, i)); + if (!cert) + break; + + /* Write to the result */ + result->certs[i] = cert; + } + result->count = i; +} + +static void parse_nested_authenticode(PKCS7_SIGNER_INFO* si, AuthenticodeArray* result) +{ + STACK_OF(X509_ATTRIBUTE)* attrs = PKCS7_get_attributes(si); + int idx = X509at_get_attr_by_NID(attrs, OBJ_txt2nid(NID_spc_nested_signature), -1); + X509_ATTRIBUTE* attr = X509at_get_attr(attrs, idx); + + int attrCount = X509_ATTRIBUTE_count(attr); + if (!attrCount) + return; + + /* Limit the maximum amount of nested attributes to be safe from malformed samples */ + attrCount = attrCount > MAX_NESTED_COUNT ? MAX_NESTED_COUNT : attrCount; + + for (int i = 0; i < attrCount; ++i) { + ASN1_TYPE* nested = X509_ATTRIBUTE_get0_type(attr, i); + if (nested == NULL) + break; + int len = nested->value.sequence->length; + const uint8_t* data = nested->value.sequence->data; + AuthenticodeArray* auth = authenticode_new(data, len); + if (!auth) + continue; + + authenticode_array_move(result, auth); + authenticode_array_free(auth); + } +} + +static void parse_pkcs9_countersig(PKCS7* p7, Authenticode* auth) +{ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + + STACK_OF(X509_ATTRIBUTE)* attrs = PKCS7_get_attributes(si); + + int idx = X509at_get_attr_by_NID(attrs, NID_pkcs9_countersignature, -1); + X509_ATTRIBUTE* attr = X509at_get_attr(attrs, idx); + + int attrCount = X509_ATTRIBUTE_count(attr); + if (!attrCount) + return; + + /* Limit the maximum amount of nested attributes to be safe from malformed samples */ + attrCount = attrCount > MAX_NESTED_COUNT ? MAX_NESTED_COUNT : attrCount; + + for (int i = 0; i < attrCount; ++i) { + ASN1_TYPE* nested = X509_ATTRIBUTE_get0_type(attr, i); + if (nested == NULL) + break; + int len = nested->value.sequence->length; + const uint8_t* data = nested->value.sequence->data; + + Countersignature* sig = pkcs9_countersig_new(data, len, p7->d.sign->cert, si->enc_digest); + if (!sig) + continue; + + countersignature_array_insert(auth->countersigs, sig); + } +} + +/* Extracts X509 certificates from MS countersignature and stores them into result */ +static void extract_ms_counter_certs(const uint8_t* data, int len, CertificateArray* result) +{ + PKCS7* p7 = d2i_PKCS7(NULL, &data, len); + if (!p7) + return; + + STACK_OF(X509)* certs = p7->d.sign->cert; + CertificateArray* certArr = certificate_array_new(sk_X509_num(certs)); + if (!certArr) { + PKCS7_free(p7); + return; + } + parse_certificates(certs, certArr); + certificate_array_move(result, certArr); + certificate_array_free(certArr); + + PKCS7_free(p7); +} + +static void parse_ms_countersig(PKCS7* p7, Authenticode* auth) +{ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + + STACK_OF(X509_ATTRIBUTE)* attrs = PKCS7_get_attributes(si); + + int idx = X509at_get_attr_by_NID(attrs, OBJ_txt2nid(NID_spc_ms_countersignature), -1); + X509_ATTRIBUTE* attr = X509at_get_attr(attrs, idx); + + int attrCount = X509_ATTRIBUTE_count(attr); + if (!attrCount) + return; + + /* Limit the maximum amount of nested attributes to be safe from malformed samples */ + attrCount = attrCount > MAX_NESTED_COUNT ? MAX_NESTED_COUNT : attrCount; + + for (int i = 0; i < attrCount; ++i) { + ASN1_TYPE* nested = X509_ATTRIBUTE_get0_type(attr, i); + if (nested == NULL) + break; + int len = nested->value.sequence->length; + const uint8_t* data = nested->value.sequence->data; + + Countersignature* sig = ms_countersig_new(data, len, si->enc_digest); + if (!sig) + return; + + /* Because MS TimeStamp countersignature has it's own SET of certificates + * extract it back into parent signature for consistency with PKCS9 */ + countersignature_array_insert(auth->countersigs, sig); + extract_ms_counter_certs(data, len, auth->certs); + } +} + +static bool authenticode_verify(PKCS7* p7, PKCS7_SIGNER_INFO* si, X509* signCert) +{ + const uint8_t* contentData = p7->d.sign->contents->d.other->value.sequence->data; + long contentLen = p7->d.sign->contents->d.other->value.sequence->length; + + uint64_t version = 0; + ASN1_INTEGER_get_uint64(&version, p7->d.sign->version); + if (version == 1) { + /* Move the pointer to the actual contents - skip OID and length */ + int pclass = 0, ptag = 0; + ASN1_get_object(&contentData, &contentLen, &ptag, &pclass, contentLen); + } + + BIO* contentBio = BIO_new_mem_buf(contentData, contentLen); + /* Create `digest` type BIO to calculate content digest for verification */ + BIO* p7bio = PKCS7_dataInit(p7, contentBio); + + char buf[4096]; + /* We now have to 'read' from p7bio to calculate content digest */ + while (BIO_read(p7bio, buf, sizeof(buf)) > 0) + continue; + + /* Pass it to the PKCS7_signatureVerify, to do the hard work for us */ + bool isValid = PKCS7_signatureVerify(p7bio, p7, si, signCert) == 1; + + BIO_free_all(p7bio); + + return isValid; +} + +/* Creates all the Authenticode objects so we can parse them with OpenSSL, is not thread-safe, needs + * to be called once before any multi-threading environmentt - https://github.com/openssl/openssl/issues/13524 */ +void initialize_authenticode_parser() +{ + OBJ_create("1.3.6.1.4.1.311.2.1.12", "spcSpOpusInfo", "SPC_SP_OPUS_INFO_OBJID"); + OBJ_create("1.3.6.1.4.1.311.3.3.1", "spcMsCountersignature", "SPC_MICROSOFT_COUNTERSIGNATURE"); + OBJ_create("1.3.6.1.4.1.311.2.4.1", "spcNestedSignature", "SPC_NESTED_SIGNATUREs"); + OBJ_create("1.3.6.1.4.1.311.2.1.4", "spcIndirectData", "SPC_INDIRECT_DATA"); +} + +/* Return array of Authenticode signatures stored in the data, there can be multiple + * of signatures as Authenticode signatures are often nested through unauth attributes */ +AuthenticodeArray* authenticode_new(const uint8_t* data, long len) +{ + if (!data || len == 0) + return NULL; + + AuthenticodeArray* result = (AuthenticodeArray*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + result->signatures = (Authenticode**)malloc(sizeof(Authenticode*)); + if (!result->signatures) { + free(result); + return NULL; + } + + Authenticode* auth = (Authenticode*)calloc(1, sizeof(*auth)); + if (!auth) { + free(result->signatures); + free(result); + return NULL; + } + + result->count = 1; + result->signatures[0] = auth; + + /* Let openssl parse the PKCS7 structure */ + PKCS7* p7 = d2i_PKCS7(NULL, &data, len); + if (!p7) { + auth->verify_flags = AUTHENTICODE_VFY_CANT_PARSE; + goto end; + } + + /* We expect SignedData type of PKCS7 */ + if (!PKCS7_type_is_signed(p7)) { + auth->verify_flags = AUTHENTICODE_VFY_WRONG_PKCS7_TYPE; + goto end; + } + + PKCS7_SIGNED* p7data = p7->d.sign; + + uint64_t version = 0; + if (ASN1_INTEGER_get_uint64(&version, p7data->version)) + auth->version = version; + + STACK_OF(X509)* certs = p7data->cert; + + auth->certs = certificate_array_new(sk_X509_num(certs)); + if (!auth->certs) { + auth->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + goto end; + } + parse_certificates(certs, auth->certs); + + /* Get Signature content that contains the message digest and it's algorithm */ + SpcIndirectDataContent* dataContent = get_content(p7data->contents); + if (!dataContent) { + auth->verify_flags = AUTHENTICODE_VFY_BAD_CONTENT; + goto end; + } + + DigestInfo* messageDigest = dataContent->messageDigest; + + int digestnid = OBJ_obj2nid(messageDigest->digestAlgorithm->algorithm); + auth->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + int digestLen = messageDigest->digest->length; + const uint8_t* digestData = messageDigest->digest->data; + byte_array_init(&auth->digest, digestData, digestLen); + + SpcIndirectDataContent_free(dataContent); + + Signer* signer = (Signer*)calloc(1, sizeof(Signer)); + if (!signer) { + auth->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + goto end; + } + auth->signer = signer; + + /* Authenticode is supposed to have only one SignerInfo value + * that contains all information for actual signing purposes + * and nested signatures or countersignatures */ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + if (!si) { + auth->verify_flags = AUTHENTICODE_VFY_NO_SIGNER_INFO; + goto end; + } + + auth->countersigs = (CountersignatureArray*)calloc(1, sizeof(CountersignatureArray)); + if (!auth->countersigs) { + auth->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + goto end; + } + /* Authenticode can contain SET of nested Authenticode signatures + * and countersignatures in unauthenticated attributes */ + parse_nested_authenticode(si, result); + parse_pkcs9_countersig(p7, auth); + parse_ms_countersig(p7, auth); + + /* Get the signing certificate for the first SignerInfo */ + STACK_OF(X509)* signCertStack = PKCS7_get0_signers(p7, certs, 0); + + X509* signCert = sk_X509_value(signCertStack, 0); + if (!signCert) { + auth->verify_flags = AUTHENTICODE_VFY_NO_SIGNER_CERT; + sk_X509_free(signCertStack); + goto end; + } + + sk_X509_free(signCertStack); + + signer->chain = parse_signer_chain(signCert, certs); + + /* Get the Signers digest of Authenticode content */ + ASN1_TYPE* digest = PKCS7_get_signed_attribute(si, NID_pkcs9_messageDigest); + if (!digest) { + auth->verify_flags = AUTHENTICODE_VFY_DIGEST_MISSING; + goto end; + } + + digestnid = OBJ_obj2nid(si->digest_alg->algorithm); + signer->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + digestLen = digest->value.asn1_string->length; + digestData = digest->value.asn1_string->data; + byte_array_init(&signer->digest, digestData, digestLen); + + /* Authenticode stores optional programName in non-optional SpcSpOpusInfo attribute */ + ASN1_TYPE* spcInfo = PKCS7_get_signed_attribute(si, OBJ_txt2nid(NID_spc_info)); + if (spcInfo) + signer->program_name = parse_program_name(spcInfo); + + /* If we got to this point, we got all we need to start verifying */ + bool isValid = authenticode_verify(p7, si, signCert); + if (!isValid) + auth->verify_flags = AUTHENTICODE_VFY_INVALID; + +end: + PKCS7_free(p7); + return result; +} + +static int authenticode_digest( + const EVP_MD* md, + const uint8_t* pe_data, + uint32_t pe_hdr_offset, + bool is_64bit, + uint32_t cert_table_addr, + uint8_t* digest) +{ + uint32_t buffer_size = 0xFFFF; + uint8_t* buffer = (uint8_t*)malloc(buffer_size); + + /* BIO with the file data */ + BIO* bio = BIO_new_mem_buf(pe_data, cert_table_addr); + + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!buffer || !bio || !mdctx) + goto error; + + if (!EVP_DigestInit(mdctx, md)) + goto error; + + /* Calculate size of the space between file start and PE header */ + /* Checksum starts at 0x58th byte of the header */ + uint32_t pe_checksum_offset = pe_hdr_offset + 0x58; + /* Space between DOS and PE header could have arbitrary amount of data, read in chunks */ + uint32_t fpos = 0; + while (fpos < pe_checksum_offset) { + uint32_t len_to_read = pe_checksum_offset - fpos; + if (len_to_read > buffer_size) + len_to_read = buffer_size; + + int rlen = BIO_read(bio, buffer, len_to_read); + if (rlen <= 0) + goto error; + + if (!EVP_DigestUpdate(mdctx, buffer, rlen)) + goto error; + + fpos += rlen; + } + + /* Skip the checksum */ + if (BIO_read(bio, buffer, 4) <= 0) + goto error; + + /* 64bit PE file is larger than 32bit */ + uint32_t pe64_extra = is_64bit ? 16 : 0; + + /* Read up to certificate table*/ + uint32_t cert_table_offset = 0x3c + pe64_extra; + + if (BIO_read(bio, buffer, cert_table_offset) <= 0) + goto error; + + if (!EVP_DigestUpdate(mdctx, buffer, cert_table_offset)) + goto error; + + /* Skip certificate table */ + if (BIO_read(bio, buffer, 8) <= 0) + goto error; + + /* PE header with check sum + checksum + cert table offset + cert table len */ + fpos = pe_checksum_offset + 4 + cert_table_offset + 8; + + /* Hash everything up to the signature (assuming signature is stored in the + * end of the file) */ + /* Read chunks of the file in case the file is large */ + while (fpos < cert_table_addr) { + uint32_t len_to_read = cert_table_addr - fpos; + if (len_to_read > buffer_size) + len_to_read = buffer_size; + + int rlen = BIO_read(bio, buffer, len_to_read); + if (rlen <= 0) + goto error; + + if (!EVP_DigestUpdate(mdctx, buffer, rlen)) + goto error; + fpos += rlen; + } + + /* Calculate the digest, write it into digest */ + if (!EVP_DigestFinal(mdctx, digest, NULL)) + goto error; + + EVP_MD_CTX_free(mdctx); + BIO_free_all(bio); + free(buffer); + return 0; + +error: + EVP_MD_CTX_free(mdctx); + BIO_free_all(bio); + free(buffer); + return 1; +} + +AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, uint64_t pe_len) +{ + const uint64_t dos_hdr_size = 0x40; + if (pe_len < dos_hdr_size) + return NULL; + + /* Check if it has DOS signature, so we don't parse random gibberish */ + uint8_t dos_prefix[] = {0x4d, 0x5a}; + if (memcmp(pe_data, dos_prefix, sizeof(dos_prefix)) != 0) + return NULL; + + /* offset to pointer in DOS header, that points to PE header */ + const int pe_hdr_ptr_offset = 0x3c; + /* Read the PE offset */ + uint32_t pe_offset = letoh32(*(uint32_t*)(pe_data + pe_hdr_ptr_offset)); + /* Offset to Magic, to know the PE class (32/64bit) */ + uint32_t magic_addr = pe_offset + 0x18; + + if (pe_len < magic_addr + sizeof(uint16_t)) + return NULL; + + /* Read the magic and check if we have 64bit PE */ + uint16_t magic = letoh16(*(uint16_t*)(pe_data + magic_addr)); + bool is_64bit = magic == 0x20b; + /* If PE is 64bit, header is 16 bytes larger */ + uint8_t pe64_extra = is_64bit ? 16 : 0; + + /* Calculate offset to certificate table directory */ + uint32_t pe_cert_table_addr = pe_offset + pe64_extra + 0x98; + + if (pe_len < pe_cert_table_addr + 2 * sizeof(uint32_t)) + return NULL; + + /* Use 64bit type due to the potential overflow in crafted binaries */ + uint64_t cert_addr = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr)); + uint64_t cert_len = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr + 4)); + + /* we need atleast 8 bytes to read dwLength, revision and certType */ + if (cert_len < 8 || pe_len < cert_addr + 8) + return NULL; + + uint32_t dwLength = letoh32(*(uint32_t*)(pe_data + cert_addr)); + if (pe_len < cert_addr + dwLength) + return NULL; + /* dwLength = offsetof(WIN_CERTIFICATE, bCertificate) + (size of the variable-length binary array contained within bCertificate) */ + AuthenticodeArray* auth_array = authenticode_new(pe_data + cert_addr + 0x8, dwLength - 0x8); + if (!auth_array) + return NULL; + + /* Compare valid signatures file digests to actual file digest, to complete verification */ + for (size_t i = 0; i < auth_array->count; ++i) { + Authenticode* sig = auth_array->signatures[i]; + + const EVP_MD* md = EVP_get_digestbyname(sig->digest_alg); + if (!md || !sig->digest.len || !sig->digest.data) { + /* If there is an verification error, keep the first error */ + if (sig->verify_flags == AUTHENTICODE_VFY_VALID) + sig->verify_flags = AUTHENTICODE_VFY_UNKNOWN_ALGORITHM; + + continue; + } + +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + int mdlen = EVP_MD_get_size(md); +#else + int mdlen = EVP_MD_size(md); +#endif + sig->file_digest.len = mdlen; + sig->file_digest.data = (uint8_t*)malloc(mdlen); + if (!sig->file_digest.data) + continue; + + if (authenticode_digest( + md, pe_data, pe_offset, is_64bit, cert_addr, sig->file_digest.data)) { + + /* If there is an verification error, keep the first error */ + if (sig->verify_flags == AUTHENTICODE_VFY_VALID) + sig->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + break; + } + + /* Complete the verification */ + if (memcmp(sig->file_digest.data, sig->digest.data, mdlen) != 0) + sig->verify_flags = AUTHENTICODE_VFY_WRONG_FILE_DIGEST; + } + + return auth_array; +} + +static void signer_free(Signer* si) +{ + if (si) { + free(si->digest.data); + free(si->digest_alg); + free(si->program_name); + certificate_array_free(si->chain); + free(si); + } +} + +static void authenticode_free(Authenticode* auth) +{ + if (auth) { + free(auth->digest.data); + free(auth->file_digest.data); + free(auth->digest_alg); + signer_free(auth->signer); + certificate_array_free(auth->certs); + countersignature_array_free(auth->countersigs); + free(auth); + } +} + +void authenticode_array_free(AuthenticodeArray* arr) +{ + if (arr) { + for (size_t i = 0; i < arr->count; ++i) { + authenticode_free(arr->signatures[i]); + } + free(arr->signatures); + free(arr); + } +} diff --git a/libyara/modules/pe/authenticode-parser/certificate.c b/libyara/modules/pe/authenticode-parser/certificate.c new file mode 100644 index 0000000000..e21f874070 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/certificate.c @@ -0,0 +1,420 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "certificate.h" + +#include +#include +#include +#include +#include +#include + +#include "helper.h" + +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL +/* Removes any escaping \/ -> / that is happening with oneline() functions + from OpenSSL 3.0 */ +static void parse_oneline_string(char* string) +{ + size_t len = strlen(string); + char* tmp = string; + while (true) { + char* ptr = strstr(tmp, "\\/"); + if (!ptr) + break; + + memmove(ptr, ptr + 1, strlen(ptr + 1)); + tmp = ptr + 1; + len--; + } + + string[len] = 0; +} +#endif + +static void parse_name_attributes(X509_NAME* raw, Attributes* attr) +{ + if (!raw || !attr) + return; + + int entryCount = X509_NAME_entry_count(raw); + for (int i = entryCount - 1; i >= 0; --i) { + X509_NAME_ENTRY* entryName = X509_NAME_get_entry(raw, i); + ASN1_STRING* asn1String = X509_NAME_ENTRY_get_data(entryName); + + const char* key = OBJ_nid2sn(OBJ_obj2nid(X509_NAME_ENTRY_get_object(entryName))); + + ByteArray array = {0}; + if (byte_array_init(&array, asn1String->data, asn1String->length) == -1) + break; + + if (strcmp(key, "C") == 0 && !attr->country.data) + attr->country = array; + else if (strcmp(key, "O") == 0 && !attr->organization.data) + attr->organization = array; + else if (strcmp(key, "OU") == 0 && !attr->organizationalUnit.data) + attr->organizationalUnit = array; + else if (strcmp(key, "dnQualifier") == 0 && !attr->nameQualifier.data) + attr->nameQualifier = array; + else if (strcmp(key, "ST") == 0 && !attr->state.data) + attr->state = array; + else if (strcmp(key, "CN") == 0 && !attr->commonName.data) + attr->commonName = array; + else if (strcmp(key, "serialNumber") == 0 && !attr->serialNumber.data) + attr->serialNumber = array; + else if (strcmp(key, "L") == 0 && !attr->locality.data) + attr->locality = array; + else if (strcmp(key, "title") == 0 && !attr->title.data) + attr->title = array; + else if (strcmp(key, "SN") == 0 && !attr->surname.data) + attr->surname = array; + else if (strcmp(key, "GN") == 0 && !attr->givenName.data) + attr->givenName = array; + else if (strcmp(key, "initials") == 0 && !attr->initials.data) + attr->initials = array; + else if (strcmp(key, "pseudonym") == 0 && !attr->pseudonym.data) + attr->pseudonym = array; + else if (strcmp(key, "generationQualifier") == 0 && !attr->generationQualifier.data) + attr->generationQualifier = array; + else if (strcmp(key, "emailAddress") == 0 && !attr->emailAddress.data) + attr->emailAddress = array; + else + free(array.data); + } +} + +/* Reconstructs signers certificate chain */ +CertificateArray* parse_signer_chain(X509* signCert, STACK_OF(X509) * certs) +{ + if (!signCert || !certs) + return NULL; + + X509_STORE* store = X509_STORE_new(); + if (!store) + return NULL; + + X509_STORE_CTX* storeCtx = X509_STORE_CTX_new(); + if (!storeCtx) { + X509_STORE_CTX_free(storeCtx); + return NULL; + } + + X509_STORE_CTX_init(storeCtx, store, signCert, certs); + + /* I can't find ability to use this function for static verification with missing trust anchors, + * because roots are generally not part of the PKCS7 signatures, so the return value is + * currently ignored and the function is only used to build the certificate chain */ + X509_verify_cert(storeCtx); + + STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(storeCtx); + + int certCount = sk_X509_num(chain); + + CertificateArray* result = (CertificateArray*)calloc(1, sizeof(*result)); + if (!result) + goto error; + + result->certs = (Certificate**)calloc(certCount, sizeof(Certificate*)); + if (!result->certs) + goto error; + + /* Convert each certificate to internal representation */ + for (int i = 0; i < certCount; ++i) { + Certificate* cert = certificate_new(sk_X509_value(chain, i)); + if (!cert) + goto error; + + result->certs[i] = cert; + result->count++; + } + + X509_STORE_free(store); + X509_STORE_CTX_free(storeCtx); + return result; + +error: /* In case of error, return nothing */ + if (result) { + for (size_t i = 0; i < result->count; ++i) { + certificate_free(result->certs[i]); + } + free(result->certs); + free(result); + } + X509_STORE_free(store); + X509_STORE_CTX_free(storeCtx); + + return NULL; +} + +/* Taken from YARA for compatibility */ +static char* integer_to_serial(ASN1_INTEGER* serial) +{ + int bytes = i2d_ASN1_INTEGER(serial, NULL); + + char* res = NULL; + /* According to X.509 specification the maximum length for the + * serial number is 20 octets. Add two bytes to account for + * DER type and length information. */ + if (bytes < 2 || bytes > 22) + return NULL; + + /* Now that we know the size of the serial number allocate enough + * space to hold it, and use i2d_ASN1_INTEGER() one last time to + * hold it in the allocated buffer. */ + uint8_t* serial_der = (uint8_t*)malloc(bytes); + if (!serial_der) + return NULL; + + uint8_t* serial_bytes; + + bytes = i2d_ASN1_INTEGER(serial, &serial_der); + + /* i2d_ASN1_INTEGER() moves the pointer as it writes into + serial_bytes. Move it back. */ + serial_der -= bytes; + + /* Skip over DER type, length information */ + serial_bytes = serial_der + 2; + bytes -= 2; + + /* Also allocate space to hold the "common" string format: + * 00:01:02:03:04... + * + * For each byte in the serial to convert to hexlified format we + * need three bytes, two for the byte itself and one for colon. + * The last one doesn't have the colon, but the extra byte is used + * for the NULL terminator. */ + res = (char*)malloc(bytes * 3); + if (res) { + for (int i = 0; i < bytes; i++) { + /* Don't put the colon on the last one. */ + if (i < bytes - 1) + snprintf(res + 3 * i, 4, "%02x:", serial_bytes[i]); + else + snprintf(res + 3 * i, 3, "%02x", serial_bytes[i]); + } + } + free(serial_der); + + return (char*)res; +} + +/* Converts the pubkey to pem, which is just + * Base64 encoding of the DER representation */ +static char* pubkey_to_pem(EVP_PKEY* pubkey) +{ + uint8_t* der = NULL; + int len = i2d_PUBKEY(pubkey, &der); /* Convert to DER */ + if (len <= 0) + return NULL; + + /* Approximate the result length (padding, newlines, 4 out bytes for every 3 in) */ + uint8_t* result = (uint8_t*)malloc(len * 3 / 2); + if (!result) { + OPENSSL_free(der); + return NULL; + } + + /* Base64 encode the DER data */ + EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); + if (!ctx) { + OPENSSL_free(der); + free(result); + return NULL; + } + + int resultLen = 0; + int tmp = 0; + EVP_EncodeInit(ctx); + EVP_EncodeUpdate(ctx, result, &tmp, der, len); + resultLen += tmp; + EVP_EncodeFinal(ctx, result + resultLen, &tmp); + resultLen += tmp; + + EVP_ENCODE_CTX_free(ctx); + OPENSSL_free(der); + + /* Remove all newlines from the encoded base64 + * resultLen is excluding NULL terminator */ + for (int i = 0; result[i] != 0; i++) { + if (result[i] == '\n') + memmove(result + i, result + i + 1, resultLen - i); + } + + return (char*)result; +} + +Certificate* certificate_new(X509* x509) +{ + Certificate* result = (Certificate*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + /* Calculate SHA1 and SHA256 digests of the X509 structure */ + result->sha1.data = (uint8_t*)malloc(SHA_DIGEST_LENGTH); + if (result->sha1.data) { + X509_digest(x509, EVP_sha1(), result->sha1.data, NULL); + result->sha1.len = SHA_DIGEST_LENGTH; + } + + result->sha256.data = (uint8_t*)malloc(SHA256_DIGEST_LENGTH); + if (result->sha256.data) { + X509_digest(x509, EVP_sha256(), result->sha256.data, NULL); + result->sha256.len = SHA256_DIGEST_LENGTH; + } + + /* 256 bytes should be enough for any name */ + char buffer[256]; + + /* X509_NAME_online is deprecated and shouldn't be used per OpenSSL docs + * but we want to comply with existing YARA code */ + X509_NAME* issuerName = X509_get_issuer_name(x509); + X509_NAME_oneline(issuerName, buffer, sizeof(buffer)); + + result->issuer = strdup(buffer); + /* This is a little ugly hack for 3.0 compatibility */ +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + parse_oneline_string(result->issuer); +#endif + + X509_NAME* subjectName = X509_get_subject_name(x509); + X509_NAME_oneline(subjectName, buffer, sizeof(buffer)); + result->subject = strdup(buffer); +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + parse_oneline_string(result->subject); +#endif + + parse_name_attributes(issuerName, &result->issuer_attrs); + parse_name_attributes(subjectName, &result->subject_attrs); + + result->version = X509_get_version(x509); + result->serial = integer_to_serial(X509_get_serialNumber(x509)); + result->not_after = ASN1_TIME_to_time_t(X509_get0_notAfter(x509)); + result->not_before = ASN1_TIME_to_time_t(X509_get0_notBefore(x509)); + int sig_nid = X509_get_signature_nid(x509); + result->sig_alg = strdup(OBJ_nid2ln(sig_nid)); + + OBJ_obj2txt(buffer, sizeof(buffer), OBJ_nid2obj(sig_nid), 1); + result->sig_alg_oid = strdup(buffer); + + EVP_PKEY* pkey = X509_get0_pubkey(x509); + if (pkey) { + result->key = pubkey_to_pem(pkey); +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + result->key_alg = strdup(OBJ_nid2sn(EVP_PKEY_get_base_id(pkey))); +#else + result->key_alg = strdup(OBJ_nid2sn(EVP_PKEY_base_id(pkey))); +#endif + } + + return result; +} + +/* Moves certificates from src to dst, returns 0 on success, + * else 1. If error occurs, arguments are unchanged */ +int certificate_array_move(CertificateArray* dst, CertificateArray* src) +{ + size_t newCount = dst->count + src->count; + + Certificate** tmp = (Certificate**)realloc(dst->certs, newCount * sizeof(Certificate*)); + if (!tmp) + return 1; + + dst->certs = tmp; + + for (size_t i = 0; i < src->count; ++i) + dst->certs[i + dst->count] = src->certs[i]; + + dst->count = newCount; + + free(src->certs); + src->certs = NULL; + src->count = 0; + + return 0; +} + +/* Allocates empty certificate array with reserved space for certCount certs */ +CertificateArray* certificate_array_new(int certCount) +{ + CertificateArray* arr = (CertificateArray*)malloc(sizeof(*arr)); + if (!arr) + return NULL; + + arr->certs = (Certificate**)malloc(sizeof(Certificate*) * certCount); + if (!arr->certs) { + free(arr); + return NULL; + } + + arr->count = certCount; + + return arr; +} + +static void certificate_attributes_free(Attributes attrs) +{ + free(attrs.country.data); + free(attrs.organization.data); + free(attrs.organizationalUnit.data); + free(attrs.nameQualifier.data); + free(attrs.state.data); + free(attrs.commonName.data); + free(attrs.serialNumber.data); + free(attrs.locality.data); + free(attrs.title.data); + free(attrs.surname.data); + free(attrs.givenName.data); + free(attrs.initials.data); + free(attrs.pseudonym.data); + free(attrs.generationQualifier.data); + free(attrs.emailAddress.data); +} + +void certificate_free(Certificate* cert) +{ + if (cert) { + free(cert->issuer); + free(cert->subject); + free(cert->sig_alg); + free(cert->sig_alg_oid); + free(cert->key_alg); + free(cert->key); + free(cert->sha1.data); + free(cert->sha256.data); + free(cert->serial); + certificate_attributes_free(cert->issuer_attrs); + certificate_attributes_free(cert->subject_attrs); + free(cert); + } +} + +void certificate_array_free(CertificateArray* arr) +{ + if (arr) { + for (size_t i = 0; i < arr->count; ++i) { + certificate_free(arr->certs[i]); + } + free(arr->certs); + free(arr); + } +} diff --git a/libyara/modules/pe/authenticode-parser/certificate.h b/libyara/modules/pe/authenticode-parser/certificate.h new file mode 100644 index 0000000000..aefb797c6a --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/certificate.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_CERTIFICATE_H +#define AUTHENTICODE_PARSER_CERTIFICATE_H + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Certificate* certificate_new(X509* x509); +void certificate_free(Certificate* cert); + +CertificateArray* parse_signer_chain(X509* signer_cert, STACK_OF(X509) * certs); +int certificate_array_move(CertificateArray* dst, CertificateArray* src); +CertificateArray* certificate_array_new(int certCount); +void certificate_array_free(CertificateArray* arr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libyara/modules/pe/authenticode-parser/countersignature.c b/libyara/modules/pe/authenticode-parser/countersignature.c new file mode 100644 index 0000000000..d905e239b0 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/countersignature.c @@ -0,0 +1,367 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "countersignature.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "certificate.h" +#include "helper.h" +#include "structs.h" + +Countersignature* pkcs9_countersig_new( + const uint8_t* data, long size, STACK_OF(X509) * certs, ASN1_STRING* enc_digest) +{ + Countersignature* result = (Countersignature*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + PKCS7_SIGNER_INFO* si = d2i_PKCS7_SIGNER_INFO(NULL, &data, size); + if (!si) { + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; + return result; + } + + int digestnid = OBJ_obj2nid(si->digest_alg->algorithm); + result->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + const ASN1_TYPE* sign_time = PKCS7_get_signed_attribute(si, NID_pkcs9_signingTime); + if (!sign_time) { + result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; + goto end; + } + + result->sign_time = ASN1_TIME_to_time_t(sign_time->value.utctime); + + X509* signCert = X509_find_by_issuer_and_serial( + certs, si->issuer_and_serial->issuer, si->issuer_and_serial->serial); + if (!signCert) { + result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; + goto end; + } + + /* PKCS9 stores certificates in the corresponding PKCS7 it countersigns */ + result->chain = parse_signer_chain(signCert, certs); + + /* Get digest that corresponds to decrypted encrypted digest in signature */ + ASN1_TYPE* messageDigest = PKCS7_get_signed_attribute(si, NID_pkcs9_messageDigest); + if (!messageDigest) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + size_t digestLen = messageDigest->value.octet_string->length; + + if (!digestLen) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + const EVP_MD* md = EVP_get_digestbynid(digestnid); + if (!md) { + result->verify_flags = COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM; + goto end; + } + + const uint8_t* digestData = messageDigest->value.octet_string->data; + byte_array_init(&result->digest, digestData, digestLen); + + /* By this point we all necessary things for verification + * Get DER representation of the authenticated attributes to calculate its + * digest that should correspond with the one encrypted in SignerInfo */ + uint8_t* authAttrsData = NULL; + int authAttrsLen = ASN1_item_i2d( + (ASN1_VALUE*)si->auth_attr, &authAttrsData, ASN1_ITEM_rptr(PKCS7_ATTR_VERIFY)); + + uint8_t calc_digest[EVP_MAX_MD_SIZE]; + calculate_digest(md, authAttrsData, authAttrsLen, calc_digest); + OPENSSL_free(authAttrsData); + + /* Get public key to decrypt encrypted digest of auth attrs */ + EVP_PKEY* pkey = X509_get0_pubkey(signCert); + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + + /* TODO try to get rid of hardcoded length bound */ + size_t decLen = 65536; + uint8_t* decData = (uint8_t*)malloc(decLen); + if (!decData) { + EVP_PKEY_CTX_free(ctx); + result->verify_flags = COUNTERSIGNATURE_VFY_INTERNAL_ERROR; + goto end; + } + + uint8_t* encData = si->enc_digest->data; + size_t encLen = si->enc_digest->length; + + /* Decrypt the encrypted digest */ + EVP_PKEY_verify_recover_init(ctx); + bool isDecrypted = EVP_PKEY_verify_recover(ctx, decData, &decLen, encData, encLen) == 1; + EVP_PKEY_CTX_free(ctx); + + if (!isDecrypted) { + free(decData); + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_DECRYPT_DIGEST; + goto end; + } + + /* compare the encrypted digest and calculated digest */ + bool isValid = false; + +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + size_t mdLen = EVP_MD_get_size(md); +#else + size_t mdLen = EVP_MD_size(md); +#endif + /* Sometimes signed data contains DER encoded DigestInfo structure which contains hash of + * authenticated attributes (39c9d136f026a9ad18fb9f41a64f76dd8418e8de625dce5d3a372bd242fc5edd) + * but other times it is just purely and I didn't find another way to distinguish it but only + * based on the length of data we get. Found mention of this in openssl mailing list: + * https://mta.openssl.org/pipermail/openssl-users/2015-September/002054.html */ + if (mdLen == decLen) { + isValid = !memcmp(calc_digest, decData, mdLen); + } else { + const uint8_t* data_ptr = decData; + DigestInfo* digest_info = d2i_DigestInfo(NULL, &data_ptr, decLen); + if (digest_info) { + isValid = !memcmp(digest_info->digest->data, calc_digest, mdLen); + DigestInfo_free(digest_info); + } else { + isValid = false; + } + } + free(decData); + + if (!isValid) { + result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; + goto end; + } + + /* Now check the countersignature message-digest that should correspond + * to Signatures encrypted digest it countersigns */ + calculate_digest(md, enc_digest->data, enc_digest->length, calc_digest); + + /* Check if calculated one matches the stored one */ + if (digestLen != mdLen || memcmp(calc_digest, digestData, mdLen) != 0) { + result->verify_flags = COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE; + goto end; + } + +end: + PKCS7_SIGNER_INFO_free(si); + return result; +} + +Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* enc_digest) +{ + Countersignature* result = (Countersignature*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + PKCS7* p7 = d2i_PKCS7(NULL, &data, size); + if (!p7) { + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; + return result; + } + + TS_TST_INFO* ts = PKCS7_to_TS_TST_INFO(p7); + if (!ts) { + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; + PKCS7_free(p7); + return result; + } + + const ASN1_TIME* rawTime = TS_TST_INFO_get_time(ts); + if (!rawTime) { + result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; + TS_TST_INFO_free(ts); + PKCS7_free(p7); + return result; + } + + result->sign_time = ASN1_TIME_to_time_t(rawTime); + + STACK_OF(X509)* sigs = PKCS7_get0_signers(p7, p7->d.sign->cert, 0); + X509* signCert = sk_X509_value(sigs, 0); + if (!signCert) { + result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; + goto end; + } + + result->chain = parse_signer_chain(signCert, p7->d.sign->cert); + + /* Imprint == digest */ + TS_MSG_IMPRINT* imprint = TS_TST_INFO_get_msg_imprint(ts); + if (!imprint) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + X509_ALGOR* digestAlg = TS_MSG_IMPRINT_get_algo(imprint); + int digestnid = OBJ_obj2nid(digestAlg->algorithm); + result->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + ASN1_STRING* rawDigest = TS_MSG_IMPRINT_get_msg(imprint); + + int digestLen = rawDigest->length; + uint8_t* digestData = rawDigest->data; + + byte_array_init(&result->digest, digestData, digestLen); + + if (!digestLen) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + const EVP_MD* md = EVP_get_digestbynid(digestnid); + if (!md) { + result->verify_flags = COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM; + goto end; + } + + uint8_t calc_digest[EVP_MAX_MD_SIZE]; + calculate_digest(md, enc_digest->data, enc_digest->length, calc_digest); + +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + int mdLen = EVP_MD_get_size(md); +#else + int mdLen = EVP_MD_size(md); +#endif + + if (digestLen != mdLen || memcmp(calc_digest, digestData, mdLen) != 0) { + result->verify_flags = COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE; + goto end; + } + + TS_VERIFY_CTX* ctx = TS_VERIFY_CTX_new(); + X509_STORE* store = X509_STORE_new(); + TS_VERIFY_CTX_init(ctx); + + TS_VERIFY_CTX_set_flags(ctx, TS_VFY_VERSION | TS_VFY_IMPRINT); + TS_VERIFY_CTX_set_store(ctx, store); +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + TS_VERIFY_CTX_set_certs(ctx, p7->d.sign->cert); +#else + TS_VERIFY_CTS_set_certs(ctx, p7->d.sign->cert); +#endif + TS_VERIFY_CTX_set_imprint(ctx, calc_digest, mdLen); + + bool isValid = TS_RESP_verify_token(ctx, p7) == 1; + + X509_STORE_free(store); + OPENSSL_free(ctx); + + if (!isValid) { + result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; + goto end; + } + + /* Verify signature with PKCS7_signatureVerify + because TS_RESP_verify_token would try to verify + chain and without trust anchors it always fails */ + BIO* p7bio = PKCS7_dataInit(p7, NULL); + + char buf[4096]; + /* We now have to 'read' from p7bio to calculate digests etc. */ + while (BIO_read(p7bio, buf, sizeof(buf)) > 0) + continue; + + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + + isValid = PKCS7_signatureVerify(p7bio, p7, si, signCert) == 1; + + BIO_free_all(p7bio); + + if (!isValid) + result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; + +end: + sk_X509_free(sigs); + PKCS7_free(p7); + TS_TST_INFO_free(ts); + return result; +} + +int countersignature_array_insert(CountersignatureArray* arr, Countersignature* sig) +{ + Countersignature** tmp = + (Countersignature**)realloc(arr->counters, (arr->count + 1) * sizeof(Countersignature*)); + if (!tmp) + return 1; + + arr->counters = tmp; + arr->counters[arr->count] = sig; + arr->count++; + + return 0; +} + +int countersignature_array_move(CountersignatureArray* dst, CountersignatureArray* src) +{ + size_t newCount = dst->count + src->count; + + Countersignature** tmp = + (Countersignature**)realloc(dst->counters, newCount * sizeof(Countersignature*)); + if (!tmp) + return 1; + + dst->counters = tmp; + + for (size_t i = 0; i < src->count; ++i) + dst->counters[i + dst->count] = src->counters[i]; + + dst->count = newCount; + + free(src->counters); + src->counters = NULL; + src->count = 0; + + return 0; +} + +void countersignature_free(Countersignature* sig) +{ + if (sig) { + free(sig->digest_alg); + free(sig->digest.data); + certificate_array_free(sig->chain); + free(sig); + } +} + +void countersignature_array_free(CountersignatureArray* arr) +{ + if (arr) { + for (size_t i = 0; i < arr->count; ++i) { + countersignature_free(arr->counters[i]); + } + free(arr->counters); + free(arr); + } +} diff --git a/libyara/modules/pe/authenticode-parser/countersignature.h b/libyara/modules/pe/authenticode-parser/countersignature.h new file mode 100644 index 0000000000..294ffed738 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/countersignature.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_COUNTERSIGNATURE_H +#define AUTHENTICODE_PARSER_COUNTERSIGNATURE_H + +#include "certificate.h" +#include "helper.h" +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Countersignature* pkcs9_countersig_new( + const uint8_t* data, long size, STACK_OF(X509) * certs, ASN1_STRING* enc_digest); +Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* enc_digest); + +int countersignature_array_insert(CountersignatureArray* arr, Countersignature* sig); +/* Moves all countersignatures of src and inserts them into dst */ +int countersignature_array_move(CountersignatureArray* dst, CountersignatureArray* src); + +void countersignature_free(Countersignature* sig); +void countersignature_array_free(CountersignatureArray* arr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libyara/modules/pe/authenticode-parser/helper.c b/libyara/modules/pe/authenticode-parser/helper.c new file mode 100644 index 0000000000..f65a391794 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/helper.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include + +uint16_t bswap16(uint16_t d) +{ + return (d << 8) | (d >> 8); +} + +uint32_t bswap32(uint32_t d) +{ + return (((d)&0xff000000) >> 24) | (((d)&0x00ff0000) >> 8) | (((d)&0x0000ff00) << 8) | + (((d)&0x000000ff) << 24); +} + +int calculate_digest(const EVP_MD* md, const uint8_t* data, size_t len, uint8_t* digest) +{ + unsigned int outLen = 0; + + EVP_MD_CTX* mdCtx = EVP_MD_CTX_new(); + if (!mdCtx) + goto end; + + if (!EVP_DigestInit_ex(mdCtx, md, NULL) || !EVP_DigestUpdate(mdCtx, data, len) || + !EVP_DigestFinal_ex(mdCtx, digest, &outLen)) + goto end; + +end: + EVP_MD_CTX_free(mdCtx); + return (int)outLen; +} + +int byte_array_init(ByteArray* arr, const uint8_t* data, int len) +{ + if (len == 0) { + arr->data = NULL; + arr->len = 0; + return 0; + } + + arr->data = (uint8_t*)malloc(len); + if (!arr->data) + return -1; + + arr->len = len; + memcpy(arr->data, data, len); + return 0; +} + +time_t ASN1_TIME_to_time_t(const ASN1_TIME* time) +{ + struct tm t = {0}; + if (!time) + return timegm(&t); + + ASN1_TIME_to_tm(time, &t); + return timegm(&t); +} diff --git a/libyara/modules/pe/authenticode-parser/helper.h b/libyara/modules/pe/authenticode-parser/helper.h new file mode 100644 index 0000000000..e435cbc51e --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/helper.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_HELPER_H +#define AUTHENTICODE_PARSER_HELPER_H + +#include +#include +#include +#include + +#include + +#ifdef _WIN32 +#define timegm _mkgmtime +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Endianity related functions for PE reading */ +uint16_t bswap16(uint16_t d); +uint32_t bswap32(uint32_t d); + +#if defined(WORDS_BIGENDIAN) +#define letoh16(x) bswap16(x) +#define letoh32(x) bswap32(x) +#define betoh16(x) (x) +#define betoh32(x) (x) +#else +#define letoh16(x) (x) +#define letoh32(x) (x) +#define betoh16(x) bswap16(x) +#define betoh32(x) bswap32(x) +#endif + +/* Calculates digest md of data, return bytes written to digest or 0 on error + * Maximum of EVP_MAX_MD_SIZE will be written to digest */ +int calculate_digest(const EVP_MD* md, const uint8_t* data, size_t len, uint8_t* digest); +/* Copies data of length len into already existing arr */ +int byte_array_init(ByteArray* arr, const uint8_t* data, int len); +/* Converts ASN1_TIME string time into a unix timestamp */ +time_t ASN1_TIME_to_time_t(const ASN1_TIME* time); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libyara/modules/pe/authenticode-parser/structs.c b/libyara/modules/pe/authenticode-parser/structs.c new file mode 100644 index 0000000000..4eee46b4d7 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/structs.c @@ -0,0 +1,78 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "structs.h" + +ASN1_CHOICE(SpcString) = { + ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0), + ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1) +} ASN1_CHOICE_END(SpcString) + +ASN1_SEQUENCE(SpcSerializedObject) = { + ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING), + ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(SpcSerializedObject) + +ASN1_CHOICE(SpcLink) = { + ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0), + ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1), + ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2) +} ASN1_CHOICE_END(SpcLink) + +ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = { + ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT), + ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY) +} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue) + +ASN1_SEQUENCE(SpcPeImageData) = { + ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING), + ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0) +} ASN1_SEQUENCE_END(SpcPeImageData) + +ASN1_SEQUENCE(AlgorithmIdentifier) = { + ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT), + ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY) +} ASN1_SEQUENCE_END(AlgorithmIdentifier) + +ASN1_SEQUENCE(DigestInfo) = { + ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier), + ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(DigestInfo) + +ASN1_SEQUENCE(SpcIndirectDataContent) = { + ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue), + ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo) +} ASN1_SEQUENCE_END(SpcIndirectDataContent) + +ASN1_SEQUENCE(SpcSpOpusInfo) = { + ASN1_EXP_OPT(SpcSpOpusInfo, programName, SpcString, 0), + ASN1_EXP_OPT(SpcSpOpusInfo, moreInfo, SpcLink, 1) +} ASN1_SEQUENCE_END(SpcSpOpusInfo) + +IMPLEMENT_ASN1_FUNCTIONS(SpcString) +IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject) +IMPLEMENT_ASN1_FUNCTIONS(SpcLink) +IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue) +IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData) +IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier) +IMPLEMENT_ASN1_FUNCTIONS(DigestInfo) +IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent) +IMPLEMENT_ASN1_FUNCTIONS(SpcSpOpusInfo) diff --git a/libyara/modules/pe/authenticode-parser/structs.h b/libyara/modules/pe/authenticode-parser/structs.h new file mode 100644 index 0000000000..1f90db69b8 --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/structs.h @@ -0,0 +1,111 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_STRUCTS_H +#define AUTHENTICODE_PARSER_STRUCTS_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NID_spc_info "1.3.6.1.4.1.311.2.1.12" +#define NID_spc_ms_countersignature "1.3.6.1.4.1.311.3.3.1" +#define NID_spc_nested_signature "1.3.6.1.4.1.311.2.4.1" +#define NID_spc_indirect_data "1.3.6.1.4.1.311.2.1.4" + +typedef struct { + int type; + union { + ASN1_BMPSTRING *unicode; + ASN1_IA5STRING *ascii; + } value; +} SpcString; + +typedef struct { + ASN1_OCTET_STRING *classId; + ASN1_OCTET_STRING *serializedData; +} SpcSerializedObject; + +typedef struct { + int type; + union { + ASN1_IA5STRING *url; + SpcSerializedObject *moniker; + SpcString *file; + } value; +} SpcLink; + +typedef struct { + ASN1_OBJECT *type; + ASN1_TYPE *value; +} SpcAttributeTypeAndOptionalValue; + +typedef struct { + ASN1_BIT_STRING *flags; + SpcLink *file; +} SpcPeImageData; + +typedef struct { + ASN1_OBJECT *algorithm; + ASN1_TYPE *parameters; +} AlgorithmIdentifier; + +typedef struct { + AlgorithmIdentifier *digestAlgorithm; + ASN1_OCTET_STRING *digest; +} DigestInfo; + +typedef struct { + SpcAttributeTypeAndOptionalValue *data; + DigestInfo *messageDigest; +} SpcIndirectDataContent; + +typedef struct { + ASN1_OBJECT *contentType; + SpcIndirectDataContent *content; +} SpcContentInfo; + +typedef struct { + SpcString *programName; + SpcLink *moreInfo; +} SpcSpOpusInfo; + +DECLARE_ASN1_FUNCTIONS(SpcString) +DECLARE_ASN1_FUNCTIONS(SpcSerializedObject) +DECLARE_ASN1_FUNCTIONS(SpcLink) +DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue) +DECLARE_ASN1_FUNCTIONS(SpcPeImageData) +DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier) +DECLARE_ASN1_FUNCTIONS(DigestInfo) +DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent) +DECLARE_ASN1_FUNCTIONS(SpcSpOpusInfo) +DECLARE_ASN1_FUNCTIONS(SpcContentInfo) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libyara/modules/pe/pe.c b/libyara/modules/pe/pe.c index 980bebcda5..47dac3651f 100644 --- a/libyara/modules/pe/pe.c +++ b/libyara/modules/pe/pe.c @@ -33,20 +33,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../crypto.h" #if defined(HAVE_LIBCRYPTO) -#include -#include -#include -#include -#include - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define X509_get_signature_nid(o) OBJ_obj2nid((o)->sig_alg->algorithm) -#endif - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define X509_get0_notBefore X509_get_notBefore -#define X509_get0_notAfter X509_get_notAfter -#endif +#include +#include #endif #include @@ -1611,207 +1599,232 @@ static void pe_parse_exports(PE* pe) // but you won't have signature-related features in the PE module. #if defined(HAVE_LIBCRYPTO) && !defined(BORINGSSL) -// -// Parse a PKCS7 blob, looking for certs and nested PKCS7 blobs. -// - -void _parse_pkcs7(PE* pe, PKCS7* pkcs7, int* counter) +#define write_certificate(cert, pe, fmt, ...) \ + do \ + { \ + char thumbprint_ascii[YR_SHA1_LEN * 2 + 1]; \ + for (int j = 0; j < cert->sha1.len; ++j) \ + sprintf(thumbprint_ascii + (j * 2), "%02x", cert->sha1.data[j]); \ + \ + set_string( \ + (char*) thumbprint_ascii, pe->object, fmt ".thumbprint", __VA_ARGS__); \ + \ + set_string(cert->issuer, pe->object, fmt ".issuer", __VA_ARGS__); \ + set_string(cert->subject, pe->object, fmt ".subject", __VA_ARGS__); \ + /* Versions are zero based, so add one. */ \ + set_integer(cert->version + 1, pe->object, fmt ".version", __VA_ARGS__); \ + set_string(cert->sig_alg, pe->object, fmt ".algorithm", __VA_ARGS__); \ + set_string( \ + cert->sig_alg_oid, pe->object, fmt ".algorithm_oid", __VA_ARGS__); \ + set_string(cert->serial, pe->object, fmt ".serial", __VA_ARGS__); \ + set_integer(cert->not_before, pe->object, fmt ".not_before", __VA_ARGS__); \ + set_integer(cert->not_after, pe->object, fmt ".not_after", __VA_ARGS__); \ + } while (0) + +void _process_authenticode( + PE* pe, + AuthenticodeArray* auth_array, + int* sig_count) { - int i, j; - time_t date_time; - int sig_nid; - char buffer[256]; - int bytes; - int idx; - const EVP_MD* sha1_digest = EVP_sha1(); - const unsigned char* p; - unsigned char thumbprint[YR_SHA1_LEN]; - char thumbprint_ascii[YR_SHA1_LEN * 2 + 1]; - - PKCS7_SIGNER_INFO* signer_info = NULL; - PKCS7* nested_pkcs7 = NULL; - ASN1_INTEGER* serial = NULL; - ASN1_TYPE* nested = NULL; - ASN1_STRING* value = NULL; - X509* cert = NULL; - STACK_OF(X509)* certs = NULL; - X509_ATTRIBUTE* xa = NULL; - STACK_OF(X509_ATTRIBUTE)* attrs = NULL; - - if (*counter >= MAX_PE_CERTS) + if (!auth_array || !auth_array->count) return; - certs = PKCS7_get0_signers(pkcs7, NULL, 0); - - if (!certs) - return; + /* If any signature will be valid -> file is correctly signed */ + bool signature_valid = false; - for (i = 0; i < sk_X509_num(certs) && *counter < MAX_PE_CERTS; i++) + for (size_t i = 0; i < auth_array->count; ++i) { - cert = sk_X509_value(certs, i); + const Authenticode* authenticode = auth_array->signatures[i]; - X509_digest(cert, sha1_digest, thumbprint, NULL); + signature_valid = authenticode->verify_flags == AUTHENTICODE_VFY_VALID + ? true + : false; - for (j = 0; j < YR_SHA1_LEN; j++) - sprintf(thumbprint_ascii + (j * 2), "%02x", thumbprint[j]); + set_integer( + signature_valid, pe->object, "signatures[%i].verified", *sig_count); set_string( - (char*) thumbprint_ascii, + authenticode->digest_alg, pe->object, - "signatures[%i].thumbprint", - *counter); - - X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer)); - - set_string(buffer, pe->object, "signatures[%i].issuer", *counter); + "signatures[%i].digest_alg", + *sig_count); - X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)); + if (authenticode->digest.data) + { + char* digest_ascii = yr_malloc(authenticode->digest.len * 2 + 1); + for (int j = 0; j < authenticode->digest.len; ++j) + sprintf(digest_ascii + (j * 2), "%02x", authenticode->digest.data[j]); - set_string(buffer, pe->object, "signatures[%i].subject", *counter); + set_string(digest_ascii, pe->object, "signatures[%i].digest", *sig_count); + yr_free(digest_ascii); + } - set_integer( - X509_get_version(cert) + 1, // Versions are zero based, so add one. - pe->object, - "signatures[%i].version", - *counter); + if (authenticode->file_digest.data) + { + char* digest_ascii = yr_malloc(authenticode->file_digest.len * 2 + 1); + for (int j = 0; j < authenticode->file_digest.len; ++j) + sprintf( + digest_ascii + (j * 2), "%02x", authenticode->file_digest.data[j]); + + set_string( + digest_ascii, pe->object, "signatures[%i].file_digest", *sig_count); + yr_free(digest_ascii); + } - sig_nid = X509_get_signature_nid(cert); - set_string( - OBJ_nid2ln(sig_nid), pe->object, "signatures[%i].algorithm", *counter); - OBJ_obj2txt(buffer, sizeof(buffer), OBJ_nid2obj(sig_nid), 1); - set_string(buffer, pe->object, "signatures[%i].algorithm_oid", *counter); + if (authenticode->certs) + { + set_integer( + authenticode->certs->count, + pe->object, + "signatures[%i].number_of_certificates", + *sig_count); - serial = X509_get_serialNumber(cert); + for (int k = 0; k < authenticode->certs->count; ++k) + { + write_certificate( + authenticode->certs->certs[k], + pe, + "signatures[%i].certificates[%i]", + *sig_count, + k); + } + } - if (serial) + const Signer* signer = authenticode->signer; + if (signer) { - // ASN1_INTEGER can be negative (serial->type & V_ASN1_NEG_INTEGER), - // in which case the serial number will be stored in 2's complement. - // - // Handle negative serial numbers, which are technically not allowed - // by RFC5280, but do exist. An example binary which has a negative - // serial number is: 4bfe05f182aa273e113db6ed7dae4bb8. - // - // Negative serial numbers are handled by calling i2d_ASN1_INTEGER() - // with a NULL second parameter. This will return the size of the - // buffer necessary to store the proper serial number. - // - // Do this even for positive serial numbers because it makes the code - // cleaner and easier to read. - - bytes = i2d_ASN1_INTEGER(serial, NULL); - - // According to X.509 specification the maximum length for the - // serial number is 20 octets. Add two bytes to account for - // DER type and length information. - - if (bytes > 2 && bytes <= 22) + /* For compatibility with previous YARA rules, write information + * about signing certificate in the same way */ + if (signer->chain && signer->chain->count >= 1) { - // Now that we know the size of the serial number allocate enough - // space to hold it, and use i2d_ASN1_INTEGER() one last time to - // hold it in the allocated buffer. - - unsigned char* serial_der = (unsigned char*) yr_malloc(bytes); + const Certificate* sign_cert = signer->chain->certs[0]; + write_certificate(sign_cert, pe, "signatures[%i]", *sig_count); + } - if (serial_der != NULL) - { - unsigned char* serial_bytes; - char* serial_ascii; + set_string( + signer->program_name, + pe->object, + "signatures[%i].signer_info.program_name", + *sig_count); + set_string( + signer->digest_alg, + pe->object, + "signatures[%i].signer_info.digest_alg", + *sig_count); - bytes = i2d_ASN1_INTEGER(serial, &serial_der); + if (signer->digest.data) + { + char* digest_ascii = yr_malloc(signer->digest.len * 2 + 1); + for (int j = 0; j < signer->digest.len; ++j) + sprintf(digest_ascii + (j * 2), "%02x", signer->digest.data[j]); + + set_string( + digest_ascii, + pe->object, + "signatures[%i].signer_info.digest", + *sig_count); + yr_free(digest_ascii); + } - // i2d_ASN1_INTEGER() moves the pointer as it writes into - // serial_bytes. Move it back. + if (signer->chain) + { + set_integer( + signer->chain->count, + pe->object, + "signatures[%i].signer_info.length_of_chain", + *sig_count); - serial_der -= bytes; + for (int k = 0; k < signer->chain->count; ++k) + { + write_certificate( + signer->chain->certs[k], + pe, + "signatures[%i].signer_info.chain[%i]", + *sig_count, + k); + } + } + } + if (authenticode->countersigs) + { + set_integer( + authenticode->countersigs->count, + pe->object, + "signatures[%i].number_of_countersignatures", + *sig_count); - // Skip over DER type, length information - serial_bytes = serial_der + 2; - bytes -= 2; + for (int j = 0; j < authenticode->countersigs->count; ++j) + { + const Countersignature* counter = + authenticode->countersigs->counters[j]; + + set_integer( + counter->verify_flags == COUNTERSIGNATURE_VFY_VALID, + pe->object, + "signatures[%i].countersignatures[%i].verified", + *sig_count, + j); + set_string( + counter->digest_alg, + pe->object, + "signatures[%i].countersignatures[%i].digest_alg", + *sig_count, + j); + set_integer( + counter->sign_time, + pe->object, + "signatures[%i].countersignatures[%i].sign_time", + *sig_count, + j); + + if (counter->digest.data) + { + char* digest_ascii = yr_malloc(counter->digest.len * 2 + 1); + for (int j = 0; j < counter->digest.len; ++j) + sprintf(digest_ascii + (j * 2), "%02x", counter->digest.data[j]); - // Also allocate space to hold the "common" string format: - // 00:01:02:03:04... - // - // For each byte in the serial to convert to hexlified format we - // need three bytes, two for the byte itself and one for colon. - // The last one doesn't have the colon, but the extra byte is used - // for the NULL terminator. + set_string( + digest_ascii, + pe->object, + "signatures[%i].countersignatures[%i].digest", + *sig_count, + j); + yr_free(digest_ascii); + } - serial_ascii = (char*) yr_malloc(bytes * 3); + if (counter->chain) + { + set_integer( + counter->chain->count, + pe->object, + "signatures[%i].countersignatures[%i].length_of_chain", + *sig_count, + j); - if (serial_ascii) + for (int k = 0; k < counter->chain->count; ++k) { - for (j = 0; j < bytes; j++) - { - // Don't put the colon on the last one. - if (j < bytes - 1) - snprintf(serial_ascii + 3 * j, 4, "%02x:", serial_bytes[j]); - else - snprintf(serial_ascii + 3 * j, 3, "%02x", serial_bytes[j]); - } - - set_string( - serial_ascii, pe->object, "signatures[%i].serial", *counter); - - yr_free(serial_ascii); + write_certificate( + counter->chain->certs[k], + pe, + "signatures[%i].countersignatures[%i].chain[%i]", + *sig_count, + j, + k); } - - yr_free(serial_der); } } } - date_time = ASN1_get_time_t(X509_get0_notBefore(cert)); - set_integer(date_time, pe->object, "signatures[%i].not_before", *counter); - - date_time = ASN1_get_time_t(X509_get0_notAfter(cert)); - set_integer(date_time, pe->object, "signatures[%i].not_after", *counter); - - (*counter)++; - } - - // See if there is a nested signature, which is apparently an authenticode - // specific feature. See https://github.com/VirusTotal/yara/issues/515. - signer_info = sk_PKCS7_SIGNER_INFO_value(pkcs7->d.sign->signer_info, 0); - - if (signer_info != NULL) - { - attrs = PKCS7_get_attributes(signer_info); - - idx = X509at_get_attr_by_NID( - attrs, OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID), -1); - - xa = X509at_get_attr(attrs, idx); - - for (j = 0; j < MAX_PE_CERTS; j++) - { - nested = X509_ATTRIBUTE_get0_type(xa, j); - if (nested == NULL) - break; - value = nested->value.sequence; - p = value->data; - nested_pkcs7 = d2i_PKCS7(NULL, &p, value->length); - if (nested_pkcs7 != NULL) - { - _parse_pkcs7(pe, nested_pkcs7, counter); - PKCS7_free(nested_pkcs7); - } - } + (*sig_count)++; } - sk_X509_free(certs); + set_integer(signature_valid, pe->object, "is_signed"); } static void pe_parse_certificates(PE* pe) { int counter = 0; - const uint8_t* eod; - const unsigned char* cert_p; - uintptr_t end; - - PWIN_CERTIFICATE win_cert; - PIMAGE_DATA_DIRECTORY directory = pe_get_directory_entry( pe, IMAGE_DIRECTORY_ENTRY_SECURITY); @@ -1831,72 +1844,9 @@ static void pe_parse_certificates(PE* pe) return; } - // Store the end of directory, making comparisons easier. - eod = pe->data + yr_le32toh(directory->VirtualAddress) + - yr_le32toh(directory->Size); - - win_cert = - (PWIN_CERTIFICATE) (pe->data + yr_le32toh(directory->VirtualAddress)); - - // - // Walk the directory, pulling out certificates. - // - // Make sure WIN_CERTIFICATE fits within the directory. - // Make sure the Length specified fits within directory too. - // - // The docs say that the length is only for the Certificate, but the next - // paragraph contradicts that. All the binaries I've seen have the Length - // being the entire structure (Certificate included). - // - - while (struct_fits_in_pe(pe, win_cert, WIN_CERTIFICATE) && - yr_le32toh(win_cert->Length) > sizeof(WIN_CERTIFICATE) && - fits_in_pe(pe, win_cert, yr_le32toh(win_cert->Length)) && - (uint8_t*) win_cert + sizeof(WIN_CERTIFICATE) < eod && - (uint8_t*) win_cert + yr_le32toh(win_cert->Length) <= eod) - { - PKCS7* pkcs7; - - // Some sanity checks - - if (yr_le32toh(win_cert->Length) == 0 || - (yr_le16toh(win_cert->Revision) != WIN_CERT_REVISION_1_0 && - yr_le16toh(win_cert->Revision) != WIN_CERT_REVISION_2_0)) - { - break; - } - - // Don't support legacy revision for now. - // Make sure type is PKCS#7 too. - - if (yr_le16toh(win_cert->Revision) != WIN_CERT_REVISION_2_0 || - yr_le16toh(win_cert->CertificateType) != WIN_CERT_TYPE_PKCS_SIGNED_DATA) - { - end = (uintptr_t) ((uint8_t*) win_cert) + yr_le32toh(win_cert->Length); - - // Next certificate is aligned to the next 8-bytes boundary. - win_cert = (PWIN_CERTIFICATE) ((end + 7) & -8); - continue; - } - - cert_p = win_cert->Certificate; - end = (uintptr_t) ((uint8_t*) win_cert) + yr_le32toh(win_cert->Length); - - while ((uintptr_t) cert_p < end && counter < MAX_PE_CERTS) - { - pkcs7 = d2i_PKCS7(NULL, &cert_p, (uint32_t) (end - (uintptr_t) cert_p)); - - if (pkcs7 == NULL) - break; - - _parse_pkcs7(pe, pkcs7, &counter); - PKCS7_free(pkcs7); - pkcs7 = NULL; - } - - // Next certificate is aligned to the next 8-bytes boundary. - win_cert = (PWIN_CERTIFICATE) ((end + 7) & -8); - } + AuthenticodeArray* auth_array = parse_authenticode(pe->data, pe->data_size); + _process_authenticode(pe, auth_array, &counter); + authenticode_array_free(auth_array); set_integer(counter, pe->object, "number_of_signatures"); } @@ -3590,9 +3540,68 @@ begin_declarations declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); + + declare_integer("verified"); + declare_string("digest_alg"); + declare_string("digest"); + declare_string("file_digest"); + declare_integer("number_of_certificates"); + begin_struct_array("certificates"); + declare_string("thumbprint"); + declare_string("issuer"); + declare_string("subject"); + declare_integer("version"); + declare_string("algorithm"); + declare_string("algorithm_oid"); + declare_string("serial"); + declare_integer("not_before"); + declare_integer("not_after"); + end_struct_array("certificates"); + + begin_struct("signer_info"); + declare_string("program_name"); + declare_string("digest"); + declare_string("digest_alg"); + declare_integer("length_of_chain"); + begin_struct_array("chain"); + declare_string("thumbprint"); + declare_string("issuer"); + declare_string("subject"); + declare_integer("version"); + declare_string("algorithm"); + declare_string("algorithm_oid"); + declare_string("serial"); + declare_integer("not_before"); + declare_integer("not_after"); + end_struct_array("chain"); + end_struct("signer_info"); + + declare_integer("number_of_countersignatures"); + begin_struct_array("countersignatures"); + declare_integer("verified"); + declare_integer("sign_time"); + declare_string("digest_alg"); + declare_string("digest"); + declare_integer("length_of_chain"); + begin_struct_array("chain"); + declare_string("thumbprint"); + declare_string("issuer"); + declare_string("subject"); + declare_integer("version"); + declare_string("algorithm"); + declare_string("algorithm_oid"); + declare_string("serial"); + declare_integer("not_before"); + declare_integer("not_after"); + end_struct_array("chain"); + end_struct_array("countersignatures") + declare_function("valid_on", "i", "i", valid_on); + end_struct_array("signatures") + // If any of the signatures correctly signs the binary + declare_integer("is_signed"); declare_integer("number_of_signatures"); #endif @@ -3602,9 +3611,9 @@ end_declarations int module_initialize(YR_MODULE* module) { #if defined(HAVE_LIBCRYPTO) - // Not checking return value here because if it fails we will not parse the - // nested signature silently. - OBJ_create(SPC_NESTED_SIGNATURE_OBJID, NULL, NULL); + // Initialize OpenSSL global objects for the auth library before any + // multithreaded environment as it is not thread-safe + initialize_authenticode_parser(); #endif return ERROR_SUCCESS; } diff --git a/libyara/modules/pe/pe_utils.c b/libyara/modules/pe/pe_utils.c index def9e405ab..3716411411 100644 --- a/libyara/modules/pe/pe_utils.c +++ b/libyara/modules/pe/pe_utils.c @@ -38,10 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#if HAVE_LIBCRYPTO -#include -#endif - PIMAGE_NT_HEADERS32 pe_get_header(const uint8_t* data, size_t data_size) { PIMAGE_DOS_HEADER mz_header; @@ -253,53 +249,6 @@ time_t timegm(struct tm* tm) #endif // HAVE__MKGMTIME #endif // !HAVE_TIMEGM -#if HAVE_LIBCRYPTO - -// Taken from http://stackoverflow.com/questions/10975542/asn1-time-conversion -// and cleaned up. Also uses timegm(3) instead of mktime(3). - -time_t ASN1_get_time_t(const ASN1_TIME* time) -{ - struct tm t; - const char* str = (const char*) time->data; - size_t i = 0; - - memset(&t, 0, sizeof(t)); - - if (time->type == V_ASN1_UTCTIME) /* two digit year */ - { - t.tm_year = (str[i++] - '0') * 10; - t.tm_year += (str[i++] - '0'); - - if (t.tm_year < 70) - t.tm_year += 100; - } - else if (time->type == V_ASN1_GENERALIZEDTIME) /* four digit year */ - { - t.tm_year = (str[i++] - '0') * 1000; - t.tm_year += (str[i++] - '0') * 100; - t.tm_year += (str[i++] - '0') * 10; - t.tm_year += (str[i++] - '0'); - t.tm_year -= 1900; - } - - t.tm_mon = (str[i++] - '0') * 10; - t.tm_mon += (str[i++] - '0') - 1; // -1 since January is 0 not 1. - t.tm_mday = (str[i++] - '0') * 10; - t.tm_mday += (str[i++] - '0'); - t.tm_hour = (str[i++] - '0') * 10; - t.tm_hour += (str[i++] - '0'); - t.tm_min = (str[i++] - '0') * 10; - t.tm_min += (str[i++] - '0'); - t.tm_sec = (str[i++] - '0') * 10; - t.tm_sec += (str[i++] - '0'); - - /* Note: we did not adjust the time based on time zone information */ - return timegm(&t); -} - -#endif - // These ordinals are taken from pefile. If a lookup fails attempt to return // "ordN" and if that fails, return NULL. The caller is responsible for freeing diff --git a/tests/test-pe.c b/tests/test-pe.c index 67a38e3cf4..423eb11682 100644 --- a/tests/test-pe.c +++ b/tests/test-pe.c @@ -337,9 +337,98 @@ int main(int argc, char** argv) "import \"pe\" \ rule test { \ condition: \ + pe.is_signed and \ pe.number_of_signatures == 1 and \ pe.signatures[0].thumbprint == \"c1bf1b8f751bf97626ed77f755f0a393106f2454\" and \ - pe.signatures[0].subject == \"/C=US/ST=California/L=Menlo Park/O=Quicken, Inc./OU=Operations/CN=Quicken, Inc.\" \ + pe.signatures[0].subject == \"/C=US/ST=California/L=Menlo Park/O=Quicken, Inc./OU=Operations/CN=Quicken, Inc.\" and \ + pe.signatures[0].verified and \ + pe.signatures[0].digest_alg == \"sha1\" and \ + pe.signatures[0].digest == \"f4ca190ec9052243b8882d492b1c12d04da7817f\" and \ + pe.signatures[0].algorithm == \"sha256WithRSAEncryption\" and \ + pe.signatures[0].algorithm_oid == \"1.2.840.113549.1.1.11\" and \ + pe.signatures[0].file_digest == \"f4ca190ec9052243b8882d492b1c12d04da7817f\" and \ + pe.signatures[0].number_of_certificates == 4 and \ + pe.signatures[0].certificates[0].not_after == 1609372799 and \ + pe.signatures[0].certificates[0].not_before == 1356048000 and \ + pe.signatures[0].certificates[0].version == 3 and \ + pe.signatures[0].certificates[0].serial == \"7e:93:eb:fb:7c:c6:4e:59:ea:4b:9a:77:d4:06:fc:3b\" and \ + pe.signatures[0].certificates[0].algorithm == \"sha1WithRSAEncryption\" and \ + pe.signatures[0].certificates[0].algorithm_oid == \"1.2.840.113549.1.1.5\" and \ + pe.signatures[0].certificates[0].thumbprint == \"6c07453ffdda08b83707c09b82fb3d15f35336b1\" and \ + pe.signatures[0].certificates[0].issuer == \"/C=ZA/ST=Western Cape/L=Durbanville/O=Thawte/OU=Thawte Certification/CN=Thawte Timestamping CA\" and \ + pe.signatures[0].certificates[0].subject == \"/C=US/O=Symantec Corporation/CN=Symantec Time Stamping Services CA - G2\" and \ + pe.signatures[0].certificates[1].not_after == 1609286399 and \ + pe.signatures[0].certificates[1].not_before == 1350518400 and \ + pe.signatures[0].certificates[1].version == 3 and \ + pe.signatures[0].certificates[1].serial == \"0e:cf:f4:38:c8:fe:bf:35:6e:04:d8:6a:98:1b:1a:50\" and \ + pe.signatures[0].certificates[1].algorithm == \"sha1WithRSAEncryption\" and \ + pe.signatures[0].certificates[1].algorithm_oid == \"1.2.840.113549.1.1.5\" and \ + pe.signatures[0].certificates[1].thumbprint == \"65439929b67973eb192d6ff243e6767adf0834e4\" and \ + pe.signatures[0].certificates[1].issuer == \"/C=US/O=Symantec Corporation/CN=Symantec Time Stamping Services CA - G2\" and \ + pe.signatures[0].certificates[1].subject == \"/C=US/O=Symantec Corporation/CN=Symantec Time Stamping Services Signer - G4\" and \ + pe.signatures[0].certificates[2].not_after == 1559692799 and \ + pe.signatures[0].certificates[2].not_before == 1491955200 and \ + pe.signatures[0].certificates[2].version == 3 and \ + pe.signatures[0].certificates[2].serial == \"21:bd:b2:cb:ec:e5:43:1e:24:f7:56:74:d6:0e:9c:1d\" and \ + pe.signatures[0].certificates[2].algorithm == \"sha256WithRSAEncryption\" and \ + pe.signatures[0].certificates[2].algorithm_oid == \"1.2.840.113549.1.1.11\" and \ + pe.signatures[0].certificates[2].thumbprint == \"c1bf1b8f751bf97626ed77f755f0a393106f2454\" and \ + pe.signatures[0].certificates[2].issuer == \"/C=US/O=Symantec Corporation/OU=Symantec Trust Network/CN=Symantec Class 3 SHA256 Code Signing CA\" and \ + pe.signatures[0].certificates[2].subject == \"/C=US/ST=California/L=Menlo Park/O=Quicken, Inc./OU=Operations/CN=Quicken, Inc.\" and \ + pe.signatures[0].certificates[3].not_after == 1702166399 and \ + pe.signatures[0].certificates[3].not_before == 1386633600 and \ + pe.signatures[0].certificates[3].version == 3 and \ + pe.signatures[0].certificates[3].serial == \"3d:78:d7:f9:76:49:60:b2:61:7d:f4:f0:1e:ca:86:2a\" and \ + pe.signatures[0].certificates[3].algorithm == \"sha256WithRSAEncryption\" and \ + pe.signatures[0].certificates[3].algorithm_oid == \"1.2.840.113549.1.1.11\" and \ + pe.signatures[0].certificates[3].thumbprint == \"007790f6561dad89b0bcd85585762495e358f8a5\" and \ + pe.signatures[0].certificates[3].issuer == \"/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5\" and \ + pe.signatures[0].certificates[3].subject == \"/C=US/O=Symantec Corporation/OU=Symantec Trust Network/CN=Symantec Class 3 SHA256 Code Signing CA\" and \ + pe.signatures[0].signer_info.digest == \"845555fec6e472a43b0714911d6c452a092e9632\" and \ + pe.signatures[0].signer_info.digest_alg == \"sha1\" and \ + pe.signatures[0].signer_info.length_of_chain == 2 and \ + pe.signatures[0].signer_info.chain[0].not_after == 1559692799 and \ + pe.signatures[0].signer_info.chain[0].not_before == 1491955200 and \ + pe.signatures[0].signer_info.chain[0].version == 3 and \ + pe.signatures[0].signer_info.chain[0].serial == \"21:bd:b2:cb:ec:e5:43:1e:24:f7:56:74:d6:0e:9c:1d\" and \ + pe.signatures[0].signer_info.chain[0].algorithm == \"sha256WithRSAEncryption\" and \ + pe.signatures[0].signer_info.chain[0].algorithm_oid == \"1.2.840.113549.1.1.11\" and \ + pe.signatures[0].signer_info.chain[0].thumbprint == \"c1bf1b8f751bf97626ed77f755f0a393106f2454\" and \ + pe.signatures[0].signer_info.chain[0].issuer == \"/C=US/O=Symantec Corporation/OU=Symantec Trust Network/CN=Symantec Class 3 SHA256 Code Signing CA\" and \ + pe.signatures[0].signer_info.chain[0].subject == \"/C=US/ST=California/L=Menlo Park/O=Quicken, Inc./OU=Operations/CN=Quicken, Inc.\" and \ + pe.signatures[0].signer_info.chain[1].not_after == 1702166399 and \ + pe.signatures[0].signer_info.chain[1].not_before == 1386633600 and \ + pe.signatures[0].signer_info.chain[1].version == 3 and \ + pe.signatures[0].signer_info.chain[1].serial == \"3d:78:d7:f9:76:49:60:b2:61:7d:f4:f0:1e:ca:86:2a\" and \ + pe.signatures[0].signer_info.chain[1].algorithm == \"sha256WithRSAEncryption\" and \ + pe.signatures[0].signer_info.chain[1].algorithm_oid == \"1.2.840.113549.1.1.11\" and \ + pe.signatures[0].signer_info.chain[1].thumbprint == \"007790f6561dad89b0bcd85585762495e358f8a5\" and \ + pe.signatures[0].signer_info.chain[1].issuer == \"/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5\" and \ + pe.signatures[0].signer_info.chain[1].subject == \"/C=US/O=Symantec Corporation/OU=Symantec Trust Network/CN=Symantec Class 3 SHA256 Code Signing CA\" and \ + pe.signatures[0].number_of_countersignatures == 1 and \ + pe.signatures[0].countersignatures[0].length_of_chain == 2 and \ + pe.signatures[0].countersignatures[0].digest == \"9fa1188e4c656d86e2d7fa133ee8138ac1ec4ec1\" and \ + pe.signatures[0].countersignatures[0].digest_alg == \"sha1\" and \ + pe.signatures[0].countersignatures[0].sign_time == 1528216551 and \ + pe.signatures[0].countersignatures[0].verified and \ + pe.signatures[0].countersignatures[0].chain[0].not_after == 1609286399 and \ + pe.signatures[0].countersignatures[0].chain[0].not_before == 1350518400 and \ + pe.signatures[0].countersignatures[0].chain[0].version == 3 and \ + pe.signatures[0].countersignatures[0].chain[0].serial == \"0e:cf:f4:38:c8:fe:bf:35:6e:04:d8:6a:98:1b:1a:50\" and \ + pe.signatures[0].countersignatures[0].chain[0].algorithm == \"sha1WithRSAEncryption\" and \ + pe.signatures[0].countersignatures[0].chain[0].algorithm_oid == \"1.2.840.113549.1.1.5\" and \ + pe.signatures[0].countersignatures[0].chain[0].thumbprint == \"65439929b67973eb192d6ff243e6767adf0834e4\" and \ + pe.signatures[0].countersignatures[0].chain[0].issuer == \"/C=US/O=Symantec Corporation/CN=Symantec Time Stamping Services CA - G2\" and \ + pe.signatures[0].countersignatures[0].chain[0].subject == \"/C=US/O=Symantec Corporation/CN=Symantec Time Stamping Services Signer - G4\" and \ + pe.signatures[0].countersignatures[0].chain[1].not_after == 1609372799 and \ + pe.signatures[0].countersignatures[0].chain[1].not_before == 1356048000 and \ + pe.signatures[0].countersignatures[0].chain[1].version == 3 and \ + pe.signatures[0].countersignatures[0].chain[1].serial == \"7e:93:eb:fb:7c:c6:4e:59:ea:4b:9a:77:d4:06:fc:3b\" and \ + pe.signatures[0].countersignatures[0].chain[1].algorithm == \"sha1WithRSAEncryption\" and \ + pe.signatures[0].countersignatures[0].chain[1].algorithm_oid == \"1.2.840.113549.1.1.5\" and \ + pe.signatures[0].countersignatures[0].chain[1].thumbprint == \"6c07453ffdda08b83707c09b82fb3d15f35336b1\" and \ + pe.signatures[0].countersignatures[0].chain[1].issuer == \"/C=ZA/ST=Western Cape/L=Durbanville/O=Thawte/OU=Thawte Certification/CN=Thawte Timestamping CA\" and \ + pe.signatures[0].countersignatures[0].chain[1].subject == \"/C=US/O=Symantec Corporation/CN=Symantec Time Stamping Services CA - G2\" \ }", "tests/data/" "079a472d22290a94ebb212aa8015cdc8dd28a968c6b4d3b88acdd58ce2d3b885"); diff --git a/windows/vs2015/libyara/libyara.vcxproj b/windows/vs2015/libyara/libyara.vcxproj index 2ec60da48f..b146b13d3d 100644 --- a/windows/vs2015/libyara/libyara.vcxproj +++ b/windows/vs2015/libyara/libyara.vcxproj @@ -247,6 +247,11 @@ + + + + + diff --git a/windows/vs2017/libyara/libyara.vcxproj b/windows/vs2017/libyara/libyara.vcxproj index dee579f80b..8c72d126f9 100644 --- a/windows/vs2017/libyara/libyara.vcxproj +++ b/windows/vs2017/libyara/libyara.vcxproj @@ -246,6 +246,11 @@ + + + + +