-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introducing a partial authenticode validation to the Linux plugin of …
…the same name. The certificate chain still can't be verified. Signed-off-by: Ivan Kwiatkowski <justicerage@manalyzer.org>
- Loading branch information
1 parent
3fb8d3d
commit a2e9eb5
Showing
9 changed files
with
550 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.