87 lines
5.1 KiB
Python
87 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
import hashlib, json, re, subprocess
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
ROOT = Path('/home/alex/sots-re')
|
|
OUT = ROOT / 'verify/results/research-completion-abi-correction-verifier/run-ee78b8773688ca09f8046e21'
|
|
AUTHOR = ROOT / 'verify/results/research-completion-abi/run-16f8e9b6376b278c4870be09'
|
|
TOOL = Path('/usr/bin/objdump')
|
|
BINARY = ROOT / 'dumps/sots.exe'
|
|
WINDOWS = {
|
|
'turn-get-create': (0x00885380, 0x0088544a),
|
|
'turn-append': (0x00884cb0, 0x00884d8f),
|
|
'nested-copy': (0x00779850, 0x00779a20),
|
|
'nested-dtor': (0x00629580, 0x006295ca),
|
|
}
|
|
EXPECTED = {
|
|
'binary': '970b7de729956a53094c7eb98aba4270aee98e2fed5daf0d39e290013c90c841',
|
|
'tool': '1eaaef2e7f57c4c7f69115c495e2466f5a8c8e5f3bc42221d092382f30f9d4cd',
|
|
}
|
|
ROW = re.compile(rb'^\s*[0-9a-f]+:\s', re.M)
|
|
|
|
def sha(data): return hashlib.sha256(data).hexdigest()
|
|
def rows(data): return [line for line in data.splitlines() if ROW.match(line)]
|
|
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
checks = []
|
|
records = []
|
|
for name, (start, stop) in WINDOWS.items():
|
|
variants = {}
|
|
for suffix, end in (('exact', stop), ('wide', stop + 4)):
|
|
argv = [str(TOOL), '-D', '-Mintel', f'--start-address=0x{start:08x}',
|
|
f'--stop-address=0x{end:08x}', str(BINARY)]
|
|
p = subprocess.run(argv, cwd=ROOT, capture_output=True, check=False)
|
|
for stream, data in (('stdout', p.stdout), ('stderr', p.stderr)):
|
|
(OUT / f'{name}-{suffix}.{stream}.txt').write_bytes(data)
|
|
variants[suffix] = p.stdout
|
|
records.append({'name': name, 'variant': suffix, 'argv': argv,
|
|
'returncode': p.returncode,
|
|
'stdout': {'bytes': len(p.stdout), 'sha256': sha(p.stdout)},
|
|
'stderr': {'bytes': len(p.stderr), 'sha256': sha(p.stderr)}})
|
|
checks += [(f'{name}-{suffix}-return-zero', p.returncode == 0),
|
|
(f'{name}-{suffix}-stdout-nonempty', bool(p.stdout)),
|
|
(f'{name}-{suffix}-stderr-empty', not p.stderr)]
|
|
archived = (AUTHOR / f'{name}.stdout.txt').read_bytes()
|
|
exact_rows, wide_rows = rows(variants['exact']), rows(variants['wide'])
|
|
checks += [(f'{name}-byte-exact-author-compare', variants['exact'] == archived),
|
|
(f'{name}-wide-row-prefix', exact_rows == wide_rows[:len(exact_rows)]),
|
|
(f'{name}-wide-adds-row', len(wide_rows) > len(exact_rows))]
|
|
|
|
get = (OUT / 'turn-get-create-exact.stdout.txt').read_text()
|
|
pre_call = get.split('885413:', 1)[0]
|
|
stack20_writes = [line.strip() for line in pre_call.splitlines()
|
|
if '[ebp-0x20]' in line.lower() and re.search(r'\bmov\s+DWORD PTR \[ebp-0x20\]', line, re.I)]
|
|
append = (OUT / 'turn-append-exact.stdout.txt').read_text()
|
|
copy = (OUT / 'nested-copy-exact.stdout.txt').read_text()
|
|
dtor = (OUT / 'nested-dtor-exact.stdout.txt').read_text()
|
|
checks += [
|
|
('binary-hash', sha(BINARY.read_bytes()) == EXPECTED['binary']),
|
|
('tool-hash', sha(TOOL.read_bytes()) == EXPECTED['tool']),
|
|
('get-no-precall-ebp-minus-20-write', not stack20_writes),
|
|
('get-last-match-overwrite-visible', all(x in get for x in ('8853d3:', '8853d8:', '8853de:', '8853e6:'))),
|
|
('get-cleanup-before-final-store', get.index('885422:') < get.index('88542d:') < get.index('885433:')),
|
|
('append-source4-copy-before-nested-copy-before-advance', append.index('884d62:') < append.index('884d6c:') < append.index('884d73:') < append.index('884d78:')),
|
|
('nested-copy-three-null-initializers', all(x in copy for x in ('779885:', '779887:', '77988a:'))),
|
|
('nested-copy-empty-and-nonempty-branches', all(x in copy for x in ('7798b2:', '7798cb:', '7798d0:', '7798ed:'))),
|
|
('nested-dtor-null-branch-loop-free-zero', all(x in dtor for x in ('629586:', '6295a8:', '6295aa:', '6295b4:', '6295c0:', '6295c2:', '6295c5:'))),
|
|
]
|
|
|
|
failed = [name for name, ok in checks if not ok]
|
|
manifest = {
|
|
'schema': 'sots-abi-independent-static/1', 'session': 'run-ee78b8773688ca09f8046e21',
|
|
'actor': 'research-abi-correction-verifier', 'role': 'verifier',
|
|
'model': 'openai/gpt-5.6-sol', 'timestamp': datetime.now(timezone.utc).isoformat(),
|
|
'input': {'path': 'dumps/sots.exe', 'bytes': BINARY.stat().st_size, 'sha256': sha(BINARY.read_bytes())},
|
|
'tool': {'path': str(TOOL), 'bytes': TOOL.stat().st_size, 'sha256': sha(TOOL.read_bytes()),
|
|
'version': subprocess.run([str(TOOL), '--version'], capture_output=True, text=True, check=True).stdout.splitlines()[0]},
|
|
'author_manifest_sha256': sha((AUTHOR / 'manifest.json').read_bytes()),
|
|
'stack_writes_to_ebp_minus_20_before_0x885413': stack20_writes,
|
|
'commands': records, 'checks': [{'name': n, 'status': 'pass' if ok else 'fail'} for n, ok in checks],
|
|
'failed': failed,
|
|
'scope': 'Independent static full compare plus widened-stop boundary ablation; no live game/allocator execution or replacement acceptance.'
|
|
}
|
|
(OUT / 'manifest.json').write_text(json.dumps(manifest, indent=2) + '\n')
|
|
print(json.dumps({'commands': len(records), 'checks': len(checks), 'failed': failed,
|
|
'manifest': str((OUT / 'manifest.json').relative_to(ROOT))}, indent=2))
|
|
raise SystemExit(bool(failed))
|