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
@@ -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