This commit is contained in:
inliver233
2026-10-24 01:50:24 +08:00
committed by dpkg123
commit 863fe6b1bf
98 changed files with 28849 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
"""Locator comparison between two builds of the same fixture.
`--across` answers one question: does the pattern still match the same number of sites on the
other build? A count that changes is a statement about the signature, not about the target, and
the point of these tests is that each of the three answers (same, changed, unusable either way)
is distinguishable in the output and in the exit code.
"""
import json
import shutil
import pytest
pytestmark = pytest.mark.integration
@pytest.fixture
def rebuilt(run_script, tmp_path, fixtures):
"""A second, independently built copy of the same recipe."""
out = tmp_path / "rebuilt.exe"
result = run_script("make_fixture_pe.py", "--out", out, "--recipe", fixtures.pe64.recipe)
assert result.returncode == 0, result.stderr
return out
def test_same_recipe_reports_an_unchanged_hit_count(run_script, fixtures, rebuilt, hexpat):
result = run_script("aob_scan.py", fixtures.pe64.path, "--patterns", hexpat(b"fixture.pdb"),
"--across", rebuilt, "--json")
assert result.returncode == 0, result.stdout
payload = json.loads(result.stdout)
entry = payload["patterns"][0]
assert entry["hits"] == entry["across"]["hits"] == 1
assert entry["across"]["same_count"] is True
assert entry["across"]["verdict"] == "unique"
assert payload["result"] == "ok"
def test_a_recipe_without_the_debug_directory_changes_the_hit_count(run_script, fixtures,
hexpat):
"""The `bare` recipe has no debug directory, so the PDB pattern has nothing to match."""
result = run_script("aob_scan.py", fixtures.pe64.path, "--patterns", hexpat(b"fixture.pdb"),
"--across", fixtures.bare.path, "--json")
assert result.returncode == 1
payload = json.loads(result.stdout)
entry = payload["patterns"][0]
assert entry["hits"] == 1
assert entry["across"]["hits"] == 0
assert entry["across"]["same_count"] is False
assert entry["across"]["verdict"] == "no-match"
assert entry["across"]["sites"] == []
assert payload["result"] == "needs-attention"
def test_an_equal_hit_count_is_not_the_same_as_a_usable_locator(run_script, fixtures):
"""Two hits on both sides: the count agrees and the pattern is still a coin flip."""
result = run_script("aob_scan.py", fixtures.pe64.path, "--patterns", fixtures.pe64.plant,
"--across", fixtures.bare.path, "--json")
assert result.returncode == 1
payload = json.loads(result.stdout)
entry = payload["patterns"][0]
assert entry["hits"] == entry["across"]["hits"] == 2
assert entry["across"]["same_count"] is True
assert entry["verdict"] == "ambiguous"
assert payload["result"] == "needs-attention"
def test_the_text_view_explains_a_changed_count(run_script, fixtures, hexpat):
result = run_script("aob_scan.py", fixtures.pe64.path, "--patterns", hexpat(b"fixture.pdb"),
"--across", fixtures.bare.path)
assert result.returncode == 1
assert "hit count changed between builds" in result.stdout
assert "across" in result.stdout
def test_the_text_view_explains_an_absent_pattern(run_script, fixtures, hexpat):
result = run_script("aob_scan.py", fixtures.bare.path, "--patterns", hexpat(b"fixture.pdb"))
assert result.returncode == 1
assert "does not exist in this build" in result.stdout
def test_a_patch_invalidates_the_signature_it_was_located_with(run_script, fixtures, tmp_path,
hexpat):
"""The complete lesson of the two tools together, in one flow.
The pattern that located the site includes the byte that gets patched, so after the patch the
pattern no longer matches that site. `--across` between the original and the patched copy is
what turns that from a silent hazard into a reported count change.
"""
patched = tmp_path / "patched.exe"
shutil.copyfile(str(fixtures.pe64.path), str(patched))
applied = run_script("pepatch.py", patched, "--pattern", fixtures.pe64.plant,
"--at-hit", "0", "--expect", fixtures.pe64.plant,
"--bytes", " ".join(fixtures.pe64.plant.split()[:-2] + ["EB", "05"]),
"--out", patched)
assert applied.returncode == 0, applied.stderr
located_first = run_script("aob_scan.py", patched, "--patterns", fixtures.pe64.plant, "--json")
assert located_first.returncode == 0
after = json.loads(located_first.stdout)["patterns"][0]
assert after["hits"] == 1, "the patched site no longer matches the pattern it was found with"
assert after["verdict"] == "unique"
assert after["sites"][0]["offset_hex"] == fixtures.pe64.meta["plant"]["second_file_offset"]
# The hazard, made concrete: the same pattern with the same index now selects a different
# site than it did before the patch, without any error from either tool.
dry = run_script("pepatch.py", patched, "--pattern", fixtures.pe64.plant, "--at-hit", "0",
"--expect", fixtures.pe64.plant,
"--bytes", " ".join(fixtures.pe64.plant.split()[:-2] + ["EB", "05"]),
"--dry-run", "--json")
assert dry.returncode == 0
assert json.loads(dry.stdout)["applied"][0]["offset"] == \
int(fixtures.pe64.meta["plant"]["second_file_offset"], 16)
# Unpatched copy on the left, patched copy on the right: the counts disagree, and that is
# reported rather than smoothed over.
comparison = run_script("aob_scan.py", fixtures.pe64.path, "--patterns",
fixtures.pe64.plant, "--across", patched, "--json")
assert comparison.returncode == 1
entry = json.loads(comparison.stdout)["patterns"][0]
assert entry["hits"] == 2
assert entry["across"]["hits"] == 1
assert entry["across"]["same_count"] is False
def test_comparing_against_a_non_pe_build_exits_three(run_script, fixtures, tmp_path):
other = tmp_path / "not-a-pe.txt"
other.write_text("this is not a portable executable\n", encoding="utf-8")
result = run_script("aob_scan.py", fixtures.pe64.path, "--patterns", fixtures.pe64.plant,
"--across", other)
assert result.returncode == 3
assert "--across" in result.stderr
assert "Traceback" not in result.stderr
def test_comparing_against_a_missing_build_exits_three(run_script, fixtures, tmp_path):
result = run_script("aob_scan.py", fixtures.pe64.path, "--patterns", fixtures.pe64.plant,
"--across", tmp_path / "absent.exe")
assert result.returncode == 3
assert "Traceback" not in result.stderr
@@ -0,0 +1,220 @@
"""End-to-end flows over the generated fixture: locate, patch, re-read, verify.
No debugger, no driver, no network and no live target takes part. Every step is a script call,
and the assertions are on what the next script in the chain observes -- which is the property
that matters, because these tools are meant to agree with each other about one image.
"""
import hashlib
import json
import shutil
import pytest
pytestmark = pytest.mark.integration
def sha256(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
@pytest.fixture
def work(tmp_path, fixtures):
"""An isolated copy of the PE32+ fixture, so a flow can never disturb another test."""
target = tmp_path / "sample.exe"
shutil.copyfile(str(fixtures.pe64.path), str(target))
return target
def test_locate_patch_reread_round_trip(run_script, fixtures, work, tmp_path, hexpat):
"""The full chain, with each tool checked against what the next one reports."""
patched = tmp_path / "patched.exe"
first, second = fixtures.pe64.plant_offsets
# 1. locate: the planted sequence is deliberately ambiguous, and the locator says so.
located = run_script("aob_scan.py", work, "--patterns", fixtures.pe64.plant, "--json")
assert located.returncode == 1
plan = json.loads(located.stdout)
assert plan["patterns"][0]["verdict"] == "ambiguous"
site = plan["patterns"][0]["sites"][0]
# 2. patch the first site only, keeping the replacement the same length as the site.
replacement = " ".join(fixtures.pe64.plant.split()[:-2] + ["EB", "05"])
applied = run_script("pepatch.py", work, "--pattern", fixtures.pe64.plant,
"--at-hit", "0", "--expect", fixtures.pe64.plant,
"--bytes", replacement, "--out", patched, "--json")
assert applied.returncode == 0, applied.stderr
result = json.loads(applied.stdout)
claim = result["applied"][0]
assert result["patches_applied"] == 1
assert result["verify_problems"] == []
# 3. the locator and the patcher must be talking about the same bytes.
assert int(site["offset_hex"], 16) == claim["offset"] == first
assert int(site["rva_hex"], 16) == int(claim["rva"], 16)
assert claim["before"] == fixtures.pe64.plant
assert claim["after"] == replacement
assert claim["equal_length"] is True
# 4. re-read the written file: the patch landed, the control site did not move, and the
# checksum was recomputed rather than left stale.
reread = run_script("peutil.py", patched, "--summary", "--check-sum", "--json")
assert reread.returncode == 0, reread.stderr
report = json.loads(reread.stdout)
assert report["summary"]["checksum_matches"] is True
assert report["summary"]["checksum_declared"] == report["summary"]["checksum_computed"]
assert report["checksum"]["matches"] is True
assert report["summary"]["file_size"] == fixtures.pe64.size
body = patched.read_bytes()
assert body[first:first + 12] == bytes(int(b, 16) for b in replacement.split())
assert body[second:second + 12] == fixtures.pe64.data[second:second + 12]
# 5. nothing outside the site and the checksum field moved.
checksum_field = int(fixtures.pe64.meta["checksum_offset"], 16)
diff = {i for i, (a, b) in enumerate(zip(fixtures.pe64.data, body)) if a != b}
assert diff - {first + 10} <= set(range(checksum_field, checksum_field + 4))
assert first + 10 in diff
def test_control_run_that_changes_nothing_leaves_the_file_identical(run_script, fixtures, work,
tmp_path):
"""The control for the flow above: a patch whose bytes equal the site's own bytes.
This is the only zero-change run the pipeline has: a spec with an empty patch array is
refused as a usage error, so a no-op patch is what "the pipeline ran and changed nothing"
looks like. The output must be byte-identical to the input, checksum included.
"""
out = tmp_path / "noop.exe"
before = sha256(work)
result = run_script("pepatch.py", work, "--at-offset", hex(fixtures.pe64.plant_offsets[0]),
"--expect", fixtures.pe64.plant, "--bytes", fixtures.pe64.plant,
"--out", out, "--json")
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["checksum_recomputed"] is True
assert payload["checksum_after"]["matches"] is True
assert payload["output_sha256"] == payload["input_sha256"] == before
assert sha256(out) == before
assert sha256(work) == before
def test_an_empty_patch_array_is_refused_rather_than_writing_a_copy(run_script, fixtures, work,
tmp_path):
spec = tmp_path / "empty.json"
spec.write_text('{"patches": []}', encoding="utf-8")
out = tmp_path / "copy.exe"
result = run_script("pepatch.py", work, "--spec", spec, "--out", out)
assert result.returncode == 2
assert not out.exists()
assert sha256(work) == sha256(fixtures.pe64.path)
def test_the_flow_stops_before_writing_when_the_pattern_is_ambiguous(run_script, fixtures, work,
tmp_path):
out = tmp_path / "never.exe"
before = sha256(work)
result = run_script("pepatch.py", work, "--pattern", fixtures.pe64.plant,
"--bytes", "90 " * 12, "--out", out)
assert result.returncode == 1
assert "matched 2 sites" in result.stderr
assert not out.exists()
assert sha256(work) == before
def test_dry_run_reports_the_plan_and_writes_nothing(run_script, fixtures, work, tmp_path):
out = tmp_path / "would-be.exe"
before = sha256(work)
result = run_script("pepatch.py", work, "--pattern", fixtures.pe64.plant, "--at-hit", "1",
"--expect", fixtures.pe64.plant, "--bytes", "CC " * 12,
"--dry-run", "--json")
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["dry_run"] is True
assert payload["patches_applied"] == 1
assert payload["applied"][0]["offset"] == fixtures.pe64.plant_offsets[1]
assert not out.exists()
assert sha256(work) == before
def test_a_spec_file_can_reach_a_branch_inside_a_longer_pattern(run_script, fixtures, work,
tmp_path):
"""The pattern-offset flow: anchor on a unique longer sequence, then patch the byte inside."""
out = tmp_path / "branch.exe"
site = fixtures.pe64.plant_offsets[0]
spec = tmp_path / "spec.json"
spec.write_text(json.dumps({"patches": [{
"name": "gate",
"pattern": fixtures.pe64.plant + " CC",
"pattern-offset": 10,
"expect": "74",
"bytes": "EB",
"why": "invert the conditional branch, keeping the taken distance",
}]}), encoding="utf-8")
result = run_script("pepatch.py", work, "--spec", spec, "--out", out, "--json")
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["patches_applied"] == 1
record = payload["applied"][0]
assert record["offset"] == site + 10
assert record["before"] == "74"
assert record["after"] == "EB"
assert record["length_basis"] == "expect"
assert record["why"].startswith("invert")
reread = run_script("peutil.py", out, "--summary", "--check-sum", "--json")
assert reread.returncode == 0
assert json.loads(reread.stdout)["checksum"]["matches"] is True
assert out.read_bytes()[site + 10] == 0xEB
assert out.read_bytes()[site + 11] == 0x05
def test_a_length_change_is_refused_before_anything_is_written(run_script, fixtures, work,
tmp_path):
out = tmp_path / "resized.exe"
result = run_script("pepatch.py", work, "--at-offset", hex(fixtures.pe64.plant_offsets[0]),
"--expect", "48", "--bytes", "CC CC CC", "--out", out)
assert result.returncode == 1
assert "--allow-resize" in result.stderr
assert not out.exists()
allowed = tmp_path / "resized-allowed.exe"
forced = run_script("pepatch.py", work, "--at-offset", hex(fixtures.pe64.plant_offsets[0]),
"--expect", "48", "--bytes", "CC CC CC", "--allow-resize",
"--out", allowed, "--json")
assert forced.returncode == 0, forced.stderr
payload = json.loads(forced.stdout)
assert payload["structural"] == ["cli"]
# One byte replaced by three: the file grows by two, and that is the structural edit the
# caller asked for by passing --allow-resize.
assert payload["output_size"] == payload["input_size"] + 2
def test_the_patched_output_still_parses_as_an_image(run_script, fixtures, work, tmp_path,
hexpat):
"""A patch that lands in the file must not damage a single header the reader trusts."""
out = tmp_path / "patched.exe"
result = run_script("pepatch.py", work, "--at-offset", "0x410", "--expect", "48 8B",
"--bytes", "CC 8B", "--out", out)
assert result.returncode == 0, result.stderr
full = run_script("peutil.py", out, "--summary", "--sections", "--imports", "--exports",
"--resources", "--tls", "--debug", "--relocs", "--check-sum", "--json")
assert full.returncode == 0, full.stderr
report = json.loads(full.stdout)
assert report["summary"]["sha256"] == sha256(out)
assert report["checksum"]["matches"] is True
assert [s["name"] for s in report["summary"]["sections"]] == \
[".text", ".rdata", ".data", ".rsrc", ".reloc"]
assert report["resources"][0]["path"] == ["VERSION", "1", "1033"]
assert report["tls"]["callback_rvas"] == ["0x1000"]
assert report["debug"][0]["format"] == "RSDS"
assert report["imports"][0]["functions"][0]["name"] == "ImportedFunction"
def test_a_real_platform_image_reproduces_its_declared_checksum(run_script, platform_binary):
"""The fixture is synthetic; this is the same claim measured on a real system image."""
result = run_script("peutil.py", platform_binary, "--summary", "--check-sum", "--json")
assert result.returncode == 0, result.stderr
report = json.loads(result.stdout)
assert report["summary"]["checksum_matches"] is True
assert report["checksum"]["declared"] == report["checksum"]["computed"]
assert report["summary"]["file_size"] == platform_binary.stat().st_size
assert report["summary"]["sections"], "a platform image must have a section table"