Files
exe-reverse/tests/unit/test_dart_snapshot_strings.py
2026-10-24 01:50:24 +08:00

227 lines
11 KiB
Python

"""Unit tests for skills/exe-reverse/scripts/dart_snapshot_strings.py, the Dart AOT string reader.
The input is an ELF shared object and no sample may be committed, so the container is generated by
`conftest.build_synthetic_elf`: a valid ELF64 header, a `.text` of sixteen bytes, a `.rodata` with
one one-byte (ASCII) literal and one two-byte (UTF-16LE) literal at known offsets, and a
section-name string table. Offsets are read from the fixture's own record of where it put each
literal, never hardcoded here.
What the synthetic image cannot do is be a real snapshot. Literal recovery is what these tests hold:
a one-byte and a two-byte literal at known offsets, a case-insensitive filter, and an offset probe
that resolves anywhere a literal occupies. The instruction-level observations this file once carried
are **withdrawn** -- a byte scan cannot read this format, and the reference records why -- so nothing
here asserts anything about how a reference is encoded.
"""
import pytest
pytestmark = pytest.mark.unit
ONE_BYTE = "one-byte"
TWO_BYTE = "two-byte"
# The two idioms the observer counts, assembled here so the positive cases are built rather than
# asserted from a table. `49 8B /r` is a load through r15 -- Dart's pool pointer; `48 8B 05` is a
# RIP-relative load, which the docstring records as near-absent in a real image.
POOL_LOAD = bytes((0x49, 0x8B, 0x87, 0x10, 0x00, 0x00, 0x00))
FILLER = bytes((0x90,)) * 8
def text_section(data, vaddr=0x1000, offset=0):
return {"offset": offset, "vaddr": vaddr, "size": len(data)}
def hex_bytes(*values):
return bytes(values)
# --- the container -----------------------------------------------------------
def test_the_fixture_is_an_elf_shared_object_the_reader_accepts(load_script, synthetic_elf):
dss = load_script("dart_snapshot_strings.py")
elf = dss.Elf(synthetic_elf.data, str(synthetic_elf.path))
assert elf.machine == 0x3E
assert elf.etype == 3
described = elf.describe()
assert described["machine"] == "x86-64"
assert described["type"] == "shared-object"
assert [s["name"] for s in described["sections"]] == [".text", ".rodata", ".shstrtab"]
def test_the_fixture_is_the_size_the_builder_promised(synthetic_elf):
assert len(synthetic_elf.data) == synthetic_elf.size
assert synthetic_elf.size < 0x1000, "a virtual address above this cannot be a file offset"
def test_sections_are_found_by_name(load_script, synthetic_elf):
dss = load_script("dart_snapshot_strings.py")
elf = dss.Elf(synthetic_elf.data)
text = elf.section(".text")
rodata = elf.section(".rodata")
assert text["offset"] == synthetic_elf.text["offset"]
assert text["vaddr"] == synthetic_elf.text["vaddr"]
assert text["size"] == synthetic_elf.text["size"]
assert rodata["offset"] == synthetic_elf.rodata["offset"]
assert rodata["vaddr"] == synthetic_elf.rodata["vaddr"]
assert rodata["size"] == synthetic_elf.rodata["size"]
assert elf.section(".absent") is None
@pytest.mark.parametrize("mutation,message", [
(lambda d: bytearray(b"not an elf image at all" + bytes(64)), "not an ELF image"),
(lambda d: bytearray(b"\x7fELF" + d[4:]), None), # a valid image
(lambda d: bytearray(d[:4] + bytes((1,)) + d[5:]), "not a 64-bit ELF"),
(lambda d: bytearray(d[:5] + bytes((2,)) + d[6:]), "not little-endian"),
(lambda d: bytearray(d[:0x30]), "not an ELF image"), # truncated header
])
def test_the_reader_refuses_a_container_it_cannot_read(load_script, synthetic_elf, mutation,
message):
"""A refusal is a ValueError with a reason; the CLI turns it into the documented exit code."""
dss = load_script("dart_snapshot_strings.py")
data = mutation(bytearray(synthetic_elf.data))
if message is None:
assert dss.Elf(bytes(data)).etype == 3
return
with pytest.raises(ValueError) as excinfo:
dss.Elf(bytes(data))
assert message in str(excinfo.value)
def test_the_address_maps_round_trip(load_script, synthetic_elf):
dss = load_script("dart_snapshot_strings.py")
elf = dss.Elf(synthetic_elf.data)
for entry in synthetic_elf.literals:
assert elf.vaddr_to_offset(entry["vaddr"]) == entry["offset"]
assert elf.offset_to_vaddr(entry["offset"]) == entry["vaddr"]
assert elf.vaddr_to_offset(0xDEADBEEF) is None
assert elf.offset_to_vaddr(0xFFFFFF) is None
# --- literal recovery --------------------------------------------------------
def test_every_literal_in_the_fixture_is_recovered_at_the_offset_it_was_placed(
load_script, synthetic_elf):
dss = load_script("dart_snapshot_strings.py")
elf = dss.Elf(synthetic_elf.data)
recovered = dss.recover_strings(synthetic_elf.data, elf.section(".rodata"))
assert [(off, va, text, enc) for off, va, text, enc in recovered] == [
(entry["offset"], entry["vaddr"], entry["text"], entry["encoding"])
for entry in synthetic_elf.literals]
def test_both_encodings_are_recovered(load_script, synthetic_elf):
"""A keyword search that only reads ASCII misses half of a localised application's literals."""
dss = load_script("dart_snapshot_strings.py")
elf = dss.Elf(synthetic_elf.data)
recovered = dss.recover_strings(synthetic_elf.data, elf.section(".rodata"))
by_encoding = {}
for _off, _va, text, encoding in recovered:
by_encoding.setdefault(encoding, set()).add(text)
assert by_encoding[ONE_BYTE] == {"premium_license", "trial_mode"}
assert by_encoding[TWO_BYTE] == {"vip_pass"}
def test_a_two_byte_literal_reports_byte_offsets_and_character_counts(load_script, synthetic_elf):
"""Pinned, because the two units differ by a factor of two for this encoding.
The offsets are bytes; the text is characters. `--at` filters on `offset <= at < offset +
len(text)`, so for a two-byte literal it accepts only the first half of the bytes the literal
occupies. Measured here as a unit fact; the CLI layer pins the effect.
"""
dss = load_script("dart_snapshot_strings.py")
entry = synthetic_elf.literal("vip_pass")
recovered = dss.recover_strings(synthetic_elf.data,
dss.Elf(synthetic_elf.data).section(".rodata"))
offset, vaddr, text, encoding = [row for row in recovered if row[0] == entry["offset"]][0]
assert (offset, vaddr, encoding) == (entry["offset"], entry["vaddr"], TWO_BYTE)
assert text == "vip_pass"
assert len(text) * 2 == 16, "sixteen bytes on disk, eight characters reported"
def test_a_two_byte_run_does_not_glue_the_tail_of_one_literal_to_the_next(load_script):
"""A run is reported from its own first byte, never from the tail of the literal before it.
This used to be a false positive. The scan accepted *any* printable byte followed by a NUL --
which is exactly the shape of the last character of an ASCII literal plus its alignment byte --
so a two-byte literal beginning right after one was reported as a literal that does not exist,
at an offset inside a different one. Both directions are asserted here, because the cheap fix
for the glue error (skip anything preceded by a character-shaped pair) reintroduces the opposite
one: the real run then starts with its own bytes looking like an interior position.
"""
dss = load_script("dart_snapshot_strings.py")
ascii_part = b"odd_phase_literal\x00"
assert len(ascii_part) % 2 == 0, "the two-byte run needs to start at an even offset"
data = ascii_part + "vip_pass".encode("utf-16-le") + b"\x00\x00"
recovered = dss.recover_strings(data, {"offset": 0, "vaddr": 0x1000, "size": len(data)})
texts = [row[2] for row in recovered]
assert "odd_phase_literal" in texts
assert "vip_pass" in texts, texts
assert "lvip_pass" not in texts, "the tail of the ASCII literal was glued onto the next run"
assert texts.count("vip_pass") == 1
def test_a_two_byte_literal_landing_at_an_odd_offset_is_recovered(load_script):
"""A two-byte run at an odd offset is found, and the run before it does not manufacture one.
Two errors lived here. The scan stepped through the two-byte form from the section base, so a
run shifted to an odd offset -- which one odd-length literal is enough to do -- was never
examined and was missed whole. Advancing one byte at a time fixes that, and the second half of
this test guards the error it introduces: the tail of an even-length one-byte literal is a
printable byte followed by alignment, which reads as the start of a two-byte run and yields a
literal that does not exist.
"""
dss = load_script("dart_snapshot_strings.py")
ascii_part = b"even_phase_liter\x00"
assert len(ascii_part) % 2 == 1, "the two-byte run needs to start at an odd offset"
data = ascii_part + "vip_pass".encode("utf-16-le") + b"\x00\x00"
recovered = dss.recover_strings(data, {"offset": 0, "vaddr": 0x1000, "size": len(data)})
texts = [row[2] for row in recovered]
assert "vip_pass" in texts, texts
assert "even_phase_liter" in texts, texts
# No phantom literal: a run is reported once, from its own first byte.
assert texts.count("vip_pass") == 1
two_byte = [row for row in recovered if row[3] == "two-byte"]
assert len(two_byte) == 1, two_byte
# An even-length one-byte literal before a two-byte run must not start a phantom run.
data2 = b"even_phase_lite\x00" + "vip_pass".encode("utf-16-le") + b"\x00\x00"
got2 = [row[2] for row in dss.recover_strings(
data2, {"offset": 0, "vaddr": 0x1000, "size": len(data2)})]
assert "vip_pass" in got2, got2
assert "vip_pass"[0] not in [t for t in got2 if t != "vip_pass"], got2
def test_min_length_drops_the_shorter_literals(load_script, synthetic_elf):
dss = load_script("dart_snapshot_strings.py")
rodata = dss.Elf(synthetic_elf.data).section(".rodata")
long_only = dss.recover_strings(synthetic_elf.data, rodata, min_length=12)
assert [row[2] for row in long_only] == ["premium_license"]
everything = dss.recover_strings(synthetic_elf.data, rodata, min_length=6)
assert {row[2] for row in everything} == {"premium_license", "trial_mode", "vip_pass"}
def test_the_limit_caps_how_many_literals_come_back(load_script, synthetic_elf):
dss = load_script("dart_snapshot_strings.py")
rodata = dss.Elf(synthetic_elf.data).section(".rodata")
assert len(dss.recover_strings(synthetic_elf.data, rodata, limit=1)) == 1
assert len(dss.recover_strings(synthetic_elf.data, rodata, limit=2)) == 2
def test_a_section_claiming_more_bytes_than_the_file_holds_is_clamped(load_script, synthetic_elf):
"""A truncated image must not read past the end; the reader clamps to the data it has."""
dss = load_script("dart_snapshot_strings.py")
truncated = synthetic_elf.data[:synthetic_elf.rodata["offset"] + 0x10]
rodata = dict(synthetic_elf.rodata, size=synthetic_elf.rodata["size"] + 0x1000)
recovered = dss.recover_strings(truncated, rodata)
assert [row[2] for row in recovered] == ["premium_license"]
def test_the_code_section_yields_no_literals(load_script, synthetic_elf):
"""The fixture's `.text` is sixteen bytes of instructions, so nothing survives the length gate."""
dss = load_script("dart_snapshot_strings.py")
text = dss.Elf(synthetic_elf.data).section(".text")
assert dss.recover_strings(synthetic_elf.data, text) == []
# --- pool observation --------------------------------------------------------