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:
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
|
||||
{
|
||||
std::uint64_t virtual_base_addr;
|
||||
std::uint64_t raw_base_addr;
|
||||
std::uintptr_t virtual_base_addr;
|
||||
std::uintptr_t raw_base_addr;
|
||||
std::vector<std::byte> data;
|
||||
};
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace
|
||||
|
||||
const auto nt_headers = get_nt_header_from_file(file, dos_header);
|
||||
|
||||
if (!nt_headers)
|
||||
if (!nt_headers) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
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.read(reinterpret_cast<char*>(section_data.data()),
|
||||
static_cast<std::streamsize>(section_data.size()));
|
||||
return ExtractedSection{.virtual_base_addr = current_section.virtual_address
|
||||
+ concrete_headers.optional_header.image_base,
|
||||
.raw_base_addr = current_section.ptr_raw_data,
|
||||
.data = std::move(section_data)};
|
||||
return ExtractedSection{
|
||||
.virtual_base_addr = static_cast<std::uintptr_t>(
|
||||
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)};
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
@@ -304,46 +305,71 @@ namespace
|
||||
namespace omath
|
||||
{
|
||||
|
||||
std::optional<std::uintptr_t> PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
|
||||
const std::string_view& pattern)
|
||||
std::optional<std::uintptr_t>
|
||||
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_bytes = static_cast<const std::byte*>(module_base_address);
|
||||
|
||||
if (!base_address)
|
||||
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::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
|
||||
const auto start = nt_header.optional_header.base_of_code;
|
||||
const auto scan_size = nt_header.optional_header.size_code;
|
||||
constexpr std::size_t signature_size = sizeof(nt_header.signature);
|
||||
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
|
||||
+ 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
|
||||
const auto result = PatternScanner::scan_for_pattern(scan_range, pattern);
|
||||
for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
|
||||
{
|
||||
const auto* section = section_table + i;
|
||||
|
||||
if (result != scan_range.end())
|
||||
return reinterpret_cast<std::uintptr_t>(&*result);
|
||||
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())
|
||||
return reinterpret_cast<std::uintptr_t>(&*result);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
},
|
||||
nt_header_variant.value());
|
||||
}
|
||||
std::optional<PeSectionScanResult>
|
||||
std::optional<SectionScanResult>
|
||||
PePatternScanner::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 = 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;
|
||||
|
||||
const auto scan_result =
|
||||
@@ -353,8 +379,8 @@ namespace omath
|
||||
return std::nullopt;
|
||||
const auto offset = std::distance(pe_section->data.begin(), scan_result);
|
||||
|
||||
return PeSectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
|
||||
.raw_base_addr = pe_section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
|
||||
.raw_base_addr = pe_section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
}
|
||||
} // namespace omath
|
||||
Reference in New Issue
Block a user