smda 2.4.2__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.
- smda/Disassembler.py +180 -0
- smda/DisassemblyResult.py +297 -0
- smda/DisassemblyStatistics.py +86 -0
- smda/SmdaConfig.py +44 -0
- smda/__init__.py +0 -0
- smda/cil/CilDisassembler.py +281 -0
- smda/cil/CilInstructionEscaper.py +473 -0
- smda/cil/FunctionAnalysisState.py +185 -0
- smda/cil/__init__.py +0 -0
- smda/common/BasicBlock.py +14 -0
- smda/common/BinaryInfo.py +111 -0
- smda/common/BlockLocator.py +42 -0
- smda/common/CodeXref.py +26 -0
- smda/common/DominatorTree.py +211 -0
- smda/common/SmdaBasicBlock.py +105 -0
- smda/common/SmdaFunction.py +297 -0
- smda/common/SmdaInstruction.py +124 -0
- smda/common/SmdaReport.py +330 -0
- smda/common/TailcallAnalyzer.py +139 -0
- smda/common/Tarjan.py +84 -0
- smda/common/__init__.py +0 -0
- smda/common/labelprovider/AbstractLabelProvider.py +41 -0
- smda/common/labelprovider/CilSymbolProvider.py +43 -0
- smda/common/labelprovider/DelphiKbSymbolProvider.py +95 -0
- smda/common/labelprovider/DelphiReSymProvider.py +440 -0
- smda/common/labelprovider/ElfApiResolver.py +62 -0
- smda/common/labelprovider/ElfSymbolProvider.py +74 -0
- smda/common/labelprovider/GoLabelProvider.py +187 -0
- smda/common/labelprovider/OrdinalHelper.py +46 -0
- smda/common/labelprovider/PdbSymbolProvider.py +89 -0
- smda/common/labelprovider/PeSymbolProvider.py +105 -0
- smda/common/labelprovider/WinApiResolver.py +104 -0
- smda/common/labelprovider/__init__.py +0 -0
- smda/ida/BackendInterface.py +36 -0
- smda/ida/IdaExporter.py +89 -0
- smda/ida/IdaInterface.py +391 -0
- smda/ida/__init__.py +0 -0
- smda/intel/BitnessAnalyzer.py +49 -0
- smda/intel/FunctionAnalysisState.py +293 -0
- smda/intel/FunctionCandidate.py +198 -0
- smda/intel/FunctionCandidateManager.py +652 -0
- smda/intel/IndirectCallAnalyzer.py +247 -0
- smda/intel/IntelDisassembler.py +596 -0
- smda/intel/IntelInstructionEscaper.py +2354 -0
- smda/intel/JumpTableAnalyzer.py +212 -0
- smda/intel/LanguageAnalyzer.py +282 -0
- smda/intel/MnemonicTfIdf.py +470 -0
- smda/intel/__init__.py +0 -0
- smda/intel/definitions.py +226 -0
- smda/utility/BracketQueue.py +59 -0
- smda/utility/DelphiKbFileLoader.py +27 -0
- smda/utility/ElfFileLoader.py +258 -0
- smda/utility/FileLoader.py +63 -0
- smda/utility/MachoFileLoader.py +219 -0
- smda/utility/MemoryFileLoader.py +7 -0
- smda/utility/PeFileLoader.py +174 -0
- smda/utility/PriorityQueue.py +38 -0
- smda/utility/StringExtractor.py +171 -0
- smda/utility/__init__.py +0 -0
- smda-2.4.2.data/data/LICENSE +9 -0
- smda-2.4.2.dist-info/METADATA +175 -0
- smda-2.4.2.dist-info/RECORD +65 -0
- smda-2.4.2.dist-info/WHEEL +5 -0
- smda-2.4.2.dist-info/licenses/LICENSE +9 -0
- smda-2.4.2.dist-info/top_level.txt +1 -0
smda/Disassembler.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import hashlib
|
|
3
|
+
import logging
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
from smda.cil.CilDisassembler import CilDisassembler
|
|
7
|
+
from smda.common.BinaryInfo import BinaryInfo
|
|
8
|
+
from smda.common.labelprovider.GoLabelProvider import GoSymbolProvider
|
|
9
|
+
from smda.common.SmdaReport import SmdaReport
|
|
10
|
+
from smda.ida.IdaExporter import IdaExporter
|
|
11
|
+
from smda.intel.IntelDisassembler import IntelDisassembler
|
|
12
|
+
from smda.SmdaConfig import SmdaConfig
|
|
13
|
+
from smda.utility.FileLoader import FileLoader
|
|
14
|
+
from smda.utility.MemoryFileLoader import MemoryFileLoader
|
|
15
|
+
from smda.utility.StringExtractor import extract_strings
|
|
16
|
+
|
|
17
|
+
LOGGER = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Disassembler:
|
|
21
|
+
def __init__(self, config=None, backend=None):
|
|
22
|
+
if config is None:
|
|
23
|
+
config = SmdaConfig()
|
|
24
|
+
self.config = config
|
|
25
|
+
self.disassembler = None
|
|
26
|
+
if backend == "intel":
|
|
27
|
+
self.disassembler = IntelDisassembler(self.config)
|
|
28
|
+
elif backend == "cil":
|
|
29
|
+
self.disassembler = CilDisassembler(self.config)
|
|
30
|
+
elif backend == "IDA":
|
|
31
|
+
self.disassembler = IdaExporter(self.config)
|
|
32
|
+
self._start_time = None
|
|
33
|
+
self._timeout = 0
|
|
34
|
+
# cache the last DisassemblyResult
|
|
35
|
+
self.disassembly = None
|
|
36
|
+
|
|
37
|
+
def initDisassembler(self, architecture="intel"):
|
|
38
|
+
"""Initialize disassembler backend to given architecture, if not initialized yet, default: intel"""
|
|
39
|
+
if self.disassembler is None:
|
|
40
|
+
if architecture == "intel":
|
|
41
|
+
self.disassembler = IntelDisassembler(self.config)
|
|
42
|
+
elif architecture == "cil":
|
|
43
|
+
self.disassembler = CilDisassembler(self.config)
|
|
44
|
+
|
|
45
|
+
def _getDurationInSeconds(self, start_ts, end_ts):
|
|
46
|
+
return (end_ts - start_ts).seconds + ((end_ts - start_ts).microseconds / 1000000.0)
|
|
47
|
+
|
|
48
|
+
def _callbackAnalysisTimeout(self):
|
|
49
|
+
if not self._timeout:
|
|
50
|
+
return False
|
|
51
|
+
time_diff = datetime.datetime.now(datetime.timezone.utc) - self._start_time
|
|
52
|
+
LOGGER.debug("Current analysis callback time %s", (time_diff))
|
|
53
|
+
return time_diff.seconds >= self._timeout
|
|
54
|
+
|
|
55
|
+
def _addStringsToReport(self, smda_report, buffer, mode=None):
|
|
56
|
+
smda_report.buffer = buffer
|
|
57
|
+
for smda_function in smda_report.getFunctions():
|
|
58
|
+
function_strings = []
|
|
59
|
+
for string_result in extract_strings(smda_function, mode=mode):
|
|
60
|
+
string, referencing_addr, string_addr, string_type = string_result
|
|
61
|
+
function_strings.append(
|
|
62
|
+
{
|
|
63
|
+
"string": string,
|
|
64
|
+
"ins_addr": referencing_addr,
|
|
65
|
+
"data_addr": string_addr,
|
|
66
|
+
"type": string_type,
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
smda_function.stringrefs = function_strings
|
|
70
|
+
|
|
71
|
+
def disassembleFile(self, file_path, pdb_path=""):
|
|
72
|
+
loader = FileLoader(file_path, map_file=True)
|
|
73
|
+
file_content = loader.getData()
|
|
74
|
+
binary_info = BinaryInfo(file_content)
|
|
75
|
+
binary_info.raw_data = loader.getRawData()
|
|
76
|
+
# we want the SHA256/SHA1/MD5 of the unmapped file not how we mapped it to memory
|
|
77
|
+
binary_info.sha256 = hashlib.sha256(binary_info.raw_data).hexdigest()
|
|
78
|
+
binary_info.sha1 = hashlib.sha1(binary_info.raw_data).hexdigest()
|
|
79
|
+
binary_info.md5 = hashlib.md5(binary_info.raw_data).hexdigest()
|
|
80
|
+
binary_info.file_path = file_path
|
|
81
|
+
binary_info.base_addr = loader.getBaseAddress()
|
|
82
|
+
binary_info.bitness = loader.getBitness()
|
|
83
|
+
binary_info.architecture = loader.getArchitecture()
|
|
84
|
+
binary_info.code_areas = loader.getCodeAreas()
|
|
85
|
+
self.initDisassembler(binary_info.architecture)
|
|
86
|
+
start = datetime.datetime.now(datetime.timezone.utc)
|
|
87
|
+
try:
|
|
88
|
+
self.disassembler.addPdbFile(binary_info, pdb_path)
|
|
89
|
+
smda_report = self._disassemble(binary_info, timeout=self.config.TIMEOUT)
|
|
90
|
+
if self.config.WITH_STRINGS:
|
|
91
|
+
is_go_binary = GoSymbolProvider(None).getPcLntabOffset(binary_info.binary)
|
|
92
|
+
string_mode = "go" if is_go_binary else None
|
|
93
|
+
self._addStringsToReport(smda_report, file_content, mode=string_mode)
|
|
94
|
+
if self.config.STORE_BUFFER:
|
|
95
|
+
smda_report.buffer = file_content
|
|
96
|
+
except Exception as exc:
|
|
97
|
+
LOGGER.error("An error occurred while disassembling file.")
|
|
98
|
+
# print("-> an error occured (", str(exc), ").")
|
|
99
|
+
smda_report = self._createErrorReport(start, exc)
|
|
100
|
+
return smda_report
|
|
101
|
+
|
|
102
|
+
def disassembleUnmappedBuffer(self, file_content):
|
|
103
|
+
loader = MemoryFileLoader(file_content, map_file=True)
|
|
104
|
+
file_content = loader.getData()
|
|
105
|
+
binary_info = BinaryInfo(file_content)
|
|
106
|
+
binary_info.raw_data = loader.getRawData()
|
|
107
|
+
# we want the SHA256/SHA1/MD5 of the unmapped file not how we mapped it to memory
|
|
108
|
+
binary_info.sha256 = hashlib.sha256(binary_info.raw_data).hexdigest()
|
|
109
|
+
binary_info.sha1 = hashlib.sha1(binary_info.raw_data).hexdigest()
|
|
110
|
+
binary_info.md5 = hashlib.md5(binary_info.raw_data).hexdigest()
|
|
111
|
+
binary_info.file_path = ""
|
|
112
|
+
binary_info.base_addr = loader.getBaseAddress()
|
|
113
|
+
binary_info.bitness = loader.getBitness()
|
|
114
|
+
binary_info.architecture = loader.getArchitecture()
|
|
115
|
+
binary_info.code_areas = loader.getCodeAreas()
|
|
116
|
+
self.initDisassembler(binary_info.architecture)
|
|
117
|
+
start = datetime.datetime.now(datetime.timezone.utc)
|
|
118
|
+
try:
|
|
119
|
+
smda_report = self._disassemble(binary_info, timeout=self.config.TIMEOUT)
|
|
120
|
+
if self.config.WITH_STRINGS:
|
|
121
|
+
is_go_binary = GoSymbolProvider(None).getPcLntabOffset(binary_info.binary)
|
|
122
|
+
string_mode = "go" if is_go_binary else None
|
|
123
|
+
self._addStringsToReport(smda_report, file_content, mode=string_mode)
|
|
124
|
+
if self.config.STORE_BUFFER:
|
|
125
|
+
smda_report.buffer = file_content
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
LOGGER.error("An error occurred while disassembling unmapped buffer.")
|
|
128
|
+
# print("-> an error occured (", str(exc), ").")
|
|
129
|
+
smda_report = self._createErrorReport(start, exc)
|
|
130
|
+
return smda_report
|
|
131
|
+
|
|
132
|
+
def disassembleBuffer(
|
|
133
|
+
self,
|
|
134
|
+
file_content,
|
|
135
|
+
base_addr,
|
|
136
|
+
bitness=None,
|
|
137
|
+
code_areas=None,
|
|
138
|
+
oep=None,
|
|
139
|
+
architecture="intel",
|
|
140
|
+
):
|
|
141
|
+
"""
|
|
142
|
+
Disassemble a given buffer (file_content), with given base_addr.
|
|
143
|
+
Optionally specify bitness, the areas to which disassembly should be limited to (code_areas) and an entry point (oep)
|
|
144
|
+
"""
|
|
145
|
+
binary_info = BinaryInfo(file_content)
|
|
146
|
+
binary_info.base_addr = base_addr
|
|
147
|
+
binary_info.bitness = bitness
|
|
148
|
+
binary_info.is_buffer = True
|
|
149
|
+
binary_info.code_areas = code_areas
|
|
150
|
+
binary_info.architecture = architecture
|
|
151
|
+
binary_info.oep = oep
|
|
152
|
+
self.initDisassembler(binary_info.architecture)
|
|
153
|
+
start = datetime.datetime.now(datetime.timezone.utc)
|
|
154
|
+
try:
|
|
155
|
+
smda_report = self._disassemble(binary_info, timeout=self.config.TIMEOUT)
|
|
156
|
+
if self.config.WITH_STRINGS:
|
|
157
|
+
is_go_binary = GoSymbolProvider(None).getPcLntabOffset(binary_info.binary)
|
|
158
|
+
string_mode = "go" if is_go_binary else None
|
|
159
|
+
self._addStringsToReport(smda_report, file_content, mode=string_mode)
|
|
160
|
+
if self.config.STORE_BUFFER:
|
|
161
|
+
smda_report.buffer = file_content
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
LOGGER.error("An error occurred while disassembling buffer.")
|
|
164
|
+
# print("-> an error occured (", str(exc), ").")
|
|
165
|
+
smda_report = self._createErrorReport(start, exc)
|
|
166
|
+
return smda_report
|
|
167
|
+
|
|
168
|
+
def _disassemble(self, binary_info, timeout=0):
|
|
169
|
+
self._start_time = datetime.datetime.now(datetime.timezone.utc)
|
|
170
|
+
self._timeout = timeout
|
|
171
|
+
self.disassembly = self.disassembler.analyzeBuffer(binary_info, self._callbackAnalysisTimeout)
|
|
172
|
+
return SmdaReport(self.disassembly, config=self.config)
|
|
173
|
+
|
|
174
|
+
def _createErrorReport(self, start, exception):
|
|
175
|
+
report = SmdaReport(config=self.config)
|
|
176
|
+
report.smda_version = self.config.VERSION
|
|
177
|
+
report.status = "error"
|
|
178
|
+
report.execution_time = self._getDurationInSeconds(start, datetime.datetime.now(datetime.timezone.utc))
|
|
179
|
+
report.message = traceback.format_exc()
|
|
180
|
+
return report
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import struct
|
|
3
|
+
|
|
4
|
+
from smda.common.BasicBlock import BasicBlock
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DisassemblyResult:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.analysis_start_ts = datetime.datetime.now(datetime.timezone.utc)
|
|
10
|
+
self.analysis_end_ts = self.analysis_start_ts
|
|
11
|
+
self.analysis_timeout = False
|
|
12
|
+
self.binary_info = None
|
|
13
|
+
self.identified_alignment = 0
|
|
14
|
+
self.oep = None
|
|
15
|
+
self.code_map = {}
|
|
16
|
+
self.data_map = set()
|
|
17
|
+
# key: offset, value: {"type": <str>, "instruction_bytes": <hexstr>}
|
|
18
|
+
self.errors = {}
|
|
19
|
+
# stored as key:
|
|
20
|
+
self.functions = {}
|
|
21
|
+
self.recursive_functions = set()
|
|
22
|
+
self.leaf_functions = set()
|
|
23
|
+
self.thunk_functions = set()
|
|
24
|
+
self.exported_functions = set()
|
|
25
|
+
self.failed_analysis_addr = []
|
|
26
|
+
self.function_borders = {}
|
|
27
|
+
# stored as key: int(i.address) = (i.size, i.mnemonic, i.op_str)
|
|
28
|
+
self.instructions = {}
|
|
29
|
+
self.ins2fn = {}
|
|
30
|
+
self.language = {}
|
|
31
|
+
self.data_refs_from = {}
|
|
32
|
+
self.data_refs_to = {}
|
|
33
|
+
self.code_refs_from = {}
|
|
34
|
+
self.code_refs_to = {}
|
|
35
|
+
# key: address of API in target DLL, value: {referencing_addr, api_name, dll_name}
|
|
36
|
+
self.apis = {}
|
|
37
|
+
self.addr_to_api = {}
|
|
38
|
+
# address:name
|
|
39
|
+
self.function_symbols = {}
|
|
40
|
+
# address:string
|
|
41
|
+
self.stringrefs = {}
|
|
42
|
+
self.candidates = {}
|
|
43
|
+
self._confidence_threshold = 0.0
|
|
44
|
+
self.code_areas = []
|
|
45
|
+
self.smda_version = ""
|
|
46
|
+
|
|
47
|
+
def setBinaryInfo(self, binary_info):
|
|
48
|
+
self.binary_info = binary_info
|
|
49
|
+
exported = binary_info.getExportedFunctions()
|
|
50
|
+
if exported is not None:
|
|
51
|
+
self.exported_functions = {key + binary_info.base_addr for key in exported}
|
|
52
|
+
self.oep = binary_info.getOep()
|
|
53
|
+
|
|
54
|
+
def getByte(self, addr):
|
|
55
|
+
if self.isAddrWithinMemoryImage(addr):
|
|
56
|
+
return self.binary_info.binary[addr - self.binary_info.base_addr]
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def getRawByte(self, offset):
|
|
60
|
+
return self.binary_info.binary[offset]
|
|
61
|
+
|
|
62
|
+
def getBytes(self, addr, num_bytes):
|
|
63
|
+
if self.isAddrWithinMemoryImage(addr):
|
|
64
|
+
rel_start_addr = addr - self.binary_info.base_addr
|
|
65
|
+
return self.binary_info.binary[rel_start_addr : rel_start_addr + num_bytes]
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def getRawBytes(self, offset, num_bytes):
|
|
69
|
+
return self.binary_info.binary[offset : offset + num_bytes]
|
|
70
|
+
|
|
71
|
+
def setConfidenceThreshold(self, threshold):
|
|
72
|
+
self._confidence_threshold = threshold
|
|
73
|
+
|
|
74
|
+
def getConfidenceThreshold(self):
|
|
75
|
+
return self._confidence_threshold
|
|
76
|
+
|
|
77
|
+
def getAnalysisDuration(self):
|
|
78
|
+
return (self.analysis_end_ts - self.analysis_start_ts).seconds + (
|
|
79
|
+
(self.analysis_end_ts - self.analysis_start_ts).microseconds / 1000000.0
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def getAnalysisOutcome(self):
|
|
83
|
+
outcome = "ok"
|
|
84
|
+
if self.analysis_timeout:
|
|
85
|
+
outcome = "timeout"
|
|
86
|
+
return outcome
|
|
87
|
+
|
|
88
|
+
def getFunctions(self):
|
|
89
|
+
return sorted(self.functions.keys())
|
|
90
|
+
|
|
91
|
+
def getBlocks(self, function_addr):
|
|
92
|
+
disasm_blocks = []
|
|
93
|
+
if function_addr in self.functions:
|
|
94
|
+
disasm_blocks = self.functions[function_addr]
|
|
95
|
+
bblocks = []
|
|
96
|
+
|
|
97
|
+
for block in disasm_blocks:
|
|
98
|
+
bblock = BasicBlock()
|
|
99
|
+
bblock.start_addr = block[0][0]
|
|
100
|
+
bblock.end_addr = block[-1][0]
|
|
101
|
+
bblock.instructions = [ins[0] for ins in block]
|
|
102
|
+
if bblock.end_addr in self.code_refs_from:
|
|
103
|
+
bblock.successors = list(self.code_refs_from[bblock.end_addr])
|
|
104
|
+
bblocks.append(bblock)
|
|
105
|
+
return bblocks
|
|
106
|
+
|
|
107
|
+
def _transformInstruction(self, ins_tuple):
|
|
108
|
+
ins_addr, _, ins_mnem, ins_ops, ins_raw_bytes = ins_tuple
|
|
109
|
+
# python3 and python2 do handling differently...
|
|
110
|
+
ins_hexbytes = "".join([f"{c:02x}" for c in ins_tuple[4]])
|
|
111
|
+
return [ins_addr, ins_hexbytes, str(ins_mnem), str(ins_ops)]
|
|
112
|
+
|
|
113
|
+
def getBlocksAsDict(self, function_addr):
|
|
114
|
+
blocks = {}
|
|
115
|
+
for block in self.functions[function_addr]:
|
|
116
|
+
instructions = []
|
|
117
|
+
for ins in block:
|
|
118
|
+
instructions.append(self._transformInstruction(ins))
|
|
119
|
+
blocks[instructions[0][0]] = instructions
|
|
120
|
+
return blocks
|
|
121
|
+
|
|
122
|
+
def getInstructions(self, block):
|
|
123
|
+
return block.instructions
|
|
124
|
+
|
|
125
|
+
def getMnemonic(self, instruction_addr):
|
|
126
|
+
if instruction_addr in self.instructions:
|
|
127
|
+
return self.instructions[instruction_addr][0]
|
|
128
|
+
return ""
|
|
129
|
+
|
|
130
|
+
def isCode(self, addr):
|
|
131
|
+
return addr in self.code_map
|
|
132
|
+
|
|
133
|
+
def isAddrWithinMemoryImage(self, destination):
|
|
134
|
+
if destination is not None:
|
|
135
|
+
return (
|
|
136
|
+
self.binary_info.base_addr <= destination < (self.binary_info.base_addr + self.binary_info.binary_size)
|
|
137
|
+
)
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def dereferenceDword(self, addr):
|
|
141
|
+
if self.isAddrWithinMemoryImage(addr):
|
|
142
|
+
rel_start_addr = addr - self.binary_info.base_addr
|
|
143
|
+
rel_end_addr = rel_start_addr + 4
|
|
144
|
+
extracted_dword = self.binary_info.binary[rel_start_addr:rel_end_addr]
|
|
145
|
+
if len(extracted_dword) < 4:
|
|
146
|
+
return None
|
|
147
|
+
return struct.unpack("I", extracted_dword)[0]
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def dereferenceQword(self, addr):
|
|
151
|
+
if self.isAddrWithinMemoryImage(addr):
|
|
152
|
+
rel_start_addr = addr - self.binary_info.base_addr
|
|
153
|
+
rel_end_addr = rel_start_addr + 8
|
|
154
|
+
extracted_qword = self.binary_info.binary[rel_start_addr:rel_end_addr]
|
|
155
|
+
if len(extracted_qword) < 8:
|
|
156
|
+
return None
|
|
157
|
+
return struct.unpack("Q", extracted_qword)[0]
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
def addCodeRefs(self, addr_from, addr_to):
|
|
161
|
+
refs_from = self.code_refs_from.get(addr_from, set())
|
|
162
|
+
refs_from.update([addr_to])
|
|
163
|
+
self.code_refs_from[addr_from] = refs_from
|
|
164
|
+
refs_to = self.code_refs_to.get(addr_to, set())
|
|
165
|
+
refs_to.update([addr_from])
|
|
166
|
+
self.code_refs_to[addr_to] = refs_to
|
|
167
|
+
|
|
168
|
+
def removeCodeRefs(self, addr_from, addr_to):
|
|
169
|
+
refs_from = self.code_refs_from.get(addr_from, set())
|
|
170
|
+
refs_from.discard(addr_to)
|
|
171
|
+
self.code_refs_from[addr_from] = refs_from
|
|
172
|
+
refs_to = self.code_refs_to.get(addr_to, set())
|
|
173
|
+
refs_to.discard(addr_from)
|
|
174
|
+
self.code_refs_to[addr_to] = refs_to
|
|
175
|
+
|
|
176
|
+
def addDataRefs(self, addr_from, addr_to):
|
|
177
|
+
refs_from = self.data_refs_from.get(addr_from, set())
|
|
178
|
+
refs_from.update([addr_to])
|
|
179
|
+
self.data_refs_from[addr_from] = refs_from
|
|
180
|
+
refs_to = self.data_refs_to.get(addr_to, set())
|
|
181
|
+
refs_to.update([addr_from])
|
|
182
|
+
self.data_refs_to[addr_to] = refs_to
|
|
183
|
+
|
|
184
|
+
def removeDataRefs(self, addr_from, addr_to):
|
|
185
|
+
refs_from = self.data_refs_from.get(addr_from, set())
|
|
186
|
+
refs_from.discard(addr_to)
|
|
187
|
+
self.data_refs_from[addr_from] = refs_from
|
|
188
|
+
refs_to = self.data_refs_to.get(addr_to, set())
|
|
189
|
+
refs_to.discard(addr_from)
|
|
190
|
+
self.data_refs_to[addr_to] = refs_to
|
|
191
|
+
|
|
192
|
+
def getBlockRefs(self, func_addr):
|
|
193
|
+
"""blocks refs should stay within function context, thus kill all references outside function"""
|
|
194
|
+
block_refs = {}
|
|
195
|
+
ins_addrs = set()
|
|
196
|
+
for block in self.functions[func_addr]:
|
|
197
|
+
for ins in block:
|
|
198
|
+
ins_addr = ins[0]
|
|
199
|
+
ins_addrs.add(ins_addr)
|
|
200
|
+
for block in self.functions[func_addr]:
|
|
201
|
+
last_ins_addr = block[-1][0]
|
|
202
|
+
if last_ins_addr in self.code_refs_from:
|
|
203
|
+
verified_refs = sorted(ins_addrs.intersection(self.code_refs_from[last_ins_addr]))
|
|
204
|
+
if verified_refs:
|
|
205
|
+
block_refs[block[0][0]] = verified_refs
|
|
206
|
+
return block_refs
|
|
207
|
+
|
|
208
|
+
def getInRefs(self, func_addr):
|
|
209
|
+
in_refs = []
|
|
210
|
+
if func_addr in self.code_refs_to:
|
|
211
|
+
in_refs = list(self.code_refs_to[func_addr])
|
|
212
|
+
return sorted(in_refs)
|
|
213
|
+
|
|
214
|
+
def getOutRefs(self, func_addr):
|
|
215
|
+
ins_addrs = set()
|
|
216
|
+
code_refs = []
|
|
217
|
+
out_refs = {}
|
|
218
|
+
for block in self.functions[func_addr]:
|
|
219
|
+
for ins in block:
|
|
220
|
+
ins_addr = ins[0]
|
|
221
|
+
ins_addrs.add(ins_addr)
|
|
222
|
+
if ins_addr in self.code_refs_from:
|
|
223
|
+
for to_addr in self.code_refs_from[ins_addr]:
|
|
224
|
+
code_refs.append([ins_addr, to_addr])
|
|
225
|
+
# function may be recursive
|
|
226
|
+
if func_addr in ins_addrs:
|
|
227
|
+
ins_addrs.remove(func_addr)
|
|
228
|
+
# reduce outrefs to addresses within the memory image
|
|
229
|
+
max_addr = self.binary_info.base_addr + self.binary_info.binary_size
|
|
230
|
+
image_refs = [ref for ref in code_refs if self.binary_info.base_addr <= ref[1] <= max_addr]
|
|
231
|
+
for ref in image_refs:
|
|
232
|
+
if ref[1] in ins_addrs:
|
|
233
|
+
continue
|
|
234
|
+
if ref[0] not in out_refs:
|
|
235
|
+
out_refs[ref[0]] = []
|
|
236
|
+
out_refs[ref[0]].append(ref[1])
|
|
237
|
+
return {src: sorted(dst) for src, dst in out_refs.items()}
|
|
238
|
+
|
|
239
|
+
def isRecursiveFunction(self, func_addr):
|
|
240
|
+
ins_addrs = set()
|
|
241
|
+
out_refs = set()
|
|
242
|
+
for block in self.functions[func_addr]:
|
|
243
|
+
for ins in block:
|
|
244
|
+
ins_addr = ins[0]
|
|
245
|
+
ins_addrs.add(ins_addr)
|
|
246
|
+
if ins_addr in self.code_refs_from:
|
|
247
|
+
for to_addr in self.code_refs_from[ins_addr]:
|
|
248
|
+
out_refs.add(to_addr)
|
|
249
|
+
return func_addr in out_refs
|
|
250
|
+
|
|
251
|
+
def isLeafFunction(self, func_addr):
|
|
252
|
+
ins_addrs = set()
|
|
253
|
+
out_refs = set()
|
|
254
|
+
for block in self.functions[func_addr]:
|
|
255
|
+
for ins in block:
|
|
256
|
+
ins_addr = ins[0]
|
|
257
|
+
ins_addrs.add(ins_addr)
|
|
258
|
+
if ins_addr in self.code_refs_from:
|
|
259
|
+
for to_addr in self.code_refs_from[ins_addr]:
|
|
260
|
+
out_refs.add(to_addr)
|
|
261
|
+
return len(out_refs.difference(ins_addrs)) == 0
|
|
262
|
+
|
|
263
|
+
def _initApiRefs(self):
|
|
264
|
+
for api_offset in self.apis:
|
|
265
|
+
api = self.apis[api_offset]
|
|
266
|
+
for ref in api["referencing_addr"]:
|
|
267
|
+
self.addr_to_api[ref] = "{}!{}".format(api["dll_name"], api["api_name"])
|
|
268
|
+
|
|
269
|
+
def getAllApiRefs(self):
|
|
270
|
+
all_api_refs = {}
|
|
271
|
+
for function_addr in self.functions:
|
|
272
|
+
all_api_refs.update(self.getApiRefs(function_addr))
|
|
273
|
+
return all_api_refs
|
|
274
|
+
|
|
275
|
+
def getApiRefs(self, func_addr):
|
|
276
|
+
if not self.addr_to_api:
|
|
277
|
+
self._initApiRefs()
|
|
278
|
+
api_refs = {}
|
|
279
|
+
for block in self.functions[func_addr]:
|
|
280
|
+
for ins in block:
|
|
281
|
+
if ins[0] in self.addr_to_api:
|
|
282
|
+
api_refs[ins[0]] = self.addr_to_api[ins[0]]
|
|
283
|
+
return api_refs
|
|
284
|
+
|
|
285
|
+
def addStringRef(self, func_addr, ref_addr, string):
|
|
286
|
+
if func_addr not in self.stringrefs:
|
|
287
|
+
self.stringrefs[func_addr] = {}
|
|
288
|
+
self.stringrefs[func_addr][ref_addr] = string
|
|
289
|
+
|
|
290
|
+
def getStringRefsForFunction(self, func_addr):
|
|
291
|
+
# addr with ref: str
|
|
292
|
+
if func_addr in self.stringrefs:
|
|
293
|
+
return self.stringrefs[func_addr]
|
|
294
|
+
return {}
|
|
295
|
+
|
|
296
|
+
def __str__(self):
|
|
297
|
+
return f"-> {self.getAnalysisDuration():5.2f}s | {len(self.functions):5d} Func (status: {self.getAnalysisOutcome()})"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
class DisassemblyStatistics:
|
|
2
|
+
num_functions = None
|
|
3
|
+
num_recursive_functions = None
|
|
4
|
+
num_leaf_functions = None
|
|
5
|
+
num_basic_blocks = None
|
|
6
|
+
num_instructions = None
|
|
7
|
+
num_api_calls = None
|
|
8
|
+
num_function_calls = None
|
|
9
|
+
num_failed_functions = None
|
|
10
|
+
num_failed_instructions = None
|
|
11
|
+
|
|
12
|
+
def __init__(self, disassembly_result=None):
|
|
13
|
+
if disassembly_result is not None:
|
|
14
|
+
self.num_functions = len(disassembly_result.functions)
|
|
15
|
+
self.num_recursive_functions = len(disassembly_result.recursive_functions)
|
|
16
|
+
self.num_leaf_functions = len(disassembly_result.leaf_functions)
|
|
17
|
+
self.num_basic_blocks = self._countBlocks(disassembly_result)
|
|
18
|
+
self.num_instructions = self._countInstructions(disassembly_result)
|
|
19
|
+
self.num_api_calls = self._countApiCalls(disassembly_result)
|
|
20
|
+
self.num_function_calls = self._countFunctionCalls(disassembly_result)
|
|
21
|
+
self.num_failed_functions = len(disassembly_result.failed_analysis_addr)
|
|
22
|
+
self.num_failed_instructions = len(disassembly_result.errors)
|
|
23
|
+
|
|
24
|
+
def _countBlocks(self, disassembly_result):
|
|
25
|
+
num_blocks = 0
|
|
26
|
+
for _, blocks in disassembly_result.functions.items():
|
|
27
|
+
num_blocks += len(blocks)
|
|
28
|
+
return num_blocks
|
|
29
|
+
|
|
30
|
+
def _countApiCalls(self, disassembly_result):
|
|
31
|
+
return len(disassembly_result.getAllApiRefs())
|
|
32
|
+
|
|
33
|
+
def _countInstructions(self, disassembly_result):
|
|
34
|
+
num_ins = 0
|
|
35
|
+
for function_offset in sorted(disassembly_result.functions):
|
|
36
|
+
for block in disassembly_result.functions[function_offset]:
|
|
37
|
+
num_ins += len(block)
|
|
38
|
+
return num_ins
|
|
39
|
+
|
|
40
|
+
def _countFunctionCalls(self, disassembly_result):
|
|
41
|
+
num_calls = 0
|
|
42
|
+
for function_start in disassembly_result.functions:
|
|
43
|
+
if function_start in disassembly_result.code_refs_to:
|
|
44
|
+
num_calls += len(disassembly_result.code_refs_to[function_start])
|
|
45
|
+
return num_calls
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def fromDict(cls, statistics_dict):
|
|
49
|
+
statistics = cls(None)
|
|
50
|
+
statistics.num_functions = statistics_dict["num_functions"]
|
|
51
|
+
statistics.num_recursive_functions = statistics_dict["num_recursive_functions"]
|
|
52
|
+
statistics.num_leaf_functions = statistics_dict["num_leaf_functions"]
|
|
53
|
+
statistics.num_basic_blocks = statistics_dict["num_basic_blocks"]
|
|
54
|
+
statistics.num_instructions = statistics_dict["num_instructions"]
|
|
55
|
+
statistics.num_api_calls = statistics_dict["num_api_calls"]
|
|
56
|
+
statistics.num_function_calls = statistics_dict["num_function_calls"]
|
|
57
|
+
statistics.num_failed_functions = statistics_dict["num_failed_functions"]
|
|
58
|
+
statistics.num_failed_instructions = statistics_dict["num_failed_instructions"]
|
|
59
|
+
return statistics
|
|
60
|
+
|
|
61
|
+
def toDict(self):
|
|
62
|
+
return {
|
|
63
|
+
"num_functions": self.num_functions,
|
|
64
|
+
"num_recursive_functions": self.num_recursive_functions,
|
|
65
|
+
"num_leaf_functions": self.num_leaf_functions,
|
|
66
|
+
"num_basic_blocks": self.num_basic_blocks,
|
|
67
|
+
"num_instructions": self.num_instructions,
|
|
68
|
+
"num_api_calls": self.num_api_calls,
|
|
69
|
+
"num_function_calls": self.num_function_calls,
|
|
70
|
+
"num_failed_functions": self.num_failed_functions,
|
|
71
|
+
"num_failed_instructions": self.num_failed_instructions,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def __add__(self, other):
|
|
75
|
+
if not isinstance(other, DisassemblyStatistics):
|
|
76
|
+
raise ValueError("Needs another DisassemblyStatistics to perform addition of values")
|
|
77
|
+
self.num_functions += other.num_functions
|
|
78
|
+
self.num_recursive_functions += other.num_recursive_functions
|
|
79
|
+
self.num_leaf_functions += other.num_leaf_functions
|
|
80
|
+
self.num_basic_blocks += other.num_basic_blocks
|
|
81
|
+
self.num_instructions += other.num_instructions
|
|
82
|
+
self.num_api_calls += other.num_api_calls
|
|
83
|
+
self.num_function_calls += other.num_function_calls
|
|
84
|
+
self.num_failed_functions += other.num_failed_functions
|
|
85
|
+
self.num_failed_instructions += other.num_failed_instructions
|
|
86
|
+
return self
|
smda/SmdaConfig.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SmdaConfig:
|
|
6
|
+
# note to self: always change this in setup.py as well!
|
|
7
|
+
VERSION = "2.4.2"
|
|
8
|
+
ESCAPER_DOWNWARD_COMPATIBILITY = "1.13.16"
|
|
9
|
+
CONFIG_FILE_PATH = str(os.path.abspath(__file__))
|
|
10
|
+
PROJECT_ROOT = str(os.path.abspath(os.sep.join([CONFIG_FILE_PATH, "..", ".."])))
|
|
11
|
+
|
|
12
|
+
### An (optional) WinAPI database as generated by ApiScout (https://github.com/danielplohmann/apiscout)
|
|
13
|
+
API_COLLECTION_FILES = {}
|
|
14
|
+
### global logging-config setup
|
|
15
|
+
# Only do basicConfig if no handlers have been configured
|
|
16
|
+
LOG_PATH = "./"
|
|
17
|
+
LOG_LEVEL = logging.INFO
|
|
18
|
+
LOG_FORMAT = "%(asctime)-15s: %(name)-32s - %(message)s"
|
|
19
|
+
|
|
20
|
+
### SMDA disassembler config
|
|
21
|
+
# maximum time in seconds for disassembly to complete
|
|
22
|
+
TIMEOUT = 300
|
|
23
|
+
# maximum number of bytes to allocate while loading
|
|
24
|
+
MAX_IMAGE_SIZE = 100 * 1024 * 1024
|
|
25
|
+
# store raw binary buffer in SmdaReport to enable carving data from refs
|
|
26
|
+
STORE_BUFFER = False
|
|
27
|
+
# extract strings during disassembly
|
|
28
|
+
WITH_STRINGS = False
|
|
29
|
+
# the queue to use for candidate management
|
|
30
|
+
CANDIDATE_QUEUE = "PriorityQueue" # choose from: ["BracketQueue", "PriorityQueue"]
|
|
31
|
+
# improve disassembly by resolving references through data flows
|
|
32
|
+
USE_ALIGNMENT = True
|
|
33
|
+
USE_SYMBOLS_AS_CANDIDATES = True
|
|
34
|
+
RESOLVE_REGISTER_CALLS = True
|
|
35
|
+
# limit this to avoid blowing up analysis time for weird samples
|
|
36
|
+
MAX_INDIRECT_CALLS_PER_BASIC_BLOCK = 50
|
|
37
|
+
HIGH_ACCURACY = True
|
|
38
|
+
RESOLVE_TAILCALLS = False
|
|
39
|
+
# optional metadata generation options
|
|
40
|
+
CALCULATE_SCC = True
|
|
41
|
+
CALCULATE_NESTING = True
|
|
42
|
+
CALCULATE_HASHING = True
|
|
43
|
+
# confidence score to use for filtering functions before including them in the output
|
|
44
|
+
CONFIDENCE_THRESHOLD = 0.0
|
smda/__init__.py
ADDED
|
File without changes
|