improved file scanners

This commit is contained in:
2026-06-10 04:29:52 +03:00
parent ce589b4f17
commit 00287c7a58
9 changed files with 660 additions and 78 deletions
+65 -20
View File
@@ -35,13 +35,22 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
std::vector<std::byte> buf(total_size, std::byte{0});
auto w8 = [&](std::size_t off, std::uint8_t v) { buf[off] = std::byte{v}; };
auto w8 = [&](std::size_t off, std::uint8_t v)
{
buf[off] = std::byte{v};
};
auto w16 = [&](std::size_t off, std::uint16_t v)
{ std::memcpy(buf.data() + off, &v, 2); };
{
std::memcpy(buf.data() + off, &v, 2);
};
auto w32 = [&](std::size_t off, std::uint32_t v)
{ std::memcpy(buf.data() + off, &v, 4); };
{
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{ std::memcpy(buf.data() + off, &v, 8); };
{
std::memcpy(buf.data() + off, &v, 8);
};
// --- ELF64 file header ---
// e_ident
@@ -53,19 +62,19 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
w8(5, 1); // ELFDATA2LSB
w8(6, 1); // EV_CURRENT
// rest of e_ident is 0
w16(16, 2); // e_type = ET_EXEC
w16(16, 2); // e_type = ET_EXEC
w16(18, 62); // e_machine = EM_X86_64
w32(20, 1); // e_version
w64(24, 0); // e_entry
w64(32, 0); // e_phoff
w32(20, 1); // e_version
w64(24, 0); // e_entry
w64(32, 0); // e_phoff
w64(40, static_cast<std::uint64_t>(shdr_table_off)); // e_shoff
w32(48, 0); // e_flags
w32(48, 0); // e_flags
w16(52, 64); // e_ehsize
w16(54, 56); // e_phentsize
w16(56, 0); // e_phnum
w16(56, 0); // e_phnum
w16(58, static_cast<std::uint16_t>(shdr_size)); // e_shentsize
w16(60, static_cast<std::uint16_t>(num_sections)); // e_shnum
w16(62, 2); // e_shstrndx = 2 (.shstrtab is section index 2)
w16(62, 2); // e_shstrndx = 2 (.shstrtab is section index 2)
// --- section data (.text) ---
const std::size_t copy_len = std::min(code_bytes.size(), text_size);
@@ -104,9 +113,9 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
// Section 1: .text
{
const std::size_t base = shdr_table_off + 1 * shdr_size;
w32(base + 0, 1); // sh_name → index 1 in shstrtab → ".text"
w32(base + 4, 1); // sh_type = SHT_PROGBITS
w64(base + 8, 6); // sh_flags = SHF_ALLOC|SHF_EXECINSTR
w32(base + 0, 1); // sh_name → index 1 in shstrtab → ".text"
w32(base + 4, 1); // sh_type = SHT_PROGBITS
w64(base + 8, 6); // sh_flags = SHF_ALLOC|SHF_EXECINSTR
w64(base + 16, static_cast<std::uint64_t>(text_off)); // sh_addr (same as offset in test)
w64(base + 24, static_cast<std::uint64_t>(text_off)); // sh_offset
w64(base + 32, static_cast<std::uint64_t>(text_size)); // sh_size
@@ -116,8 +125,8 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
// Section 2: .shstrtab
{
const std::size_t base = shdr_table_off + 2 * shdr_size;
w32(base + 0, 0); // sh_name → index 0 → "" (good enough for scanner)
w32(base + 4, 3); // sh_type = SHT_STRTAB
w32(base + 0, 0); // sh_name → index 0 → "" (good enough for scanner)
w32(base + 4, 3); // sh_type = SHT_STRTAB
w64(base + 24, static_cast<std::uint64_t>(shstrtab_off)); // sh_offset
w64(base + 32, static_cast<std::uint64_t>(shstrtab_size)); // sh_size
}
@@ -151,6 +160,18 @@ TEST(unit_test_elf_pattern_scan_memory, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_elf64_with_text_section(code);
const auto result =
ElfPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf}, ".text");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_elf_pattern_scan_memory, pattern_not_found_returns_nullopt)
{
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03, 0x04};
@@ -182,8 +203,8 @@ TEST(unit_test_elf_pattern_scan_memory, missing_section_returns_nullopt)
const std::vector<std::uint8_t> code = {0x90, 0x90};
const auto buf = make_elf64_with_text_section(code);
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf},
"90 90", ".nonexistent");
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "90 90",
".nonexistent");
EXPECT_FALSE(result.has_value());
}
@@ -201,8 +222,8 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan)
}
const auto file_result = ElfPatternScanner::scan_for_pattern_in_file(tmp_path, "48 89 E5 DE AD", ".text");
const auto mem_result =
ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "48 89 E5 DE AD", ".text");
const auto mem_result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf},
"48 89 E5 DE AD", ".text");
std::filesystem::remove(tmp_path);
@@ -212,3 +233,27 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan)
EXPECT_EQ(file_result->raw_base_addr, mem_result->raw_base_addr);
EXPECT_EQ(file_result->target_offset, mem_result->target_offset);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_elf64_with_text_section(code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_elf_consteval_test.elf";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = ElfPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path, ".text");
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_loaded_module_null_returns_nullopt)
{
const auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
@@ -1,5 +1,7 @@
// Tests for MachOPatternScanner::scan_for_pattern_in_memory_file
#include <cstring>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
#include <omath/utility/macho_pattern_scan.hpp>
#include <span>
@@ -37,38 +39,43 @@ static std::vector<std::byte> make_macho64_with_text_section(const std::vector<s
constexpr std::size_t total_size = text_raw_off + text_raw_size;
constexpr std::uint64_t text_vmaddr = 0x1000ULL;
constexpr std::uint32_t cmd_size =
static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
constexpr std::uint32_t cmd_size = static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
std::vector<std::byte> buf(total_size, std::byte{0});
auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); };
auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); };
auto w32 = [&](std::size_t off, std::uint32_t v)
{
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
// MachHeader64
w32(0, mh_magic_64);
w32(4, 0x0100000C); // cputype = CPU_TYPE_ARM64 (doesn't matter for scan)
w32(12, 2); // filetype = MH_EXECUTE
w32(16, 1); // ncmds = 1
w32(20, cmd_size); // sizeofcmds
w32(4, 0x0100000C); // cputype = CPU_TYPE_ARM64 (doesn't matter for scan)
w32(12, 2); // filetype = MH_EXECUTE
w32(16, 1); // ncmds = 1
w32(20, cmd_size); // sizeofcmds
// SegmentCommand64 at 0x20
constexpr std::size_t seg_off = hdr_size;
w32(seg_off + 0, lc_segment_64);
w32(seg_off + 4, cmd_size);
std::memcpy(buf.data() + seg_off + 8, "__TEXT", 6); // segname
w64(seg_off + 24, text_vmaddr); // vmaddr
w64(seg_off + 32, text_raw_size); // vmsize
w64(seg_off + 40, text_raw_off); // fileoff
w64(seg_off + 48, text_raw_size); // filesize
w32(seg_off + 64, 1); // nsects
w64(seg_off + 24, text_vmaddr); // vmaddr
w64(seg_off + 32, text_raw_size); // vmsize
w64(seg_off + 40, text_raw_off); // fileoff
w64(seg_off + 48, text_raw_size); // filesize
w32(seg_off + 64, 1); // nsects
// Section64 at 0x68
constexpr std::size_t sect_off = seg_off + seg_size;
std::memcpy(buf.data() + sect_off + 0, "__text", 6); // sectname
std::memcpy(buf.data() + sect_off + 0, "__text", 6); // sectname
std::memcpy(buf.data() + sect_off + 16, "__TEXT", 6); // segname
w64(sect_off + 32, text_vmaddr); // addr
w64(sect_off + 40, text_raw_size); // size
w64(sect_off + 32, text_vmaddr); // addr
w64(sect_off + 40, text_raw_size); // size
w32(sect_off + 48, static_cast<std::uint32_t>(text_raw_off)); // offset (file offset)
// Section data
@@ -105,6 +112,18 @@ TEST(unit_test_macho_memory_file_scan, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_macho_memory_file_scan, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_macho64_with_text_section(code);
const auto result =
MachOPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_macho_memory_file_scan, pattern_not_found_returns_nullopt)
{
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03};
@@ -143,3 +162,27 @@ TEST(unit_test_macho_memory_file_scan, raw_addr_and_virtual_addr_correct)
EXPECT_EQ(result->raw_base_addr, expected_raw_off);
EXPECT_EQ(result->virtual_base_addr, 0x1000u);
}
TEST(unit_test_macho_memory_file_scan, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_macho64_with_text_section(code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_macho_consteval_test.bin";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = MachOPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path);
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_macho_memory_file_scan, consteval_loaded_module_null_returns_nullopt)
{
const auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
+56 -11
View File
@@ -1,5 +1,7 @@
// Tests for PePatternScanner::scan_for_pattern_in_memory_file
#include <cstring>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp>
#include <span>
@@ -10,8 +12,7 @@ using namespace omath;
// Reuse the fake-module builder from unit_test_pe_pattern_scan_loaded.cpp but
// lay out the buffer as a raw PE *file* (ptr_raw_data != virtual_address).
static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, std::uint32_t ptr_raw_data,
std::uint32_t section_size,
const std::vector<std::uint8_t>& code_bytes)
std::uint32_t section_size, const std::vector<std::uint8_t>& code_bytes)
{
constexpr std::uint32_t e_lfanew = 0x80;
constexpr std::uint32_t nt_sig = 0x4550;
@@ -24,9 +25,18 @@ static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, s
const std::uint32_t total_size = ptr_raw_data + section_size + 0x100;
std::vector<std::byte> buf(total_size, std::byte{0});
auto w16 = [&](std::size_t off, std::uint16_t v) { std::memcpy(buf.data() + off, &v, 2); };
auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); };
auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); };
auto w16 = [&](std::size_t off, std::uint16_t v)
{
std::memcpy(buf.data() + off, &v, 2);
};
auto w32 = [&](std::size_t off, std::uint32_t v)
{
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
// DOS header
w16(0x00, 0x5A4D);
@@ -48,10 +58,10 @@ static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, s
// Section header (.text)
const std::size_t sh_off = section_table_off;
std::memcpy(buf.data() + sh_off, ".text", 5);
w32(sh_off + 8, section_size); // VirtualSize
w32(sh_off + 12, virtual_address); // VirtualAddress
w32(sh_off + 16, section_size); // SizeOfRawData
w32(sh_off + 20, ptr_raw_data); // PointerToRawData
w32(sh_off + 8, section_size); // VirtualSize
w32(sh_off + 12, virtual_address); // VirtualAddress
w32(sh_off + 16, section_size); // SizeOfRawData
w32(sh_off + 20, ptr_raw_data); // PointerToRawData
// Place code at raw file offset
const std::size_t copy_len = std::min(code_bytes.size(), static_cast<std::size_t>(section_size));
@@ -87,13 +97,24 @@ TEST(unit_test_pe_memory_file_scan, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_pe_memory_file_scan, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
const auto result =
PePatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_pe_memory_file_scan, pattern_not_found_returns_nullopt)
{
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03};
const auto buf = make_fake_pe_file(0x1000, 0x400, static_cast<std::uint32_t>(code.size()), code);
const auto result =
PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC");
const auto result = PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC");
EXPECT_FALSE(result.has_value());
}
@@ -126,3 +147,27 @@ TEST(unit_test_pe_memory_file_scan, raw_addr_differs_from_virtual_address)
// virtual_base_addr = virtual_address + image_base (image_base = 0)
EXPECT_EQ(result->virtual_base_addr, 0x3000u);
}
TEST(unit_test_pe_memory_file_scan, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_pe_consteval_test.exe";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = PePatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path);
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_pe_memory_file_scan, consteval_loaded_module_null_returns_nullopt)
{
const auto result = PePatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}