File size: 3,879 Bytes
e678ca6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | #!/usr/bin/env python3
"""Reconcile bounded source byte ranges against structural analysis evidence."""
import hashlib
import json
from pathlib import Path
from analyze_native import parse, take
from analyze_native_metadata import resource_leaves, rva_range
from analyze_qvm import write_json
from analyze_vm_extensions import verified
def partition(data, offset, size, ranges, gap_classifier):
take(data, offset, size)
end = offset+size
points = {offset, end}
for r in ranges:
if r['bytes'] < 0 or not offset <= r['file_offset'] <= r['file_offset']+r['bytes'] <= end:
raise ValueError('evidence range outside coverage region')
points.update([r['file_offset'], r['file_offset']+r['bytes']])
points = sorted(points)
segments = []
for start, stop in zip(points, points[1:]):
if start == stop:
continue
labels = sorted({r['kind'] for r in ranges if r['file_offset'] <= start and stop <= r['file_offset']+r['bytes']})
payload = take(data, start, stop-start)
segments.append({'file_offset': start, 'bytes': stop-start, 'evidence_kinds': labels,
'classification': 'referenced_structure' if labels else gap_classifier(payload),
'sha256': hashlib.sha256(payload).hexdigest(),
**({'hex': payload.hex()} if not labels else {})})
counts = {}
for s in segments:
counts[s['classification']] = counts.get(s['classification'], 0)+s['bytes']
return {'region_file_offset': offset, 'region_bytes': size, 'segments': segments, 'classification_bytes': counts}
def main():
results = []
for label in ('macos--libSDL-1.2.0.dylib', 'windows--Quake3-UrT.exe'):
report = json.loads(Path(f'docs/research/native/{label}.json').read_text())
data = verified((Path('.cache/UrbanTerror43')/report['source']['path']).read_bytes(), report['source']['sha256'])
layout, _ = parse(data)
if layout['format'] == 'Mach-O':
from analyze_native_metadata import stab_records, decode_stabs
metadata = decode_stabs(data, layout, stab_records(data, layout))
section = next(s for s in layout['sections'] if s['name'] == '__text')
ranges = [{**f, 'kind': 'paired_STABS_function'} for f in metadata['functions']]
coverage = partition(data, section['file_offset'], section['bytes'], ranges,
lambda b: 'x86_single_byte_NOP_sequence' if b == b'\x90'*len(b) else 'unclassified')
else:
ranges = []
leaves = resource_leaves(data, layout, ranges)
directory = next(d for d in layout['data_directories'] if d['index'] == 2)
offset, _ = rva_range(data, layout, directory['rva_or_security_file_offset'], directory['bytes'])
coverage = partition(data, offset, directory['bytes'], ranges,
lambda b: 'zero_filled_gap' if not any(b) else 'unclassified')
coverage['leaf_count'] = len(leaves)
output = {'source': report['source'], **coverage, 'limitations':
'Coverage is limited to the named SDL text section or PE resource-directory region. '
'Byte-pattern gap classification does not prove runtime unreachability, alignment intent or payload semantics.'}
write_json(Path(f'docs/research/native/{label}.coverage.json'), output)
results.append({'image': label, 'region_bytes': coverage['region_bytes'],
'classification_bytes': coverage['classification_bytes'],
'gap_count': sum(not s['evidence_kinds'] for s in coverage['segments'])})
write_json(Path('docs/research/native/coverage-summary.json'), results)
print(json.dumps(results, indent=2))
if __name__ == '__main__':
main()
|