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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import logging
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from dnfile import dnPE
|
|
9
|
+
import dnfile
|
|
10
|
+
from dncil.cil.body import CilMethodBody
|
|
11
|
+
from dncil.cil.body.reader import CilMethodBodyReaderBase
|
|
12
|
+
from dncil.cil.error import MethodBodyFormatError
|
|
13
|
+
from dncil.clr.token import InvalidToken, StringToken, Token
|
|
14
|
+
from dnfile.enums import MetadataTables
|
|
15
|
+
|
|
16
|
+
from smda.common.labelprovider.CilSymbolProvider import CilSymbolProvider
|
|
17
|
+
from smda.DisassemblyResult import DisassemblyResult
|
|
18
|
+
|
|
19
|
+
from .FunctionAnalysisState import FunctionAnalysisState
|
|
20
|
+
|
|
21
|
+
LOGGER = logging.getLogger(__name__)
|
|
22
|
+
DOTNET_META_TABLES_BY_INDEX = {table.value: table.name for table in MetadataTables}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_dotnet_user_string(pe, token: StringToken) -> Union[str, InvalidToken]:
|
|
26
|
+
"""read user string from #US stream"""
|
|
27
|
+
try:
|
|
28
|
+
user_string: Optional[dnfile.stream.UserString] = pe.net.user_strings.get(token.rid)
|
|
29
|
+
except UnicodeDecodeError:
|
|
30
|
+
return InvalidToken(token.value)
|
|
31
|
+
|
|
32
|
+
if user_string is None or (isinstance(user_string, bytes) or user_string.value is None):
|
|
33
|
+
return InvalidToken(token.value)
|
|
34
|
+
|
|
35
|
+
return user_string.value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def resolve_token(pe, token: Token) -> Any:
|
|
39
|
+
""" """
|
|
40
|
+
if isinstance(token, StringToken):
|
|
41
|
+
return read_dotnet_user_string(pe, token)
|
|
42
|
+
|
|
43
|
+
table_name: str = DOTNET_META_TABLES_BY_INDEX.get(token.table, "")
|
|
44
|
+
if not table_name:
|
|
45
|
+
# table_index is not valid
|
|
46
|
+
return InvalidToken(token.value)
|
|
47
|
+
|
|
48
|
+
table: Any = getattr(pe.net.mdtables, table_name, None)
|
|
49
|
+
if table is None:
|
|
50
|
+
# table index is valid but table is not present
|
|
51
|
+
return InvalidToken(token.value)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
return table.rows[token.rid - 1]
|
|
55
|
+
except IndexError:
|
|
56
|
+
# table index is valid but row index is not valid
|
|
57
|
+
return InvalidToken(token.value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def format_operand(pe, operand: Any) -> str:
|
|
61
|
+
""" """
|
|
62
|
+
if isinstance(operand, Token):
|
|
63
|
+
operand = resolve_token(pe, operand)
|
|
64
|
+
if isinstance(operand, str):
|
|
65
|
+
return f'"{operand}"'
|
|
66
|
+
elif isinstance(operand, int):
|
|
67
|
+
return hex(operand)
|
|
68
|
+
elif isinstance(operand, list):
|
|
69
|
+
return f"[{', '.join([f'({x:04X})' for x in operand])}]"
|
|
70
|
+
elif isinstance(operand, dnfile.mdtable.MemberRefRow):
|
|
71
|
+
if isinstance(operand.Class.row, (dnfile.mdtable.TypeRefRow,)):
|
|
72
|
+
return f"{str(operand.Class.row.TypeNamespace)}.{operand.Class.row.TypeName}::{operand.Name}"
|
|
73
|
+
else:
|
|
74
|
+
return f"{operand.Name}"
|
|
75
|
+
elif isinstance(operand, dnfile.mdtable.MethodSpecRow):
|
|
76
|
+
operand = operand.Method.row
|
|
77
|
+
if isinstance(operand, (dnfile.mdtable.TypeRefRow,)):
|
|
78
|
+
return f"{str(operand.TypeNamespace)}.{operand.TypeName}::{operand.Name}"
|
|
79
|
+
else:
|
|
80
|
+
return f"{operand.Name}"
|
|
81
|
+
elif isinstance(operand, dnfile.mdtable.TypeRefRow):
|
|
82
|
+
return f"{str(operand.TypeNamespace)}.{operand.TypeName}"
|
|
83
|
+
elif isinstance(operand, (dnfile.mdtable.FieldRow, dnfile.mdtable.MethodDefRow)):
|
|
84
|
+
return f"{operand.Name}"
|
|
85
|
+
elif operand is None:
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
return str(operand)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DnfileMethodBodyReader(CilMethodBodyReaderBase):
|
|
92
|
+
def __init__(self, pe, row):
|
|
93
|
+
""" """
|
|
94
|
+
self.pe: dnPE = pe
|
|
95
|
+
self.offset: int = self.pe.get_offset_from_rva(row.Rva)
|
|
96
|
+
|
|
97
|
+
def read(self, n: int) -> bytes:
|
|
98
|
+
""" """
|
|
99
|
+
data: bytes = self.pe.get_data(self.pe.get_rva_from_offset(self.offset), n)
|
|
100
|
+
self.offset += n
|
|
101
|
+
return data
|
|
102
|
+
|
|
103
|
+
def tell(self) -> int:
|
|
104
|
+
""" """
|
|
105
|
+
return self.offset
|
|
106
|
+
|
|
107
|
+
def seek(self, offset: int) -> int:
|
|
108
|
+
""" """
|
|
109
|
+
self.offset = offset
|
|
110
|
+
return self.offset
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CilDisassembler:
|
|
114
|
+
def __init__(self, config):
|
|
115
|
+
self.config = config
|
|
116
|
+
self._tfidf = None
|
|
117
|
+
self.binary_info = None
|
|
118
|
+
self.label_providers = []
|
|
119
|
+
self.cil_label_provider = CilSymbolProvider(self.config)
|
|
120
|
+
self._addLabelProviders()
|
|
121
|
+
self.disassembly = DisassemblyResult()
|
|
122
|
+
self.disassembly.smda_version = config.VERSION
|
|
123
|
+
|
|
124
|
+
def addPdbFile(self, binary_info, pdb_path):
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
def _addLabelProviders(self):
|
|
128
|
+
self.label_providers.append(self.cil_label_provider)
|
|
129
|
+
|
|
130
|
+
def _updateLabelProviders(self, binary_info):
|
|
131
|
+
for provider in self.label_providers:
|
|
132
|
+
provider.update(binary_info)
|
|
133
|
+
|
|
134
|
+
def resolveSymbol(self, address):
|
|
135
|
+
for provider in self.label_providers:
|
|
136
|
+
if not provider.isSymbolProvider():
|
|
137
|
+
continue
|
|
138
|
+
result = provider.getSymbol(address)
|
|
139
|
+
if result:
|
|
140
|
+
return result
|
|
141
|
+
return ""
|
|
142
|
+
|
|
143
|
+
def _updateApiInformation(self, from_addr, ins_bytes, api_function):
|
|
144
|
+
if ins_bytes.endswith(b"\x0a"):
|
|
145
|
+
if api_function.startswith("<dnfile.mdtable.MemberRefRow"):
|
|
146
|
+
return
|
|
147
|
+
self.disassembly.addr_to_api[from_addr] = api_function
|
|
148
|
+
|
|
149
|
+
def analyzeFunction(self, pe, start_addr, method_body):
|
|
150
|
+
LOGGER.debug("analyzeFunction() starting analysis of candidate @0x%08x", start_addr)
|
|
151
|
+
state = FunctionAnalysisState(start_addr, method_body.instructions[0].offset, self.disassembly)
|
|
152
|
+
for insn in method_body.instructions:
|
|
153
|
+
state.setNextInstructionReachable(True)
|
|
154
|
+
i_bytes = insn.get_bytes()
|
|
155
|
+
i_address = insn.offset
|
|
156
|
+
i_size = len(i_bytes)
|
|
157
|
+
i_mnemonic = str(insn.opcode)
|
|
158
|
+
i_op_str = format_operand(pe, insn.operand)
|
|
159
|
+
# debug output for all instructions
|
|
160
|
+
if False:
|
|
161
|
+
from smda.cil.CilInstructionEscaper import CilInstructionEscaper
|
|
162
|
+
from smda.common.SmdaInstruction import SmdaInstruction
|
|
163
|
+
|
|
164
|
+
smda_ins = SmdaInstruction([i_address, i_bytes.hex(), i_mnemonic, i_op_str])
|
|
165
|
+
escaped = CilInstructionEscaper.escapeBinary(smda_ins)
|
|
166
|
+
print(
|
|
167
|
+
f"{escaped:<20}"
|
|
168
|
+
+ f"{insn.offset:04X}"
|
|
169
|
+
+ " "
|
|
170
|
+
+ f"{' '.join(f'{b:02x}' for b in insn.get_bytes()): <20}"
|
|
171
|
+
+ f"{str(insn.opcode): <15}"
|
|
172
|
+
+ format_operand(pe, insn.operand)
|
|
173
|
+
)
|
|
174
|
+
# https://en.wikipedia.org/wiki/List_of_CIL_instructions
|
|
175
|
+
if i_mnemonic in ["ret"]:
|
|
176
|
+
state.setNextInstructionReachable(False)
|
|
177
|
+
if i_mnemonic in [
|
|
178
|
+
"beq",
|
|
179
|
+
"beq.s",
|
|
180
|
+
"bge",
|
|
181
|
+
"bge.s",
|
|
182
|
+
"bge.un",
|
|
183
|
+
"bge.un.s",
|
|
184
|
+
"bgt",
|
|
185
|
+
"bgt.s",
|
|
186
|
+
"bgt.un",
|
|
187
|
+
"bgt.un.s",
|
|
188
|
+
"ble",
|
|
189
|
+
"ble.s",
|
|
190
|
+
"ble.un",
|
|
191
|
+
"ble.un.s",
|
|
192
|
+
"blt",
|
|
193
|
+
"blt.s",
|
|
194
|
+
"blt.un",
|
|
195
|
+
"blt.un.s",
|
|
196
|
+
"bne.un",
|
|
197
|
+
"bne.un.s",
|
|
198
|
+
"br",
|
|
199
|
+
"br.s",
|
|
200
|
+
"brfalse",
|
|
201
|
+
"brfalse.s",
|
|
202
|
+
"brinst",
|
|
203
|
+
"brinst.s",
|
|
204
|
+
"brnull",
|
|
205
|
+
"brnull.s",
|
|
206
|
+
"brtrue",
|
|
207
|
+
"brtrue.s",
|
|
208
|
+
"brzero",
|
|
209
|
+
"brzero.s",
|
|
210
|
+
]:
|
|
211
|
+
target = int(i_op_str, 16)
|
|
212
|
+
state.addCodeRef(i_address, target, by_jump=True)
|
|
213
|
+
if i_mnemonic in ["jmp"]:
|
|
214
|
+
raise Exception(
|
|
215
|
+
"Found unhandled CIL jmp instruction, report back its structure and have Daniel fix it."
|
|
216
|
+
)
|
|
217
|
+
target = int(i_op_str, 16)
|
|
218
|
+
state.addCodeRef(i_address, target, by_jump=True)
|
|
219
|
+
state.setNextInstructionReachable(False)
|
|
220
|
+
if i_mnemonic in ["ldstr"]:
|
|
221
|
+
# we possibly want to extract and collect these and put them in the stringref part of SmdaFunction
|
|
222
|
+
self.disassembly.addStringRef(start_addr, i_address, i_op_str[1:-1])
|
|
223
|
+
if i_mnemonic in ["call", "callvirt"]:
|
|
224
|
+
self._updateApiInformation(i_address, i_bytes, i_op_str)
|
|
225
|
+
# https://blog.objektkultur.de/about-tail-recursion-in-.net/
|
|
226
|
+
if state.prev_opcode.startswith("tail"):
|
|
227
|
+
state.setNextInstructionReachable(False)
|
|
228
|
+
if i_bytes.endswith(b"\x06"):
|
|
229
|
+
operand = resolve_token(pe, insn.operand)
|
|
230
|
+
if isinstance(operand, dnfile.mdtable.MethodDefRow):
|
|
231
|
+
# override operand string with "address" of the method
|
|
232
|
+
method_name = self.cil_label_provider.decodeSymbolName(operand.Name.value)
|
|
233
|
+
i_op_str = f"0x{self.cil_label_provider.getAddress(method_name):x}"
|
|
234
|
+
if i_mnemonic in ["throw"]:
|
|
235
|
+
state.setNextInstructionReachable(False)
|
|
236
|
+
if i_mnemonic in ["switch"]:
|
|
237
|
+
next_addrs = []
|
|
238
|
+
for target in insn.operand:
|
|
239
|
+
next_addrs.append(target)
|
|
240
|
+
state.addCodeRef(i_address, target, by_jump=True)
|
|
241
|
+
state.prev_opcode = i_mnemonic
|
|
242
|
+
state.addInstruction(i_address, i_size, i_mnemonic, i_op_str, i_bytes)
|
|
243
|
+
state.label = self.resolveSymbol(state.start_addr)
|
|
244
|
+
state.finalizeAnalysis()
|
|
245
|
+
return state
|
|
246
|
+
|
|
247
|
+
def analyzeBuffer(self, binary_info, cbAnalysisTimeout=None):
|
|
248
|
+
LOGGER.debug(
|
|
249
|
+
"Analyzing buffer with %d bytes @0x%08x",
|
|
250
|
+
binary_info.binary_size,
|
|
251
|
+
binary_info.base_addr,
|
|
252
|
+
)
|
|
253
|
+
self._updateLabelProviders(binary_info)
|
|
254
|
+
self.disassembly = DisassemblyResult()
|
|
255
|
+
self.disassembly.smda_version = self.config.VERSION
|
|
256
|
+
self.disassembly.setBinaryInfo(binary_info)
|
|
257
|
+
self.disassembly.binary_info.architecture = "cil"
|
|
258
|
+
self.disassembly.analysis_start_ts = datetime.datetime.now(datetime.timezone.utc)
|
|
259
|
+
self.disassembly.language = "cil"
|
|
260
|
+
|
|
261
|
+
LOGGER.debug("Starting parser-based analysis.")
|
|
262
|
+
pe = dnfile.dnPE(data=binary_info.raw_data)
|
|
263
|
+
for row in pe.net.mdtables.MethodDef:
|
|
264
|
+
if not row.ImplFlags.miIL or any((row.Flags.mdAbstract, row.Flags.mdPinvokeImpl)):
|
|
265
|
+
# skip methods that do not have a method body
|
|
266
|
+
continue
|
|
267
|
+
try:
|
|
268
|
+
method_body = CilMethodBody(DnfileMethodBodyReader(pe, row))
|
|
269
|
+
except MethodBodyFormatError as e:
|
|
270
|
+
LOGGER.error(e)
|
|
271
|
+
continue
|
|
272
|
+
if not method_body.instructions:
|
|
273
|
+
continue
|
|
274
|
+
if cbAnalysisTimeout and cbAnalysisTimeout():
|
|
275
|
+
break
|
|
276
|
+
self.analyzeFunction(pe, method_body.offset, method_body)
|
|
277
|
+
# package up and finish
|
|
278
|
+
self.disassembly.analysis_end_ts = datetime.datetime.now(datetime.timezone.utc)
|
|
279
|
+
if cbAnalysisTimeout():
|
|
280
|
+
self.disassembly.analysis_timeout = True
|
|
281
|
+
return self.disassembly
|