Feature/elf pattern scan (#133)

* added some code

* improvement

* visit

* added scanner code

* update

* fixed naming

* added const

* added type casting

* added file

* patch

* added unlikely

* added in module scanner

* fixing test

* fix

* remove

* improvement

* fix

* Update source/utility/elf_pattern_scan.cpp

Co-authored-by: Saikari <lin@sz.cn.eu.org>

* rev

* fix

* patch

* decomposed method

* fix

* fix

* improvement

* fix

* fix

* commented stuff

---------

Co-authored-by: Saikari <lin@sz.cn.eu.org>
This commit is contained in:
2026-01-01 03:37:55 +03:00
committed by GitHub
parent 368272b1e8
commit 2af77d30d4
8 changed files with 527 additions and 56 deletions

View File

@@ -0,0 +1,25 @@
//
// Created by Vladislav on 30.12.2025.
//
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class ElfPatternScanner final
{
public:
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
};
} // namespace omath

View File

@@ -7,23 +7,20 @@
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
struct PeSectionScanResult
{
std::uint64_t virtual_base_addr;
std::uint64_t raw_base_addr;
std::ptrdiff_t target_offset;
};
class PePatternScanner final class PePatternScanner final
{ {
public: public:
[[nodiscard]] [[nodiscard]]
static std::optional<std::uintptr_t> scan_for_pattern_in_loaded_module(const void* module_base_address, static std::optional<std::uintptr_t>
const std::string_view& pattern); scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
[[nodiscard]] [[nodiscard]]
static std::optional<PeSectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
}; };

View File

@@ -0,0 +1,16 @@
//
// Created by Vladislav on 01.01.2026.
//
#pragma once
#include <cstddef>
#include <cstdint>
namespace omath
{
struct SectionScanResult final
{
std::uintptr_t virtual_base_addr;
std::uintptr_t raw_base_addr;
std::ptrdiff_t target_offset;
};
}

View File

@@ -0,0 +1,325 @@
//
// Created by Vladislav on 30.12.2025.
//
#include "omath/utility/pattern_scan.hpp"
#include <array>
#include <fstream>
#include <omath/utility/elf_pattern_scan.hpp>
#include <utility>
#include <variant>
#include <vector>
#pragma pack(push, 1)
namespace
{
// Common
constexpr uint8_t ei_nident = 16;
constexpr uint8_t ei_class = 4;
constexpr uint8_t elfclass32 = 1;
constexpr uint8_t elfclass64 = 2;
// ReSharper disable CppDeclaratorNeverUsed
struct Elf32Ehdr final
{
unsigned char e_ident[ei_nident];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint32_t e_entry;
uint32_t e_phoff;
uint32_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Elf64Ehdr final
{
unsigned char e_ident[ei_nident];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry;
uint64_t e_phoff;
uint64_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Elf32Shdr final
{
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
uint32_t sh_addr;
uint32_t sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
};
struct Elf64Shdr final
{
uint32_t sh_name;
uint32_t sh_type;
uint64_t sh_flags;
uint64_t sh_addr;
uint64_t sh_offset;
uint64_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint64_t sh_addralign;
uint64_t sh_entsize;
};
// ReSharper restore CppDeclaratorNeverUsed
#pragma pack(pop)
} // namespace
namespace
{
enum class FileArch : std::int8_t
{
x32,
x64,
};
template<FileArch arch>
struct ElfHeaders
{
using FileHeader = std::conditional_t<arch == FileArch::x64, Elf64Ehdr, Elf32Ehdr>;
using SectionHeader = std::conditional_t<arch == FileArch::x64, Elf64Shdr, Elf32Shdr>;
FileHeader file_header;
SectionHeader section_header;
};
[[nodiscard]]
bool not_elf_file(std::fstream& file)
{
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
std::array<char, valid_elf_signature.size() + 1> elf_signature{};
const std::streampos back_up_pose = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(elf_signature.data(), 4);
file.seekg(back_up_pose, std::ios_base::beg);
return std::string_view{elf_signature.data(), 4} != valid_elf_signature;
}
[[nodiscard]]
std::optional<FileArch> get_file_arch(std::fstream& file)
{
std::array<char, ei_nident> e_ident{};
const std::streampos back_up_pose = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(e_ident.data(), e_ident.size());
file.seekg(back_up_pose, std::ios_base::beg);
if (e_ident[ei_class] == elfclass64)
return FileArch::x64;
if (e_ident[ei_class] == elfclass32)
return FileArch::x32;
return std::nullopt;
}
struct ExtractedSection final
{
std::uintptr_t virtual_base_addr{};
std::uintptr_t raw_base_addr{};
std::vector<std::byte> data;
};
[[maybe_unused]]
std::optional<ExtractedSection> get_elf_section_by_name(const std::filesystem::path& path,
const std::string_view& section_name)
{
std::fstream file(path, std::ios::binary | std::ios::in);
if (!file.is_open()) [[unlikely]]
return std::nullopt;
if (not_elf_file(file)) [[unlikely]]
return std::nullopt;
const auto architecture = get_file_arch(file);
if (!architecture.has_value()) [[unlikely]]
return std::nullopt;
std::variant<ElfHeaders<FileArch::x64>, ElfHeaders<FileArch::x32>> elf_headers;
if (architecture.value() == FileArch::x64)
elf_headers = ElfHeaders<FileArch::x64>{};
else if (architecture.value() == FileArch::x32)
elf_headers = ElfHeaders<FileArch::x32>{};
return std::visit(
[&](auto& header) -> std::optional<ExtractedSection>
{
auto& [file_header, section_header] = header;
file.seekg(0, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&file_header), sizeof(file_header))) [[unlikely]]
return std::nullopt;
const std::streamoff shstr_off =
static_cast<std::streamoff>(file_header.e_shoff)
+ static_cast<std::streamoff>(file_header.e_shstrndx) * sizeof(section_header);
file.seekg(shstr_off, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&section_header), sizeof(section_header))) [[unlikely]]
return std::nullopt;
std::vector<char> shstrtab(static_cast<std::size_t>(section_header.sh_size));
file.seekg(section_header.sh_offset, std::ios_base::beg);
if (!file.read(shstrtab.data(), static_cast<std::streamsize>(shstrtab.size()))) [[unlikely]]
return std::nullopt;
for (std::uint16_t i = 0; i < file_header.e_shnum; ++i)
{
decltype(section_header) current_section{};
const std::streamoff off = static_cast<std::streamoff>(file_header.e_shoff)
+ static_cast<std::streamoff>(i) * sizeof(current_section);
file.seekg(off, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&current_section), sizeof(current_section)))
return std::nullopt;
if (current_section.sh_name >= shstrtab.size())
continue;
// ReSharper disable once CppTooWideScopeInitStatement
const std::string_view name = &shstrtab[current_section.sh_name];
if (section_name != name)
continue;
ExtractedSection out;
out.virtual_base_addr = static_cast<std::uintptr_t>(current_section.sh_addr);
out.raw_base_addr = static_cast<std::uintptr_t>(current_section.sh_offset);
out.data.resize(static_cast<std::size_t>(current_section.sh_size));
file.seekg(static_cast<std::streamoff>(out.raw_base_addr), std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(out.data.data()),
static_cast<std::streamsize>(out.data.size()))) [[unlikely]]
return std::nullopt;
return out;
}
return std::nullopt;
},
elf_headers);
}
template<class FileHeader, class SectionHeader>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* file_header = reinterpret_cast<const FileHeader*>(base);
const auto shoff = static_cast<std::size_t>(file_header->e_shoff);
const auto shnum = static_cast<std::size_t>(file_header->e_shnum);
const auto shstrnd = static_cast<std::size_t>(file_header->e_shstrndx);
const auto shstrtab_off = shoff + shstrnd * sizeof(SectionHeader);
const auto* shstrtab_hdr = reinterpret_cast<const SectionHeader*>(base + shstrtab_off);
const auto shstrtab = reinterpret_cast<const char*>(base + static_cast<std::size_t>(shstrtab_hdr->sh_offset));
const auto shstrtab_size = static_cast<std::size_t>(shstrtab_hdr->sh_size);
for (std::size_t i = 0; i < shnum; ++i)
{
const auto section_off = shoff + i * sizeof(SectionHeader);
const auto* section = reinterpret_cast<const SectionHeader*>(base + section_off);
if (section->sh_size == 0)
continue;
if (std::cmp_greater_equal(section->sh_name, shstrtab_size))
continue;
if (std::string_view{shstrtab + section->sh_name} != target_section_name)
continue;
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size);
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (scan_result == section_end)
return std::nullopt;
return reinterpret_cast<std::uintptr_t>(scan_result);
}
return std::nullopt;
}
} // namespace
namespace omath
{
std::optional<std::uintptr_t>
ElfPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
if (module_base_address == nullptr)
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
// Validate ELF signature.
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
return std::nullopt;
// Detect architecture.
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
: ei_class_value == elfclass32 ? FileArch::x32
: std::optional<FileArch>{};
if (!arch.has_value())
return std::nullopt;
if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
pattern, target_section_name);
if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
pattern, target_section_name);
std::unreachable();
}
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto pe_section = get_elf_section_by_name(path_to_file, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_result =
PatternScanner::scan_for_pattern(pe_section->data.cbegin(), pe_section->data.cend(), pattern);
if (scan_result == pe_section->data.cend())
return std::nullopt;
const auto offset = std::distance(pe_section->data.begin(), scan_result);
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset};
}
} // namespace omath

View File

@@ -239,8 +239,8 @@ namespace
struct ExtractedSection struct ExtractedSection
{ {
std::uint64_t virtual_base_addr; std::uintptr_t virtual_base_addr;
std::uint64_t raw_base_addr; std::uintptr_t raw_base_addr;
std::vector<std::byte> data; std::vector<std::byte> data;
}; };
@@ -261,7 +261,7 @@ namespace
const auto nt_headers = get_nt_header_from_file(file, dos_header); const auto nt_headers = get_nt_header_from_file(file, dos_header);
if (!nt_headers) if (!nt_headers) [[unlikely]]
return std::nullopt; return std::nullopt;
if (invalid_nt_header_file(nt_headers.value())) [[unlikely]] if (invalid_nt_header_file(nt_headers.value())) [[unlikely]]
@@ -290,9 +290,10 @@ namespace
file.seekg(current_section.ptr_raw_data, std::ios::beg); file.seekg(current_section.ptr_raw_data, std::ios::beg);
file.read(reinterpret_cast<char*>(section_data.data()), file.read(reinterpret_cast<char*>(section_data.data()),
static_cast<std::streamsize>(section_data.size())); static_cast<std::streamsize>(section_data.size()));
return ExtractedSection{.virtual_base_addr = current_section.virtual_address return ExtractedSection{
+ concrete_headers.optional_header.image_base, .virtual_base_addr = static_cast<std::uintptr_t>(
.raw_base_addr = current_section.ptr_raw_data, current_section.virtual_address + concrete_headers.optional_header.image_base),
.raw_base_addr = static_cast<std::uintptr_t>(current_section.ptr_raw_data),
.data = std::move(section_data)}; .data = std::move(section_data)};
} }
return std::nullopt; return std::nullopt;
@@ -304,46 +305,71 @@ namespace
namespace omath namespace omath
{ {
std::optional<std::uintptr_t> PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address, std::optional<std::uintptr_t>
const std::string_view& pattern) PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{ {
const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address); const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address);
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
if (!base_address) if (!base_address)
return std::nullopt; return std::nullopt;
auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address); const auto* dos_header = static_cast<const DosHeader*>(module_base_address);
if (!nt_header_variant) if (invalid_dos_header_file(*dos_header)) [[unlikely]]
return std::nullopt;
const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
if (!nt_header_variant) [[unlikely]]
return std::nullopt; return std::nullopt;
return std::visit( return std::visit(
[base_address, &pattern](const auto& nt_header) -> std::optional<std::uintptr_t> [base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name,
&pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
{ {
// Define .text segment as scan area constexpr std::size_t signature_size = sizeof(nt_header.signature);
const auto start = nt_header.optional_header.base_of_code; const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
const auto scan_size = nt_header.optional_header.size_code; + sizeof(FileHeader) + nt_header.file_header.size_optional_header;
const auto scan_range = std::span{reinterpret_cast<std::byte*>(base_address) + start, scan_size}; const auto* section_table = reinterpret_cast<const SectionHeader*>(base_bytes + section_table_off);
// ReSharper disable once CppTooWideScopeInitStatement for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
const auto result = PatternScanner::scan_for_pattern(scan_range, pattern); {
const auto* section = section_table + i;
if (std::string_view{section->name} != target_section_name || section->size_raw_data == 0)
continue;
const auto section_size = section->virtual_size != 0
? static_cast<std::size_t>(section->virtual_size)
: static_cast<std::size_t>(section->size_raw_data);
const auto* section_begin =
reinterpret_cast<std::byte*>(base_address + section->virtual_address);
const auto scan_range = std::span{section_begin, section_size};
const auto result =
PatternScanner::scan_for_pattern(scan_range.begin(), scan_range.end(), pattern);
if (result != scan_range.end()) if (result != scan_range.end())
return reinterpret_cast<std::uintptr_t>(&*result); return reinterpret_cast<std::uintptr_t>(&*result);
}
return std::nullopt; return std::nullopt;
}, },
nt_header_variant.value()); nt_header_variant.value());
} }
std::optional<PeSectionScanResult> std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern, const std::string_view& pattern,
const std::string_view& target_section_name) const std::string_view& target_section_name)
{ {
const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name); const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name);
if (!pe_section.has_value()) if (!pe_section.has_value()) [[unlikely]]
return std::nullopt; return std::nullopt;
const auto scan_result = const auto scan_result =
@@ -353,7 +379,7 @@ namespace omath
return std::nullopt; return std::nullopt;
const auto offset = std::distance(pe_section->data.begin(), scan_result); const auto offset = std::distance(pe_section->data.begin(), scan_result);
return PeSectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr, return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr, .raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }

View File

@@ -0,0 +1,17 @@
//
// Created by Vladislav on 30.12.2025.
//
// /Users/vladislav/Downloads/valencia
#include <gtest/gtest.h>
#include <omath/utility/elf_pattern_scan.hpp>
#include <print>
TEST(unit_test_elf_pattern_scan_file, ScanMissingPattern)
{
//FIXME: Implement normal tests :)
//constexpr std::string_view path = "/Users/vladislav/Downloads/crackme";
//const auto res = omath::ElfPatternScanner::scan_for_pattern_in_file(path, "F3 0F 1E FA 55 48 89 E5 B8 00 00 00 00", ".text");
//EXPECT_TRUE(res.has_value());
//std::println("In virtual mem: 0x{:x}", res->virtual_base_addr+res->target_offset);
}

View File

@@ -11,32 +11,60 @@ static std::vector<std::uint8_t> make_fake_module(std::uint32_t base_of_code,
std::uint32_t size_code, std::uint32_t size_code,
const std::vector<std::uint8_t>& code_bytes) const std::vector<std::uint8_t>& code_bytes)
{ {
// Constants
constexpr std::uint32_t e_lfanew = 0x80; constexpr std::uint32_t e_lfanew = 0x80;
const std::uint32_t total_size = e_lfanew + 0x200 + size_code + 0x100; constexpr std::uint32_t nt_sig = 0x4550; // "PE\0\0"
constexpr std::uint16_t opt_magic = 0x020B; // PE32+
constexpr std::uint16_t num_sections = 1;
constexpr std::uint16_t opt_hdr_size = 0xF0; // Standard PE32+ optional header size
constexpr std::uint32_t section_table_off = e_lfanew + 4 + 20 + opt_hdr_size; // sig(4) + FileHdr(20)
constexpr std::uint32_t section_header_size = 40;
constexpr std::uint32_t text_characteristics = 0x60000020; // code | execute | read
const std::uint32_t headers_end = section_table_off + section_header_size;
const std::uint32_t code_end = base_of_code + size_code;
const std::uint32_t total_size = std::max(headers_end, code_end) + 0x100; // leave some padding
std::vector<std::uint8_t> buf(total_size, 0); std::vector<std::uint8_t> buf(total_size, 0);
// DOS header: e_magic at 0, e_lfanew at offset 0x3C auto w16 = [&](std::size_t off, std::uint16_t v) { std::memcpy(buf.data() + off, &v, sizeof(v)); };
buf[0] = 0x4D; buf[1] = 0x5A; // 'M' 'Z' (little-endian 0x5A4D) auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, sizeof(v)); };
constexpr std::uint32_t le = e_lfanew; auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, sizeof(v)); };
std::memcpy(buf.data() + 0x3C, &le, sizeof(le));
// NT signature at e_lfanew // DOS header
constexpr std::uint32_t nt_sig = 0x4550; // 'PE\0\0' w16(0x00, 0x5A4D); // e_magic "MZ"
std::memcpy(buf.data() + e_lfanew, &nt_sig, sizeof(nt_sig)); w32(0x3C, e_lfanew); // e_lfanew
// FileHeader is 20 bytes: we only need to ensure its size is present; leave zeros // NT signature
w32(e_lfanew, nt_sig);
// OptionalHeader magic (optional header begins at e_lfanew + 4 + sizeof(FileHeader) == e_lfanew + 24) // FileHeader (starts at e_lfanew + 4)
constexpr std::uint16_t opt_magic = 0x020B; // x64 const std::size_t fh_off = e_lfanew + 4;
std::memcpy(buf.data() + e_lfanew + 24, &opt_magic, sizeof(opt_magic)); w16(fh_off + 2, num_sections); // NumberOfSections
w16(fh_off + 16, opt_hdr_size); // SizeOfOptionalHeader
// size_code is at offset 4 inside OptionalHeader -> absolute e_lfanew + 28 // OptionalHeader PE32+ (starts at e_lfanew + 4 + 20)
std::memcpy(buf.data() + e_lfanew + 28, &size_code, sizeof(size_code)); const std::size_t opt_off = fh_off + 20;
w16(opt_off + 0, opt_magic); // Magic
w32(opt_off + 4, size_code); // SizeOfCode
w32(opt_off + 16, 0); // AddressOfEntryPoint (unused in test)
w32(opt_off + 20, base_of_code); // BaseOfCode
w64(opt_off + 24, 0); // ImageBase
w32(opt_off + 32, 0x1000); // SectionAlignment
w32(opt_off + 36, 0x200); // FileAlignment
w32(opt_off + 56, code_end); // SizeOfImage (simple upper bound)
w32(opt_off + 60, headers_end); // SizeOfHeaders
w32(opt_off + 108, 0); // NumberOfRvaAndSizes (0 directories)
// base_of_code is at offset 20 inside OptionalHeader -> absolute e_lfanew + 44 // Section header (.text) at section_table_off
std::memcpy(buf.data() + e_lfanew + 44, &base_of_code, sizeof(base_of_code)); const std::size_t sh_off = section_table_off;
std::memcpy(buf.data() + sh_off + 0, ".text", 5); // Name[8]
w32(sh_off + 8, size_code); // VirtualSize
w32(sh_off + 12, base_of_code); // VirtualAddress
w32(sh_off + 16, size_code); // SizeOfRawData
w32(sh_off + 20, base_of_code); // PointerToRawData
w32(sh_off + 36, text_characteristics); // Characteristics
// place code bytes at offset base_of_code // Place code bytes at BaseOfCode
if (base_of_code + code_bytes.size() <= buf.size()) if (base_of_code + code_bytes.size() <= buf.size())
std::memcpy(buf.data() + base_of_code, code_bytes.data(), code_bytes.size()); std::memcpy(buf.data() + base_of_code, code_bytes.data(), code_bytes.size());
@@ -59,11 +87,12 @@ TEST(PePatternScanLoaded, FindsPatternAtBase)
TEST(PePatternScanLoaded, WildcardMatches) TEST(PePatternScanLoaded, WildcardMatches)
{ {
const std::vector<std::uint8_t> code = {0xDE, 0xAD, 0xBE, 0xEF}; const std::vector<std::uint8_t> code = {0xDE, 0xAD, 0xBE, 0xEF};
auto buf = make_fake_module(0x300, static_cast<std::uint32_t>(code.size()), code); constexpr std::uint32_t base_of_code = 0x300;
auto buf = make_fake_module(base_of_code, static_cast<std::uint32_t>(code.size()), code);
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE"); const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE", ".text");
ASSERT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
const uintptr_t addr = res.value(); const uintptr_t addr = res.value();
const uintptr_t base = reinterpret_cast<uintptr_t>(buf.data()); const uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
EXPECT_EQ(addr - base, 0x300u); EXPECT_EQ(addr - base, base_of_code);
} }

View File

@@ -163,6 +163,23 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
std::uint32_t size_headers; /* keep space */ std::uint32_t size_headers; /* keep space */
std::uint8_t pad[200]; std::uint8_t pad[200];
}; };
struct SectionHeader
{
char name[8];
union
{
std::uint32_t physical_address;
std::uint32_t virtual_size;
};
std::uint32_t virtual_address;
std::uint32_t size_raw_data;
std::uint32_t ptr_raw_data;
std::uint32_t ptr_relocs;
std::uint32_t ptr_line_numbers;
std::uint32_t num_relocs;
std::uint32_t num_line_numbers;
std::uint32_t characteristics;
};
struct ImageNtHeadersX64 struct ImageNtHeadersX64
{ {
std::uint32_t signature; std::uint32_t signature;
@@ -176,22 +193,41 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
const std::uint32_t bufsize = 0x400 + size_code; const std::uint32_t bufsize = 0x400 + size_code;
std::vector<std::uint8_t> buf(bufsize, 0); std::vector<std::uint8_t> buf(bufsize, 0);
// DOS header // DOS header
const auto dos = reinterpret_cast<DosHeader*>(buf.data()); const auto dos = reinterpret_cast<DosHeader*>(buf.data());
dos->e_magic = 0x5A4D; dos->e_magic = 0x5A4D;
dos->e_lfanew = 0x80; dos->e_lfanew = 0x80;
// NT headers // NT headers
const auto nt = reinterpret_cast<ImageNtHeadersX64*>(buf.data() + dos->e_lfanew); const auto nt = reinterpret_cast<ImageNtHeadersX64*>(buf.data() + dos->e_lfanew);
nt->signature = 0x4550; // 'PE\0\0' nt->signature = 0x4550; // 'PE\0\0'
nt->file_header.machine = 0x8664; nt->file_header.machine = 0x8664;
nt->file_header.num_sections = 1; nt->file_header.num_sections = 1;
nt->file_header.size_optional_header = static_cast<std::uint16_t>(sizeof(OptionalHeaderX64));
nt->optional_header.magic = 0x020B; // x64 nt->optional_header.magic = 0x020B; // x64
nt->optional_header.base_of_code = base_of_code; nt->optional_header.base_of_code = base_of_code;
nt->optional_header.size_code = size_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<std::size_t>(dos->e_lfanew) + 4 + sizeof(FileHeader) + sizeof(OptionalHeaderX64);
nt->optional_header.size_headers = static_cast<std::uint32_t>(section_table_off + sizeof(SectionHeader));
// Section header (.text)
const auto sect = reinterpret_cast<SectionHeader*>(buf.data() + section_table_off);
std::memset(sect, 0, sizeof(SectionHeader));
std::memcpy(sect->name, ".text", 5);
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
// place code at base_of_code // place code at base_of_code
std::memcpy(buf.data() + base_of_code, pattern_bytes.data(), pattern_bytes.size()); 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"); const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD BE EF", ".text");
EXPECT_TRUE(res.has_value()); EXPECT_TRUE(res.has_value());
} }