scientific-method-engine 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scientific_method_engine/__init__.py +5 -0
- scientific_method_engine/__main__.py +3 -0
- scientific_method_engine/cli.py +75 -0
- scientific_method_engine/ghidra/ClearNoReturnFunctions.java +22 -0
- scientific_method_engine/ghidra/CreateFunctions.java +27 -0
- scientific_method_engine/ghidra/ExportBoundedFlow.java +59 -0
- scientific_method_engine/ghidra/ExportFunctionFingerprints.java +135 -0
- scientific_method_engine/ghidra/ExportFunctionInventory.java +50 -0
- scientific_method_engine/ghidra/MergeFallThroughFragment.java +63 -0
- scientific_method_engine/ghidra/RecoverCitedFunctions.java +93 -0
- scientific_method_engine/ghidra/RepairReturningCallers.java +76 -0
- scientific_method_engine/ghidra/ReportCallArguments.java +65 -0
- scientific_method_engine/ghidra/ReportCallPaths.java +106 -0
- scientific_method_engine/ghidra/ReportCallSitesWithScalars.java +95 -0
- scientific_method_engine/ghidra/ReportCallsToRange.java +67 -0
- scientific_method_engine/ghidra/ReportConstantFirstArgumentCalls.java +55 -0
- scientific_method_engine/ghidra/ReportDataBytes.java +36 -0
- scientific_method_engine/ghidra/ReportDecompileMatches.java +71 -0
- scientific_method_engine/ghidra/ReportDecompileWindow.java +61 -0
- scientific_method_engine/ghidra/ReportFilePatternInMemory.java +102 -0
- scientific_method_engine/ghidra/ReportFirstArgumentCallSummary.java +63 -0
- scientific_method_engine/ghidra/ReportFunctionScalarConstants.java +56 -0
- scientific_method_engine/ghidra/ReportFunctionSummary.java +64 -0
- scientific_method_engine/ghidra/ReportInstructionContext.java +64 -0
- scientific_method_engine/ghidra/ReportInstructionWindow.java +36 -0
- scientific_method_engine/ghidra/ReportMemoryBlockForFileOffset.java +105 -0
- scientific_method_engine/ghidra/ReportMemoryBlocks.java +52 -0
- scientific_method_engine/ghidra/ReportRandomnessCandidates.java +74 -0
- scientific_method_engine/ghidra/ReportReferences.java +42 -0
- scientific_method_engine/ghidra/ReportScalarConstants.java +55 -0
- scientific_method_engine/ghidra/ReportStringReferences.java +102 -0
- scientific_method_engine/ghidra/ReportSymbolReferences.java +72 -0
- scientific_method_engine/x86/__init__.py +0 -0
- scientific_method_engine/x86/dispatch.py +51 -0
- scientific_method_engine/x86/image.py +172 -0
- scientific_method_engine/x86/machine.py +659 -0
- scientific_method_engine/x86/pe.py +96 -0
- scientific_method_engine/x86/reports.py +1259 -0
- scientific_method_engine/x86/trace.py +547 -0
- scientific_method_engine/x86/values.py +123 -0
- scientific_method_engine-0.1.0.dist-info/METADATA +110 -0
- scientific_method_engine-0.1.0.dist-info/RECORD +45 -0
- scientific_method_engine-0.1.0.dist-info/WHEEL +4 -0
- scientific_method_engine-0.1.0.dist-info/entry_points.txt +2 -0
- scientific_method_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Bounded PE32/i386 source mappings. No loader execution or inferred code entries."""
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
import struct
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def pe32(data):
|
|
7
|
+
"""Parse the headers and section table of a PE32/i386 executable.
|
|
8
|
+
|
|
9
|
+
``data`` is the whole file as bytes. Returns a dict with ``format``, ``imageBase``,
|
|
10
|
+
``sizeOfImage``, ``sizeOfHeaders``, ``mappingProvenance``, ``loadAssumption`` and ``sections``;
|
|
11
|
+
each section has ``index``, ``name``, ``rva``, ``va``, ``virtualSize``, ``rawStart``,
|
|
12
|
+
``rawSize``, ``loadedRawSize`` (raw bytes actually loaded), ``mappedExtent`` and ``executable``. Raises ``ValueError`` for anything other than an MZ-stubbed
|
|
13
|
+
PE32 for i386, and for truncated, overlapping or ambiguous sections. The mapping assumes the
|
|
14
|
+
preferred image base; rebasing and imports are not simulated.
|
|
15
|
+
"""
|
|
16
|
+
def span(at, size):
|
|
17
|
+
if at < 0 or size < 0 or at + size > len(data):
|
|
18
|
+
raise ValueError("PE source range is truncated")
|
|
19
|
+
def word(at):
|
|
20
|
+
span(at, 2)
|
|
21
|
+
return struct.unpack_from('<H', data, at)[0]
|
|
22
|
+
def dword(at):
|
|
23
|
+
span(at, 4)
|
|
24
|
+
return struct.unpack_from('<I', data, at)[0]
|
|
25
|
+
span(0, 64)
|
|
26
|
+
if data[:2] != b'MZ':
|
|
27
|
+
raise ValueError("PE source needs an MZ header")
|
|
28
|
+
nt = dword(60)
|
|
29
|
+
span(nt, 24)
|
|
30
|
+
if nt < 64 or data[nt:nt + 4] != b'PE\0\0' or word(nt + 4) != 0x14c:
|
|
31
|
+
raise ValueError("Only PE32/i386 sources are supported")
|
|
32
|
+
count, optional_size = word(nt + 6), word(nt + 20)
|
|
33
|
+
optional = nt + 24
|
|
34
|
+
span(optional, optional_size)
|
|
35
|
+
if not 1 <= count <= 96 or optional_size < 96 or word(optional) != 0x10b:
|
|
36
|
+
raise ValueError("Invalid PE32 section count or optional header")
|
|
37
|
+
directories = dword(optional + 92)
|
|
38
|
+
if directories > 16 or 96 + directories * 8 > optional_size:
|
|
39
|
+
raise ValueError("PE data directories escape optional header")
|
|
40
|
+
base, size, headers = dword(optional + 28), dword(optional + 56), dword(optional + 60)
|
|
41
|
+
table = optional + optional_size
|
|
42
|
+
span(table, count * 40)
|
|
43
|
+
if not size or base + size > 1 << 32 or headers < table + count * 40 or headers > len(data) or headers > size:
|
|
44
|
+
raise ValueError("Invalid PE image/header extent")
|
|
45
|
+
sections = []
|
|
46
|
+
for i in range(count):
|
|
47
|
+
at = table + i * 40
|
|
48
|
+
virtual_size, rva, raw_size, raw = (dword(at + j) for j in (8, 12, 16, 20))
|
|
49
|
+
extent = max(virtual_size, raw_size)
|
|
50
|
+
# Raw bytes past VirtualSize are file-alignment padding, not loaded source.
|
|
51
|
+
loaded = min(raw_size, virtual_size) if virtual_size else raw_size
|
|
52
|
+
if not extent or rva < headers or rva + extent > size:
|
|
53
|
+
raise ValueError("PE section escapes image or overlaps headers")
|
|
54
|
+
if raw_size:
|
|
55
|
+
span(raw, raw_size)
|
|
56
|
+
if raw < headers:
|
|
57
|
+
raise ValueError("PE section raw bytes overlap headers")
|
|
58
|
+
section = {"index": i, "name": data[at:at + 8].split(b'\0')[0].decode('ascii', errors='replace'),
|
|
59
|
+
"rva": rva, "va": base + rva, "virtualSize": virtual_size,
|
|
60
|
+
"rawStart": raw, "rawSize": raw_size, "loadedRawSize": loaded, "mappedExtent": extent,
|
|
61
|
+
"executable": bool(dword(at + 36) & 0x20000000)}
|
|
62
|
+
for prior in sections:
|
|
63
|
+
if max(rva, prior['rva']) < min(rva + extent, prior['rva'] + prior['mappedExtent']):
|
|
64
|
+
raise ValueError("Ambiguous PE virtual section mapping")
|
|
65
|
+
if raw_size and prior['rawSize'] and max(raw, prior['rawStart']) < min(raw + raw_size, prior['rawStart'] + prior['rawSize']):
|
|
66
|
+
raise ValueError("Overlapping PE raw sections")
|
|
67
|
+
sections.append(section)
|
|
68
|
+
return {"format": "PE32/i386", "imageBase": base, "sizeOfImage": size,
|
|
69
|
+
"sizeOfHeaders": headers, "sections": sections,
|
|
70
|
+
"mappingProvenance": "source COFF/PE optional header and section table",
|
|
71
|
+
"loadAssumption": "preferred image base; rebasing, imports and runtime patching are not simulated"}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def prepare_pe(data, config):
|
|
75
|
+
metadata = pe32(data)
|
|
76
|
+
result = deepcopy(config)
|
|
77
|
+
if config.get('bits', 32) != 32 or config.get('addressModel', 'flat32') != 'flat32':
|
|
78
|
+
raise ValueError("PE32 requires the 32-bit flat model")
|
|
79
|
+
if config.get('relocations') or config.get('targetSelector'):
|
|
80
|
+
raise ValueError("MZ relocation/overlay inputs cannot be used for PE32")
|
|
81
|
+
regions = result.get('regions', [])
|
|
82
|
+
if not isinstance(regions, list) or not all(isinstance(r, dict) for r in regions):
|
|
83
|
+
raise ValueError("Regions must be a list of objects")
|
|
84
|
+
result.update(bits=32, addressModel='flat32', peMetadata=metadata)
|
|
85
|
+
for region in regions:
|
|
86
|
+
start, end = region.get('start'), region.get('end')
|
|
87
|
+
if type(start) is not int or type(end) is not int or end <= start:
|
|
88
|
+
raise ValueError("PE code bounds must be integer file offsets")
|
|
89
|
+
section = next((s for s in metadata['sections'] if s['executable'] and s['rawStart'] <= start < end <= s['rawStart'] + s['loadedRawSize']), None)
|
|
90
|
+
if section is None:
|
|
91
|
+
raise ValueError("PE code region must be within loaded raw executable section bytes")
|
|
92
|
+
va = section['va'] + start - section['rawStart']
|
|
93
|
+
if region.get('ip', va) != va or region.get('segment', 0) != 0 or region.get('resident', False):
|
|
94
|
+
raise ValueError("PE region mapping disagrees with source section table")
|
|
95
|
+
region.update(ip=va, segment=0, resident=False, sectionIndex=section['index'])
|
|
96
|
+
return result
|