Skip to content

Commit

Permalink
[manape] Parsing stopped too early when an export table contained no
Browse files Browse the repository at this point in the history
names.
[strings] Added rules to detect strings/code ripped from Mimikatz.
Changed the CI script to improve coverage and added the badge at the
top of the repo.
  • Loading branch information
JusticeRage committed Nov 27, 2018
1 parent fddf2c3 commit a1280fe
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 31 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ before_script:
script:
- make
- bin/manalyze --version
- bin/manalyze --help
- bin/manalyze-tests
- bin/hash-library-test
after_success:
- git clone https://github.com/radare/radare2-regressions.git
# Run Manalyze on r2's problematic binaries with all plugins except ClamAV and VirusTotal:
- bin/manalyze -r radare2-regressions/bins/pe/ >/dev/null 2>&1
- bin/manalyze -r radare2-regressions/bins/fuzzed/ -ojson >/dev/null 2>&1
- bin/manalyze -d all -p all test/testfiles/manatest*
- bin/manalyze -d all -p all --hashes --extract /tmp/ test/testfiles/manatest.exe test/testfiles/manatest2.exe >/dev/null
- bin/manalyze -d all -p all --hashes -o json test/testfiles/manatest3.exe >/dev/null
- coveralls --exclude external/yara --exclude test --exclude plugins/plugin_virustotal/json_spirit --exclude examples --exclude radare2-regressions --gcov gcov-4.8 >/dev/null
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Manalyze [![Build Status](https://travis-ci.org/JusticeRage/Manalyze.svg?branch=master)](https://travis-ci.org/JusticeRage/Manalyze) [![Documentation](https://readthedocs.org/projects/manalyze/badge/?version=latest)](https://docs.manalyzer.org/en/latest/)
# Manalyze [![Build Status](https://travis-ci.org/JusticeRage/Manalyze.svg?branch=master)](https://travis-ci.org/JusticeRage/Manalyze) [![Coverage Status](https://coveralls.io/repos/github/JusticeRage/Manalyze/badge.svg?branch=master)](https://coveralls.io/github/JusticeRage/Manalyze?branch=master) [![Documentation](https://readthedocs.org/projects/manalyze/badge/?version=latest)](https://docs.manalyzer.org/en/latest/)

## Introduction
My work on Manalyze started when my antivirus tried to quarantine my malware sample collection for the thirtieth time. It is also born from my increasing frustration with AV products which make decisions without ever explaining why they deem a file malicious.
Expand Down
48 changes: 47 additions & 1 deletion bin/yara_rules/suspicious_strings.yara
Original file line number Diff line number Diff line change
Expand Up @@ -1578,8 +1578,9 @@ rule Misc_Suspicious_Strings
rule BITS_CLSID
{
meta:
description = "References the BITS service"
description = "References the BITS service."
author = "Ivan Kwiatkowski (@JusticeRage)"
show_strings = "false"
// The BITS service seems to be used heavily by EquationGroup.
strings:
$uuid_background_copy_manager_1_5 = { 1F 77 87 F0 4F D7 1A 4C BB 8A E1 6A CA 91 24 EA }
Expand All @@ -1596,3 +1597,48 @@ rule BITS_CLSID
condition:
any of them
}

rule Mimikatz
{
meta:
description = "Contains code from Mimikatz."
author = "Ivan Kwiatkowski (@JusticeRage)"
show_strings = "false"
strings:
$x64_W2K3_SecData = { 48 8d 6e 30 48 8d 0d }
$x64_W2K8_SecData = { 48 8d 94 24 b0 00 00 00 48 8d 0d }
$x64_W2K12_SecData = { 4c 8d 85 30 01 00 00 48 8d 15 }
$x64_W2K12R2_SecData = { 0f b6 4c 24 30 85 c0 0f 45 cf 8a c1 }
$x64_WI52_SysCred = { b9 14 00 00 00 f3 aa 48 8d 3d }
$x64_WI60_SysCred = { 48 8b ca f3 aa 48 8d 3d }
$x64_WI61_SysCred = { 8b ca f3 aa 48 8d 3d }
$x86_W2K3_SecData = { 53 56 8d 45 98 50 b9 }
$x86_W2K8_SecData = { 8b 45 14 83 c0 18 50 b9 }
$x86_WI51_SysCred = { 00 ab 33 c0 bf }
$x86_WI52_SysCred = { 59 33 d2 88 10 40 49 75 }
$x86_WI60_SysCred = { 6a 14 59 b8 }
$x86_WI62_SysCred = { 6a 14 5a 8b f2 b9 }
$x86_WI63_SysCred = { 6a 14 59 8b d1 b8 }
condition:
all of ($x64_*) or all of ($x86_*)
}

rule Mimikatz_2
{
meta:
description = "Contains strings from Mimikatz"
author = "Ivan Kwiatkowski (@JusticeRage)"
strings:
$primary = "Primary" fullword
$credentialkeys = "CredentialKeys" fullword
$bcrypt1 = "BCryptCloseAlgorithmProvider" fullword
$bcrypt2 = "BCryptDestroyKey" fullword
$bcrypt3 = "BCryptDecrypt" fullword
$bcrypt4 = "BCryptEncrypt" fullword
$bcrypt5 = "BCryptGenerateSymmetricKey" fullword
$bcrypt6 = "BCryptGetProperty" fullword
$bcrypt7 = "BCryptSetProperty" fullword
$bcrypt8 = "BCryptOpenAlgorithmProvider" fullword
condition:
$primary and $credentialkeys and 5 of ($bcrypt*)
}
56 changes: 30 additions & 26 deletions manape/pe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -665,41 +665,43 @@ bool PE::_parse_exports()
return false;
}

if (ied.Characteristics != 0) {
_ied.reset(ied);

if (_ied->Characteristics != 0) {
PRINT_WARNING << "IMAGE_EXPORT_DIRECTORY field Characteristics is reserved and should be 0!"
<< DEBUG_INFO_INSIDEPE << std::endl; // TODO: Move to structural plugin?
}
if (ied.NumberOfFunctions == 0) {
if (_ied->NumberOfFunctions == 0) {
return true; // No exports
}

// Read the export name
unsigned int offset = rva_to_offset(ied.Name);
if (!offset || !utils::read_string_at_offset(_file_handle.get(), offset, ied.NameStr))
unsigned int offset = rva_to_offset(_ied->Name);
if (!offset || !utils::read_string_at_offset(_file_handle.get(), offset, _ied->NameStr))
{
PRINT_ERROR << "Could not read the exported DLL name." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}

// Get the address and ordinal of each exported function
offset = rva_to_offset(ied.AddressOfFunctions);
offset = rva_to_offset(_ied->AddressOfFunctions);
if (!offset || fseek(_file_handle.get(), offset, SEEK_SET))
{
PRINT_ERROR << "Could not reach exported functions address table."
<< DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}

for (unsigned int i = 0 ; i < ied.NumberOfFunctions ; ++i)
for (unsigned int i = 0 ; i < _ied->NumberOfFunctions ; ++i)
{
pexported_function ex = boost::make_shared<exported_function>();
if (4 != fread(&(ex->Address), 1, 4, _file_handle.get()))
{
PRINT_ERROR << "Could not read an exported function's address."
<< DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}
ex->Ordinal = ied.Base + i;
ex->Ordinal = _ied->Base + i;

// If the address is located in the export directory, then it is a forwarded export.
image_data_directory export_dir = _ioh->directories[IMAGE_DIRECTORY_ENTRY_EXPORT];
Expand All @@ -709,65 +711,67 @@ bool PE::_parse_exports()
if (!offset || !utils::read_string_at_offset(_file_handle.get(), offset, ex->ForwardName))
{
PRINT_ERROR << "Could not read a forwarded export name." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}
}

_exports.push_back(ex);
}

if (_ied->NumberOfNames == 0) {
return true;
}

// Associate possible exported names with the RVAs we just obtained. First, read the name and ordinal table.
boost::scoped_array<boost::uint32_t> names;
boost::scoped_array<boost::uint16_t> ords;
try
{
// ied.NumberOfNames is an untrusted value. Allocate in a try-catch block to prevent crashes. See issue #1.
names.reset(new boost::uint32_t[ied.NumberOfNames]);
ords.reset(new boost::uint16_t[ied.NumberOfNames]);
names.reset(new boost::uint32_t[_ied->NumberOfNames]);
ords.reset(new boost::uint16_t[_ied->NumberOfNames]);
}
catch (const std::bad_alloc&)
{
PRINT_ERROR << "Could not allocate an array big enough to hold exported name RVAs. This PE may have been manually crafted."
<< DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}
offset = rva_to_offset(ied.AddressOfNames);
offset = rva_to_offset(_ied->AddressOfNames);
if (!offset || fseek(_file_handle.get(), offset, SEEK_SET))
{
PRINT_ERROR << "Could not reach exported function's name table." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}

if (ied.NumberOfNames * sizeof(boost::uint32_t) != fread(names.get(), 1, ied.NumberOfNames * sizeof(boost::uint32_t), _file_handle.get()))
if (_ied->NumberOfNames * sizeof(boost::uint32_t) != fread(names.get(), 1, _ied->NumberOfNames * sizeof(boost::uint32_t), _file_handle.get()))
{
PRINT_ERROR << "Could not read an exported function's name address." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}

offset = rva_to_offset(ied.AddressOfNameOrdinals);
offset = rva_to_offset(_ied->AddressOfNameOrdinals);
if (!offset || fseek(_file_handle.get(), offset, SEEK_SET))
{
PRINT_ERROR << "Could not reach exported functions NameOrdinals table." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}
if (ied.NumberOfNames * sizeof(boost::uint16_t) != fread(ords.get(), 1, ied.NumberOfNames * sizeof(boost::uint16_t), _file_handle.get()))
if (_ied->NumberOfNames * sizeof(boost::uint16_t) != fread(ords.get(), 1, _ied->NumberOfNames * sizeof(boost::uint16_t), _file_handle.get()))
{
PRINT_ERROR << "Could not read an exported function's name ordinal." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}

// Now match the names with with the exported addresses.
for (unsigned int i = 0 ; i < ied.NumberOfNames ; ++i)
for (unsigned int i = 0 ; i < _ied->NumberOfNames ; ++i)
{
offset = rva_to_offset(names[i]);
if (!offset || ords[i] >= _exports.size() || !utils::read_string_at_offset(_file_handle.get(), offset, _exports.at(ords[i])->Name))
{
PRINT_ERROR << "Could not match an export name with its address!" << DEBUG_INFO_INSIDEPE << std::endl;
return false;
return true;
}
}

_ied.reset(ied);
return true;
}

Expand Down
4 changes: 2 additions & 2 deletions plugins/plugins_yara.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class YaraPlugin : public IPlugin
* @param summary The summary to set if there is a match.
* @param level The threat level to set if there is a match.
* @param meta_field_name The meta field name (of the yara rule) to query to
* extract results.
* extract results.
* @param show_strings Adds the matched strings/patterns to the result.
* @param callback A post-processing function to accept or reject matches.
*
Expand Down Expand Up @@ -93,7 +93,7 @@ class YaraPlugin : public IPlugin
}

found_valid = true;
if (!show_strings) {
if (!show_strings || (it->operator[]("show_strings") == "false")) {
res->add_information(it->operator[](meta_field_name));
}
else
Expand Down
4 changes: 4 additions & 0 deletions src/dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ void dump_exports(const mana::PE& pe, io::OutputFormatter& formatter)
for (auto it = exports->begin() ; it != exports->end() ; ++it)
{
// TODO: Demangle C++ names here
auto name = (*it)->Name;
if (name.empty()) {
name = "(Unnamed function)";
}
io::pNode ex(new io::OutputTreeNode((*it)->Name, io::OutputTreeNode::LIST));
ex->append(boost::make_shared<io::OutputTreeNode>("Ordinal", (*it)->Ordinal));
ex->append(boost::make_shared<io::OutputTreeNode>("Address", (*it)->Address, io::OutputTreeNode::HEX));
Expand Down

0 comments on commit a1280fe

Please sign in to comment.