diff --git a/.gitignore b/.gitignore index 53b0920..1d6caeb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ cmake-build-debug cmake-build-release build .vscode +examples_build diff --git a/.travis.yml b/.travis.yml index 4d78ce8..ff11b28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,32 @@ language: cpp + os: - linux - osx + compiler: - clang - gcc + before_install: -- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get update -qq ; fi -- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update ; fi -script: cmake . && make +- ./travis.sh "$TRAVIS_OS_NAME" initialize +script: +- ./travis.sh "$TRAVIS_OS_NAME" build + matrix: exclude: - compiler: gcc os: osx + env: global: - secure: "O+BGqz4ugoVIJbQTh0dJjKRrsSVzkCYSe0WpRzEWK3l8Mw7hqX300g81TxRwTzN2zfUsROMzaeGaXWfGzYakgW59K1WIioaczxtv2MzzUQTbqzJPa+qQoP9bk/b2wJ5jcOL965/rudRju4UiIwuIgzDAMN3nAfIEJgV/2zANLIg=" + addons: coverity_scan: project: name: "trailofbits/pe-parse" description: "Principled, lightweight C/C++ PE parser" notification_email: dan@trailofbits.com - build_command_prepend: "cmake ." - build_command: "make" + build_command: "./travis.sh linux build" branch_pattern: master diff --git a/CMakeLists.txt b/CMakeLists.txt index fc7f267..0298add 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required(VERSION 3.1) project(pe-parse) +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "Default install directory" FORCE) +endif () + set(CMAKE_VERBOSE_MAKEFILE True) if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo") @@ -9,5 +13,8 @@ endif () include(cmake/compilation_flags.cmake) list(APPEND GLOBAL_CXXFLAGS ${DEFAULT_CXX_FLAGS}) -add_subdirectory(parser-library) -add_subdirectory(dump-prog) +add_subdirectory(pe-parser-library) +add_subdirectory(dump-pe) + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") diff --git a/README.md b/README.md index e1fd6ac..ed45bc6 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,19 @@ pe-parse is built using `cmake` and has no major dependencies. 2. `cmake .` 3. `make` +Using the library +======= +Once the library is installed, linking to it is easy! Add the following lines in your CMake project: + +``` +find_package(peparse REQUIRED) + +target_link_libraries(your_target_name ${PEPARSE_LIBRARIES}) +target_include_directories(your_target_name PRIVATE ${PEPARSE_INCLUDE_DIRS}) +``` + +You can see a full example in the examples/peaddrconv folder. + Authors ======= pe-parse was designed and implemented by Andrew Ruef (andrew@trailofbits.com), with significant contributions from [Wesley Shields](https://github.com/wxsBSD). diff --git a/cmake/compilation_flags.cmake b/cmake/compilation_flags.cmake index d22807c..ef193ca 100644 --- a/cmake/compilation_flags.cmake +++ b/cmake/compilation_flags.cmake @@ -1,5 +1,5 @@ if (WIN32) - list(APPEND DEFAULT_CXX_FLAGS /W4) + list(APPEND DEFAULT_CXX_FLAGS /W4 /analyze) if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") list(APPEND DEFAULT_CXX_FLAGS /Zi) @@ -24,13 +24,13 @@ else () ) if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - list(APPEND PROJECT_CXXFLAGS -gdwarf-2 -g3) + list(APPEND DEFAULT_CXX_FLAGS -gdwarf-2 -g3) endif () if (CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "This is a debug build; enabling -Weverything...") - list(APPEND PROJECT_CXXFLAGS + list(APPEND DEFAULT_CXX_FLAGS -Weverything -Wno-c++98-compat -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-global-constructors -Wno-exit-time-destructors -Wno-padded -Wno-error diff --git a/dump-prog/CMakeLists.txt b/dump-pe/CMakeLists.txt similarity index 60% rename from dump-prog/CMakeLists.txt rename to dump-pe/CMakeLists.txt index 5ecd67b..17bc535 100644 --- a/dump-prog/CMakeLists.txt +++ b/dump-pe/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.1) -project(dump-prog) +project(dump-pe) -add_executable(${PROJECT_NAME} dump.cpp) +add_executable(${PROJECT_NAME} main.cpp) target_link_libraries(${PROJECT_NAME} PRIVATE pe-parser-library) target_compile_options(${PROJECT_NAME} PRIVATE ${GLOBAL_CXXFLAGS}) + +install(TARGETS ${PROJECT_NAME} DESTINATION "bin") diff --git a/dump-prog/dump.cpp b/dump-pe/main.cpp similarity index 63% rename from dump-prog/dump.cpp rename to dump-pe/main.cpp index 5897fd5..e6982fd 100644 --- a/dump-prog/dump.cpp +++ b/dump-pe/main.cpp @@ -22,9 +22,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "parse.h" #include #include +#include + +#include using namespace std; using namespace peparse; @@ -273,112 +275,118 @@ int printSecs(void *N, << endl; int main(int argc, char *argv[]) { - if (argc == 2) { - parsed_pe *p = ParsePEFromFile(argv[1]); + if (argc != 2 || (argc == 2 && std::strcmp(argv[1], "--help") == 0)) { + std::cout << "dump-pe utility from Trail of Bits\n"; + std::cout << "Repository: https://github.com/trailofbits/pe-parse\n\n"; + std::cout << "Usage:\n\tdump-pe /path/to/executable.exe\n"; + return 1; + } - if (p != NULL) { - // print out some things - DUMP_FIELD(Signature); - DUMP_FIELD(FileHeader.Machine); - DUMP_FIELD(FileHeader.NumberOfSections); - DUMP_DEC_FIELD(FileHeader.TimeDateStamp); - DUMP_FIELD(FileHeader.PointerToSymbolTable); - DUMP_DEC_FIELD(FileHeader.NumberOfSymbols); - DUMP_FIELD(FileHeader.SizeOfOptionalHeader); - DUMP_FIELD(FileHeader.Characteristics); - if (p->peHeader.nt.OptionalMagic == NT_OPTIONAL_32_MAGIC) { - DUMP_FIELD(OptionalHeader.Magic); - DUMP_DEC_FIELD(OptionalHeader.MajorLinkerVersion); - DUMP_DEC_FIELD(OptionalHeader.MinorLinkerVersion); - DUMP_FIELD(OptionalHeader.SizeOfCode); - DUMP_FIELD(OptionalHeader.SizeOfInitializedData); - DUMP_FIELD(OptionalHeader.SizeOfUninitializedData); - DUMP_FIELD(OptionalHeader.AddressOfEntryPoint); - DUMP_FIELD(OptionalHeader.BaseOfCode); - DUMP_FIELD(OptionalHeader.BaseOfData); - DUMP_FIELD(OptionalHeader.ImageBase); - DUMP_FIELD(OptionalHeader.SectionAlignment); - DUMP_FIELD(OptionalHeader.FileAlignment); - DUMP_DEC_FIELD(OptionalHeader.MajorOperatingSystemVersion); - DUMP_DEC_FIELD(OptionalHeader.MinorOperatingSystemVersion); - DUMP_DEC_FIELD(OptionalHeader.Win32VersionValue); - DUMP_FIELD(OptionalHeader.SizeOfImage); - DUMP_FIELD(OptionalHeader.SizeOfHeaders); - DUMP_FIELD(OptionalHeader.CheckSum); - DUMP_FIELD(OptionalHeader.Subsystem); - DUMP_FIELD(OptionalHeader.DllCharacteristics); - DUMP_FIELD(OptionalHeader.SizeOfStackReserve); - DUMP_FIELD(OptionalHeader.SizeOfStackCommit); - DUMP_FIELD(OptionalHeader.SizeOfHeapReserve); - DUMP_FIELD(OptionalHeader.SizeOfHeapCommit); - DUMP_FIELD(OptionalHeader.LoaderFlags); - DUMP_DEC_FIELD(OptionalHeader.NumberOfRvaAndSizes); - } else { - DUMP_FIELD(OptionalHeader64.Magic); - DUMP_DEC_FIELD(OptionalHeader64.MajorLinkerVersion); - DUMP_DEC_FIELD(OptionalHeader64.MinorLinkerVersion); - DUMP_FIELD(OptionalHeader64.SizeOfCode); - DUMP_FIELD(OptionalHeader64.SizeOfInitializedData); - DUMP_FIELD(OptionalHeader64.SizeOfUninitializedData); - DUMP_FIELD(OptionalHeader64.AddressOfEntryPoint); - DUMP_FIELD(OptionalHeader64.BaseOfCode); - DUMP_FIELD(OptionalHeader64.ImageBase); - DUMP_FIELD(OptionalHeader64.SectionAlignment); - DUMP_FIELD(OptionalHeader64.FileAlignment); - DUMP_DEC_FIELD(OptionalHeader64.MajorOperatingSystemVersion); - DUMP_DEC_FIELD(OptionalHeader64.MinorOperatingSystemVersion); - DUMP_DEC_FIELD(OptionalHeader64.Win32VersionValue); - DUMP_FIELD(OptionalHeader64.SizeOfImage); - DUMP_FIELD(OptionalHeader64.SizeOfHeaders); - DUMP_FIELD(OptionalHeader64.CheckSum); - DUMP_FIELD(OptionalHeader64.Subsystem); - DUMP_FIELD(OptionalHeader64.DllCharacteristics); - DUMP_FIELD(OptionalHeader64.SizeOfStackReserve); - DUMP_FIELD(OptionalHeader64.SizeOfStackCommit); - DUMP_FIELD(OptionalHeader64.SizeOfHeapReserve); - DUMP_FIELD(OptionalHeader64.SizeOfHeapCommit); - DUMP_FIELD(OptionalHeader64.LoaderFlags); - DUMP_DEC_FIELD(OptionalHeader64.NumberOfRvaAndSizes); - } + parsed_pe *p = ParsePEFromFile(argv[1]); + + if (p != NULL) { + // print out some things + DUMP_FIELD(Signature); + DUMP_FIELD(FileHeader.Machine); + DUMP_FIELD(FileHeader.NumberOfSections); + DUMP_DEC_FIELD(FileHeader.TimeDateStamp); + DUMP_FIELD(FileHeader.PointerToSymbolTable); + DUMP_DEC_FIELD(FileHeader.NumberOfSymbols); + DUMP_FIELD(FileHeader.SizeOfOptionalHeader); + DUMP_FIELD(FileHeader.Characteristics); + if (p->peHeader.nt.OptionalMagic == NT_OPTIONAL_32_MAGIC) { + DUMP_FIELD(OptionalHeader.Magic); + DUMP_DEC_FIELD(OptionalHeader.MajorLinkerVersion); + DUMP_DEC_FIELD(OptionalHeader.MinorLinkerVersion); + DUMP_FIELD(OptionalHeader.SizeOfCode); + DUMP_FIELD(OptionalHeader.SizeOfInitializedData); + DUMP_FIELD(OptionalHeader.SizeOfUninitializedData); + DUMP_FIELD(OptionalHeader.AddressOfEntryPoint); + DUMP_FIELD(OptionalHeader.BaseOfCode); + DUMP_FIELD(OptionalHeader.BaseOfData); + DUMP_FIELD(OptionalHeader.ImageBase); + DUMP_FIELD(OptionalHeader.SectionAlignment); + DUMP_FIELD(OptionalHeader.FileAlignment); + DUMP_DEC_FIELD(OptionalHeader.MajorOperatingSystemVersion); + DUMP_DEC_FIELD(OptionalHeader.MinorOperatingSystemVersion); + DUMP_DEC_FIELD(OptionalHeader.Win32VersionValue); + DUMP_FIELD(OptionalHeader.SizeOfImage); + DUMP_FIELD(OptionalHeader.SizeOfHeaders); + DUMP_FIELD(OptionalHeader.CheckSum); + DUMP_FIELD(OptionalHeader.Subsystem); + DUMP_FIELD(OptionalHeader.DllCharacteristics); + DUMP_FIELD(OptionalHeader.SizeOfStackReserve); + DUMP_FIELD(OptionalHeader.SizeOfStackCommit); + DUMP_FIELD(OptionalHeader.SizeOfHeapReserve); + DUMP_FIELD(OptionalHeader.SizeOfHeapCommit); + DUMP_FIELD(OptionalHeader.LoaderFlags); + DUMP_DEC_FIELD(OptionalHeader.NumberOfRvaAndSizes); + } else { + DUMP_FIELD(OptionalHeader64.Magic); + DUMP_DEC_FIELD(OptionalHeader64.MajorLinkerVersion); + DUMP_DEC_FIELD(OptionalHeader64.MinorLinkerVersion); + DUMP_FIELD(OptionalHeader64.SizeOfCode); + DUMP_FIELD(OptionalHeader64.SizeOfInitializedData); + DUMP_FIELD(OptionalHeader64.SizeOfUninitializedData); + DUMP_FIELD(OptionalHeader64.AddressOfEntryPoint); + DUMP_FIELD(OptionalHeader64.BaseOfCode); + DUMP_FIELD(OptionalHeader64.ImageBase); + DUMP_FIELD(OptionalHeader64.SectionAlignment); + DUMP_FIELD(OptionalHeader64.FileAlignment); + DUMP_DEC_FIELD(OptionalHeader64.MajorOperatingSystemVersion); + DUMP_DEC_FIELD(OptionalHeader64.MinorOperatingSystemVersion); + DUMP_DEC_FIELD(OptionalHeader64.Win32VersionValue); + DUMP_FIELD(OptionalHeader64.SizeOfImage); + DUMP_FIELD(OptionalHeader64.SizeOfHeaders); + DUMP_FIELD(OptionalHeader64.CheckSum); + DUMP_FIELD(OptionalHeader64.Subsystem); + DUMP_FIELD(OptionalHeader64.DllCharacteristics); + DUMP_FIELD(OptionalHeader64.SizeOfStackReserve); + DUMP_FIELD(OptionalHeader64.SizeOfStackCommit); + DUMP_FIELD(OptionalHeader64.SizeOfHeapReserve); + DUMP_FIELD(OptionalHeader64.SizeOfHeapCommit); + DUMP_FIELD(OptionalHeader64.LoaderFlags); + DUMP_DEC_FIELD(OptionalHeader64.NumberOfRvaAndSizes); + } #undef DUMP_FIELD #undef DUMP_DEC_FIELD - std::cout << "Imports: " << endl; - IterImpVAString(p, printImports, NULL); - std::cout << "Relocations: " << endl; - IterRelocs(p, printRelocs, NULL); - std::cout << "Symbols (symbol table): " << endl; - IterSymbols(p, printSymbols, NULL); - std::cout << "Sections: " << endl; - IterSec(p, printSecs, NULL); - std::cout << "Exports: " << endl; - IterExpVA(p, printExps, NULL); + std::cout << "Imports: " << endl; + IterImpVAString(p, printImports, NULL); + std::cout << "Relocations: " << endl; + IterRelocs(p, printRelocs, NULL); + std::cout << "Symbols (symbol table): " << endl; + IterSymbols(p, printSymbols, NULL); + std::cout << "Sections: " << endl; + IterSec(p, printSecs, NULL); + std::cout << "Exports: " << endl; + IterExpVA(p, printExps, NULL); - // read the first 8 bytes from the entry point and print them - VA entryPoint; - if (GetEntryPoint(p, entryPoint)) { - std::cout << "First 8 bytes from entry point (0x"; + // read the first 8 bytes from the entry point and print them + VA entryPoint; + if (GetEntryPoint(p, entryPoint)) { + std::cout << "First 8 bytes from entry point (0x"; - std::cout << to_string(entryPoint, hex); - std::cout << "):" << endl; - for (std::size_t i = 0; i < 8; i++) { - ::uint8_t b; - ReadByteAtVA(p, i + entryPoint, b); - std::cout << " 0x" << to_string(b, hex); - } - - std::cout << endl; + std::cout << to_string(entryPoint, hex); + std::cout << "):" << endl; + for (std::size_t i = 0; i < 8; i++) { + ::uint8_t b; + ReadByteAtVA(p, i + entryPoint, b); + std::cout << " 0x" << to_string(b, hex); } - std::cout << "Resources: " << endl; - IterRsrc(p, printRsrc, NULL); - DestructParsedPE(p); - } else { - std::cout << "Error: " << GetPEErr() << " (" << GetPEErrString() << ")" - << endl; - std::cout << "Location: " << GetPEErrLoc() << endl; + std::cout << endl; } + + std::cout << "Resources: " << endl; + IterRsrc(p, printRsrc, NULL); + DestructParsedPE(p); + } else { + std::cout << "Error: " << GetPEErr() << " (" << GetPEErrString() << ")" + << endl; + std::cout << "Location: " << GetPEErrLoc() << endl; } + return 0; } diff --git a/examples/peaddrconv/CMakeLists.txt b/examples/peaddrconv/CMakeLists.txt new file mode 100644 index 0000000..3577f5c --- /dev/null +++ b/examples/peaddrconv/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.1) +project(peaddrconv) + +if (WIN32) + list(APPEND PEADDRCONV_CXXFLAGS /W4 /analyze) + + if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + list(APPEND PEADDRCONV_CXXFLAGS /Zi) + endif () + + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + list(APPEND PEADDRCONV_CXXFLAGS /WX) + endif () + +else () + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_EXTENSIONS OFF) + + list(APPEND PEADDRCONV_CXXFLAGS + -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization + -Wformat=2 -Winit-self -Wlong-long -Wmissing-declarations -Wmissing-include-dirs -Wcomment + -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion + -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wunused -Wuninitialized + -Wno-missing-declarations + ) + + if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + list(APPEND PEADDRCONV_CXXFLAGS -gdwarf-2 -g3) + endif () +endif () + +find_package(peparse REQUIRED) + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} ${PEPARSE_LIBRARIES}) +target_include_directories(${PROJECT_NAME} PRIVATE ${PEPARSE_INCLUDE_DIRS}) +target_compile_options(${PROJECT_NAME} PRIVATE ${PEADDRCONV_CXXFLAGS}) + +install(TARGETS ${PROJECT_NAME} DESTINATION "bin") diff --git a/examples/peaddrconv/README.md b/examples/peaddrconv/README.md new file mode 100644 index 0000000..479d5e0 --- /dev/null +++ b/examples/peaddrconv/README.md @@ -0,0 +1 @@ +Note that you have to install the library before you can build this example! diff --git a/examples/peaddrconv/main.cpp b/examples/peaddrconv/main.cpp new file mode 100644 index 0000000..daae916 --- /dev/null +++ b/examples/peaddrconv/main.cpp @@ -0,0 +1,374 @@ +#include +#include +#include + +#include +#include + +#include + +using ParsedPeRef = + std::unique_ptr; + +ParsedPeRef openExecutable(const std::string &path) noexcept { + // The factory function does not throw exceptions! + ParsedPeRef obj(peparse::ParsePEFromFile(path.data()), + peparse::DestructParsedPE); + if (!obj) { + return ParsedPeRef(nullptr, peparse::DestructParsedPE); + } + + return obj; +} + +enum class AddressType { + PhysicalOffset, + RelativeVirtualAddress, + VirtualAddress +}; + +bool convertAddress(ParsedPeRef &pe, + std::uintptr_t address, + AddressType source_type, + AddressType destination_type, + std::uintptr_t &result) noexcept { + if (source_type == destination_type) { + result = address; + return true; + } + + std::uint64_t image_base_address = 0U; + if (pe->peHeader.nt.FileHeader.Machine == peparse::IMAGE_FILE_MACHINE_AMD64) { + image_base_address = pe->peHeader.nt.OptionalHeader64.ImageBase; + } else { + image_base_address = pe->peHeader.nt.OptionalHeader.ImageBase; + } + + struct SectionAddressLimits final { + std::uintptr_t lowest_rva; + std::uintptr_t lowest_offset; + + std::uintptr_t highest_rva; + std::uintptr_t highest_offset; + }; + + auto L_getSectionAddressLimits = [](void *N, + peparse::VA secBase, + std::string &secName, + peparse::image_section_header s, + peparse::bounded_buffer *data) -> int { + static_cast(secBase); + static_cast(secName); + static_cast(data); + + SectionAddressLimits *section_address_limits = + static_cast(N); + + section_address_limits->lowest_rva = + std::min(section_address_limits->lowest_rva, + static_cast(s.VirtualAddress)); + + section_address_limits->lowest_offset = + std::min(section_address_limits->lowest_offset, + static_cast(s.PointerToRawData)); + + std::uintptr_t sectionSize; + if (s.SizeOfRawData != 0) { + sectionSize = s.SizeOfRawData; + } else { + sectionSize = s.Misc.VirtualSize; + } + + section_address_limits->highest_rva = + std::max(section_address_limits->highest_rva, + static_cast(s.VirtualAddress + sectionSize)); + + section_address_limits->highest_offset = + std::max(section_address_limits->highest_offset, + static_cast(s.PointerToRawData + sectionSize)); + + return 0; + }; + + SectionAddressLimits section_address_limits = { + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::min(), + std::numeric_limits::min()}; + + IterSec(pe.get(), L_getSectionAddressLimits, §ion_address_limits); + + switch (source_type) { + case AddressType::PhysicalOffset: { + if (address >= section_address_limits.highest_offset) { + return false; + } + + if (destination_type == AddressType::RelativeVirtualAddress) { + struct CallbackData final { + bool found; + std::uintptr_t address; + std::uintptr_t result; + }; + + auto L_inspectSection = [](void *N, + peparse::VA secBase, + std::string &secName, + peparse::image_section_header s, + peparse::bounded_buffer *data) -> int { + static_cast(secBase); + static_cast(secName); + static_cast(data); + + std::uintptr_t sectionBaseOffset = s.PointerToRawData; + + std::uintptr_t sectionEndOffset = sectionBaseOffset; + if (s.SizeOfRawData != 0) { + sectionEndOffset += s.SizeOfRawData; + } else { + sectionEndOffset += s.Misc.VirtualSize; + } + + auto callback_data = static_cast(N); + if (callback_data->address >= sectionBaseOffset && + callback_data->address < sectionEndOffset) { + callback_data->result = s.VirtualAddress + (callback_data->address - + s.PointerToRawData); + + callback_data->found = true; + return 1; + } + + return 0; + }; + + CallbackData callback_data = {false, address, 0U}; + IterSec(pe.get(), L_inspectSection, &callback_data); + + if (!callback_data.found) { + return false; + } + + result = callback_data.result; + return true; + + } else if (destination_type == AddressType::VirtualAddress) { + std::uintptr_t rva = 0U; + if (!convertAddress(pe, + address, + source_type, + AddressType::RelativeVirtualAddress, + rva)) { + return false; + } + + result = image_base_address + rva; + return true; + } + + return false; + } + + case AddressType::RelativeVirtualAddress: { + if (address < section_address_limits.lowest_rva) { + result = address; + return true; + } else if (address >= section_address_limits.highest_rva) { + return false; + } + + if (destination_type == AddressType::PhysicalOffset) { + struct CallbackData final { + bool found; + std::uintptr_t address; + std::uintptr_t result; + }; + + auto L_inspectSection = [](void *N, + peparse::VA secBase, + std::string &secName, + peparse::image_section_header s, + peparse::bounded_buffer *data) -> int { + static_cast(secBase); + static_cast(secName); + static_cast(data); + + std::uintptr_t sectionBaseAddress = s.VirtualAddress; + std::uintptr_t sectionEndAddress = + sectionBaseAddress + s.Misc.VirtualSize; + + auto callback_data = static_cast(N); + if (callback_data->address >= sectionBaseAddress && + callback_data->address < sectionEndAddress) { + callback_data->result = + s.PointerToRawData + + (callback_data->address - sectionBaseAddress); + + callback_data->found = true; + return 1; + } + + return 0; + }; + + CallbackData callback_data = {false, address, 0U}; + IterSec(pe.get(), L_inspectSection, &callback_data); + + if (!callback_data.found) { + return false; + } + + result = callback_data.result; + return true; + + } else if (destination_type == AddressType::VirtualAddress) { + result = image_base_address + address; + return true; + } + + return false; + } + + case AddressType::VirtualAddress: { + if (address < image_base_address) { + return false; + } + + std::uintptr_t rva = address - image_base_address; + return convertAddress(pe, + rva, + AddressType::RelativeVirtualAddress, + destination_type, + result); + } + + default: { return false; } + } +} + +int main(int argc, char *argv[]) { + if (argc != 3 || (argc == 2 && std::strcmp(argv[1], "--help") == 0)) { + std::cout << "PE address conversion utility from Trail of Bits\n"; + std::cout << "Usage:\n\tpeaddrconv /path/to/executable.exe address\n\n"; + std::cout << "The
parameter is always interpreted as hex!\n"; + + return 1; + } + + const char *executable_path = argv[1]; + const char *address_as_string = argv[2]; + + char *last_parsed_char = nullptr; + errno = 0; + + auto address = std::strtoull(address_as_string, &last_parsed_char, 16); + if (address == 0U && *last_parsed_char != 0) { + std::cout << "Invalid address specified\n"; + return 1; + + } else if (address == ULLONG_MAX && errno == ERANGE) { + std::cout << "The address you specified is too big\n"; + return 1; + } + + auto pe = openExecutable(executable_path); + if (!pe) { + std::cout << "Failed to open the executable\n\n"; + + std::cout << "Error: " << peparse::GetPEErr() << " (" + << peparse::GetPEErrString() << ")\n"; + + std::cout << "Location: " << peparse::GetPEErrLoc() << "\n"; + return 1; + } + + std::uint64_t image_base_address = 0U; + if (pe->peHeader.nt.FileHeader.Machine == peparse::IMAGE_FILE_MACHINE_AMD64) { + image_base_address = pe->peHeader.nt.OptionalHeader64.ImageBase; + } else { + image_base_address = pe->peHeader.nt.OptionalHeader.ImageBase; + } + + std::cout << "Image base address: 0x" << std::hex << image_base_address + << "\n"; + std::cout << "Converting address 0x" << std::hex << address << "...\n\n"; + + std::uintptr_t result = 0U; + + std::cout << "as Physical offset (off)\n"; + std::cout << " to rva:\t"; + if (convertAddress(pe, + address, + AddressType::PhysicalOffset, + AddressType::RelativeVirtualAddress, + result)) { + std::cout << "0x" << std::hex << result; + } else { + std::cout << "-"; + } + std::cout << "\n"; + + std::cout << " to va:\t"; + if (convertAddress(pe, + address, + AddressType::PhysicalOffset, + AddressType::VirtualAddress, + result)) { + std::cout << "0x" << std::hex << result; + } else { + std::cout << "-"; + } + std::cout << "\n\n"; + + std::cout << "as Relative virtual address (rva)\n"; + std::cout << " to off:\t"; + if (convertAddress(pe, + address, + AddressType::RelativeVirtualAddress, + AddressType::PhysicalOffset, + result)) { + std::cout << "0x" << std::hex << result; + } else { + std::cout << "-"; + } + std::cout << "\n"; + + std::cout << " to va:\t"; + if (convertAddress(pe, + address, + AddressType::RelativeVirtualAddress, + AddressType::VirtualAddress, + result)) { + std::cout << "0x" << std::hex << result; + } else { + std::cout << "-"; + } + std::cout << "\n\n"; + + std::cout << "as Virtual address (va)\n"; + std::cout << " to off:\t"; + if (convertAddress(pe, + address, + AddressType::VirtualAddress, + AddressType::PhysicalOffset, + result)) { + std::cout << "0x" << std::hex << result; + } else { + std::cout << "-"; + } + std::cout << "\n"; + + std::cout << " to rva:\t"; + if (convertAddress(pe, + address, + AddressType::VirtualAddress, + AddressType::RelativeVirtualAddress, + result)) { + std::cout << "0x" << std::hex << result; + } else { + std::cout << "-"; + } + std::cout << "\n"; + + return 0; +} diff --git a/packages/archlinux/.gitignore b/packages/archlinux/.gitignore new file mode 100644 index 0000000..e402c05 --- /dev/null +++ b/packages/archlinux/.gitignore @@ -0,0 +1,5 @@ +pkg +src +*.pkg.tar +*.pkg.tar.xz + diff --git a/packages/archlinux/PKGBUILD b/packages/archlinux/PKGBUILD new file mode 100644 index 0000000..68f836d --- /dev/null +++ b/packages/archlinux/PKGBUILD @@ -0,0 +1,18 @@ +pkgname=pe-parse +pkgver=1.0 +pkgrel=1 +arch=("x86_64" "x86") +pkgdesc="PE parsing library from Trail of Bits" +url="https://github.com/trailofbits/pe-parse" +license=('MIT') +makedepends=("cmake") +provides=("pe-parse") + +build() { + cmake ../../../ + make -j `nproc` +} + +package() { + make DESTDIR="$pkgdir/" install +} diff --git a/parser-library/CMakeLists.txt b/parser-library/CMakeLists.txt deleted file mode 100644 index 24ec07d..0000000 --- a/parser-library/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -cmake_minimum_required(VERSION 3.1) -project(pe-parser-library) - -add_library(${PROJECT_NAME} buffer.cpp parse.cpp) -target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_options(${PROJECT_NAME} PRIVATE ${GLOBAL_CXXFLAGS}) diff --git a/pe-parser-library/CMakeLists.txt b/pe-parser-library/CMakeLists.txt new file mode 100644 index 0000000..aca02d5 --- /dev/null +++ b/pe-parser-library/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.1) +project(pe-parser-library) + +# List all files explicitly; this will make IDEs happy (i.e. QtCreator, CLion, ...) +list(APPEND PEPARSERLIB_SOURCEFILES + include/parser-library/parse.h + include/parser-library/nt-headers.h + include/parser-library/to_string.h + + src/buffer.cpp + src/parse.cpp +) + +add_library(${PROJECT_NAME} STATIC ${PEPARSERLIB_SOURCEFILES}) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_compile_options(${PROJECT_NAME} PRIVATE ${GLOBAL_CXXFLAGS}) + +install(TARGETS ${PROJECT_NAME} DESTINATION "lib") +install(FILES "cmake/peparse-config.cmake" DESTINATION "lib/cmake/peparse") +install(DIRECTORY "include/parser-library" DESTINATION "include") diff --git a/pe-parser-library/cmake/peparse-config.cmake b/pe-parser-library/cmake/peparse-config.cmake new file mode 100644 index 0000000..0435b8e --- /dev/null +++ b/pe-parser-library/cmake/peparse-config.cmake @@ -0,0 +1,5 @@ +find_path(PEPARSE_INCLUDE_DIR "parser-library/parse.h") +find_library(PEPARSE_LIBRARIES NAMES "libpe-parser-library.a") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libproxy DEFAULT_MSG PEPARSE_INCLUDE_DIR PEPARSE_LIBRARIES) diff --git a/parser-library/nt-headers.h b/pe-parser-library/include/parser-library/nt-headers.h similarity index 99% rename from parser-library/nt-headers.h rename to pe-parser-library/include/parser-library/nt-headers.h index 5ce8d64..d3f744a 100644 --- a/parser-library/nt-headers.h +++ b/pe-parser-library/include/parser-library/nt-headers.h @@ -25,6 +25,7 @@ THE SOFTWARE. #pragma once #include +#include #define _offset(t, f) \ static_cast( \ diff --git a/parser-library/parse.h b/pe-parser-library/include/parser-library/parse.h similarity index 100% rename from parser-library/parse.h rename to pe-parser-library/include/parser-library/parse.h diff --git a/parser-library/to_string.h b/pe-parser-library/include/parser-library/to_string.h similarity index 100% rename from parser-library/to_string.h rename to pe-parser-library/include/parser-library/to_string.h diff --git a/parser-library/buffer.cpp b/pe-parser-library/src/buffer.cpp similarity index 97% rename from parser-library/buffer.cpp rename to pe-parser-library/src/buffer.cpp index 1c0f477..b9b0f8f 100644 --- a/parser-library/buffer.cpp +++ b/pe-parser-library/src/buffer.cpp @@ -22,12 +22,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "parse.h" - #include #include +// keep this header above "windows.h" because it contains many types +#include + #ifdef WIN32 + +#define WIN32_LEAN_AND_MEAN +#define VC_EXTRALEAN + #include #include #else diff --git a/parser-library/parse.cpp b/pe-parser-library/src/parse.cpp similarity index 99% rename from parser-library/parse.cpp rename to pe-parser-library/src/parse.cpp index 3ff1460..19c72eb 100644 --- a/parser-library/parse.cpp +++ b/pe-parser-library/src/parse.cpp @@ -22,15 +22,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "parse.h" -#include "nt-headers.h" -#include "to_string.h" - #include #include #include #include +#include +#include +#include + using namespace std; namespace peparse { diff --git a/python/pepy.cpp b/python/pepy.cpp index e2bfdb3..7ffcc40 100644 --- a/python/pepy.cpp +++ b/python/pepy.cpp @@ -25,8 +25,8 @@ * SUCH DAMAGE. */ -#include "parse.h" #include +#include #include using namespace peparse; @@ -37,9 +37,9 @@ using namespace peparse; * Add some definition for compatibility between python2 and python3 */ #if PY_MAJOR_VERSION >= 3 - #define PyInt_FromLong PyLong_FromLong - #define PyInt_AsLong PyLong_AsLong - #define PyString_FromString PyUnicode_FromString +#define PyInt_FromLong PyLong_FromLong +#define PyInt_AsLong PyLong_AsLong +#define PyString_FromString PyUnicode_FromString #endif /* @@ -47,12 +47,11 @@ using namespace peparse; * Needed for compatibility with python3 */ #ifndef PyVarObject_HEAD_INIT - #define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, +#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, #endif #ifndef Py_TYPE - #define Py_TYPE(_ob_) (((PyObject*)(_ob_))->ob_type) +#define Py_TYPE(_ob_) (((PyObject *) (_ob_))->ob_type) #endif /* These are used to across multiple objects. */ @@ -77,9 +76,13 @@ using namespace peparse; static PyObject *pepy_error; -struct pepy { PyObject_HEAD }; +struct pepy { + PyObject_HEAD +}; -struct pepy_parsed { PyObject_HEAD parsed_pe *pe; }; +struct pepy_parsed { + PyObject_HEAD parsed_pe *pe; +}; struct pepy_section { PyObject_HEAD PyObject *name; @@ -164,7 +167,7 @@ static PyGetSetDef pepy_import_getseters[] = { {NULL}}; static PyTypeObject pepy_import_type = { - PyVarObject_HEAD_INIT(NULL,0) /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) /* ob_size */ "pepy.import", /* tp_name */ sizeof(pepy_import), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -238,7 +241,7 @@ static PyGetSetDef pepy_export_getseters[] = { {NULL}}; static PyTypeObject pepy_export_type = { - PyVarObject_HEAD_INIT(NULL,0) /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) /* ob_size */ "pepy.export", /* tp_name */ sizeof(pepy_export), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -310,7 +313,7 @@ static PyGetSetDef pepy_relocation_getseters[] = { {NULL}}; static PyTypeObject pepy_relocation_type = { - PyVarObject_HEAD_INIT(NULL,0) /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) /* ob_size */ "pepy.relocation", /* tp_name */ sizeof(pepy_relocation), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -412,7 +415,7 @@ static PyGetSetDef pepy_section_getseters[] = { {NULL}}; static PyTypeObject pepy_section_type = { - PyVarObject_HEAD_INIT(NULL,0) /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) /* ob_size */ "pepy.section", /* tp_name */ sizeof(pepy_section), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -616,7 +619,7 @@ static PyGetSetDef pepy_resource_getseters[] = { {NULL}}; static PyTypeObject pepy_resource_type = { - PyVarObject_HEAD_INIT(NULL,0) /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) /* ob_size */ "pepy.resource", /* tp_name */ sizeof(pepy_resource), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -716,20 +719,21 @@ static PyObject *pepy_parsed_get_bytes(PyObject *self, PyObject *args) { * PybyteArray_FromStringAndSize */ - uint8_t *buf = new(std::nothrow) uint8_t[len]; + uint8_t *buf = new (std::nothrow) uint8_t[len]; if (!buf) { /* in case allocation failed */ - PyErr_SetString(pepy_error, "Unable to create initial buffer (allocation failure)."); + PyErr_SetString(pepy_error, + "Unable to create initial buffer (allocation failure)."); return NULL; } - for (idx = 0; idx < len; idx++) { + for (idx = 0; idx < len; idx++) { if (!ReadByteAtVA(((pepy_parsed *) self)->pe, start + idx, buf[idx])) break; } /* use idx as content length, if we get less than asked for */ - ret = PyByteArray_FromStringAndSize(reinterpret_cast(buf), idx); + ret = PyByteArray_FromStringAndSize(reinterpret_cast(buf), idx); if (!ret) { PyErr_SetString(pepy_error, "Unable to create new byte array."); return NULL; @@ -1203,7 +1207,7 @@ static PyMethodDef pepy_parsed_methods[] = { {NULL}}; static PyTypeObject pepy_parsed_type = { - PyVarObject_HEAD_INIT(NULL,0) /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) /* ob_size */ "pepy.parsed", /* tp_name */ sizeof(pepy_parsed), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -1279,8 +1283,7 @@ static PyObject *pepy_parse(PyObject *self, PyObject *args) { static PyMethodDef pepy_methods[] = { {"parse", pepy_parse, METH_VARARGS, "Parse PE from file."}, {NULL}}; -static -PyObject* pepi_module_init(void) { +static PyObject *pepi_module_init(void) { PyObject *m; if (PyType_Ready(&pepy_parsed_type) < 0 || @@ -1293,15 +1296,15 @@ PyObject* pepi_module_init(void) { #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "pepy", - "Python interface to pe-parse.", - -1, - pepy_methods, - NULL, - NULL, - NULL, - NULL, + PyModuleDef_HEAD_INIT, + "pepy", + "Python interface to pe-parse.", + -1, + pepy_methods, + NULL, + NULL, + NULL, + NULL, }; #endif @@ -1401,13 +1404,11 @@ PyObject* pepi_module_init(void) { } #if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC PyInit_pepy(void) -{ +PyMODINIT_FUNC PyInit_pepy(void) { return pepi_module_init(); } #else -PyMODINIT_FUNC initpepy(void) -{ +PyMODINIT_FUNC initpepy(void) { pepi_module_init(); } #endif diff --git a/python/setup.py b/python/setup.py index 63c93fb..a30bac1 100644 --- a/python/setup.py +++ b/python/setup.py @@ -28,15 +28,15 @@ from distutils.core import setup, Extension INCLUDE_DIRS = ['/usr/local/include', '/opt/local/include', '/usr/include', - '../parser-library'] + '../pe-parser-library/include'] LIBRARY_DIRS = ['/usr/lib', '/usr/local/lib'] extension_mod = Extension('pepy', sources = ['pepy.cpp', - '../parser-library/parse.cpp', - '../parser-library/buffer.cpp'], - extra_compile_args = ["-g", "-O0"], # Debug only + '../pe-parser-library/src/parse.cpp', + '../pe-parser-library/src/buffer.cpp'], + extra_compile_args = ["-std=c++11", "-g", "-O0"], # Debug only include_dirs = INCLUDE_DIRS, library_dirs = LIBRARY_DIRS) diff --git a/travis.sh b/travis.sh new file mode 100755 index 0000000..df9ddc1 --- /dev/null +++ b/travis.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash + +main() { + if [ $# -ne 2 ] ; then + printf "Usage:\n\ttravis.sh \n" + return 1 + fi + + local platform_name="$1" + local operation_type="$2" + + if [[ "${platform_name}" != "osx" && "${platform_name}" != "linux" ]] ; then + printf "Invalid platform: ${platform_name}\n" + return 1 + fi + + if [[ "${operation_type}" == "initialize" ]] ; then + "${platform_name}_initialize" + return $? + + elif [[ "$operation_type" == "build" ]] ; then + "${platform_name}_build" + return $? + + else + printf "Invalid operation\n" + return 1 + fi +} + +get_processor_count() { + which nproc > /dev/null + if [ $? -eq 0 ] ; then + nproc + return 0 + fi + + which sysctl > /dev/null + if [ $? -eq 0 ] ; then + sysctl -n hw.ncpu + return 0 + fi + + return 1 +} + +linux_initialize() { + printf "Initializing platform: linux\n" + local log_file=`mktemp` + + printf " > Updating the package database..\n" + sudo apt-get -qq update > "${log_file}" 2>&1 + if [ $? -ne 0 ] ; then + printf " x The package database could not be updated\n\n\n" + cat "${log_file}" + return 1 + fi + + printf " > Installing the required packages...\n" + sudo apt-get install -qqy cmake python2.7 python-dev build-essential realpath > "${log_file}" 2>&1 + if [ $? -ne 0 ] ; then + printf " x Could not install the required dependencies\n\n\n" + cat "${log_file}" + return 1 + fi + + printf " > The system has been successfully initialized\n" + return 0 +} + +osx_initialize() { + printf "Initializing platform: macOS\n" + local log_file=`mktemp` + + printf " > Updating the package database..\n" + brew update > "${log_file}" 2>&1 + if [ $? -ne 0 ] ; then + printf " x The package database could not be updated\n\n\n" + cat "${log_file}" + return 1 + fi + + printf " > Installing CMake...\n" + brew install cmake > "${log_file}" 2>&1 + if [ $? -ne 0 ] ; then + printf " x Failed to install CMake\n\n\n" + cat "${log_file}" + fi + + printf " > The system has been successfully initialized\n" + return 0 +} + +common_build() { + printf "Gathering system information...\n" + + which cmake > /dev/null + printf " > CMake version: " + if [ $? -eq 0 ] ; then + cmake --version | head -n 1 + else + printf "not found\n" + fi + + which gcc > /dev/null + printf " > GCC version: " + if [ $? -eq 0 ] ; then + gcc --version | head -n 1 + else + printf "not found\n" + fi + + which clang > /dev/null + printf " > Clang version: " + if [ $? -eq 0 ] ; then + clang --version | head -n 1 + else + printf "not found\n" + fi + + printf "\n" + + printf "Library\n" + if [ ! -d "build" ] ; then + printf " > Creating the build directory...\n" + mkdir "build" + if [ $? -ne 0 ] ; then + printf " x Failed to create the build directory\n" + return 1 + fi + fi + + local log_file=`mktemp` + local processor_count=`get_processor_count` + + printf " > Configuring...\n" + ( cd "build" && cmake .. ) > "$log_file" 2>&1 + if [ $? -ne 0 ] ; then + printf " x Configure failed; CMake returned an error.\n\n\n" + cat "$log_file" + return 1 + fi + + printf " > Building...\n" + ( cd "build" && make -j "${processor_count}" ) > "$log_file" 2>&1 + if [ $? -ne 0 ] ; then + printf " x The build has failed.\n\n\n" + cat "$log_file" + return 1 + fi + + printf " > Installing...\n" + sudo touch /usr/lib/test_file > /dev/null 2>&1 + if [ $? -ne 0 ] ; then + printf " x Access denied to /usr/lib; examples will not be built\n" + + else + ( cd "build" && sudo make install ) > "$log_file" 2>&1 + if [ $? -ne 0 ] ; then + printf " x Failed to install the library.\n\n\n" + cat "$log_file" + return 1 + fi + + printf "\n" + + printf "Examples\n" + if [ ! -d "examples_build" ] ; then + printf " > Creating the build directory...\n" + mkdir "examples_build" + if [ $? -ne 0 ] ; then + printf " x Failed to create the build directory\n\n\n" + cat "$log_file" + return 1 + fi + fi + + printf " > Configuring...\n" + ( cd "examples_build" && cmake "../examples/peaddrconv" ) > "$log_file" 2>&1 + if [ $? -ne 0 ] ; then + printf " x Configure failed; CMake returned an error.\n\n\n" + cat "$log_file" + return 1 + fi + + printf " > Building...\n" + ( cd "examples_build" && make -j "${processor_count}" ) > "$log_file" 2>&1 + if [ $? -ne 0 ] ; then + printf " x The build has failed.\n\n\n" + cat "$log_file" + return 1 + fi + fi + + printf "\n" + + printf "pepy\n" + + printf " > Building...\n" + ( cd python && python2 ./setup.py build ) > "$log_file" 2>&1 + if [ $? -ne 0 ] ; then + printf " x Build failed.\n\n\n" + cat "$log_file" + return 1 + fi + + return 0 +} + +linux_build() { + printf "Building platform: linux\n" + + source /etc/*-release + printf "Distribution: ${DISTRIB_DESCRIPTION}\n\n" + + common_build + if [ $? -ne 0 ] ; then + return 1 + fi + + return 0 +} + +osx_build() { + printf "Building platform: macOS\n\n" + + printf "macOS version: " + sw_vers -productVersion + + common_build + if [ $? -ne 0 ] ; then + return 1 + fi + + return 0 +} + +main $@ +exit $?