Skip to content

Commit 2148a5d

Browse files
committed
Removed unbounded regexps which could cause issues with very long import names (fixes #65).
1 parent 08c0d97 commit 2148a5d

5 files changed

Lines changed: 87 additions & 23 deletions

File tree

docs/writing-plugins.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ In Manalyze, looking up imports is a two-step process. You usually query the lis
402402

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

405-
auto dlls = pe.find_imports("WS2_32.dll", false);
405+
auto dlls = pe.find_imported_dlls("WS2_32.dll", false);
406406
407407
...will return all shared libraries imported by the PE matching the regular expression given as the first argument. The second argument controls whether the regular expression is case sensitive and defaults to false when omitted.
408408

include/manape/pe.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ class PE
119119
*/
120120
DECLSPEC const_shared_strings get_imported_functions(const std::string& dll) const;
121121

122+
/**
123+
* @brief Returns the number of unique imported function names across all imported DLLs.
124+
*
125+
* @return The number of unique imported functions.
126+
*
127+
* Implementation is located in imports.cpp.
128+
*/
129+
DECLSPEC size_t count_imported_functions() const;
130+
122131
/**
123132
* @brief Finds imported DLLs whose names match a particular regular expression.
124133
*
@@ -136,20 +145,20 @@ class PE
136145
* @brief Finds imported functions matching regular expressions.
137146
*
138147
* @param const std::string& function_name_regexp The regular expression selecting function names.
139-
* @param const std::string& dll_name_regexp The regular expression selecting imported dlls into which the
140-
* functions should be searched.
148+
* @param const std::optional<std::string>& dll_name_regexp
149+
* Optional regular expression selecting imported DLLs into which
150+
* the functions should be searched. std::nullopt means all DLLs.
141151
* @param bool case_sensitivity Whethter the regular expression should be case sensitive (default is false).
142152
*
143153
* @return A shared vector containing the matching function names.
144154
*
145-
* The default value for dll_name_regexp implies that all DLLs should be searched.
146155
* Note that functions will only be returned if they match the WHOLE input sequence.
147156
* /!\ Warning: Functions imported by ordinal can NOT be found using this function!
148157
*
149158
* Implementation is located in imports.cpp.
150159
*/
151160
DECLSPEC const_shared_strings find_imports(const std::string& function_name_regexp,
152-
const std::string& dll_name_regexp = ".*",
161+
const std::optional<std::string>& dll_name_regexp = std::nullopt,
153162
bool case_sensitivity = false) const;
154163

155164
/**

manape/imports.cpp

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "manape/pe.h"
1919

2020
#include <regex>
21+
#include <set>
2122

2223
namespace mana {
2324

@@ -306,6 +307,36 @@ const_shared_strings PE::get_imported_functions(const std::string& dll) const
306307

307308
// ----------------------------------------------------------------------------
308309

310+
size_t PE::count_imported_functions() const
311+
{
312+
if (!_initialized) {
313+
return 0;
314+
}
315+
316+
std::set<std::string> unique_imports;
317+
for (const auto& library : _imports)
318+
{
319+
auto imports = library->get_imports();
320+
if (imports == nullptr) {
321+
continue;
322+
}
323+
for (const auto& imported_symbol : *imports)
324+
{
325+
std::string name;
326+
if (imported_symbol->Name == "") {
327+
name = *nt::translate_ordinal(imported_symbol->AddressOfData & 0x7FFF, *library->get_name());
328+
}
329+
else {
330+
name = imported_symbol->Name;
331+
}
332+
unique_imports.insert(name);
333+
}
334+
}
335+
return unique_imports.size();
336+
}
337+
338+
// ----------------------------------------------------------------------------
339+
309340
shared_imports PE::find_imported_dlls(const std::string& name_regexp,
310341
bool case_sensitivity) const
311342
{
@@ -335,19 +366,14 @@ shared_imports PE::find_imported_dlls(const std::string& name_regexp,
335366
// ----------------------------------------------------------------------------
336367

337368
const_shared_strings PE::find_imports(const std::string& function_name_regexp,
338-
const std::string& dll_name_regexp,
369+
const std::optional<std::string>& dll_name_regexp,
339370
bool case_sensitivity) const
340371
{
341372
auto destination = std::make_shared<std::vector<std::string> >();
342373
if (!_initialized) {
343374
return destination;
344375
}
345376

346-
auto matching_dlls = find_imported_dlls(dll_name_regexp);
347-
if (!matching_dlls || matching_dlls->empty()) {
348-
return destination;
349-
}
350-
351377
std::regex e;
352378
if (case_sensitivity) {
353379
e = std::regex(function_name_regexp);
@@ -356,19 +382,19 @@ const_shared_strings PE::find_imports(const std::string& function_name_regexp,
356382
e = std::regex(function_name_regexp, std::regex::icase);
357383
}
358384

359-
// Iterate on matching DLLs
360-
for (const auto& it : *matching_dlls)
385+
auto add_matching_imports = [&](const pImportedLibrary& library)
361386
{
362-
auto imported_functions = it->get_imports();
387+
auto imported_functions = library->get_imports();
363388
if (imported_functions == nullptr) {
364-
continue;
389+
return;
365390
}
391+
366392
// Iterate on functions imported by each of these DLLs
367393
for (const auto& it2 : *imported_functions)
368394
{
369395
std::string name;
370396
if (it2->Name == "") {
371-
name = *nt::translate_ordinal(it2->AddressOfData & 0x7FFF, *it->get_name());
397+
name = *nt::translate_ordinal(it2->AddressOfData & 0x7FFF, *library->get_name());
372398
}
373399
else {
374400
name = it2->Name;
@@ -378,7 +404,28 @@ const_shared_strings PE::find_imports(const std::string& function_name_regexp,
378404
destination->push_back(name);
379405
}
380406
}
407+
};
408+
409+
if (dll_name_regexp.has_value())
410+
{
411+
auto matching_dlls = find_imported_dlls(*dll_name_regexp, case_sensitivity);
412+
if (!matching_dlls || matching_dlls->empty()) {
413+
return destination;
414+
}
415+
416+
// Iterate on matching DLLs only.
417+
for (const auto& it : *matching_dlls) {
418+
add_matching_imports(it);
419+
}
381420
}
421+
else
422+
{
423+
// No DLL filter: iterate on all imported DLLs.
424+
for (const auto& it : _imports) {
425+
add_matching_imports(it);
426+
}
427+
}
428+
382429
return destination;
383430
}
384431

plugins/plugin_packer_detection.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ class PackerDetectionPlugin : public IPlugin
166166
// Look for the "Total imports" @comp.id.
167167
if (std::get<0>(*it) == 1)
168168
{
169-
auto imports = pe.find_imports(".*");
169+
auto imports = pe.count_imported_functions();
170170
// For some reason, the number of imports present here seems to be wrong in a lot of goodware.
171171
// It seems however that that number is never smaller than the actual number of imports.
172-
if (std::get<2>(*it) < imports->size())
172+
if (std::get<2>(*it) < imports)
173173
{
174174
if (res->get_summary() == nullptr) {
175175
res->set_summary("The PE is packed or was manually edited.");
@@ -232,10 +232,10 @@ class PackerDetectionPlugin : public IPlugin
232232
}
233233

234234
// A low number of imports indicates that the binary is packed.
235-
mana::const_shared_strings imports = pe.find_imports(".*"); // Get all imports
235+
auto imports = pe.count_imported_functions();
236236

237237
// A single import could indicate that the file is a .NET executable; don't warn about that.
238-
if (imports->size() == 1)
238+
if (imports == 1)
239239
{
240240
auto mscoree = pe.find_imported_dlls("mscoree.dll");
241241
if (!mscoree->empty())
@@ -265,10 +265,10 @@ class PackerDetectionPlugin : public IPlugin
265265
}
266266
}
267267

268-
if (imports->size() < min_imports)
268+
if (imports < min_imports)
269269
{
270270
std::stringstream ss;
271-
ss << "The PE only has " << imports->size() << " import(s).";
271+
ss << "The PE only has " << imports << " import(s).";
272272
res->add_information(ss.str());
273273
res->raise_level(SUSPICIOUS);
274274
}

test/imports.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,21 @@ BOOST_AUTO_TEST_CASE(find_imports_case_insensitivity)
127127
BOOST_CHECK_EQUAL(pfunctions->at(0), "WriteProcessMemory");
128128

129129
// Try again with case sensitivity on.
130-
pfunctions = pe.find_imports("WRITEPROCESSMEMORY", ".*", true);
130+
pfunctions = pe.find_imports("WRITEPROCESSMEMORY", std::nullopt, true);
131131
BOOST_ASSERT(pfunctions);
132132
BOOST_ASSERT(pfunctions->size() == 0);
133133
}
134134

135135
// ----------------------------------------------------------------------------
136136

137+
BOOST_AUTO_TEST_CASE(count_imported_functions)
138+
{
139+
mana::PE pe("testfiles/manatest.exe");
140+
BOOST_CHECK_EQUAL(pe.count_imported_functions(), 56);
141+
}
142+
143+
// ----------------------------------------------------------------------------
144+
137145
BOOST_AUTO_TEST_CASE(hash_imports)
138146
{
139147
mana::PE pe("testfiles/manatest.exe");

0 commit comments

Comments
 (0)