diff --git a/CMakeLists.txt b/CMakeLists.txt
index a63b6d7..e0ac0d7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -59,6 +59,10 @@ include_directories(
${PROJECT_SOURCE_DIR}/plugins
${Boost_INCLUDE_DIRS}
)
+# Explicitly add OpenSSL include directories if it has been found.
+if(OPENSSL_FOUND)
+ include_directories(${OPENSSL_INCLUDE_DIR})
+endif()
add_definitions(-DWITH_MANACOMMONS) # Use functions from manacommons.
add_library(manape SHARED manape/pe.cpp manape/nt_values.cpp manape/utils.cpp manape/imports.cpp manape/resources.cpp manape/section.cpp manape/imported_library.cpp)
@@ -84,7 +88,8 @@ if (WIN32)
add_definitions(-DBOOST_ALL_NO_LIB -DNOMINMAX) # Problems with autolink and MSVC + don't hide std::min and std::max.
# Windows only plugins:
- add_library(plugin_authenticode SHARED plugins/plugin_authenticode.cpp)
+ add_library(plugin_authenticode SHARED plugins/plugin_authenticode/plugin_authenticode.cpp
+ plugins/plugin_authenticode/plugin_authenticode_commons.cpp)
target_link_libraries(plugin_authenticode manape hash-library manacommons yara ${Boost_LIBRARIES})
else()
string (REGEX MATCH "BSD" IS_BSD ${CMAKE_SYSTEM_NAME}) # Detect if we are compiling on a BSD system.
@@ -105,7 +110,8 @@ else()
# Compile the *nix authenticode plugin if OpenSSL was found.
if (OPENSSL_FOUND)
- add_library(plugin_authenticode SHARED plugins/plugin_authenticode_openssl.cpp)
+ add_library(plugin_authenticode SHARED plugins/plugin_authenticode/plugin_authenticode_openssl.cpp
+ plugins/plugin_authenticode/plugin_authenticode_commons.cpp)
target_link_libraries(plugin_authenticode ${OPENSSL_LIBRARIES})
endif()
endif()
diff --git a/bin/yara_rules/company_names.yara b/bin/yara_rules/company_names.yara
index 28b10f4..38029cc 100644
--- a/bin/yara_rules/company_names.yara
+++ b/bin/yara_rules/company_names.yara
@@ -21,9 +21,11 @@ rule CompanyNames
description = "Contains the names of famous IT companies"
author = "Ivan Kwiatkowski (@JusticeRage)"
strings:
- $adobe = "adobe" nocase wide ascii
- $ms = "microsoft" nocase wide ascii
+ // Not checking for Microsoft, because many MS binaries are verified through the
+ // security catalog and do not embed a digital signature.
+ $adobe = "adobe" nocase wide ascii
$google = "google" nocase wide ascii
+ $firefox = "firefox" nocase wide ascii
$intel = "intel" nocase wide ascii
$amd = "advanced micro devices" nocase wide ascii
$amd2 = "amd" nocase wide ascii fullword
@@ -44,3 +46,23 @@ rule CompanyNames
condition:
any of them
}
+
+rule CompanyNamesHomographs
+{
+ meta:
+ description = "Tries to impersonate a famous IT company with homographs"
+ author = "Ivan Kwiatkowski (@JusticeRage)"
+ type = "homograph"
+ strings:
+ $adobe = "adobe" nocase wide ascii
+ $adobe_homograph = { (41 00 | 10 04 | 91 03 | 21 FF) (64 00 | 01 05 | 7E 21 | 44 FF) (6F 00 | BF 03 | 3E 04 | 4F FF) (62 00 | 2C 04 | 42 FF) (65 00 | 35 04 | 45 FF) }
+ $microsoft = "microsoft" nocase wide ascii
+ $microsoft_homograph = { (4D 00 | 9C 03 | 1C 04 | 6F 21 | 2D FF) (69 00 | 56 04 | 70 21 | 49 FF) (63 00 | F2 03 | 41 04 | 7D 21 | 43 FF) (72 00 | 52 FF) (6F 00 | BF 03 | 3E 04 | 4F FF) (73 00 | 55 04 | 53 FF) (6F 00 | BF 03 | 3E 04 | 4F FF) (66 00 | 46 FF) (74 00 | 54 FF) }
+ $google = "google" nocase wide ascii
+ $google_homograph = { (47 00 | 0C 05 | 27 FF) (6F 00 | BF 03 | 3E 04 | 4F FF) (6F 00 | BF 03 | 3E 04 | 4F FF) (67 00 | 47 FF) (6C 00 | 7C 21 | 4C FF) (65 00 | 35 04 | 45 FF) }
+ condition:
+ // Do not match on the original strings, as that will have been caught above.
+ ($adobe_homograph and not $adobe) or
+ ($google_homograph and not $google) or
+ ($microsoft_homograph and not $microsoft)
+}
diff --git a/plugins/plugin_authenticode.cpp b/plugins/plugin_authenticode/plugin_authenticode.cpp
old mode 100755
new mode 100644
similarity index 71%
rename from plugins/plugin_authenticode.cpp
rename to plugins/plugin_authenticode/plugin_authenticode.cpp
index 0467d5c..7d6885a
--- a/plugins/plugin_authenticode.cpp
+++ b/plugins/plugin_authenticode/plugin_authenticode.cpp
@@ -1,608 +1,604 @@
-/*
- 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 .
-*/
-
-#pragma comment(lib, "wintrust")
-#pragma comment(lib, "crypt32.lib")
-
-#include
-#include
-#include // Windows-only plugin (1)
-#include
-#include
-
-#include "manape/utils.h"
-#include "manacommons/utf8/utf8.h"
-
-#include "plugin_framework/plugin_interface.h"
-#include "yara/yara_wrapper.h"
-
-/*
- * (1) I wish I could have implemented this in a platform-independent fashion.
- * Sadly, parsing PKCS#7 is quite annoying, and I was unable to find an easy to use,
- * portable, lightweight cryptography library supporting it. For a while, I considered
- * implementing it on my own, but then I figured that you need to be on Windows to have
- * access to the CAs trusted by the OS anyway.
- */
-
-namespace plugin {
-
-/**
- * @brief Retrieves the information about the publisher / issuer present in the
- * certificate.
- *
- * @param const std::string& file_path The file to analyze.
- * @param plugin::pResult result The result into which the information should be
- * appended.
- */
-void get_certificate_info(const std::wstring& file_path, plugin::pResult result);
-
-/**
- * @brief Helper function designed to create information and insert it into a result
- * based on a read unicode string.
- *
- * The information appended to the result will look like this: "type: data"
- *
- * @param const std::string& type The description of the data.
- * @param const std::wstring& data The contents of the information
- * @param pResult result The result to update.
- *
- */
-void make_information(const std::string& type, const std::wstring& data, pResult result)
-{
- std::stringstream ss;
- std::string out;
- try
- {
- std::vector utf8result;
- utf8::utf16to8(data.begin(), data.end(), std::back_inserter(utf8result));
- out = std::string(utf8result.begin(), utf8result.end());
- }
- catch (utf8::invalid_utf16)
- {
- PRINT_WARNING << "[plugin_authenticode] Couldn't convert a string from UTF-16 to UTF-8!"
- << DEBUG_INFO << std::endl;
- return;
- }
- ss << type << ": " << out;
- result->add_information(ss.str());
-}
-
-/**
- * @brief Looks for well-known company names in the RT_VERSION resource of the PE.
- *
- * The idea behind this check is that if the binary is unsigned but pretends to come from
- * Microsoft, Adobe, etc. then it is very likely a malware.
- *
- * @param const mana::PE& pe The PE to analyze.
- * @param pResult res The result to update if something is found.
- */
-void check_version_info(const mana::PE& pe, pResult res);
-
-/**
- * @brief This plugin uses the Windows API to verify the digital signature of a PE.
- */
-class AuthenticodePlugin : public IPlugin
-{
-public:
- int get_api_version() const override { return 1; }
-
- pString get_id() const override {
- return boost::make_shared("authenticode");
- }
-
- pString get_description() const override {
- return boost::make_shared("Checks if the digital signature of the PE is valid.");
- }
-
- pResult analyze(const mana::PE& pe) override
- {
- pResult res = create_result();
-
- WINTRUST_FILE_INFO file_info;
- memset(&file_info, 0, sizeof(file_info));
- file_info.cbStruct = sizeof(WINTRUST_FILE_INFO);
- std::string path = *pe.get_path();
- std::wstring wide_path(path.begin(), path.end());
- file_info.pcwszFilePath = wide_path.c_str();
-
- WINTRUST_DATA data;
- memset(&data, 0, sizeof(data));
- data.cbStruct = sizeof(data);
- data.dwUIChoice = WTD_UI_NONE;
- data.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; // Check revocations for the whole chain.
- data.dwUnionChoice = WTD_CHOICE_FILE;
- data.dwStateAction = WTD_STATEACTION_VERIFY;
- data.pFile = &file_info;
-
- GUID guid_verify = WINTRUST_ACTION_GENERIC_VERIFY_V2;
- int error_code;
-
- int retval = ::WinVerifyTrust(0, &guid_verify, &data);
- switch (retval)
- {
- case ERROR_SUCCESS:
- res->set_level(SAFE);
- res->set_summary("The PE's digital signature is valid.");
- break;
- case TRUST_E_EXPLICIT_DISTRUST:
- res->set_level(MALICIOUS);
- res->set_summary("The PE's digital signature has been explicitly blacklisted.");
- case TRUST_E_NOSIGNATURE:
- error_code = ::GetLastError();
- if (TRUST_E_NOSIGNATURE == error_code ||
- TRUST_E_SUBJECT_FORM_UNKNOWN == error_code ||
- TRUST_E_PROVIDER_UNKNOWN == error_code)
- {
- // No digital signature.
- break;
- }
- res->set_summary("Unknown error encountered while reading the signature.");
- break;
- case TRUST_E_BAD_DIGEST:
- res->set_level(MALICIOUS);
- res->set_summary("The PE's digital signature is invalid.");
- break;
- case CERT_E_REVOKED:
- res->set_level(MALICIOUS);
- res->set_summary("The PE's certificate was explicitly revoked by its issuer.");
- break;
- case CERT_E_EXPIRED:
- res->set_level(SUSPICIOUS);
- res->set_summary("The PE's certificate has expired.");
- break;
- default:
- std::stringstream ss;
- ss << "Unknown error encountered while reading the signature (0x" << std::hex << retval << ").";
- res->set_summary(ss.str());
- break;
- }
-
- if (res->get_level() != NO_OPINION) {
- get_certificate_info(wide_path, res);
- }
- else { // No certificate: try to determine if the application should be signed.
- check_version_info(pe, res);
- }
-
- // Close a handle that was opened by the verification
- data.dwStateAction = WTD_STATEACTION_CLOSE;
- ::WinVerifyTrust(0, &guid_verify, &data);
-
- return res;
- }
-};
-
-// ----------------------------------------------------------------------------
-
-extern "C"
-{
- PLUGIN_API IPlugin* create() { return new AuthenticodePlugin(); }
- PLUGIN_API void destroy(IPlugin* p) { if (p) delete p; }
-};
-
-// ----------------------------------------------------------------------------
-
-std::string make_error(const std::string& message)
-{
- std::stringstream ss;
- ss << message << " (Windows error code: 0x" << std::hex << ::GetLastError() << ")";
- return ss.str();
-}
-
-// ----------------------------------------------------------------------------
-
-/**
- * @brief Reads the information related to the PE's publisher in the digital signature.
- *
- * @param PCMSG_SIGNER_INFO info A structure returned by CryptMsgGetParam.
- * @param pResult result The result to fill with the obtained information.
- */
-void get_publisher_information(PCMSG_SIGNER_INFO info, pResult result)
-{
- DWORD size;
- DWORD res;
- PSPC_SP_OPUS_INFO opus = nullptr;
-
- for (unsigned int i = 0 ; i < info->AuthAttrs.cAttr ; ++i)
- {
- if (::lstrcmpA(SPC_SP_OPUS_INFO_OBJID, info->AuthAttrs.rgAttr[i].pszObjId) == 0)
- {
- res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
- SPC_SP_OPUS_INFO_OBJID, // OID defining the structure type
- info->AuthAttrs.rgAttr[i].rgValue[0].pbData, // Structure to decode
- info->AuthAttrs.rgAttr[i].rgValue[0].cbData, // Size of the structure
- 0, // Flags
- nullptr, // NULL buffer: return the needed size
- &size); // Destination of the size
-
- if (!res)
- {
- result->add_information(make_error("Could not get certificate information: CryptDecodeObject failed."));
- goto END;
- }
-
- opus = (PSPC_SP_OPUS_INFO) malloc(size);
- if (!opus)
- {
- result->add_information(make_error("Could not get certificate information: malloc failed."));
- goto END;
- }
-
- res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
- SPC_SP_OPUS_INFO_OBJID, // OID defining the structure type
- info->AuthAttrs.rgAttr[i].rgValue[0].pbData, // Structure to decode
- info->AuthAttrs.rgAttr[i].rgValue[0].cbData, // Size of the structure
- 0, // Flags
- opus, // The structure to fill
- &size); // Destination of the size
-
- if (!res)
- {
- result->add_information(make_error("Could not get certificate information: CryptDecodeObject failed."));
- goto END;
- }
-
- if (opus->pwszProgramName != nullptr)
- {
- std::wstring wide_program_name(opus->pwszProgramName);
- make_information("Program name", wide_program_name, result);
- }
- if (opus->pPublisherInfo != nullptr)
- {
- std::wstring wide_publisher_info;
- switch (opus->pPublisherInfo->dwLinkChoice)
- {
- case SPC_URL_LINK_CHOICE:
- wide_publisher_info.assign(opus->pPublisherInfo->pwszUrl);
- break;
- case SPC_FILE_LINK_CHOICE:
- wide_publisher_info.assign(opus->pPublisherInfo->pwszFile);
- break;
- }
- make_information("Publisher information", wide_publisher_info, result);
- }
- if (opus->pMoreInfo != nullptr)
- {
- std::wstring wide_more_info;
- switch (opus->pMoreInfo->dwLinkChoice)
- {
- case SPC_URL_LINK_CHOICE:
- wide_more_info.assign(opus->pMoreInfo->pwszUrl);
- break;
- case SPC_FILE_LINK_CHOICE:
- wide_more_info.assign(opus->pMoreInfo->pwszFile);
- break;
- }
- make_information("Additional information", wide_more_info, result);
- }
- }
- }
-
- END:
- free(opus);
-}
-
-// ----------------------------------------------------------------------------
-
-/**
- * @brief A wrapper around GetCertNameString.
- *
- * It simplifies the process of querying information by hiding the complexity of having to
- * call the function twice (one for the size, and one for the result) and having to allocate
- * memory in between.
- *
- * @param PCCERT_CONTEXT context The certificate context to query
- * @param DWORD type A GetCertNameString argument which is just forwarded.
- * @param DWORD flags A GetCertNameString argument which is just forwarded.
- * @param const std::string& description A description of the queried parameter, tu display in the result.
- * @param pResult result The result to update with the obtained information.
- */
-void GetCertNameString_wrapper(PCCERT_CONTEXT context, DWORD type, DWORD flags, const std::string& description, pResult result)
-{
-
- DWORD size;
- std::stringstream ss;
-
- size = ::CertGetNameString(context, // The certificate context
- type,
- flags,
- nullptr, // ...I'm not too sure what this is.
- nullptr, // Destination buffer is NULL - we want the size for now
- 0); // Size of the destination buffer
-
- if (size == 0)
- {
- result->add_information(make_error("Could not get certificate details: CertGetNameString failed."));
- return;
- }
-
- char* name = (char*) malloc(size);
- if (name == nullptr)
- {
- result->add_information(make_error("Could not get certificate details: malloc failed."));
- return;
- }
-
- if (!::CertGetNameString(context, // The certificate context
- type,
- flags,
- nullptr, // ...I'm not too sure what this is.
- name, // Destination buffer
- size)) // Size of the destination buffer)
- {
- result->add_information(make_error("Could not get certificate details: CertGetNameString failed."));
- goto END;
- }
-
- if (name[0] == '\0') { // No information
- goto END;
- }
-
- result->add_information(description, name);
-
-END:
- free(name);
-}
-
-// ----------------------------------------------------------------------------
-
-void get_certificate_details(PCMSG_SIGNER_INFO info, HCERTSTORE hStore, pResult result)
-{
- CERT_INFO cert_info;
- PCCERT_CONTEXT context;
-
- cert_info.Issuer = info->Issuer;
- cert_info.SerialNumber = info->SerialNumber;
-
- context = ::CertFindCertificateInStore(hStore, // Handle to the certificate store
- X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
- 0, // No flags
- CERT_FIND_SUBJECT_CERT, // Find a certificate matching a serial number and an issuer
- &cert_info, // The data to look for
- nullptr); // NULL on the first call - used to find the next matching certificate.
-
- if (!context)
- {
- result->add_information(make_error("Could not get certificate information: CertFindCertificateInStore failed."));
- return;
- }
-
- GetCertNameString_wrapper(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, "Issued by", result);
- GetCertNameString_wrapper(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, "Issued to", result);
- GetCertNameString_wrapper(context, CERT_NAME_EMAIL_TYPE, 0, "Subject's email", result);
-
- if (!context) {
- ::CertFreeCertificateContext(context);
- }
-}
-
-// ----------------------------------------------------------------------------
-
-void get_certificate_timestamp(PCMSG_SIGNER_INFO info, pResult result)
-{
- PCMSG_SIGNER_INFO cs_info = nullptr;
- FILETIME file_time;
- SYSTEMTIME system_time;
- struct tm time;
- char date_string[100];
- BOOL res;
- DWORD size;
-
-
-
- for (unsigned int i = 0 ; i < info->UnauthAttrs.cAttr ; ++i)
- {
- // 1) Get the CMSG_SIGNER_INFO structure
- if (::lstrcmpA(szOID_RSA_counterSign, info->UnauthAttrs.rgAttr[i].pszObjId) == 0)
- {
- res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
- PKCS7_SIGNER_INFO, // Structure of the data to decode
- info->UnauthAttrs.rgAttr[i].rgValue->pbData, // The data to decode
- info->UnauthAttrs.rgAttr[i].rgValue->cbData, // Size of the data to decode
- 0, // Flags
- nullptr, // Destination buffer is null - we only want the size
- &size); // Put the size here
- if (!res)
- {
- result->add_information(make_error("Could not get certificate timestamp: CryptDecodeObject failed."));
- return;
- }
-
- cs_info = (PCMSG_SIGNER_INFO) malloc(size);
- if (cs_info == nullptr)
- {
- result->add_information(make_error("Could not get certificate timestamp: malloc failed."));
- return;
- }
-
- res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
- PKCS7_SIGNER_INFO, // Structure of the data to decode
- info->UnauthAttrs.rgAttr[i].rgValue->pbData, // The data to decode
- info->UnauthAttrs.rgAttr[i].rgValue->cbData, // Size of the data to decode
- 0, // Flags
- cs_info, // Destination buffer
- &size); // Size of the destination buffer
- if (!res)
- {
- result->add_information(make_error("Could not get certificate timestamp: CryptDecodeObject failed."));
- goto END;
- }
- break;
- }
- }
-
- if (cs_info == nullptr) { // Timestamp unavailable
- goto END;
- }
-
- // 2) Read the FILETIME data from it
- for (unsigned int i = 0 ; i < cs_info->AuthAttrs.cAttr ; ++i)
- {
- if (::lstrcmpA(szOID_RSA_signingTime, cs_info->AuthAttrs.rgAttr[i].pszObjId) == 0)
- {
- size = sizeof(FILETIME);
- res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
- szOID_RSA_signingTime,
- cs_info->AuthAttrs.rgAttr[i].rgValue->pbData,
- cs_info->AuthAttrs.rgAttr[i].rgValue->cbData,
- 0,
- &file_time,
- &size);
- if (!res)
- {
- result->add_information(make_error("Could not get certificate timestamp: CryptDecodeObject failed."));
- goto END;
- }
-
- if (!::FileTimeToSystemTime(&file_time, &system_time))
- {
- result->add_information(make_error("Could not convert certificate timestamp: FileTimeToSystemTime failed."));
- goto END;
- }
-
- time.tm_hour = system_time.wHour;
- time.tm_min = system_time.wMinute;
- time.tm_mday = system_time.wDay;
- time.tm_mon = system_time.wMonth - 1;
- time.tm_sec = system_time.wSecond;
- time.tm_year = system_time.wYear - 1900;
- strftime(date_string, sizeof(date_string), "%Y-%b-%d %H:%M:%S %z", &time);
-
- std::stringstream ss;
- ss << "Signing time: " << date_string;
- result->add_information(ss.str());
- }
- }
-
- END:
- free(cs_info);
-}
-
-// ----------------------------------------------------------------------------
-
-void get_certificate_info(const std::wstring& file_path, pResult result)
-{
- HCERTSTORE hStore = nullptr;
- HCRYPTMSG hMsg = nullptr;
- PCMSG_SIGNER_INFO info = nullptr;
-
- BOOL res = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, // The function targets a file (as opposed to a memory structure)
- file_path.c_str(), // The file to query
- CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, // The content is an embedded PKCS #7 signed message
- CERT_QUERY_FORMAT_FLAG_BINARY, // The content should be returned in binary format
- 0, // Reserved
- 0, // We don't care about the encoding...
- 0, // ...we don't care about the content type...
- 0, // ...and we don't care about the format type either.
- &hStore, // Destination of the certificate store
- &hMsg, // Destination of the message
- nullptr); // No context
- if (!res)
- {
- result->add_information(make_error("Could not get certificate information: CryptQueryObject failed."));
- goto END;
- }
-
- DWORD info_size;
- res = CryptMsgGetParam(hMsg, // Handle to the cryptographic message
- CMSG_SIGNER_INFO_PARAM, // Information about the message signer
- 0, // Index of the required parameter - means nothing here.
- nullptr, // NULL buffer: return the needed size.
- &info_size); // Destination of the size
- if (!res)
- {
- result->add_information(make_error("Could not get certificate information: CryptMsgGetParam failed."));
- goto END;
- }
-
- info = (PCMSG_SIGNER_INFO) malloc(info_size);
- if (!info)
- {
- result->add_information(make_error("Could not get certificate information: malloc failed."));
- goto END;
- }
-
- res = CryptMsgGetParam(hMsg, // Handle to the cryptographic message
- CMSG_SIGNER_INFO_PARAM, // Information about the message signer
- 0, // Index of the required parameter - means nothing here.
- info, // NULL buffer: return the needed size.
- &info_size); // Destination of the size
- if (!res)
- {
- result->add_information(make_error("Could not get certificate information: CryptMsgGetParam failed."));
- goto END;
- }
-
- get_publisher_information(info, result);
- get_certificate_details(info, hStore, result);
- get_certificate_timestamp(info, result);
-
- END:
- free(info);
-
- if (hStore != nullptr) {
- ::CertCloseStore(hStore, 0);
- }
- if (hMsg != nullptr) {
- ::CryptMsgClose(hMsg);
- }
-}
-
-// ----------------------------------------------------------------------------
-
-void check_version_info(const mana::PE& pe, pResult res)
-{
- // Find the VERSION_INFO resource
- auto resources = pe.get_resources();
- mana::pResource version_info;
- for (auto it = resources->begin() ; it != resources->end() ; ++it)
- {
- if (*(*it)->get_type() == "RT_VERSION")
- {
- version_info = *it;
- break;
- }
- }
-
- // No RT_VERSION resource, we're done.
- if (!version_info) {
- return;
- }
-
- yara::Yara y;
- if (!y.load_rules("yara_rules/company_names.yara"))
- {
- std::cerr << "Could not load company_names.yara!" << std::endl;
- return;
- }
- auto m = y.scan_bytes(*version_info->get_raw_data());
- if (m && m->size() > 0)
- {
- std::stringstream ss;
- auto found_strings = m->at(0)->get_found_strings();
- if (found_strings.size() > 0)
- {
- ss << "PE pretends to be from " << *(m->at(0)->get_found_strings().begin())
- << " but is not signed!";
- res->set_summary(ss.str());
- res->raise_level(MALICIOUS);
- }
- }
-}
-
-
-} // !namespace plugin
+/*
+ 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 .
+*/
+
+#include "plugins/plugin_authenticode/plugin_authenticode.h"
+
+namespace plugin {
+
+/**
+ * @brief This plugin uses the Windows API to verify the digital signature of a PE.
+ */
+class AuthenticodePlugin : public IPlugin
+{
+public:
+ int get_api_version() const override { return 1; }
+
+ pString get_id() const override {
+ return boost::make_shared("authenticode");
+ }
+
+ pString get_description() const override {
+ return boost::make_shared("Checks if the digital signature of the PE is valid.");
+ }
+
+ pResult analyze(const mana::PE& pe) override
+ {
+ pResult res = create_result();
+
+ WINTRUST_FILE_INFO file_info;
+ memset(&file_info, 0, sizeof(file_info));
+ file_info.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ std::string path = *pe.get_path();
+ std::wstring wide_path(path.begin(), path.end());
+ file_info.pcwszFilePath = wide_path.c_str();
+
+ WINTRUST_DATA data;
+ memset(&data, 0, sizeof(data));
+ data.cbStruct = sizeof(data);
+ data.dwUIChoice = WTD_UI_NONE;
+ data.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; // Check revocations for the whole chain.
+ data.dwUnionChoice = WTD_CHOICE_FILE;
+ data.dwStateAction = WTD_STATEACTION_VERIFY;
+ data.pFile = &file_info;
+
+ GUID guid_verify = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ do_winverifytrust(guid_verify, data, res);
+
+ if (res->get_level() != NO_OPINION) {
+ get_certificate_info(wide_path, res);
+ }
+ else { // If no certificate was found, check if the file is known in the security catalog:
+ //check_catalog_signature(pe, res);
+ }
+
+ // Still not verified: try to determine if the application should be signed.
+ if (res->get_level() == NO_OPINION) {
+ check_version_info(pe, res);
+ }
+
+ // Close a handle that was opened by the verification
+ data.dwStateAction = WTD_STATEACTION_CLOSE;
+ ::WinVerifyTrust(nullptr, &guid_verify, &data);
+
+ return res;
+ }
+};
+
+// ----------------------------------------------------------------------------
+
+extern "C"
+{
+ PLUGIN_API IPlugin* create() { return new AuthenticodePlugin(); }
+ PLUGIN_API void destroy(IPlugin* p) { if (p) delete p; }
+};
+
+// ----------------------------------------------------------------------------
+
+std::string make_error(const std::string& message)
+{
+ std::stringstream ss;
+ ss << message << " (Windows error code: 0x" << std::hex << ::GetLastError() << ")";
+ return ss.str();
+}
+
+// ----------------------------------------------------------------------------
+
+void get_publisher_information(PCMSG_SIGNER_INFO info, pResult result)
+{
+ DWORD size;
+ DWORD res;
+ PSPC_SP_OPUS_INFO opus = nullptr;
+
+ for (unsigned int i = 0 ; i < info->AuthAttrs.cAttr ; ++i)
+ {
+ if (::lstrcmpA(SPC_SP_OPUS_INFO_OBJID, info->AuthAttrs.rgAttr[i].pszObjId) == 0)
+ {
+ res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
+ SPC_SP_OPUS_INFO_OBJID, // OID defining the structure type
+ info->AuthAttrs.rgAttr[i].rgValue[0].pbData, // Structure to decode
+ info->AuthAttrs.rgAttr[i].rgValue[0].cbData, // Size of the structure
+ 0, // Flags
+ nullptr, // NULL buffer: return the needed size
+ &size); // Destination of the size
+
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate information: CryptDecodeObject failed."));
+ goto END;
+ }
+
+ opus = (PSPC_SP_OPUS_INFO) malloc(size);
+ if (!opus)
+ {
+ result->add_information(make_error("Could not get certificate information: malloc failed."));
+ goto END;
+ }
+
+ res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
+ SPC_SP_OPUS_INFO_OBJID, // OID defining the structure type
+ info->AuthAttrs.rgAttr[i].rgValue[0].pbData, // Structure to decode
+ info->AuthAttrs.rgAttr[i].rgValue[0].cbData, // Size of the structure
+ 0, // Flags
+ opus, // The structure to fill
+ &size); // Destination of the size
+
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate information: CryptDecodeObject failed."));
+ goto END;
+ }
+
+ if (opus->pwszProgramName != nullptr)
+ {
+ std::wstring wide_program_name(opus->pwszProgramName);
+ make_information("Program name", wide_program_name, result);
+ }
+ if (opus->pPublisherInfo != nullptr)
+ {
+ std::wstring wide_publisher_info;
+ switch (opus->pPublisherInfo->dwLinkChoice)
+ {
+ case SPC_URL_LINK_CHOICE:
+ wide_publisher_info.assign(opus->pPublisherInfo->pwszUrl);
+ break;
+ case SPC_FILE_LINK_CHOICE:
+ wide_publisher_info.assign(opus->pPublisherInfo->pwszFile);
+ break;
+ }
+ make_information("Publisher information", wide_publisher_info, result);
+ }
+ if (opus->pMoreInfo != nullptr)
+ {
+ std::wstring wide_more_info;
+ switch (opus->pMoreInfo->dwLinkChoice)
+ {
+ case SPC_URL_LINK_CHOICE:
+ wide_more_info.assign(opus->pMoreInfo->pwszUrl);
+ break;
+ case SPC_FILE_LINK_CHOICE:
+ wide_more_info.assign(opus->pMoreInfo->pwszFile);
+ break;
+ }
+ make_information("Additional information", wide_more_info, result);
+ }
+ }
+ }
+
+ END:
+ free(opus);
+}
+
+// ----------------------------------------------------------------------------
+
+void GetCertNameString_wrapper(PCCERT_CONTEXT context, DWORD type, DWORD flags, const std::string& description, pResult result)
+{
+
+ DWORD size;
+ std::stringstream ss;
+
+ size = ::CertGetNameString(context, // The certificate context
+ type,
+ flags,
+ nullptr, // ...I'm not too sure what this is.
+ nullptr, // Destination buffer is NULL - we want the size for now
+ 0); // Size of the destination buffer
+
+ if (size == 0)
+ {
+ result->add_information(make_error("Could not get certificate details: CertGetNameString failed."));
+ return;
+ }
+
+ char* name = static_cast(malloc(size));
+ if (name == nullptr)
+ {
+ result->add_information(make_error("Could not get certificate details: malloc failed."));
+ return;
+ }
+
+ if (!::CertGetNameString(context, // The certificate context
+ type,
+ flags,
+ nullptr, // ...I'm not too sure what this is.
+ name, // Destination buffer
+ size)) // Size of the destination buffer)
+ {
+ result->add_information(make_error("Could not get certificate details: CertGetNameString failed."));
+ goto END;
+ }
+
+ if (name[0] == '\0') { // No information
+ goto END;
+ }
+
+ result->add_information(description, name);
+
+END:
+ free(name);
+}
+
+// ----------------------------------------------------------------------------
+
+void get_certificate_details(PCMSG_SIGNER_INFO info, HCERTSTORE hStore, pResult result)
+{
+ CERT_INFO cert_info;
+ PCCERT_CONTEXT context;
+
+ cert_info.Issuer = info->Issuer;
+ cert_info.SerialNumber = info->SerialNumber;
+
+ context = ::CertFindCertificateInStore(hStore, // Handle to the certificate store
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
+ 0, // No flags
+ CERT_FIND_SUBJECT_CERT, // Find a certificate matching a serial number and an issuer
+ &cert_info, // The data to look for
+ nullptr); // NULL on the first call - used to find the next matching certificate.
+
+ if (!context)
+ {
+ result->add_information(make_error("Could not get certificate information: CertFindCertificateInStore failed."));
+ return;
+ }
+
+ GetCertNameString_wrapper(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, "Issued by", result);
+ GetCertNameString_wrapper(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, "Issued to", result);
+ GetCertNameString_wrapper(context, CERT_NAME_EMAIL_TYPE, 0, "Subject's email", result);
+
+ if (!context) {
+ ::CertFreeCertificateContext(context);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+void get_certificate_timestamp(PCMSG_SIGNER_INFO info, pResult result)
+{
+ PCMSG_SIGNER_INFO cs_info = nullptr;
+ FILETIME file_time;
+ SYSTEMTIME system_time;
+ struct tm time;
+ char date_string[100];
+ BOOL res;
+ DWORD size;
+
+
+
+ for (unsigned int i = 0 ; i < info->UnauthAttrs.cAttr ; ++i)
+ {
+ // 1) Get the CMSG_SIGNER_INFO structure
+ if (::lstrcmpA(szOID_RSA_counterSign, info->UnauthAttrs.rgAttr[i].pszObjId) == 0)
+ {
+ res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
+ PKCS7_SIGNER_INFO, // Structure of the data to decode
+ info->UnauthAttrs.rgAttr[i].rgValue->pbData, // The data to decode
+ info->UnauthAttrs.rgAttr[i].rgValue->cbData, // Size of the data to decode
+ 0, // Flags
+ nullptr, // Destination buffer is null - we only want the size
+ &size); // Put the size here
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate timestamp: CryptDecodeObject failed."));
+ return;
+ }
+
+ cs_info = static_cast(malloc(size));
+ if (cs_info == nullptr)
+ {
+ result->add_information(make_error("Could not get certificate timestamp: malloc failed."));
+ return;
+ }
+
+ res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, // Encoding
+ PKCS7_SIGNER_INFO, // Structure of the data to decode
+ info->UnauthAttrs.rgAttr[i].rgValue->pbData, // The data to decode
+ info->UnauthAttrs.rgAttr[i].rgValue->cbData, // Size of the data to decode
+ 0, // Flags
+ cs_info, // Destination buffer
+ &size); // Size of the destination buffer
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate timestamp: CryptDecodeObject failed."));
+ goto END;
+ }
+ break;
+ }
+ }
+
+ if (cs_info == nullptr) { // Timestamp unavailable
+ goto END;
+ }
+
+ // 2) Read the FILETIME data from it
+ for (unsigned int i = 0 ; i < cs_info->AuthAttrs.cAttr ; ++i)
+ {
+ if (::lstrcmpA(szOID_RSA_signingTime, cs_info->AuthAttrs.rgAttr[i].pszObjId) == 0)
+ {
+ size = sizeof(FILETIME);
+ res = ::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ szOID_RSA_signingTime,
+ cs_info->AuthAttrs.rgAttr[i].rgValue->pbData,
+ cs_info->AuthAttrs.rgAttr[i].rgValue->cbData,
+ 0,
+ &file_time,
+ &size);
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate timestamp: CryptDecodeObject failed."));
+ goto END;
+ }
+
+ if (!::FileTimeToSystemTime(&file_time, &system_time))
+ {
+ result->add_information(make_error("Could not convert certificate timestamp: FileTimeToSystemTime failed."));
+ goto END;
+ }
+
+ time.tm_hour = system_time.wHour;
+ time.tm_min = system_time.wMinute;
+ time.tm_mday = system_time.wDay;
+ time.tm_mon = system_time.wMonth - 1;
+ time.tm_sec = system_time.wSecond;
+ time.tm_year = system_time.wYear - 1900;
+ strftime(date_string, sizeof(date_string), "%Y-%b-%d %H:%M:%S %z", &time);
+
+ std::stringstream ss;
+ ss << "Signing time: " << date_string;
+ result->add_information(ss.str());
+ }
+ }
+
+ END:
+ free(cs_info);
+}
+
+// ----------------------------------------------------------------------------
+
+void get_certificate_info(const std::wstring& file_path, pResult result)
+{
+ HCERTSTORE hStore = nullptr;
+ HCRYPTMSG hMsg = nullptr;
+ PCMSG_SIGNER_INFO info = nullptr;
+
+ BOOL res = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, // The function targets a file (as opposed to a memory structure)
+ file_path.c_str(), // The file to query
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, // The content is an embedded PKCS #7 signed message
+ CERT_QUERY_FORMAT_FLAG_BINARY, // The content should be returned in binary format
+ 0, // Reserved
+ 0, // We don't care about the encoding...
+ 0, // ...we don't care about the content type...
+ 0, // ...and we don't care about the format type either.
+ &hStore, // Destination of the certificate store
+ &hMsg, // Destination of the message
+ nullptr); // No context
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate information: CryptQueryObject failed."));
+ goto END;
+ }
+
+ DWORD info_size;
+ res = CryptMsgGetParam(hMsg, // Handle to the cryptographic message
+ CMSG_SIGNER_INFO_PARAM, // Information about the message signer
+ 0, // Index of the required parameter - means nothing here.
+ nullptr, // NULL buffer: return the needed size.
+ &info_size); // Destination of the size
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate information: CryptMsgGetParam failed."));
+ goto END;
+ }
+
+ info = static_cast(malloc(info_size));
+ if (!info)
+ {
+ result->add_information(make_error("Could not get certificate information: malloc failed."));
+ goto END;
+ }
+
+ res = CryptMsgGetParam(hMsg, // Handle to the cryptographic message
+ CMSG_SIGNER_INFO_PARAM, // Information about the message signer
+ 0, // Index of the required parameter - means nothing here.
+ info, // NULL buffer: return the needed size.
+ &info_size); // Destination of the size
+ if (!res)
+ {
+ result->add_information(make_error("Could not get certificate information: CryptMsgGetParam failed."));
+ goto END;
+ }
+
+ get_publisher_information(info, result);
+ get_certificate_details(info, hStore, result);
+ get_certificate_timestamp(info, result);
+
+ END:
+ free(info);
+
+ if (hStore != nullptr) {
+ ::CertCloseStore(hStore, 0);
+ }
+ if (hMsg != nullptr) {
+ ::CryptMsgClose(hMsg);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+void do_winverifytrust(GUID& guid, WINTRUST_DATA& data, pResult res)
+{
+ int error_code;
+
+ // According to the documentation, INVALID_HANDLE_VALUE should be accepted here.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa388208(v=vs.85).aspx
+ int retval = ::WinVerifyTrust(static_cast(INVALID_HANDLE_VALUE), &guid, &data);
+ switch (retval)
+ {
+ case ERROR_SUCCESS:
+ res->set_level(SAFE);
+ if (data.dwUnionChoice == WTD_CHOICE_CATALOG) {
+ res->set_summary("The PE is a trusted Microsoft binary.");
+ res->add_information("The file's hash is recognized by Windows.");
+ }
+ else {
+ res->set_summary("The PE's digital signature is valid.");
+ }
+ break;
+ case TRUST_E_EXPLICIT_DISTRUST:
+ res->set_level(MALICIOUS);
+ res->set_summary("The PE's digital signature has been explicitly blacklisted.");
+ break;
+ case TRUST_E_NOSIGNATURE:
+ error_code = ::GetLastError();
+ if (TRUST_E_NOSIGNATURE == error_code ||
+ TRUST_E_SUBJECT_FORM_UNKNOWN == error_code ||
+ TRUST_E_PROVIDER_UNKNOWN == error_code)
+ {
+ // No digital signature.
+ break;
+ }
+ res->set_summary("Unknown error encountered while reading the signature.");
+ break;
+ case TRUST_E_BAD_DIGEST:
+ res->set_level(MALICIOUS);
+ res->set_summary("The PE's digital signature is invalid.");
+ break;
+ case CERT_E_REVOKED:
+ res->set_level(MALICIOUS);
+ res->set_summary("The PE's certificate was explicitly revoked by its issuer.");
+ break;
+ case CERT_E_EXPIRED:
+ res->set_level(SUSPICIOUS);
+ res->set_summary("The PE's certificate has expired.");
+ break;
+ default:
+ std::stringstream ss;
+ ss << "Unknown error encountered while reading the signature (0x" << std::hex << retval << ").";
+ res->set_summary(ss.str());
+ break;
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief A simple function used to translate the PE path into a std::wstring
+ * as is required by Microsoft's API.
+ *
+ * @param s The string to convert.
+ *
+ * @return a std::wstring representing the input.
+ */
+std::wstring multibytetowide_helper(const std::string& s)
+{
+ int len;
+ int input_len = s.length() + 1;
+ len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), input_len, nullptr, 0);
+ auto buffer = new wchar_t[len];
+ MultiByteToWideChar(CP_ACP, 0, s.c_str(), input_len, buffer, len);
+ std::wstring r(buffer);
+ delete[] buffer;
+ return r;
+}
+
+// ----------------------------------------------------------------------------
+
+void make_information(const std::string& type, const std::wstring& data, pResult result)
+{
+ std::stringstream ss;
+ std::string out;
+ try
+ {
+ std::vector utf8result;
+ utf8::utf16to8(data.begin(), data.end(), std::back_inserter(utf8result));
+ out = std::string(utf8result.begin(), utf8result.end());
+ }
+ catch (utf8::invalid_utf16)
+ {
+ PRINT_WARNING << "[plugin_authenticode] Couldn't convert a string from UTF-16 to UTF-8!"
+ << DEBUG_INFO << std::endl;
+ return;
+ }
+ ss << type << ": " << out;
+ result->add_information(ss.str());
+}
+
+// ----------------------------------------------------------------------------
+
+void check_catalog_signature(const mana::PE& pe, pResult res)
+{
+ PVOID context = nullptr;
+ PVOID catalog = nullptr;
+ DWORD size = 0;
+ PBYTE hash_buffer = nullptr;
+ std::wstring wpath(multibytetowide_helper(*pe.get_path()));
+ std::wstringstream ss;
+ std::wstring member_tag;
+ CATALOG_INFO info;
+ WINTRUST_DATA data;
+ WINTRUST_CATALOG_INFO catalog_info;
+ GUID guid_verify = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+
+ // First, obtain the catalog containing the corresponding signatures from Windows.
+ if (!::CryptCATAdminAcquireContext(&context, nullptr, 0)) {
+ return;
+ }
+ auto handle = ::CreateFile(pe.get_path()->c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
+ if (handle == INVALID_HANDLE_VALUE) {
+ goto end;
+ }
+ ::CryptCATAdminCalcHashFromFileHandle(handle, &size, nullptr, 0); // One call to get the size...
+ if (size == 0) {
+ goto end;
+ }
+ hash_buffer = static_cast(calloc(size, 1));
+ if (!::CryptCATAdminCalcHashFromFileHandle(handle, &size, hash_buffer, 0) || hash_buffer == nullptr) { // ...and one to get the hash.
+ goto end;
+ }
+
+ // The hash is used as a reference in the catalog. Convert it to a string.
+ for (unsigned int i = 0; i < size; i++) {
+ ss << boost::wformat(L"%02X") % hash_buffer[i];
+ }
+ member_tag.assign(ss.str());
+
+ catalog = ::CryptCATAdminEnumCatalogFromHash(context, hash_buffer, size, 0, nullptr);
+ if (!catalog || !CryptCATCatalogInfoFromContext(catalog, &info, 0)) {
+ goto end;
+ }
+
+ // We have obtained a valid catalog and its information. Verify that the binary is known.
+ memset(&data, 0, sizeof(WINTRUST_DATA));
+ data.cbStruct = sizeof(WINTRUST_DATA);
+ data.dwUIChoice = WTD_UI_NONE;
+ data.dwUnionChoice = WTD_CHOICE_CATALOG;
+ data.dwStateAction = WTD_STATEACTION_VERIFY;
+ memset(&catalog_info, 0, sizeof(WINTRUST_CATALOG_INFO));
+ catalog_info.cbStruct = sizeof(WINTRUST_CATALOG_INFO);
+ catalog_info.pcwszCatalogFilePath = info.wszCatalogFile;
+ catalog_info.pcwszMemberFilePath = wpath.c_str();
+ catalog_info.pcwszMemberTag = member_tag.c_str();
+ data.pCatalog = &catalog_info;
+ do_winverifytrust(guid_verify, data, res);
+
+ end:
+ if (catalog != nullptr) ::CryptCATAdminReleaseCatalogContext(context, catalog, 0);
+ if (context != nullptr) ::CryptCATAdminReleaseContext(context, 0);
+ if (handle != INVALID_HANDLE_VALUE) ::CloseHandle(handle);
+ if (hash_buffer != nullptr) free(hash_buffer);
+}
+
+
+} // !namespace plugin
diff --git a/plugins/plugin_authenticode/plugin_authenticode.h b/plugins/plugin_authenticode/plugin_authenticode.h
new file mode 100644
index 0000000..69584cd
--- /dev/null
+++ b/plugins/plugin_authenticode/plugin_authenticode.h
@@ -0,0 +1,132 @@
+/*
+ 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 .
+*/
+
+#pragma once
+#pragma comment(lib, "wintrust")
+#pragma comment(lib, "crypt32.lib")
+
+#include
+#include // Windows-only plugin (1)
+#include
+#include
+#include
+#include
+
+#include "manape/utils.h"
+#include "manacommons/utf8/utf8.h"
+
+#include "plugin_framework/plugin_interface.h"
+
+/*
+* (1) I wish I could have implemented this in a platform-independent fashion.
+* Sadly, parsing PKCS#7 is quite annoying, and I was unable to find an easy to use,
+* portable, lightweight cryptography library supporting it. For a while, I considered
+* implementing it on my own, but then I figured that you need to be on Windows to have
+* access to the CAs trusted by the OS anyway.
+*/
+
+namespace plugin
+{
+
+/**
+ * @brief Retrieves the information about the publisher / issuer present in the
+ * certificate.
+ *
+ * @param file_path The file to analyze.
+ * @param result The result into which the information should be
+ * appended.
+ */
+void get_certificate_info(const std::wstring& file_path, plugin::pResult result);
+
+// ----------------------------------------------------------------------------
+
+/**
+* @brief Verifies if a PE is known in a Windows security catalog.
+*
+* Many Microsoft binaries do not embed a certificate. Instead, they are known
+* by hash by the OS. This function verifies whether this is the case.
+*
+* @param pe The PE to assess.
+* @param res A result object to update in case any information is found.
+*/
+void check_catalog_signature(const mana::PE& pe, pResult res);
+
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Helper function designed to create information and insert it into a result
+ * based on a read unicode string.
+ *
+ * The information appended to the result will look like this: "type: data"
+ *
+ * @param type The description of the data.
+ * @param data The contents of the information
+ * @param result The result to update.
+ *
+ */
+void make_information(const std::string& type, const std::wstring& data, pResult result);
+
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Looks for well-known company names in the RT_VERSION resource of the PE.
+ *
+ * Defined in plugin_authenticode_common.cpp.
+ */
+void check_version_info(const mana::PE& pe, pResult res);
+
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Helper function which performs the call to WinVerifyTrust and
+ * updates the result object accordingly.
+ *
+ * @param guid The GUID describing the action to perform.
+ * @param data A structure containing the necessary information to
+ * perform the verification.
+ * res The result object to update.
+ */
+void do_winverifytrust(GUID& guid, WINTRUST_DATA& data, pResult res);
+
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Reads the information related to the PE's publisher in the digital signature.
+ *
+ * @param info A structure returned by CryptMsgGetParam.
+ * @param result The result to fill with the obtained information.
+ */
+void get_publisher_information(PCMSG_SIGNER_INFO info, pResult result);
+
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief A wrapper around GetCertNameString.
+ *
+ * It simplifies the process of querying information by hiding the complexity of having to
+ * call the function twice (one for the size, and one for the result) and having to allocate
+ * memory in between.
+ *
+ * @param context The certificate context to query
+ * @param type A GetCertNameString argument which is just forwarded.
+ * @param flags A GetCertNameString argument which is just forwarded.
+ * @param description A description of the queried parameter, tu display in the result.
+ * @param result The result to update with the obtained information.
+ */
+void GetCertNameString_wrapper(PCCERT_CONTEXT context, DWORD type, DWORD flags, const std::string& description, pResult result);
+
+}
\ No newline at end of file
diff --git a/plugins/plugin_authenticode/plugin_authenticode_commons.cpp b/plugins/plugin_authenticode/plugin_authenticode_commons.cpp
new file mode 100644
index 0000000..6ec7d2f
--- /dev/null
+++ b/plugins/plugin_authenticode/plugin_authenticode_commons.cpp
@@ -0,0 +1,80 @@
+/*
+ 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 .
+*/
+
+#include "plugin_framework/plugin_interface.h"
+#include "yara/yara_wrapper.h"
+
+namespace plugin {
+
+/**
+ * @brief Looks for well-known company names in the RT_VERSION resource of the PE.
+ *
+ * The idea behind this check is that if the binary is unsigned but pretends to come from
+ * Microsoft, Adobe, etc. then it is very likely a malware.
+ *
+ * @param pe The PE to analyze.
+ * @param res The result to update if something is found.
+ */
+void check_version_info(const mana::PE& pe, pResult res)
+{
+ // Find the VERSION_INFO resource
+ auto resources = pe.get_resources();
+ mana::pResource version_info;
+ for (auto it = resources->begin() ; it != resources->end() ; ++it)
+ {
+ if (*(*it)->get_type() == "RT_VERSION")
+ {
+ version_info = *it;
+ break;
+ }
+ }
+
+ // No RT_VERSION resource, we're done.
+ if (!version_info) {
+ return;
+ }
+
+ yara::Yara y;
+ if (!y.load_rules("yara_rules/company_names.yara"))
+ {
+ std::cerr << "Could not load company_names.yara!" << std::endl;
+ return;
+ }
+ auto m = y.scan_bytes(*version_info->get_raw_data());
+ if (m && m->size() > 0)
+ {
+ std::stringstream ss;
+ auto found_strings = m->at(0)->get_found_strings();
+ if (found_strings.size() > 0)
+ {
+ res->set_summary("The programs tries to mislead users about its origins.");
+ if ((m->at(0)->get_metadata()["type"] == "homograph")) {
+ res->add_information("The PE uses homographs to impersonate a well known company!");
+ }
+ else
+ {
+ ss << "The PE pretends to be from " << *(m->at(0)->get_found_strings().begin())
+ << " but is not signed!";
+ res->add_information(ss.str());
+ }
+
+ res->raise_level(MALICIOUS);
+ }
+ }
+}
+
+} // namespace plugin
\ No newline at end of file
diff --git a/plugins/plugin_authenticode_openssl.cpp b/plugins/plugin_authenticode/plugin_authenticode_openssl.cpp
similarity index 95%
rename from plugins/plugin_authenticode_openssl.cpp
rename to plugins/plugin_authenticode/plugin_authenticode_openssl.cpp
index 5f45b7f..826a191 100644
--- a/plugins/plugin_authenticode_openssl.cpp
+++ b/plugins/plugin_authenticode/plugin_authenticode_openssl.cpp
@@ -29,6 +29,13 @@ typedef boost::shared_ptr pBIO;
namespace plugin
{
+/**
+ * @brief Looks for well-known company names in the RT_VERSION resource of the PE.
+ *
+ * Defined in plugin_authenticode_common.cpp.
+ */
+void check_version_info(const mana::PE& pe, pResult res);
+
/**
* @brief Returns the contents of an OpenSSL BIO as a string.
*
@@ -164,8 +171,10 @@ class OpenSSLAuthenticodePlugin : public IPlugin
pResult res = create_result();
auto certs = pe.get_certificates();
- if (certs == nullptr || certs->size() == 0) {
- return res; // No authenticode signature.
+ if (certs == nullptr || certs->size() == 0) // No authenticode signature.
+ {
+ check_version_info(pe, res);
+ return res;
}
for (auto it = certs->begin() ; it != certs->end() ; ++it)