diff --git a/pe_helpers/pe_helpers.cpp b/pe_helpers/pe_helpers.cpp new file mode 100644 index 00000000..ce49b3ba --- /dev/null +++ b/pe_helpers/pe_helpers.cpp @@ -0,0 +1,363 @@ +#include "pe_helpers/pe_helpers.hpp" +#include +#include + +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; +} + +static inline PIMAGE_NT_HEADERS 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; + LONG newExeHeaderOffset = dosHeader->e_lfanew; + + return (PIMAGE_NT_HEADERS)((char*)hModule + newExeHeaderOffset); +} + +static inline PIMAGE_FILE_HEADER get_file_header(HMODULE hModule) +{ + return &get_nt_header(hModule)->FileHeader; +} + +static inline PIMAGE_OPTIONAL_HEADER get_optional_header(HMODULE hModule) +{ + return &get_nt_header(hModule)->OptionalHeader; +} + +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> 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, ¤t_protection)) { + return false; + } + + for (auto &rp : replace_bytes) { + if (rp.first == 0x00) 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, ¤t_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], + 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) +{ + return !!(get_file_header(hModule)->Machine == IMAGE_FILE_MACHINE_AMD64); +} + +bool pe_helpers::is_module_32(HMODULE hModule) +{ + return !!(get_file_header(hModule)->Machine == IMAGE_FILE_MACHINE_I386); +} + +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::wstring &lib_fullpath, const char** err_reason) { + + // create a remote page + const size_t lib_path_str_bytes = lib_fullpath.size() * 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 LoadLibraryA() and pass "launc.dll" + 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(); + } + + WaitForSingleObject(remote_thread, INFINITE); + + // 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; +} diff --git a/pe_helpers/pe_helpers/pe_helpers.hpp b/pe_helpers/pe_helpers/pe_helpers.hpp new file mode 100644 index 00000000..b556134c --- /dev/null +++ b/pe_helpers/pe_helpers/pe_helpers.hpp @@ -0,0 +1,36 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +namespace pe_helpers +{ + +typedef struct SectionHeadersResult +{ + WORD count; + PIMAGE_SECTION_HEADER ptr; +} SectionHeadersResult_t; + + +uint8_t* search_memory(uint8_t *mem, size_t size, const std::string &search_patt); + +bool replace_memory(uint8_t *mem, size_t size, const std::string &replace_patt, HANDLE hProcess); + +std::string get_err_string(DWORD code); + +bool is_module_64(HMODULE hModule); + +bool is_module_32(HMODULE hModule); + +SectionHeadersResult get_section_headers(HMODULE hModule); + +PIMAGE_SECTION_HEADER get_section_header_with_name(HMODULE hModule, const char* name); + +DWORD loadlib_remote(HANDLE hProcess, const std::wstring &lib_fullpath, const char** err_reason = nullptr); + +size_t get_pe_size(HMODULE hModule); + +}