Skip to content

Commit

Permalink
Introducing a partial authenticode validation to the Linux plugin of …
Browse files Browse the repository at this point in the history
…the same name.

The certificate chain still can't be verified.

Signed-off-by: Ivan Kwiatkowski <justicerage@manalyzer.org>
  • Loading branch information
JusticeRage committed Aug 8, 2018
1 parent 3fb8d3d commit a2e9eb5
Show file tree
Hide file tree
Showing 9 changed files with 550 additions and 31 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ else()
# Compile the *nix authenticode plugin if OpenSSL was found.
if (OPENSSL_FOUND)
add_library(plugin_authenticode SHARED plugins/plugin_authenticode/plugin_authenticode_openssl.cpp
plugins/plugin_authenticode/plugin_authenticode_commons.cpp)
plugins/plugin_authenticode/commons.cpp
plugins/plugin_authenticode/asn1.cpp
plugins/plugin_authenticode/pe_authenticode_digest.cpp)
target_link_libraries(plugin_authenticode ${OPENSSL_LIBRARIES})
endif()
endif()
Expand Down
16 changes: 8 additions & 8 deletions manape/pe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,29 +467,29 @@ unsigned int PE::rva_to_offset(boost::uint64_t rva) const
}

// Special case: PE with no sections
if (_sections.size() == 0) {
if (_sections.empty()) {
return rva & 0xFFFFFFFF; // If the file is bigger than 4GB, this assumption may not be true.
}

// Find the corresponding section.
pSection section = pSection();
for (std::vector<pSection>::const_iterator it = _sections.begin() ; it != _sections.end() ; ++it)
for (const auto& it : _sections)
{
if (is_address_in_section(rva, *it))
if (is_address_in_section(rva, it))
{
section = *it;
section = it;
break;
}
}

if (section == nullptr)
{
// No section found. Maybe the VirsualSize is erroneous? Try with the RawSizeOfData.
for (std::vector<pSection>::const_iterator it = _sections.begin() ; it != _sections.end() ; ++it)
for (const auto& it : _sections)
{
if (is_address_in_section(rva, *it, true))
if (is_address_in_section(rva, it, true))
{
section = *it;
section = it;
break;
}
}
Expand Down Expand Up @@ -547,7 +547,7 @@ bool PE::_reach_directory(int directory) const
{
PRINT_ERROR << "Tried to reach a directory, but ImageOptionalHeader was not parsed!"
<< DEBUG_INFO_INSIDEPE << std::endl;
return 0;
return false;
}

if (_ioh->directories[directory].VirtualAddress == 0 && _ioh->directories[directory].Size == 0) {
Expand Down
184 changes: 184 additions & 0 deletions plugins/plugin_authenticode/asn1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
This file is part of Manalyze.
Manalyze is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Manalyze is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Manalyze. If not, see <http://www.gnu.org/licenses/>.
*/

#include "plugins/plugin_authenticode/asn1.h"

namespace plugin {

std::string OID_to_string(const bytes& in)
{
if (in.empty()) {
return "";
}
std::stringstream ss;

int b = in[0] % 40;
int a = (in[0] - b) / 40;
ss << a << "." << b;

for (unsigned int i = 1 ; i < in.size() ; ++i)
{
ss << ".";
if (in[i] < 128) {
ss << static_cast<int>(in[i]); // Do not interpret as a char.
}
else
{
if (i+1 >= in.size()) // Don't read outside of the bounds.
{
PRINT_WARNING << "[plugin_authenticode] Tried to convert a malformed OID!" << std::endl;
return "";
}
ss << static_cast<int>((in[i]-128)*128 + in[i+1]);
++i;
}
}
return ss.str();
}

// ----------------------------------------------------------------------------

bool check_pkcs_sanity(const pPKCS7& p)
{
if (p == nullptr)
{
PRINT_WARNING << "[plugin_authenticode] Error reading the PKCS7 certificate." << std::endl;
return false;
}

if (!PKCS7_type_is_signed(p.get()))
{
PRINT_WARNING << "[plugin_authenticode] The PKCS7 structure is not signed!" << std::endl;
return false;
}

// The SpcIndirectDataContent structure of the signature cannot be accessed directly
// with OpenSSL's API. Retrieve the information manually.
if (p->d.sign == nullptr ||
p->d.sign->contents == nullptr ||
p->d.sign->contents->type == nullptr ||
p->d.sign->contents->type->data == nullptr ||
p->d.sign->contents->d.other == nullptr ||
p->d.sign->contents->d.other->value.asn1_string == nullptr)
{
PRINT_WARNING << "[plugin_authenticode] Unable to access the "
"SpcIndirectDataContent structure." << std::endl;
return false;
}

// Assert that the data indeed points to a SpcIndirectDataContent object by checking the OID.
bytes oid(p->d.sign->contents->type->data,
p->d.sign->contents->type->data + p->d.sign->contents->type->length);
if (OID_to_string(oid) != SPC_INDIRECT_DATA)
{
PRINT_WARNING << "[plugin_authenticode] Unable to access the "
"SpcIndirectDataContent structure." << std::endl;
return false;
}

return true;
}

// ----------------------------------------------------------------------------

long asn1_read(const unsigned char** data,
long max_length,
const std::string& expected,
const std::string& object_name)
{
int tag = 0, xclass = 0;
long size = 0;

ASN1_get_object(data, &size, &tag, &xclass, max_length); // Return value ignored. Who knows what this function returns?
std::string tag_s = ASN1_tag2str(tag);
if (tag_s != expected)
{
PRINT_WARNING << "[plugin_authenticode] The " << object_name << " ASN1 string is malformed!" << std::endl;
PRINT_WARNING << "(Expected " << expected << ", but got " << tag_s << " instead.)" << std::endl;
return 0;
}
return size;
}

// ----------------------------------------------------------------------------

bool parse_spc_asn1(ASN1_STRING* asn1, AuthenticodeDigest& digest)
{
const unsigned char* asn1_data = asn1->data;
bytes buffer;

// Start at the SpcIndirectDataContent..
long size = asn1_read(&asn1_data, asn1->length, "SEQUENCE", "SpcIndirectDataContent");
if (size == 0) {
return false;
}
// Read the SpcAttributeTypeAndOptionalValue.
size = asn1_read(&asn1_data, asn1->length, "SEQUENCE", "SpcAttributeTypeAndOptionalValue");
if (size == 0) {
return false;
}
// Read SpcAttributeTypeAndOptionalValue->type
size = asn1_read(&asn1_data, asn1->length, "OBJECT", "type");
if (size == 0) {
return false;
}
// Assert that the type read has the expected OID.
buffer.assign(asn1_data, asn1_data + size);
if(OID_to_string(buffer) != SPC_PE_IMAGE_DATAOBJ)
{
PRINT_WARNING << "[plugin_authenticode] The SpcAttributeTypeAndOptionalValue has an invalid type!" << std::endl;
return false;
}
asn1_data += size; // Skip over the OID.
// Read SpcAttributeTypeAndOptionalValue->value (SpcPeImageData)
size = asn1_read(&asn1_data, asn1->length, "SEQUENCE", "SpcPeImageData");
if (size == 0) {
return false;
}
asn1_data += size; // Skip the structure.

// Read the DigestInfo.
size = asn1_read(&asn1_data, asn1->length, "SEQUENCE", "DigestInfo");
if (size == 0) {
return false;
}
// Read DigestInfo->AlgorithmIdentifier
size = asn1_read(&asn1_data, asn1->length, "SEQUENCE", "AlgorithmIdentifier");
if (size == 0) {
return false;
}
// Read DigestInfo->AlgorithmIdentifier->algorithm)
size = asn1_read(&asn1_data, asn1->length, "OBJECT", "algorithm");
if (size == 0) {
return false;
}
buffer.assign(asn1_data, asn1_data + size);
digest.algorithm = OID_to_string(buffer);
asn1_data += size;
// Read and skip DigestInfo->AlgorithmIdentifier->parameters
size = asn1_read(&asn1_data, asn1->length, "NULL", "parameters");
// Read the digest.
size = asn1_read(&asn1_data, asn1->length, "OCTET STRING", "digest");
if (size == 0) {
return false;
}
digest.digest.assign(asn1_data, asn1_data + size);

return true;
}

} // !namespace plugin
120 changes: 120 additions & 0 deletions plugins/plugin_authenticode/asn1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
This file is part of Manalyze.
Manalyze is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Manalyze is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Manalyze. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <string>
#include <sstream>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/cstdint.hpp>

#include <openssl/pkcs7.h>
#include <openssl/x509.h>
#include <openssl/bio.h>

#include "manacommons/color.h"

typedef std::vector<boost::uint8_t> bytes;
typedef boost::shared_ptr<PKCS7> pPKCS7;
typedef boost::shared_ptr<BIO> pBIO;

// A simple struct describing the authenticode digest.
// The first member is the algorithm used (OID), and the second member is the digest.
struct AuthenticodeDigest
{
std::string algorithm;
bytes digest;
};

// Redefine the ASN1_OBJECT structure because OpenSSL 1.1 can't seem to find it otherwise.
struct asn1_object_st
{
const char *sn, *ln;
int nid;
int length;
const unsigned char *data; /* data remains const after init */
int flags; /* Should we free this one */
};

namespace plugin {

const std::string SPC_INDIRECT_DATA("1.3.6.1.4.1.311.2.1.4");
const std::string SPC_PE_IMAGE_DATAOBJ("1.3.6.1.4.1.311.2.1.15");

/**
* @brief Converts an hexadecimal OID into its string representation.
*
* @param const bytes& in The raw OID bytes.
*
* @return A string containing the OID in its (somewhat) human-readable form.
*/
std::string OID_to_string(const bytes& in);

// ----------------------------------------------------------------------------

/**
* @brief This function asserts that a PKCS7 object has a valid structure
* before attempting any operations on it.
*
* @param pPKCS7 p The PKCS7 object to verify.
*
* @return Whether the object can be used safely to verify an Authenticode signature.
*/

// ----------------------------------------------------------------------------

bool check_pkcs_sanity(const pPKCS7& p);

/**
* @brief Helper function designed to read ASN1 objects.
*
* This function is useful to read objects of an expected type. Its main use is
* to avoid code duplication around error messages.
*
* @param const unsigned char** data A pointer to the ASN1 string to read.
* It will be updated to point to the next object in the string.
* @param long max_length The maximum number of bytes to read.
* @param const std::string& expected The object type expected (i.e. "SEQUENCE").
* This argument is given as a string for code readability.
* @param const std::string& structure_name The name of the object read (for error messages only).
*
* @return The size of the object read. The data pointer will be updated to point to it.
*/
long asn1_read(const unsigned char** data,
long max_length,
const std::string& expected,
const std::string& object_name);

// ----------------------------------------------------------------------------

/**
* @brief This function parses an ASN1 SpcIndirectDataContent object.
*
* The SpcIndirectDataContent contains the digest and algorithm of the authenticode
* hash generated for the PE. This function's role is to go down the ASN1 rabbit hole
* and retreive this information so that the digest can be computed independently and
* verified against the information contained in this signature.
*
* @param ASN1_STRING* asn1 The ASN1 string pointing to the SpcIndirectDataContent object.
* @param AuthenticodeDigest& digest The structure into which the digest information will be put.
*
* @return Whether the ASN1 was parsed successfully.
*/
bool parse_spc_asn1(ASN1_STRING* asn1, AuthenticodeDigest& digest);

} // !namespace plugin
File renamed without changes.

0 comments on commit a2e9eb5

Please sign in to comment.