mirror of
https://github.com/VirusTotal/yara.git
synced 2026-10-11 22:49:58 +00:00
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:
co-authored by
Victor M. Alvarez
parent
22fde83d7a
commit
b9cd46df15
@@ -8,6 +8,7 @@
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Avast
|
||||
Google Inc.
|
||||
Hilko Bengen <[email protected]>
|
||||
Joachim Metz <[email protected]>
|
||||
|
||||
@@ -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]>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user