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()