gbe_fork/helpers/pe_helpers.cpp

485 lines
15 KiB
C++

#include "pe_helpers/pe_helpers.hpp"
#include "common_helpers/common_helpers.hpp"
#include <vector>
#include <utility>
#include <mutex>
#include <cwchar>
static inline bool is_hex(const char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
static inline uint8_t char_to_byte(const char c)
{
if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a') + 0xa;
if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A') + 0xa;
return (uint8_t)c;
}
PIMAGE_NT_HEADERS pe_helpers::get_nt_header(HMODULE hModule)
{
// https://dev.to/wireless90/validating-the-pe-signature-my-av-flagged-me-windows-pe-internals-2m5o/
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)(char*)hModule;
if (dosHeader->e_magic != 0x5A4D) { // "MZ"
return nullptr;
}
LONG newExeHeaderOffset = dosHeader->e_lfanew;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((char*)hModule + newExeHeaderOffset);
if (!ntHeader || ntHeader->Signature != 0x00004550) { // "PE\0\0"
return nullptr;
}
return ntHeader;
}
PIMAGE_FILE_HEADER pe_helpers::get_file_header(HMODULE hModule)
{
auto nt_header = get_nt_header(hModule);
return nt_header ? &nt_header->FileHeader : nullptr;
}
PIMAGE_OPTIONAL_HEADER pe_helpers::get_optional_header(HMODULE hModule)
{
auto nt_header = get_nt_header(hModule);
return nt_header ? &nt_header->OptionalHeader : nullptr;
}
uint8_t* pe_helpers::search_memory(uint8_t *mem, size_t size, const std::string &search_patt)
{
if (!mem || !size || search_patt.empty()) return nullptr;
if (search_patt.find_first_not_of(" \t", 0) == std::string::npos) {
return nullptr; // empty search patt
}
const uint8_t *end = mem + size;
for (uint8_t *base = mem; base < end; ++base)
{
// incremental offset after each byte match
size_t search_patt_offset = 0;
bool error = false;
for (const uint8_t *displacement = base; displacement < end; ++displacement)
{
uint8_t mask = 0xFF;
uint8_t s_byte = 0;
// skip spaces
search_patt_offset = search_patt.find_first_not_of(" \t", search_patt_offset);
if (search_patt_offset == std::string::npos) {
break;
}
const auto this_char = search_patt[search_patt_offset];
const auto next_char = (search_patt_offset + 1) < search_patt.size()
? search_patt[search_patt_offset + 1]
: '\0';
if (this_char == '?') {
if (next_char == '?' || // "??"
next_char == ' ' || // "? "
next_char == 't') { // "? "
mask = 0x00;
s_byte = 0;
} else if (is_hex(next_char)) { // "?c"
mask = 0x0F;
s_byte = char_to_byte(next_char);
} else { // unknown
return nullptr;
}
// skip
search_patt_offset += 2;
} else if (is_hex(this_char)) {
if (next_char == '?') { // "c?"
mask = 0xF0;
s_byte = char_to_byte(this_char) << 4;
} else if (is_hex(next_char)) { // "34"
mask = 0xFF;
s_byte = (char_to_byte(this_char) << 4) | char_to_byte(next_char);
} else { // unknown
return nullptr;
}
// skip
search_patt_offset += 2;
} else { // unknown
return nullptr;
}
if ((*displacement & mask) != (s_byte & mask)) {
error = true;
break;
}
}
if (!error && (search_patt_offset >= search_patt.size())) {
return base;
}
}
return nullptr;
}
bool pe_helpers::replace_memory(uint8_t *mem, size_t size, const std::string &replace_patt, HANDLE hProcess)
{
if (!mem || !size || replace_patt.empty()) return false;
size_t replace_patt_offset = replace_patt.find_first_not_of(" \t", 0);
if (replace_patt_offset == std::string::npos) {
return false; // empty patt
}
// mask - byte
std::vector<std::pair<uint8_t, uint8_t>> replace_bytes{};
for (;
replace_patt_offset < replace_patt.size();
replace_patt_offset = replace_patt.find_first_not_of(" \t", replace_patt_offset)) {
const auto this_char = replace_patt[replace_patt_offset];
const auto next_char = (replace_patt_offset + 1) < replace_patt.size()
? replace_patt[replace_patt_offset + 1]
: '\0';
if (this_char == '?') {
if (next_char == '?' || // "??"
next_char == ' ' || // "? "
next_char == 't') { // "? "
replace_bytes.push_back({
0x00,
0,
});
} else if (is_hex(next_char)) { // "?c"
replace_bytes.push_back({
0x0F,
char_to_byte(next_char),
});
} else { // unknown
return false;
}
// skip
replace_patt_offset += 2;
} else if (is_hex(this_char)) {
if (next_char == '?') { // "c?"
replace_bytes.push_back({
0xF0,
char_to_byte(this_char) << 4,
});
} else if (is_hex(next_char)) { // "34"
replace_bytes.push_back({
0xFF,
(char_to_byte(this_char) << 4) | char_to_byte(next_char),
});
} else { // unknown
return false;
}
// skip
replace_patt_offset += 2;
} else { // unknown
return false;
}
}
// remove trailing "??"
// while last element == "??"
while (replace_bytes.size() &&
replace_bytes.back().first == 0x00)
{
replace_bytes.pop_back();
}
if (replace_bytes.empty() || replace_bytes.size() > size) return false;
// change protection
DWORD current_protection = 0;
if (!VirtualProtectEx(hProcess, mem, replace_bytes.size(), PAGE_READWRITE, &current_protection)) {
return false;
}
for (auto &rp : replace_bytes) {
if (rp.first == 0x00) {
++mem;
continue;
}
const uint8_t b_mem = (uint8_t)(*mem & (uint8_t)~rp.first);
const uint8_t b_replace = (uint8_t)(rp.second & rp.first);
const uint8_t new_b_mem = b_mem | b_replace;
*mem = b_mem | b_replace;
++mem;
}
// restore protection
if (!VirtualProtectEx(hProcess, mem, replace_bytes.size(), current_protection, &current_protection)) {
return false;
}
return true;
}
// https://learn.microsoft.com/en-us/windows/win32/debug/retrieving-the-last-error-code
std::string pe_helpers::get_err_string(DWORD code)
{
std::string err_str(8192, '\0');
DWORD msg_chars = FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
(LPSTR)&err_str[0],
static_cast<DWORD>(err_str.size()),
NULL);
if (!msg_chars) return std::string();
err_str = err_str.substr(0, msg_chars);
return err_str;
}
bool pe_helpers::is_module_64(HMODULE hModule)
{
auto file_header = get_file_header(hModule);
return file_header ? (file_header->Machine == IMAGE_FILE_MACHINE_AMD64) : false;
}
bool pe_helpers::is_module_32(HMODULE hModule)
{
auto file_header = get_file_header(hModule);
return file_header ? (file_header->Machine == IMAGE_FILE_MACHINE_I386) : false;
}
pe_helpers::SectionHeadersResult pe_helpers::get_section_headers(HMODULE hModule)
{
PIMAGE_NT_HEADERS ntHeader = get_nt_header(hModule);
PIMAGE_OPTIONAL_HEADER optionalHeader = &ntHeader->OptionalHeader;
PIMAGE_FILE_HEADER fileHeader = get_file_header(hModule);
WORD optionalHeadrSize = fileHeader->SizeOfOptionalHeader;
struct SectionHeadersResult res {};
res.count = fileHeader->NumberOfSections;
res.ptr = fileHeader->NumberOfSections
? (PIMAGE_SECTION_HEADER)((char *)optionalHeader + optionalHeadrSize)
: nullptr;
return res;
}
PIMAGE_SECTION_HEADER pe_helpers::get_section_header_with_name(HMODULE hModule, const char *name)
{
if (!name) return nullptr;
auto res = get_section_headers(hModule);
if (!res.count) return nullptr;
for (size_t i = 0; i < res.count; ++i) {
if (strncmp((const char *)res.ptr[i].Name, name, sizeof(res.ptr[i].Name)) == 0) {
return &res.ptr[i];
}
}
return nullptr;
}
DWORD pe_helpers::loadlib_remote(HANDLE hProcess, const std::string &fullpath, const char** err_reason)
{
const auto lib_fullpath = common_helpers::to_wstr(fullpath);
if (lib_fullpath.empty()) {
if (err_reason) {
*err_reason = "Failed to convert path to wide string";
}
return ERROR_INVALID_PARAMETER;
}
// create a remote page
const size_t lib_path_str_bytes = (lib_fullpath.size() + 1) * sizeof(lib_fullpath[0]);
LPVOID lib_remote_page = VirtualAllocEx(
hProcess,
NULL,
lib_path_str_bytes + sizeof(lib_fullpath[0]) * 2, // *2 just to be safe
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE
);
if (!lib_remote_page) {
if (err_reason) {
*err_reason = "Failed to remotely allocate page with VirtualAllocEx()";
}
return GetLastError();
}
SIZE_T bytes_written = 0;
BOOL written = WriteProcessMemory(
hProcess,
lib_remote_page,
(LPCVOID)&lib_fullpath[0],
lib_path_str_bytes,
&bytes_written
);
if (!written || (bytes_written < lib_path_str_bytes)) {
// cleanup allcoated page
VirtualFreeEx(
hProcess,
lib_remote_page,
0,
MEM_RELEASE
);
if (err_reason) {
*err_reason = "Failed to remotely write dll path with WriteProcessMemory()";
}
return GetLastError();
}
// call LoadLibraryW() and pass the dll fullpath
HANDLE remote_thread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)LoadLibraryW,
lib_remote_page,
0,
NULL
);
if (!remote_thread) {
// cleanup allcoated page
VirtualFreeEx(
hProcess,
lib_remote_page,
0,
MEM_RELEASE
);
if (err_reason) {
*err_reason = "Failed to create/run remote thread with CreateRemoteThread()";
}
return GetLastError();
}
// wait for DllMain
WaitForSingleObject(remote_thread, INFINITE);
CloseHandle(remote_thread);
// cleanup allcoated page
VirtualFreeEx(
hProcess,
lib_remote_page,
0,
MEM_RELEASE
);
return ERROR_SUCCESS;
}
size_t pe_helpers::get_pe_size(HMODULE hModule)
{
// https://stackoverflow.com/a/34695773
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
// "The PE file header consists of a Microsoft MS-DOS stub, the PE signature, the COFF file header, and an optional header"
// "The combined size of an MS-DOS stub, PE header, and section headers rounded up to a multiple of FileAlignment."
size_t size = get_optional_header(hModule)->SizeOfHeaders;
SectionHeadersResult headers = get_section_headers(hModule);
for (size_t i = 0; i < headers.count; ++i) {
size += headers.ptr[i].SizeOfRawData;
}
return size;
}
static std::string path_a{};
static std::string modulename_a{};
const std::string& pe_helpers::get_current_exe_path()
{
static std::recursive_mutex path_mtx{};
if (path_a.empty()) {
std::lock_guard lk(path_mtx);
if (path_a.empty()) {
DWORD err = GetLastError();
std::wstring path_w(8192, '\0');
DWORD read_chars = GetModuleFileNameW(GetModuleHandleW(nullptr), &path_w[0], (DWORD)path_w.size());
if (read_chars >= path_w.size()) {
path_w.resize(read_chars);
read_chars = GetModuleFileNameW(GetModuleHandleW(nullptr), &path_w[0], (DWORD)path_w.size());
}
if ((read_chars < path_w.size()) && path_w[0]) {
auto modulename_idx = path_w.find_last_of(L"\\/") + 1;
modulename_a = common_helpers::to_str(path_w.substr(modulename_idx, read_chars - modulename_idx));
path_a = common_helpers::to_str(path_w.substr(0, modulename_idx));
}
SetLastError(err);
}
}
return path_a;
}
const std::string& pe_helpers::get_current_exe_name()
{
if (modulename_a.empty()) {
get_current_exe_path();
}
return modulename_a;
}
bool pe_helpers::ends_with_i(PUNICODE_STRING target, std::wstring_view query)
{
return common_helpers::ends_with_i(
std::wstring_view( target->Buffer, target->Length / sizeof(target->Buffer[0]) ),
query
);
}
MEMORY_BASIC_INFORMATION pe_helpers::get_mem_page_details(const void* mem)
{
MEMORY_BASIC_INFORMATION mbi{};
if (VirtualQuery(mem, &mbi, sizeof(mbi))) {
return mbi;
} else {
return {};
}
}
size_t pe_helpers::get_current_exe_mem_size()
{
auto hmod = GetModuleHandleW(NULL);
size_t size = 0;
{
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQuery((LPVOID)hmod, &mbi, sizeof(mbi))) {
return 0;
}
size = mbi.RegionSize; // PE header
}
auto sections = get_section_headers(hmod);
if (!sections.count) {
return 0;
}
for (size_t i = 0; i < sections.count; ++i) {
auto section = sections.ptr[i];
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQuery((LPCVOID)((uint8_t *)hmod + section.VirtualAddress), &mbi, sizeof(mbi))) {
return 0;
}
size = mbi.RegionSize; // actual section size in mem
}
return size;
}