diff --git a/include/retdec/unpacker/lib_loader.h b/include/retdec/unpacker/lib_loader.h deleted file mode 100644 index 72bc2836..00000000 --- a/include/retdec/unpacker/lib_loader.h +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @file include/retdec/unpacker/lib_loader.h - * @brief Wrapper for multiplatform loading of dynamic libraries. - * @copyright (c) 2017 Avast Software, licensed under the MIT license - */ - -#ifndef RETDEC_UNPACKER_LIB_LOADER_H -#define RETDEC_UNPACKER_LIB_LOADER_H - -#include - -#include "retdec/utils/os.h" - -#ifdef OS_WINDOWS - #include -#else - #include -#endif - -namespace retdec { -namespace unpackertool { - -#ifdef OS_WINDOWS - using LibHandle = HMODULE; - using FuncHandle = FARPROC; - - static inline LibHandle LOAD_LIBRARY(const std::string& path) - { - return LoadLibraryA(path.c_str()); - } - - static inline FuncHandle GET_FUNCTION(LibHandle& lib, const std::string& name) - { - return GetProcAddress(lib, name.c_str()); - } - - static inline void UNLOAD_LIBRARY(LibHandle& handle) - { - FreeLibrary(handle); - } - - static inline std::string GET_LAST_ERROR() - { - // @todo: Implement on windows - return std::string(); - } -#else - using LibHandle = void*; - using FuncHandle = void*; - - static inline LibHandle LOAD_LIBRARY(const std::string& path) - { - return dlopen(path.c_str(), RTLD_NOW); - } - - static inline FuncHandle GET_FUNCTION(LibHandle& lib, const std::string& name) - { - return dlsym(lib, name.c_str()); - } - - static inline void UNLOAD_LIBRARY(LibHandle& handle) - { - dlclose(handle); - } - - static inline std::string GET_LAST_ERROR() - { - const char* errorMsg = dlerror(); - if (errorMsg == nullptr) - return std::string(); - - return std::string(errorMsg); - } -#endif - -/** - * @brief Abstract loader of dynamic libraries. - * - * Performs the multiplatform loading of dynamic libraries. It is required to have - * LOAD_LIBRARY, GET_FUNCTION and UNLOAD_LIBRARY functions implemented on the targeted platform. - * Also LibHandle and FuncHandle types need to be defined. - */ -class LibLoader -{ -public: - /** - * Loads the specified dynamic library. - * - * @param path Name of the dynamic library. - * - * @return The handle to the loaded library. - */ - static LibHandle loadLibrary(const std::string& path) - { - return LOAD_LIBRARY(path); - } - - /** - * Loads the specified function from the dynamic library. - * - * @tparam FuncType Type of the function loaded. - * - * @param handle Handle of the loaded library returned by @ref loadLibrary. - * @param name The name of the function to load. - * - * @return The pointer to the loaded function. - */ - template static FuncType loadFunction(LibHandle& handle, const std::string& name) - { - return reinterpret_cast(reinterpret_cast(GET_FUNCTION(handle, name.c_str()))); - } - - /** - * Unloads the specified dynamic library from memory. - * - * @param handle Handle to the loaded library. - */ - static void unloadLibrary(LibHandle handle) - { - UNLOAD_LIBRARY(handle); - } - - /** - * Returns the last error in case of error during the library loading. - * - * @return Error string. - */ - static std::string getLastError() - { - return GET_LAST_ERROR(); - } -}; - -} // namespace unpackertool -} // namespace retdec - -#endif diff --git a/include/retdec/unpacker/plugin.h b/include/retdec/unpacker/plugin.h index a1d4f400..fbfaf042 100644 --- a/include/retdec/unpacker/plugin.h +++ b/include/retdec/unpacker/plugin.h @@ -8,39 +8,17 @@ #define RETDEC_UNPACKER_PLUGIN_H #include +#include #include #include -#include "retdec/unpacker/lib_loader.h" #include "retdec/unpacker/unpacker_exception.h" +#define plugin(T) retdec::unpackertool::Plugin::instance() + namespace retdec { namespace unpackertool { -#ifdef _MSC_VER -#define REGISTER_PLUGIN_EXPORT_SPEC __declspec(dllexport) -#else -#define REGISTER_PLUGIN_EXPORT_SPEC -#endif - -#define EXPAND(id) #id -#define MAKE_STRING(id) EXPAND(id) - -#define REGISTER_PLUGIN_FUNCTION_ID registerPlugin -#define REGISTER_PLUGIN_FUNCTION_NAME MAKE_STRING(REGISTER_PLUGIN_FUNCTION_ID) -#define REGISTER_PLUGIN(PluginType) \ - Plugin* _plugin = nullptr; \ - extern "C" REGISTER_PLUGIN_EXPORT_SPEC Plugin* REGISTER_PLUGIN_FUNCTION_ID() { \ - if (_plugin != nullptr) return _plugin; \ - _plugin = new PluginType(); \ - return _plugin; \ - } -#define MAKE_PLUGIN_SHARED(PluginType) \ - extern "C" REGISTER_PLUGIN_EXPORT_SPEC Plugin* REGISTER_PLUGIN_FUNCTION_ID(); \ - static inline PluginType* this_plugin() { \ - return static_cast(REGISTER_PLUGIN_FUNCTION_ID()); \ - } - /** * Exit code of the plugin from Plugin::unpack method. */ @@ -61,15 +39,10 @@ enum PluginExitCode * 1. Create new folder for your plugin in unpackertool/plugins/ and add 'add_subdirectory(YOUR_PLUGIN)' into unpackertool/plugins/CMakeLists.txt. * 2. Create CMakeLists.txt in your new folder based on the template in unpackertool/plugins/example/ and uncomment install target. * 3. Subclass Plugin class while - * - Providing all data in init() method to info attribute (see @ref Plugin::Info). + * - Providing all data constructor to info attribute (see @ref Plugin::Info). * - Providing implementation of Plugin::prepare method. * - Providing implementation of Plugin::unpack method. * - Providing implementation of Plugin::cleanup method. - * 4. Put macro REGISTER_PLUGIN(YOUR_PLUGIN_CLASS) below your Plugin class declaration. - * - In case your Plugin class declaration & definition are separated (*.cpp & *.h file), you have to put it into *.cpp file - * - In case you want to access your Plugin object from different parts of the code in your plugin, you have to separate your declaration & defintion - * and put MAKE_PLUGIN_SHARED(YOUR_PLUGIN_CLASS) into *.h file below your declaration. You can then use inlined function @c this_plugin() - * which is provided by the used macro. */ class Plugin { @@ -172,11 +145,6 @@ public: return _cachedExitCode; } - /** - * Pure virtual method that performs initialization of plugin after it is created. - */ - virtual void init() = 0; - /** * Pure virtual method that performs preparation of unpacking. */ @@ -192,26 +160,6 @@ public: */ virtual void cleanup() = 0; - /** - * Gets the library handle. - * - * @return The loaded library handle. - */ - LibHandle getHandle() - { - return _libHandle; - } - - /** - * Sets the library handle. - * - * @param handle Handle of the loaded library. - */ - void setHandle(LibHandle handle) - { - _libHandle = handle; - } - /** * Prints the message on the standard output prepending the message with '[PLUGIN-NAME]'. * End of line is automatically inserted at the end of the message. @@ -238,6 +186,19 @@ public: Plugin::logImpl(std::cerr, "[ERROR] [", getInfo()->name, "] ", args...); } + /** + * Returns the instance of specific type of plugin. This should be the only way + * how plugin instances are obtained. + * + * @return Plugin instance. + */ + template + static T* instance() + { + static std::unique_ptr pluginInstance = std::make_unique(); + return pluginInstance.get(); + } + protected: Plugin() : _cachedExitCode(PLUGIN_EXIT_UNPACKED) {} Plugin(const Plugin&); @@ -247,7 +208,6 @@ protected: Plugin::Arguments startupArgs; ///< Startup arguments of the plugin. private: - LibHandle _libHandle; ///< Handle of the library that represents this plugin. PluginExitCode _cachedExitCode; ///< Cached exit code of the plugin for the unpacked file. template static void logImpl(std::ostream& out, const T& data, const Args&... args) diff --git a/src/unpackertool/CMakeLists.txt b/src/unpackertool/CMakeLists.txt index 9754d4a9..32d49a2d 100644 --- a/src/unpackertool/CMakeLists.txt +++ b/src/unpackertool/CMakeLists.txt @@ -8,24 +8,8 @@ set(UNPACKERTOOL_SOURCES add_executable(retdec-unpackertool ${UNPACKERTOOL_SOURCES}) set_target_properties(retdec-unpackertool PROPERTIES OUTPUT_NAME "retdec-unpacker") -# There are multiple cases for linking because of the plugin model and different behavior under Windows and Linux -# 1. The libraries that are used only in the unpacker core are linked standardly -# 2. The libraries that are used in plugins under Linux are linked as whole-archives -# 3. The libraries that are used in plugins under Windows/macOS are linked to plugins themselves in plugins/*/CMakeLists.txt -# 4. The libraries that are used both by core and plugins need to be linked as whole-archive under Linux, and under Windows/macOS like in 1 and 3 simultaneously -target_link_libraries(retdec-unpackertool retdec-cpdetect) -if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - target_link_libraries(retdec-unpackertool -rdynamic - -Wl,-force_load retdec-utils - -Wl,-force_load retdec-unpacker - -Wl,-force_load retdec-loader - -Wl,-force_load retdec-pelib - dl) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - target_link_libraries(retdec-unpackertool -rdynamic -Wl,-whole-archive retdec-utils retdec-unpacker retdec-loader pelib -Wl,-no-whole-archive dl) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") - target_link_libraries(retdec-unpackertool shlwapi) -endif() +target_link_libraries(retdec-unpackertool retdec-unpacker retdec-loader retdec-cpdetect retdec-utils pelib) +target_link_libraries(retdec-unpackertool retdec-unpacker-upx retdec-unpacker-mpress) install(TARGETS retdec-unpackertool RUNTIME DESTINATION bin) diff --git a/src/unpackertool/plugin_mgr.cpp b/src/unpackertool/plugin_mgr.cpp index a69d3730..347c7975 100644 --- a/src/unpackertool/plugin_mgr.cpp +++ b/src/unpackertool/plugin_mgr.cpp @@ -7,128 +7,23 @@ #include #include -#include "retdec/utils/filesystem_path.h" #include "retdec/utils/string.h" -#include "retdec/unpacker/lib_loader.h" #include "retdec/unpacker/plugin.h" #include "plugin_mgr.h" +#include "plugins/mpress/mpress.h" +#include "plugins/upx/upx.h" + using namespace retdec::utils; namespace retdec { namespace unpackertool { -/** - * Constructor. - */ -PluginMgr::PluginMgr() : _plugins() +const std::vector PluginMgr::plugins = { -} - -/** - * Destructor. - */ -PluginMgr::~PluginMgr() -{ - for (auto& pluginPair : _plugins) - { - for (auto& plugin : pluginPair.second) - { - // We need to unload the library after the plugin is destroyed - // This cannot be done in Plugin destructor as we are destructing the heap - // of the plugin, which resides in the library address space - LibHandle handle = plugin->getHandle(); - delete plugin; - LibLoader::unloadLibrary(handle); - } - } -} - -/** - * Loads the plugin with the specified name. - * - * @param name The name of the plugin. - * - * @return True if the load was successful, otherwise false. - */ -bool PluginMgr::loadPlugin(const std::string& name) -{ - // Load the library representing the plugin - LibHandle lib = LibLoader::loadLibrary(name); - if (!lib) - { - std::cerr << "Failed to load plugin '" << name << "'. Reason: " << LibLoader::getLastError() << std::endl; - return false; - } - - // Find the registration function - CreatePluginFunc createPlugin; - if ((createPlugin = LibLoader::loadFunction(lib, REGISTER_PLUGIN_FUNCTION_NAME)) == nullptr) - { - LibLoader::unloadLibrary(lib); - return false; - } - - // Register the plugin - Plugin* plugin = createPlugin(); - if (plugin == nullptr) - { - LibLoader::unloadLibrary(lib); - return false; - } - - // Initialize - plugin->init(); - - // Check whether initialization set Plugin::Info structure - const Plugin::Info* pluginInfo = plugin->getInfo(); - if (pluginInfo->isUninitialized()) - { - LibLoader::unloadLibrary(lib); - return false; - } - - // Set the handle to the library so we can properly free it - plugin->setHandle(lib); - - // Put it into table - _plugins[pluginInfo->name].push_back(plugin); - return true; -} - -/** - * Load all plugins recursively in the specified path. - * - * @param dirPath Path to traverse recursively. - */ -void PluginMgr::loadPlugins(const std::string& dirPath) -{ - FilesystemPath path(dirPath); - for (const auto& subpath : path) - { - // In case of directory, recursively call loadPlugins - if (subpath->isDirectory()) - { - loadPlugins(subpath->getPath()); - } - else - { - // if PLUGIN_SUFFIX is at the end of the name of the file - if (retdec::utils::endsWith(subpath->getPath(), PLUGIN_SUFFIX)) - loadPlugin(subpath->getPath()); - } - } -} - -/** - * Returns the table of registered plugins. - * - * @return The table of plugins. - */ -const PluginTable& PluginMgr::plugins() const -{ - return _plugins; -} + mpress_plugin, + upx_plugin +}; /** * Find the matching plugins in the registered plugins table. @@ -138,30 +33,32 @@ const PluginTable& PluginMgr::plugins() const * * @return The list of matched plugins. */ -PluginList PluginMgr::matchingPlugins(const std::string& packerName, const std::string& packerVersion) const +PluginList PluginMgr::matchingPlugins(const std::string& packerName, const std::string& packerVersion) { - // Find the packer name in plugin table to extract list of plugins for this packer - PluginTable::const_iterator itr = _plugins.find(packerName); - if (itr == _plugins.end()) - return PluginList(); - // Iterate over all plugins for this packer name and match it against the used packer version PluginList matchedPlugins; - const PluginList& pluginList = itr->second; + for (const auto& plugin : plugins) + { + if (!utils::areEqualCaseInsensitive(plugin->getInfo()->name, packerName)) + continue; + + matchedPlugins.push_back(plugin); + } // For wildcard just return the all versions of this packers if (packerVersion == WILDCARD_ALL_VERSIONS) - return pluginList; + return matchedPlugins; - for (const auto& plugin : pluginList) + PluginList result; + for (const auto& plugin : matchedPlugins) { // Non case-sensitive regular expressions to match against packerVersion std::regex versionRegex(plugin->getInfo()->packerVersion, std::regex::icase); if (std::regex_search(packerVersion, versionRegex)) - matchedPlugins.push_back(plugin); + result.push_back(plugin); } - return matchedPlugins; + return result; } } // namepsace unpackertool diff --git a/src/unpackertool/plugin_mgr.h b/src/unpackertool/plugin_mgr.h index 053bdae2..be2bb736 100644 --- a/src/unpackertool/plugin_mgr.h +++ b/src/unpackertool/plugin_mgr.h @@ -9,67 +9,18 @@ #include #include +#include #include #include -#include "retdec/utils/os.h" -#include "singleton.h" - namespace retdec { namespace unpackertool { -#ifdef OS_WINDOWS -#define PLUGIN_SUFFIX "dll" -#define PLUGIN_SUFFIX_LEN 3 -#else -#define PLUGIN_SUFFIX "so" -#define PLUGIN_SUFFIX_LEN 2 -#endif - #define WILDCARD_ALL_VERSIONS "" class Plugin; -/** - * @brief Case-insensitive string comparison. - * - * The structure for case-insensitive string comparison. - */ -struct IcaseStringCompare -{ - /** - * Functor used as compare function. - * - * @param lhs Left-hand side of compare. - * @param rhs Right-hand side of compare. - * - * @return True if the strings are case-insensitivelly equal, otherwise false. - */ - bool operator ()(const std::string& lhs, const std::string& rhs) const - { - if (lhs.length() < rhs.length()) - return true; - else if (lhs.length() > rhs.length()) - return false; - else - { - for (size_t i = 0; i < lhs.length(); ++i) - { - // Cast to unsigned char required because of MSVC assert - const unsigned char lc = lhs[i]; - const unsigned char rc = rhs[i]; - if (std::tolower(lc) != std::tolower(rc)) - return std::tolower(lc) < std::tolower(rc); - } - } - - return false; - } -}; - using PluginList = std::vector; ///< Type for list of plugins. -using PluginTable = std::map; ///< Mapping of case-insensitive packer name to list of plugins. -using CreatePluginFunc = Plugin* (*)(); ///< Type for plugin registration function. /** * @brief The manager of unpacking plugins. @@ -85,24 +36,16 @@ using CreatePluginFunc = Plugin* (*)(); ///< Type for plugin registration functi */ class PluginMgr { - IS_SINGLETON(PluginMgr) public: - ~PluginMgr(); + PluginMgr(const PluginMgr&) = delete; - bool loadPlugin(const std::string& path); - void loadPlugins(const std::string& dirPath); + static const PluginList plugins; - const PluginTable& plugins() const; - PluginList matchingPlugins(const std::string& packerName, const std::string& packerVersion) const; + static PluginList matchingPlugins(const std::string& packerName, const std::string& packerVersion); private: - PluginMgr(); - PluginMgr(const PluginMgr&); - PluginMgr& operator =(const PluginMgr&); - - PluginTable _plugins; ///< Table of registered plugins. + PluginMgr() = default; }; -#define sPluginMgr Singleton::instance() } // namespace unpackertool } // namespace retdec diff --git a/src/unpackertool/plugins/CMakeLists.txt b/src/unpackertool/plugins/CMakeLists.txt index d5967a8c..72b5b86e 100644 --- a/src/unpackertool/plugins/CMakeLists.txt +++ b/src/unpackertool/plugins/CMakeLists.txt @@ -1,47 +1,3 @@ - -# Functions to get target's link libs and include dirs. -function(GetLinkLibraries ret target) - if(TARGET ${target}) - get_property(LIBS TARGET ${target} PROPERTY INTERFACE_LINK_LIBRARIES) - set(RESULT "${target}") - if(LIBS) - foreach(LIB ${LIBS}) - GetLinkLibraries(SUB_LIBS ${LIB}) - if(SUB_LIBS) - foreach(SUB_LIB ${SUB_LIBS}) - if(NOT ${SUB_LIB} IN_LIST RESULT) - list(APPEND RESULT ${SUB_LIBS}) - endif() - endforeach() - endif() - endforeach() - endif() - endif() - set(${ret} ${RESULT} PARENT_SCOPE) -endfunction() -function(GetIncludeDirectories ret target) - GetLinkLibraries(LIBS ${target}) - set(RESULT "") - foreach(LIB ${LIBS}) - get_property(INCLUDES TARGET ${LIB} PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - if(INCLUDES) - foreach(INCL ${INCLUDES}) - if(NOT ${INCL} IN_LIST RESULT) - list(APPEND RESULT ${INCL}) - endif() - endforeach() - endif() - endforeach() - set(${ret} ${RESULT} PARENT_SCOPE) -endfunction() - -# retdec-fileformat-headers target can be linked with targets to get fileformat's -# includes without actually linking fileformat's libraries. -GetIncludeDirectories(FILEFORMAT_HEADERS retdec-fileformat) -add_library(retdec-fileformat-headers INTERFACE) -target_include_directories(retdec-fileformat-headers INTERFACE ${FILEFORMAT_HEADERS}) -add_dependencies(retdec-fileformat-headers retdec-fileformat) - # Subdirectories. add_subdirectory(mpress) add_subdirectory(upx) diff --git a/src/unpackertool/plugins/mpress/CMakeLists.txt b/src/unpackertool/plugins/mpress/CMakeLists.txt index 2d87f600..0c0362bb 100644 --- a/src/unpackertool/plugins/mpress/CMakeLists.txt +++ b/src/unpackertool/plugins/mpress/CMakeLists.txt @@ -3,14 +3,6 @@ set(MPRESS_SOURCES mpress.cpp ) -add_library(retdec-mpress SHARED ${MPRESS_SOURCES}) -target_link_libraries(retdec-mpress retdec-fileformat-headers) -target_include_directories(retdec-mpress PUBLIC ${PROJECT_SOURCE_DIR}/src/) -# Plugin related libraries are linked to the plugin on Windows -if(MSVC OR APPLE) - target_link_libraries(retdec-mpress retdec-unpacker) -endif() -install(TARGETS retdec-mpress - LIBRARY DESTINATION bin/unpacker-plugins - RUNTIME DESTINATION bin/unpacker-plugins -) +add_library(retdec-unpacker-mpress STATIC ${MPRESS_SOURCES}) +target_link_libraries(retdec-unpacker-mpress retdec-unpacker retdec-fileformat) +target_include_directories(retdec-unpacker-mpress PUBLIC ${PROJECT_SOURCE_DIR}/src/) diff --git a/src/unpackertool/plugins/mpress/mpress.cpp b/src/unpackertool/plugins/mpress/mpress.cpp index f9dd1f39..28d07cc6 100644 --- a/src/unpackertool/plugins/mpress/mpress.cpp +++ b/src/unpackertool/plugins/mpress/mpress.cpp @@ -16,6 +16,7 @@ #include "retdec/unpacker/decompression/lzmat/lzmat_data.h" #include "retdec/unpacker/unpacker_exception.h" +#include "unpackertool/plugins/mpress/mpress.h" #include "unpackertool/plugins/mpress/mpress_exceptions.h" using namespace retdec::unpacker; @@ -24,722 +25,646 @@ namespace retdec { namespace unpackertool { namespace mpress { -enum Compression +/** + * Constructor. + */ +MpressPlugin::MpressPlugin() : _file(), _peFile(nullptr), _unpackerStub(MPRESS_UNPACKER_STUB_UNKNOWN), + _fixStub(MPRESS_FIX_STUB_UNKNOWN), _packedContentSect(nullptr), _addedSectionCount(0), + _iatVa(0), _iatSize(0), _oepVa(0), _importHintsOffset(0) { - COMPRESSION_LZMA, - COMPRESSION_LZMAT, - COMPRESSION_UNKNOWN -}; + info.name = "MPRESS"; + info.pluginVersion = "0.99"; + info.packerVersion = R"/([12]\..{2})/"; // 1.xx or 2.xx + info.author = "Marek Milkovic"; +} -enum MpressUnpackerStub +/** + * Destructor. + */ +MpressPlugin::~MpressPlugin() { - MPRESS_UNPACKER_STUB_101_105, - MPRESS_UNPACKER_STUB_107_127, - MPRESS_UNPACKER_STUB_201, - MPRESS_UNPACKER_STUB_205_LZMA, - MPRESS_UNPACKER_STUB_205_LZMAT, - MPRESS_UNPACKER_STUB_212_219_LZMA, - MPRESS_UNPACKER_STUB_212_219_LZMAT, - MPRESS_UNPACKER_STUB_UNKNOWN -}; + cleanup(); +} -enum MpressFixStub +/** + * Performs preparation of unpacking. + */ +void MpressPlugin::prepare() { - MPRESS_FIX_STUB_10x, - MPRESS_FIX_STUB_127_20x, - MPRESS_FIX_STUB_21x, - MPRESS_FIX_STUB_UNKNOWN -}; + _file = retdec::loader::createImage(getStartupArguments()->inputFile); + if (!_file) + throw UnsupportedFileException(); -struct MpressUnpackerStubData + if (!_file->getFileFormat()->isPe()) + throw UnsupportedFileException(); + + // We currently don't support PE32+ as the decompiler doesn't support them anyways + if (static_cast(_file->getFileFormat())->getPeClass() != PeLib::PEFILE32) + throw UnsupportedFileException(); + + if (!_file->getEpSegment()) + throw NoEntryPointException(); + + _peFile = static_cast(PeLib::openPeFile(getStartupArguments()->inputFile)); + _peFile->readMzHeader(); + _peFile->readPeHeader(); + + // Detect the version of the used MPRESS packer and the compression used + if (detectUnpackerStubVersion() == MPRESS_UNPACKER_STUB_UNKNOWN) + throw UnsupportedStubException(); +} + +/** + * Starts unpacking in the current plugin. + */ +void MpressPlugin::unpack() { - std::uint32_t signature; // Offset written near the EP which tells where is the offset to the fix imports stub - std::uint32_t packedContentOffset; // Offset of the section with the packed content - std::uint32_t fixStubOffset; // Offset from the EP where the offset of Fix Imports Stub is written - std::uint32_t relocOffset; // Offset from Fix Imports Stub where relocations are written - std::uint32_t relocSizeOffset; // Offset from the EP where size of relocations is written - Compression compression; // Compression method used while packing -}; + // Find the section which contains the packed content + unsigned long long ep; + std::vector packedContentSectAddrBytes; -struct MpressFixStubData + _file->getFileFormat()->getEpAddress(ep); + _file->getEpSegment()->getBytes(packedContentSectAddrBytes, ep - _file->getEpSegment()->getAddress() + mpressUnpackerStubData[_unpackerStub].packedContentOffset, 4); + + DynamicBuffer packedContentSectAddrBuffer(packedContentSectAddrBytes, _file->getFileFormat()->getEndianness()); + std::uint32_t packedContentSectAddr = ep + mpressUnpackerStubData[_unpackerStub].packedContentOffset + packedContentSectAddrBuffer.read(0); + + _packedContentSect = _file->getSegmentFromAddress(packedContentSectAddr); + if (_packedContentSect == nullptr) + throw PackedDataSectionNotFoundException(); + + std::vector packedContent; + _packedContentSect->getBytes(packedContent); + + DynamicBuffer packedContentBuffer(packedContent, _file->getFileFormat()->getEndianness()); + + // First 6 bytes contains metadata about the packed content + // 2 bytes == size of the section with packed content shifted right by 0xC + // 4 bytes == actual size of the packed content + std::uint32_t unpackedSize = packedContentBuffer.read(0) << 0xC; + std::uint32_t packedSize = packedContentBuffer.read(2); + + // Remove the header of the packed data from the buffer + packedContentBuffer.erase(0, 6); + + if (packedSize > packedContentBuffer.getRealDataSize()) + throw CorruptedUnpackingStubException(); + + // Remove tail of the packed data + packedContentBuffer.erase(packedSize, packedContentBuffer.getRealDataSize() - packedSize); + + // Decode the content of .MPRESS1 section which is compressed with LZMAT algorithm + DynamicBuffer unpackedContent(unpackedSize, _file->getFileFormat()->getEndianness()); + if (!decompressData(packedContentBuffer, unpackedContent)) + throw DecompressionFailedException(); + + // Fix JMP and CALL instructions with correction of their offsets + fixJumpsAndCalls(unpackedContent); + + // Detect the version of fix stub here + if (detectFixStubVersion(unpackedContent) == MPRESS_FIX_STUB_UNKNOWN) + throw UnsupportedStubException(); + + // Fix imports & EP + fixImportsAndEp(unpackedContent); + + // Fix relocations + fixRelocations(); + + // Split the unpacked section as much as possible into individual sections based on the known offsets + offsetAnalysis(unpackedContent); + + // Perform section trailing bytes analysis + trailingBytesAnalysis(unpackedContent); + + // Save the new file + saveFile(getStartupArguments()->outputFile, unpackedContent); +} + +/** + * Performs freeing of all owned resources. + */ +void MpressPlugin::cleanup() { - std::uint32_t signature; // Byte at the beginning of fix imports stub - std::uint32_t importHintsOffset; // Offset from Fix Imports Stub where Import Hints are stored - std::uint32_t oepOffset; // Offset from Fix Imports Stub where the offset of OEP is written -}; + delete _peFile; + _peFile = nullptr; +} -static MpressUnpackerStubData mpressUnpackerStubData[MPRESS_UNPACKER_STUB_UNKNOWN] = +bool MpressPlugin::decompressData(DynamicBuffer& compressedContent, DynamicBuffer& decompressedContent) { - { 0x2B6, 0x2BC, 0x2B8, 0x2C8, 0x2C0, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_101_105 - { 0x29E, 0x2A4, 0x2A0, 0x2B0, 0x2A8, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_107_127 - { 0x299, 0x29F, 0x29B, 0x2AB, 0x2A3, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_201 - { 0xB57, 0xB5D, 0xB59, 0xB61, 0xB69, COMPRESSION_LZMA }, // MPRESS_UNPACKER_STUB_205_LZMA - { 0x29C, 0x2A2, 0x29E, 0x2A6, 0x2AE, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_205_LZMAT - { 0xB5A, 0xB60, 0xB5C, 0xB64, 0xB6C, COMPRESSION_LZMA }, // MPRESS_UNPACKER_STUB_212_219_LZMA - { 0x29F, 0x2A5, 0x2A1, 0x2A9, 0x2B1, COMPRESSION_LZMAT } // MPRESS_UNPACKER_STUB_212_219_LZMAT -}; - -static MpressFixStubData mpressFixStubData[MPRESS_FIX_STUB_UNKNOWN] = -{ - { 0x8B, 0xC1, 0xBD }, // MPRESS_FIX_STUB_10x - { 0x45, 0x138, 0x134 }, // MPRESS_FIX_STUB_127_20x - { 0x35, 0x128, 0x124 } // MPRESS_FIX_STUB_21x -}; - -class MpressPlugin : public Plugin -{ -public: - MpressPlugin() : _file(), _peFile(nullptr), _unpackerStub(MPRESS_UNPACKER_STUB_UNKNOWN), - _fixStub(MPRESS_FIX_STUB_UNKNOWN), _packedContentSect(nullptr), _addedSectionCount(0), - _iatVa(0), _iatSize(0), _oepVa(0), _importHintsOffset(0) {} - - virtual ~MpressPlugin() override {} - - /** - * Initialization of plugin providing @ref Plugin::Info data. - */ - virtual void init() override + if (mpressUnpackerStubData[_unpackerStub].compression == COMPRESSION_LZMAT) { - info.name = "MPRESS"; - info.pluginVersion = "0.99"; - info.packerVersion = R"/([12]\..{2})/"; // 1.xx or 2.xx - info.author = "Marek Milkovic"; - } - - /** - * Performs preparation of unpacking. - */ - virtual void prepare() override - { - _file = retdec::loader::createImage(getStartupArguments()->inputFile); - if (!_file) - throw UnsupportedFileException(); - - if (!_file->getFileFormat()->isPe()) - throw UnsupportedFileException(); - - // We currently don't support PE32+ as the decompiler doesn't support them anyways - if (static_cast(_file->getFileFormat())->getPeClass() != PeLib::PEFILE32) - throw UnsupportedFileException(); - - if (!_file->getEpSegment()) - throw NoEntryPointException(); - - _peFile = static_cast(PeLib::openPeFile(getStartupArguments()->inputFile)); - _peFile->readMzHeader(); - _peFile->readPeHeader(); - - // Detect the version of the used MPRESS packer and the compression used - if (detectUnpackerStubVersion() == MPRESS_UNPACKER_STUB_UNKNOWN) - throw UnsupportedStubException(); - } - - /** - * Starts unpacking in the current plugin. - */ - virtual void unpack() override - { - // Find the section which contains the packed content - unsigned long long ep; - std::vector packedContentSectAddrBytes; - - _file->getFileFormat()->getEpAddress(ep); - _file->getEpSegment()->getBytes(packedContentSectAddrBytes, ep - _file->getEpSegment()->getAddress() + mpressUnpackerStubData[_unpackerStub].packedContentOffset, 4); - - DynamicBuffer packedContentSectAddrBuffer(packedContentSectAddrBytes, _file->getFileFormat()->getEndianness()); - std::uint32_t packedContentSectAddr = ep + mpressUnpackerStubData[_unpackerStub].packedContentOffset + packedContentSectAddrBuffer.read(0); - - _packedContentSect = _file->getSegmentFromAddress(packedContentSectAddr); - if (_packedContentSect == nullptr) - throw PackedDataSectionNotFoundException(); - - std::vector packedContent; - _packedContentSect->getBytes(packedContent); - - DynamicBuffer packedContentBuffer(packedContent, _file->getFileFormat()->getEndianness()); - - // First 6 bytes contains metadata about the packed content - // 2 bytes == size of the section with packed content shifted right by 0xC - // 4 bytes == actual size of the packed content - std::uint32_t unpackedSize = packedContentBuffer.read(0) << 0xC; - std::uint32_t packedSize = packedContentBuffer.read(2); - - // Remove the header of the packed data from the buffer - packedContentBuffer.erase(0, 6); - - if (packedSize > packedContentBuffer.getRealDataSize()) - throw CorruptedUnpackingStubException(); - - // Remove tail of the packed data - packedContentBuffer.erase(packedSize, packedContentBuffer.getRealDataSize() - packedSize); - - // Decode the content of .MPRESS1 section which is compressed with LZMAT algorithm - DynamicBuffer unpackedContent(unpackedSize, _file->getFileFormat()->getEndianness()); - if (!decompressData(packedContentBuffer, unpackedContent)) - throw DecompressionFailedException(); - - // Fix JMP and CALL instructions with correction of their offsets - fixJumpsAndCalls(unpackedContent); - - // Detect the version of fix stub here - if (detectFixStubVersion(unpackedContent) == MPRESS_FIX_STUB_UNKNOWN) - throw UnsupportedStubException(); - - // Fix imports & EP - fixImportsAndEp(unpackedContent); - - // Fix relocations - fixRelocations(); - - // Split the unpacked section as much as possible into individual sections based on the known offsets - offsetAnalysis(unpackedContent); - - // Perform section trailing bytes analysis - trailingBytesAnalysis(unpackedContent); - - // Save the new file - saveFile(getStartupArguments()->outputFile, unpackedContent); - } - - /** - * Performs freeing of all owned resources. - */ - virtual void cleanup() override - { - delete _peFile; - _peFile = nullptr; - } - -private: - bool decompressData(DynamicBuffer& compressedContent, DynamicBuffer& decompressedContent) - { - if (mpressUnpackerStubData[_unpackerStub].compression == COMPRESSION_LZMAT) + LzmatData lzmatData(compressedContent); + if (!lzmatData.decompress(decompressedContent)) { - LzmatData lzmatData(compressedContent); - if (!lzmatData.decompress(decompressedContent)) - { - error("Unable to decompress LZMAT compressed content"); - return false; - } - } - else if (mpressUnpackerStubData[_unpackerStub].compression == COMPRESSION_LZMA) - { - // Decode the LZMA properties and remove them from the compressedContent - std::uint8_t pb, lp, lc; - decodeLzmaProperties(compressedContent, pb, lp, lc); - - LzmaData lzmaData(compressedContent, pb, lp, lc); - if (!lzmaData.decompress(decompressedContent)) - { - error("Unable to decompress LZMA compressed content"); - return false; - } - } - else - { - error("Unable to decompressed content"); + error("Unable to decompress LZMAT compressed content"); return false; } - - return true; } - - void decodeLzmaProperties(DynamicBuffer& compressedContent, std::uint8_t& pb, std::uint8_t& lp, std::uint8_t& lc) + else if (mpressUnpackerStubData[_unpackerStub].compression == COMPRESSION_LZMA) { - lp = compressedContent.read(0) & 0x0F; - pb = (compressedContent.read(0) & 0xF0) >> 4; - lc = compressedContent.read(1); + // Decode the LZMA properties and remove them from the compressedContent + std::uint8_t pb, lp, lc; + decodeLzmaProperties(compressedContent, pb, lp, lc); - compressedContent.erase(0, 2); - } - - std::uint32_t getFixStub() - { - unsigned long long ep, epOffset; - std::vector fixStubOffsetBytes; - - // Fix imports stub is calculated from the EP section where there is offset into it written at specific offset - _file->getFileFormat()->getEpAddress(ep); - epOffset = ep - _file->getEpSegment()->getAddress(); - _file->getEpSegment()->getBytes(fixStubOffsetBytes, epOffset + mpressUnpackerStubData[_unpackerStub].fixStubOffset, 4); - - DynamicBuffer fixStubOffsetBuffer(fixStubOffsetBytes, _file->getFileFormat()->getEndianness()); - - // If we subtract the address of .MPRESS1 section, we have the offset in section - std::uint32_t fixImportsStubAddr = ep + mpressUnpackerStubData[_unpackerStub].fixStubOffset + 4 + fixStubOffsetBuffer.read(0); - fixImportsStubAddr -= _packedContentSect->getAddress(); - return fixImportsStubAddr; - } - - void fixJumpsAndCalls(DynamicBuffer& buffer) - { - std::uint32_t pos = 0; - std::uint32_t maxAddr = buffer.getRealDataSize() - 0x1000; - while (pos < maxAddr) + LzmaData lzmaData(compressedContent, pb, lp, lc); + if (!lzmaData.decompress(decompressedContent)) { - std::uint32_t moveOffset = pos; - std::uint8_t opcode = buffer.read(pos++); - if ((opcode & 0xFE) != 0xE8) // JMP == E9, CALL == E8 + error("Unable to decompress LZMA compressed content"); + return false; + } + } + else + { + error("Unable to decompressed content"); + return false; + } + + return true; +} + +void MpressPlugin::decodeLzmaProperties(DynamicBuffer& compressedContent, std::uint8_t& pb, std::uint8_t& lp, std::uint8_t& lc) +{ + lp = compressedContent.read(0) & 0x0F; + pb = (compressedContent.read(0) & 0xF0) >> 4; + lc = compressedContent.read(1); + + compressedContent.erase(0, 2); +} + +std::uint32_t MpressPlugin::getFixStub() +{ + unsigned long long ep, epOffset; + std::vector fixStubOffsetBytes; + + // Fix imports stub is calculated from the EP section where there is offset into it written at specific offset + _file->getFileFormat()->getEpAddress(ep); + epOffset = ep - _file->getEpSegment()->getAddress(); + _file->getEpSegment()->getBytes(fixStubOffsetBytes, epOffset + mpressUnpackerStubData[_unpackerStub].fixStubOffset, 4); + + DynamicBuffer fixStubOffsetBuffer(fixStubOffsetBytes, _file->getFileFormat()->getEndianness()); + + // If we subtract the address of .MPRESS1 section, we have the offset in section + std::uint32_t fixImportsStubAddr = ep + mpressUnpackerStubData[_unpackerStub].fixStubOffset + 4 + fixStubOffsetBuffer.read(0); + fixImportsStubAddr -= _packedContentSect->getAddress(); + return fixImportsStubAddr; +} + +void MpressPlugin::fixJumpsAndCalls(DynamicBuffer& buffer) +{ + std::uint32_t pos = 0; + std::uint32_t maxAddr = buffer.getRealDataSize() - 0x1000; + while (pos < maxAddr) + { + std::uint32_t moveOffset = pos; + std::uint8_t opcode = buffer.read(pos++); + if ((opcode & 0xFE) != 0xE8) // JMP == E9, CALL == E8 + continue; + + std::int32_t offset = buffer.read(pos); + moveOffset++; + pos += 4; + + if (offset >= 0) + { + if (offset >= static_cast(maxAddr)) continue; - - std::int32_t offset = buffer.read(pos); - moveOffset++; - pos += 4; - - if (offset >= 0) - { - if (offset >= static_cast(maxAddr)) - continue; - } - else - { - offset += moveOffset; - if (offset < 0) - continue; - - offset += maxAddr; - } - - buffer.write(offset - moveOffset, pos - 4); } - } - - void fixImportsAndEp(DynamicBuffer& buffer) - { - // At the offset from EP is written EIP relative offset of fix import stub - // Fix import stub is then located at the + offset + - // This stub was packed together with code and data in .MPRESS1 section and also contains hints for import table rebuild - std::uint32_t fixStubAddr = getFixStub(); - - // Offet to the hints is located at the + offset - // Import hints are located at the + offset + - std::uint32_t importHints = fixStubAddr + mpressFixStubData[_fixStub].importHintsOffset + buffer.read(fixStubAddr + mpressFixStubData[_fixStub].importHintsOffset); - - // Go through MPRESS import hints and fill the import directory - std::int32_t destOffset = _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()) + importHints; - std::int32_t lowestDestOffset = std::numeric_limits::max(); - std::int32_t highestDestOffset = 0; - std::int32_t destOffsetDiff; - std::uint32_t readPos = importHints; - while ((destOffsetDiff = buffer.read(readPos)) != -1) - { - destOffset += destOffsetDiff; - buffer.writeRepeatingByte(0, readPos, 4); - readPos += 4; - - std::string moduleName = buffer.readString(readPos); - buffer.writeRepeatingByte(0, readPos, static_cast(moduleName.length())); - readPos += static_cast(moduleName.length() + 1); - - destOffsetDiff = 0; - while (buffer.read(readPos) != 0) - { - // Import by ordinal - if (buffer.read(readPos) <= 0x20) - { - std::uint16_t ordinal = buffer.read(readPos + 1); - _peFile->impDir().addFunction(moduleName, ordinal); - buffer.writeRepeatingByte(0, readPos, 3); - readPos += 3; - } - // Import by name - else - { - std::string symbolName = buffer.readString(readPos); - _peFile->impDir().addFunction(moduleName, symbolName); - buffer.writeRepeatingByte(0, readPos, static_cast(symbolName.length() + 1)); - readPos += static_cast(symbolName.length() + 1); - } - - destOffsetDiff += 4; - } - readPos++; // skip null terminator - - // Set FirstThunk to point into IAT - int fileIndex = _peFile->impDir().getFileIndex(moduleName, PeLib::NEWDIR); - if (fileIndex == -1) - throw InvalidImportHintsException(); - - _peFile->impDir().setFirstThunk(fileIndex, PeLib::NEWDIR, destOffset); - lowestDestOffset = std::min(destOffset, lowestDestOffset); - highestDestOffset = std::max(destOffset, highestDestOffset); - destOffset += destOffsetDiff; - } - - buffer.writeRepeatingByte(0, readPos, 4); // clear out last -1 from the hint list - - // Fix the import directory and import address directory addresses - // Since ILT is lost we need to make them from scratch in the whole new section - // Ensure the .imports section will be large enough, resize if there are more data - std::uint32_t importSectSize = _peFile->impDir().size() & ~(_peFile->peHeader().getFileAlignment() - 1); - if (_peFile->impDir().size() & (_peFile->peHeader().getFileAlignment() - 1)) - importSectSize += _peFile->peHeader().getFileAlignment(); - _peFile->peHeader().addSection(".imports", importSectSize); - _peFile->peHeader().setCharacteristics(_peFile->peHeader().calcNumberOfSections() - 1, PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA); - _peFile->peHeader().setIddImportRva(_peFile->peHeader().getVirtualAddress(_peFile->peHeader().calcNumberOfSections() - 1)); - _peFile->peHeader().setIddImportSize(importSectSize); - _peFile->peHeader().makeValid(_peFile->mzHeader().size()); - - // IAT needs to be put at the desired offset - std::uint32_t iatOffset = lowestDestOffset; - _peFile->peHeader().setIddIatRva(iatOffset); - _peFile->peHeader().setIddIatSize(highestDestOffset + 4 - lowestDestOffset); - _peFile->peHeader().makeValid(_peFile->mzHeader().size()); - - // Offset to OEP is stored at the offset from the - // This offset is addressed from the + offset - std::uint32_t oepOffset = _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()) + fixStubAddr + - mpressFixStubData[_fixStub].importHintsOffset + buffer.read(fixStubAddr + mpressFixStubData[_fixStub].oepOffset); - _peFile->peHeader().setAddressOfEntryPoint(oepOffset); - _peFile->peHeader().makeValid(_peFile->mzHeader().size()); - - // Finally, get rid of the fix imports stub as it is going to mess up frontend analysis in the unpacked section - // At the end we add 16 because of 4 bytes directly belonging to the importHintsOffset and additional 12 bytes used - // to store library calls GetProcAddress and LoadLibrary - buffer.writeRepeatingByte(0, fixStubAddr, mpressFixStubData[_fixStub].importHintsOffset + 16); - - // Set to the global plugin attributes, so it can be used later - _iatVa = iatOffset; - _iatSize = highestDestOffset + 4 - lowestDestOffset; - _oepVa = oepOffset; - _importHintsOffset = importHints; - } - - void offsetAnalysis(const DynamicBuffer& buffer) - { - std::uint32_t fixStubOffset = getFixStub(); - std::uint32_t dataFlags = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; - std::uint32_t codeFlags = dataFlags | PeLib::PELIB_IMAGE_SCN_CNT_CODE | PeLib::PELIB_IMAGE_SCN_MEM_EXECUTE; - - // Remove the .MPRESS2 section as the getEpSection()->getIndex() will be shifted by newly created - // sections and we can do it safely here - //_peFile->peHeader().removeSection(_file->getEpSection()->getIndex()); - // @todo There is some problem with removing a section and valid PE file in some cases - _peFile->peHeader().setCharacteristics(_file->getEpSegment()->getSecSeg()->getIndex(), dataFlags); - - // Resize packed content section so we can put unpacked content into it - size_t diff = buffer.getRealDataSize() - _peFile->peHeader().getSizeOfRawData(_packedContentSect->getSecSeg()->getIndex()); - _peFile->peHeader().setSizeOfRawData(_packedContentSect->getSecSeg()->getIndex(), buffer.getRealDataSize()); - for (size_t i = _packedContentSect->getSecSeg()->getIndex() + 1; i < _peFile->peHeader().calcNumberOfSections(); ++i) - { - auto ii = static_cast(i); - auto tmp = _peFile->peHeader().getPointerToRawData(ii) + diff; - _peFile->peHeader().setPointerToRawData(ii, static_cast(tmp)); - } - - // Here we will split the big unpacked section into the more data sections and try to locate the code section as much as possible - // We will use import hints, IAT, fix stub address and OEP to locate the original sections - // We know that hints and fix stub are located at the end of original sections - // We also know that IAT is its own section - std::uint32_t oepOffset = _oepVa - _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()); - std::uint32_t iatOffset = (_iatVa & ~(_peFile->peHeader().getSectionAlignment() - 1)) - _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()); - - // We will need this as all other heuristics are based on the shrinking of .text section - std::uint32_t moveOffset = 0; - - // Hints are located before the OEP, we suspect that there is data section before the code - if (_importHintsOffset < oepOffset) - { - // Split the section into .data0|.text - if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".data0", ".text", - (_importHintsOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment()) == 0) - { - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), dataFlags); - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, codeFlags); - _addedSectionCount++; - - // We will need this as all other heuristics are based on the shrinking of .text section - // We need to subtract this from every split offset calculation - moveOffset = _peFile->peHeader().getSizeOfRawData(_packedContentSect->getSecSeg()->getIndex()); - } - - // We don't mind if IAT is at the beginning of the data, since it is treated properly as data - // However there can be IAT between data and code - if (_importHintsOffset < iatOffset && iatOffset < oepOffset) - { - // Split the layout into .data0|.data2|.text - if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex() + 1, ".data2", ".text", - ((iatOffset + _iatSize) & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment() - moveOffset) == 0) - { - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, dataFlags); - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 2, codeFlags); - _addedSectionCount++; - moveOffset += _peFile->peHeader().getSizeOfRawData(_packedContentSect->getSecSeg()->getIndex() + 1); - } - } - - // Another heuristic is based on the fact that fix stub can be located behind the code - // There we can get original code section almost perfectly - if (fixStubOffset > oepOffset) - { - // Split into .data0|.text|.data1 or .data0|.data2|.text|.data1 - if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex() + _addedSectionCount, ".text", ".data1", - (fixStubOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment() - moveOffset) == 0) - { - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + _addedSectionCount, codeFlags); - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + _addedSectionCount + 1, dataFlags); - _addedSectionCount++; - } - } - } - // The OEP is before the hints, so code section is probably the first one in this big section else { - // Split into .text|.data0 - if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".text", ".data0", - (_importHintsOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment()) == 0) + offset += moveOffset; + if (offset < 0) + continue; + + offset += maxAddr; + } + + buffer.write(offset - moveOffset, pos - 4); + } +} + +void MpressPlugin::fixImportsAndEp(DynamicBuffer& buffer) +{ + // At the offset from EP is written EIP relative offset of fix import stub + // Fix import stub is then located at the + offset + + // This stub was packed together with code and data in .MPRESS1 section and also contains hints for import table rebuild + std::uint32_t fixStubAddr = getFixStub(); + + // Offet to the hints is located at the + offset + // Import hints are located at the + offset + + std::uint32_t importHints = fixStubAddr + mpressFixStubData[_fixStub].importHintsOffset + buffer.read(fixStubAddr + mpressFixStubData[_fixStub].importHintsOffset); + + // Go through MPRESS import hints and fill the import directory + std::int32_t destOffset = _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()) + importHints; + std::int32_t lowestDestOffset = std::numeric_limits::max(); + std::int32_t highestDestOffset = 0; + std::int32_t destOffsetDiff; + std::uint32_t readPos = importHints; + while ((destOffsetDiff = buffer.read(readPos)) != -1) + { + destOffset += destOffsetDiff; + buffer.writeRepeatingByte(0, readPos, 4); + readPos += 4; + + std::string moduleName = buffer.readString(readPos); + buffer.writeRepeatingByte(0, readPos, static_cast(moduleName.length())); + readPos += static_cast(moduleName.length() + 1); + + destOffsetDiff = 0; + while (buffer.read(readPos) != 0) + { + // Import by ordinal + if (buffer.read(readPos) <= 0x20) + { + std::uint16_t ordinal = buffer.read(readPos + 1); + _peFile->impDir().addFunction(moduleName, ordinal); + buffer.writeRepeatingByte(0, readPos, 3); + readPos += 3; + } + // Import by name + else + { + std::string symbolName = buffer.readString(readPos); + _peFile->impDir().addFunction(moduleName, symbolName); + buffer.writeRepeatingByte(0, readPos, static_cast(symbolName.length() + 1)); + readPos += static_cast(symbolName.length() + 1); + } + + destOffsetDiff += 4; + } + readPos++; // skip null terminator + + // Set FirstThunk to point into IAT + int fileIndex = _peFile->impDir().getFileIndex(moduleName, PeLib::NEWDIR); + if (fileIndex == -1) + throw InvalidImportHintsException(); + + _peFile->impDir().setFirstThunk(fileIndex, PeLib::NEWDIR, destOffset); + lowestDestOffset = std::min(destOffset, lowestDestOffset); + highestDestOffset = std::max(destOffset, highestDestOffset); + destOffset += destOffsetDiff; + } + + buffer.writeRepeatingByte(0, readPos, 4); // clear out last -1 from the hint list + + // Fix the import directory and import address directory addresses + // Since ILT is lost we need to make them from scratch in the whole new section + // Ensure the .imports section will be large enough, resize if there are more data + std::uint32_t importSectSize = _peFile->impDir().size() & ~(_peFile->peHeader().getFileAlignment() - 1); + if (_peFile->impDir().size() & (_peFile->peHeader().getFileAlignment() - 1)) + importSectSize += _peFile->peHeader().getFileAlignment(); + _peFile->peHeader().addSection(".imports", importSectSize); + _peFile->peHeader().setCharacteristics(_peFile->peHeader().calcNumberOfSections() - 1, PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA); + _peFile->peHeader().setIddImportRva(_peFile->peHeader().getVirtualAddress(_peFile->peHeader().calcNumberOfSections() - 1)); + _peFile->peHeader().setIddImportSize(importSectSize); + _peFile->peHeader().makeValid(_peFile->mzHeader().size()); + + // IAT needs to be put at the desired offset + std::uint32_t iatOffset = lowestDestOffset; + _peFile->peHeader().setIddIatRva(iatOffset); + _peFile->peHeader().setIddIatSize(highestDestOffset + 4 - lowestDestOffset); + _peFile->peHeader().makeValid(_peFile->mzHeader().size()); + + // Offset to OEP is stored at the offset from the + // This offset is addressed from the + offset + std::uint32_t oepOffset = _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()) + fixStubAddr + + mpressFixStubData[_fixStub].importHintsOffset + buffer.read(fixStubAddr + mpressFixStubData[_fixStub].oepOffset); + _peFile->peHeader().setAddressOfEntryPoint(oepOffset); + _peFile->peHeader().makeValid(_peFile->mzHeader().size()); + + // Finally, get rid of the fix imports stub as it is going to mess up frontend analysis in the unpacked section + // At the end we add 16 because of 4 bytes directly belonging to the importHintsOffset and additional 12 bytes used + // to store library calls GetProcAddress and LoadLibrary + buffer.writeRepeatingByte(0, fixStubAddr, mpressFixStubData[_fixStub].importHintsOffset + 16); + + // Set to the global plugin attributes, so it can be used later + _iatVa = iatOffset; + _iatSize = highestDestOffset + 4 - lowestDestOffset; + _oepVa = oepOffset; + _importHintsOffset = importHints; +} + +void MpressPlugin::offsetAnalysis(const DynamicBuffer& buffer) +{ + std::uint32_t fixStubOffset = getFixStub(); + std::uint32_t dataFlags = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; + std::uint32_t codeFlags = dataFlags | PeLib::PELIB_IMAGE_SCN_CNT_CODE | PeLib::PELIB_IMAGE_SCN_MEM_EXECUTE; + + // Remove the .MPRESS2 section as the getEpSection()->getIndex() will be shifted by newly created + // sections and we can do it safely here + //_peFile->peHeader().removeSection(_file->getEpSection()->getIndex()); + // @todo There is some problem with removing a section and valid PE file in some cases + _peFile->peHeader().setCharacteristics(_file->getEpSegment()->getSecSeg()->getIndex(), dataFlags); + + // Resize packed content section so we can put unpacked content into it + size_t diff = buffer.getRealDataSize() - _peFile->peHeader().getSizeOfRawData(_packedContentSect->getSecSeg()->getIndex()); + _peFile->peHeader().setSizeOfRawData(_packedContentSect->getSecSeg()->getIndex(), buffer.getRealDataSize()); + for (size_t i = _packedContentSect->getSecSeg()->getIndex() + 1; i < _peFile->peHeader().calcNumberOfSections(); ++i) + { + auto ii = static_cast(i); + auto tmp = _peFile->peHeader().getPointerToRawData(ii) + diff; + _peFile->peHeader().setPointerToRawData(ii, static_cast(tmp)); + } + + // Here we will split the big unpacked section into the more data sections and try to locate the code section as much as possible + // We will use import hints, IAT, fix stub address and OEP to locate the original sections + // We know that hints and fix stub are located at the end of original sections + // We also know that IAT is its own section + std::uint32_t oepOffset = _oepVa - _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()); + std::uint32_t iatOffset = (_iatVa & ~(_peFile->peHeader().getSectionAlignment() - 1)) - _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()); + + // We will need this as all other heuristics are based on the shrinking of .text section + std::uint32_t moveOffset = 0; + + // Hints are located before the OEP, we suspect that there is data section before the code + if (_importHintsOffset < oepOffset) + { + // Split the section into .data0|.text + if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".data0", ".text", + (_importHintsOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment()) == 0) + { + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), dataFlags); + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, codeFlags); + _addedSectionCount++; + + // We will need this as all other heuristics are based on the shrinking of .text section + // We need to subtract this from every split offset calculation + moveOffset = _peFile->peHeader().getSizeOfRawData(_packedContentSect->getSecSeg()->getIndex()); + } + + // We don't mind if IAT is at the beginning of the data, since it is treated properly as data + // However there can be IAT between data and code + if (_importHintsOffset < iatOffset && iatOffset < oepOffset) + { + // Split the layout into .data0|.data2|.text + if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex() + 1, ".data2", ".text", + ((iatOffset + _iatSize) & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment() - moveOffset) == 0) + { + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, dataFlags); + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 2, codeFlags); + _addedSectionCount++; + moveOffset += _peFile->peHeader().getSizeOfRawData(_packedContentSect->getSecSeg()->getIndex() + 1); + } + } + + // Another heuristic is based on the fact that fix stub can be located behind the code + // There we can get original code section almost perfectly + if (fixStubOffset > oepOffset) + { + // Split into .data0|.text|.data1 or .data0|.data2|.text|.data1 + if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex() + _addedSectionCount, ".text", ".data1", + (fixStubOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment() - moveOffset) == 0) + { + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + _addedSectionCount, codeFlags); + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + _addedSectionCount + 1, dataFlags); + _addedSectionCount++; + } + } + } + // The OEP is before the hints, so code section is probably the first one in this big section + else + { + // Split into .text|.data0 + if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".text", ".data0", + (_importHintsOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment()) == 0) + { + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), codeFlags); + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, dataFlags); + _addedSectionCount++; + } + + // There can be even IAT between the .text and .data0 if the hints were placed far behind + if (oepOffset < iatOffset && iatOffset < _importHintsOffset) + { + // Split into .text|.data2|.data0 + if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".text", ".data2", + (iatOffset + _iatSize) & ~(_peFile->peHeader().getSectionAlignment() - 1)) == 0) { _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), codeFlags); _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, dataFlags); _addedSectionCount++; } + } - // There can be even IAT between the .text and .data0 if the hints were placed far behind - if (oepOffset < iatOffset && iatOffset < _importHintsOffset) + // This will probably never happen because if there would be space for fix stub, the hints would be there more probably, but just in case + if (fixStubOffset < oepOffset) + { + // Split into .data1|.text|.data0 or .data1|.text|.data2|.data0 + if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".data1", ".text", + (fixStubOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment()) == 0) { - // Split into .text|.data2|.data0 - if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".text", ".data2", - (iatOffset + _iatSize) & ~(_peFile->peHeader().getSectionAlignment() - 1)) == 0) - { - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), codeFlags); - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, dataFlags); - _addedSectionCount++; - } + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), dataFlags); + _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, codeFlags); + _addedSectionCount++; } + } + } - // This will probably never happen because if there would be space for fix stub, the hints would be there more probably, but just in case - if (fixStubOffset < oepOffset) + _peFile->peHeader().makeValid(_peFile->mzHeader().size()); +} + +void MpressPlugin::trailingBytesAnalysis(const DynamicBuffer& buffer) +{ + // Analysis of the trailing bytes of the section + // 64 bytes at the every section alignment multiple are checked, if they are all 0, the new section is probably here so it is created + // Only code section left after this function is the one containing the OEP, the code will execute even if some of the code is in data sections + // and decompiler will take care of this in front-end instruction decoder + std::uint32_t sectionAlignment = _peFile->peHeader().getSectionAlignment(); + std::uint32_t section = _peFile->peHeader().getSectionWithRva(_peFile->peHeader().getAddressOfEntryPoint()); + std::uint32_t startOffset = _peFile->peHeader().getPointerToRawData(section) - _peFile->peHeader().getPointerToRawData(_packedContentSect->getSecSeg()->getIndex()); + std::uint32_t endOffset = startOffset + _peFile->peHeader().getSizeOfRawData(section); + std::uint32_t oepOffset = _peFile->peHeader().getAddressOfEntryPoint() - _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()); + + // Used to build the section names + std::uint32_t nameCounter = 3; + + // Start at the next section alignment closest to the start of current .text section + // Move with the step of section alignment + // End if end of the unpacked section is reached + for (std::uint32_t offset = startOffset + sectionAlignment; offset < endOffset; offset += sectionAlignment) + { + // Inspect trailing 64 bytes, if they are all 0, needSplit is marked as true, resulting in section split + bool needSplit = true; + for (std::uint8_t idx = 1; idx <= 64; ++idx) + { + if (buffer.read(offset - idx) != 0) { - // Split into .data1|.text|.data0 or .data1|.text|.data2|.data0 - if (_peFile->peHeader().splitSection(_packedContentSect->getSecSeg()->getIndex(), ".data1", ".text", - (fixStubOffset & ~(_peFile->peHeader().getSectionAlignment() - 1)) + _peFile->peHeader().getSectionAlignment()) == 0) - { - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex(), dataFlags); - _peFile->peHeader().setCharacteristics(_packedContentSect->getSecSeg()->getIndex() + 1, codeFlags); - _addedSectionCount++; - } + needSplit = false; + break; } } + if (!needSplit) + continue; + + std::stringstream ssFirst, ssSecond; + std::uint32_t flags[2]; + // OEP lies in the first part of splitted sections + if (startOffset <= oepOffset && oepOffset < offset) + { + ssFirst << ".text"; + ssSecond << ".data" << nameCounter++; + flags[1] = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; + flags[0] = flags[1] | PeLib::PELIB_IMAGE_SCN_MEM_EXECUTE | PeLib::PELIB_IMAGE_SCN_CNT_CODE; + } + // OEP lies in the second part of splitted sections + else if (offset <= oepOffset && oepOffset < endOffset) + { + ssFirst << ".data" << nameCounter++; + ssSecond << ".text"; + flags[0] = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; + flags[1] = flags[0] | PeLib::PELIB_IMAGE_SCN_MEM_EXECUTE | PeLib::PELIB_IMAGE_SCN_CNT_CODE; + } + // OEP doesn't lie in neither of these two parts, this can happen if .text section was already created and we are analysis the rest after that + else + { + ssFirst << ".data" << nameCounter++; + ssSecond << ".data" << nameCounter++; + flags[0] = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; + flags[1] = flags[0]; + } + + _peFile->peHeader().splitSection(section, ssFirst.str(), ssSecond.str(), offset - startOffset); + _peFile->peHeader().setCharacteristics(section, flags[0]); + _peFile->peHeader().setCharacteristics(section + 1, flags[1]); _peFile->peHeader().makeValid(_peFile->mzHeader().size()); + _addedSectionCount++; + section++; + startOffset = offset; } +} - void trailingBytesAnalysis(const DynamicBuffer& buffer) +void MpressPlugin::fixRelocations() +{ + // We will only manipulate this section as all information are stored here + const retdec::loader::Segment* epSegment = _file->getEpSegment(); + + // Calculate the offset of EP in EP section + unsigned long long epAddress; + _file->getFileFormat()->getEpAddress(epAddress); + epAddress -= epSegment->getAddress(); + + // Read the data at the desired offsets + std::vector relocRvaBytes, relocSizeBytes; + epSegment->getBytes(relocRvaBytes, epAddress + mpressUnpackerStubData[_unpackerStub].relocOffset, 4); + epSegment->getBytes(relocSizeBytes, epAddress + mpressUnpackerStubData[_unpackerStub].relocSizeOffset, 4); + + DynamicBuffer relocRvaBuffer(relocRvaBytes, _file->getFileFormat()->getEndianness()); + DynamicBuffer relocSizeBuffer(relocSizeBytes, _file->getFileFormat()->getEndianness()); + + // When the size of relocation is 0, there are no relocations + std::uint32_t relocSize = relocSizeBuffer.read(0); + if (relocSize == 0) + return; + + // Set the base relocation directory in the new file + // All relocations are here undamaged so we are good with this + std::uint32_t relocRva = relocRvaBuffer.read(0); + _peFile->peHeader().setIddBaseRelocRva(relocRva); + _peFile->peHeader().setIddBaseRelocSize(relocSize); + _peFile->peHeader().makeValid(_peFile->mzHeader().size()); +} + +MpressUnpackerStub MpressPlugin::detectUnpackerStubVersion() +{ + unsigned long long ep; + std::vector signatureBytes; + + // Get the data in EP section so we can compare it with signature + _file->getFileFormat()->getEpAddress(ep); + ep -= _file->getEpSegment()->getAddress(); + _file->getEpSegment()->getBytes(signatureBytes, ep + 8, 4); + + DynamicBuffer signatureBuffer(signatureBytes, _file->getFileFormat()->getEndianness()); + std::uint32_t signature = signatureBuffer.read(0); + + // Signature should not be bigger than 0xC00 for all versions of MPRESS + // This heuristic can catch corrupted unpacking stubs + if (signature >= 0xC00) + throw CorruptedUnpackingStubException(); + + for (std::uint32_t version = 0; version < static_cast(MPRESS_UNPACKER_STUB_UNKNOWN); ++version) { - // Analysis of the trailing bytes of the section - // 64 bytes at the every section alignment multiple are checked, if they are all 0, the new section is probably here so it is created - // Only code section left after this function is the one containing the OEP, the code will execute even if some of the code is in data sections - // and decompiler will take care of this in front-end instruction decoder - std::uint32_t sectionAlignment = _peFile->peHeader().getSectionAlignment(); - std::uint32_t section = _peFile->peHeader().getSectionWithRva(_peFile->peHeader().getAddressOfEntryPoint()); - std::uint32_t startOffset = _peFile->peHeader().getPointerToRawData(section) - _peFile->peHeader().getPointerToRawData(_packedContentSect->getSecSeg()->getIndex()); - std::uint32_t endOffset = startOffset + _peFile->peHeader().getSizeOfRawData(section); - std::uint32_t oepOffset = _peFile->peHeader().getAddressOfEntryPoint() - _peFile->peHeader().getVirtualAddress(_packedContentSect->getSecSeg()->getIndex()); - - // Used to build the section names - std::uint32_t nameCounter = 3; - - // Start at the next section alignment closest to the start of current .text section - // Move with the step of section alignment - // End if end of the unpacked section is reached - for (std::uint32_t offset = startOffset + sectionAlignment; offset < endOffset; offset += sectionAlignment) + if (signature == mpressUnpackerStubData[version].signature) { - // Inspect trailing 64 bytes, if they are all 0, needSplit is marked as true, resulting in section split - bool needSplit = true; - for (std::uint8_t idx = 1; idx <= 64; ++idx) - { - if (buffer.read(offset - idx) != 0) - { - needSplit = false; - break; - } - } - - if (!needSplit) - continue; - - std::stringstream ssFirst, ssSecond; - std::uint32_t flags[2]; - // OEP lies in the first part of splitted sections - if (startOffset <= oepOffset && oepOffset < offset) - { - ssFirst << ".text"; - ssSecond << ".data" << nameCounter++; - flags[1] = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; - flags[0] = flags[1] | PeLib::PELIB_IMAGE_SCN_MEM_EXECUTE | PeLib::PELIB_IMAGE_SCN_CNT_CODE; - } - // OEP lies in the second part of splitted sections - else if (offset <= oepOffset && oepOffset < endOffset) - { - ssFirst << ".data" << nameCounter++; - ssSecond << ".text"; - flags[0] = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; - flags[1] = flags[0] | PeLib::PELIB_IMAGE_SCN_MEM_EXECUTE | PeLib::PELIB_IMAGE_SCN_CNT_CODE; - } - // OEP doesn't lie in neither of these two parts, this can happen if .text section was already created and we are analysis the rest after that - else - { - ssFirst << ".data" << nameCounter++; - ssSecond << ".data" << nameCounter++; - flags[0] = PeLib::PELIB_IMAGE_SCN_MEM_WRITE | PeLib::PELIB_IMAGE_SCN_MEM_READ | PeLib::PELIB_IMAGE_SCN_CNT_INITIALIZED_DATA; - flags[1] = flags[0]; - } - - _peFile->peHeader().splitSection(section, ssFirst.str(), ssSecond.str(), offset - startOffset); - _peFile->peHeader().setCharacteristics(section, flags[0]); - _peFile->peHeader().setCharacteristics(section + 1, flags[1]); - _peFile->peHeader().makeValid(_peFile->mzHeader().size()); - _addedSectionCount++; - section++; - startOffset = offset; + _unpackerStub = static_cast(version); + return static_cast(version); } } - void fixRelocations() + return MPRESS_UNPACKER_STUB_UNKNOWN; +} + +MpressFixStub MpressPlugin::detectFixStubVersion(DynamicBuffer& unpackedContent) +{ + std::uint32_t fixStub = getFixStub(); + for (std::uint32_t version = 0; version < static_cast(MPRESS_FIX_STUB_UNKNOWN); ++version) { - // We will only manipulate this section as all information are stored here - const retdec::loader::Segment* epSegment = _file->getEpSegment(); - - // Calculate the offset of EP in EP section - unsigned long long epAddress; - _file->getFileFormat()->getEpAddress(epAddress); - epAddress -= epSegment->getAddress(); - - // Read the data at the desired offsets - std::vector relocRvaBytes, relocSizeBytes; - epSegment->getBytes(relocRvaBytes, epAddress + mpressUnpackerStubData[_unpackerStub].relocOffset, 4); - epSegment->getBytes(relocSizeBytes, epAddress + mpressUnpackerStubData[_unpackerStub].relocSizeOffset, 4); - - DynamicBuffer relocRvaBuffer(relocRvaBytes, _file->getFileFormat()->getEndianness()); - DynamicBuffer relocSizeBuffer(relocSizeBytes, _file->getFileFormat()->getEndianness()); - - // When the size of relocation is 0, there are no relocations - std::uint32_t relocSize = relocSizeBuffer.read(0); - if (relocSize == 0) - return; - - // Set the base relocation directory in the new file - // All relocations are here undamaged so we are good with this - std::uint32_t relocRva = relocRvaBuffer.read(0); - _peFile->peHeader().setIddBaseRelocRva(relocRva); - _peFile->peHeader().setIddBaseRelocSize(relocSize); - _peFile->peHeader().makeValid(_peFile->mzHeader().size()); - } - - MpressUnpackerStub detectUnpackerStubVersion() - { - unsigned long long ep; - std::vector signatureBytes; - - // Get the data in EP section so we can compare it with signature - _file->getFileFormat()->getEpAddress(ep); - ep -= _file->getEpSegment()->getAddress(); - _file->getEpSegment()->getBytes(signatureBytes, ep + 8, 4); - - DynamicBuffer signatureBuffer(signatureBytes, _file->getFileFormat()->getEndianness()); - std::uint32_t signature = signatureBuffer.read(0); - - // Signature should not be bigger than 0xC00 for all versions of MPRESS - // This heuristic can catch corrupted unpacking stubs - if (signature >= 0xC00) - throw CorruptedUnpackingStubException(); - - for (std::uint32_t version = 0; version < static_cast(MPRESS_UNPACKER_STUB_UNKNOWN); ++version) + if (unpackedContent.read(fixStub + 7) == mpressFixStubData[version].signature) { - if (signature == mpressUnpackerStubData[version].signature) - { - _unpackerStub = static_cast(version); - return static_cast(version); - } + _fixStub = static_cast(version); + return static_cast(version); } - - return MPRESS_UNPACKER_STUB_UNKNOWN; } - MpressFixStub detectFixStubVersion(DynamicBuffer& unpackedContent) + return MPRESS_FIX_STUB_UNKNOWN; +} + +void MpressPlugin::saveFile(const std::string& fileName, DynamicBuffer& content) +{ + // Removes the file if it already exists + std::remove(fileName.c_str()); + + // Headers + _peFile->mzHeader().write(fileName, 0); + _peFile->peHeader().write(fileName, _peFile->mzHeader().size()); + _peFile->peHeader().writeSections(fileName); + + // Copy the section bytes from original file for the sections preceding the packed section + for (std::uint32_t index = 0; index < _packedContentSect->getSecSeg()->getIndex(); ++index) + copySectionFromOriginalFile(index, fileName, index); + + // Copy the section bytes in between packed section and EP section + for (std::uint32_t index = _packedContentSect->getSecSeg()->getIndex() + _addedSectionCount; index < _file->getEpSegment()->getSecSeg()->getIndex(); ++index) + copySectionFromOriginalFile(index, fileName, index + _addedSectionCount); + + // Copy the section bytes from original file for sections after EP section excluded + for (std::uint32_t index = _file->getEpSegment()->getSecSeg()->getIndex() + 1; index < _file->getNumberOfSegments(); ++index) + copySectionFromOriginalFile(index, fileName, index + _addedSectionCount); + + // Write content of new import section + _peFile->impDir().write(fileName, _peFile->peHeader().rvaToOffset(_peFile->peHeader().getIddImportRva()), _peFile->peHeader().getIddImportRva()); + + // After this all we need to update the IAT with the contents of ILT + // since Import Directory in PeLib is built after the write to the file + for (size_t fileIndex = 0; fileIndex < _peFile->impDir().getNumberOfFiles(PeLib::OLDDIR); ++fileIndex) { - std::uint32_t fixStub = getFixStub(); - for (std::uint32_t version = 0; version < static_cast(MPRESS_FIX_STUB_UNKNOWN); ++version) + auto tmp = static_cast(fileIndex); + auto w = static_cast(_packedContentSect->getSecSeg()->getIndex()); + size_t destOffset = _peFile->impDir().getFirstThunk(tmp, PeLib::OLDDIR) - _peFile->peHeader().getVirtualAddress(w); + for (size_t funcIndex = 0; funcIndex < _peFile->impDir().getNumberOfFunctions(tmp, PeLib::OLDDIR); ++funcIndex, destOffset += 4) { - if (unpackedContent.read(fixStub + 7) == mpressFixStubData[version].signature) - { - _fixStub = static_cast(version); - return static_cast(version); - } + content.write( + _peFile->impDir().getOriginalFirstThunk(tmp, static_cast(funcIndex), PeLib::OLDDIR), + static_cast(destOffset)); } - - return MPRESS_FIX_STUB_UNKNOWN; } - void saveFile(const std::string& fileName, DynamicBuffer& content) - { - // Removes the file if it already exists - std::remove(fileName.c_str()); + // Write the unpacked content to the packed content section + // Use regular file as we will write more sections at once + std::fstream outputFile(fileName, std::ios::binary | std::ios::out | std::ios::in); + outputFile.seekp(_peFile->peHeader().getPointerToRawData(_packedContentSect->getSecSeg()->getIndex()), std::ios_base::beg); + outputFile.write(reinterpret_cast(content.getRawBuffer()), content.getRealDataSize()); + outputFile.close(); +} - // Headers - _peFile->mzHeader().write(fileName, 0); - _peFile->peHeader().write(fileName, _peFile->mzHeader().size()); - _peFile->peHeader().writeSections(fileName); - - // Copy the section bytes from original file for the sections preceding the packed section - for (std::uint32_t index = 0; index < _packedContentSect->getSecSeg()->getIndex(); ++index) - copySectionFromOriginalFile(index, fileName, index); - - // Copy the section bytes in between packed section and EP section - for (std::uint32_t index = _packedContentSect->getSecSeg()->getIndex() + _addedSectionCount; index < _file->getEpSegment()->getSecSeg()->getIndex(); ++index) - copySectionFromOriginalFile(index, fileName, index + _addedSectionCount); - - // Copy the section bytes from original file for sections after EP section excluded - for (std::uint32_t index = _file->getEpSegment()->getSecSeg()->getIndex() + 1; index < _file->getNumberOfSegments(); ++index) - copySectionFromOriginalFile(index, fileName, index + _addedSectionCount); - - // Write content of new import section - _peFile->impDir().write(fileName, _peFile->peHeader().rvaToOffset(_peFile->peHeader().getIddImportRva()), _peFile->peHeader().getIddImportRva()); - - // After this all we need to update the IAT with the contents of ILT - // since Import Directory in PeLib is built after the write to the file - for (size_t fileIndex = 0; fileIndex < _peFile->impDir().getNumberOfFiles(PeLib::OLDDIR); ++fileIndex) - { - auto tmp = static_cast(fileIndex); - auto w = static_cast(_packedContentSect->getSecSeg()->getIndex()); - size_t destOffset = _peFile->impDir().getFirstThunk(tmp, PeLib::OLDDIR) - _peFile->peHeader().getVirtualAddress(w); - for (size_t funcIndex = 0; funcIndex < _peFile->impDir().getNumberOfFunctions(tmp, PeLib::OLDDIR); ++funcIndex, destOffset += 4) - { - content.write( - _peFile->impDir().getOriginalFirstThunk(tmp, static_cast(funcIndex), PeLib::OLDDIR), - static_cast(destOffset)); - } - } - - // Write the unpacked content to the packed content section - // Use regular file as we will write more sections at once - std::fstream outputFile(fileName, std::ios::binary | std::ios::out | std::ios::in); - outputFile.seekp(_peFile->peHeader().getPointerToRawData(_packedContentSect->getSecSeg()->getIndex()), std::ios_base::beg); - outputFile.write(reinterpret_cast(content.getRawBuffer()), content.getRealDataSize()); - outputFile.close(); - } - - void copySectionFromOriginalFile(std::uint32_t origSectIndex, const std::string& newFileName, std::uint32_t newSectIndex) - { - const retdec::loader::Segment* seg = _file->getSegment(origSectIndex); - std::vector bytes; - seg->getBytes(bytes); - _peFile->peHeader().writeSectionData(newFileName, newSectIndex, bytes); - } - - std::unique_ptr _file; - PeLib::PeFile32* _peFile; - MpressUnpackerStub _unpackerStub; - MpressFixStub _fixStub; - const retdec::loader::Segment* _packedContentSect; - std::uint32_t _addedSectionCount; - - std::uint32_t _iatVa, _iatSize; - std::uint32_t _oepVa; - std::uint32_t _importHintsOffset; -}; - -REGISTER_PLUGIN(MpressPlugin) +void MpressPlugin::copySectionFromOriginalFile(std::uint32_t origSectIndex, const std::string& newFileName, std::uint32_t newSectIndex) +{ + const retdec::loader::Segment* seg = _file->getSegment(origSectIndex); + std::vector bytes; + seg->getBytes(bytes); + _peFile->peHeader().writeSectionData(newFileName, newSectIndex, bytes); +} } // namespace mpress } // namespace unpackertool diff --git a/src/unpackertool/plugins/mpress/mpress.h b/src/unpackertool/plugins/mpress/mpress.h new file mode 100644 index 00000000..8621a92c --- /dev/null +++ b/src/unpackertool/plugins/mpress/mpress.h @@ -0,0 +1,123 @@ +/** + * @file src/unpackertool/plugins/mpress/mpress.h + * @brief Unpacker plugin for MPRESS packer. + * @copyright (c) 2017 Avast Software, licensed under the MIT license + */ + +#ifndef UNPACKERTOOL_PLUGINS_MPRESS_MPRESS_H +#define UNPACKERTOOL_PLUGINS_MPRESS_MPRESS_H + +#include "retdec/loader/loader.h" +#include "retdec/unpacker/dynamic_buffer.h" +#include "retdec/unpacker/plugin.h" + +#define mpress_plugin plugin(retdec::unpackertool::mpress::MpressPlugin) + +namespace retdec { +namespace unpackertool { +namespace mpress { + +enum Compression +{ + COMPRESSION_LZMA, + COMPRESSION_LZMAT, + COMPRESSION_UNKNOWN +}; + +enum MpressUnpackerStub +{ + MPRESS_UNPACKER_STUB_101_105, + MPRESS_UNPACKER_STUB_107_127, + MPRESS_UNPACKER_STUB_201, + MPRESS_UNPACKER_STUB_205_LZMA, + MPRESS_UNPACKER_STUB_205_LZMAT, + MPRESS_UNPACKER_STUB_212_219_LZMA, + MPRESS_UNPACKER_STUB_212_219_LZMAT, + MPRESS_UNPACKER_STUB_UNKNOWN +}; + +enum MpressFixStub +{ + MPRESS_FIX_STUB_10x, + MPRESS_FIX_STUB_127_20x, + MPRESS_FIX_STUB_21x, + MPRESS_FIX_STUB_UNKNOWN +}; + +struct MpressUnpackerStubData +{ + std::uint32_t signature; // Offset written near the EP which tells where is the offset to the fix imports stub + std::uint32_t packedContentOffset; // Offset of the section with the packed content + std::uint32_t fixStubOffset; // Offset from the EP where the offset of Fix Imports Stub is written + std::uint32_t relocOffset; // Offset from Fix Imports Stub where relocations are written + std::uint32_t relocSizeOffset; // Offset from the EP where size of relocations is written + Compression compression; // Compression method used while packing +}; + +struct MpressFixStubData +{ + std::uint32_t signature; // Byte at the beginning of fix imports stub + std::uint32_t importHintsOffset; // Offset from Fix Imports Stub where Import Hints are stored + std::uint32_t oepOffset; // Offset from Fix Imports Stub where the offset of OEP is written +}; + +static MpressUnpackerStubData mpressUnpackerStubData[MPRESS_UNPACKER_STUB_UNKNOWN] = +{ + { 0x2B6, 0x2BC, 0x2B8, 0x2C8, 0x2C0, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_101_105 + { 0x29E, 0x2A4, 0x2A0, 0x2B0, 0x2A8, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_107_127 + { 0x299, 0x29F, 0x29B, 0x2AB, 0x2A3, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_201 + { 0xB57, 0xB5D, 0xB59, 0xB61, 0xB69, COMPRESSION_LZMA }, // MPRESS_UNPACKER_STUB_205_LZMA + { 0x29C, 0x2A2, 0x29E, 0x2A6, 0x2AE, COMPRESSION_LZMAT }, // MPRESS_UNPACKER_STUB_205_LZMAT + { 0xB5A, 0xB60, 0xB5C, 0xB64, 0xB6C, COMPRESSION_LZMA }, // MPRESS_UNPACKER_STUB_212_219_LZMA + { 0x29F, 0x2A5, 0x2A1, 0x2A9, 0x2B1, COMPRESSION_LZMAT } // MPRESS_UNPACKER_STUB_212_219_LZMAT +}; + +static MpressFixStubData mpressFixStubData[MPRESS_FIX_STUB_UNKNOWN] = +{ + { 0x8B, 0xC1, 0xBD }, // MPRESS_FIX_STUB_10x + { 0x45, 0x138, 0x134 }, // MPRESS_FIX_STUB_127_20x + { 0x35, 0x128, 0x124 } // MPRESS_FIX_STUB_21x +}; + + +class MpressPlugin : public Plugin +{ +public: + MpressPlugin(); + virtual ~MpressPlugin() override; + + virtual void prepare() override; + virtual void unpack() override; + virtual void cleanup() override; + +private: + bool decompressData(unpacker::DynamicBuffer& compressedContent, unpacker::DynamicBuffer& decompressedContent); + void decodeLzmaProperties(unpacker::DynamicBuffer& compressedContent, std::uint8_t& pb, std::uint8_t& lp, std::uint8_t& lc); + std::uint32_t getFixStub(); + void fixJumpsAndCalls(unpacker::DynamicBuffer& buffer); + void fixImportsAndEp(unpacker::DynamicBuffer& buffer); + void offsetAnalysis(const unpacker::DynamicBuffer& buffer); + void trailingBytesAnalysis(const unpacker::DynamicBuffer& buffer); + void fixRelocations(); + MpressUnpackerStub detectUnpackerStubVersion(); + MpressFixStub detectFixStubVersion(unpacker::DynamicBuffer& unpackedContent); + void saveFile(const std::string& fileName, unpacker::DynamicBuffer& content); + void copySectionFromOriginalFile(std::uint32_t origSectIndex, const std::string& newFileName, std::uint32_t newSectIndex); + + std::unique_ptr _file; + PeLib::PeFile32* _peFile; + MpressUnpackerStub _unpackerStub; + MpressFixStub _fixStub; + const retdec::loader::Segment* _packedContentSect; + std::uint32_t _addedSectionCount; + + std::uint32_t _iatVa, _iatSize; + std::uint32_t _oepVa; + std::uint32_t _importHintsOffset; +}; + +} // namespace mpress +} // namespace unpackertool +} // namespace retdec + +#endif diff --git a/src/unpackertool/plugins/upx/CMakeLists.txt b/src/unpackertool/plugins/upx/CMakeLists.txt index e9a329ab..8b415d2f 100644 --- a/src/unpackertool/plugins/upx/CMakeLists.txt +++ b/src/unpackertool/plugins/upx/CMakeLists.txt @@ -15,14 +15,6 @@ set(UPX_SOURCES elf/elf_upx_stub.cpp ) -add_library(retdec-upx SHARED ${UPX_SOURCES}) -target_link_libraries(retdec-upx retdec-fileformat-headers) -target_include_directories(retdec-upx PUBLIC ${PROJECT_SOURCE_DIR}/src/) -# Plugin related libraries are linked to the plugin on Windows -if(MSVC OR APPLE) - target_link_libraries(retdec-upx retdec-utils retdec-unpacker) -endif() -install(TARGETS retdec-upx - LIBRARY DESTINATION bin/unpacker-plugins - RUNTIME DESTINATION bin/unpacker-plugins -) +add_library(retdec-unpacker-upx STATIC ${UPX_SOURCES}) +target_link_libraries(retdec-unpacker-upx retdec-unpacker retdec-fileformat retdec-utils) +target_include_directories(retdec-unpacker-upx PUBLIC ${PROJECT_SOURCE_DIR}/src/) diff --git a/src/unpackertool/plugins/upx/decompressors/decompressor.cpp b/src/unpackertool/plugins/upx/decompressors/decompressor.cpp index c3299e65..3207a68d 100644 --- a/src/unpackertool/plugins/upx/decompressors/decompressor.cpp +++ b/src/unpackertool/plugins/upx/decompressors/decompressor.cpp @@ -40,9 +40,9 @@ void Decompressor::performDecompression(const std::weak_ptr& com auto compressedData = compressedDataWptr.lock(); if (!compressedData->decompress(unpackedData)) { - if (this_plugin()->getStartupArguments()->brute) + if (upx_plugin->getStartupArguments()->brute) { - this_plugin()->log("Bruteforcing compressed data with XOR."); + upx_plugin->log("Bruteforcing compressed data with XOR."); // If we failed, try to bruteforce the data with XOR DynamicBuffer originalData = compressedData->getBuffer(); @@ -54,7 +54,7 @@ void Decompressor::performDecompression(const std::weak_ptr& com if (compressedData->decompress(unpackedData)) { - this_plugin()->log("Bruteforcing compressed data with XOR succeeded on XOR value 0x", std::hex, i, std::dec, "."); + upx_plugin->log("Bruteforcing compressed data with XOR succeeded on XOR value 0x", std::hex, i, std::dec, "."); return; } } diff --git a/src/unpackertool/plugins/upx/elf/elf_upx_stub.cpp b/src/unpackertool/plugins/upx/elf/elf_upx_stub.cpp index f917c15e..e887609e 100644 --- a/src/unpackertool/plugins/upx/elf/elf_upx_stub.cpp +++ b/src/unpackertool/plugins/upx/elf/elf_upx_stub.cpp @@ -106,7 +106,7 @@ template void ElfUpxStub::unpack(const std::string& outputFile) AddressType segReadPos; DynamicBuffer unpackedData(_file->getFileFormat()->getEndianness()); - this_plugin()->log("Unpacking block at file offset 0x", std::hex, firstBlockOffset + readPos, std::dec, "."); + upx_plugin->log("Unpacking block at file offset 0x", std::hex, firstBlockOffset + readPos, std::dec, "."); unpackBlock(unpackedData, firstBlockOffset + readPos, segReadPos, originalProgHeaders[i].p_filesz); retdec::utils::writeFile(output, unpackedData.getBuffer(), initialOffset + originalProgHeaders[i].p_offset); @@ -170,7 +170,7 @@ template void ElfUpxStub::unpack(const std::string& outputFile) if (additionalDataBehindStub) { - this_plugin()->log("Additional packed data detected at the end of the file."); + upx_plugin->log("Additional packed data detected at the end of the file."); // These data are always at the offset which is aligned by 4 additionalDataPos = retdec::utils::alignUp(_file->getEpSegment()->getSize(), 4); @@ -180,7 +180,7 @@ template void ElfUpxStub::unpack(const std::string& outputFile) } else { - this_plugin()->log("Additional packed data detected between LOAD segments."); + upx_plugin->log("Additional packed data detected between LOAD segments."); // We just continue reading where we ended up additionalDataPos = readPos + firstBlockOffset; @@ -189,7 +189,7 @@ template void ElfUpxStub::unpack(const std::string& outputFile) additionalDataSize = ep - additionalDataPos; } - this_plugin()->log("Additional data are at file offset 0x", std::hex, additionalDataPos, + upx_plugin->log("Additional data are at file offset 0x", std::hex, additionalDataPos, " and have size of 0x", additionalDataSize, std::dec, "."); std::vector additionalDataBytes; @@ -208,7 +208,7 @@ template void ElfUpxStub::unpack(const std::string& outputFile) // If there is, unpack data into the gap DynamicBuffer unpackedData(_file->getFileFormat()->getEndianness()); - this_plugin()->log("Unpacking block from additional data behind segment ", i, "."); + upx_plugin->log("Unpacking block from additional data behind segment ", i, "."); unpackBlock(unpackedData, additionalData, readPos); retdec::utils::writeFile(output, unpackedData.getBuffer(), originalProgHeaders[i].p_offset + originalProgHeaders[i].p_filesz); @@ -221,7 +221,7 @@ template void ElfUpxStub::unpack(const std::string& outputFile) { DynamicBuffer unpackedData(_file->getFileFormat()->getEndianness()); - this_plugin()->log("Unpacking last block from additional data at the end of the file."); + upx_plugin->log("Unpacking last block from additional data at the end of the file."); unpackBlock(unpackedData, additionalData, readPos); output.seekp(0, std::ios::end); @@ -486,7 +486,7 @@ template void ElfUpxStub::unfilterBlock(const DynamicBuffer& pa if (!ret) throw UnsupportedFilterException(filterId); - this_plugin()->log("Unfiltering filter 0x", std::hex, static_cast(filterId), std::dec, " with parameter ", static_cast(filterParam), "."); + upx_plugin->log("Unfiltering filter 0x", std::hex, static_cast(filterId), std::dec, " with parameter ", static_cast(filterParam), "."); } // Explicit instantiation. diff --git a/src/unpackertool/plugins/upx/macho/macho_upx_stub.cpp b/src/unpackertool/plugins/upx/macho/macho_upx_stub.cpp index ff26cd0a..ad3916fe 100644 --- a/src/unpackertool/plugins/upx/macho/macho_upx_stub.cpp +++ b/src/unpackertool/plugins/upx/macho/macho_upx_stub.cpp @@ -99,7 +99,7 @@ template void MachOUpxStub::unpack(const std::string& outputFil } else { - this_plugin()->log("Unpacking universal Mach-O."); + upx_plugin->log("Unpacking universal Mach-O."); std::vector> offsetAndSize; std::uint32_t offset = 0; @@ -114,7 +114,7 @@ template void MachOUpxStub::unpack(const std::string& outputFil else offset = retdec::utils::alignUp(output.tellp(), 0x1000); - this_plugin()->log("Unpacking architecture ", archToName(machoFormat->getTargetArchitecture()), " from universal Mach-O."); + upx_plugin->log("Unpacking architecture ", archToName(machoFormat->getTargetArchitecture()), " from universal Mach-O."); // This is not nice, but we have no other option. MachOUpxStub is templated class and therefore @c bits is set to 32 or 64 based on the bit width of the architecture. // Universal Mach-O binaries may contain both 32 and 64 bits binaries. If we want to get correct values out of MachOUpxStubTraits we need to create new versions of these @@ -180,7 +180,7 @@ template void MachOUpxStub::unpack(std::ifstream& inputFile, st // Move to the specific offset of the first packed block. inputFile.seekg(baseInputOffset + getFirstBlockOffset(inputFile), std::ios::beg); - this_plugin()->log("Unpacking original Mach-O header."); + upx_plugin->log("Unpacking original Mach-O header."); // First read packed original Mach-O header and unpack it. DynamicBuffer packedOriginalHeader = readNextBlock(inputFile); @@ -223,7 +223,7 @@ template void MachOUpxStub::unpack(std::ifstream& inputFile, st if (command.filesize == 0) continue; - this_plugin()->log("Unpacking block of load segment command with file offset 0x", std::hex, command.fileoff, + upx_plugin->log("Unpacking block of load segment command with file offset 0x", std::hex, command.fileoff, " and file size 0x", command.filesize, std::dec, "."); DynamicBuffer packedBlock = readNextBlock(inputFile); @@ -327,7 +327,7 @@ template void MachOUpxStub::unfilterBlock(const DynamicBuffer& if (!ret) throw UnsupportedFilterException(filterId); - this_plugin()->log("Unfiltering filter 0x", std::hex, static_cast(filterId), std::dec, " with parameter ", static_cast(filterParam), "."); + upx_plugin->log("Unfiltering filter 0x", std::hex, static_cast(filterId), std::dec, " with parameter ", static_cast(filterParam), "."); } // Explicit instantiation. diff --git a/src/unpackertool/plugins/upx/pe/pe_upx_stub.cpp b/src/unpackertool/plugins/upx/pe/pe_upx_stub.cpp index 6c4a85f7..46505bec 100644 --- a/src/unpackertool/plugins/upx/pe/pe_upx_stub.cpp +++ b/src/unpackertool/plugins/upx/pe/pe_upx_stub.cpp @@ -380,11 +380,11 @@ template void PeUpxStub::detectUnfilter(const DynamicBuffer& un _filterId = FILTER_NONE; _filterCount = 0; _filterParam = 0; - this_plugin()->log("No filter detected, or unknown filter present in the file."); + upx_plugin->log("No filter detected, or unknown filter present in the file."); return; } - this_plugin()->log("Detected filter 0x", std::hex, _filterId, " with parameter 0x", _filterParam, " based on ", detectionBasedOn, "."); + upx_plugin->log("Detected filter 0x", std::hex, _filterId, " with parameter 0x", _filterParam, " based on ", detectionBasedOn, "."); } template void PeUpxStub::unpackData(DynamicBuffer& unpackedData) @@ -420,7 +420,7 @@ template void PeUpxStub::unpackData(DynamicBuffer& unpackedData } } - this_plugin()->log("Unpacked data based on ", tryAgain ? "UPX metadata." : "signature."); + upx_plugin->log("Unpacked data based on ", tryAgain ? "UPX metadata." : "signature."); } /** @@ -574,7 +574,7 @@ template UpxExtraData PeUpxStub::parseExtraData(retdec::unpacke throw OriginalHeaderCorruptedException(); originalHeader = DynamicBuffer(unpackedData, originalHeaderOffset, sectionHeadersEnd); - this_plugin()->log("Original header found at address 0x", std::hex, originalHeaderOffset, std::dec, " in extra data."); + upx_plugin->log("Original header found at address 0x", std::hex, originalHeaderOffset, std::dec, " in extra data."); // Extra data starts right after original PE header std::uint32_t upxExtraDataOffset = originalHeaderOffset + sectionHeadersEnd; @@ -587,7 +587,7 @@ template UpxExtraData PeUpxStub::parseExtraData(retdec::unpacke { extraData.setImportsOffset(unpackedData.read(upxExtraDataOffset)); upxExtraDataOffset += 8; - this_plugin()->log("Import hints address 0x", std::hex, extraData.getImportsOffset(), std::dec, " found in extra data."); + upx_plugin->log("Import hints address 0x", std::hex, extraData.getImportsOffset(), std::dec, " found in extra data."); } // If RVA in relocations directory is set (relocs RVA and size must be non-zero and RELOCS_STRIPPED flag cannot be set) @@ -600,7 +600,7 @@ template UpxExtraData PeUpxStub::parseExtraData(retdec::unpacke extraData.setRelocationsOffset(unpackedData.read(upxExtraDataOffset)); extraData.setRelocationsBigEndian(unpackedData.read(upxExtraDataOffset + 4)); upxExtraDataOffset += 5; - this_plugin()->log("Relocations hints address 0x", std::hex, extraData.getRelocationsOffset(), std::dec, " found in extra data."); + upx_plugin->log("Relocations hints address 0x", std::hex, extraData.getRelocationsOffset(), std::dec, " found in extra data."); } return extraData; @@ -800,7 +800,7 @@ template void PeUpxStub::fixTls(const DynamicBuffer& originalHe if (tlsRva >= _newPeFile->peHeader().getSizeOfImage()) throw InvalidDataDirectoryException("TLS"); - this_plugin()->log("Original TLS directory found at RVA 0x", std::hex, tlsRva, " with size 0x", tlsSize, std::dec, "."); + upx_plugin->log("Original TLS directory found at RVA 0x", std::hex, tlsRva, " with size 0x", tlsSize, std::dec, "."); } /** @@ -816,7 +816,7 @@ template void PeUpxStub::fixOep(const DynamicBuffer& originalHe _newPeFile->peHeader().setAddressOfEntryPoint(originalHeader.read(0x28)); _newPeFile->peHeader().makeValid(_newPeFile->mzHeader().size()); - this_plugin()->log("Original entry point address set to 0x", std::hex, _newPeFile->peHeader().getAddressOfEntryPoint(), std::dec, "."); + upx_plugin->log("Original entry point address set to 0x", std::hex, _newPeFile->peHeader().getAddressOfEntryPoint(), std::dec, "."); } /** @@ -852,7 +852,7 @@ template void PeUpxStub::fixExports(const retdec::unpacker::Dyn if (exportsRva >= _newPeFile->peHeader().getSizeOfImage()) throw InvalidDataDirectoryException("Exports"); - this_plugin()->log("Original exports directory found at RVA 0x", std::hex, exportsRva, " with size 0x", exportsSize, std::dec, "."); + upx_plugin->log("Original exports directory found at RVA 0x", std::hex, exportsRva, " with size 0x", exportsSize, std::dec, "."); // Calculate the offset of exports in UPX2 section std::uint32_t exportsVa = _newPeFile->peHeader().rvaToVa(oldExportsRva); @@ -938,7 +938,7 @@ template void PeUpxStub::fixLoadConfiguration(const DynamicBuff if (loadConfigRva >= _newPeFile->peHeader().getSizeOfImage()) throw InvalidDataDirectoryException("Load configuration"); - this_plugin()->log("Original load configuration directory found at RVA 0x", std::hex, loadConfigRva, " with size 0x", loadConfigSize, std::dec, "."); + upx_plugin->log("Original load configuration directory found at RVA 0x", std::hex, loadConfigRva, " with size 0x", loadConfigSize, std::dec, "."); } /** @@ -973,7 +973,7 @@ template void PeUpxStub::fixResources(const DynamicBuffer& unpa if (compressedRsrcRva >= _newPeFile->peHeader().getSizeOfImage()) throw InvalidDataDirectoryException("Resources"); - this_plugin()->log("Original resources directory found at RVA 0x", std::hex, compressedRsrcRva, " with size 0x", compressedRsrcSize, std::dec, "."); + upx_plugin->log("Original resources directory found at RVA 0x", std::hex, compressedRsrcRva, " with size 0x", compressedRsrcSize, std::dec, "."); unsigned long long imageBase = _file->getBaseAddress(); @@ -1092,7 +1092,7 @@ template void PeUpxStub::fixCoffSymbolTable() // MinGW files use COFF symbols even though it shouldn't be used for EXEs if (_newPeFile->peHeader().getPointerToSymbolTable() > 0) // Check whether COFF symbol table exists { - this_plugin()->log("Detected COFF symbol table. Packed file may contain DWARF debug info."); + upx_plugin->log("Detected COFF symbol table. Packed file may contain DWARF debug info."); // Calculate the starting offset of COFF symbols by calculating raw sizes of all sections in packed file std::uint32_t totalSectionSize = _file->getSegment(0)->getSecSeg()->getOffset(); @@ -1114,7 +1114,7 @@ template void PeUpxStub::fixCoffSymbolTable() _newPeFile->peHeader().setPointerToSymbolTable(newSymbolTablePointer); } else - this_plugin()->log("Packed file seems to be truncated. Not copying DWARF debug info."); + upx_plugin->log("Packed file seems to be truncated. Not copying DWARF debug info."); } } @@ -1134,7 +1134,7 @@ template void PeUpxStub::fixCertificates() if (securityOffset == 0) return; - this_plugin()->log("Original certificates directory found at offset 0x", std::hex, securityOffset, " with size 0x", securitySize, std::dec, "."); + upx_plugin->log("Original certificates directory found at offset 0x", std::hex, securityOffset, " with size 0x", securitySize, std::dec, "."); // Calculate the offset of certificates in the overlay because certificates does not always begin // at the start of overlay. @@ -1235,7 +1235,7 @@ template void PeUpxStub::saveFile(const std::string& outputFile std::uint32_t overlaySize = static_cast(_file->getFileFormat()->getLoadedFileLength() - _file->getFileFormat()->getDeclaredFileLength()); std::vector overlay(overlaySize); - this_plugin()->log("Packed file has overlay with size of 0x", std::hex, overlaySize, std::dec, " bytes. Copying into unpacked file."); + upx_plugin->log("Packed file has overlay with size of 0x", std::hex, overlaySize, std::dec, " bytes. Copying into unpacked file."); std::fstream inputFileHandle(_file->getFileFormat()->getPathToFile(), std::ios::binary | std::ios::in); retdec::utils::readFile(inputFileHandle, overlay, _file->getFileFormat()->getDeclaredFileLength(), overlaySize); diff --git a/src/unpackertool/plugins/upx/upx.cpp b/src/unpackertool/plugins/upx/upx.cpp index b839b1ac..09bc1e1b 100644 --- a/src/unpackertool/plugins/upx/upx.cpp +++ b/src/unpackertool/plugins/upx/upx.cpp @@ -21,13 +21,15 @@ namespace retdec { namespace unpackertool { namespace upx { -REGISTER_PLUGIN(UpxPlugin) - /** * Constructor. */ UpxPlugin::UpxPlugin() : _file(), _stub() { + info.name = "UPX"; + info.pluginVersion = "1.0"; + info.packerVersion = R"/(.*)/"; + info.author = "Marek Milkovic"; } /** @@ -35,17 +37,7 @@ UpxPlugin::UpxPlugin() : _file(), _stub() */ UpxPlugin::~UpxPlugin() { -} - -/** - * Initialization of plugin providing @ref Plugin::Info data. - */ -void UpxPlugin::init() -{ - info.name = "UPX"; - info.pluginVersion = "1.0"; - info.packerVersion = R"/(.*)/"; - info.author = "Marek Milkovic"; + cleanup(); } /** diff --git a/src/unpackertool/plugins/upx/upx.h b/src/unpackertool/plugins/upx/upx.h index 73b8ba5f..c917d855 100644 --- a/src/unpackertool/plugins/upx/upx.h +++ b/src/unpackertool/plugins/upx/upx.h @@ -13,6 +13,8 @@ #include "retdec/unpacker/plugin.h" #include "unpackertool/plugins/upx/upx_stub.h" +#define upx_plugin plugin(retdec::unpackertool::upx::UpxPlugin) + namespace retdec { namespace unpackertool { namespace upx { @@ -33,7 +35,6 @@ public: UpxPlugin(); virtual ~UpxPlugin(); - virtual void init() override; virtual void prepare() override; virtual void unpack() override; virtual void cleanup() override; @@ -43,8 +44,6 @@ private: std::shared_ptr _stub; ///< Correct version of unpacking stub. }; -MAKE_PLUGIN_SHARED(UpxPlugin) - } // namespace upx } // namespace unpackertool } // namespace retdec diff --git a/src/unpackertool/plugins/upx/upx_stub.cpp b/src/unpackertool/plugins/upx/upx_stub.cpp index 4243f57f..cdd50cea 100644 --- a/src/unpackertool/plugins/upx/upx_stub.cpp +++ b/src/unpackertool/plugins/upx/upx_stub.cpp @@ -277,7 +277,7 @@ std::shared_ptr UpxStub::_createStubImpl(retdec::loader::Image* file, c else if (metadata.isDefined()) detectionBasedOn = "metadata"; - this_plugin()->log("Detected ", compressionName, " unpacking stub based on ", detectionBasedOn, "."); + upx_plugin->log("Detected ", compressionName, " unpacking stub based on ", detectionBasedOn, "."); UpxStub* stub = nullptr; switch (file->getFileFormat()->getFileFormat()) diff --git a/src/unpackertool/singleton.h b/src/unpackertool/singleton.h deleted file mode 100644 index 585b9207..00000000 --- a/src/unpackertool/singleton.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file src/unpackertool/singleton.h - * @brief Templated singleton declaration. - * @copyright (c) 2017 Avast Software, licensed under the MIT license - */ - -#ifndef UNPACKERTOOL_SINGLETON_H -#define UNPACKERTOOL_SINGLETON_H - -namespace retdec { -namespace unpackertool { - -#define IS_SINGLETON(CLASS) friend class Singleton; - -/** - * @brief Abstract singleton class. - * - * This abstract singleton class can be used for making - * singletons from any class. You need to put IS_SINGLETON(ClassName) - * in the class you want to make singleton. - */ -template class Singleton -{ -public: - virtual ~Singleton() {} - - /** - * Returns the instance of the singleton. - * - * @return Singleton instance. - */ - static T& instance() - { - static T instance; - return instance; - } - -private: - Singleton(); - Singleton(const Singleton&); - Singleton& operator =(const Singleton&); -}; - -} // namespace unpackertool -} // namespace retdec - -#endif diff --git a/src/unpackertool/unpacker.cpp b/src/unpackertool/unpacker.cpp index 779fac1a..58a26c48 100644 --- a/src/unpackertool/unpacker.cpp +++ b/src/unpackertool/unpacker.cpp @@ -77,7 +77,7 @@ ExitCode unpackFile(const std::string& inputFile, const std::string& outputFile, ExitCode ret = EXIT_CODE_NOTHING_TO_DO; for (const auto& detectedPacker : detectedPackers) { - PluginList plugins = sPluginMgr.matchingPlugins(detectedPacker.name, detectedPacker.versionInfo); + PluginList plugins = PluginMgr::matchingPlugins(detectedPacker.name, detectedPacker.versionInfo); if (plugins.empty()) { @@ -113,13 +113,6 @@ ExitCode processArgs(ArgHandler& handler, char argc, char** argv) return EXIT_CODE_OK; } - // First of all, load plugins - // If -d|--dir argument isn't specified, take the current path from argv[0] - std::string pluginsDir = FilesystemPath(argv[0]).getParentPath().append("/unpacker-plugins"); - if (handler["dir"]->used) - pluginsDir = handler["dir"]->input; - sPluginMgr.loadPlugins(pluginsDir); - bool brute = handler["brute"]->used; // -h|--help @@ -132,16 +125,12 @@ ExitCode processArgs(ArgHandler& handler, char argc, char** argv) { std::cout << "List of available plugins:" << std::endl; - const PluginTable& plugins = sPluginMgr.plugins(); - for (const auto& pluginPair : plugins) + for (const auto& plugin : PluginMgr::plugins) { - for (const auto& plugin : pluginPair.second) - { - const Plugin::Info* info = plugin->getInfo(); - std::cout << info->name << " " << info->pluginVersion - << " for packer '" << info->name << " " << info->packerVersion - << "' (" << info->author << ")" << std::endl; - } + const Plugin::Info* info = plugin->getInfo(); + std::cout << info->name << " " << info->pluginVersion + << " for packer '" << info->name << " " << info->packerVersion + << "' (" << info->author << ")" << std::endl; } } // PACKED_FILE [-o|--output FILE] @@ -178,7 +167,7 @@ int main(int argc, char** argv) " -h|--help Prints this help message.\n" "\n" "Listing group:\n" - " -p|--plugins Prints the list of all available plugins in the plugin directory.\n" + " -p|--plugins Prints the list of all available plugins.\n" "\n" "Unpacking group:\n" " PACKED_FILE Specifies the packed file, which is needed to be unpacked.\n" @@ -186,9 +175,6 @@ int main(int argc, char** argv) " Default value is 'PACKED_FILE-unpacked'.\n" "\n" "Non-group optional arguments:\n" - " -d|--dir DIR Specifies the directory that contains all the plugins as DIR.\n" - " This directory is recursively searched. If no -d|--dir options is\n" - " specified, the default search path is /path/to/unpacker/unpacker-plugins/.\n" " -b|--brute Tell unpacker to run plugins in the brute mode. Plugins may or may not\n" " implement brute methods for unpacking. They can completely ignore this argument." ); @@ -196,7 +182,6 @@ int main(int argc, char** argv) handler.registerArg('h', "help", false); handler.registerArg('o', "output", true); handler.registerArg('p', "plugins", false); - handler.registerArg('d', "dir", true); handler.registerArg('b', "brute", false); return processArgs(handler, argc, argv);