diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e1b2718..824b6e94 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ project(unit_tests) include(GoogleTest) -file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/general/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/engines/*.cpp") +file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/general/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/engines/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp") add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES} main.cpp) set_target_properties( diff --git a/tests/general/mem_fd_helper.hpp b/tests/general/mem_fd_helper.hpp new file mode 100644 index 00000000..9bd27f1a --- /dev/null +++ b/tests/general/mem_fd_helper.hpp @@ -0,0 +1,192 @@ +#pragma once +// Cross-platform helper for creating binary test "files" without writing to disk where possible. +// +// Strategy: +// - Linux (non-Android, or Android API >= 30): memfd_create → /proc/self/fd/ (no disk I/O) +// - All other platforms: anonymous temp file via std::tmpfile(), accessed via /proc/self/fd/ +// on Linux, or a named temp file (cleaned up on destruction) elsewhere. +// +// Usage: +// auto f = MemFdFile::create(myVector); +// ASSERT_TRUE(f.valid()); +// scanner.scan_for_pattern_in_file(f.path(), ...); + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +# include +# include +# if defined(__ANDROID__) +# if __ANDROID_API__ >= 30 +# include +# define OMATH_TEST_USE_MEMFD 1 +# endif +// Android < 30: fall through to tmpfile() path below +# else +// Desktop Linux: memfd_create available since glibc 2.27 / kernel 3.17 +# include +# define OMATH_TEST_USE_MEMFD 1 +# endif +#endif + +class MemFdFile +{ +public: + MemFdFile() = default; + + ~MemFdFile() + { +#if defined(OMATH_TEST_USE_MEMFD) + if (m_fd >= 0) + ::close(m_fd); +#else + if (!m_temp_path.empty()) + std::filesystem::remove(m_temp_path); +#endif + } + + MemFdFile(const MemFdFile&) = delete; + MemFdFile& operator=(const MemFdFile&) = delete; + + MemFdFile(MemFdFile&& o) noexcept + : m_path(std::move(o.m_path)) +#if defined(OMATH_TEST_USE_MEMFD) + , m_fd(o.m_fd) +#else + , m_temp_path(std::move(o.m_temp_path)) +#endif + { +#if defined(OMATH_TEST_USE_MEMFD) + o.m_fd = -1; +#else + o.m_temp_path.clear(); +#endif + } + + [[nodiscard]] bool valid() const { return !m_path.empty(); } + + [[nodiscard]] const std::filesystem::path& path() const { return m_path; } + + static MemFdFile create(const std::vector& data) + { + return create(data.data(), data.size()); + } + + static MemFdFile create(const std::uint8_t* data, std::size_t size) + { + MemFdFile f; + +#if defined(OMATH_TEST_USE_MEMFD) + f.m_fd = static_cast(::memfd_create("test_bin", 0)); + if (f.m_fd < 0) + return f; + + if (!write_all(f.m_fd, data, size)) + { + ::close(f.m_fd); + f.m_fd = -1; + return f; + } + f.m_path = "/proc/self/fd/" + std::to_string(f.m_fd); + +#else + // Portable fallback: write to a uniquely-named temp file and delete on destruction + const auto tmp_dir = std::filesystem::temp_directory_path(); + std::mt19937_64 rng(std::random_device{}()); + const auto unique_name = "omath_test_" + std::to_string(rng()) + ".bin"; + f.m_temp_path = (tmp_dir / unique_name).string(); + f.m_path = f.m_temp_path; + + std::ofstream out(f.m_temp_path, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + { + f.m_temp_path.clear(); + f.m_path.clear(); + return f; + } + out.write(reinterpret_cast(data), static_cast(size)); + if (!out) + { + out.close(); + std::filesystem::remove(f.m_temp_path); + f.m_temp_path.clear(); + f.m_path.clear(); + } +#endif + return f; + } + +private: + std::filesystem::path m_path; + +#if defined(OMATH_TEST_USE_MEMFD) + int m_fd = -1; + + static bool write_all(int fd, const std::uint8_t* data, std::size_t size) + { + std::size_t written = 0; + while (written < size) + { + const auto n = ::write(fd, data + written, size - written); + if (n <= 0) + return false; + written += static_cast(n); + } + return true; + } +#else + std::string m_temp_path; +#endif +}; + +// --------------------------------------------------------------------------- +// Build a minimal PE binary in-memory with a single .text section. +// Layout (all offsets compile-time): +// 0x00: DOS header (64 B) 0x40: pad 0x80: NT sig 0x84: FileHeader (20 B) +// 0x98: OptionalHeader (0xF0 B) 0x188: SectionHeader (44 B) 0x1B4: section data +// --------------------------------------------------------------------------- +inline std::vector build_minimal_pe(const std::vector& section_bytes) +{ + constexpr std::uint32_t e_lfanew = 0x80u; + constexpr std::uint16_t size_opt = 0xF0u; + constexpr std::size_t nt_off = e_lfanew; + constexpr std::size_t fh_off = nt_off + 4; + constexpr std::size_t oh_off = fh_off + 20; + constexpr std::size_t sh_off = oh_off + size_opt; + constexpr std::size_t data_off = sh_off + 44; + + std::vector buf(data_off + section_bytes.size(), 0u); + + buf[0] = 'M'; buf[1] = 'Z'; + std::memcpy(buf.data() + 0x3Cu, &e_lfanew, 4); + + buf[nt_off] = 'P'; buf[nt_off + 1] = 'E'; + + const std::uint16_t machine = 0x8664u, num_sections = 1u; + std::memcpy(buf.data() + fh_off, &machine, 2); + std::memcpy(buf.data() + fh_off + 2, &num_sections, 2); + std::memcpy(buf.data() + fh_off + 16, &size_opt, 2); + + const std::uint16_t magic = 0x20Bu; + std::memcpy(buf.data() + oh_off, &magic, 2); + + const char name[8] = {'.','t','e','x','t',0,0,0}; + std::memcpy(buf.data() + sh_off, name, 8); + + const auto vsize = static_cast(section_bytes.size()); + const std::uint32_t vaddr = 0x1000u; + const auto ptr_raw = static_cast(data_off); + std::memcpy(buf.data() + sh_off + 8, &vsize, 4); + std::memcpy(buf.data() + sh_off + 12, &vaddr, 4); + std::memcpy(buf.data() + sh_off + 16, &vsize, 4); + std::memcpy(buf.data() + sh_off + 20, &ptr_raw, 4); + + std::memcpy(buf.data() + data_off, section_bytes.data(), section_bytes.size()); + return buf; +} diff --git a/tests/general/unit_test_macho_scanner.cpp b/tests/general/unit_test_macho_scanner.cpp index 1e15bdcd..d8d6f294 100644 --- a/tests/general/unit_test_macho_scanner.cpp +++ b/tests/general/unit_test_macho_scanner.cpp @@ -6,8 +6,8 @@ #include #include #include -#include #include +#include "mem_fd_helper.hpp" using namespace omath; @@ -16,11 +16,12 @@ namespace // Mach-O magic numbers constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF; constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE; - constexpr std::uint32_t lc_segment = 0x1; + constexpr std::uint32_t lc_segment = 0x1; constexpr std::uint32_t lc_segment_64 = 0x19; constexpr std::string_view segment_name = "__TEXT"; constexpr std::string_view section_name = "__text"; + #pragma pack(push, 1) struct MachHeader64 { @@ -107,249 +108,174 @@ namespace }; #pragma pack(pop) - // Helper function to create a minimal 64-bit Mach-O file with a __text section - bool write_minimal_macho64_file(const std::string& path, const std::vector& section_bytes) + // Build a minimal 64-bit Mach-O binary in-memory with a __text section + std::vector build_minimal_macho64(const std::vector& section_bytes) { - std::ofstream f(path, std::ios::binary); - if (!f.is_open()) - return false; - - // Calculate sizes - constexpr std::size_t header_size = sizeof(MachHeader64); - constexpr std::size_t segment_size = sizeof(SegmentCommand64); - constexpr std::size_t section_size = sizeof(Section64); - constexpr std::size_t load_cmd_size = segment_size + section_size; - // Section data will start after headers - const std::size_t section_offset = header_size + load_cmd_size; - - // Create Mach-O header - MachHeader64 header{}; - header.magic = mh_magic_64; - header.cputype = 0x01000007; // CPU_TYPE_X86_64 - header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_64_ALL - header.filetype = 0x2; // MH_EXECUTE - header.ncmds = 1; - header.sizeofcmds = static_cast(load_cmd_size); - header.flags = 0; - header.reserved = 0; - - f.write(reinterpret_cast(&header), sizeof(header)); - - // Create segment command - SegmentCommand64 segment{}; - segment.cmd = lc_segment_64; - segment.cmdsize = static_cast(load_cmd_size); - std::ranges::copy(segment_name, segment.segname); - segment.vmaddr = 0x100000000; - segment.vmsize = section_bytes.size(); - segment.fileoff = section_offset; - segment.filesize = section_bytes.size(); - segment.maxprot = 7; // VM_PROT_ALL - segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE - segment.nsects = 1; - segment.flags = 0; - - f.write(reinterpret_cast(&segment), sizeof(segment)); - - // Create section - Section64 section{}; - std::ranges::copy(section_name, section.sectname); - std::ranges::copy(segment_name, segment.segname); - section.addr = 0x100000000; - section.size = section_bytes.size(); - section.offset = static_cast(section_offset); - section.align = 0; - section.reloff = 0; - section.nreloc = 0; - section.flags = 0; - section.reserved1 = 0; - section.reserved2 = 0; - section.reserved3 = 0; - - f.write(reinterpret_cast(§ion), sizeof(section)); - - // Write section data - f.write(reinterpret_cast(section_bytes.data()), static_cast(section_bytes.size())); - - f.close(); - return true; + constexpr std::size_t load_cmd_size = sizeof(SegmentCommand64) + sizeof(Section64); + const std::size_t section_offset = sizeof(MachHeader64) + load_cmd_size; + + std::vector buf(section_offset + section_bytes.size(), 0u); + + auto* header = reinterpret_cast(buf.data()); + header->magic = mh_magic_64; + header->cputype = 0x01000007; // CPU_TYPE_X86_64 + header->cpusubtype = 0x3; + header->filetype = 0x2; // MH_EXECUTE + header->ncmds = 1; + header->sizeofcmds = static_cast(load_cmd_size); + + auto* segment = reinterpret_cast(buf.data() + sizeof(MachHeader64)); + segment->cmd = lc_segment_64; + segment->cmdsize = static_cast(load_cmd_size); + std::ranges::copy(segment_name, segment->segname); + segment->vmaddr = 0x100000000; + segment->vmsize = section_bytes.size(); + segment->fileoff = section_offset; + segment->filesize = section_bytes.size(); + segment->maxprot = 7; + segment->initprot = 5; + segment->nsects = 1; + + auto* section = reinterpret_cast(buf.data() + sizeof(MachHeader64) + sizeof(SegmentCommand64)); + std::ranges::copy(section_name, section->sectname); + std::ranges::copy(segment_name, section->segname); + section->addr = 0x100000000; + section->size = section_bytes.size(); + section->offset = static_cast(section_offset); + + std::memcpy(buf.data() + section_offset, section_bytes.data(), section_bytes.size()); + return buf; } - // Helper function to create a minimal 32-bit Mach-O file with a __text section - bool write_minimal_macho32_file(const std::string& path, const std::vector& section_bytes) + // Build a minimal 32-bit Mach-O binary in-memory with a __text section + std::vector build_minimal_macho32(const std::vector& section_bytes) { - std::ofstream f(path, std::ios::binary); - if (!f.is_open()) - return false; - - // Calculate sizes - constexpr std::size_t header_size = sizeof(MachHeader32); - constexpr std::size_t segment_size = sizeof(SegmentCommand32); - constexpr std::size_t section_size = sizeof(Section32); - constexpr std::size_t load_cmd_size = segment_size + section_size; - - // Section data will start after headers - constexpr std::size_t section_offset = header_size + load_cmd_size; - - // Create Mach-O header - MachHeader32 header{}; - header.magic = mh_magic_32; - header.cputype = 0x7; // CPU_TYPE_X86 - header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_ALL - header.filetype = 0x2; // MH_EXECUTE - header.ncmds = 1; - header.sizeofcmds = static_cast(load_cmd_size); - header.flags = 0; - - f.write(reinterpret_cast(&header), sizeof(header)); - - // Create segment command - SegmentCommand32 segment{}; - segment.cmd = lc_segment; - segment.cmdsize = static_cast(load_cmd_size); - std::ranges::copy(segment_name, segment.segname); - segment.vmaddr = 0x1000; - segment.vmsize = static_cast(section_bytes.size()); - segment.fileoff = static_cast(section_offset); - segment.filesize = static_cast(section_bytes.size()); - segment.maxprot = 7; // VM_PROT_ALL - segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE - segment.nsects = 1; - segment.flags = 0; - - f.write(reinterpret_cast(&segment), sizeof(segment)); - - // Create section - Section32 section{}; - std::ranges::copy(section_name, section.sectname); - std::ranges::copy(segment_name, segment.segname); - section.addr = 0x1000; - section.size = static_cast(section_bytes.size()); - section.offset = static_cast(section_offset); - section.align = 0; - section.reloff = 0; - section.nreloc = 0; - section.flags = 0; - section.reserved1 = 0; - section.reserved2 = 0; - - f.write(reinterpret_cast(§ion), sizeof(section)); - - // Write section data - f.write(reinterpret_cast(section_bytes.data()), static_cast(section_bytes.size())); - - f.close(); - return true; + constexpr std::size_t load_cmd_size = sizeof(SegmentCommand32) + sizeof(Section32); + constexpr std::size_t section_offset = sizeof(MachHeader32) + load_cmd_size; + + std::vector buf(section_offset + section_bytes.size(), 0u); + + auto* header = reinterpret_cast(buf.data()); + header->magic = mh_magic_32; + header->cputype = 0x7; + header->cpusubtype = 0x3; + header->filetype = 0x2; + header->ncmds = 1; + header->sizeofcmds = static_cast(load_cmd_size); + + auto* segment = reinterpret_cast(buf.data() + sizeof(MachHeader32)); + segment->cmd = lc_segment; + segment->cmdsize = static_cast(load_cmd_size); + std::ranges::copy(segment_name, segment->segname); + segment->vmaddr = 0x1000; + segment->vmsize = static_cast(section_bytes.size()); + segment->fileoff = static_cast(section_offset); + segment->filesize = static_cast(section_bytes.size()); + segment->maxprot = 7; + segment->initprot = 5; + segment->nsects = 1; + + auto* section = reinterpret_cast(buf.data() + sizeof(MachHeader32) + sizeof(SegmentCommand32)); + std::ranges::copy(section_name, section->sectname); + std::ranges::copy(segment_name, section->segname); + section->addr = 0x1000; + section->size = static_cast(section_bytes.size()); + section->offset = static_cast(section_offset); + + std::memcpy(buf.data() + section_offset, section_bytes.data(), section_bytes.size()); + return buf; } } // namespace -// Test scanning for a pattern that exists in a 64-bit Mach-O file TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern64) { - constexpr std::string_view path = "./test_minimal_macho64.bin"; - const std::vector bytes = {0x55, 0x48, 0x89, 0xE5, 0x90, 0x90}; // push rbp; mov rbp, rsp; nop; nop - ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes)); + const std::vector bytes = {0x55, 0x48, 0x89, 0xE5, 0x90, 0x90}; + const auto f = MemFdFile::create(build_minimal_macho64(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "55 48 89 E5", "__text"); EXPECT_TRUE(res.has_value()); if (res.has_value()) - { EXPECT_EQ(res->target_offset, 0); - } } -// Test scanning for a pattern that exists in a 32-bit Mach-O file TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern32) { - constexpr std::string_view path = "./test_minimal_macho32.bin"; - const std::vector bytes = {0x55, 0x89, 0xE5, 0x90, 0x90}; // push ebp; mov ebp, esp; nop; nop - ASSERT_TRUE(write_minimal_macho32_file(path.data(), bytes)); + const std::vector bytes = {0x55, 0x89, 0xE5, 0x90, 0x90}; + const auto f = MemFdFile::create(build_minimal_macho32(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 89 E5", "__text"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "55 89 E5", "__text"); EXPECT_TRUE(res.has_value()); if (res.has_value()) - { EXPECT_EQ(res->target_offset, 0); - } } -// Test scanning for a pattern that does not exist TEST(unit_test_macho_pattern_scan_file, ScanMissingPattern) { - constexpr std::string_view path = "./test_minimal_macho_missing.bin"; const std::vector bytes = {0x00, 0x01, 0x02, 0x03}; - ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes)); + const auto f = MemFdFile::create(build_minimal_macho64(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "FF EE DD", "__text"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "FF EE DD", "__text"); EXPECT_FALSE(res.has_value()); } -// Test scanning for a pattern at a non-zero offset TEST(unit_test_macho_pattern_scan_file, ScanPatternAtOffset) { - constexpr std::string_view path = "./test_minimal_macho_offset.bin"; - const std::vector bytes = {0x90, 0x90, 0x90, 0x55, 0x48, 0x89, 0xE5}; // nops then pattern - ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes)); + const std::vector bytes = {0x90, 0x90, 0x90, 0x55, 0x48, 0x89, 0xE5}; + const auto f = MemFdFile::create(build_minimal_macho64(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "55 48 89 E5", "__text"); EXPECT_TRUE(res.has_value()); if (res.has_value()) - { EXPECT_EQ(res->target_offset, 3); - } } -// Test scanning with wildcards TEST(unit_test_macho_pattern_scan_file, ScanWithWildcard) { - constexpr std::string_view path = "./test_minimal_macho_wildcard.bin"; const std::vector bytes = {0x55, 0x48, 0x89, 0xE5, 0x90}; - ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes)); + const auto f = MemFdFile::create(build_minimal_macho64(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 ? 89 E5", "__text"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "55 ? 89 E5", "__text"); EXPECT_TRUE(res.has_value()); } -// Test scanning a non-existent file TEST(unit_test_macho_pattern_scan_file, ScanNonExistentFile) { const auto res = MachOPatternScanner::scan_for_pattern_in_file("/non/existent/file.bin", "55 48", "__text"); EXPECT_FALSE(res.has_value()); } -// Test scanning an invalid (non-Mach-O) file TEST(unit_test_macho_pattern_scan_file, ScanInvalidFile) { - constexpr std::string_view path = "./test_invalid_macho.bin"; - std::ofstream f(path.data(), std::ios::binary); const std::vector garbage = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; - f.write(reinterpret_cast(garbage.data()), static_cast(garbage.size())); - f.close(); + const auto f = MemFdFile::create(garbage); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__text"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "55 48", "__text"); EXPECT_FALSE(res.has_value()); } -// Test scanning for a non-existent section TEST(unit_test_macho_pattern_scan_file, ScanNonExistentSection) { - constexpr std::string_view path = "./test_minimal_macho_nosect.bin"; const std::vector bytes = {0x55, 0x48, 0x89, 0xE5}; - ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes)); + const auto f = MemFdFile::create(build_minimal_macho64(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__nonexistent"); + const auto res = MachOPatternScanner::scan_for_pattern_in_file(f.path(), "55 48", "__nonexistent"); EXPECT_FALSE(res.has_value()); } -// Test scanning with null module base address TEST(unit_test_macho_pattern_scan_loaded, ScanNullModule) { const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(nullptr, "55 48", "__text"); EXPECT_FALSE(res.has_value()); } -// Test scanning in loaded module with invalid magic TEST(unit_test_macho_pattern_scan_loaded, ScanInvalidMagic) { std::vector invalid_data(256, 0x00); diff --git a/tests/general/unit_test_pe_pattern_scan_file.cpp b/tests/general/unit_test_pe_pattern_scan_file.cpp index 5e2abb84..18a85cef 100644 --- a/tests/general/unit_test_pe_pattern_scan_file.cpp +++ b/tests/general/unit_test_pe_pattern_scan_file.cpp @@ -1,114 +1,28 @@ // Unit test for PePatternScanner::scan_for_pattern_in_file using a synthetic PE-like file #include #include -#include -#include #include -#include +#include +#include "mem_fd_helper.hpp" using namespace omath; -// Helper: write a trivial PE-like file with DOS header and a single section named .text -static bool write_minimal_pe_file(const std::string& path, const std::vector& section_bytes) -{ - std::ofstream f(path, std::ios::binary); - if (!f.is_open()) return false; - - // Write DOS header (e_magic = 0x5A4D, e_lfanew at offset 0x3C) - std::vector dos(64, 0); - dos[0] = 'M'; dos[1] = 'Z'; - // e_lfanew -> place NT headers right after DOS (offset 0x80) - std::uint32_t e_lfanew = 0x80; - std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew)); - f.write(reinterpret_cast(dos.data()), dos.size()); - - // Pad up to e_lfanew - if (f.tellp() < static_cast(e_lfanew)) - { - std::vector pad(e_lfanew - static_cast(f.tellp()), 0); - f.write(pad.data(), pad.size()); - } - - // NT headers signature 'PE\0\0' - f.put('P'); f.put('E'); f.put('\0'); f.put('\0'); - - // FileHeader: machine, num_sections - std::uint16_t machine = 0x8664; // x64 - std::uint16_t num_sections = 1; - std::uint32_t dummy32 = 0; - std::uint32_t dummy32b = 0; - std::uint16_t size_optional = 0xF0; // reasonable - std::uint16_t characteristics = 0; - f.write(reinterpret_cast(&machine), sizeof(machine)); - f.write(reinterpret_cast(&num_sections), sizeof(num_sections)); - f.write(reinterpret_cast(&dummy32), sizeof(dummy32)); - f.write(reinterpret_cast(&dummy32b), sizeof(dummy32b)); - std::uint32_t num_symbols = 0; - f.write(reinterpret_cast(&num_symbols), sizeof(num_symbols)); - f.write(reinterpret_cast(&size_optional), sizeof(size_optional)); - f.write(reinterpret_cast(&characteristics), sizeof(characteristics)); - - // OptionalHeader (x64) minimal: magic 0x20b, image_base, size_of_code, size_of_headers - std::uint16_t magic = 0x20b; - f.write(reinterpret_cast(&magic), sizeof(magic)); - // filler for rest of optional header up to size_optional - std::vector opt(size_optional - sizeof(magic), 0); - // set size_code near end - // we'll set image_base and size_code fields in reasonable positions for extractor - // For simplicity, leave zeros; extractor primarily uses optional_header.image_base and size_code later, - // but we will craft a SectionHeader that points to raw data we append below. - f.write(reinterpret_cast(opt.data()), opt.size()); - - // Section header (name 8 bytes, then remaining 36 bytes) - char name[8] = {'.','t','e','x','t',0,0,0}; - f.write(name, 8); - - // Write placeholder bytes for the rest of the section header and remember its start - constexpr std::uint32_t section_header_rest = 36u; - const std::streampos header_rest_pos = f.tellp(); - std::vector placeholder(section_header_rest, 0); - f.write(placeholder.data(), placeholder.size()); - - // Now write section raw data and remember its file offset - const std::streampos data_pos = f.tellp(); - f.write(reinterpret_cast(section_bytes.data()), static_cast(section_bytes.size())); - - // Patch section header fields: virtual_size, virtual_address, size_raw_data, ptr_raw_data - const std::uint32_t virtual_size = static_cast(section_bytes.size()); - constexpr std::uint32_t virtual_address = 0x1000u; - const std::uint32_t size_raw_data = static_cast(section_bytes.size()); - const std::uint32_t ptr_raw_data = static_cast(data_pos); - - // Seek back to the header_rest_pos and write fields in order - f.seekp(header_rest_pos, std::ios::beg); - f.write(reinterpret_cast(&virtual_size), sizeof(virtual_size)); - f.write(reinterpret_cast(&virtual_address), sizeof(virtual_address)); - f.write(reinterpret_cast(&size_raw_data), sizeof(size_raw_data)); - f.write(reinterpret_cast(&ptr_raw_data), sizeof(ptr_raw_data)); - - // Seek back to end for consistency - f.seekp(0, std::ios::end); - - f.close(); - return true; -} - TEST(unit_test_pe_pattern_scan_file, ScanFindsPattern) { - constexpr std::string_view path = "./test_minimal_pe.bin"; - const std::vector bytes = {0x55, 0x8B, 0xEC, 0x90, 0x90}; // pattern at offset 0 - ASSERT_TRUE(write_minimal_pe_file(path.data(), bytes)); + const std::vector bytes = {0x55, 0x8B, 0xEC, 0x90, 0x90}; + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "55 8B EC", ".text"); EXPECT_TRUE(res.has_value()); } TEST(unit_test_pe_pattern_scan_file, ScanMissingPattern) { - constexpr std::string_view path = "./test_minimal_pe_2.bin"; const std::vector bytes = {0x00, 0x01, 0x02, 0x03}; - ASSERT_TRUE(write_minimal_pe_file(path.data(), bytes)); + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "FF EE DD", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "FF EE DD", ".text"); EXPECT_FALSE(res.has_value()); } diff --git a/tests/general/unit_test_pe_pattern_scan_more.cpp b/tests/general/unit_test_pe_pattern_scan_more.cpp index 20265a70..8db91e31 100644 --- a/tests/general/unit_test_pe_pattern_scan_more.cpp +++ b/tests/general/unit_test_pe_pattern_scan_more.cpp @@ -1,120 +1,89 @@ // Additional tests for PePatternScanner to exercise edge cases and loaded-module scanning #include #include -#include #include #include #include +#include "mem_fd_helper.hpp" using namespace omath; -static bool write_bytes(const std::string& path, const std::vector& data) -{ - std::ofstream f(path, std::ios::binary); - if (!f.is_open()) - return false; - f.write(reinterpret_cast(data.data()), data.size()); - return true; -} - TEST(unit_test_pe_pattern_scan_more, InvalidDosHeader) { - constexpr std::string_view path = "./test_bad_dos.bin"; std::vector data(128, 0); - // write wrong magic data[0] = 'N'; data[1] = 'Z'; - ASSERT_TRUE(write_bytes(path.data(), data)); + const auto f = MemFdFile::create(data); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "55 8B EC", ".text"); EXPECT_FALSE(res.has_value()); } TEST(unit_test_pe_pattern_scan_more, InvalidNtSignature) { - constexpr std::string_view path = "./test_bad_nt.bin"; std::vector data(256, 0); - // valid DOS header data[0] = 'M'; data[1] = 'Z'; - // point e_lfanew to 0x80 constexpr std::uint32_t e_lfanew = 0x80; std::memcpy(data.data() + 0x3C, &e_lfanew, sizeof(e_lfanew)); - // write garbage at e_lfanew (not 'PE\0\0') data[e_lfanew + 0] = 'X'; data[e_lfanew + 1] = 'Y'; data[e_lfanew + 2] = 'Z'; data[e_lfanew + 3] = 'W'; - ASSERT_TRUE(write_bytes(path.data(), data)); + const auto f = MemFdFile::create(data); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "55 8B EC", ".text"); EXPECT_FALSE(res.has_value()); } TEST(unit_test_pe_pattern_scan_more, SectionNotFound) { - // reuse minimal writer but with section named .data and search .text - constexpr std::string_view path = "./test_section_not_found.bin"; - std::ofstream f(path.data(), std::ios::binary); - ASSERT_TRUE(f.is_open()); - // DOS - std::vector dos(64, 0); - dos[0] = 'M'; - dos[1] = 'Z'; - std::uint32_t e_lfanew = 0x80; - std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew)); - f.write(reinterpret_cast(dos.data()), dos.size()); - // pad - std::vector pad(e_lfanew - static_cast(f.tellp()), 0); - f.write(pad.data(), pad.size()); - // NT sig - f.put('P'); - f.put('E'); - f.put('\0'); - f.put('\0'); - // FileHeader minimal - std::uint16_t machine = 0x8664; - std::uint16_t num_sections = 1; - std::uint32_t z = 0; - std::uint32_t z2 = 0; - std::uint32_t numsym = 0; - std::uint16_t size_opt = 0xF0; - std::uint16_t ch = 0; - f.write(reinterpret_cast(&machine), sizeof(machine)); - f.write(reinterpret_cast(&num_sections), sizeof(num_sections)); - f.write(reinterpret_cast(&z), sizeof(z)); - f.write(reinterpret_cast(&z2), sizeof(z2)); - f.write(reinterpret_cast(&numsym), sizeof(numsym)); - f.write(reinterpret_cast(&size_opt), sizeof(size_opt)); - f.write(reinterpret_cast(&ch), sizeof(ch)); - // Optional header magic - std::uint16_t magic = 0x20b; - f.write(reinterpret_cast(&magic), sizeof(magic)); - std::vector opt(size_opt - sizeof(magic), 0); - f.write(reinterpret_cast(opt.data()), opt.size()); - // Section header named .data - char name[8] = {'.', 'd', 'a', 't', 'a', 0, 0, 0}; - f.write(name, 8); - std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200; - f.write(reinterpret_cast(&vs), 4); - f.write(reinterpret_cast(&va), 4); - f.write(reinterpret_cast(&srd), 4); - f.write(reinterpret_cast(&prd), 4); - std::vector rest(16, 0); - f.write(rest.data(), rest.size()); - // section bytes - std::vector sec = {0x00, 0x01, 0x02, 0x03}; - f.write(reinterpret_cast(sec.data()), sec.size()); - f.close(); - - auto res = PePatternScanner::scan_for_pattern_in_file(path, "00 01", ".text"); + // Minimal PE with a .data section; scanning for .text should fail + constexpr std::uint32_t e_lfanew = 0x80u; + constexpr std::uint16_t size_opt = 0xF0u; + constexpr std::size_t nt_off = e_lfanew; + constexpr std::size_t fh_off = nt_off + 4; + constexpr std::size_t oh_off = fh_off + 20; + constexpr std::size_t sh_off = oh_off + size_opt; + constexpr std::size_t data_off = sh_off + 44; + + const std::vector sec_data = {0x00, 0x01, 0x02, 0x03}; + std::vector buf(data_off + sec_data.size(), 0u); + + buf[0] = 'M'; buf[1] = 'Z'; + std::memcpy(buf.data() + 0x3C, &e_lfanew, 4); + buf[nt_off] = 'P'; buf[nt_off + 1] = 'E'; + + const std::uint16_t machine = 0x8664u, num_sections = 1u; + std::memcpy(buf.data() + fh_off, &machine, 2); + std::memcpy(buf.data() + fh_off + 2, &num_sections, 2); + std::memcpy(buf.data() + fh_off + 16, &size_opt, 2); + + const std::uint16_t magic = 0x20Bu; + std::memcpy(buf.data() + oh_off, &magic, 2); + + const char name[8] = {'.','d','a','t','a',0,0,0}; + std::memcpy(buf.data() + sh_off, name, 8); + + const std::uint32_t vs = 4u, va = 0x1000u, srd = 4u, prd = static_cast(data_off); + std::memcpy(buf.data() + sh_off + 8, &vs, 4); + std::memcpy(buf.data() + sh_off + 12, &va, 4); + std::memcpy(buf.data() + sh_off + 16, &srd, 4); + std::memcpy(buf.data() + sh_off + 20, &prd, 4); + std::memcpy(buf.data() + data_off, sec_data.data(), sec_data.size()); + + const auto f = MemFdFile::create(buf); + ASSERT_TRUE(f.valid()); + + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "00 01", ".text"); EXPECT_FALSE(res.has_value()); } TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds) { // Create an in-memory buffer that mimics loaded module layout - // Define local header structs matching those in source struct DosHeader { std::uint16_t e_magic; @@ -158,9 +127,9 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds) std::uint32_t base_of_code; std::uint64_t image_base; std::uint32_t section_alignment; - std::uint32_t file_alignment; /* rest omitted */ + std::uint32_t file_alignment; std::uint32_t size_image; - std::uint32_t size_headers; /* keep space */ + std::uint32_t size_headers; std::uint8_t pad[200]; }; struct SectionHeader @@ -188,44 +157,38 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds) }; const std::vector pattern_bytes = {0xDE, 0xAD, 0xBE, 0xEF, 0x90}; - constexpr std::uint32_t base_of_code = 0x200; // will place bytes at offset 0x200 + constexpr std::uint32_t base_of_code = 0x200; const std::uint32_t size_code = static_cast(pattern_bytes.size()); const std::uint32_t bufsize = 0x400 + size_code; std::vector buf(bufsize, 0); - // DOS header const auto dos = reinterpret_cast(buf.data()); - dos->e_magic = 0x5A4D; + dos->e_magic = 0x5A4D; dos->e_lfanew = 0x80; - // NT headers const auto nt = reinterpret_cast(buf.data() + dos->e_lfanew); - nt->signature = 0x4550; // 'PE\0\0' - nt->file_header.machine = 0x8664; - nt->file_header.num_sections = 1; - nt->file_header.size_optional_header = static_cast(sizeof(OptionalHeaderX64)); - - nt->optional_header.magic = 0x020B; // x64 - nt->optional_header.base_of_code = base_of_code; - nt->optional_header.size_code = size_code; + nt->signature = 0x4550; + nt->file_header.machine = 0x8664; + nt->file_header.num_sections = 1; + nt->file_header.size_optional_header = static_cast(sizeof(OptionalHeaderX64)); + nt->optional_header.magic = 0x020B; + nt->optional_header.base_of_code = base_of_code; + nt->optional_header.size_code = size_code; - // Compute section table offset: e_lfanew + 4 (sig) + FileHeader + OptionalHeader const std::size_t section_table_off = static_cast(dos->e_lfanew) + 4 + sizeof(FileHeader) + sizeof(OptionalHeaderX64); nt->optional_header.size_headers = static_cast(section_table_off + sizeof(SectionHeader)); - // Section header (.text) const auto sect = reinterpret_cast(buf.data() + section_table_off); std::memset(sect, 0, sizeof(SectionHeader)); std::memcpy(sect->name, ".text", 5); - sect->virtual_size = size_code; + sect->virtual_size = size_code; sect->virtual_address = base_of_code; - sect->size_raw_data = size_code; - sect->ptr_raw_data = base_of_code; - sect->characteristics = 0x60000020; // code | execute | read + sect->size_raw_data = size_code; + sect->ptr_raw_data = base_of_code; + sect->characteristics = 0x60000020; - // place code at base_of_code std::memcpy(buf.data() + base_of_code, pattern_bytes.data(), pattern_bytes.size()); const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD BE EF", ".text"); diff --git a/tests/general/unit_test_pe_pattern_scan_more2.cpp b/tests/general/unit_test_pe_pattern_scan_more2.cpp index 7b4ddeb4..163a2c1f 100644 --- a/tests/general/unit_test_pe_pattern_scan_more2.cpp +++ b/tests/general/unit_test_pe_pattern_scan_more2.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "mem_fd_helper.hpp" using namespace omath; @@ -19,95 +20,6 @@ struct TestFileHeader std::uint16_t characteristics; }; -static bool write_bytes(const std::string& path, const std::vector& data) -{ - std::ofstream f(path, std::ios::binary); - if (!f.is_open()) - return false; - f.write(reinterpret_cast(data.data()), data.size()); - return true; -} - -// Helper: write a trivial PE-like file with DOS header and a single section named .text -static bool write_minimal_pe_file(const std::string& path, const std::vector& section_bytes) -{ - std::ofstream f(path, std::ios::binary); - if (!f.is_open()) - return false; - - // Write DOS header (e_magic = 0x5A4D, e_lfanew at offset 0x3C) - std::vector dos(64, 0); - dos[0] = 'M'; - dos[1] = 'Z'; - std::uint32_t e_lfanew = 0x80; - std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew)); - f.write(reinterpret_cast(dos.data()), dos.size()); - - // Pad up to e_lfanew - if (f.tellp() < static_cast(e_lfanew)) - { - std::vector pad(e_lfanew - static_cast(f.tellp()), 0); - f.write(pad.data(), pad.size()); - } - - // NT headers signature 'PE\0\0' - f.put('P'); - f.put('E'); - f.put('\0'); - f.put('\0'); - - // FileHeader minimal - std::uint16_t machine = 0x8664; // x64 - std::uint16_t num_sections = 1; - std::uint32_t dummy32 = 0; - std::uint32_t dummy32b = 0; - std::uint16_t size_optional = 0xF0; - std::uint16_t characteristics = 0; - f.write(reinterpret_cast(&machine), sizeof(machine)); - f.write(reinterpret_cast(&num_sections), sizeof(num_sections)); - f.write(reinterpret_cast(&dummy32), sizeof(dummy32)); - f.write(reinterpret_cast(&dummy32b), sizeof(dummy32b)); - std::uint32_t num_symbols = 0; - f.write(reinterpret_cast(&num_symbols), sizeof(num_symbols)); - f.write(reinterpret_cast(&size_optional), sizeof(size_optional)); - f.write(reinterpret_cast(&characteristics), sizeof(characteristics)); - - // OptionalHeader minimal filler - std::uint16_t magic = 0x20b; - f.write(reinterpret_cast(&magic), sizeof(magic)); - std::vector opt(size_optional - sizeof(magic), 0); - f.write(reinterpret_cast(opt.data()), opt.size()); - - // Section header (name 8 bytes, then remaining 36 bytes) - char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0}; - f.write(name, 8); - - constexpr std::uint32_t section_header_rest = 36u; - const std::streampos header_rest_pos = f.tellp(); - std::vector placeholder(section_header_rest, 0); - f.write(placeholder.data(), placeholder.size()); - - // Now write section raw data and remember its file offset - const std::streampos data_pos = f.tellp(); - f.write(reinterpret_cast(section_bytes.data()), static_cast(section_bytes.size())); - - // Patch section header fields - const std::uint32_t virtual_size = static_cast(section_bytes.size()); - constexpr std::uint32_t virtual_address = 0x1000u; - const std::uint32_t size_raw_data = static_cast(section_bytes.size()); - const std::uint32_t ptr_raw_data = static_cast(data_pos); - - f.seekp(header_rest_pos, std::ios::beg); - f.write(reinterpret_cast(&virtual_size), sizeof(virtual_size)); - f.write(reinterpret_cast(&virtual_address), sizeof(virtual_address)); - f.write(reinterpret_cast(&size_raw_data), sizeof(size_raw_data)); - f.write(reinterpret_cast(&ptr_raw_data), sizeof(ptr_raw_data)); - f.seekp(0, std::ios::end); - - f.close(); - return true; -} - TEST(unit_test_pe_pattern_scan_more2, LoadedModuleNullBaseReturnsNull) { const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(nullptr, "DE AD"); @@ -116,7 +28,6 @@ TEST(unit_test_pe_pattern_scan_more2, LoadedModuleNullBaseReturnsNull) TEST(unit_test_pe_pattern_scan_more2, LoadedModuleInvalidOptionalHeaderReturnsNull) { - // Construct in-memory buffer with DOS header but invalid optional header magic std::vector buf(0x200, 0); struct DosHeader { @@ -128,19 +39,11 @@ TEST(unit_test_pe_pattern_scan_more2, LoadedModuleInvalidOptionalHeaderReturnsNu dos->e_magic = 0x5A4D; dos->e_lfanew = 0x80; - // Place an NT header with wrong optional magic at e_lfanew const auto nt_ptr = buf.data() + dos->e_lfanew; - // write signature - nt_ptr[0] = 'P'; - nt_ptr[1] = 'E'; - nt_ptr[2] = 0; - nt_ptr[3] = 0; - // craft FileHeader with size_optional_header large enough + nt_ptr[0] = 'P'; nt_ptr[1] = 'E'; nt_ptr[2] = 0; nt_ptr[3] = 0; + constexpr std::uint16_t size_opt = 0xE0; - // file header starts at offset 4 - std::memcpy(nt_ptr + 4 + 12, &size_opt, - sizeof(size_opt)); // size_optional_header located after 12 bytes into FileHeader - // write optional header magic to be invalid value + std::memcpy(nt_ptr + 4 + 12, &size_opt, sizeof(size_opt)); constexpr std::uint16_t bad_magic = 0x9999; std::memcpy(nt_ptr + 4 + sizeof(std::uint32_t) + sizeof(std::uint16_t) + sizeof(std::uint16_t), &bad_magic, sizeof(bad_magic)); @@ -151,13 +54,11 @@ TEST(unit_test_pe_pattern_scan_more2, LoadedModuleInvalidOptionalHeaderReturnsNu TEST(unit_test_pe_pattern_scan_more2, FileX86OptionalHeaderScanFindsPattern) { - constexpr std::string_view path = "./test_pe_x86.bin"; const std::vector pattern = {0xDE, 0xAD, 0xBE, 0xEF}; + const auto f = MemFdFile::create(build_minimal_pe(pattern)); + ASSERT_TRUE(f.valid()); - // Use helper from this file to write a consistent minimal PE file with .text section - ASSERT_TRUE(write_minimal_pe_file(path.data(), pattern)); - - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE AD BE EF", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "DE AD BE EF", ".text"); ASSERT_TRUE(res.has_value()); EXPECT_GE(res->virtual_base_addr, 0u); EXPECT_GE(res->raw_base_addr, 0u); @@ -166,97 +67,73 @@ TEST(unit_test_pe_pattern_scan_more2, FileX86OptionalHeaderScanFindsPattern) TEST(unit_test_pe_pattern_scan_more2, FilePatternNotFoundReturnsNull) { - const std::string path = "./test_pe_no_pattern.bin"; std::vector data(512, 0); - // minimal DOS/NT headers to make extract_section fail earlier or return empty data - data[0] = 'M'; - data[1] = 'Z'; + data[0] = 'M'; data[1] = 'Z'; constexpr std::uint32_t e_lfanew = 0x80; std::memcpy(data.data() + 0x3C, &e_lfanew, sizeof(e_lfanew)); - // NT signature - data[e_lfanew + 0] = 'P'; - data[e_lfanew + 1] = 'E'; - data[e_lfanew + 2] = 0; - data[e_lfanew + 3] = 0; - // FileHeader: one section, size_optional_header set low + data[e_lfanew + 0] = 'P'; data[e_lfanew + 1] = 'E'; + constexpr std::uint16_t num_sections = 1; constexpr std::uint16_t size_optional_header = 0xE0; std::memcpy(data.data() + e_lfanew + 6, &num_sections, sizeof(num_sections)); std::memcpy(data.data() + e_lfanew + 4 + 12, &size_optional_header, sizeof(size_optional_header)); - // Optional header magic x64 + constexpr std::uint16_t magic = 0x020B; std::memcpy(data.data() + e_lfanew + 4 + sizeof(TestFileHeader), &magic, sizeof(magic)); - // Section header .text with small data that does not contain the pattern + constexpr std::size_t offset_to_segment_table = e_lfanew + 4 + sizeof(TestFileHeader) + size_optional_header; constexpr char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0}; std::memcpy(data.data() + offset_to_segment_table, name, 8); std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200; - std::memcpy(data.data() + offset_to_segment_table + 8, &vs, 4); - std::memcpy(data.data() + offset_to_segment_table + 12, &va, 4); + std::memcpy(data.data() + offset_to_segment_table + 8, &vs, 4); + std::memcpy(data.data() + offset_to_segment_table + 12, &va, 4); std::memcpy(data.data() + offset_to_segment_table + 16, &srd, 4); std::memcpy(data.data() + offset_to_segment_table + 20, &prd, 4); - // write file - ASSERT_TRUE(write_bytes(path, data)); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text"); + const auto f = MemFdFile::create(data); + ASSERT_TRUE(f.valid()); + + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "AA BB CC", ".text"); EXPECT_FALSE(res.has_value()); } -// Extra tests for pe_pattern_scan edge cases (on-disk API) TEST(PePatternScanMore2, PatternAtStartFound) { - const std::string path = "./test_pe_more_start.bin"; const std::vector bytes = {0x90, 0x01, 0x02, 0x03, 0x04}; - ASSERT_TRUE(write_minimal_pe_file(path, bytes)); + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "90 01 02", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "90 01 02", ".text"); EXPECT_TRUE(res.has_value()); } TEST(PePatternScanMore2, PatternAtEndFound) { - const std::string path = "./test_pe_more_end.bin"; - std::vector bytes = {0x00, 0x11, 0x22, 0x33, 0x44}; - ASSERT_TRUE(write_minimal_pe_file(path, bytes)); + const std::vector bytes = {0x00, 0x11, 0x22, 0x33, 0x44}; + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "22 33 44", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "22 33 44", ".text"); if (!res.has_value()) { - // Try to locate the section header and print the raw section bytes the scanner would read - std::ifstream in(path, std::ios::binary); - ASSERT_TRUE(in.is_open()); - // search for ".text" name - in.seekg(0, std::ios::beg); - std::vector filebuf((std::istreambuf_iterator(in)), std::istreambuf_iterator()); - const auto it = std::search(filebuf.begin(), filebuf.end(), std::begin(".text"), std::end(".text") - 1); - if (it != filebuf.end()) + // Debug: inspect section header via the memfd path + std::ifstream in(f.path(), std::ios::binary); + if (in.is_open()) { - const size_t pos = std::distance(filebuf.begin(), it); - // after name, next fields: virtual_size (4), virtual_address(4), size_raw_data(4), ptr_raw_data(4) - const size_t meta_off = pos + 8; - uint32_t virtual_size{}; - uint32_t virtual_address{}; - uint32_t size_raw_data{}; - uint32_t ptr_raw_data{}; - std::memcpy(&virtual_size, filebuf.data() + meta_off, sizeof(virtual_size)); - std::memcpy(&virtual_address, filebuf.data() + meta_off + 4, sizeof(virtual_address)); - std::memcpy(&size_raw_data, filebuf.data() + meta_off + 8, sizeof(size_raw_data)); - std::memcpy(&ptr_raw_data, filebuf.data() + meta_off + 12, sizeof(ptr_raw_data)); - - std::cerr << "Parsed section header: virtual_size=" << virtual_size << " virtual_address=0x" << std::hex - << virtual_address << std::dec << " size_raw_data=" << size_raw_data - << " ptr_raw_data=" << ptr_raw_data << "\n"; - - if (ptr_raw_data + size_raw_data <= filebuf.size()) + std::vector filebuf((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + const auto it = std::search(filebuf.begin(), filebuf.end(), std::begin(".text"), std::end(".text") - 1); + if (it != filebuf.end()) { - std::cerr << "Extracted section bytes:\n"; - for (size_t i = 0; i < size_raw_data; i += 16) - { - std::fprintf(stderr, "%04zx: ", i); - for (size_t j = 0; j < 16 && i + j < size_raw_data; ++j) - std::fprintf(stderr, "%02x ", static_cast(filebuf[ptr_raw_data + i + j])); - std::fprintf(stderr, "\n"); - } + const std::size_t pos = std::distance(filebuf.begin(), it); + const std::size_t meta_off = pos + 8; + std::uint32_t virtual_size{}, virtual_address{}, size_raw_data{}, ptr_raw_data{}; + std::memcpy(&virtual_size, filebuf.data() + meta_off, sizeof(virtual_size)); + std::memcpy(&virtual_address, filebuf.data() + meta_off + 4, sizeof(virtual_address)); + std::memcpy(&size_raw_data, filebuf.data() + meta_off + 8, sizeof(size_raw_data)); + std::memcpy(&ptr_raw_data, filebuf.data() + meta_off + 12, sizeof(ptr_raw_data)); + std::cerr << "Parsed section header: virtual_size=" << virtual_size << " virtual_address=0x" + << std::hex << virtual_address << std::dec << " size_raw_data=" << size_raw_data + << " ptr_raw_data=" << ptr_raw_data << "\n"; } } } @@ -265,30 +142,30 @@ TEST(PePatternScanMore2, PatternAtEndFound) TEST(PePatternScanMore2, WildcardMatches) { - const std::string path = "./test_pe_more_wild.bin"; const std::vector bytes = {0xDE, 0xAD, 0xBE, 0xEF}; - ASSERT_TRUE(write_minimal_pe_file(path, bytes)); + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE ?? BE", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "DE ?? BE", ".text"); EXPECT_TRUE(res.has_value()); } TEST(PePatternScanMore2, PatternLongerThanBuffer) { - const std::string path = "./test_pe_more_small.bin"; const std::vector bytes = {0xAA, 0xBB}; - ASSERT_TRUE(write_minimal_pe_file(path, bytes)); + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "AA BB CC", ".text"); EXPECT_FALSE(res.has_value()); } TEST(PePatternScanMore2, InvalidPatternParse) { - const std::string path = "./test_pe_more_invalid.bin"; const std::vector bytes = {0x01, 0x02, 0x03}; - ASSERT_TRUE(write_minimal_pe_file(path, bytes)); + const auto f = MemFdFile::create(build_minimal_pe(bytes)); + ASSERT_TRUE(f.valid()); - const auto res = PePatternScanner::scan_for_pattern_in_file(path, "01 GG 03", ".text"); + const auto res = PePatternScanner::scan_for_pattern_in_file(f.path(), "01 GG 03", ".text"); EXPECT_FALSE(res.has_value()); }