mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-12 22:53:27 +00:00
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:
25
include/omath/utility/elf_pattern_scan.hpp
Normal file
25
include/omath/utility/elf_pattern_scan.hpp
Normal 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
|
||||||
@@ -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");
|
||||||
};
|
};
|
||||||
|
|||||||
16
include/omath/utility/section_scan_result.hpp
Normal file
16
include/omath/utility/section_scan_result.hpp
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
325
source/utility/elf_pattern_scan.cpp
Normal file
325
source/utility/elf_pattern_scan.cpp
Normal 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*>(§ion_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*>(¤t_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
|
||||||
@@ -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,10 +290,11 @@ 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),
|
||||||
.data = std::move(section_data)};
|
.raw_base_addr = static_cast<std::uintptr_t>(current_section.ptr_raw_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 (result != scan_range.end())
|
if (std::string_view{section->name} != target_section_name || section->size_raw_data == 0)
|
||||||
return reinterpret_cast<std::uintptr_t>(&*result);
|
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())
|
||||||
|
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,8 +379,8 @@ 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};
|
||||||
}
|
}
|
||||||
} // namespace omath
|
} // namespace omath
|
||||||
17
tests/general/unit_test_elf_scanner.cpp
Normal file
17
tests/general/unit_test_elf_scanner.cpp
Normal 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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user