Skip to content

Commit

Permalink
Added further improvements to the import analysis plugin based on
Browse files Browse the repository at this point in the history
suggestions from @henke37 (#9).
Import research functions are now case insensitive by default (an option
was added to control this behavior, test case added as well).
A list of WMI namespaces was added to the suspicious strings.
  • Loading branch information
JusticeRage committed May 11, 2016
1 parent cc63775 commit 7c9cab4
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 20 deletions.
3 changes: 1 addition & 2 deletions bin/manalyze.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# VirusTotal API key. Get yours at https://www.virustotal.com/, or through
# clever GitHub searches.
# VirusTotal API key. Get yours at https://www.virustotal.com/
virustotal.api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# A PE is flagged as suspicious (possibly packed) if there are less imports
Expand Down
15 changes: 13 additions & 2 deletions bin/yara_rules/suspicious_strings.yara
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,18 @@ rule AutoIT_compiled_script
any of them
}

rule WMI_strings
{
meta:
description = "Accesses the WMI"
author = "Ivan Kwiatkowski (@JusticeRage)"
strings:
// WMI namespaces which may be referenced in the ConnectServer call. All in the form of "ROOT\something"
$a0 = /ROOT\\(CIMV2|AccessLogging|ADFS|aspnet|Cli|Hardware|interop|InventoryLogging|Microsoft.{10}|Policy|RSOP|SECURITY|ServiceModel|snmpStandardCimv2|subscription|virtualization|WebAdministration|WMI)/ nocase ascii wide
condition:
any of them
}

rule Misc_Suspicious_Strings
{
meta:
Expand All @@ -700,8 +712,7 @@ rule Misc_Suspicious_Strings
$a2 = "hack" nocase ascii wide
$a3 = "exploit" nocase ascii wide
$a4 = "cmd.exe" nocase ascii wide
$a5 = "CWSandbox" nocase wide ascii // Found in some Zeus/Citadel samples
condition:
any of them
}
}
7 changes: 5 additions & 2 deletions docs/writing-plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,12 @@ In Manalyze, looking up imports is a two-step process. You usually query the lis

You can also use the ``find_imports`` function if you're looking for something specific. For instance::

auto functions = pe.find_imports(".*basic_ostream.*", "MSVCP\\d{3}.dll|KERNEL32.dll");
auto functions = pe.find_imports(".*basic_ostream.*", "MSVCP\\d{3}.dll|KERNEL32.dll", false);

...where the first argument is a regular expression matching the functions to look for, and the second one is a regular expression matching the DLLs to search. You can omit the latter to look for the requested functions in any DLL.
...where the first argument is a regular expression matching the functions to look for, the second one is a regular expression matching the DLLs to search, and the third one is whether the regular expression is case sensitive.
You can omit the latter two to look for the requested functions in any DLL with a case insensitive expression::

auto functions = pe.find_imports(".*bAsIc_OsTrEaM.*"); // Will search in any DLL, case insensitive

Exports
-------
Expand Down
7 changes: 5 additions & 2 deletions include/manape/pe.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class PE
* @param const std::string& function_name_regexp The regular expression selecting function names.
* @param const std::string& dll_name_regexp The regular expression selecting imported dlls into which the
* functions should be searched.
* @param bool case_sensitivity Whethter the regular expression should be case sensitive (default is false).
*
* @return A shared vector containing the matching function names.
*
Expand All @@ -127,7 +128,8 @@ class PE
* Implementation is located in imports.cpp.
*/
DECLSPEC const_shared_strings find_imports(const std::string& function_name_regexp,
const std::string& dll_name_regexp = ".*") const;
const std::string& dll_name_regexp = ".*",
bool case_sensitivity = false) const;

DECLSPEC boost::optional<dos_header> get_dos_header() const {
return _h_dos;
Expand Down Expand Up @@ -359,10 +361,11 @@ class PE
*
* @param const std::string& name_regexp The regular expression used to match DLL names.
* @param std::vector<pimage_library_descriptor>& destination The vector into which the result should be stored.
* @param bool case_sensitivity Whether the regular expression should be case sensitive (default is false).
*
* Implementation is located in imports.cpp.
*/
std::vector<pimage_library_descriptor> _find_imported_dlls(const std::string& name_regexp) const;
std::vector<pimage_library_descriptor> _find_imported_dlls(const std::string& name_regexp, bool case_sensitivity = false) const;

std::string _path;
bool _initialized;
Expand Down
24 changes: 20 additions & 4 deletions manape/imports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,22 @@ const_shared_strings PE::get_imported_functions(const std::string& dll) const

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

std::vector<pimage_library_descriptor> PE::_find_imported_dlls(const std::string& name_regexp) const
std::vector<pimage_library_descriptor> PE::_find_imported_dlls(const std::string& name_regexp,
bool case_sensitivity) const
{
std::vector<pimage_library_descriptor> destination;
if (!_initialized) {
return destination;
}

boost::regex e(name_regexp);
boost::regex e;
if (case_sensitivity) {
e = boost::regex(name_regexp);
}
else {
e = boost::regex(name_regexp, boost::regex::icase);
}

for (std::vector<pimage_library_descriptor>::const_iterator it = _imports.begin() ; it != _imports.end() ; ++it)
{
if (boost::regex_match((*it)->first->NameStr, e)) {
Expand All @@ -215,7 +223,8 @@ std::vector<pimage_library_descriptor> PE::_find_imported_dlls(const std::string
// ----------------------------------------------------------------------------

const_shared_strings PE::find_imports(const std::string& function_name_regexp,
const std::string& dll_name_regexp) const
const std::string& dll_name_regexp,
bool case_sensitivity) const
{
auto destination = boost::make_shared<std::vector<std::string> >();
if (!_initialized) {
Expand All @@ -224,7 +233,14 @@ const_shared_strings PE::find_imports(const std::string& function_name_regexp,

auto matching_dlls = _find_imported_dlls(dll_name_regexp);

boost::regex e(function_name_regexp);
boost::regex e;
if (case_sensitivity) {
e = boost::regex(function_name_regexp);
}
else {
e = boost::regex(function_name_regexp, boost::regex::icase);
}

// Iterate on matching DLLs
for (auto it = matching_dlls.begin() ; it != matching_dlls.end() ; ++it)
{
Expand Down
27 changes: 21 additions & 6 deletions plugins/plugin_imports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ std::string raw_socket_api = "accept|bind|connect|recv|send|gethost(by)?name|ine

std::string wininet_api = "Internet(.*)|WSA(.*)|URLDownloadToFile(A|W)";

std::string registry_api = "Reg(.*)(Key|Value)(.*)|SH(.*)(Reg|Key)(.*)|SHQueryValueEx(A|W)|SHGetValue(A|W)";

std::string process_creation_api = "CreateProcess(.*)|system|WinExec|ShellExecute(A|W)";

std::string process_manipulation_api = "EnumProcess*|OpenProcess|TerminateProcess|ReadProcessMemory|Process32(First|Next)(W)?";
std::string process_manipulation_api = "EnumProcess(.*)|OpenProcess|TerminateProcess|ReadProcessMemory|Process32(First|Next)(W)?";

std::string service_manipulation_api = "OpenSCManager(A|W)|(Open|Control|Create|Delete)Service(A|W)?|QueryService*|"
std::string service_manipulation_api = "OpenSCManager(A|W)|(Open|Control|Create|Delete)Service(A|W)?|QueryService(.*)|"
"ChangeServiceConfig(A|W)|EnumServicesStatus(Ex)?(A|W)";

std::string privilege_api = "AdjustTokenPrivileges|IsNTAdmin|LsaEnumerateLogonSessions|SamQueryInformationUse|"
"SamIGetPrivateData|SfcTerminateWatcherThread|(Zw)?OpenProcessToken(Ex)?|(Zw)?DuplicateToken(Ex)?";
std::string privilege_api = "AdjustTokenPrivileges|IsNTAdmin|LsaEnumerateLogonSessions|SamQueryInformationUser|"
"SamIGetPrivateData|SfcTerminateWatcherThread|(Zw)?OpenProcessToken(Ex)?|(Zw)?DuplicateToken(Ex)?|"
"(SHTest|Check)TokenMembership";

std::string dacl_api = "SetKernelObjectSecurity|SetFileSecurity(A|W)|SetNamedSecurityInfo(A|W)|SetSecurityInfo";

Expand All @@ -56,12 +59,18 @@ std::string packer_api = "VirtualAlloc|VirtualProtect";

std::string temporary_files = "GetTempPath(A|W)|(Create|Write)File(A|W)";

std::string driver_enumeration = "EnumDeviceDrivers|GetDeviceDriver*";
std::string driver_enumeration = "EnumDeviceDrivers|GetDeviceDriver(.*)";

std::string eventlog_deletion = "EvtClearLog|ClearEventLog(A|W)";

std::string screenshot_api = "CreateCompatibleDC|GetDC(Ex)?|FindWindow|PrintWindow|BitBlt";

std::string audio_api = "waveInOpen|DirectSoundCaptureCreate(.*)";

std::string shutdown_functions = "Initiate(System)?Shutdown(Ex)?(A|W)|LockWorkStation|ExitWindows(Ex)?";

std::string networking_api = "(Un)?EnableRouter|SetAdapterIpAddress|SetIp(Forward|Net|Statistics|TTL)(.*)|SetPerTcp(6)?ConnectionEStats";

/**
* @brief Checks the presence of some functions in the PE and updates the
* result accordingly.
Expand Down Expand Up @@ -117,7 +126,7 @@ class ImportsPlugin : public IPlugin
check_functions(pe, dynamic_import, NO_OPINION, "[!] The program may be hiding some of its imports", AT_LEAST_TWO, res);
check_functions(pe, anti_debug, SUSPICIOUS, "Functions which can be used for anti-debugging purposes", AT_LEAST_ONE, res);
check_functions(pe, vanilla_injection, MALICIOUS, "Code injection capabilities", AT_LEAST_THREE, res);
check_functions(pe, "Reg(.*)(Key|Value)(.*)", NO_OPINION, "Can access the registry", AT_LEAST_ONE, res);
check_functions(pe, "", 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' Native API", AT_LEAST_TWO, res);
check_functions(pe, "Crypt(.*)", NO_OPINION, "Uses Microsoft's cryptographic API", AT_LEAST_ONE, res);
Expand All @@ -134,6 +143,12 @@ class ImportsPlugin : public IPlugin
check_functions(pe, eventlog_deletion, MALICIOUS, "Deletes entries from the event log", AT_LEAST_ONE, res);
check_functions(pe, dacl_api, SUSPICIOUS, "Changes object ACLs", AT_LEAST_ONE, res);
check_functions(pe, screenshot_api, SUSPICIOUS, "Can take screenshots", AT_LEAST_TWO, res);
check_functions(pe, audio_api, SUSPICIOUS, "Can use the microphone to record audio.", AT_LEAST_ONE, res);
check_functions(pe, networking_api, SUSPICIOUS, "Modifies the network configuration", AT_LEAST_ONE, res);
check_functions(pe, "GetClipboardData", NO_OPINION, "Reads the contents of the clipboard", AT_LEAST_ONE, res);
check_functions(pe, "IsUserAnAdmin", NO_OPINION, "Checks if it has admin rights", AT_LEAST_ONE, res);
check_functions(pe, "Cert(Add|Open|Register|Remove|Save|Srv|Store)(.*)", SUSPICIOUS, "Interacts with the certificate store", AT_LEAST_ONE, res);
check_functions(pe, shutdown_functions, NO_OPINION, "Can shut the system down or lock the screen", AT_LEAST_ONE, res);

switch (res->get_level())
{
Expand Down
2 changes: 0 additions & 2 deletions plugins/plugin_virustotal/plugin_virustotal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ class VirusTotalPlugin : public IPlugin
}
else if (_config->at("api_key") == "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
{
// If you really can't be bothered, you can find a lot of keys with the following
// GitHub dork: "https://www.virustotal.com/vtapi/v2"
PRINT_WARNING << "Please edit the configuration file with your VirusTotal API key." << std::endl;
return res;
}
Expand Down
16 changes: 16 additions & 0 deletions test/imports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ BOOST_AUTO_TEST_CASE(find_imports_no_match)

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

BOOST_AUTO_TEST_CASE(find_imports_case_insensitivity)
{
mana::PE pe("testfiles/manatest.exe");
auto pfunctions = pe.find_imports("WRITEPROCESSMEMORY");
BOOST_ASSERT(pfunctions);
BOOST_ASSERT(pfunctions->size() == 1);
BOOST_CHECK_EQUAL(pfunctions->at(0), "WriteProcessMemory");

// Try again with case sensitivity on.
pfunctions = pe.find_imports("WRITEPROCESSMEMORY", ".*", true);
BOOST_ASSERT(pfunctions);
BOOST_ASSERT(pfunctions->size() == 0);
}

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

BOOST_AUTO_TEST_CASE(hash_imports)
{
mana::PE pe("testfiles/manatest.exe");
Expand Down

0 comments on commit 7c9cab4

Please sign in to comment.