smda 2.3.1__tar.gz → 2.4.2__tar.gz
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-2.3.1/smda.egg-info → smda-2.4.2}/PKG-INFO +19 -2
- {smda-2.3.1 → smda-2.4.2}/README.md +3 -0
- {smda-2.3.1 → smda-2.4.2}/setup.py +1 -1
- {smda-2.3.1 → smda-2.4.2}/smda/SmdaConfig.py +1 -1
- {smda-2.3.1 → smda-2.4.2}/smda/common/BinaryInfo.py +1 -1
- {smda-2.3.1 → smda-2.4.2}/smda/common/SmdaReport.py +1 -1
- smda-2.4.2/smda/common/labelprovider/DelphiReSymProvider.py +440 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/FunctionCandidateManager.py +8 -1
- {smda-2.3.1 → smda-2.4.2}/smda/intel/IntelDisassembler.py +2 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/LanguageAnalyzer.py +10 -2
- {smda-2.3.1 → smda-2.4.2}/smda/utility/FileLoader.py +1 -1
- {smda-2.3.1 → smda-2.4.2}/smda/utility/PeFileLoader.py +11 -13
- {smda-2.3.1 → smda-2.4.2/smda.egg-info}/PKG-INFO +19 -2
- {smda-2.3.1 → smda-2.4.2}/smda.egg-info/SOURCES.txt +2 -5
- smda-2.4.2/tests/testPeFileLoader.py +24 -0
- {smda-2.3.1 → smda-2.4.2}/LICENSE +0 -0
- {smda-2.3.1 → smda-2.4.2}/pyproject.toml +0 -0
- {smda-2.3.1 → smda-2.4.2}/setup.cfg +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/Disassembler.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/DisassemblyResult.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/DisassemblyStatistics.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/cil/CilDisassembler.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/cil/CilInstructionEscaper.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/cil/FunctionAnalysisState.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/cil/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/BasicBlock.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/BlockLocator.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/CodeXref.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/DominatorTree.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/SmdaBasicBlock.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/SmdaFunction.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/SmdaInstruction.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/TailcallAnalyzer.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/Tarjan.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/AbstractLabelProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/CilSymbolProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/DelphiKbSymbolProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/ElfApiResolver.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/ElfSymbolProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/GoLabelProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/OrdinalHelper.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/PdbSymbolProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/PeSymbolProvider.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/WinApiResolver.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/common/labelprovider/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/ida/BackendInterface.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/ida/IdaExporter.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/ida/IdaInterface.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/ida/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/BitnessAnalyzer.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/FunctionAnalysisState.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/FunctionCandidate.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/IndirectCallAnalyzer.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/IntelInstructionEscaper.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/JumpTableAnalyzer.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/MnemonicTfIdf.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/intel/definitions.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/BracketQueue.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/DelphiKbFileLoader.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/ElfFileLoader.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/MachoFileLoader.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/MemoryFileLoader.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/PriorityQueue.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/StringExtractor.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda/utility/__init__.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda.egg-info/dependency_links.txt +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda.egg-info/requires.txt +0 -0
- {smda-2.3.1 → smda-2.4.2}/smda.egg-info/top_level.txt +0 -0
- {smda-2.3.1 → smda-2.4.2}/tests/testBracketQueue.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/tests/testEscaper.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/tests/testFileFormatParsers.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/tests/testIntegration.py +0 -0
- {smda-2.3.1 → smda-2.4.2}/tests/testTarjan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: smda
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.
|
|
5
5
|
Home-page: https://github.com/danielplohmann/smda
|
|
6
6
|
Author: Daniel Plohmann
|
|
@@ -15,6 +15,20 @@ Classifier: Topic :: Security
|
|
|
15
15
|
Classifier: Topic :: Software Development :: Disassemblers
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
+
Requires-Dist: capstone
|
|
19
|
+
Requires-Dist: dncil
|
|
20
|
+
Requires-Dist: dnfile
|
|
21
|
+
Requires-Dist: lief>=0.16.0
|
|
22
|
+
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
24
|
+
Dynamic: classifier
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: description-content-type
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: license
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
Dynamic: requires-dist
|
|
31
|
+
Dynamic: summary
|
|
18
32
|
|
|
19
33
|
|
|
20
34
|
# SMDA
|
|
@@ -119,6 +133,9 @@ make test
|
|
|
119
133
|
```
|
|
120
134
|
|
|
121
135
|
## Version History
|
|
136
|
+
* 2025-11-28: v2.4.2 - Fix for a bug when extracting and merging code areas from section tables. (THX: @r0ny123)
|
|
137
|
+
* 2025-11-28: v2.4.1 - Modernized packaging by also building a wheel. (THX: @dimbleby)
|
|
138
|
+
* 2025-11-21: v2.4.0 - Integration of DelphiReSym by @WenzWenzWenz for Delphi VMT parsing, thanks to @r0ny123 for adapting it!!
|
|
122
139
|
* 2025-10-21: v2.3.1 - Fixed lief error for section/segment flags in ELF files crashing file loading. Now properly parsing and providing symbol info for PEs in their own xmetadata section.
|
|
123
140
|
* 2025-10-21: v2.3.0 - Major code refactor and cleanup, with many thanks to the contribution @r0ny123!!
|
|
124
141
|
* 2025-07-25: v2.2.3 - Minor bugfixes.
|
|
@@ -101,6 +101,9 @@ make test
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
## Version History
|
|
104
|
+
* 2025-11-28: v2.4.2 - Fix for a bug when extracting and merging code areas from section tables. (THX: @r0ny123)
|
|
105
|
+
* 2025-11-28: v2.4.1 - Modernized packaging by also building a wheel. (THX: @dimbleby)
|
|
106
|
+
* 2025-11-21: v2.4.0 - Integration of DelphiReSym by @WenzWenzWenz for Delphi VMT parsing, thanks to @r0ny123 for adapting it!!
|
|
104
107
|
* 2025-10-21: v2.3.1 - Fixed lief error for section/segment flags in ELF files crashing file loading. Now properly parsing and providing symbol info for PEs in their own xmetadata section.
|
|
105
108
|
* 2025-10-21: v2.3.0 - Major code refactor and cleanup, with many thanks to the contribution @r0ny123!!
|
|
106
109
|
* 2025-07-25: v2.2.3 - Minor bugfixes.
|
|
@@ -12,7 +12,7 @@ requirements.append("lief>=0.16.0")
|
|
|
12
12
|
setup(
|
|
13
13
|
name="smda",
|
|
14
14
|
# note to self: always change this in config as well.
|
|
15
|
-
version="2.
|
|
15
|
+
version="2.4.2",
|
|
16
16
|
description="A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.",
|
|
17
17
|
long_description_content_type="text/markdown",
|
|
18
18
|
long_description=long_description,
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
|
|
5
5
|
class SmdaConfig:
|
|
6
6
|
# note to self: always change this in setup.py as well!
|
|
7
|
-
VERSION = "2.
|
|
7
|
+
VERSION = "2.4.2"
|
|
8
8
|
ESCAPER_DOWNWARD_COMPATIBILITY = "1.13.16"
|
|
9
9
|
CONFIG_FILE_PATH = str(os.path.abspath(__file__))
|
|
10
10
|
PROJECT_ROOT = str(os.path.abspath(os.sep.join([CONFIG_FILE_PATH, "..", ".."])))
|
|
@@ -94,7 +94,7 @@ class BinaryInfo:
|
|
|
94
94
|
def isInCodeAreas(self, address):
|
|
95
95
|
is_inside = False
|
|
96
96
|
# if no code areas found, assume the whole image is code and calculate according to base address and size
|
|
97
|
-
if len(self.code_areas) == 0:
|
|
97
|
+
if self.code_areas is None or len(self.code_areas) == 0:
|
|
98
98
|
if self.base_addr <= address <= self.base_addr + self.binary_size:
|
|
99
99
|
is_inside = True
|
|
100
100
|
else:
|
|
@@ -226,7 +226,7 @@ class SmdaReport:
|
|
|
226
226
|
smda_report.base_addr = report_dict["base_addr"]
|
|
227
227
|
smda_report.binary_size = report_dict["binary_size"]
|
|
228
228
|
smda_report.bitness = report_dict["bitness"]
|
|
229
|
-
smda_report.code_areas = report_dict["code_areas"]
|
|
229
|
+
smda_report.code_areas = report_dict["code_areas"] if report_dict["code_areas"] is not None else []
|
|
230
230
|
smda_report.code_sections = (
|
|
231
231
|
[("", section[1], section[2]) for section in report_dict["code_sections"]]
|
|
232
232
|
if "code_sections" in report_dict
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
"""
|
|
3
|
+
DelphiReSymProvider - Delphi Symbol Recovery for SMDA
|
|
4
|
+
Adapted from DelphiReSym by Lukas Wenz (https://github.com/WenzWenzWenz/DelphiReSym)
|
|
5
|
+
Parses Delphi metadata structures (VMT, MDT, RTTI) to extract function symbols from Delphi binaries.
|
|
6
|
+
Supports Delphi versions 2010 through 12 Athens.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import struct
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from .AbstractLabelProvider import AbstractLabelProvider
|
|
15
|
+
|
|
16
|
+
LOGGER = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# VMT (Virtual Method Table) structure constants
|
|
19
|
+
VMT_MDT_FIELD_INDEX = 6 # Index of MDT pointer field in VMT
|
|
20
|
+
VMT_RTTI_FIELD_INDEX = 4 # Index of RTTI_Class pointer field in VMT
|
|
21
|
+
VMT_VMETHOD_START_INDEX = 11 # First virtual method field index
|
|
22
|
+
VMT_VMETHOD_END_INDEX = 22 # Last virtual method field index (exclusive)
|
|
23
|
+
VMT_SAFECALL_EXCEPTION_INDEX = 14 # SafeCallException field (optional, skip in validation)
|
|
24
|
+
|
|
25
|
+
# Architecture-specific VMT jump distances (distance from VMT start to first method)
|
|
26
|
+
VMT_JUMP_DIST_32BIT = 88
|
|
27
|
+
VMT_JUMP_DIST_64BIT = 200
|
|
28
|
+
|
|
29
|
+
# RTTI (Runtime Type Information) constants
|
|
30
|
+
RTTI_MAX_MAGIC_BYTE = 0x15 # Maximum valid magic byte value for RTTI objects
|
|
31
|
+
RTTI_CLASS_MAGIC_BYTE = 0x07 # Magic byte indicating RTTI_Class type
|
|
32
|
+
|
|
33
|
+
# Method entry structure magic value for parameter offset calculation
|
|
34
|
+
# This value is from the original DelphiReSym and works for Delphi 11
|
|
35
|
+
PARAM_ENTRY_PADDING = 3
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class ArchitectureSettings:
|
|
40
|
+
"""Architecture-specific settings for VMT/MDT parsing."""
|
|
41
|
+
|
|
42
|
+
ptr_size: int
|
|
43
|
+
jump_dist: int # Expected distance from VMT to first method
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def mdt_offset(self) -> int:
|
|
47
|
+
"""Offset to MDT pointer within VMT structure."""
|
|
48
|
+
return self.ptr_size * VMT_MDT_FIELD_INDEX
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def rtti_offset(self) -> int:
|
|
52
|
+
"""Offset to RTTI_Class pointer within VMT structure."""
|
|
53
|
+
return self.ptr_size * VMT_RTTI_FIELD_INDEX
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ParameterInfo:
|
|
58
|
+
"""Information about a function parameter."""
|
|
59
|
+
|
|
60
|
+
type_name: Optional[str] = None
|
|
61
|
+
parameter_name: str = ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class MethodInfo:
|
|
66
|
+
"""Information about a method extracted from MDT."""
|
|
67
|
+
|
|
68
|
+
function_offset: int
|
|
69
|
+
function_name: str = ""
|
|
70
|
+
return_type: str = "void"
|
|
71
|
+
parameters: List[ParameterInfo] = field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DelphiReSymProvider(AbstractLabelProvider):
|
|
75
|
+
"""
|
|
76
|
+
Label provider for extracting Delphi symbols from modern Delphi executables.
|
|
77
|
+
Parses Virtual Method Tables (VMT), Method Definition Tables (MDT), and RTTI metadata.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, config):
|
|
81
|
+
self._config = config
|
|
82
|
+
self._func_symbols = {}
|
|
83
|
+
self._binary = None
|
|
84
|
+
self._base_addr = 0
|
|
85
|
+
self._bitness = 32
|
|
86
|
+
self._code_start = 0
|
|
87
|
+
self._code_end = 0
|
|
88
|
+
self._settings = None
|
|
89
|
+
|
|
90
|
+
def update(self, binary_info):
|
|
91
|
+
"""Parse Delphi metadata from the given binary."""
|
|
92
|
+
self._binary = binary_info.binary
|
|
93
|
+
self._base_addr = binary_info.base_addr
|
|
94
|
+
self._bitness = binary_info.bitness
|
|
95
|
+
|
|
96
|
+
# Only process PE files with .text sections
|
|
97
|
+
if not self._is_compatible():
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Determine code areas
|
|
101
|
+
code_areas = binary_info.code_areas
|
|
102
|
+
if not code_areas:
|
|
103
|
+
LOGGER.debug("No code areas found, skipping DelphiReSym parsing")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Code areas are virtual addresses - convert to file offsets for scanning
|
|
107
|
+
# Use the first code area (typically .text section)
|
|
108
|
+
code_area_start_va = code_areas[0][0]
|
|
109
|
+
code_area_end_va = code_areas[0][1]
|
|
110
|
+
|
|
111
|
+
# Convert virtual addresses to file offsets
|
|
112
|
+
self._code_start = code_area_start_va - self._base_addr
|
|
113
|
+
self._code_end = code_area_end_va - self._base_addr
|
|
114
|
+
|
|
115
|
+
# Validate the offsets are within binary bounds
|
|
116
|
+
if self._code_start < 0 or self._code_end > len(self._binary):
|
|
117
|
+
LOGGER.debug(
|
|
118
|
+
f"Code area offsets out of bounds: {self._code_start}-{self._code_end}, binary size: {len(self._binary)}"
|
|
119
|
+
)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# Set up architecture-specific settings
|
|
123
|
+
if self._bitness == 32:
|
|
124
|
+
self._settings = ArchitectureSettings(ptr_size=4, jump_dist=VMT_JUMP_DIST_32BIT)
|
|
125
|
+
elif self._bitness == 64:
|
|
126
|
+
self._settings = ArchitectureSettings(ptr_size=8, jump_dist=VMT_JUMP_DIST_64BIT)
|
|
127
|
+
else:
|
|
128
|
+
LOGGER.warning(f"Unsupported bitness: {self._bitness}")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
LOGGER.debug("Starting DelphiReSym symbol extraction...")
|
|
133
|
+
self._parse_delphi_symbols()
|
|
134
|
+
if self._func_symbols:
|
|
135
|
+
LOGGER.info(f"Extracted {len(self._func_symbols)} Delphi symbols")
|
|
136
|
+
else:
|
|
137
|
+
LOGGER.debug("No Delphi symbols extracted")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
LOGGER.warning(f"Error during DelphiReSym parsing: {e}")
|
|
140
|
+
|
|
141
|
+
def _is_compatible(self):
|
|
142
|
+
"""Check if binary is a PE file."""
|
|
143
|
+
if len(self._binary) < 0x40:
|
|
144
|
+
return False
|
|
145
|
+
return self._binary[:2] == b"MZ"
|
|
146
|
+
|
|
147
|
+
def _read_ptr(self, offset: int) -> Optional[int]:
|
|
148
|
+
"""Read a pointer at the given offset."""
|
|
149
|
+
try:
|
|
150
|
+
if self._settings.ptr_size == 4:
|
|
151
|
+
return struct.unpack("<I", self._binary[offset : offset + 4])[0]
|
|
152
|
+
else:
|
|
153
|
+
return struct.unpack("<Q", self._binary[offset : offset + 8])[0]
|
|
154
|
+
except (struct.error, IndexError):
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def _read_byte(self, offset: int) -> Optional[int]:
|
|
158
|
+
"""Read a byte at the given offset."""
|
|
159
|
+
if 0 <= offset < len(self._binary):
|
|
160
|
+
return self._binary[offset]
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def _read_short(self, offset: int) -> Optional[int]:
|
|
164
|
+
"""Read a 16-bit value at the given offset."""
|
|
165
|
+
try:
|
|
166
|
+
return struct.unpack("<H", self._binary[offset : offset + 2])[0]
|
|
167
|
+
except (struct.error, IndexError):
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
def _read_pascal_string(self, offset: int) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Read a Pascal-style string (length byte followed by characters).
|
|
173
|
+
Returns empty string on error.
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
length = self._read_byte(offset)
|
|
177
|
+
if length is None or length == 0:
|
|
178
|
+
return ""
|
|
179
|
+
|
|
180
|
+
string_data = self._binary[offset + 1 : offset + 1 + length]
|
|
181
|
+
if len(string_data) < length:
|
|
182
|
+
return ""
|
|
183
|
+
|
|
184
|
+
return string_data.decode("latin-1", errors="ignore")
|
|
185
|
+
except (IndexError, UnicodeDecodeError):
|
|
186
|
+
return ""
|
|
187
|
+
|
|
188
|
+
def _offset_in_code_area(self, offset: int) -> bool:
|
|
189
|
+
"""Check if offset is within code area."""
|
|
190
|
+
return self._code_start <= offset < self._code_end
|
|
191
|
+
|
|
192
|
+
def _addr_to_offset(self, addr: int) -> int:
|
|
193
|
+
"""Convert virtual address to file offset."""
|
|
194
|
+
return addr - self._base_addr
|
|
195
|
+
|
|
196
|
+
def _offset_to_addr(self, offset: int) -> int:
|
|
197
|
+
"""Convert file offset to virtual address."""
|
|
198
|
+
return offset + self._base_addr
|
|
199
|
+
|
|
200
|
+
def _check_vmt_candidate(self, vmt_offset: int, next_struct_offset: int) -> bool:
|
|
201
|
+
"""
|
|
202
|
+
Perform sanity checks on a VMT candidate.
|
|
203
|
+
Returns True if the candidate passes all checks.
|
|
204
|
+
"""
|
|
205
|
+
addresses_to_check = [next_struct_offset]
|
|
206
|
+
|
|
207
|
+
# Check MDT pointer
|
|
208
|
+
mdt_offset_field = vmt_offset + self._settings.mdt_offset
|
|
209
|
+
mdt_addr = self._read_ptr(mdt_offset_field)
|
|
210
|
+
if mdt_addr is not None:
|
|
211
|
+
mdt_offset = self._addr_to_offset(mdt_addr)
|
|
212
|
+
addresses_to_check.append(mdt_offset)
|
|
213
|
+
# MDTs should be at higher addresses than VMTs
|
|
214
|
+
if mdt_offset <= vmt_offset:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
# Check virtual method pointers (fields 11-22, excluding SafeCallException which is optional)
|
|
218
|
+
for field_num in range(VMT_VMETHOD_START_INDEX, VMT_VMETHOD_END_INDEX):
|
|
219
|
+
if field_num != VMT_SAFECALL_EXCEPTION_INDEX:
|
|
220
|
+
field_offset = vmt_offset + (self._settings.ptr_size * field_num)
|
|
221
|
+
method_addr = self._read_ptr(field_offset)
|
|
222
|
+
if method_addr is not None:
|
|
223
|
+
method_offset = self._addr_to_offset(method_addr)
|
|
224
|
+
addresses_to_check.append(method_offset)
|
|
225
|
+
|
|
226
|
+
# All addresses should be within code area
|
|
227
|
+
return all(self._offset_in_code_area(off) for off in addresses_to_check)
|
|
228
|
+
|
|
229
|
+
def _find_vmts(self) -> list:
|
|
230
|
+
"""
|
|
231
|
+
Scan for Virtual Method Tables in the code section.
|
|
232
|
+
Uses a sliding window approach to find VMT structures.
|
|
233
|
+
"""
|
|
234
|
+
vmt_offsets = []
|
|
235
|
+
current_offset = self._code_start
|
|
236
|
+
|
|
237
|
+
LOGGER.debug(f"Scanning for VMTs from 0x{current_offset:x} to 0x{self._code_end:x}")
|
|
238
|
+
|
|
239
|
+
while current_offset < self._code_end - self._settings.ptr_size:
|
|
240
|
+
ptr_value = self._read_ptr(current_offset)
|
|
241
|
+
if ptr_value is None:
|
|
242
|
+
current_offset += 1
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
target_offset = self._addr_to_offset(ptr_value)
|
|
246
|
+
distance = target_offset - current_offset
|
|
247
|
+
|
|
248
|
+
# Check if this matches the expected VMT jump distance
|
|
249
|
+
if distance == self._settings.jump_dist and self._check_vmt_candidate(current_offset, target_offset):
|
|
250
|
+
vmt_offsets.append(current_offset)
|
|
251
|
+
LOGGER.debug(f"Found VMT at offset 0x{current_offset:x}")
|
|
252
|
+
|
|
253
|
+
current_offset += 1
|
|
254
|
+
|
|
255
|
+
return vmt_offsets
|
|
256
|
+
|
|
257
|
+
def _traverse_rtti_object(self, rtti_offset: int) -> Optional[str]:
|
|
258
|
+
"""
|
|
259
|
+
Traverse an RTTI object to extract type information.
|
|
260
|
+
Returns the type name (with namespace for RTTI_Class types).
|
|
261
|
+
"""
|
|
262
|
+
magic_byte = self._read_byte(rtti_offset)
|
|
263
|
+
if magic_byte is None or magic_byte > RTTI_MAX_MAGIC_BYTE:
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
# Read object name (Pascal string at offset +1)
|
|
267
|
+
object_name = self._read_pascal_string(rtti_offset + 1)
|
|
268
|
+
if not object_name:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
# RTTI_Class contains namespace information
|
|
272
|
+
# RTTI_Class structure layout:
|
|
273
|
+
# MagicByte(1) + ObjectName(pascal) + Unknown(1) + Pointers(2*ptr_size) + Unknown(2) + Namespace(pascal)
|
|
274
|
+
if magic_byte == RTTI_CLASS_MAGIC_BYTE:
|
|
275
|
+
namespace_offset = rtti_offset + 1 + len(object_name) + 1 + 2 * self._settings.ptr_size + 2
|
|
276
|
+
namespace = self._read_pascal_string(namespace_offset)
|
|
277
|
+
if namespace:
|
|
278
|
+
return f"{namespace}.{object_name}"
|
|
279
|
+
|
|
280
|
+
return object_name
|
|
281
|
+
|
|
282
|
+
def _resolve_type_from_double_ptr(self, ptr_field_offset: int) -> Optional[str]:
|
|
283
|
+
"""
|
|
284
|
+
Resolve a type name from a double-dereferenced pointer to an RTTI object.
|
|
285
|
+
Used for both return types and parameter types.
|
|
286
|
+
"""
|
|
287
|
+
ptr_addr = self._read_ptr(ptr_field_offset)
|
|
288
|
+
if ptr_addr is None or ptr_addr == 0:
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
ptr_offset = self._addr_to_offset(ptr_addr)
|
|
292
|
+
rtti_addr = self._read_ptr(ptr_offset)
|
|
293
|
+
if rtti_addr is None or rtti_addr == 0:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
rtti_offset = self._addr_to_offset(rtti_addr)
|
|
297
|
+
return self._traverse_rtti_object(rtti_offset)
|
|
298
|
+
|
|
299
|
+
def _extract_method_info(self, method_entry_offset: int) -> Optional[MethodInfo]:
|
|
300
|
+
"""Extract detailed information about a method from its MethodEntry structure."""
|
|
301
|
+
try:
|
|
302
|
+
# Function entry point (offset +2)
|
|
303
|
+
func_addr = self._read_ptr(method_entry_offset + 2)
|
|
304
|
+
if func_addr is None:
|
|
305
|
+
return None
|
|
306
|
+
func_offset = self._addr_to_offset(func_addr)
|
|
307
|
+
|
|
308
|
+
# Function name (Pascal string at offset +ptr_size+2)
|
|
309
|
+
func_name_offset = method_entry_offset + self._settings.ptr_size + 2
|
|
310
|
+
func_name = self._read_pascal_string(func_name_offset)
|
|
311
|
+
if not func_name:
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
method_info = MethodInfo(function_offset=func_offset, function_name=func_name)
|
|
315
|
+
|
|
316
|
+
# Return type (at offset +function_name_len+ptr_size+4)
|
|
317
|
+
return_type_field_offset = method_entry_offset + len(func_name) + 1 + self._settings.ptr_size + 4
|
|
318
|
+
return_type_name = self._resolve_type_from_double_ptr(return_type_field_offset)
|
|
319
|
+
if return_type_name:
|
|
320
|
+
method_info.return_type = return_type_name
|
|
321
|
+
|
|
322
|
+
# Parameter count (at offset +function_name_len+2*ptr_size+6)
|
|
323
|
+
param_count_offset = method_entry_offset + len(func_name) + 1 + 2 * self._settings.ptr_size + 6
|
|
324
|
+
param_count = self._read_byte(param_count_offset)
|
|
325
|
+
|
|
326
|
+
if param_count is not None and param_count > 0:
|
|
327
|
+
# Parse parameters
|
|
328
|
+
param_offset = param_count_offset + 2
|
|
329
|
+
for _ in range(param_count):
|
|
330
|
+
# Parameter RTTI (double-dereferenced pointer)
|
|
331
|
+
param_type_name = self._resolve_type_from_double_ptr(param_offset)
|
|
332
|
+
|
|
333
|
+
# Parameter name (Pascal string at offset +ptr_size+2)
|
|
334
|
+
param_name_offset = param_offset + self._settings.ptr_size + 2
|
|
335
|
+
param_name = self._read_pascal_string(param_name_offset)
|
|
336
|
+
|
|
337
|
+
method_info.parameters.append(ParameterInfo(type_name=param_type_name, parameter_name=param_name))
|
|
338
|
+
|
|
339
|
+
# Move to next parameter entry
|
|
340
|
+
param_offset = param_name_offset + len(param_name) + 1 + PARAM_ENTRY_PADDING
|
|
341
|
+
|
|
342
|
+
return method_info
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
LOGGER.debug(f"Error extracting method info at 0x{method_entry_offset:x}: {e}")
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
def _parse_mdt(self, mdt_offset: int) -> list:
|
|
349
|
+
"""Parse a Method Definition Table to extract method information."""
|
|
350
|
+
methods = []
|
|
351
|
+
|
|
352
|
+
# Read number of method entries (short at offset +2)
|
|
353
|
+
num_entries = self._read_short(mdt_offset + 2)
|
|
354
|
+
if num_entries is None or num_entries == 0:
|
|
355
|
+
return methods
|
|
356
|
+
|
|
357
|
+
# Method entry references start at offset +4
|
|
358
|
+
me_refs_start = mdt_offset + 4
|
|
359
|
+
|
|
360
|
+
for i in range(num_entries):
|
|
361
|
+
me_ref_offset = me_refs_start + i * (self._settings.ptr_size + 4)
|
|
362
|
+
me_addr = self._read_ptr(me_ref_offset)
|
|
363
|
+
|
|
364
|
+
if me_addr is None:
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
me_offset = self._addr_to_offset(me_addr)
|
|
368
|
+
method_info = self._extract_method_info(me_offset)
|
|
369
|
+
|
|
370
|
+
if method_info:
|
|
371
|
+
methods.append(method_info)
|
|
372
|
+
|
|
373
|
+
return methods
|
|
374
|
+
|
|
375
|
+
def _parse_delphi_symbols(self):
|
|
376
|
+
"""Main parsing routine to extract Delphi symbols."""
|
|
377
|
+
# Step 1: Find all VMT structures
|
|
378
|
+
vmt_offsets = self._find_vmts()
|
|
379
|
+
LOGGER.debug(f"Found {len(vmt_offsets)} VMT candidates")
|
|
380
|
+
|
|
381
|
+
if not vmt_offsets:
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
# Step 2: For each VMT, extract MDT and parse methods
|
|
385
|
+
for vmt_offset in vmt_offsets:
|
|
386
|
+
# Get MDT address from VMT
|
|
387
|
+
mdt_field_offset = vmt_offset + self._settings.mdt_offset
|
|
388
|
+
mdt_addr = self._read_ptr(mdt_field_offset)
|
|
389
|
+
|
|
390
|
+
if mdt_addr is None:
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
mdt_offset = self._addr_to_offset(mdt_addr)
|
|
394
|
+
|
|
395
|
+
# Get RTTI namespace for this VMT
|
|
396
|
+
rtti_field_offset = vmt_offset + self._settings.rtti_offset
|
|
397
|
+
rtti_addr = self._read_ptr(rtti_field_offset)
|
|
398
|
+
namespace = None
|
|
399
|
+
if rtti_addr is not None:
|
|
400
|
+
rtti_offset = self._addr_to_offset(rtti_addr)
|
|
401
|
+
namespace = self._traverse_rtti_object(rtti_offset)
|
|
402
|
+
|
|
403
|
+
# Parse all methods in this MDT
|
|
404
|
+
methods = self._parse_mdt(mdt_offset)
|
|
405
|
+
|
|
406
|
+
# Store function symbols
|
|
407
|
+
for method in methods:
|
|
408
|
+
func_addr = self._offset_to_addr(method.function_offset)
|
|
409
|
+
|
|
410
|
+
# Build function name with namespace if available
|
|
411
|
+
full_name = f"{namespace}.{method.function_name}" if namespace else method.function_name
|
|
412
|
+
|
|
413
|
+
# Add return type and parameters to make it more informative
|
|
414
|
+
if method.return_type != "void" or method.parameters:
|
|
415
|
+
param_str = ", ".join([f"{p.parameter_name}: {p.type_name or '?'}" for p in method.parameters])
|
|
416
|
+
full_name = f"{full_name}({param_str}): {method.return_type}"
|
|
417
|
+
|
|
418
|
+
self._func_symbols[func_addr] = full_name
|
|
419
|
+
|
|
420
|
+
def isSymbolProvider(self):
|
|
421
|
+
return True
|
|
422
|
+
|
|
423
|
+
def isApiProvider(self):
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
def getApi(self, absolute_addr):
|
|
427
|
+
return None
|
|
428
|
+
|
|
429
|
+
def getSymbol(self, address):
|
|
430
|
+
# sanitize output because the extractor may produce non-printable characters
|
|
431
|
+
symbol = self._func_symbols.get(address, "")
|
|
432
|
+
if not all(c.isprintable() or c.isspace() for c in symbol):
|
|
433
|
+
symbol = ""
|
|
434
|
+
return symbol
|
|
435
|
+
|
|
436
|
+
def getFunctionSymbols(self):
|
|
437
|
+
return self._func_symbols
|
|
438
|
+
|
|
439
|
+
def getRelocations(self):
|
|
440
|
+
return {}
|
|
@@ -562,10 +562,17 @@ class FunctionCandidateManager:
|
|
|
562
562
|
elif self.lang_analyzer.checkDelphi():
|
|
563
563
|
LOGGER.debug("Programming language recognized as Delphi, adding function start addresses from VMTs")
|
|
564
564
|
delphi_objects = self.lang_analyzer.getDelphiObjects()
|
|
565
|
-
LOGGER.debug("delphi candidates based on VMT analysis: %d", len(delphi_objects))
|
|
565
|
+
LOGGER.debug("delphi candidates based on legacy VMT analysis: %d", len(delphi_objects))
|
|
566
566
|
for obj in delphi_objects:
|
|
567
567
|
self.addLanguageSpecCandidate(obj, "delphi")
|
|
568
568
|
|
|
569
|
+
# Also extract symbols using DelphiReSym metadata parsing
|
|
570
|
+
LOGGER.debug("Extracting Delphi symbols using DelphiReSym metadata parsing")
|
|
571
|
+
delphi_resym_objects = self.lang_analyzer.getDelphiReSymObjects()
|
|
572
|
+
LOGGER.debug("delphi candidates based on DelphiReSym analysis: %d", len(delphi_resym_objects))
|
|
573
|
+
for obj in delphi_resym_objects:
|
|
574
|
+
self.addLanguageSpecCandidate(obj, "delphi_resym")
|
|
575
|
+
|
|
569
576
|
def locateStubChainCandidates(self):
|
|
570
577
|
# binaries often contain long sequences of stubs, consisting only of jmp dword ptr <offset>, add such chains as candidates
|
|
571
578
|
for block in re.finditer(b"(?P<block>(\xff\x25[\\S\\s]{4}){2,})", self.disassembly.binary_info.binary):
|
|
@@ -8,6 +8,7 @@ from capstone import CS_ARCH_X86, CS_MODE_32, CS_MODE_64, Cs
|
|
|
8
8
|
|
|
9
9
|
from smda.common.BinaryInfo import BinaryInfo
|
|
10
10
|
from smda.common.labelprovider.DelphiKbSymbolProvider import DelphiKbSymbolProvider
|
|
11
|
+
from smda.common.labelprovider.DelphiReSymProvider import DelphiReSymProvider
|
|
11
12
|
from smda.common.labelprovider.ElfApiResolver import ElfApiResolver
|
|
12
13
|
from smda.common.labelprovider.ElfSymbolProvider import ElfSymbolProvider
|
|
13
14
|
from smda.common.labelprovider.GoLabelProvider import GoSymbolProvider
|
|
@@ -92,6 +93,7 @@ class IntelDisassembler:
|
|
|
92
93
|
self.label_providers.append(PdbSymbolProvider(self.config))
|
|
93
94
|
self.label_providers.append(GoSymbolProvider(self.config))
|
|
94
95
|
self.label_providers.append(DelphiKbSymbolProvider(self.config))
|
|
96
|
+
self.label_providers.append(DelphiReSymProvider(self.config))
|
|
95
97
|
|
|
96
98
|
def _updateLabelProviders(self, binary_info):
|
|
97
99
|
for provider in self.label_providers:
|
|
@@ -5,6 +5,7 @@ import struct
|
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
|
|
7
7
|
from smda.common.labelprovider.DelphiKbSymbolProvider import DelphiKbSymbolProvider
|
|
8
|
+
from smda.common.labelprovider.DelphiReSymProvider import DelphiReSymProvider
|
|
8
9
|
from smda.common.labelprovider.GoLabelProvider import GoSymbolProvider
|
|
9
10
|
|
|
10
11
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -15,6 +16,7 @@ class LanguageAnalyzer:
|
|
|
15
16
|
self.disassembly = disassembly
|
|
16
17
|
self.go_resolver = GoSymbolProvider(None)
|
|
17
18
|
self.delphi_kb_resolver = DelphiKbSymbolProvider(None)
|
|
19
|
+
self.delphi_resym_resolver = DelphiReSymProvider(None)
|
|
18
20
|
self.strings = None
|
|
19
21
|
|
|
20
22
|
def validPEHeader(self):
|
|
@@ -89,12 +91,13 @@ class LanguageAnalyzer:
|
|
|
89
91
|
match.group("string")
|
|
90
92
|
# Regex: <DWORD_LEN_STRING><STRING><TERMINATOR>
|
|
91
93
|
for match in re.finditer(
|
|
92
|
-
b"(?P<length
|
|
94
|
+
b"\x00\x00.(?P<length>.)(?P<string>[ -~]{6,128})\x00",
|
|
93
95
|
self.disassembly.binary_info.binary,
|
|
94
96
|
)
|
|
95
|
-
if len(match.group("string")) ==
|
|
97
|
+
if len(match.group("string")) == ord(match.group("length"))
|
|
96
98
|
]
|
|
97
99
|
if len(delphi_strings) > 100:
|
|
100
|
+
LOGGER.info("Detected %d Delphi-like strings.", len(delphi_strings))
|
|
98
101
|
delphi_score = max(delphi_score, 0.8)
|
|
99
102
|
return delphi_score
|
|
100
103
|
|
|
@@ -223,6 +226,11 @@ class LanguageAnalyzer:
|
|
|
223
226
|
self.delphi_kb_resolver.update(self.disassembly.binary_info)
|
|
224
227
|
return self.delphi_kb_resolver.getFunctionSymbols()
|
|
225
228
|
|
|
229
|
+
def getDelphiReSymObjects(self):
|
|
230
|
+
"""Extract Delphi symbols using DelphiReSym metadata parsing."""
|
|
231
|
+
self.delphi_resym_resolver.update(self.disassembly.binary_info)
|
|
232
|
+
return self.delphi_resym_resolver.getFunctionSymbols()
|
|
233
|
+
|
|
226
234
|
def identify(self):
|
|
227
235
|
result = {
|
|
228
236
|
# programming language : probability
|
|
@@ -160,17 +160,15 @@ class PeFileLoader:
|
|
|
160
160
|
|
|
161
161
|
@staticmethod
|
|
162
162
|
def mergeCodeAreas(code_areas):
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
if not code_areas:
|
|
164
|
+
return []
|
|
165
|
+
sorted_areas = sorted(code_areas)
|
|
166
|
+
result = [sorted_areas[0]]
|
|
167
|
+
for i in range(1, len(sorted_areas)):
|
|
168
|
+
last_area = result[-1]
|
|
169
|
+
current_area = sorted_areas[i]
|
|
170
|
+
if last_area[1] == current_area[0]:
|
|
171
|
+
result[-1] = [last_area[0], current_area[1]]
|
|
172
172
|
else:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
return merged_code_areas
|
|
173
|
+
result.append(current_area)
|
|
174
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: smda
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.
|
|
5
5
|
Home-page: https://github.com/danielplohmann/smda
|
|
6
6
|
Author: Daniel Plohmann
|
|
@@ -15,6 +15,20 @@ Classifier: Topic :: Security
|
|
|
15
15
|
Classifier: Topic :: Software Development :: Disassemblers
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
+
Requires-Dist: capstone
|
|
19
|
+
Requires-Dist: dncil
|
|
20
|
+
Requires-Dist: dnfile
|
|
21
|
+
Requires-Dist: lief>=0.16.0
|
|
22
|
+
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
24
|
+
Dynamic: classifier
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: description-content-type
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: license
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
Dynamic: requires-dist
|
|
31
|
+
Dynamic: summary
|
|
18
32
|
|
|
19
33
|
|
|
20
34
|
# SMDA
|
|
@@ -119,6 +133,9 @@ make test
|
|
|
119
133
|
```
|
|
120
134
|
|
|
121
135
|
## Version History
|
|
136
|
+
* 2025-11-28: v2.4.2 - Fix for a bug when extracting and merging code areas from section tables. (THX: @r0ny123)
|
|
137
|
+
* 2025-11-28: v2.4.1 - Modernized packaging by also building a wheel. (THX: @dimbleby)
|
|
138
|
+
* 2025-11-21: v2.4.0 - Integration of DelphiReSym by @WenzWenzWenz for Delphi VMT parsing, thanks to @r0ny123 for adapting it!!
|
|
122
139
|
* 2025-10-21: v2.3.1 - Fixed lief error for section/segment flags in ELF files crashing file loading. Now properly parsing and providing symbol info for PEs in their own xmetadata section.
|
|
123
140
|
* 2025-10-21: v2.3.0 - Major code refactor and cleanup, with many thanks to the contribution @r0ny123!!
|
|
124
141
|
* 2025-07-25: v2.2.3 - Minor bugfixes.
|
|
@@ -2,11 +2,6 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
setup.py
|
|
5
|
-
./smda/Disassembler.py
|
|
6
|
-
./smda/DisassemblyResult.py
|
|
7
|
-
./smda/DisassemblyStatistics.py
|
|
8
|
-
./smda/SmdaConfig.py
|
|
9
|
-
./smda/__init__.py
|
|
10
5
|
smda/Disassembler.py
|
|
11
6
|
smda/DisassemblyResult.py
|
|
12
7
|
smda/DisassemblyStatistics.py
|
|
@@ -36,6 +31,7 @@ smda/common/__init__.py
|
|
|
36
31
|
smda/common/labelprovider/AbstractLabelProvider.py
|
|
37
32
|
smda/common/labelprovider/CilSymbolProvider.py
|
|
38
33
|
smda/common/labelprovider/DelphiKbSymbolProvider.py
|
|
34
|
+
smda/common/labelprovider/DelphiReSymProvider.py
|
|
39
35
|
smda/common/labelprovider/ElfApiResolver.py
|
|
40
36
|
smda/common/labelprovider/ElfSymbolProvider.py
|
|
41
37
|
smda/common/labelprovider/GoLabelProvider.py
|
|
@@ -74,4 +70,5 @@ tests/testBracketQueue.py
|
|
|
74
70
|
tests/testEscaper.py
|
|
75
71
|
tests/testFileFormatParsers.py
|
|
76
72
|
tests/testIntegration.py
|
|
73
|
+
tests/testPeFileLoader.py
|
|
77
74
|
tests/testTarjan.py
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from smda.utility.PeFileLoader import PeFileLoader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PeFileLoaderTestSuite(unittest.TestCase):
|
|
7
|
+
def test_mergeCodeAreas(self):
|
|
8
|
+
test_cases = [
|
|
9
|
+
("Overlapping intervals", [[1, 5], [3, 7], [8, 12]], [[1, 5], [3, 7], [8, 12]]),
|
|
10
|
+
("Contiguous intervals", [[1, 5], [5, 10], [10, 15]], [[1, 15]]),
|
|
11
|
+
("Unsorted contiguous intervals", [[10, 15], [1, 5], [5, 10]], [[1, 15]]),
|
|
12
|
+
("Separated intervals", [[1, 5], [6, 10], [11, 15]], [[1, 5], [6, 10], [11, 15]]),
|
|
13
|
+
("Empty list", [], []),
|
|
14
|
+
("Single interval", [[1, 5]], [[1, 5]]),
|
|
15
|
+
("Mixed intervals", [[1, 5], [5, 10], [11, 15], [15, 20]], [[1, 10], [11, 20]]),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
for name, intervals, expected in test_cases:
|
|
19
|
+
with self.subTest(msg=name):
|
|
20
|
+
self.assertEqual(PeFileLoader.mergeCodeAreas(intervals), expected)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|