Skip to content

Commit

Permalink
Wrote more unit tests and started orginizing them better.
Browse files Browse the repository at this point in the history
  • Loading branch information
JusticeRage committed Jan 20, 2016
1 parent 4fa827f commit f5798d1
Show file tree
Hide file tree
Showing 13 changed files with 447 additions and 269 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ external/hash-library

# Sphinx build files
docs/_build
docs/html

# Malwares and documentation
resources/*.mal
Expand Down
2 changes: 1 addition & 1 deletion docs/before-contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ All code contributions should make every effort to match Manalyze's coding style
# define PLUGIN_API __attribute__((visibility("default")))
#endif

* Do not import whole namespaces in headers (i.e. ``using namespace std;`` is prohibited).
* Do not import whole namespaces in headers (i.e. ``using namespace std;`` is prohibited).
* Pointer and reference being part of the type, write ``char* s1;`` or ``std::string& s2;`` instead of ``char *s1;`` or ``std::string &s2;``.
* Trigraphs and digraphs are banned.
* Never omit brackets in control structures.
Expand Down
2 changes: 1 addition & 1 deletion docs/writing-plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Modifications to ``CMakeLists.txt``: declare a new library, for instance just un
add_library(plugin_helloworld SHARED plugins/plugin_helloworld.cpp)
target_link_libraries(plugin_helloworld manape hash-library manacommons)

There are some parts missing, but it's okay for now. Points of interest are the mandatory included file(s), and the definition of a new class inheriting from ``plugin::IPlugin`` which defines the interface all plugins must adhere to. Internal plugins contain some additional magic to let the core know about them at startup. If you're building an external plugin, omit those two lines: Manalyze will find them by scanning its folder for library files. Instead, you have to define the ``create`` and ``destroy`` functions so the core can instantiate your plugin.
There are some parts missing, but it's okay for now. Points of interest are the mandatory included file(s), and the definition of a new class inheriting from ``plugin::IPlugin`` which defines the interface all plugins must adhere to. Internal plugins contain some additional magic to let the core know about them at startup. If you're building an external plugin, omit the ``AutoRegister`` instance: Manalyze will find it by scanning its folder for library files. Instead, you have to define the ``create`` and ``destroy`` functions so the core can load and unload your plugin.

If you try to build the plugin right now, you'll see that the compiler is very annoyed about some missing functions. Let's go back to our source file and finish our plugin's implementation::

Expand Down
74 changes: 74 additions & 0 deletions manape/pe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,80 @@ bool PE::_parse_section_table(FILE* f)

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

bool PE::_parse_debug(FILE* f)
{
if (!_ioh) {
return false;
}
if (!_reach_directory(f, IMAGE_DIRECTORY_ENTRY_DEBUG)) { // No debug information.
return true;
}

unsigned int size = 6 * sizeof(boost::uint32_t) + 2 * sizeof(boost::uint16_t);
unsigned int number_of_entries = _ioh->directories[IMAGE_DIRECTORY_ENTRY_DEBUG].Size / size;

for (unsigned int i = 0 ; i < number_of_entries ; ++i)
{
auto debug = boost::make_shared<debug_directory_entry>();
memset(debug.get(), 0, size);
if (size != fread(debug.get(), 1, size, f))
{
PRINT_ERROR << "Could not read the DEBUG_DIRECTORY_ENTRY" << DEBUG_INFO_INSIDEPE << std::endl;
return false;
}

// VC++ Debug information
if (debug->Type == nt::DEBUG_TYPES.at("IMAGE_DEBUG_TYPE_CODEVIEW"))
{
pdb_info pdb;
unsigned int pdb_size = 2 * sizeof(boost::uint32_t) + 16 * sizeof(boost::uint8_t);
memset(&pdb, 0, pdb_size);

unsigned int saved_offset = ftell(f);
fseek(f, debug->PointerToRawData, SEEK_SET);
if (pdb_size != fread(&pdb, 1, pdb_size, f) ||
(pdb.Signature != 0x53445352 && pdb.Signature != 0x3031424E)) // Signature: "RSDS" or "NB10"
{
PRINT_ERROR << "Could not read PDB file information of invalid magic number." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
}
pdb.PdbFileName = utils::read_ascii_string(f); // Not optimal, but it'll help if I decide to
// further parse these debug sub-structures.
debug->Filename = pdb.PdbFileName;
fseek(f, saved_offset, SEEK_SET);
}
else if (debug->Type == nt::DEBUG_TYPES.at("IMAGE_DEBUG_TYPE_MISC"))
{
image_debug_misc misc;
unsigned int misc_size = 2 * sizeof(boost::uint32_t) + 4 * sizeof(boost::uint8_t);
memset(&misc, 1, misc_size);
unsigned int saved_offset = ftell(f);
fseek(f, debug->PointerToRawData, SEEK_SET);
if (misc_size != fread(&misc, 1, misc_size, f))
{
PRINT_ERROR << "Could not read DBG file information" << DEBUG_INFO_INSIDEPE << std::endl;
return false;
}
switch (misc.Unicode)
{
case 1:
misc.DbgFile = utils::read_unicode_string(f, misc.Length - misc_size);
break;
case 0:
misc.DbgFile = utils::read_ascii_string(f, misc.Length - misc_size);
break;
}
debug->Filename = misc.DbgFile;
fseek(f, saved_offset, SEEK_SET);
}
_debug_entries.push_back(debug);
}

return true;
}

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

unsigned int PE::_rva_to_offset(boost::uint64_t rva) const
{
if (!_ioh) // Image Optional Header was not parsed.
Expand Down
82 changes: 4 additions & 78 deletions manape/resources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ bool PE::_parse_resources(FILE* f)
offset = _rva_to_offset(entry.OffsetToData);
if (!offset)
{
PRINT_WARNING << "Could not locate the section containing resource ";
PRINT_WARNING << "Could not locate the section containing resource " << DEBUG_INFO_INSIDEPE;
if (id) {
std::cerr << id;
}
Expand Down Expand Up @@ -214,80 +214,6 @@ bool PE::_parse_resources(FILE* f)

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

bool PE::_parse_debug(FILE* f)
{
if (!_ioh) {
return false;
}
if (!_reach_directory(f, IMAGE_DIRECTORY_ENTRY_DEBUG)) { // No debug information.
return true;
}

unsigned int size = 6*sizeof(boost::uint32_t) + 2*sizeof(boost::uint16_t);
unsigned int number_of_entries = _ioh->directories[IMAGE_DIRECTORY_ENTRY_DEBUG].Size / size;

for (unsigned int i = 0 ; i < number_of_entries ; ++i)
{
auto debug = boost::make_shared<debug_directory_entry>();
memset(debug.get(), 0, size);
if (size != fread(debug.get(), 1, size, f))
{
PRINT_ERROR << "Could not read the DEBUG_DIRECTORY_ENTRY" << DEBUG_INFO_INSIDEPE << std::endl;
return false;
}

// VC++ Debug information
if (debug->Type == nt::DEBUG_TYPES.at("IMAGE_DEBUG_TYPE_CODEVIEW"))
{
pdb_info pdb;
unsigned int pdb_size = 2*sizeof(boost::uint32_t) + 16*sizeof(boost::uint8_t);
memset(&pdb, 0, pdb_size);

unsigned int saved_offset = ftell(f);
fseek(f, debug->PointerToRawData, SEEK_SET);
if (pdb_size != fread(&pdb, 1, pdb_size, f) ||
(pdb.Signature != 0x53445352 && pdb.Signature != 0x3031424E)) // Signature: "RSDS" or "NB10"
{
PRINT_ERROR << "Could not read PDB file information of invalid magic number." << DEBUG_INFO_INSIDEPE << std::endl;
return false;
}
pdb.PdbFileName = utils::read_ascii_string(f); // Not optimal, but it'll help if I decide to
// further parse these debug sub-structures.
debug->Filename = pdb.PdbFileName;
fseek(f, saved_offset, SEEK_SET);
}
else if (debug->Type == nt::DEBUG_TYPES.at("IMAGE_DEBUG_TYPE_MISC"))
{
image_debug_misc misc;
unsigned int misc_size = 2*sizeof(boost::uint32_t) + 4*sizeof(boost::uint8_t);
memset(&misc, 1, misc_size);
unsigned int saved_offset = ftell(f);
fseek(f, debug->PointerToRawData, SEEK_SET);
if (misc_size != fread(&misc, 1, misc_size, f))
{
PRINT_ERROR << "Could not read DBG file information" << DEBUG_INFO_INSIDEPE << std::endl;
return false;
}
switch (misc.Unicode)
{
case 1:
misc.DbgFile = utils::read_unicode_string(f, misc.Length - misc_size);
break;
case 0:
misc.DbgFile = utils::read_ascii_string(f, misc.Length - misc_size);
break;
}
debug->Filename = misc.DbgFile;
fseek(f, saved_offset, SEEK_SET);
}
_debug_entries.push_back(debug);
}

return true;
}

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

shared_bytes Resource::get_raw_data() const
{
auto res = boost::make_shared<std::vector<boost::uint8_t> >();
Expand Down Expand Up @@ -338,15 +264,15 @@ bool parse_version_info_header(vs_version_info_header& header, FILE* f)
// ----------------------------------------------------------------------------

template<>
std::string Resource::interpret_as()
DECLSPEC pString Resource::interpret_as()
{
if (_type != "RT_MANIFEST")
{
PRINT_WARNING << "Resources of type " << _type << "cannot be interpreted as std::strings." << DEBUG_INFO << std::endl;
return "";
return boost::make_shared<std::string>();
}
shared_bytes manifest_bytes = get_raw_data();
return std::string(manifest_bytes->begin(), manifest_bytes->end());
return boost::make_shared<std::string>(manifest_bytes->begin(), manifest_bytes->end());
}

// ----------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions plugins/plugin_packer_detection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const std::vector<std::string> common_names = boost::assign::list_of(".text")
(".reloc")
(".bss")
(".tls")
(".sxdata"); // Apparently related to SafeSEH.
(".sxdata") // Apparently related to SafeSEH.
(".gfids");

// Also check for known packer section names (i.e. UPX0, etc.)
const std::map<std::string, std::string> KNOWN_PACKER_SECTIONS =
Expand Down Expand Up @@ -70,8 +71,7 @@ class PackerDetectionPlugin : public IPlugin
if (common_names.end() == std::find(common_names.begin(), common_names.end(), *(*it)->get_name()))
{
// Check section name against known packer section names and set summary accordingly.
for (auto it2 = KNOWN_PACKER_SECTIONS.begin() ;
it2 != KNOWN_PACKER_SECTIONS.end() ; ++it2)
for (auto it2 = KNOWN_PACKER_SECTIONS.begin() ; it2 != KNOWN_PACKER_SECTIONS.end() ; ++it2)
{
boost::regex e(it2->first, boost::regex::icase);
if (boost::regex_match(*(*it)->get_name(), e)) {
Expand Down
3 changes: 2 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
cmake_minimum_required (VERSION 2.6)
project (manalyze-tests)
include_directories(${PROJECT_SOURCE_DIR}/include)

add_executable(manalyze-tests hash-library.cpp pe.cpp)
add_executable(manalyze-tests fixtures.cpp hash-library.cpp pe.cpp imports.cpp resources.cpp)

target_link_libraries(
manalyze-tests
Expand Down
62 changes: 62 additions & 0 deletions test/fixtures.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

#include "fixtures.h"

SetWorkingDirectory::SetWorkingDirectory()
{
// Save the current working directory
_original_directory = fs::current_path().string();

// Go to the test directory
fs::path working_dir(unit::master_test_suite().argv[0]);
working_dir = working_dir.parent_path();
fs::current_path(working_dir / ".." / "test");
}

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

SetWorkingDirectory::~SetWorkingDirectory() {
fs::current_path(_original_directory);
}

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

void create_file(const fs::path & ph, const std::string & contents)
{
std::ofstream f(ph.c_str());
if (!f)
throw fs::filesystem_error("could not create a file",
ph, bs::error_code(errno, bs::system_category()));
if (!contents.empty()) f << contents;
}

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

SetupFiles::SetupFiles()
{
create_file("fox", "The quick brown fox jumps over the lazy dog");
create_file("empty");
}

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

SetupFiles::~SetupFiles()
{
fs::remove("fox");
fs::remove("empty");
}
51 changes: 12 additions & 39 deletions test/hash-library.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,11 @@
along with Manalyze. If not, see <http://www.gnu.org/licenses/>.
*/

#include <boost/system/api_config.hpp>

#define BOOST_TEST_MODULE ManalyzeTests
#if !defined BOOST_WINDOWS_API
# define BOOST_TEST_DYN_LINK
#endif

#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>

#include "hash-library/hashes.h"

namespace fs = boost::filesystem;
namespace bs = boost::system;
#include "fixtures.h"

BOOST_AUTO_TEST_CASE(hash_phrase)
{
Expand Down Expand Up @@ -58,39 +48,18 @@ BOOST_AUTO_TEST_CASE(null_hash)

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

/**
* Function taken from boost::filesystem's unit tests
*/
void create_file(const fs::path & ph, const std::string & contents = std::string())
// Make sure that null pointers are returned when asked to hash missing files.
BOOST_AUTO_TEST_CASE(hash_missing_file)
{
std::ofstream f(ph.c_str());
if (!f)
throw fs::filesystem_error("could not create a file",
ph, bs::error_code(errno, bs::system_category()));
if (!contents.empty()) f << contents;
hash::const_shared_strings hashes = hash::hash_file(hash::ALL_DIGESTS, "I_DON'T_EXIST.txt");
BOOST_ASSERT(!hashes);
hash::pString h = hash::hash_file(*hash::ALL_DIGESTS.at(ALL_DIGESTS_MD5), "I_DON'T_EXIST.txt");
BOOST_CHECK(!h);
}

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

class SetupFiles {

public:
SetupFiles()
{
create_file("fox", "The quick brown fox jumps over the lazy dog");
create_file("empty");
}

~SetupFiles()
{
fs::remove("fox");
fs::remove("empty");
}
};

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

BOOST_FIXTURE_TEST_SUITE(hash_files, SetupFiles)
// ----------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE(hash_phrase_file)
{
Expand All @@ -101,6 +70,8 @@ BOOST_AUTO_TEST_CASE(hash_phrase_file)
BOOST_CHECK_EQUAL("4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15", hashes->at(ALL_DIGESTS_SHA3));
}

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

BOOST_AUTO_TEST_CASE(null_hash_file)
{
hash::const_shared_strings hashes = hash::hash_file(hash::ALL_DIGESTS, "empty");
Expand All @@ -110,4 +81,6 @@ BOOST_AUTO_TEST_CASE(null_hash_file)
BOOST_CHECK_EQUAL("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", hashes->at(ALL_DIGESTS_SHA3));
}

// ----------------------------------------------------------------------------
BOOST_AUTO_TEST_SUITE_END()
// ----------------------------------------------------------------------------

0 comments on commit f5798d1

Please sign in to comment.