Skip to content

Commit

Permalink
[plugin_resources] Added a test that tries to determine the timezone …
Browse files Browse the repository at this point in the history
…of the compilation machine.

[plugin_resources] No longer raise alerts for deltas of a few seconds between the PE and resource timestamps.
[plugin_imports]   Fixed registry edition functions not being displayed anymore.
  • Loading branch information
JusticeRage committed Nov 22, 2018
1 parent 979c865 commit b606b30
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 74 deletions.
38 changes: 26 additions & 12 deletions include/manape/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@
#define DECLSPEC
#endif

namespace btime = boost::posix_time;

namespace utils
{

typedef boost::shared_ptr<std::string> pString;
typedef boost::shared_ptr<btime::ptime> pptime;

// Disable the "unary minus operator applied to unsigned type" warning.
#pragma warning(push)
Expand Down Expand Up @@ -156,23 +159,34 @@ double DECLSPEC shannon_entropy(const std::vector<boost::uint8_t>& bytes);
// ----------------------------------------------------------------------------

/**
* @brief Converts a POSIX timestamp into a human-readable string.
*
* @param uint32_t epoch_timestamp The timestamp to convert.
*
* @return A human readable string representing the given timestamp.
*/
* @brief Converts a POSIX timestamp into a human-readable string.
*
* @param uint32_t epoch_timestamp The timestamp to convert.
*
* @return A human readable string representing the given timestamp.
*/
pString DECLSPEC timestamp_to_string(boost::uint64_t epoch_timestamp);

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

/**
* @brief Converts a DosDate timestamp into a human-readable string.
*
* @param uint32_t dosdate The timestamp to convert.
*
* @return A human readable string representing the given timestamp.
*/
* @brief Converts a DosDate timestamp into a boost::time object.
*
* @param uint32_t dosdate The timestamp to convert.
*
* @return A shared boost ptime object representing the given timestamp.
*/
pptime DECLSPEC dosdate_to_btime(boost::uint32_t dosdate);

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

/**
* @brief Converts a DosDate timestamp into a human-readable string.
*
* @param uint32_t dosdate The timestamp to convert.
*
* @return A human readable string representing the given timestamp.
*/
pString DECLSPEC dosdate_to_string(boost::uint32_t dosdate);

}
75 changes: 42 additions & 33 deletions manape/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

#include "manape/utils.h"

namespace btime = boost::posix_time;

namespace utils {

std::string read_ascii_string(FILE* f, unsigned int max_bytes)
Expand Down Expand Up @@ -171,41 +169,52 @@ pString timestamp_to_string(boost::uint64_t epoch_timestamp)

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

pString dosdate_to_string(boost::uint32_t dosdate)
pptime dosdate_to_btime(boost::uint32_t dosdate)
{
static std::locale loc(std::cout.getloc(), new btime::time_facet("%Y-%b-%d %H:%M:%S%F %z"));
boost::uint16_t date = dosdate >> 16;
boost::uint16_t time = dosdate & 0xFFFF;
boost::uint16_t year = ((date & 0xFE00) >> 9) + 1980;
boost::uint16_t month = (date & 0x1E0) >> 5;
boost::uint16_t day = date & 0x1F;
boost::uint16_t hour = (time & 0xF800) >> 11;
boost::uint16_t minute = (time & 0x7E0) >> 5;
boost::uint16_t second = (time & 0x1F) << 1;
if (second == 60) {
second = 59;
}
std::stringstream ss;
ss.imbue(loc);
boost::uint16_t date = dosdate >> 16;
boost::uint16_t time = dosdate & 0xFFFF;
boost::uint16_t year = ((date & 0xFE00) >> 9) + 1980;
boost::uint16_t month = (date & 0x1E0) >> 5;
boost::uint16_t day = date & 0x1F;
boost::uint16_t hour = (time & 0xF800) >> 11;
boost::uint16_t minute = (time & 0x7E0) >> 5;
boost::uint16_t second = (time & 0x1F) << 1;
if (second == 60) {
second = 59;
}

if (dosdate == 0) {
return boost::make_shared<btime::ptime>(btime::ptime(boost::gregorian::date(1980, 1, 1)));
}

try {
return boost::make_shared<btime::ptime>(btime::ptime(boost::gregorian::date(year, month, day), btime::hours(hour) + btime::minutes(minute) + btime::seconds(second)));
}
catch (std::exception&)
{
PRINT_WARNING << "Tried to convert an invalid DosDate: " << dosdate << "." << DEBUG_INFO << std::endl;
return pptime();
}
}

if (dosdate == 0)
{
btime::ptime t(boost::gregorian::date(1980, 1, 1));
ss << t;
return boost::make_shared<std::string>(ss.str());
}
// ----------------------------------------------------------------------------

try
{
btime::ptime t(boost::gregorian::date(year, month, day), btime::hours(hour) + btime::minutes(minute) + btime::seconds(second));
ss << t;
}
catch (std::exception&)
{
PRINT_WARNING << "Tried to convert an invalid DosDate: " << dosdate << "." << DEBUG_INFO << std::endl;
ss << boost::posix_time::from_time_t(0) << " (ERROR)";
}
pString dosdate_to_string(boost::uint32_t dosdate)
{
static std::locale loc(std::cout.getloc(), new btime::time_facet("%Y-%b-%d %H:%M:%S%F %z"));
std::stringstream ss;
ss.imbue(loc);
const auto time = dosdate_to_btime(dosdate);
if (time) {
ss << *time;
}
else {
ss << boost::posix_time::from_time_t(0) << " (ERROR)";
}

return boost::make_shared<std::string>(ss.str());
}

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

} // namespace utils
5 changes: 3 additions & 2 deletions plugins/plugin_imports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "plugin_framework/auto_register.h"

#include "manacommons/color.h"
#include <yara/atoms.h>

namespace plugin {

Expand Down Expand Up @@ -145,7 +146,7 @@ bool check_functions(const mana::PE& pe,
REQUIREMENT req,
pResult res)
{
mana::const_shared_strings found_imports = pe.find_imports(func_regex);
auto found_imports = pe.find_imports(func_regex);
if (found_imports && count_functions(*found_imports) >= static_cast<unsigned int>(req)) // Safe cast: these are positive enum indexes
{
res->raise_level(level);
Expand Down Expand Up @@ -223,7 +224,7 @@ class ImportsPlugin : public IPlugin
check_functions(pe, power_loader, MALICIOUS, "Code injection capabilities (PowerLoader)", AT_LEAST_TWO, res);
check_functions(pe, atom_bombing, MALICIOUS, "Code injection capabilities (atom bombing)", AT_LEAST_THREE, res);
check_functions(pe, process_doppelganging, MALICIOUS, "Code injection capabilities (process doppelganging)", AT_LEAST_THREE, res);
check_functions(pe, "", NO_OPINION, "Can access the registry", AT_LEAST_ONE, res);
check_functions(pe, registry_api, NO_OPINION, "Can access the registry", AT_LEAST_ONE, res);
check_functions(pe, process_creation_api, NO_OPINION, "Possibly launches other programs", AT_LEAST_ONE, res);
check_functions(pe, "(Nt|Zw).*", SUSPICIOUS, "Uses Windows's Native API", AT_LEAST_TWO, res);
check_functions(pe, "Crypt.*", NO_OPINION, "Uses Microsoft's cryptographic API", AT_LEAST_ONE, res);
Expand Down
110 changes: 83 additions & 27 deletions plugins/plugin_resources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,91 @@ class ResourcesPlugin : public IPlugin
return boost::make_shared<std::string>("resources");
}

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

pString get_description() const override {
return boost::make_shared<std::string>("Analyzes the program's resources.");
}

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

/**
* @brief This function inspects the timestamps of the resources.
*
* It makes sure that the reported timestamps match the one contained in the PE header.
*
* @param pe The PE file to analyze.
* @param res The result object to fill.
*/
void check_resource_timestamps(const mana::PE& pe, pResult res)
{
const auto r = pe.get_resources();
const auto pe_timestamp = boost::posix_time::from_time_t(pe.get_pe_header()->TimeDateStamp);
auto timestamps = std::set<std::string>();
auto timezones = std::set<int>();
for (const auto& it : *r)
{
if (it->get_timestamp() == 0) { // Ignore empty timestamps.
continue;
}

// Create a set of timestamps which differ from the one reported in the PE header.
const auto res_timestamp = utils::dosdate_to_btime(it->get_timestamp());
if (!res_timestamp) { // Ignore un-convertable timestamps.
continue;
}

auto delta = *res_timestamp - pe_timestamp;
// There might be a slight delta between the PE timestamp and the one found in the resources.
// Assume nobody will tamper them to fake the compilation date by less than 12 hours.
if (delta > btime::hours(12) || delta < btime::hours(-12)) {
timestamps.insert(*utils::dosdate_to_string(it->get_timestamp()));
}

// I have noticed that some timestamps differ from exactly [1-12] hours.
// Could it be that something in the build chain uses local timestamps?
// Report it if we have a delta of exactly 1-12h.

auto hours = delta.hours();
// There can be a delta of 1 second between the two timestamps, possibly due to delays during the compilation.
// Account for it by rounding up to the next hour if needed.
if (abs(delta.minutes()) == 59 && abs(delta.seconds()) > 50)
{
if (hours < 0) {
hours -= 1;
}
else {
hours += 1;
}
}

if (hours != 0 && abs(hours) <= 12 &&
timezones.find(hours) == timezones.end())
{
if (abs(delta.minutes()) == 59 || abs(delta.minutes()) <= 1) {
std::stringstream ss;
ss << "The binary may have been compiled on a machine on the UTC" << std::showpos << hours << " timezone.";
res->add_information(ss.str());
timezones.insert(hours);
}
}
}
if (!timestamps.empty()) // New timestamps have been found.
{
res->raise_level(SUSPICIOUS);
res->set_summary("The PE header may have been manually modified.");
auto info = boost::make_shared<io::OutputTreeNode>("The resource timestamps differ from the PE header",
io::OutputTreeNode::STRINGS,
io::OutputTreeNode::NEW_LINE);
for (const auto& timestamp : timestamps) {
info->append(timestamp);
}
res->add_information(info);
}
}

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

pResult analyze(const mana::PE& pe) override
{
pResult res = create_result();
Expand Down Expand Up @@ -100,33 +181,8 @@ class ResourcesPlugin : public IPlugin
}
}

// Check for anomalies in resource timestamps. Compare them as strings as they are not in the same format.
const auto pe_timestamp = utils::timestamp_to_string(pe.get_pe_header()->TimeDateStamp);
auto timestamps = std::set<std::string>();
for (const auto& it : *r)
{
if (it->get_timestamp() == 0) { // Ignore empty timestamps.
continue;
}

// Create a set of timestamps which differ from the one reported in the PE header.
const auto res_timestamp = utils::dosdate_to_string(it->get_timestamp());
if (*res_timestamp != *pe_timestamp) {
timestamps.insert(*res_timestamp);
}
}
if (!timestamps.empty()) // New timestamps have been found.
{
res->raise_level(SUSPICIOUS);
res->set_summary("The PE header may have been manually modified.");
auto info = boost::make_shared<io::OutputTreeNode>("The resource timestamps differ from the PE header",
io::OutputTreeNode::STRINGS,
io::OutputTreeNode::NEW_LINE);
for (const auto& timestamp : timestamps) {
info->append(timestamp);
}
res->add_information(info);
}
// Check for anomalies in the resource timestamps.
check_resource_timestamps(pe, res);

const double ratio = static_cast<double>(size) / static_cast<double>(pe.get_filesize());
if (ratio > .75)
Expand Down

0 comments on commit b606b30

Please sign in to comment.