From 3f1a54be56648cb675cd99f20ccd9848df9db47b Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 24 Jan 2026 01:23:39 -0500 Subject: [PATCH 1/3] elf changes --- lin_key.txt | 303 +++++++++++++++++++++++++++++++++++++++ src/elf/elf_image.cpp | 213 +++++++++++++++++++++++++++ src/elf/elf_patterns.cpp | 205 ++++++++++++++++++++++++++ src/elf/elf_scanner.cpp | 269 ++++++++++++++++++++++++++++++++++ src/pe/pe_patterns.cpp | 171 ++++++++++++++++++++++ win_key.txt | 40 ++++++ 6 files changed, 1201 insertions(+) create mode 100644 lin_key.txt create mode 100644 src/elf/elf_image.cpp create mode 100644 src/elf/elf_patterns.cpp create mode 100644 src/elf/elf_scanner.cpp create mode 100644 win_key.txt diff --git a/lin_key.txt b/lin_key.txt new file mode 100644 index 0000000..04bb874 --- /dev/null +++ b/lin_key.txt @@ -0,0 +1,303 @@ +[CFG] Debug logging enabled +[IO] Detected ELF file +[IO] File size: 70158584 bytes +[IO] Mapped view @ 0x7fceda200000 size=70158584 bytes +[ELF] e_type=2 e_shoff=70156472 e_shnum=33 e_shstrndx=32 +[ELF] String table at offset 70156189 size 276 +[ELF] Section: .interp addr=0x4002e0 offset=0x2e0 size=0x1c +[ELF] Section: .note.ABI-tag addr=0x4002fc offset=0x2fc size=0x20 +[ELF] Section: .hash addr=0x400320 offset=0x320 size=0x9f0 +[ELF] Section: .gnu.hash addr=0x400d10 offset=0xd10 size=0x268 +[ELF] Section: .dynsym addr=0x400f78 offset=0xf78 size=0x22c8 +[ELF] Section: .dynstr addr=0x403240 offset=0x3240 size=0xee5 +[ELF] Section: .gnu.version addr=0x404126 offset=0x4126 size=0x2e6 +[ELF] Section: .gnu.version_r addr=0x404410 offset=0x4410 size=0x1e0 +[ELF] Section: .rela.dyn addr=0x4045f0 offset=0x45f0 size=0x9d8 +[ELF] Section: .rela.plt addr=0x404fc8 offset=0x4fc8 size=0x1950 +[ELF] Section: .init addr=0x407000 offset=0x7000 size=0x17 +[ELF] Section: .plt addr=0x407020 offset=0x7020 size=0x10f0 +[ELF] Section: .plt.got addr=0x408110 offset=0x8110 size=0x1c0 +[ELF] Section: .text addr=0x408300 offset=0x8300 size=0x344f18f +[ELF] Section: .fini addr=0x3857490 offset=0x3457490 size=0x9 +[ELF] Section: .rodata addr=0x3858000 offset=0x3458000 size=0x8a7f70 +[ELF] Section: pck addr=0x40fff70 offset=0x3cfff70 size=0x8 +[ELF] Section: .eh_frame_hdr addr=0x40fff78 offset=0x3cfff78 size=0xc7ed4 +[ELF] Section: .eh_frame addr=0x41c7e50 offset=0x3dc7e50 size=0x4fdb98 +[ELF] Section: .gcc_except_table addr=0x46c59e8 offset=0x42c59e8 size=0x6e31 +[ELF] Section: .tdata addr=0x46cd490 offset=0x42cd490 size=0xe0 +[ELF] Section: .tbss addr=0x46cd570 offset=0x42cd570 size=0xa08 +[ELF] Section: .init_array addr=0x46cd570 offset=0x42cd570 size=0x58 +[ELF] Section: .fini_array addr=0x46cd5c8 offset=0x42cd5c8 size=0x10 +[ELF] Section: .data.rel.ro addr=0x46cd5e0 offset=0x42cd5e0 size=0x13438 +[ELF] Section: .dynamic addr=0x46e0a18 offset=0x42e0a18 size=0x230 +[ELF] Section: .got addr=0x46e0c48 offset=0x42e0c48 size=0x3a0 +[ELF] Section: .got.plt addr=0x46e0fe8 offset=0x42e0fe8 size=0x888 +[ELF] Section: .data addr=0x46e1880 offset=0x42e1880 size=0x66c0 +[ELF] Section: .bss addr=0x46e7f80 offset=0x42e7f40 size=0x1de148 +[ELF] Section: .comment addr=0x0 offset=0x42e7f40 size=0x5d +[ELF] Section: .shstrtab addr=0x0 offset=0x42e7f9d size=0x114 +[ELF] BaseAddress=0x400000 +[ELF] Type=ET_EXEC +[ELF] Section count: 32 +[SECT] .text VA=0x408300 size=0x344f18f +[SECT] rodata (.rodata) VA=0x3858000 size=0x8a7f70 +[SECT] .data VA=0x46e1880 size=0x66c0 +[GodotVer] Scanning .rodata for 'Godot Engine' (9076592 bytes) +[GodotVer] Occurrence 1: Godot Engine +[GodotVer] Occurrence 2: Godot Engine project +[GodotVer] Occurrence 3: Godot Engine contributors. (c) 2007-present Juan Linietsky, Ariel Manzur. +[GodotVer] Occurrence 4: Godot Engine +[GodotVer] Occurrence 5: Godot Engine/1.0 UPnP/1.1 MiniUPnPc/2.3.3 +Content-Length: %d +Content-Type: text/xml; charset="utf-8" +SOAPAction: "%s" +Connection: close + + +[GodotVer] Occurrence 6: Godot Engine Project +[GodotVer] Occurrence 7: Godot Engine 4.5.1.stable's CanvasItemMaterial. + + +[GodotVer] Occurrence 8: Godot Engine 4.5.1.stable's %s. + + +[GodotVer] Occurrence 9: Godot Engine 4.5.1.stable's ParticleProcessMaterial. + + +[GodotVer] Occurrence 10: Godot Engine 4.5.1.stable's FogMaterial. + +shader_type fog; + +uniform float density : hint_range(0, 1, 0.0001) = 1.0; +uniform vec4 albedo : source_color = vec4(1.0); +uniform vec4 emission : source_color = vec4(0, 0, 0, 1); +uniform float height_falloff = 0.0; +uniform float edge_fade = 0.1; +uniform sampler3D density_texture: hint_default_white; + + +void fog() { + DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0); + DENSITY *= texture(density_texture, UVW).r; + DENSITY *= pow(clamp(-2.0 * SDF / min(min(SIZE.x, SIZE.y), SIZE.z), 0.0, 1.0), edge_fade); + ALBEDO = albedo.rgb; + EMISSION = emission.rgb; +} + +[GodotVer] Occurrence 11: Godot Engine 4.5.1.stable's PanoramaSkyMaterial. + +shader_type sky; + +uniform sampler2D source_panorama : %s, source_color, hint_default_black; +uniform float exposure : hint_range(0, 128) = 1.0; + +void sky() { + COLOR = texture(source_panorama, SKY_COORDS).rgb * exposure; +} + +[GodotVer] Occurrence 12: Godot Engine 4.5.1.stable's ProceduralSkyMaterial. + +shader_type sky; +%s + +uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0); +uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); +uniform float inv_sky_curve : hint_range(1, 100) = 4.0; +uniform vec4 ground_bottom_color : source_color = vec4(0.2, 0.169, 0.133, 1.0); +uniform vec4 ground_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); +uniform float inv_ground_curve : hint_range(1, 100) = 30.0; +uniform float sun_angle_max = 0.877; +uniform float inv_sun_curve : hint_range(1, 100) = 22.78; +uniform float exposure : hint_range(0, 128) = 1.0; + +uniform sampler2D sky_cover : filter_linear, source_color, hint_default_black; +uniform vec4 sky_cover_modulate : source_color = vec4(1.0, 1.0, 1.0, 1.0); + +void sky() { + float v_angle = clamp(EYEDIR.y, -1.0, 1.0); + vec3 sky = mix(sky_top_color.rgb, sky_horizon_color.rgb, clamp(pow(1.0 - v_angle, inv_sky_curve), 0.0, 1.0)); + + if (LIGHT0_ENABLED) { + float sun_angle = dot(LIGHT0_DIRECTION, EYEDIR); + float sun_size = cos(LIGHT0_SIZE); + if (sun_angle > sun_size) { + sky = LIGHT0_COLOR * LIGHT0_ENERGY; + } else if (sun_angle > sun_angle_max) { + float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); + sky = mix(sky, LIGHT0_COLOR * LIGHT0_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT1_ENABLED) { + float sun_angle = dot(LIGHT1_DIRECTION, EYEDIR); + float sun_size = cos(LIGHT1_SIZE); + if (sun_angle > sun_size) { + sky = LIGHT1_COLOR * LIGHT1_ENERGY; + } else if (sun_angle > sun_angle_max) { + float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); + sky = mix(sky, LIGHT1_COLOR * LIGHT1_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT2_ENABLED) { + float sun_angle = dot(LIGHT2_DIRECTION, EYEDIR); + float sun_size = cos(LIGHT2_SIZE); + if (sun_angle > sun_size) { + sky = LIGHT2_COLOR * LIGHT2_ENERGY; + } else if (sun_angle > sun_angle_max) { + float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); + sky = mix(sky, LIGHT2_COLOR * LIGHT2_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT3_ENABLED) { + float sun_angle = dot(LIGHT3_DIRECTION, EYEDIR); + float sun_size = cos(LIGHT3_SIZE); + if (sun_angle > sun_size) { + sky = LIGHT3_COLOR * LIGHT3_ENERGY; + } else if (sun_angle > sun_angle_max) { + float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); + sky = mix(sky, LIGHT3_COLOR * LIGHT3_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); + } + } + + %s + %s + vec3 ground = mix(ground_bottom_color.rgb, ground_horizon_color.rgb, clamp(pow(1.0 + v_angle, inv_ground_curve), 0.0, 1.0)); + + COLOR = mix(ground, sky, step(0.0, EYEDIR.y)) * exposure; +} + +[GodotVer] Occurrence 13: Godot Engine 4.5.1.stable's PhysicalSkyMaterial. + +shader_type sky; +%s + +uniform float rayleigh : hint_range(0, 64) = 2.0; +uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0); +uniform float mie : hint_range(0, 1) = 0.005; +uniform float mie_eccentricity : hint_range(-1, 1) = 0.8; +uniform vec4 mie_color : source_color = vec4(0.69, 0.729, 0.812, 1.0); + +uniform float turbidity : hint_range(0, 1000) = 10.0; +uniform float sun_disk_scale : hint_range(0, 360) = 1.0; +uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0); +uniform float exposure : hint_range(0, 128) = 1.0; + +uniform sampler2D night_sky : filter_linear, source_color, hint_default_black; + +const vec3 UP = vec3( 0.0, 1.0, 0.0 ); + +// Optical length at zenith for molecules. +const float rayleigh_zenith_size = 8.4e3; +const float mie_zenith_size = 1.25e3; + +float henyey_greenstein(float cos_theta, float g) { + const float k = 0.0795774715459; + return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); +} + +void sky() { + if (LIGHT0_ENABLED) { + float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 ); + float sun_energy = max(0.0, 0.757 * zenith_angle) * LIGHT0_ENERGY; + float sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0); + + // Rayleigh coefficients. + float rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) ); + vec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001; + // mie coefficients from Preetham + vec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434; + + // Optical length. + float zenith = max(0.0, dot(UP, EYEDIR)); + float optical_mass = 1.0 / (zenith + 0.15 * pow(3.885 + 54.5 * zenith, -1.253)); + float rayleigh_scatter = rayleigh_zenith_size * optical_mass; + float mie_scatter = mie_zenith_size * optical_mass; + + // Light extinction based on thickness of atmosphere. + vec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter)); + + // In scattering. + float cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION)); + + float rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0)); + vec3 betaRTheta = rayleigh_beta * rayleigh_phase; + + float mie_phase = henyey_greenstein(cos_theta, mie_eccentricity); + vec3 betaMTheta = mie_beta * mie_phase; + + vec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5)); + // Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js + Lin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0)); + + // Hack in the ground color. + Lin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR))); + + // Solar disk and out-scattering. + float sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale); + float sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale * 0.5); + float sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta); + vec3 L0 = (sun_energy * extinction) * sundisk * LIGHT0_COLOR; + %s + + vec3 color = Lin + L0; + COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade)))); + COLOR *= exposure; + } else { + // There is no sun, so display night_sky and nothing else. + %s + COLOR *= exposure; + } +} + +[GodotVer] Occurrence 14: Godot Engine contributors (see AUTHORS.md). +Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +[GodotVer] Occurrence 15: Godot Engine v4.5.1.stable.official +[GodotVer] Parsed version: 4.5.1.stable.official +Godot Engine version: 4.5.1.stable.official +[ANCHOR] Searching for: 'Can't open encrypted pack directory.' +[ANCHOR] Hits: 1 +[ANCHOR] VA=0x43d0270 +[find_lea] target_va=0x43d0270 +[find_lea] No matching LEA instruction found. +[LEA] Not found for anchor VA=0x43d0270 +[ANCHOR] Searching for: 'Can't open encrypted pack-referenced file '%s'.' +[ANCHOR] Hits: 1 +[ANCHOR] VA=0x43d06b8 +[find_lea] target_va=0x43d06b8 +[find_lea] No matching LEA instruction found. +[LEA] Not found for anchor VA=0x43d06b8 +[ANCHOR] Searching for: 'Condition "fae.is_null()" is true.' +[ANCHOR] Hits: 3 +[ANCHOR] VA=0x43d0298 +[find_lea] target_va=0x43d0298 +[find_lea] No matching LEA instruction found. +[LEA] Not found for anchor VA=0x43d0298 +[ANCHOR] VA=0x43d06e8 +[find_lea] target_va=0x43d06e8 +[find_lea] No matching LEA instruction found. +[LEA] Not found for anchor VA=0x43d06e8 +[ANCHOR] VA=0x43d33b0 +[find_lea] target_va=0x43d33b0 +[find_lea] No matching LEA instruction found. +[LEA] Not found for anchor VA=0x43d33b0 diff --git a/src/elf/elf_image.cpp b/src/elf/elf_image.cpp new file mode 100644 index 0000000..23dd86f --- /dev/null +++ b/src/elf/elf_image.cpp @@ -0,0 +1,213 @@ +#include "elf_image.h" +#include "common/timer.h" +#include "common/utils.h" + +#include +#include +#include + +namespace { + // Helper functions to read little-endian values from a buffer safely. + template + T read_from_buffer(std::span buffer, size_t offset) { + T value; + std::memcpy(&value, buffer.data() + offset, sizeof(T)); + return value; + } + + uint16_t read_u16(std::span b, size_t off) { return read_from_buffer(b, off); } + uint32_t read_u32(std::span b, size_t off) { return read_from_buffer(b, off); } + uint64_t read_u64(std::span b, size_t off) { return read_from_buffer(b, off); } + + // ELF constants + constexpr uint16_t ET_EXEC = 2; + constexpr uint16_t ET_DYN = 3; +} + +ELFImage::ELFImage(std::span data) : m_data(data) {} + +std::unique_ptr ELFImage::parse(std::span data) { + Timer timer("ELF parse"); + + // --- ELF Header Checks (64 bytes minimum) --- + if (data.size() < 64) return nullptr; + + // Check ELF magic: 0x7F 'E' 'L' 'F' + if (data[0] != 0x7F || data[1] != 'E' || data[2] != 'L' || data[3] != 'F') { + return nullptr; + } + + // Check for 64-bit class (EI_CLASS at offset 0x04) + if (data[4] != 2) { // ELFCLASS64 + DBG("[ELF] Not a 64-bit ELF file. Class: ", static_cast(data[4])); + return nullptr; + } + + // Check for little-endian (EI_DATA at offset 0x05) + if (data[5] != 1) { // ELFDATA2LSB + DBG("[ELF] Not a little-endian ELF file. Data encoding: ", static_cast(data[5])); + return nullptr; + } + + // Read ELF type (ET_EXEC or ET_DYN) at offset 0x10 + uint16_t e_type = read_u16(data, 0x10); + + // Read section header table offset (e_shoff) at offset 0x28 + uint64_t e_shoff = read_u64(data, 0x28); + + // Read section header entry size (e_shentsize) at offset 0x3A + uint16_t e_shentsize = read_u16(data, 0x3A); + + // Read number of section headers (e_shnum) at offset 0x3C + uint16_t e_shnum = read_u16(data, 0x3C); + + // Read section header string table index (e_shstrndx) at offset 0x3E + uint16_t e_shstrndx = read_u16(data, 0x3E); + + DBG("[ELF] e_type=", e_type, " e_shoff=", e_shoff, " e_shnum=", e_shnum, " e_shstrndx=", e_shstrndx); + + // Validate section header table + if (e_shoff + static_cast(e_shnum) * e_shentsize > data.size()) { + DBG("[ELF] Section header table extends beyond file size."); + return nullptr; + } + + // --- Create and Populate Image --- + auto img = std::unique_ptr(new ELFImage(data)); + img->m_is_elf64 = true; + + // Set ELF type + // Note: ELF section sh_addr values are already absolute VAs, so base_address + // should be 0. Unlike PE where sections have RVAs relative to ImageBase, + // ELF section headers contain the actual virtual addresses. + if (e_type == ET_EXEC) { + img->m_elf_type = ELFType::EXEC; + img->m_base_address = 0; // Section addresses are already absolute VAs + } else if (e_type == ET_DYN) { + img->m_elf_type = ELFType::DYN; + img->m_base_address = 0; // PIE - use section addresses directly + } else { + DBG("[ELF] Unknown e_type: ", e_type); + return nullptr; + } + + // Read the section header string table section header + if (e_shstrndx >= e_shnum) { + DBG("[ELF] Invalid e_shstrndx: ", e_shstrndx); + return nullptr; + } + + uint64_t shstrtab_shdr_offset = e_shoff + static_cast(e_shstrndx) * e_shentsize; + if (shstrtab_shdr_offset + 64 > data.size()) { + DBG("[ELF] String table section header out of bounds."); + return nullptr; + } + + // Read string table section offset and size + img->m_shstrtab_offset = read_u64(data, shstrtab_shdr_offset + 0x18); // sh_offset + img->m_shstrtab_size = read_u64(data, shstrtab_shdr_offset + 0x20); // sh_size + + if (img->m_shstrtab_offset + img->m_shstrtab_size > data.size()) { + DBG("[ELF] String table extends beyond file size."); + return nullptr; + } + + DBG("[ELF] String table at offset ", img->m_shstrtab_offset, " size ", img->m_shstrtab_size); + + // --- Parse Section Headers --- + for (uint16_t i = 0; i < e_shnum; ++i) { + uint64_t shdr_offset = e_shoff + static_cast(i) * e_shentsize; + if (shdr_offset + 64 > data.size()) break; + + uint32_t sh_name = read_u32(data, shdr_offset + 0x00); // Name offset in string table + uint64_t sh_addr = read_u64(data, shdr_offset + 0x10); // Virtual address + uint64_t sh_offset = read_u64(data, shdr_offset + 0x18); // File offset + uint64_t sh_size = read_u64(data, shdr_offset + 0x20); // Section size + + std::string section_name = img->read_section_name(sh_name); + + // Skip empty sections + if (section_name.empty() || sh_size == 0) continue; + + img->m_sections.emplace_back(Section{ + section_name, + static_cast(sh_addr), + static_cast(sh_size), + static_cast(sh_offset), + static_cast(sh_size) + }); + + DBG("[ELF] Section: ", section_name, " addr=0x", std::hex, sh_addr, " offset=0x", sh_offset, " size=0x", sh_size, std::dec); + } + + return img; +} + +std::string ELFImage::read_section_name(uint32_t name_offset) const { + if (name_offset >= m_shstrtab_size) { + return ""; + } + + // Find null terminator + uint64_t start = m_shstrtab_offset + name_offset; + uint64_t end = m_shstrtab_offset + m_shstrtab_size; + + const uint8_t* name_start = m_data.data() + start; + const uint8_t* name_end = m_data.data() + end; + const uint8_t* null_pos = static_cast(std::memchr(name_start, '\0', name_end - name_start)); + + if (null_pos) { + return std::string(reinterpret_cast(name_start), null_pos - name_start); + } + + return ""; +} + +const Section* ELFImage::get_section(const std::string& name) const { + auto it = std::find_if(m_sections.begin(), m_sections.end(), [&](const Section& s) { + return s.name == name; + }); + return (it != m_sections.end()) ? &(*it) : nullptr; +} + +int64_t ELFImage::va_to_file_offset(uint64_t va) const { + // For PIE (ET_DYN), va is already relative to base 0 + // For non-PIE (ET_EXEC), subtract the base address + uint64_t adjusted_va = va; + if (m_elf_type == ELFType::EXEC && va >= m_base_address) { + adjusted_va = va - m_base_address; + } + + for (const auto& s : m_sections) { + if (adjusted_va >= s.virtual_address && adjusted_va < s.virtual_address + s.virtual_size) { + uint32_t delta = adjusted_va - s.virtual_address; + int64_t offset = static_cast(s.file_offset) + delta; + + // Sanity check + if (offset >= 0 && static_cast(offset) < m_data.size()) { + return offset; + } + } + } + return -1; +} + +std::optional> ELFImage::read_va(uint64_t va, size_t size) const { + int64_t offset = va_to_file_offset(va); + if (offset < 0 || static_cast(offset) + size > m_data.size()) { + DBG("[READ] read_va(0x", std::hex, va, ", ", std::dec, size, ") failed: out of bounds."); + return std::nullopt; + } + + auto start_it = m_data.begin() + offset; + auto end_it = start_it + size; + return std::vector(start_it, end_it); +} + +std::optional ELFImage::read_u64_va(uint64_t va) const { + auto buf_opt = read_va(va, 8); + if (!buf_opt) { + return std::nullopt; + } + return read_u64(*buf_opt, 0); +} diff --git a/src/elf/elf_patterns.cpp b/src/elf/elf_patterns.cpp new file mode 100644 index 0000000..f803040 --- /dev/null +++ b/src/elf/elf_patterns.cpp @@ -0,0 +1,205 @@ +#include "elf_patterns.h" +#include "common/timer.h" +#include "common/utils.h" + +#include +#include +#include +#include + +bool is_va_in_section(uint64_t va, const ELFImage& elf, const Section& section) { + uint64_t start_va = elf.get_base_address() + section.virtual_address; + uint64_t end_va = start_va + section.virtual_size; + bool in_section = (va >= start_va && va < end_va); + + // DBG call is useful but can be very noisy, so it's good to have it conditional + if (is_debug_enabled()) { + DBG("[is_va_in_section] VA=0x", std::hex, va, " section=", section.name, + " range=[0x", start_va, ", 0x", end_va, ") -> ", std::boolalpha, in_section, std::dec); + } + return in_section; +} + +uint64_t find_lea_to_target_va(const ELFImage& elf, const Section& text_sec, uint64_t target_va) { + Timer timer("find_lea_to_target_va"); + DBG("[find_lea] target_va=0x", std::hex, target_va, std::dec); + + auto text_data = elf.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); + if (text_data.size() < 7) return 0; + + const uint64_t text_va_base = elf.get_base_address() + text_sec.virtual_address; + + // A set of valid ModR/M bytes for [RIP + disp32] addressing with any register operand. + // The format is 00_REG_101. + static const std::unordered_set valid_modrm = { + 0x05, 0x0D, 0x15, 0x1D, 0x25, 0x2D, 0x35, 0x3D + }; + + // First, try LEA with RIP-relative addressing (7 bytes: REX.W + 8D + ModR/M + disp32) + // This is what MSVC typically generates on Windows. + for (size_t i = 1; i < text_data.size() - 6; ++i) { + if (text_data[i] == 0x8D) { // LEA opcode + uint8_t rex = text_data[i - 1]; + if ((rex & 0xF8) == 0x48) { // REX.W prefix + uint8_t modrm = text_data[i + 1]; + if (valid_modrm.count(modrm)) { + int32_t disp; + std::memcpy(&disp, &text_data[i + 2], sizeof(disp)); + + uint64_t instr_va = text_va_base + (i - 1); + uint64_t rip_after = instr_va + 7; + uint64_t calculated_target = rip_after + disp; + + if (calculated_target == target_va) { + DBG("[find_lea] Found LEA at VA=0x", std::hex, instr_va); + return instr_va; + } + } + } + } + } + + // Second, try MOV r32, imm32 with 32-bit immediate (GCC on Linux often uses this). + // For non-PIE binaries where addresses fit in 32 bits, GCC uses: + // MOV EAX-EDI, imm32: B8-BF + imm32 (5 bytes) + // MOV R8D-R15D, imm32: 41 B8-BF + imm32 (6 bytes, REX.B prefix) + // The 32-bit value is zero-extended to 64 bits. + if (target_va <= 0xFFFFFFFF) { + uint32_t target_imm = static_cast(target_va); + + for (size_t i = 0; i < text_data.size() - 5; ++i) { + uint8_t byte = text_data[i]; + + // Check for MOV EAX-EDI, imm32 (B8-BF) + if (byte >= 0xB8 && byte <= 0xBF) { + uint32_t imm; + std::memcpy(&imm, &text_data[i + 1], sizeof(imm)); + if (imm == target_imm) { + uint64_t instr_va = text_va_base + i; + DBG("[find_lea] Found MOV r32,imm32 at VA=0x", std::hex, instr_va); + return instr_va; + } + } + + // Check for MOV R8D-R15D, imm32 (41 B8-BF) + if (byte == 0x41 && i + 6 <= text_data.size()) { + uint8_t opcode = text_data[i + 1]; + if (opcode >= 0xB8 && opcode <= 0xBF) { + uint32_t imm; + std::memcpy(&imm, &text_data[i + 2], sizeof(imm)); + if (imm == target_imm) { + uint64_t instr_va = text_va_base + i; + DBG("[find_lea] Found MOV r32,imm32 (REX.B) at VA=0x", std::hex, instr_va); + return instr_va; + } + } + } + } + } + + DBG("[find_lea] No matching instruction found."); + return 0; +} + +std::optional find_rip_relative_load_in_window( + const ELFImage& elf, const Section& text_sec, uint64_t from_va, size_t window) +{ + Timer timer("find_rip_relative_load_in_window"); + DBG("[LOAD_SCAN] from_va=0x", std::hex, from_va, " window=", std::dec, window); + + int64_t start_offset = elf.va_to_file_offset(from_va); + if (start_offset < 0) { + DBG("[LOAD_SCAN] from_va is not a valid address."); + return std::nullopt; + } + + auto text_data = elf.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); + size_t search_start = start_offset - text_sec.file_offset; + size_t search_end = std::min(search_start + window, text_data.size()); + + const uint64_t text_va_base = text_sec.virtual_address; + + // Helper lambda to check if an address is in a data section + // Note: We prioritize .data over .bss because encryption keys are in initialized data. + // .bss is uninitialized and often doesn't exist in the file. + auto is_in_data_section = [&](uint64_t va) -> bool { + for (const auto& s : elf.get_sections()) { + if (s.name == ".data" || s.name == ".data.rel.ro") { + if (is_va_in_section(va, elf, s)) { + return true; + } + } + } + return false; + }; + + for (size_t i = search_start; i + 6 < search_end; ++i) { + // Pattern 1: REX.W + MOV/LEA with RIP-relative addressing (7 bytes) + if ((text_data[i] & 0xF8) == 0x48) { + uint8_t opcode = text_data[i + 1]; + + if (opcode == 0x8B || opcode == 0x8D) { // MOV or LEA + uint8_t modrm = text_data[i + 2]; + if ((modrm & 0xC7) == 0x05) { // RIP-relative addressing + int32_t disp; + std::memcpy(&disp, &text_data[i + 3], sizeof(disp)); + + uint64_t instr_va = text_va_base + i; + uint64_t rip_after = instr_va + 7; + uint64_t target_va = rip_after + disp; + + uint64_t final_blob_va = 0; + + if (opcode == 0x8B) { // MOV - dereference pointer + auto ptr_opt = elf.read_u64_va(target_va); + if (!ptr_opt) continue; + final_blob_va = *ptr_opt; + } else { // LEA - direct address + final_blob_va = target_va; + } + + if (is_in_data_section(final_blob_va)) { + LoadType type = (opcode == 0x8B) ? LoadType::MOV_DEREF : LoadType::LEA_ADDRESS; + DBG("[LOAD_SCAN] Found valid ", (type == LoadType::MOV_DEREF ? "MOV" : "LEA"), + " at VA=0x", std::hex, instr_va, " -> VA=0x", final_blob_va, std::dec); + return RipRelativeLoad{instr_va, target_va, type}; + } + } + } + } + + // Pattern 2: MOV r32, imm32 (5 bytes) - GCC on Linux uses this for addresses < 2GB + // Format: B8-BF + imm32 (loads into EAX-EDI) + if (text_data[i] >= 0xB8 && text_data[i] <= 0xBF && i + 5 <= search_end) { + uint32_t imm; + std::memcpy(&imm, &text_data[i + 1], sizeof(imm)); + + if (is_in_data_section(imm)) { + uint64_t instr_va = text_va_base + i; + DBG("[LOAD_SCAN] Found MOV r32,imm32 at VA=0x", std::hex, instr_va, + " loading addr=0x", imm, std::dec); + return RipRelativeLoad{instr_va, imm, LoadType::LEA_ADDRESS}; + } + } + + // Pattern 3: REX.B + MOV r32, imm32 (6 bytes) + // Format: 41 B8-BF + imm32 (loads into R8D-R15D) + if (text_data[i] == 0x41 && i + 6 <= search_end) { + uint8_t opcode = text_data[i + 1]; + if (opcode >= 0xB8 && opcode <= 0xBF) { + uint32_t imm; + std::memcpy(&imm, &text_data[i + 2], sizeof(imm)); + + if (is_in_data_section(imm)) { + uint64_t instr_va = text_va_base + i; + DBG("[LOAD_SCAN] Found MOV r32,imm32 (REX.B) at VA=0x", std::hex, instr_va, + " loading addr=0x", imm, std::dec); + return RipRelativeLoad{instr_va, imm, LoadType::LEA_ADDRESS}; + } + } + } + } + + DBG("[LOAD_SCAN] No valid MOV/LEA found in window."); + return std::nullopt; +} diff --git a/src/elf/elf_scanner.cpp b/src/elf/elf_scanner.cpp new file mode 100644 index 0000000..fa987e2 --- /dev/null +++ b/src/elf/elf_scanner.cpp @@ -0,0 +1,269 @@ +#include "keydot/elf_scanner.h" +#include "common/mapped_file.h" +#include "common/timer.h" +#include "common/utils.h" +#include "elf/elf_image.h" +#include "elf/elf_patterns.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::vector find_subsequence( + std::span haystack, + size_t start, + size_t length, + std::string_view needle) +{ + Timer timer("find_subsequence '" + std::string(needle) + "'", false); + if (start + length > haystack.size()) { + length = haystack.size() - start; + } + + std::vector found_indices; + auto search_area = haystack.subspan(start, length); + std::span needle_span( + reinterpret_cast(needle.data()), + needle.size() + ); + + auto it = search_area.begin(); + while (true) { + it = std::search(it, search_area.end(), needle_span.begin(), needle_span.end()); + if (it == search_area.end()) { + break; + } + // Calculate offset relative to the full haystack, not the subspan + size_t absolute_offset = (it - haystack.begin()); + found_indices.push_back(absolute_offset); + ++it; // Continue search after the found occurrence + } + + timer.print_manual(std::string(needle), needle.length()); + return found_indices; +} + +// Extract a bounded C-string view from [start, end). +// Returns a view from start to the first '\0' or end if no '\0' found. +inline std::string_view bounded_cstr_view(const char* start, const char* end) { + const char* nul = std::find(start, end, '\0'); + return std::string_view(start, static_cast(nul - start)); +} + +// Parse version substring "v" from a string_view. +// Returns only the version part (without the 'v'), up to whitespace/end. +inline std::optional parse_version_from_view(std::string_view s) { + size_t pos = s.find('v'); + while (pos != std::string_view::npos) { + if (pos + 1 < s.size() && std::isdigit(static_cast(s[pos + 1]))) { + size_t end = s.find_first_of(" \t", pos); + const size_t start = pos + 1; + const size_t count = (end == std::string_view::npos ? s.size() : end) - start; + return std::string(s.substr(start, count)); + } + pos = s.find('v', pos + 1); + } + return std::nullopt; +} + +std::optional find_godot_version_in_elf(const ELFImage& elf) { + Timer timer("find_godot_version_in_elf"); + + const Section* rodata = elf.get_section(".rodata"); + if (!rodata) { + DBG("[GodotVer] .rodata section not found"); + return std::nullopt; + } + + const uint8_t* base = elf.get_raw_data().data(); + const char* seg_begin = reinterpret_cast(base + rodata->file_offset); + const char* seg_end = seg_begin + rodata->file_size; + + static const std::string needle = "Godot Engine"; + auto searcher = std::boyer_moore_searcher(needle.begin(), needle.end()); + + DBG("[GodotVer] Scanning .rodata for '", needle, "' (", rodata->file_size, " bytes)"); + + const char* pos = seg_begin; + size_t occ_idx = 0; + while (true) { + auto it = std::search(pos, seg_end, searcher); + if (it == seg_end) break; // no more matches + ++occ_idx; + + std::string_view full_sv = bounded_cstr_view(it, seg_end); + DBG("[GodotVer] Occurrence ", occ_idx, ": ", std::string(full_sv)); + + if (auto ver = parse_version_from_view(full_sv)) { + DBG("[GodotVer] Parsed version: ", *ver); + return ver; + } + + pos = it + needle.size(); + } + + DBG("[GodotVer] No occurrence contained a version pattern"); + return std::nullopt; +} + +} + +int scan_elf_file(const std::string& path) { + // --- Stage 1: Memory Map the file --- + MappedFile mapped_file(path); + if (!mapped_file.is_valid()) { + return 1; // MappedFile constructor already printed the error + } + + // --- Stage 2: ELF parse --- + Timer elf_parse_timer("ELFImage::parse"); + auto elf = ELFImage::parse(mapped_file.get_data()); + elf_parse_timer.~Timer(); + + if (!elf || !elf->is_elf64()) { + std::cerr << "Error: Not a valid ELF64 (x64) image." << std::endl; + return 2; + } + + DBG("[ELF] BaseAddress=0x", std::hex, elf->get_base_address(), std::dec); + DBG("[ELF] Type=", (elf->get_elf_type() == ELFType::EXEC ? "ET_EXEC" : "ET_DYN")); + DBG("[ELF] Section count: ", elf->get_sections().size()); + + // --- Stage 3: section lookups --- + const Section *text, *rodata, *data; + { + Timer section_lookup_timer("Section lookups"); + text = elf->get_section(".text"); + rodata = elf->get_section(".rodata"); + if (!rodata) { + rodata = elf->get_section(".data.rel.ro"); + } + data = elf->get_section(".data"); + } + if (!text || !rodata || !data) { + std::cerr << "Error: Required sections .text/.rodata/.data not found." << std::endl; + return 3; + } + + DBG("[SECT] .text VA=0x", std::hex, text->virtual_address, " size=0x", text->virtual_size, std::dec); + DBG("[SECT] rodata (", rodata->name, ") VA=0x", std::hex, rodata->virtual_address, " size=0x", rodata->virtual_size, std::dec); + DBG("[SECT] .data VA=0x", std::hex, data->virtual_address, " size=0x", data->virtual_size, std::dec); + + // Optional: Godot version extraction + auto godot_ver = find_godot_version_in_elf(*elf); + if (godot_ver) { + std::cout << "Godot Engine version: " << *godot_ver << std::endl; + } else { + std::cout << "Could not determine Godot Engine version from ELF." << std::endl; + } + + // --- Stage 4: anchor search loop --- + const std::vector anchors = { + "Can't open encrypted pack directory.", + "Can't open encrypted pack-referenced file '%s'.", + "Condition \"fae.is_null()\" is true." + }; + + bool found = false; + for (const auto& anchor_str : anchors) { + Timer anchor_timer("Anchor '" + anchor_str + "' search"); + DBG("[ANCHOR] Searching for: '", anchor_str, "'"); + + // 4a: Find the anchor string in the .rodata section + auto hits = find_subsequence(elf->get_raw_data(), rodata->file_offset, rodata->file_size, anchor_str); + DBG("[ANCHOR] Hits: ", hits.size()); + + for (const auto& hit : hits) { + uint32_t anchor_rva = rodata->virtual_address + static_cast(hit - rodata->file_offset); + uint64_t anchor_va = elf->get_base_address() + anchor_rva; + DBG("[ANCHOR] VA=0x", std::hex, anchor_va, std::dec); + + // 4b: Find a `LEA` instruction in the .text section that points to our string + uint64_t lea_site = find_lea_to_target_va(*elf, *text, anchor_va); + if (lea_site == 0) { + DBG("[LEA] Not found for anchor VA=0x", std::hex, anchor_va, std::dec); + continue; + } + DBG("[LEA] Site=0x", std::hex, lea_site, std::dec); + + // 4c: Scan forward from AFTER the anchor instruction for the key blob load + // GCC on Linux may place the key load far from the error string (up to 64KB away) + auto load_instr_opt = find_rip_relative_load_in_window(*elf, *text, lea_site + 1, 0x600); + if (!load_instr_opt) { + DBG("[LOAD_SCAN] Not found in 0x600 window. Expanding to 0x10000..."); + load_instr_opt = find_rip_relative_load_in_window(*elf, *text, lea_site + 1, 0x10000); + if (!load_instr_opt) { + DBG("[LOAD_SCAN] Not found in 0x10000 window either."); + continue; + } + } + const auto& load_instr = *load_instr_opt; + + // 4d: Get the blob pointer VA, handling MOV vs LEA difference + uint64_t ptr_to_blob_va = 0; + if (load_instr.type == LoadType::MOV_DEREF) { + // For MOV, the target_va is a pointer we must read to get the final address + DBG("[SCAN] Instruction is MOV, reading pointer from 0x", std::hex, load_instr.target_va, std::dec); + auto ptr_opt = elf->read_u64_va(load_instr.target_va); + if (!ptr_opt) { + DBG("[READ] Failed to read pointer for MOV at VA=0x", std::hex, load_instr.target_va, std::dec); + continue; + } + ptr_to_blob_va = *ptr_opt; + } else { // LoadType::LEA_ADDRESS + DBG("[SCAN] Instruction is LEA, target VA is the pointer."); + ptr_to_blob_va = load_instr.target_va; + } + DBG("[READ] Final Blob pointer VA=0x", std::hex, ptr_to_blob_va, std::dec); + + // 4e: Validate that the blob pointer is in a valid data section + const Section* blob_data_section = nullptr; + for (const auto& s : elf->get_sections()) { + if ((s.name == ".data" || s.name == ".bss" || s.name == ".data.rel.ro") && is_va_in_section(ptr_to_blob_va, *elf, s)) { + blob_data_section = &s; + break; + } + } + if (!blob_data_section) { + DBG("[SECT] Final blob VA 0x", std::hex, ptr_to_blob_va, " not in any data section.", std::dec); + continue; + } + DBG("[SECT] Blob VA is in section '", blob_data_section->name, "'."); + + // 4f: Read the final 32-byte key blob + auto blob = elf->read_va(ptr_to_blob_va, 32); + if (!blob || blob->size() != 32) { + DBG("[READ] Blob read failed or not 32 bytes."); + continue; + } + + std::cout << std::left << std::setw(17) << "Anchor" << ": " << anchor_str << std::endl; + std::cout << std::hex << std::uppercase << std::setfill('0'); + std::cout << std::left << std::setw(17) << "String VA" << ": 0x" << anchor_va << std::endl; + std::cout << std::left << std::setw(17) << "LEA at" << ": 0x" << lea_site << std::endl; + std::cout << std::left << std::setw(17) << "off_* qword VA" << ": 0x" << load_instr.target_va << std::endl; + std::cout << std::left << std::setw(17) << "Blob VA" << ": 0x" << ptr_to_blob_va << std::endl; + std::cout << std::dec << std::setfill(' '); + std::cout << std::left << std::setw(17) << "32-byte (hex)" << ": " << hex_string(*blob) << std::endl; + + found = true; + break; + } + + if (found) break; + } + + if (!found) { + std::cerr << "Failed to locate the 32-byte key blob using the provided anchors." << std::endl; + return 4; + } + + return 0; +} diff --git a/src/pe/pe_patterns.cpp b/src/pe/pe_patterns.cpp index 1b9a2b5..3abeabd 100644 --- a/src/pe/pe_patterns.cpp +++ b/src/pe/pe_patterns.cpp @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream #include "pe_patterns.h" #include "common/timer.h" #include "common/utils.h" @@ -412,4 +413,174 @@ std::optional find_rip_relative_load_around_va( DBG("[AROUND_SCAN] No valid MOV/LEA found in radius."); return std::nullopt; +======= +#include "pe_patterns.h" +#include "common/timer.h" +#include "common/utils.h" + +#include +#include +#include +#include + +std::vector find_subsequence( + std::span haystack, + size_t start, + size_t length, + std::string_view needle) +{ + Timer timer("find_subsequence '" + std::string(needle) + "'", false); + if (start + length > haystack.size()) { + length = haystack.size() - start; + } + + std::vector found_indices; + auto search_area = haystack.subspan(start, length); + std::span needle_span( + reinterpret_cast(needle.data()), + needle.size() + ); + + auto it = search_area.begin(); + while (true) { + it = std::search(it, search_area.end(), needle_span.begin(), needle_span.end()); + if (it == search_area.end()) { + break; + } + // Calculate offset relative to the full haystack, not the subspan + size_t absolute_offset = (it - haystack.begin()); + found_indices.push_back(absolute_offset); + ++it; // Continue search after the found occurrence + } + + timer.print_manual(std::string(needle), needle.length()); + return found_indices; +} + +bool is_va_in_section(uint64_t va, const PEImage& pe, const Section& section) { + uint64_t start_va = pe.get_image_base() + section.virtual_address; + uint64_t end_va = start_va + section.virtual_size; + bool in_section = (va >= start_va && va < end_va); + + // DBG call is useful but can be very noisy, so it's good to have it conditional + if (is_debug_enabled()) { + DBG("[is_va_in_section] VA=0x", std::hex, va, " section=", section.name, + " range=[0x", start_va, ", 0x", end_va, ") -> ", std::boolalpha, in_section, std::dec); + } + return in_section; +} + +uint64_t find_lea_to_target_va(const PEImage& pe, const Section& text_sec, uint64_t target_va) { + Timer timer("find_lea_to_target_va"); + DBG("[find_lea] target_va=0x", std::hex, target_va, std::dec); + + auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); + if (text_data.size() < 7) return 0; + + const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; + + // A set of valid ModR/M bytes for [RIP + disp32] addressing with any register operand. + // The format is 00_REG_101. + static const std::unordered_set valid_modrm = { + 0x05, 0x0D, 0x15, 0x1D, 0x25, 0x2D, 0x35, 0x3D + }; + + // We are looking for REX.W + 8D + ModR/M + disp32. + // Start search at index 1 to allow checking for REX prefix at i-1. + for (size_t i = 1; i < text_data.size() - 6; ++i) { + // Opcode for LEA is 0x8D + if (text_data[i] == 0x8D) { + uint8_t rex = text_data[i - 1]; + // REX.W prefix for 64-bit operand (0x48-0x4F) + if ((rex & 0xF8) == 0x48) { + uint8_t modrm = text_data[i + 1]; + if (valid_modrm.count(modrm)) { + int32_t disp; + std::memcpy(&disp, &text_data[i + 2], sizeof(disp)); + + uint64_t instr_va = text_va_base + (i - 1); + uint64_t rip_after = instr_va + 7; // RIP is value after the instruction + uint64_t calculated_target = rip_after + disp; + + if (calculated_target == target_va) { + DBG("[find_lea] Found LEA at VA=0x", std::hex, instr_va); + return instr_va; + } + } + } + } + } + DBG("[find_lea] No matching LEA instruction found."); + return 0; +} + +std::optional find_rip_relative_load_in_window( + const PEImage& pe, const Section& text_sec, uint64_t from_va, size_t window) +{ + Timer timer("find_rip_relative_load_in_window"); + DBG("[LOAD_SCAN] from_va=0x", std::hex, from_va, " window=", std::dec, window); + + int64_t start_offset = pe.va_to_file_offset(from_va); + if (start_offset < 0) { + DBG("[LOAD_SCAN] from_va is not a valid address."); + return std::nullopt; + } + + auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); + size_t search_start = start_offset - text_sec.file_offset; + size_t search_end = std::min(search_start + window, text_data.size()); + + const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; + + for (size_t i = search_start; i + 6 < search_end; ++i) { + if ((text_data[i] & 0xF8) == 0x48) { // REX.W prefix (0x48-0x4F) + uint8_t opcode = text_data[i + 1]; + + if (opcode == 0x8B || opcode == 0x8D) { // MOV or LEA + uint8_t modrm = text_data[i + 2]; + if ((modrm & 0xC7) == 0x05) { // Check for RIP-relative addressing + int32_t disp; + std::memcpy(&disp, &text_data[i + 3], sizeof(disp)); + + uint64_t instr_va = text_va_base + i; + uint64_t rip_after = instr_va + 7; + uint64_t target_va = rip_after + disp; + + uint64_t final_blob_va = 0; + bool is_valid = false; + + if (opcode == 0x8B) { // MOV + auto ptr_opt = pe.read_u64_va(target_va); + if (!ptr_opt) { + continue; + } + final_blob_va = *ptr_opt; + } else { // LEA + final_blob_va = target_va; + } + + // Check if the FINAL address (after any dereferencing) is in a .data section. + for (const auto& s : pe.get_sections()) { + if (s.name.rfind(".data", 0) == 0) { // Matches .data, .data1, etc. + if (is_va_in_section(final_blob_va, pe, s)) { + is_valid = true; + break; + } + } + } + + if (is_valid) { + LoadType type = (opcode == 0x8B) ? LoadType::MOV_DEREF : LoadType::LEA_ADDRESS; + DBG("[LOAD_SCAN] Found valid ", (type == LoadType::MOV_DEREF ? "MOV" : "LEA"), " at VA=0x", std::hex, instr_va, + " leading to final VA=0x", final_blob_va, " in a .data section.", std::dec); + return RipRelativeLoad{instr_va, target_va, type}; + } + } + } + } + } + + DBG("[LOAD_SCAN] No valid MOV/LEA found in window."); + return std::nullopt; +>>>>>>> Stashed changes } \ No newline at end of file diff --git a/win_key.txt b/win_key.txt new file mode 100644 index 0000000..cb91343 --- /dev/null +++ b/win_key.txt @@ -0,0 +1,40 @@ +[CFG] Debug logging enabled +[IO] File size: 96625152 bytes +[IO] Mapped view @ 0x7f8b1a800000 size=96625152 bytes +[PE] ImageBase=0x140000000 +[PE] Section count: 13 +[SECT] .text RVA=0x1000 size=0x48601d8 +[SECT] .rdata RVA=0x4945000 size=0xea0410 +[SECT] .data RVA=0x4862000 size=0xe2a20 +[GodotVer] Scanning .rdata for 'Godot Engine' (15336960 bytes) +[GodotVer] Occurrence 1: Godot Engine running with display/window/energy_saving/keep_screen_on = true +[GodotVer] Occurrence 2: Godot Engine +[GodotVer] Occurrence 3: Godot Engine +[GodotVer] Occurrence 4: Godot Engine contributors. (c) 2007-present Juan Linietsky, Ariel Manzur. +[GodotVer] Occurrence 5: Godot Engine v4.5.1.stable.official +[GodotVer] Parsed version: 4.5.1.stable.official +Godot Engine version: 4.5.1.stable.official +[ANCHOR] Searching for: 'Can't open encrypted pack directory.' +[ANCHOR] Hits: 1 +[ANCHOR] RVA=0x4d78be0 VA=0x144d78be0 +[find_lea] target_va=0x144d78be0 +[find_lea] Found LEA at VA=0x14279126c +[LEA] Site=0x14279126c +[LOAD_SCAN] from_va=0x14279126d window=1536 +[is_va_in_section] VA=0x144d78ba8 section=.data range=[0x144862000, 0x144944a20) -> false +[is_va_in_section] VA=0x144d78481 section=.data range=[0x144862000, 0x144944a20) -> false +[is_va_in_section] VA=0x144d7893c section=.data range=[0x144862000, 0x144944a20) -> false +[is_va_in_section] VA=0x145d0b940 section=.data range=[0x144862000, 0x144944a20) -> false +[is_va_in_section] VA=0x1444b9b70 section=.data range=[0x144862000, 0x144944a20) -> false +[is_va_in_section] VA=0x14492e310 section=.data range=[0x144862000, 0x144944a20) -> true +[LOAD_SCAN] Found valid LEA at VA=0x1427914f4 leading to final VA=0x14492e310 in a .data section. +[SCAN] Instruction is LEA, target VA is the pointer. +[READ] Final Blob pointer VA=0x14492e310 +[is_va_in_section] VA=0x14492e310 section=.data range=[0x144862000, 0x144944a20) -> true +[SECT] Blob VA is in section '.data'. +Anchor : Can't open encrypted pack directory. +String VA00000000: 0x144D78BE0 +LEA at00000000000: 0x14279126C +off_* qword VA000: 0x14492E310 +Blob VA0000000000: 0x14492E310 +32-byte (hex) : 0000000000000000000000000000000000000000000000000000000000000000 From 4eed85a8486339d329832c16039a1559a6e1adb2 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 24 Jan 2026 01:35:58 -0500 Subject: [PATCH 2/3] removed test outputs --- lin_key.txt | 303 ---------------------------------------------------- win_key.txt | 40 ------- 2 files changed, 343 deletions(-) delete mode 100644 lin_key.txt delete mode 100644 win_key.txt diff --git a/lin_key.txt b/lin_key.txt deleted file mode 100644 index 04bb874..0000000 --- a/lin_key.txt +++ /dev/null @@ -1,303 +0,0 @@ -[CFG] Debug logging enabled -[IO] Detected ELF file -[IO] File size: 70158584 bytes -[IO] Mapped view @ 0x7fceda200000 size=70158584 bytes -[ELF] e_type=2 e_shoff=70156472 e_shnum=33 e_shstrndx=32 -[ELF] String table at offset 70156189 size 276 -[ELF] Section: .interp addr=0x4002e0 offset=0x2e0 size=0x1c -[ELF] Section: .note.ABI-tag addr=0x4002fc offset=0x2fc size=0x20 -[ELF] Section: .hash addr=0x400320 offset=0x320 size=0x9f0 -[ELF] Section: .gnu.hash addr=0x400d10 offset=0xd10 size=0x268 -[ELF] Section: .dynsym addr=0x400f78 offset=0xf78 size=0x22c8 -[ELF] Section: .dynstr addr=0x403240 offset=0x3240 size=0xee5 -[ELF] Section: .gnu.version addr=0x404126 offset=0x4126 size=0x2e6 -[ELF] Section: .gnu.version_r addr=0x404410 offset=0x4410 size=0x1e0 -[ELF] Section: .rela.dyn addr=0x4045f0 offset=0x45f0 size=0x9d8 -[ELF] Section: .rela.plt addr=0x404fc8 offset=0x4fc8 size=0x1950 -[ELF] Section: .init addr=0x407000 offset=0x7000 size=0x17 -[ELF] Section: .plt addr=0x407020 offset=0x7020 size=0x10f0 -[ELF] Section: .plt.got addr=0x408110 offset=0x8110 size=0x1c0 -[ELF] Section: .text addr=0x408300 offset=0x8300 size=0x344f18f -[ELF] Section: .fini addr=0x3857490 offset=0x3457490 size=0x9 -[ELF] Section: .rodata addr=0x3858000 offset=0x3458000 size=0x8a7f70 -[ELF] Section: pck addr=0x40fff70 offset=0x3cfff70 size=0x8 -[ELF] Section: .eh_frame_hdr addr=0x40fff78 offset=0x3cfff78 size=0xc7ed4 -[ELF] Section: .eh_frame addr=0x41c7e50 offset=0x3dc7e50 size=0x4fdb98 -[ELF] Section: .gcc_except_table addr=0x46c59e8 offset=0x42c59e8 size=0x6e31 -[ELF] Section: .tdata addr=0x46cd490 offset=0x42cd490 size=0xe0 -[ELF] Section: .tbss addr=0x46cd570 offset=0x42cd570 size=0xa08 -[ELF] Section: .init_array addr=0x46cd570 offset=0x42cd570 size=0x58 -[ELF] Section: .fini_array addr=0x46cd5c8 offset=0x42cd5c8 size=0x10 -[ELF] Section: .data.rel.ro addr=0x46cd5e0 offset=0x42cd5e0 size=0x13438 -[ELF] Section: .dynamic addr=0x46e0a18 offset=0x42e0a18 size=0x230 -[ELF] Section: .got addr=0x46e0c48 offset=0x42e0c48 size=0x3a0 -[ELF] Section: .got.plt addr=0x46e0fe8 offset=0x42e0fe8 size=0x888 -[ELF] Section: .data addr=0x46e1880 offset=0x42e1880 size=0x66c0 -[ELF] Section: .bss addr=0x46e7f80 offset=0x42e7f40 size=0x1de148 -[ELF] Section: .comment addr=0x0 offset=0x42e7f40 size=0x5d -[ELF] Section: .shstrtab addr=0x0 offset=0x42e7f9d size=0x114 -[ELF] BaseAddress=0x400000 -[ELF] Type=ET_EXEC -[ELF] Section count: 32 -[SECT] .text VA=0x408300 size=0x344f18f -[SECT] rodata (.rodata) VA=0x3858000 size=0x8a7f70 -[SECT] .data VA=0x46e1880 size=0x66c0 -[GodotVer] Scanning .rodata for 'Godot Engine' (9076592 bytes) -[GodotVer] Occurrence 1: Godot Engine -[GodotVer] Occurrence 2: Godot Engine project -[GodotVer] Occurrence 3: Godot Engine contributors. (c) 2007-present Juan Linietsky, Ariel Manzur. -[GodotVer] Occurrence 4: Godot Engine -[GodotVer] Occurrence 5: Godot Engine/1.0 UPnP/1.1 MiniUPnPc/2.3.3 -Content-Length: %d -Content-Type: text/xml; charset="utf-8" -SOAPAction: "%s" -Connection: close - - -[GodotVer] Occurrence 6: Godot Engine Project -[GodotVer] Occurrence 7: Godot Engine 4.5.1.stable's CanvasItemMaterial. - - -[GodotVer] Occurrence 8: Godot Engine 4.5.1.stable's %s. - - -[GodotVer] Occurrence 9: Godot Engine 4.5.1.stable's ParticleProcessMaterial. - - -[GodotVer] Occurrence 10: Godot Engine 4.5.1.stable's FogMaterial. - -shader_type fog; - -uniform float density : hint_range(0, 1, 0.0001) = 1.0; -uniform vec4 albedo : source_color = vec4(1.0); -uniform vec4 emission : source_color = vec4(0, 0, 0, 1); -uniform float height_falloff = 0.0; -uniform float edge_fade = 0.1; -uniform sampler3D density_texture: hint_default_white; - - -void fog() { - DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0); - DENSITY *= texture(density_texture, UVW).r; - DENSITY *= pow(clamp(-2.0 * SDF / min(min(SIZE.x, SIZE.y), SIZE.z), 0.0, 1.0), edge_fade); - ALBEDO = albedo.rgb; - EMISSION = emission.rgb; -} - -[GodotVer] Occurrence 11: Godot Engine 4.5.1.stable's PanoramaSkyMaterial. - -shader_type sky; - -uniform sampler2D source_panorama : %s, source_color, hint_default_black; -uniform float exposure : hint_range(0, 128) = 1.0; - -void sky() { - COLOR = texture(source_panorama, SKY_COORDS).rgb * exposure; -} - -[GodotVer] Occurrence 12: Godot Engine 4.5.1.stable's ProceduralSkyMaterial. - -shader_type sky; -%s - -uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0); -uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); -uniform float inv_sky_curve : hint_range(1, 100) = 4.0; -uniform vec4 ground_bottom_color : source_color = vec4(0.2, 0.169, 0.133, 1.0); -uniform vec4 ground_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); -uniform float inv_ground_curve : hint_range(1, 100) = 30.0; -uniform float sun_angle_max = 0.877; -uniform float inv_sun_curve : hint_range(1, 100) = 22.78; -uniform float exposure : hint_range(0, 128) = 1.0; - -uniform sampler2D sky_cover : filter_linear, source_color, hint_default_black; -uniform vec4 sky_cover_modulate : source_color = vec4(1.0, 1.0, 1.0, 1.0); - -void sky() { - float v_angle = clamp(EYEDIR.y, -1.0, 1.0); - vec3 sky = mix(sky_top_color.rgb, sky_horizon_color.rgb, clamp(pow(1.0 - v_angle, inv_sky_curve), 0.0, 1.0)); - - if (LIGHT0_ENABLED) { - float sun_angle = dot(LIGHT0_DIRECTION, EYEDIR); - float sun_size = cos(LIGHT0_SIZE); - if (sun_angle > sun_size) { - sky = LIGHT0_COLOR * LIGHT0_ENERGY; - } else if (sun_angle > sun_angle_max) { - float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); - sky = mix(sky, LIGHT0_COLOR * LIGHT0_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); - } - } - - if (LIGHT1_ENABLED) { - float sun_angle = dot(LIGHT1_DIRECTION, EYEDIR); - float sun_size = cos(LIGHT1_SIZE); - if (sun_angle > sun_size) { - sky = LIGHT1_COLOR * LIGHT1_ENERGY; - } else if (sun_angle > sun_angle_max) { - float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); - sky = mix(sky, LIGHT1_COLOR * LIGHT1_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); - } - } - - if (LIGHT2_ENABLED) { - float sun_angle = dot(LIGHT2_DIRECTION, EYEDIR); - float sun_size = cos(LIGHT2_SIZE); - if (sun_angle > sun_size) { - sky = LIGHT2_COLOR * LIGHT2_ENERGY; - } else if (sun_angle > sun_angle_max) { - float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); - sky = mix(sky, LIGHT2_COLOR * LIGHT2_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); - } - } - - if (LIGHT3_ENABLED) { - float sun_angle = dot(LIGHT3_DIRECTION, EYEDIR); - float sun_size = cos(LIGHT3_SIZE); - if (sun_angle > sun_size) { - sky = LIGHT3_COLOR * LIGHT3_ENERGY; - } else if (sun_angle > sun_angle_max) { - float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max); - sky = mix(sky, LIGHT3_COLOR * LIGHT3_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0)); - } - } - - %s - %s - vec3 ground = mix(ground_bottom_color.rgb, ground_horizon_color.rgb, clamp(pow(1.0 + v_angle, inv_ground_curve), 0.0, 1.0)); - - COLOR = mix(ground, sky, step(0.0, EYEDIR.y)) * exposure; -} - -[GodotVer] Occurrence 13: Godot Engine 4.5.1.stable's PhysicalSkyMaterial. - -shader_type sky; -%s - -uniform float rayleigh : hint_range(0, 64) = 2.0; -uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0); -uniform float mie : hint_range(0, 1) = 0.005; -uniform float mie_eccentricity : hint_range(-1, 1) = 0.8; -uniform vec4 mie_color : source_color = vec4(0.69, 0.729, 0.812, 1.0); - -uniform float turbidity : hint_range(0, 1000) = 10.0; -uniform float sun_disk_scale : hint_range(0, 360) = 1.0; -uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0); -uniform float exposure : hint_range(0, 128) = 1.0; - -uniform sampler2D night_sky : filter_linear, source_color, hint_default_black; - -const vec3 UP = vec3( 0.0, 1.0, 0.0 ); - -// Optical length at zenith for molecules. -const float rayleigh_zenith_size = 8.4e3; -const float mie_zenith_size = 1.25e3; - -float henyey_greenstein(float cos_theta, float g) { - const float k = 0.0795774715459; - return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); -} - -void sky() { - if (LIGHT0_ENABLED) { - float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 ); - float sun_energy = max(0.0, 0.757 * zenith_angle) * LIGHT0_ENERGY; - float sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0); - - // Rayleigh coefficients. - float rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) ); - vec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001; - // mie coefficients from Preetham - vec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434; - - // Optical length. - float zenith = max(0.0, dot(UP, EYEDIR)); - float optical_mass = 1.0 / (zenith + 0.15 * pow(3.885 + 54.5 * zenith, -1.253)); - float rayleigh_scatter = rayleigh_zenith_size * optical_mass; - float mie_scatter = mie_zenith_size * optical_mass; - - // Light extinction based on thickness of atmosphere. - vec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter)); - - // In scattering. - float cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION)); - - float rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0)); - vec3 betaRTheta = rayleigh_beta * rayleigh_phase; - - float mie_phase = henyey_greenstein(cos_theta, mie_eccentricity); - vec3 betaMTheta = mie_beta * mie_phase; - - vec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5)); - // Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js - Lin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0)); - - // Hack in the ground color. - Lin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR))); - - // Solar disk and out-scattering. - float sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale); - float sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale * 0.5); - float sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta); - vec3 L0 = (sun_energy * extinction) * sundisk * LIGHT0_COLOR; - %s - - vec3 color = Lin + L0; - COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade)))); - COLOR *= exposure; - } else { - // There is no sun, so display night_sky and nothing else. - %s - COLOR *= exposure; - } -} - -[GodotVer] Occurrence 14: Godot Engine contributors (see AUTHORS.md). -Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -[GodotVer] Occurrence 15: Godot Engine v4.5.1.stable.official -[GodotVer] Parsed version: 4.5.1.stable.official -Godot Engine version: 4.5.1.stable.official -[ANCHOR] Searching for: 'Can't open encrypted pack directory.' -[ANCHOR] Hits: 1 -[ANCHOR] VA=0x43d0270 -[find_lea] target_va=0x43d0270 -[find_lea] No matching LEA instruction found. -[LEA] Not found for anchor VA=0x43d0270 -[ANCHOR] Searching for: 'Can't open encrypted pack-referenced file '%s'.' -[ANCHOR] Hits: 1 -[ANCHOR] VA=0x43d06b8 -[find_lea] target_va=0x43d06b8 -[find_lea] No matching LEA instruction found. -[LEA] Not found for anchor VA=0x43d06b8 -[ANCHOR] Searching for: 'Condition "fae.is_null()" is true.' -[ANCHOR] Hits: 3 -[ANCHOR] VA=0x43d0298 -[find_lea] target_va=0x43d0298 -[find_lea] No matching LEA instruction found. -[LEA] Not found for anchor VA=0x43d0298 -[ANCHOR] VA=0x43d06e8 -[find_lea] target_va=0x43d06e8 -[find_lea] No matching LEA instruction found. -[LEA] Not found for anchor VA=0x43d06e8 -[ANCHOR] VA=0x43d33b0 -[find_lea] target_va=0x43d33b0 -[find_lea] No matching LEA instruction found. -[LEA] Not found for anchor VA=0x43d33b0 diff --git a/win_key.txt b/win_key.txt deleted file mode 100644 index cb91343..0000000 --- a/win_key.txt +++ /dev/null @@ -1,40 +0,0 @@ -[CFG] Debug logging enabled -[IO] File size: 96625152 bytes -[IO] Mapped view @ 0x7f8b1a800000 size=96625152 bytes -[PE] ImageBase=0x140000000 -[PE] Section count: 13 -[SECT] .text RVA=0x1000 size=0x48601d8 -[SECT] .rdata RVA=0x4945000 size=0xea0410 -[SECT] .data RVA=0x4862000 size=0xe2a20 -[GodotVer] Scanning .rdata for 'Godot Engine' (15336960 bytes) -[GodotVer] Occurrence 1: Godot Engine running with display/window/energy_saving/keep_screen_on = true -[GodotVer] Occurrence 2: Godot Engine -[GodotVer] Occurrence 3: Godot Engine -[GodotVer] Occurrence 4: Godot Engine contributors. (c) 2007-present Juan Linietsky, Ariel Manzur. -[GodotVer] Occurrence 5: Godot Engine v4.5.1.stable.official -[GodotVer] Parsed version: 4.5.1.stable.official -Godot Engine version: 4.5.1.stable.official -[ANCHOR] Searching for: 'Can't open encrypted pack directory.' -[ANCHOR] Hits: 1 -[ANCHOR] RVA=0x4d78be0 VA=0x144d78be0 -[find_lea] target_va=0x144d78be0 -[find_lea] Found LEA at VA=0x14279126c -[LEA] Site=0x14279126c -[LOAD_SCAN] from_va=0x14279126d window=1536 -[is_va_in_section] VA=0x144d78ba8 section=.data range=[0x144862000, 0x144944a20) -> false -[is_va_in_section] VA=0x144d78481 section=.data range=[0x144862000, 0x144944a20) -> false -[is_va_in_section] VA=0x144d7893c section=.data range=[0x144862000, 0x144944a20) -> false -[is_va_in_section] VA=0x145d0b940 section=.data range=[0x144862000, 0x144944a20) -> false -[is_va_in_section] VA=0x1444b9b70 section=.data range=[0x144862000, 0x144944a20) -> false -[is_va_in_section] VA=0x14492e310 section=.data range=[0x144862000, 0x144944a20) -> true -[LOAD_SCAN] Found valid LEA at VA=0x1427914f4 leading to final VA=0x14492e310 in a .data section. -[SCAN] Instruction is LEA, target VA is the pointer. -[READ] Final Blob pointer VA=0x14492e310 -[is_va_in_section] VA=0x14492e310 section=.data range=[0x144862000, 0x144944a20) -> true -[SECT] Blob VA is in section '.data'. -Anchor : Can't open encrypted pack directory. -String VA00000000: 0x144D78BE0 -LEA at00000000000: 0x14279126C -off_* qword VA000: 0x14492E310 -Blob VA0000000000: 0x14492E310 -32-byte (hex) : 0000000000000000000000000000000000000000000000000000000000000000 From c46d11411272a57314e296e71795053f6f300e18 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 24 Jan 2026 01:48:32 -0500 Subject: [PATCH 3/3] how did github miss a conflict lol --- src/pe/pe_patterns.cpp | 301 +++++++++-------------------------------- 1 file changed, 65 insertions(+), 236 deletions(-) diff --git a/src/pe/pe_patterns.cpp b/src/pe/pe_patterns.cpp index 3abeabd..80da67c 100644 --- a/src/pe/pe_patterns.cpp +++ b/src/pe/pe_patterns.cpp @@ -1,4 +1,3 @@ -<<<<<<< Updated upstream #include "pe_patterns.h" #include "common/timer.h" #include "common/utils.h" @@ -22,7 +21,7 @@ static const std::array VALID_MODRM_BYTES = { 0x05, 0x0D, 0x15, 0x1D, 0x25, 0x2D, 0x35, 0x3D }; -static bool is_va_in_named_section(uint64_t va, const PEImage& pe, +static bool is_va_in_named_section(uint64_t va, const PEImage& pe, const std::vector& section_prefixes) { for (const auto& section : pe.get_sections()) { for (const auto& prefix : section_prefixes) { @@ -41,7 +40,7 @@ static std::optional match_rip_relative_instruction( const uint8_t* data, size_t data_size, size_t offset, uint64_t base_va, const PEImage& pe, const std::vector& allowed_sections) { - + if (offset + 6 >= data_size) { return std::nullopt; } @@ -87,7 +86,7 @@ static std::optional match_rip_relative_instruction( final_va = *ptr; } - if (!allowed_sections.empty() && + if (!allowed_sections.empty() && !is_va_in_named_section(final_va, pe, allowed_sections)) { return std::nullopt; } @@ -102,9 +101,9 @@ static std::optional match_rip_relative_instruction( static std::vector find_pattern_in_span( const uint8_t* haystack, size_t haystack_size, const uint8_t* needle, size_t needle_size) { - + std::vector indices; - + for (size_t i = 0; i + needle_size <= haystack_size; ++i) { bool found = true; for (size_t j = 0; j < needle_size; ++j) { @@ -117,7 +116,7 @@ static std::vector find_pattern_in_span( indices.push_back(i); } } - + return indices; } @@ -126,25 +125,25 @@ std::vector find_subsequence( size_t start, size_t length, std::string_view needle) { - + Timer timer("find_subsequence", false); - + if (start >= haystack.size()) { return {}; } - + length = std::min(length, haystack.size() - start); - + const uint8_t* haystack_ptr = haystack.data() + start; const uint8_t* needle_ptr = reinterpret_cast(needle.data()); size_t needle_size = needle.size(); - + auto indices = find_pattern_in_span(haystack_ptr, length, needle_ptr, needle_size); - + for (auto& idx : indices) { idx += start; } - + timer.print_manual(std::string(needle), needle.length()); return indices; } @@ -153,27 +152,27 @@ bool is_va_in_section(uint64_t va, const PEImage& pe, const Section& section) { const uint64_t start_va = pe.get_image_base() + section.virtual_address; const uint64_t end_va = start_va + section.virtual_size; const bool in_section = (va >= start_va && va < end_va); - + if (is_debug_enabled()) { DBG("[is_va_in_section] VA=0x", std::hex, va, " section=", section.name, - " range=[0x", start_va, ", 0x", end_va, ") -> ", + " range=[0x", start_va, ", 0x", end_va, ") -> ", std::boolalpha, in_section, std::dec); } - + return in_section; } uint64_t find_lea_to_target_va(const PEImage& pe, const Section& text_sec, uint64_t target_va) { Timer timer("find_lea_to_target_va"); DBG("[find_lea] target_va=0x", std::hex, target_va, std::dec); - + auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); if (text_data.size() < 7) return 0; - + const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; const uint8_t* data = text_data.data(); size_t data_size = text_data.size(); - + // Search for REX.W (0x48) + LEA (0x8D) pattern for (size_t i = 1; i < data_size - 6; ++i) { if (data[i] == LEA_OPCODE) { @@ -189,15 +188,15 @@ uint64_t find_lea_to_target_va(const PEImage& pe, const Section& text_sec, uint6 break; } } - + if (valid_modrm) { int32_t disp; std::memcpy(&disp, &data[i + 2], sizeof(disp)); - + const uint64_t instr_va = text_va_base + (i - 1); const uint64_t rip_after = instr_va + 7; const uint64_t calculated_target = rip_after + disp; - + if (calculated_target == target_va) { DBG("[find_lea] Found LEA at VA=0x", std::hex, instr_va); return instr_va; @@ -205,50 +204,50 @@ uint64_t find_lea_to_target_va(const PEImage& pe, const Section& text_sec, uint6 } } } - + DBG("[find_lea] No matching LEA instruction found."); return 0; } std::optional find_key_load_near_mov_edx_20h( const PEImage& pe, const Section& text_sec, uint64_t lea_site, size_t search_radius) { - + Timer timer("find_key_load_near_mov_edx_20h"); - DBG("[EDX_SEARCH] Starting search near LEA at 0x", std::hex, lea_site, + DBG("[EDX_SEARCH] Starting search near LEA at 0x", std::hex, lea_site, " radius=0x", search_radius, std::dec); - + auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; const uint8_t* data = text_data.data(); size_t data_size = text_data.size(); - + const int64_t lea_offset = pe.va_to_file_offset(lea_site); if (lea_offset < 0) { DBG("[EDX_SEARCH] Invalid LEA VA"); return std::nullopt; } - + const size_t lea_in_text = lea_offset - text_sec.file_offset; const size_t search_start = (lea_in_text > search_radius) ? lea_in_text - search_radius : 0; const size_t search_end = std::min(lea_in_text + search_radius, data_size); - - DBG("[EDX_SEARCH] Searching in text range [0x", std::hex, + + DBG("[EDX_SEARCH] Searching in text range [0x", std::hex, text_va_base + search_start, "-0x", text_va_base + search_end, ")", std::dec); - + // Look for "mov edx, 20h" pattern (BA 20 00 00 00) for (size_t i = search_start; i + 4 < search_end; ++i) { - if (data[i] == 0xBA && + if (data[i] == 0xBA && data[i + 1] == 0x20 && data[i + 2] == 0x00 && data[i + 3] == 0x00 && data[i + 4] == 0x00) { - + const uint64_t edx_instr_va = text_va_base + i; DBG("[EDX_SEARCH] Found 'mov edx, 20h' at VA=0x", std::hex, edx_instr_va, std::dec); - + const size_t key_search_start = i + 5; const size_t key_search_end = std::min(key_search_start + 0x200, data_size); - + for (size_t j = key_search_start; j + 6 < key_search_end; ++j) { auto match = match_rip_relative_instruction(data, data_size, j, text_va_base, pe, @@ -260,25 +259,25 @@ std::optional find_key_load_near_mov_edx_20h( break; } } - + DBG("[EDX_SEARCH] No 'mov edx, 20h' pattern found in search radius"); return std::nullopt; } std::optional find_rip_relative_in_range( - const PEImage& pe, const Section& text_sec, + const PEImage& pe, const Section& text_sec, size_t start_offset, size_t end_offset, uint64_t reference_va, bool require_data_section) { - + auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; const uint8_t* data = text_data.data(); size_t data_size = text_data.size(); - + const std::vector allowed_sections = require_data_section ? std::vector{".data"} : std::vector{".data", ".rdata"}; - + for (size_t i = start_offset; i + 6 < end_offset && i + 6 < data_size; ++i) { auto match = match_rip_relative_instruction(data, data_size, i, text_va_base, pe, @@ -287,35 +286,35 @@ std::optional find_rip_relative_in_range( return RipRelativeLoad{match->instruction_va, match->target_va, match->type}; } } - + return std::nullopt; } std::optional find_rip_relative_load_around_va( const PEImage& pe, const Section& text_sec, uint64_t anchor_va, size_t radius) { - + Timer timer("find_rip_relative_load_around_va"); DBG("[AROUND_SCAN] anchor_va=0x", std::hex, anchor_va, " radius=", std::dec, radius); - + const int64_t anchor_offset = pe.va_to_file_offset(anchor_va); if (anchor_offset < 0) { DBG("[AROUND_SCAN] anchor_va is not a valid address."); return std::nullopt; } - + auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; const uint8_t* data = text_data.data(); size_t data_size = text_data.size(); - + const size_t anchor_in_text = anchor_offset - text_sec.file_offset; const size_t search_start = (anchor_in_text > radius) ? anchor_in_text - radius : 0; const size_t search_end = std::min(anchor_in_text + radius, data_size); - - DBG("[AROUND_SCAN] Searching in range [0x", std::hex, - text_va_base + search_start, "-0x", text_va_base + search_end, + + DBG("[AROUND_SCAN] Searching in range [0x", std::hex, + text_va_base + search_start, "-0x", text_va_base + search_end, ") relative to anchor at 0x", anchor_va, std::dec); - + // Pattern 1: Key-loading loop pattern (LEA + MOVZX) for (size_t i = search_start; i + 11 < search_end; ++i) { // Check for LEA pattern: 48 8D 05 ?? ?? ?? ?? @@ -328,20 +327,20 @@ std::optional find_rip_relative_load_around_va( break; } } - + if (valid_modrm) { // Check for MOVZX pattern: 41 0F B6 04 07 if (data[i + 7] == 0x41 && data[i + 8] == MOVZX_OPCODE_1 && data[i + 9] == MOVZX_OPCODE_2 && data[i + 10] == 0x04 && data[i + 11] == 0x07) { - + int32_t disp; std::memcpy(&disp, &data[i + 3], sizeof(disp)); - + const uint64_t instr_va = text_va_base + i; const uint64_t rip_after = instr_va + 7; const uint64_t target_va = rip_after + disp; - + if (pe.read_va(target_va, 32)) { DBG("[AROUND_SCAN] Found PATTERN 1 at VA=0x", std::hex, instr_va); return RipRelativeLoad{instr_va, target_va, LoadType::LEA_ADDRESS}; @@ -350,18 +349,18 @@ std::optional find_rip_relative_load_around_va( } } } - + // Pattern 2: MOV EDX, 20h followed by RIP-relative instruction for (size_t i = search_start; i + 4 < search_end; ++i) { - if (data[i] == 0xBA && + if (data[i] == 0xBA && data[i + 1] == 0x20 && data[i + 2] == 0x00 && data[i + 3] == 0x00 && data[i + 4] == 0x00) { - + const size_t pattern_start = i + 5; const size_t pattern_end = std::min(i + 0x100, search_end); - + for (size_t j = pattern_start; j + 6 < pattern_end; ++j) { auto match = match_rip_relative_instruction(data, data_size, j, text_va_base, pe, @@ -370,7 +369,7 @@ std::optional find_rip_relative_load_around_va( // Check if it points to valid 32-byte data uint64_t final_va = match->type == LoadType::MOV_DEREF ? *pe.read_u64_va(match->target_va) : match->target_va; - + if (pe.read_va(final_va, 32)) { DBG("[AROUND_SCAN] Found PATTERN 2 at VA=0x", std::hex, match->instruction_va); return RipRelativeLoad{match->instruction_va, match->target_va, match->type}; @@ -379,11 +378,11 @@ std::optional find_rip_relative_load_around_va( } } } - + // Pattern 3: General search with filtering std::optional best_match; size_t best_distance = std::numeric_limits::max(); - + for (size_t i = search_start; i + 6 < search_end; ++i) { auto match = match_rip_relative_instruction(data, data_size, i, text_va_base, pe, @@ -392,195 +391,25 @@ std::optional find_rip_relative_load_around_va( if (match->instruction_va == anchor_va) { continue; } - + const size_t distance = (i > anchor_in_text) ? (i - anchor_in_text) : (anchor_in_text - i); - + uint64_t final_va = match->type == LoadType::MOV_DEREF ? *pe.read_u64_va(match->target_va) : match->target_va; - + if (pe.read_va(final_va, 32) && distance < best_distance) { best_match = match; best_distance = distance; } } } - + if (best_match) { DBG("[AROUND_SCAN] Selected candidate at VA=0x", std::hex, best_match->instruction_va, " distance=0x", best_distance, std::dec); return RipRelativeLoad{best_match->instruction_va, best_match->target_va, best_match->type}; } - + DBG("[AROUND_SCAN] No valid MOV/LEA found in radius."); return std::nullopt; -======= -#include "pe_patterns.h" -#include "common/timer.h" -#include "common/utils.h" - -#include -#include -#include -#include - -std::vector find_subsequence( - std::span haystack, - size_t start, - size_t length, - std::string_view needle) -{ - Timer timer("find_subsequence '" + std::string(needle) + "'", false); - if (start + length > haystack.size()) { - length = haystack.size() - start; - } - - std::vector found_indices; - auto search_area = haystack.subspan(start, length); - std::span needle_span( - reinterpret_cast(needle.data()), - needle.size() - ); - - auto it = search_area.begin(); - while (true) { - it = std::search(it, search_area.end(), needle_span.begin(), needle_span.end()); - if (it == search_area.end()) { - break; - } - // Calculate offset relative to the full haystack, not the subspan - size_t absolute_offset = (it - haystack.begin()); - found_indices.push_back(absolute_offset); - ++it; // Continue search after the found occurrence - } - - timer.print_manual(std::string(needle), needle.length()); - return found_indices; -} - -bool is_va_in_section(uint64_t va, const PEImage& pe, const Section& section) { - uint64_t start_va = pe.get_image_base() + section.virtual_address; - uint64_t end_va = start_va + section.virtual_size; - bool in_section = (va >= start_va && va < end_va); - - // DBG call is useful but can be very noisy, so it's good to have it conditional - if (is_debug_enabled()) { - DBG("[is_va_in_section] VA=0x", std::hex, va, " section=", section.name, - " range=[0x", start_va, ", 0x", end_va, ") -> ", std::boolalpha, in_section, std::dec); - } - return in_section; -} - -uint64_t find_lea_to_target_va(const PEImage& pe, const Section& text_sec, uint64_t target_va) { - Timer timer("find_lea_to_target_va"); - DBG("[find_lea] target_va=0x", std::hex, target_va, std::dec); - - auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); - if (text_data.size() < 7) return 0; - - const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; - - // A set of valid ModR/M bytes for [RIP + disp32] addressing with any register operand. - // The format is 00_REG_101. - static const std::unordered_set valid_modrm = { - 0x05, 0x0D, 0x15, 0x1D, 0x25, 0x2D, 0x35, 0x3D - }; - - // We are looking for REX.W + 8D + ModR/M + disp32. - // Start search at index 1 to allow checking for REX prefix at i-1. - for (size_t i = 1; i < text_data.size() - 6; ++i) { - // Opcode for LEA is 0x8D - if (text_data[i] == 0x8D) { - uint8_t rex = text_data[i - 1]; - // REX.W prefix for 64-bit operand (0x48-0x4F) - if ((rex & 0xF8) == 0x48) { - uint8_t modrm = text_data[i + 1]; - if (valid_modrm.count(modrm)) { - int32_t disp; - std::memcpy(&disp, &text_data[i + 2], sizeof(disp)); - - uint64_t instr_va = text_va_base + (i - 1); - uint64_t rip_after = instr_va + 7; // RIP is value after the instruction - uint64_t calculated_target = rip_after + disp; - - if (calculated_target == target_va) { - DBG("[find_lea] Found LEA at VA=0x", std::hex, instr_va); - return instr_va; - } - } - } - } - } - DBG("[find_lea] No matching LEA instruction found."); - return 0; -} - -std::optional find_rip_relative_load_in_window( - const PEImage& pe, const Section& text_sec, uint64_t from_va, size_t window) -{ - Timer timer("find_rip_relative_load_in_window"); - DBG("[LOAD_SCAN] from_va=0x", std::hex, from_va, " window=", std::dec, window); - - int64_t start_offset = pe.va_to_file_offset(from_va); - if (start_offset < 0) { - DBG("[LOAD_SCAN] from_va is not a valid address."); - return std::nullopt; - } - - auto text_data = pe.get_raw_data().subspan(text_sec.file_offset, text_sec.file_size); - size_t search_start = start_offset - text_sec.file_offset; - size_t search_end = std::min(search_start + window, text_data.size()); - - const uint64_t text_va_base = pe.get_image_base() + text_sec.virtual_address; - - for (size_t i = search_start; i + 6 < search_end; ++i) { - if ((text_data[i] & 0xF8) == 0x48) { // REX.W prefix (0x48-0x4F) - uint8_t opcode = text_data[i + 1]; - - if (opcode == 0x8B || opcode == 0x8D) { // MOV or LEA - uint8_t modrm = text_data[i + 2]; - if ((modrm & 0xC7) == 0x05) { // Check for RIP-relative addressing - int32_t disp; - std::memcpy(&disp, &text_data[i + 3], sizeof(disp)); - - uint64_t instr_va = text_va_base + i; - uint64_t rip_after = instr_va + 7; - uint64_t target_va = rip_after + disp; - - uint64_t final_blob_va = 0; - bool is_valid = false; - - if (opcode == 0x8B) { // MOV - auto ptr_opt = pe.read_u64_va(target_va); - if (!ptr_opt) { - continue; - } - final_blob_va = *ptr_opt; - } else { // LEA - final_blob_va = target_va; - } - - // Check if the FINAL address (after any dereferencing) is in a .data section. - for (const auto& s : pe.get_sections()) { - if (s.name.rfind(".data", 0) == 0) { // Matches .data, .data1, etc. - if (is_va_in_section(final_blob_va, pe, s)) { - is_valid = true; - break; - } - } - } - - if (is_valid) { - LoadType type = (opcode == 0x8B) ? LoadType::MOV_DEREF : LoadType::LEA_ADDRESS; - DBG("[LOAD_SCAN] Found valid ", (type == LoadType::MOV_DEREF ? "MOV" : "LEA"), " at VA=0x", std::hex, instr_va, - " leading to final VA=0x", final_blob_va, " in a .data section.", std::dec); - return RipRelativeLoad{instr_va, target_va, type}; - } - } - } - } - } - - DBG("[LOAD_SCAN] No valid MOV/LEA found in window."); - return std::nullopt; ->>>>>>> Stashed changes } \ No newline at end of file