Process Authenticode signatures using authenticode-parser (#1623)

* 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

* Forgot to include structs.c

* Add Avast to authors and myself to contributors

* Set forgotten algorithm_oid

* Fix incorrect indentation

* Fix race condition when initializing openssl objects

* Don't malloc ByteArray with length 0

* Auth. parser update - bug fixes, openssl3.0 support

* Update authenticode parser

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