From 241f5bb5c5f32f15fdbdc53d93fe6b67dc51a204 Mon Sep 17 00:00:00 2001 From: houndthe Date: Wed, 22 Dec 2021 13:14:06 +0100 Subject: [PATCH 1/9] Import authenticode-parser into Yara. Rewrite PE signature processing using the authenticode-parser. Update bazel OpenSSL dependency to 1.1.1 from 1.1.0 --- bazel/yara.bzl | 10 + bazel/yara_deps.bzl | 6 +- configure.ac | 2 + docs/modules/pe.rst | 115 ++++ libyara/Makefile.am | 8 + .../authenticode-parser/authenticode.h | 198 ++++++ libyara/include/yara/pe_utils.h | 5 - .../pe/authenticode-parser/authenticode.c | 640 ++++++++++++++++++ .../pe/authenticode-parser/certificate.c | 383 +++++++++++ .../pe/authenticode-parser/certificate.h | 45 ++ .../pe/authenticode-parser/countersignature.c | 352 ++++++++++ .../pe/authenticode-parser/countersignature.h | 53 ++ .../modules/pe/authenticode-parser/helper.c | 78 +++ .../modules/pe/authenticode-parser/helper.h | 68 ++ .../modules/pe/authenticode-parser/structs.h | 111 +++ libyara/modules/pe/pe.c | 490 +++++++------- libyara/modules/pe/pe_utils.c | 51 -- tests/test-pe.c | 81 ++- windows/vs2015/libyara/libyara.vcxproj | 5 + windows/vs2017/libyara/libyara.vcxproj | 5 + 20 files changed, 2406 insertions(+), 300 deletions(-) create mode 100644 libyara/include/authenticode-parser/authenticode.h create mode 100644 libyara/modules/pe/authenticode-parser/authenticode.c create mode 100644 libyara/modules/pe/authenticode-parser/certificate.c create mode 100644 libyara/modules/pe/authenticode-parser/certificate.h create mode 100644 libyara/modules/pe/authenticode-parser/countersignature.c create mode 100644 libyara/modules/pe/authenticode-parser/countersignature.h create mode 100644 libyara/modules/pe/authenticode-parser/helper.c create mode 100644 libyara/modules/pe/authenticode-parser/helper.h create mode 100644 libyara/modules/pe/authenticode-parser/structs.h diff --git a/bazel/yara.bzl b/bazel/yara.bzl index 51c0729b1e..bef28ebe88 100644 --- a/bazel/yara.bzl +++ b/bazel/yara.bzl @@ -187,6 +187,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", ], hdrs = [ "libyara/include/yara.h", diff --git a/bazel/yara_deps.bzl b/bazel/yara_deps.bzl index 44b18ed21f..76a5dc3aab 100644 --- a/bazel/yara_deps.bzl +++ b/bazel/yara_deps.bzl @@ -36,9 +36,9 @@ def yara_deps(): maybe( http_archive, name = "openssl", - url = "https://github.com/openssl/openssl/archive/OpenSSL_1_1_0h.tar.gz", - sha256 = "f56dd7d81ce8d3e395f83285bd700a1098ed5a4cb0a81ce9522e41e6db7e0389", - strip_prefix = "openssl-OpenSSL_1_1_0h", + url = "https://github.com/openssl/openssl/archive/OpenSSL_1_1_1h.tar.gz", + sha256 = "d1f723c1f6b6d1eaf26655caa50d2f60d4d33f4b04977b1da63def878f386fcc", + strip_prefix = "openssl-OpenSSL_1_1_1h", build_file = "@com_github_virustotal_yara//:bazel/openssl.BUILD", ) maybe( diff --git a/configure.ac b/configure.ac index ae98f53bb8..8f89410a2a 100644 --- a/configure.ac +++ b/configure.ac @@ -311,6 +311,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" @@ -329,6 +330,7 @@ AM_CONDITIONAL([ADDRESS_SANITIZER], [test x$address_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 30ba89476a..fc30a42aac 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 7233ff206b..57ca685679 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..98fbeede97 --- /dev/null +++ b/libyara/include/authenticode-parser/authenticode.h @@ -0,0 +1,198 @@ +/* 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 */ + 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 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, long 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..bd860fe32b --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/authenticode.c @@ -0,0 +1,640 @@ +/* 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 */ +static void initialize_openssl() +{ + 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; + + /* We need to initialize all the custom objects for further parsing */ + initialize_openssl(); + + 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); + free(result->signatures); + 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, long pe_len) +{ + const int dos_hdr_size = 0x40; + if (pe_len < dos_hdr_size) + 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; + + uint32_t cert_addr = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr)); + uint32_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 + cert_len) + return NULL; + + uint32_t dwLength = letoh32(*(uint32_t*)(pe_data + cert_addr)); + if (pe_len < cert_addr + dwLength) + return NULL; + + AuthenticodeArray* auth_array = authenticode_new(pe_data + cert_addr + 0x8, dwLength); + 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; + } + + int mdlen = EVP_MD_size(md); + 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..7686c5161a --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/certificate.c @@ -0,0 +1,383 @@ +/* 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" + +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*)malloc(sizeof(*result)); + if (!result) + goto error; + + result->certs = (Certificate**)malloc(sizeof(Certificate*) * certCount); + if (!result->certs) + goto error; + + result->count = 0; + /* 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 */ + free(result); + if (result) { + for (size_t i = 0; i < result->count; ++i) { + certificate_free(result->certs[i]); + } + free(result->certs); + } + 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); + + X509_NAME* subjectName = X509_get_subject_name(x509); + X509_NAME_oneline(subjectName, buffer, sizeof(buffer)); + result->subject = strdup(buffer); + + 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)); + result->sig_alg = strdup(OBJ_nid2ln(X509_get_signature_nid(x509))); + + EVP_PKEY* pkey = X509_get0_pubkey(x509); + if (pkey) { + result->key = pubkey_to_pem(pkey); + result->key_alg = strdup(OBJ_nid2sn(EVP_PKEY_base_id(pkey))); + } + + 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->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..5bc2c108ab --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/countersignature.c @@ -0,0 +1,352 @@ +/* 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)); + + /* Get digest that corresponds to decrypted encrypted digest in signature */ + ASN1_TYPE* messageDigest = PKCS7_get_signed_attribute(si, NID_pkcs9_messageDigest); + + const ASN1_TYPE* sign_time = PKCS7_get_signed_attribute(si, NID_pkcs9_signingTime); + 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); + /* PKCS9 stores certificates in the corresponding PKCS7 it countersigns */ + result->chain = parse_signer_chain(signCert, certs); + + if (!sign_time) { + result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; + goto end; + } + if (!signCert) { + result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; + goto end; + } + 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; + /* 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 */ + size_t mdLen = EVP_MD_size(md); + 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); + 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); + result->chain = parse_signer_chain(signCert, p7->d.sign->cert); + + /* Imprint == digest */ + TS_MSG_IMPRINT* imprint = TS_TST_INFO_get_msg_imprint(ts); + + if (!rawTime) { + result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; + goto end; + } + if (!signCert) { + result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; + goto end; + } + 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); + int mdLen = EVP_MD_size(md); + + 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); + TS_VERIFY_CTS_set_certs(ctx, p7->d.sign->cert); + TS_VERIFY_CTX_set_imprint(ctx, calc_digest, mdLen); + + bool isValid = TS_RESP_verify_token(ctx, p7) == 1; + + /* VERIFY_CTX_free tries to free these, we don't want that */ + TS_VERIFY_CTX_set_imprint(ctx, NULL, 0); + TS_VERIFY_CTS_set_certs(ctx, NULL); + + TS_VERIFY_CTX_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..be846b827e --- /dev/null +++ b/libyara/modules/pe/authenticode-parser/helper.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 "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) +{ + 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.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 b5cd8f383d..2cbf39f0c5 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 || defined(LIBRESSL_VERSION_NUMBER) -#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 @@ -1603,207 +1591,236 @@ 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->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); - - if (serial_der != NULL) - { - unsigned char* serial_bytes; - char* serial_ascii; - - bytes = i2d_ASN1_INTEGER(serial, &serial_der); + const Certificate* sign_cert = signer->chain->certs[0]; + write_certificate(sign_cert, pe, "signatures[%i]", *sig_count); - // i2d_ASN1_INTEGER() moves the pointer as it writes into - // serial_bytes. Move it back. + char thumbprint_ascii[YR_SHA1_LEN * 2 + 1]; + for (int j = 0; j < sign_cert->sha1.len; ++j) + sprintf(thumbprint_ascii + (j * 2), "%02x", sign_cert->sha1.data[j]); - serial_der -= bytes; - - // Skip over DER type, length information - serial_bytes = serial_der + 2; - bytes -= 2; + write_certificate(sign_cert, pe, "signatures[%i]", *sig_count); + } - // 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( + 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); - serial_ascii = (char*) yr_malloc(bytes * 3); + 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); + } - if (serial_ascii) - { - 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); - } + if (signer->chain) + { + set_integer( + signer->chain->count, + pe->object, + "signatures[%i].signer_info.length_of_chain", + *sig_count); - yr_free(serial_der); + 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); - 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); + 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]); - idx = X509at_get_attr_by_NID( - attrs, OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID), -1); + set_string( + digest_ascii, + pe->object, + "signatures[%i].countersignatures[%i].digest", + *sig_count, + j); + yr_free(digest_ascii); + } - xa = X509at_get_attr(attrs, idx); + if (counter->chain) + { + set_integer( + counter->chain->count, + pe->object, + "signatures[%i].countersignatures[%i].length_of_chain", + *sig_count, + j); - 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); + for (int k = 0; k < counter->chain->count; ++k) + { + write_certificate( + counter->chain->certs[k], + pe, + "signatures[%i].countersignatures[%i].chain[%i]", + *sig_count, + j, + k); + } + } } } + + (*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); @@ -1823,72 +1840,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"); } @@ -3574,9 +3528,65 @@ 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("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("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("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 diff --git a/libyara/modules/pe/pe_utils.c b/libyara/modules/pe/pe_utils.c index 0b1d637c24..093b43a1ad 100644 --- a/libyara/modules/pe/pe_utils.c +++ b/libyara/modules/pe/pe_utils.c @@ -37,10 +37,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; @@ -252,53 +248,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 fe00ae9b3a..1704e98a83 100644 --- a/tests/test-pe.c +++ b/tests/test-pe.c @@ -322,9 +322,88 @@ 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].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].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].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].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].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].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].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].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].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 48dd401543..dd4ecf6ca9 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 27e3232963..8540738d57 100644 --- a/windows/vs2017/libyara/libyara.vcxproj +++ b/windows/vs2017/libyara/libyara.vcxproj @@ -246,6 +246,11 @@ + + + + + From a7c54e969c7745b31367210ac736213bdfa5b95e Mon Sep 17 00:00:00 2001 From: houndthe Date: Wed, 22 Dec 2021 13:49:57 +0100 Subject: [PATCH 2/9] Forgot to include structs.c --- .../modules/pe/authenticode-parser/structs.c | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 libyara/modules/pe/authenticode-parser/structs.c 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) From ea66ae5aea92924c80d6e625c2c9e7dc2bf1fb2e Mon Sep 17 00:00:00 2001 From: houndthe Date: Sat, 22 Jan 2022 03:23:05 +0100 Subject: [PATCH 3/9] Add Avast to authors and myself to contributors --- AUTHORS | 1 + CONTRIBUTORS | 1 + 2 files changed, 2 insertions(+) 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 From b2587637d71f59a84bbe51ce78e21b68332546f9 Mon Sep 17 00:00:00 2001 From: houndthe Date: Mon, 24 Jan 2022 00:39:02 +0100 Subject: [PATCH 4/9] Set forgotten algorithm_oid --- libyara/include/authenticode-parser/authenticode.h | 1 + libyara/modules/pe/authenticode-parser/authenticode.c | 10 ++++++++-- libyara/modules/pe/authenticode-parser/certificate.c | 7 ++++++- .../modules/pe/authenticode-parser/countersignature.c | 7 ++----- libyara/modules/pe/pe.c | 11 +++++------ tests/test-pe.c | 10 ++++++++++ 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/libyara/include/authenticode-parser/authenticode.h b/libyara/include/authenticode-parser/authenticode.h index 98fbeede97..2dffa5ad8c 100644 --- a/libyara/include/authenticode-parser/authenticode.h +++ b/libyara/include/authenticode-parser/authenticode.h @@ -105,6 +105,7 @@ typedef struct { 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 */ diff --git a/libyara/modules/pe/authenticode-parser/authenticode.c b/libyara/modules/pe/authenticode-parser/authenticode.c index bd860fe32b..d9b8f3c041 100644 --- a/libyara/modules/pe/authenticode-parser/authenticode.c +++ b/libyara/modules/pe/authenticode-parser/authenticode.c @@ -531,6 +531,11 @@ AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len) 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 */ @@ -553,8 +558,9 @@ AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len) if (pe_len < pe_cert_table_addr + 2 * sizeof(uint32_t)) return NULL; - uint32_t cert_addr = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr)); - uint32_t cert_len = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr + 4)); + /* 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 + cert_len) diff --git a/libyara/modules/pe/authenticode-parser/certificate.c b/libyara/modules/pe/authenticode-parser/certificate.c index 7686c5161a..d5022df78e 100644 --- a/libyara/modules/pe/authenticode-parser/certificate.c +++ b/libyara/modules/pe/authenticode-parser/certificate.c @@ -282,7 +282,11 @@ Certificate* certificate_new(X509* 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)); - result->sig_alg = strdup(OBJ_nid2ln(X509_get_signature_nid(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) { @@ -360,6 +364,7 @@ void certificate_free(Certificate* 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); diff --git a/libyara/modules/pe/authenticode-parser/countersignature.c b/libyara/modules/pe/authenticode-parser/countersignature.c index 5bc2c108ab..35e1c5aedd 100644 --- a/libyara/modules/pe/authenticode-parser/countersignature.c +++ b/libyara/modules/pe/authenticode-parser/countersignature.c @@ -256,11 +256,8 @@ Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* bool isValid = TS_RESP_verify_token(ctx, p7) == 1; - /* VERIFY_CTX_free tries to free these, we don't want that */ - TS_VERIFY_CTX_set_imprint(ctx, NULL, 0); - TS_VERIFY_CTS_set_certs(ctx, NULL); - - TS_VERIFY_CTX_free(ctx); + X509_STORE_free(store); + OPENSSL_free(ctx); if (!isValid) { result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; diff --git a/libyara/modules/pe/pe.c b/libyara/modules/pe/pe.c index 2cbf39f0c5..1a115dcdf8 100644 --- a/libyara/modules/pe/pe.c +++ b/libyara/modules/pe/pe.c @@ -1606,6 +1606,8 @@ static void pe_parse_exports(PE* pe) /* 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__); \ @@ -1689,12 +1691,6 @@ void _process_authenticode( { const Certificate* sign_cert = signer->chain->certs[0]; write_certificate(sign_cert, pe, "signatures[%i]", *sig_count); - - char thumbprint_ascii[YR_SHA1_LEN * 2 + 1]; - for (int j = 0; j < sign_cert->sha1.len; ++j) - sprintf(thumbprint_ascii + (j * 2), "%02x", sign_cert->sha1.data[j]); - - write_certificate(sign_cert, pe, "signatures[%i]", *sig_count); } set_string( @@ -3540,6 +3536,7 @@ begin_declarations declare_string("subject"); declare_integer("version"); declare_string("algorithm"); + declare_string("algorithm_oid"); declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); @@ -3556,6 +3553,7 @@ begin_declarations declare_string("subject"); declare_integer("version"); declare_string("algorithm"); + declare_string("algorithm_oid"); declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); @@ -3575,6 +3573,7 @@ begin_declarations declare_string("subject"); declare_integer("version"); declare_string("algorithm"); + declare_string("algorithm_oid"); declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); diff --git a/tests/test-pe.c b/tests/test-pe.c index 1704e98a83..d49c15104c 100644 --- a/tests/test-pe.c +++ b/tests/test-pe.c @@ -329,6 +329,8 @@ int main(int argc, char** argv) 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 \ @@ -336,6 +338,7 @@ int main(int argc, char** argv) 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 \ @@ -344,6 +347,7 @@ int main(int argc, char** argv) 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 \ @@ -352,6 +356,7 @@ int main(int argc, char** argv) 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 \ @@ -360,6 +365,7 @@ int main(int argc, char** argv) 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 \ @@ -371,6 +377,7 @@ int main(int argc, char** argv) 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 \ @@ -379,6 +386,7 @@ int main(int argc, char** argv) 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 \ @@ -393,6 +401,7 @@ int main(int argc, char** argv) 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 \ @@ -401,6 +410,7 @@ int main(int argc, char** argv) 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\" \ From 9802e2496fd16c0ff229f2138b8e6e5f10a52379 Mon Sep 17 00:00:00 2001 From: houndthe Date: Mon, 24 Jan 2022 00:40:28 +0100 Subject: [PATCH 5/9] Fix incorrect indentation --- libyara/modules/pe/pe.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libyara/modules/pe/pe.c b/libyara/modules/pe/pe.c index 1a115dcdf8..dcfaf2ba0f 100644 --- a/libyara/modules/pe/pe.c +++ b/libyara/modules/pe/pe.c @@ -3536,7 +3536,7 @@ begin_declarations declare_string("subject"); declare_integer("version"); declare_string("algorithm"); - declare_string("algorithm_oid"); + declare_string("algorithm_oid"); declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); @@ -3553,7 +3553,7 @@ begin_declarations declare_string("subject"); declare_integer("version"); declare_string("algorithm"); - declare_string("algorithm_oid"); + declare_string("algorithm_oid"); declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); @@ -3573,7 +3573,7 @@ begin_declarations declare_string("subject"); declare_integer("version"); declare_string("algorithm"); - declare_string("algorithm_oid"); + declare_string("algorithm_oid"); declare_string("serial"); declare_integer("not_before"); declare_integer("not_after"); From 2af01e17049503c2b569dbebda369ed382941376 Mon Sep 17 00:00:00 2001 From: houndthe Date: Wed, 9 Feb 2022 09:59:41 +0100 Subject: [PATCH 6/9] Fix race condition when initializing openssl objects --- libyara/include/authenticode-parser/authenticode.h | 7 +++++++ .../modules/pe/authenticode-parser/authenticode.c | 8 +++----- libyara/modules/pe/pe.c | 6 +++--- tests/test-pe.c | 14 +++++++------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/libyara/include/authenticode-parser/authenticode.h b/libyara/include/authenticode-parser/authenticode.h index 2dffa5ad8c..2a38411468 100644 --- a/libyara/include/authenticode-parser/authenticode.h +++ b/libyara/include/authenticode-parser/authenticode.h @@ -155,6 +155,13 @@ typedef struct { 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, diff --git a/libyara/modules/pe/authenticode-parser/authenticode.c b/libyara/modules/pe/authenticode-parser/authenticode.c index d9b8f3c041..f21ab1b3da 100644 --- a/libyara/modules/pe/authenticode-parser/authenticode.c +++ b/libyara/modules/pe/authenticode-parser/authenticode.c @@ -273,8 +273,9 @@ static bool authenticode_verify(PKCS7* p7, PKCS7_SIGNER_INFO* si, X509* signCert return isValid; } -/* Creates all the Authenticode objects so we can parse them with OpenSSL */ -static void initialize_openssl() +/* 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"); @@ -289,9 +290,6 @@ AuthenticodeArray* authenticode_new(const uint8_t* data, long len) if (!data || len == 0) return NULL; - /* We need to initialize all the custom objects for further parsing */ - initialize_openssl(); - AuthenticodeArray* result = (AuthenticodeArray*)calloc(1, sizeof(*result)); if (!result) return NULL; diff --git a/libyara/modules/pe/pe.c b/libyara/modules/pe/pe.c index dcfaf2ba0f..732084999c 100644 --- a/libyara/modules/pe/pe.c +++ b/libyara/modules/pe/pe.c @@ -3595,9 +3595,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/tests/test-pe.c b/tests/test-pe.c index d49c15104c..775de438bb 100644 --- a/tests/test-pe.c +++ b/tests/test-pe.c @@ -338,7 +338,7 @@ int main(int argc, char** argv) 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].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 \ @@ -347,7 +347,7 @@ int main(int argc, char** argv) 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].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 \ @@ -356,7 +356,7 @@ int main(int argc, char** argv) 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].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 \ @@ -365,7 +365,7 @@ int main(int argc, char** argv) 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].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 \ @@ -386,7 +386,7 @@ int main(int argc, char** argv) 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].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 \ @@ -401,7 +401,7 @@ int main(int argc, char** argv) 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].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 \ @@ -410,7 +410,7 @@ int main(int argc, char** argv) 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].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\" \ From 4d98236cf05f8990e3a694907c141312ee83a8a3 Mon Sep 17 00:00:00 2001 From: houndthe Date: Sun, 20 Feb 2022 14:42:57 +0100 Subject: [PATCH 7/9] Don't malloc ByteArray with length 0 --- libyara/modules/pe/authenticode-parser/helper.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libyara/modules/pe/authenticode-parser/helper.c b/libyara/modules/pe/authenticode-parser/helper.c index be846b827e..f65a391794 100644 --- a/libyara/modules/pe/authenticode-parser/helper.c +++ b/libyara/modules/pe/authenticode-parser/helper.c @@ -58,6 +58,12 @@ int calculate_digest(const EVP_MD* md, const uint8_t* data, size_t len, uint8_t* 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; From 9dd6a80d858932151321429d8f0f811866e20075 Mon Sep 17 00:00:00 2001 From: houndthe Date: Wed, 9 Mar 2022 20:50:16 +0100 Subject: [PATCH 8/9] Auth. parser update - bug fixes, openssl3.0 support --- .../pe/authenticode-parser/authenticode.c | 10 +++- .../pe/authenticode-parser/certificate.c | 33 +++++++++++ .../pe/authenticode-parser/countersignature.c | 58 ++++++++++++------- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/libyara/modules/pe/authenticode-parser/authenticode.c b/libyara/modules/pe/authenticode-parser/authenticode.c index f21ab1b3da..65141228c9 100644 --- a/libyara/modules/pe/authenticode-parser/authenticode.c +++ b/libyara/modules/pe/authenticode-parser/authenticode.c @@ -561,14 +561,14 @@ AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len) 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 + cert_len) + 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; - - AuthenticodeArray* auth_array = authenticode_new(pe_data + cert_addr + 0x8, dwLength); + /* 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; @@ -585,7 +585,11 @@ AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len) 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) diff --git a/libyara/modules/pe/authenticode-parser/certificate.c b/libyara/modules/pe/authenticode-parser/certificate.c index d5022df78e..337b92e877 100644 --- a/libyara/modules/pe/authenticode-parser/certificate.c +++ b/libyara/modules/pe/authenticode-parser/certificate.c @@ -30,6 +30,27 @@ SOFTWARE. #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) @@ -269,11 +290,19 @@ Certificate* certificate_new(X509* x509) * 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); @@ -291,7 +320,11 @@ Certificate* certificate_new(X509* x509) 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; diff --git a/libyara/modules/pe/authenticode-parser/countersignature.c b/libyara/modules/pe/authenticode-parser/countersignature.c index 35e1c5aedd..d905e239b0 100644 --- a/libyara/modules/pe/authenticode-parser/countersignature.c +++ b/libyara/modules/pe/authenticode-parser/countersignature.c @@ -52,25 +52,26 @@ Countersignature* pkcs9_countersig_new( int digestnid = OBJ_obj2nid(si->digest_alg->algorithm); result->digest_alg = strdup(OBJ_nid2ln(digestnid)); - /* Get digest that corresponds to decrypted encrypted digest in signature */ - ASN1_TYPE* messageDigest = PKCS7_get_signed_attribute(si, NID_pkcs9_messageDigest); - const ASN1_TYPE* sign_time = PKCS7_get_signed_attribute(si, NID_pkcs9_signingTime); - 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); - /* PKCS9 stores certificates in the corresponding PKCS7 it countersigns */ - result->chain = parse_signer_chain(signCert, certs); - 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; @@ -132,12 +133,17 @@ Countersignature* pkcs9_countersig_new( /* 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 */ - size_t mdLen = EVP_MD_size(md); if (mdLen == decLen) { isValid = !memcmp(calc_digest, decData, mdLen); } else { @@ -192,23 +198,26 @@ Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* } 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); - result->chain = parse_signer_chain(signCert, p7->d.sign->cert); - - /* Imprint == digest */ - TS_MSG_IMPRINT* imprint = TS_TST_INFO_get_msg_imprint(ts); - - if (!rawTime) { - result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; - goto end; - } 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; @@ -238,7 +247,12 @@ Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* 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; @@ -251,7 +265,11 @@ Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* 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; From 3b2b2fd5ae598dbe17fc256cfbdc8d32842f6fd7 Mon Sep 17 00:00:00 2001 From: houndthe Date: Sun, 12 Jun 2022 16:10:04 +0200 Subject: [PATCH 9/9] Update authenticode parser --- libyara/include/authenticode-parser/authenticode.h | 2 +- libyara/modules/pe/authenticode-parser/authenticode.c | 6 +++--- libyara/modules/pe/authenticode-parser/certificate.c | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/libyara/include/authenticode-parser/authenticode.h b/libyara/include/authenticode-parser/authenticode.h index 2a38411468..4dfe8a2d20 100644 --- a/libyara/include/authenticode-parser/authenticode.h +++ b/libyara/include/authenticode-parser/authenticode.h @@ -174,7 +174,7 @@ void initialize_authenticode_parser(); * @param pe_len * @return AuthenticodeArray* */ -AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len); +AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, uint64_t pe_len); /** * @brief Constructs AuthenticodeArray from binary data containing Authenticode diff --git a/libyara/modules/pe/authenticode-parser/authenticode.c b/libyara/modules/pe/authenticode-parser/authenticode.c index 65141228c9..7515102464 100644 --- a/libyara/modules/pe/authenticode-parser/authenticode.c +++ b/libyara/modules/pe/authenticode-parser/authenticode.c @@ -302,8 +302,8 @@ AuthenticodeArray* authenticode_new(const uint8_t* data, long len) Authenticode* auth = (Authenticode*)calloc(1, sizeof(*auth)); if (!auth) { - free(result); free(result->signatures); + free(result); return NULL; } @@ -523,9 +523,9 @@ static int authenticode_digest( return 1; } -AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len) +AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, uint64_t pe_len) { - const int dos_hdr_size = 0x40; + const uint64_t dos_hdr_size = 0x40; if (pe_len < dos_hdr_size) return NULL; diff --git a/libyara/modules/pe/authenticode-parser/certificate.c b/libyara/modules/pe/authenticode-parser/certificate.c index 337b92e877..e21f874070 100644 --- a/libyara/modules/pe/authenticode-parser/certificate.c +++ b/libyara/modules/pe/authenticode-parser/certificate.c @@ -129,15 +129,14 @@ CertificateArray* parse_signer_chain(X509* signCert, STACK_OF(X509) * certs) int certCount = sk_X509_num(chain); - CertificateArray* result = (CertificateArray*)malloc(sizeof(*result)); + CertificateArray* result = (CertificateArray*)calloc(1, sizeof(*result)); if (!result) goto error; - result->certs = (Certificate**)malloc(sizeof(Certificate*) * certCount); + result->certs = (Certificate**)calloc(certCount, sizeof(Certificate*)); if (!result->certs) goto error; - result->count = 0; /* Convert each certificate to internal representation */ for (int i = 0; i < certCount; ++i) { Certificate* cert = certificate_new(sk_X509_value(chain, i)); @@ -153,12 +152,12 @@ CertificateArray* parse_signer_chain(X509* signCert, STACK_OF(X509) * certs) return result; error: /* In case of error, return nothing */ - free(result); 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);