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.
Files changed (65) hide show
  1. smda/Disassembler.py +180 -0
  2. smda/DisassemblyResult.py +297 -0
  3. smda/DisassemblyStatistics.py +86 -0
  4. smda/SmdaConfig.py +44 -0
  5. smda/__init__.py +0 -0
  6. smda/cil/CilDisassembler.py +281 -0
  7. smda/cil/CilInstructionEscaper.py +473 -0
  8. smda/cil/FunctionAnalysisState.py +185 -0
  9. smda/cil/__init__.py +0 -0
  10. smda/common/BasicBlock.py +14 -0
  11. smda/common/BinaryInfo.py +111 -0
  12. smda/common/BlockLocator.py +42 -0
  13. smda/common/CodeXref.py +26 -0
  14. smda/common/DominatorTree.py +211 -0
  15. smda/common/SmdaBasicBlock.py +105 -0
  16. smda/common/SmdaFunction.py +297 -0
  17. smda/common/SmdaInstruction.py +124 -0
  18. smda/common/SmdaReport.py +330 -0
  19. smda/common/TailcallAnalyzer.py +139 -0
  20. smda/common/Tarjan.py +84 -0
  21. smda/common/__init__.py +0 -0
  22. smda/common/labelprovider/AbstractLabelProvider.py +41 -0
  23. smda/common/labelprovider/CilSymbolProvider.py +43 -0
  24. smda/common/labelprovider/DelphiKbSymbolProvider.py +95 -0
  25. smda/common/labelprovider/DelphiReSymProvider.py +440 -0
  26. smda/common/labelprovider/ElfApiResolver.py +62 -0
  27. smda/common/labelprovider/ElfSymbolProvider.py +74 -0
  28. smda/common/labelprovider/GoLabelProvider.py +187 -0
  29. smda/common/labelprovider/OrdinalHelper.py +46 -0
  30. smda/common/labelprovider/PdbSymbolProvider.py +89 -0
  31. smda/common/labelprovider/PeSymbolProvider.py +105 -0
  32. smda/common/labelprovider/WinApiResolver.py +104 -0
  33. smda/common/labelprovider/__init__.py +0 -0
  34. smda/ida/BackendInterface.py +36 -0
  35. smda/ida/IdaExporter.py +89 -0
  36. smda/ida/IdaInterface.py +391 -0
  37. smda/ida/__init__.py +0 -0
  38. smda/intel/BitnessAnalyzer.py +49 -0
  39. smda/intel/FunctionAnalysisState.py +293 -0
  40. smda/intel/FunctionCandidate.py +198 -0
  41. smda/intel/FunctionCandidateManager.py +652 -0
  42. smda/intel/IndirectCallAnalyzer.py +247 -0
  43. smda/intel/IntelDisassembler.py +596 -0
  44. smda/intel/IntelInstructionEscaper.py +2354 -0
  45. smda/intel/JumpTableAnalyzer.py +212 -0
  46. smda/intel/LanguageAnalyzer.py +282 -0
  47. smda/intel/MnemonicTfIdf.py +470 -0
  48. smda/intel/__init__.py +0 -0
  49. smda/intel/definitions.py +226 -0
  50. smda/utility/BracketQueue.py +59 -0
  51. smda/utility/DelphiKbFileLoader.py +27 -0
  52. smda/utility/ElfFileLoader.py +258 -0
  53. smda/utility/FileLoader.py +63 -0
  54. smda/utility/MachoFileLoader.py +219 -0
  55. smda/utility/MemoryFileLoader.py +7 -0
  56. smda/utility/PeFileLoader.py +174 -0
  57. smda/utility/PriorityQueue.py +38 -0
  58. smda/utility/StringExtractor.py +171 -0
  59. smda/utility/__init__.py +0 -0
  60. smda-2.4.2.data/data/LICENSE +9 -0
  61. smda-2.4.2.dist-info/METADATA +175 -0
  62. smda-2.4.2.dist-info/RECORD +65 -0
  63. smda-2.4.2.dist-info/WHEEL +5 -0
  64. smda-2.4.2.dist-info/licenses/LICENSE +9 -0
  65. 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