Move reverse engineering artifacts under contrib
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Map REDscript/native registration strings to nearby code pointer candidates."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
from find_pe_string_xrefs import Section, parse_pe
|
||||
|
||||
|
||||
def section_for_rva(sections: list[Section], rva: int) -> Section | None:
|
||||
for section in sections:
|
||||
if section.contains_rva(rva):
|
||||
return section
|
||||
return None
|
||||
|
||||
|
||||
def is_readable_data(section: Section) -> bool:
|
||||
return bool(section.characteristics & 0x40000000) and not section.is_code
|
||||
|
||||
|
||||
def iter_ascii_strings(data: bytes, section: Section, min_len: int) -> list[tuple[int, str]]:
|
||||
start = section.raw_offset
|
||||
end = min(len(data), section.raw_offset + section.raw_size)
|
||||
pattern = re.compile(rb"[\x20-\x7e]{%d,}" % min_len)
|
||||
return [
|
||||
(section.virtual_address + match.start(), match.group(0).decode("ascii", "replace"))
|
||||
for match in pattern.finditer(data[start:end])
|
||||
]
|
||||
|
||||
|
||||
def read_i32(data: bytes, offset: int) -> int | None:
|
||||
if offset < 0 or offset + 4 > len(data):
|
||||
return None
|
||||
return struct.unpack_from("<i", data, offset)[0]
|
||||
|
||||
|
||||
def find_rip_disp_xrefs(data: bytes, sections: list[Section], image_base: int, target_rvas: set[int]) -> dict[int, list[int]]:
|
||||
target_vas = {image_base + rva: rva for rva in target_rvas}
|
||||
hits = {rva: [] for rva in target_rvas}
|
||||
for section in sections:
|
||||
if not section.is_code:
|
||||
continue
|
||||
|
||||
start = section.raw_offset
|
||||
end = min(len(data), section.raw_offset + section.raw_size)
|
||||
for offset in range(start, max(start, end - 4)):
|
||||
disp = read_i32(data, offset)
|
||||
if disp is None:
|
||||
continue
|
||||
instr_end_va = image_base + section.virtual_address + (offset - section.raw_offset) + 4
|
||||
target_rva = target_vas.get(instr_end_va + disp)
|
||||
if target_rva is not None:
|
||||
hits[target_rva].append(section.virtual_address + (offset - section.raw_offset))
|
||||
return hits
|
||||
|
||||
|
||||
def iter_nearby_rip_code_targets(
|
||||
data: bytes,
|
||||
sections: list[Section],
|
||||
image_base: int,
|
||||
code_section: Section,
|
||||
center_disp_rva: int,
|
||||
radius: int,
|
||||
) -> list[tuple[int, int, str]]:
|
||||
# Common x64 RIP-relative LEA/MOV forms used by the generated registration
|
||||
# thunks to pass function pointers and type descriptors around.
|
||||
patterns: tuple[tuple[bytes, int, str], ...] = (
|
||||
(b"\x48\x8d\x05", 3, "lea rax"),
|
||||
(b"\x48\x8d\x0d", 3, "lea rcx"),
|
||||
(b"\x48\x8d\x15", 3, "lea rdx"),
|
||||
(b"\x48\x8d\x1d", 3, "lea rbx"),
|
||||
(b"\x48\x8d\x35", 3, "lea rsi"),
|
||||
(b"\x48\x8d\x3d", 3, "lea rdi"),
|
||||
(b"\x4c\x8d\x05", 3, "lea r8"),
|
||||
(b"\x4c\x8d\x0d", 3, "lea r9"),
|
||||
(b"\x4c\x8d\x15", 3, "lea r10"),
|
||||
(b"\x4c\x8d\x1d", 3, "lea r11"),
|
||||
(b"\x4c\x8d\x25", 3, "lea r12"),
|
||||
(b"\x4c\x8d\x2d", 3, "lea r13"),
|
||||
(b"\x4c\x8d\x35", 3, "lea r14"),
|
||||
(b"\x4c\x8d\x3d", 3, "lea r15"),
|
||||
)
|
||||
|
||||
center_off = code_section.rva_to_offset(center_disp_rva)
|
||||
start = max(code_section.raw_offset, center_off - radius)
|
||||
end = min(len(data), code_section.raw_offset + code_section.raw_size, center_off + radius)
|
||||
out: list[tuple[int, int, str]] = []
|
||||
|
||||
for offset in range(start, end):
|
||||
for prefix, disp_start, label in patterns:
|
||||
if data[offset : offset + len(prefix)] != prefix:
|
||||
continue
|
||||
|
||||
disp = read_i32(data, offset + disp_start)
|
||||
if disp is None:
|
||||
continue
|
||||
|
||||
instr_rva = code_section.virtual_address + (offset - code_section.raw_offset)
|
||||
instr_end_va = image_base + instr_rva + disp_start + 4
|
||||
target_rva = instr_end_va + disp - image_base
|
||||
target_section = section_for_rva(sections, target_rva)
|
||||
if target_section and target_section.is_code:
|
||||
out.append((instr_rva, target_rva, label))
|
||||
|
||||
return sorted(set(out))
|
||||
|
||||
|
||||
def likely_registered_code(ref_rva: int, candidates: list[tuple[int, int, str]]) -> tuple[int, int, str] | None:
|
||||
for candidate in candidates:
|
||||
instr_rva, _target_rva, label = candidate
|
||||
if instr_rva > ref_rva and label == "lea rax":
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("pe", type=Path)
|
||||
parser.add_argument("--pattern", required=True, help="case-insensitive regex for registration strings")
|
||||
parser.add_argument("--min-len", type=int, default=5)
|
||||
parser.add_argument("--radius", type=int, default=192)
|
||||
parser.add_argument("--max-strings", type=int, default=200)
|
||||
args = parser.parse_args()
|
||||
|
||||
data = args.pe.read_bytes()
|
||||
image_base, sections = parse_pe(data)
|
||||
regex = re.compile(args.pattern, re.IGNORECASE)
|
||||
|
||||
strings: list[tuple[int, str]] = []
|
||||
for section in sections:
|
||||
if is_readable_data(section):
|
||||
strings.extend((rva, text) for rva, text in iter_ascii_strings(data, section, args.min_len) if regex.search(text))
|
||||
strings = sorted(strings, key=lambda item: (item[1].lower(), item[0]))
|
||||
|
||||
xrefs = find_rip_disp_xrefs(data, sections, image_base, {rva for rva, _ in strings})
|
||||
print(f"image_base=0x{image_base:x}")
|
||||
print(f"matches={len(strings)} pattern={args.pattern!r}")
|
||||
|
||||
emitted = 0
|
||||
for string_rva, text in strings:
|
||||
refs = xrefs.get(string_rva, [])
|
||||
if not refs:
|
||||
continue
|
||||
|
||||
print(f"name={text!r} string_rva=0x{string_rva:x} xrefs={len(refs)}")
|
||||
for ref in refs[:12]:
|
||||
code_section = section_for_rva(sections, ref)
|
||||
if not code_section:
|
||||
continue
|
||||
candidates = iter_nearby_rip_code_targets(data, sections, image_base, code_section, ref, args.radius)
|
||||
print(f" xref_disp_rva=0x{ref:x} nearby_code_ptrs={len(candidates)}")
|
||||
likely = likely_registered_code(ref, candidates)
|
||||
if likely:
|
||||
instr_rva, target_rva, label = likely
|
||||
print(f" likely_registered_code {label} instr_rva=0x{instr_rva:x} target_rva=0x{target_rva:x}")
|
||||
for instr_rva, target_rva, label in candidates[:16]:
|
||||
print(f" {label} instr_rva=0x{instr_rva:x} target_rva=0x{target_rva:x}")
|
||||
if len(candidates) > 16:
|
||||
print(f" ... +{len(candidates) - 16}")
|
||||
|
||||
emitted += 1
|
||||
if emitted >= args.max_strings:
|
||||
break
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user