mirror of
https://github.com/VirusTotal/yara.git
synced 2026-10-11 22:49:58 +00:00
* 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]>
649 lines
21 KiB
C
649 lines
21 KiB
C
/* 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);
|
|
}
|
|
}
|