smda 4.2.2__tar.gz → 4.2.3__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-4.2.2/src/smda.egg-info → smda-4.2.3}/PKG-INFO +2 -1
- {smda-4.2.2 → smda-4.2.3}/README.md +1 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/SmdaConfig.py +1 -1
- smda-4.2.3/src/smda/__init__.py +1 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/PeSymbolProvider.py +13 -11
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/RustSymbolProvider.py +12 -12
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/FunctionCandidateManager.py +18 -4
- {smda-4.2.2 → smda-4.2.3/src/smda.egg-info}/PKG-INFO +2 -1
- {smda-4.2.2 → smda-4.2.3}/tests/testPdataExtraction.py +28 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testPeSymbolProvider.py +40 -6
- {smda-4.2.2 → smda-4.2.3}/tests/testRustSymbolProvider.py +34 -3
- smda-4.2.2/src/smda/__init__.py +0 -1
- {smda-4.2.2 → smda-4.2.3}/LICENSE +0 -0
- {smda-4.2.2 → smda-4.2.3}/pyproject.toml +0 -0
- {smda-4.2.2 → smda-4.2.3}/setup.cfg +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/Disassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/DisassemblyResult.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/DisassemblyStatistics.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64Backend.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64CapstoneVerification.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64Disassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64InstructionEscaper.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/FunctionAnalysisState.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/FunctionCandidateManager.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/analyzers.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/definitions.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/cil/CilDisassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/cil/CilInstructionEscaper.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/cil/FunctionAnalysisState.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/cil/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/BasicBlock.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/BinaryInfo.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/BlockLocator.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/CodeXref.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/DominatorTree.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/ExceptionHandling.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/RecursiveDisassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaBasicBlock.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaFunction.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaInstruction.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaReport.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/TailcallAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/Tarjan.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/arch/ArchBackend.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/arch/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/AbstractLabelProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/CilSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/DelphiKbSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/DelphiPythiaProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/DelphiReSymProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/ElfApiResolver.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/ElfSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/GoLabelProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/MachoDemangler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/MachoSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/OrdinalHelper.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/PdbSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/WinApiResolver.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/import_parsers.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/main.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/rust.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/rust_legacy.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/rust_v0.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/utils.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/DalvikDisassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/DalvikFunctionAnalysisState.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/DalvikOpcodeDecoder.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/ida/BackendInterface.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/ida/IdaExporter.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/ida/IdaInterface.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/ida/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/BitnessAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/FunctionAnalysisState.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/FunctionCandidate.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/IndirectCallAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/IntelDisassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/IntelInstructionEscaper.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/JumpTableAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/LanguageAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/MnemonicTfIdf.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/X86Backend.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/intel/definitions.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/BracketQueue.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/DelphiKbFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/DexFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/ElfFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/FileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/MachoBinary.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/MachoFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/MemoryFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/PeFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/PriorityQueue.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/StringExtractor.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/__init__.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda/utility/common.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/SOURCES.txt +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/dependency_links.txt +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/requires.txt +0 -0
- {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/top_level.txt +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testAArch64Disassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testAArch64EscaperCapstoneVerification.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testAArch64EscaperCoverage.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testAArch64EscaperImproved.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testAArch64MachoCorpus.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testBenchmarkEval.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testBitnessAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testBracketQueue.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testCandidateSafeguards.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testCommonModels.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testDalvikDisassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testDefinitionsExpansion.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testDelphiPythiaProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testElfSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testEscaper.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testExceptionHandling.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testFileFormatParsers.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testIndirectCallAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testIntegration.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testIntelDisassembler.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testLanguageAnalyzer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testLateCandidateProduction.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testMachoSymbolProvider.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testPeFileLoader.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testSmdaInstructionDetail.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testSmdaInstructionDetailAArch64.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testSmdaReportBuffer.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testTarjan.py +0 -0
- {smda-4.2.2 → smda-4.2.3}/tests/testXmetadataNormalization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smda
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.3
|
|
4
4
|
Summary: A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.
|
|
5
5
|
Author-email: Daniel Plohmann <daniel.plohmann@mailbox.org>
|
|
6
6
|
License: BSD 2-Clause
|
|
@@ -146,6 +146,7 @@ make test
|
|
|
146
146
|
|
|
147
147
|
## Version History
|
|
148
148
|
|
|
149
|
+
* 2026-07-10: v4.2.3 - Fix: function promotion bug caused by missing symbol type evaluation (THX: @r0ny123).
|
|
149
150
|
* 2026-07-09: v4.2.2 - Now also parsing delay import tables from Windows PEs.
|
|
150
151
|
* 2026-07-09: v4.2.1 - Better detection of CFG instructions with prefixes, improved accuracy of gap search. (THX: @r0ny123) IDA ARM64 export.
|
|
151
152
|
* 2026-06-24: v4.2.0 - Further improvements for inter-procedural operand escaping (closer to x86_x64 and traditional PIC hashing).
|
|
@@ -105,6 +105,7 @@ make test
|
|
|
105
105
|
|
|
106
106
|
## Version History
|
|
107
107
|
|
|
108
|
+
* 2026-07-10: v4.2.3 - Fix: function promotion bug caused by missing symbol type evaluation (THX: @r0ny123).
|
|
108
109
|
* 2026-07-09: v4.2.2 - Now also parsing delay import tables from Windows PEs.
|
|
109
110
|
* 2026-07-09: v4.2.1 - Better detection of CFG instructions with prefixes, improved accuracy of gap search. (THX: @r0ny123) IDA ARM64 export.
|
|
110
111
|
* 2026-06-24: v4.2.0 - Further improvements for inter-procedural operand escaping (closer to x86_x64 and traditional PIC hashing).
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
|
|
5
5
|
class SmdaConfig:
|
|
6
6
|
# keep this in sync with smda.__version__
|
|
7
|
-
VERSION = "4.2.
|
|
7
|
+
VERSION = "4.2.3"
|
|
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, "..", "..", ".."])))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "4.2.3"
|
|
@@ -53,7 +53,14 @@ class PeSymbolProvider(AbstractLabelProvider):
|
|
|
53
53
|
def parseExports(self, lief_binary, base_addr=None):
|
|
54
54
|
active_base = self._resolve_base_addr(lief_binary, base_addr)
|
|
55
55
|
function_symbols = {}
|
|
56
|
-
|
|
56
|
+
export = lief_binary.get_export()
|
|
57
|
+
if export is None:
|
|
58
|
+
return function_symbols
|
|
59
|
+
for function in export.entries:
|
|
60
|
+
if function.is_extern or function.is_forwarded:
|
|
61
|
+
# forwarder/extern entries redirect to another module's export and have no
|
|
62
|
+
# local address (LIEF sets .address to 0 for them) - not a local function.
|
|
63
|
+
continue
|
|
57
64
|
function_name = ""
|
|
58
65
|
with contextlib.suppress(UnicodeDecodeError, AttributeError):
|
|
59
66
|
# here may occur a LIEF exception that we want to skip ->
|
|
@@ -65,25 +72,20 @@ class PeSymbolProvider(AbstractLabelProvider):
|
|
|
65
72
|
|
|
66
73
|
def parseSymbols(self, lief_binary, base_addr=None):
|
|
67
74
|
active_base = self._resolve_base_addr(lief_binary, base_addr)
|
|
68
|
-
# find VA of first code section
|
|
69
75
|
function_symbols = {}
|
|
70
|
-
code_base_address = None
|
|
71
|
-
for section in lief_binary.sections:
|
|
72
|
-
if section.characteristics & 0x20000000:
|
|
73
|
-
code_base_address = active_base + section.virtual_address
|
|
74
|
-
break
|
|
75
|
-
if code_base_address is None:
|
|
76
|
-
return function_symbols
|
|
77
76
|
for symbol in lief_binary.symbols:
|
|
78
77
|
if hasattr(symbol.complex_type, "name") and symbol.complex_type.name == "FUNCTION":
|
|
78
|
+
if symbol.section is None:
|
|
79
|
+
# section_idx 0/-1/-2 (undefined-external/absolute/debug): not a locally
|
|
80
|
+
# defined function, its value is not a usable in-image offset.
|
|
81
|
+
continue
|
|
79
82
|
function_name = ""
|
|
80
83
|
with contextlib.suppress(UnicodeDecodeError, AttributeError):
|
|
81
84
|
# here may occur a LIEF exception that we want to skip ->
|
|
82
85
|
# UnicodeDecodeError: 'utf-32-le' codec can't decode bytes in position 0-3: code point not in range(0x110000)
|
|
83
86
|
function_name = symbol.name
|
|
84
87
|
if function_name and all(ord(c) in range(0x20, 0x7F) for c in function_name):
|
|
85
|
-
|
|
86
|
-
function_offset = code_base_address + symbol.value
|
|
88
|
+
function_offset = active_base + symbol.section.virtual_address + symbol.value
|
|
87
89
|
if function_offset not in function_symbols:
|
|
88
90
|
function_symbols[function_offset] = function_name
|
|
89
91
|
return function_symbols
|
|
@@ -143,7 +143,12 @@ class RustSymbolProvider(AbstractLabelProvider):
|
|
|
143
143
|
"""Process PE binary symbols for Rust demangling."""
|
|
144
144
|
active_base = resolve_pe_base_addr(lief_binary, base_addr)
|
|
145
145
|
# Parse PE exports
|
|
146
|
-
|
|
146
|
+
export = lief_binary.get_export()
|
|
147
|
+
for function in export.entries if export is not None else []:
|
|
148
|
+
if function.is_extern or function.is_forwarded:
|
|
149
|
+
# forwarder/extern entries redirect to another module's export and have no
|
|
150
|
+
# local address (LIEF sets .address to 0 for them) - not a local function.
|
|
151
|
+
continue
|
|
147
152
|
try:
|
|
148
153
|
try:
|
|
149
154
|
raw_name = function.name
|
|
@@ -158,21 +163,16 @@ class RustSymbolProvider(AbstractLabelProvider):
|
|
|
158
163
|
except _DEMANGLE_ERRORS as exc:
|
|
159
164
|
LOGGER.debug("Failed to demangle Rust symbol %s: %s", function.name, exc)
|
|
160
165
|
|
|
161
|
-
code_base_address = None
|
|
162
|
-
for section in lief_binary.sections:
|
|
163
|
-
if section.characteristics & 0x20000000:
|
|
164
|
-
code_base_address = active_base + section.virtual_address
|
|
165
|
-
break
|
|
166
|
-
if code_base_address is None:
|
|
167
|
-
return
|
|
168
166
|
# working example: 3969e1a88a063155a6f61b0ca1ac33114c1a39151f3c7dd019084abd30553eab
|
|
169
167
|
# Parse PE symbols (COFF) if available and LIEF extracted them
|
|
170
168
|
# (Similar logic to PeSymbolProvider but focusing on Rust)
|
|
171
169
|
for symbol in lief_binary.symbols:
|
|
172
170
|
# Check if it is a function symbol and has a section
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
if hasattr(symbol.complex_type, "name") and symbol.complex_type.name == "FUNCTION":
|
|
172
|
+
if symbol.section is None:
|
|
173
|
+
# section_idx 0/-1/-2 (undefined-external/absolute/debug): not a locally
|
|
174
|
+
# defined function, its value is not a usable in-image offset.
|
|
175
|
+
continue
|
|
176
176
|
try:
|
|
177
177
|
try:
|
|
178
178
|
raw_name = symbol.name
|
|
@@ -182,7 +182,7 @@ class RustSymbolProvider(AbstractLabelProvider):
|
|
|
182
182
|
demangled = demangle(raw_name)
|
|
183
183
|
if demangled:
|
|
184
184
|
demangled = remove_bad_spaces(demangled)
|
|
185
|
-
function_offset =
|
|
185
|
+
function_offset = active_base + symbol.section.virtual_address + symbol.value
|
|
186
186
|
if function_offset not in self._func_symbols:
|
|
187
187
|
self._func_symbols[function_offset] = demangled
|
|
188
188
|
except _DEMANGLE_ERRORS as exc:
|
|
@@ -744,12 +744,26 @@ class FunctionCandidateManager:
|
|
|
744
744
|
if section_name == ".pdata":
|
|
745
745
|
rva_start = section_va_start - self.disassembly.binary_info.base_addr
|
|
746
746
|
rva_end = section_va_end - self.disassembly.binary_info.base_addr
|
|
747
|
-
# .pdata entries are 12 bytes long (3 DWORDs)
|
|
747
|
+
# .pdata entries are 12 bytes long (3 DWORDs): BeginAddress, EndAddress, UnwindInfoAddress
|
|
748
748
|
for offset in range(rva_start, rva_end - 11, 12):
|
|
749
|
-
|
|
750
|
-
if len(
|
|
749
|
+
packed_entry = self.disassembly.getRawBytes(offset, 12)
|
|
750
|
+
if len(packed_entry) < 12:
|
|
751
751
|
break
|
|
752
|
-
rva_function_candidate = struct.unpack("
|
|
752
|
+
rva_function_candidate, _rva_function_end, rva_unwind_info = struct.unpack("<III", packed_entry)
|
|
753
753
|
if rva_function_candidate == 0:
|
|
754
754
|
break
|
|
755
|
+
if self._isChainedUnwindInfo(rva_unwind_info):
|
|
756
|
+
# UNW_FLAG_CHAININFO: this entry is a secondary fragment (e.g. a
|
|
757
|
+
# split-off cold/epilogue chunk) chained to another function's
|
|
758
|
+
# primary RUNTIME_FUNCTION entry, not an independent function start.
|
|
759
|
+
continue
|
|
755
760
|
self.addExceptionCandidate(self.disassembly.binary_info.base_addr + rva_function_candidate)
|
|
761
|
+
|
|
762
|
+
def _isChainedUnwindInfo(self, rva_unwind_info):
|
|
763
|
+
# x64 UNWIND_INFO header byte 0 packs Version (bits 0-2) and Flags (bits 3-7);
|
|
764
|
+
# UNW_FLAG_CHAININFO (0x4) means this RUNTIME_FUNCTION entry chains to another
|
|
765
|
+
# function's primary entry instead of describing an independent function.
|
|
766
|
+
header_byte = self.disassembly.getRawBytes(rva_unwind_info, 1)
|
|
767
|
+
if not header_byte:
|
|
768
|
+
return False
|
|
769
|
+
return bool((header_byte[0] >> 3) & 0x4)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smda
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.3
|
|
4
4
|
Summary: A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.
|
|
5
5
|
Author-email: Daniel Plohmann <daniel.plohmann@mailbox.org>
|
|
6
6
|
License: BSD 2-Clause
|
|
@@ -146,6 +146,7 @@ make test
|
|
|
146
146
|
|
|
147
147
|
## Version History
|
|
148
148
|
|
|
149
|
+
* 2026-07-10: v4.2.3 - Fix: function promotion bug caused by missing symbol type evaluation (THX: @r0ny123).
|
|
149
150
|
* 2026-07-09: v4.2.2 - Now also parsing delay import tables from Windows PEs.
|
|
150
151
|
* 2026-07-09: v4.2.1 - Better detection of CFG instructions with prefixes, improved accuracy of gap search. (THX: @r0ny123) IDA ARM64 export.
|
|
151
152
|
* 2026-06-24: v4.2.0 - Further improvements for inter-procedural operand escaping (closer to x86_x64 and traditional PIC hashing).
|
|
@@ -81,6 +81,34 @@ class PdataExtractionTestSuite(unittest.TestCase):
|
|
|
81
81
|
self.assertNotIn(BASE_ADDR, candidates)
|
|
82
82
|
self.assertEqual(len(candidates), 1)
|
|
83
83
|
|
|
84
|
+
def test_pdata_chained_unwind_info_excluded(self):
|
|
85
|
+
config = SmdaConfig()
|
|
86
|
+
fcm = FunctionCandidateManager(config)
|
|
87
|
+
|
|
88
|
+
# Entry format: Start RVA, End RVA, Unwind RVA
|
|
89
|
+
entries = [
|
|
90
|
+
(0x2000, 0x2040, 0x3000), # primary entry, unwind info at 0x3000 (Flags=0)
|
|
91
|
+
(0x2050, 0x2090, 0x3010), # chained entry, unwind info at 0x3010 (UNW_FLAG_CHAININFO set)
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
pdata_bytes = b"".join(struct.pack("III", *entry) for entry in entries)
|
|
95
|
+
|
|
96
|
+
binary = bytearray(BINARY_SIZE)
|
|
97
|
+
binary[PDATA_OFFSET : PDATA_OFFSET + len(pdata_bytes)] = pdata_bytes
|
|
98
|
+
# UNWIND_INFO header byte 0 packs Version (bits 0-2) and Flags (bits 3-7); 0x20 sets
|
|
99
|
+
# UNW_FLAG_CHAININFO (0x4) with Version 0.
|
|
100
|
+
binary[0x3010] = 0x20
|
|
101
|
+
|
|
102
|
+
disasm = DisassemblyResult()
|
|
103
|
+
disasm.binary_info = MockBinaryInfo(64, BASE_ADDR, bytes(binary), pdata_size=len(pdata_bytes))
|
|
104
|
+
|
|
105
|
+
fcm.init(disasm)
|
|
106
|
+
|
|
107
|
+
candidates = fcm.candidates
|
|
108
|
+
self.assertIn(BASE_ADDR + 0x2000, candidates)
|
|
109
|
+
self.assertNotIn(BASE_ADDR + 0x2050, candidates)
|
|
110
|
+
self.assertEqual(len(candidates), 1)
|
|
111
|
+
|
|
84
112
|
def test_pdata_misaligned_size(self):
|
|
85
113
|
config = SmdaConfig()
|
|
86
114
|
fcm = FunctionCandidateManager(config)
|
|
@@ -36,9 +36,11 @@ class _MockDelayImport:
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class _MockExport:
|
|
39
|
-
def __init__(self, name, address):
|
|
39
|
+
def __init__(self, name, address, *, is_extern=False, is_forwarded=False):
|
|
40
40
|
self.name = name
|
|
41
41
|
self.address = address
|
|
42
|
+
self.is_extern = is_extern
|
|
43
|
+
self.is_forwarded = is_forwarded
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
class _MockSection:
|
|
@@ -48,10 +50,11 @@ class _MockSection:
|
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
class _MockSymbol:
|
|
51
|
-
def __init__(self, name, value):
|
|
53
|
+
def __init__(self, name, value, *, section=None):
|
|
52
54
|
self.name = name
|
|
53
55
|
self.value = value
|
|
54
56
|
self.complex_type = SimpleNamespace(name="FUNCTION")
|
|
57
|
+
self.section = section
|
|
55
58
|
|
|
56
59
|
|
|
57
60
|
class _MockPeBinary:
|
|
@@ -76,6 +79,11 @@ class _MockPeBinary:
|
|
|
76
79
|
self.has_delay_imports = bool(self.delay_imports)
|
|
77
80
|
self.has_imports = bool(self.imports)
|
|
78
81
|
|
|
82
|
+
def get_export(self):
|
|
83
|
+
if not self.exported_functions:
|
|
84
|
+
return None
|
|
85
|
+
return SimpleNamespace(entries=self.exported_functions)
|
|
86
|
+
|
|
79
87
|
|
|
80
88
|
class TestPeSymbolProviderImports(unittest.TestCase):
|
|
81
89
|
def test_parse_imports_uses_active_base_addr_not_imagebase(self):
|
|
@@ -182,7 +190,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
|
|
|
182
190
|
pe_binary = _MockPeBinary(
|
|
183
191
|
exported_functions=[_MockExport("exported_func", 0x1000)],
|
|
184
192
|
sections=[_MockSection(0x20000000, 0x1000)],
|
|
185
|
-
symbols=[_MockSymbol("local_func", 0x200)],
|
|
193
|
+
symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
|
|
186
194
|
imagebase=0x140000000,
|
|
187
195
|
)
|
|
188
196
|
symbols = provider.collectSymbols(pe_binary, base_addr=0x400000)
|
|
@@ -194,7 +202,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
|
|
|
194
202
|
pe_binary = _MockPeBinary(
|
|
195
203
|
exported_functions=[_MockExport("exported_func", 0x1000)],
|
|
196
204
|
sections=[_MockSection(0x20000000, 0x1000)],
|
|
197
|
-
symbols=[_MockSymbol("local_func", 0x200)],
|
|
205
|
+
symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
|
|
198
206
|
imagebase=0x140000000,
|
|
199
207
|
)
|
|
200
208
|
symbols = provider.collectSymbols(pe_binary, base_addr=0)
|
|
@@ -227,7 +235,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
|
|
|
227
235
|
imports=[_MockImportLibrary("KERNEL32.dll", [_MockImportEntry("CreateFileW", 0x3000)])],
|
|
228
236
|
exported_functions=[_MockExport("exported_func", 0x1000)],
|
|
229
237
|
sections=[_MockSection(0x20000000, 0x1000)],
|
|
230
|
-
symbols=[_MockSymbol("local_func", 0x200)],
|
|
238
|
+
symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
|
|
231
239
|
imagebase=0x140000000,
|
|
232
240
|
)
|
|
233
241
|
binary_info = BinaryInfo(b"")
|
|
@@ -254,7 +262,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
|
|
|
254
262
|
pe_binary = _MockPeBinary(
|
|
255
263
|
exported_functions=[_MockExport("exported_func", 0x1000)],
|
|
256
264
|
sections=[_MockSection(0x20000000, 0x1000)],
|
|
257
|
-
symbols=[_MockSymbol("local_func", 0x200)],
|
|
265
|
+
symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
|
|
258
266
|
imagebase=0x140000000,
|
|
259
267
|
)
|
|
260
268
|
binary_info = BinaryInfo(b"")
|
|
@@ -267,6 +275,32 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
|
|
|
267
275
|
self.assertEqual(symbols[0x401000], "exported_func")
|
|
268
276
|
self.assertEqual(symbols[0x401200], "local_func")
|
|
269
277
|
|
|
278
|
+
def test_parse_exports_skips_forwarded_and_extern_entries(self):
|
|
279
|
+
provider = PeSymbolProvider(None)
|
|
280
|
+
pe_binary = _MockPeBinary(
|
|
281
|
+
exported_functions=[
|
|
282
|
+
_MockExport("real_func", 0x1000),
|
|
283
|
+
_MockExport("forwarded_func", 0, is_forwarded=True),
|
|
284
|
+
_MockExport("extern_func", 0, is_extern=True),
|
|
285
|
+
],
|
|
286
|
+
imagebase=0x140000000,
|
|
287
|
+
)
|
|
288
|
+
symbols = provider.parseExports(pe_binary, base_addr=0x400000)
|
|
289
|
+
self.assertEqual(symbols, {0x401000: "real_func"})
|
|
290
|
+
|
|
291
|
+
def test_parse_symbols_skips_entries_with_no_section(self):
|
|
292
|
+
provider = PeSymbolProvider(None)
|
|
293
|
+
pe_binary = _MockPeBinary(
|
|
294
|
+
sections=[_MockSection(0x20000000, 0x1000)],
|
|
295
|
+
symbols=[
|
|
296
|
+
_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000)),
|
|
297
|
+
_MockSymbol("undefined_func", 0, section=None),
|
|
298
|
+
],
|
|
299
|
+
imagebase=0x140000000,
|
|
300
|
+
)
|
|
301
|
+
symbols = provider.parseSymbols(pe_binary, base_addr=0x400000)
|
|
302
|
+
self.assertEqual(symbols, {0x401200: "local_func"})
|
|
303
|
+
|
|
270
304
|
|
|
271
305
|
if __name__ == "__main__":
|
|
272
306
|
unittest.main()
|
|
@@ -18,12 +18,13 @@ from smda.common.labelprovider.RustSymbolProvider import RustSymbolProvider
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class MockSymbol:
|
|
21
|
-
def __init__(self, name, value, is_function=True, demangled_name=None):
|
|
21
|
+
def __init__(self, name, value, is_function=True, demangled_name=None, section=None):
|
|
22
22
|
self.name = name
|
|
23
23
|
self.value = value
|
|
24
24
|
self.is_function = is_function
|
|
25
25
|
self._demangled_name = demangled_name
|
|
26
26
|
self.complex_type = type("obj", (object,), {"name": "FUNCTION"})
|
|
27
|
+
self.section = section
|
|
27
28
|
|
|
28
29
|
@property
|
|
29
30
|
def demangled_name(self):
|
|
@@ -44,6 +45,11 @@ class MockLiefBinary:
|
|
|
44
45
|
self.symbols = symbols
|
|
45
46
|
self.imports = []
|
|
46
47
|
|
|
48
|
+
def get_export(self):
|
|
49
|
+
if not self.exported_functions:
|
|
50
|
+
return None
|
|
51
|
+
return type("obj", (object,), {"entries": self.exported_functions})()
|
|
52
|
+
|
|
47
53
|
|
|
48
54
|
class MockSection:
|
|
49
55
|
def __init__(self, characteristics, virtual_address):
|
|
@@ -52,9 +58,11 @@ class MockSection:
|
|
|
52
58
|
|
|
53
59
|
|
|
54
60
|
class MockExport:
|
|
55
|
-
def __init__(self, name, address):
|
|
61
|
+
def __init__(self, name, address, is_extern=False, is_forwarded=False):
|
|
56
62
|
self.name = name
|
|
57
63
|
self.address = address
|
|
64
|
+
self.is_extern = is_extern
|
|
65
|
+
self.is_forwarded = is_forwarded
|
|
58
66
|
|
|
59
67
|
|
|
60
68
|
class TestRustDemangler(unittest.TestCase):
|
|
@@ -240,7 +248,7 @@ class TestRustSymbolProvider(unittest.TestCase):
|
|
|
240
248
|
def test_pe_rust_symbols_use_base_addr_not_imagebase(self):
|
|
241
249
|
provider = RustSymbolProvider(None)
|
|
242
250
|
mock_binary = MockLiefBinary(
|
|
243
|
-
[MockSymbol("_ZN3foo3barE", 0x200)],
|
|
251
|
+
[MockSymbol("_ZN3foo3barE", 0x200, section=MockSection(0x20000000, 0x1000))],
|
|
244
252
|
exported_functions=[MockExport("_RNvC6_123foo3bar", 0x1000)],
|
|
245
253
|
)
|
|
246
254
|
mock_binary.imagebase = 0x140000000
|
|
@@ -254,6 +262,29 @@ class TestRustSymbolProvider(unittest.TestCase):
|
|
|
254
262
|
self.assertNotIn(0x140001000, provider.getFunctionSymbols())
|
|
255
263
|
self.assertNotIn(0x140001200, provider.getFunctionSymbols())
|
|
256
264
|
|
|
265
|
+
def test_pe_rust_symbols_skip_forwarded_exports_and_sectionless_symbols(self):
|
|
266
|
+
provider = RustSymbolProvider(None)
|
|
267
|
+
mock_binary = MockLiefBinary(
|
|
268
|
+
[
|
|
269
|
+
MockSymbol("_ZN3foo3barE", 0x200, section=MockSection(0x20000000, 0x1000)),
|
|
270
|
+
MockSymbol("_ZN3foo3bazE", 0, section=None),
|
|
271
|
+
],
|
|
272
|
+
exported_functions=[
|
|
273
|
+
MockExport("_RNvC6_123foo3bar", 0x1000),
|
|
274
|
+
MockExport("_RNvC6_123foo3qux", 0, is_forwarded=True),
|
|
275
|
+
],
|
|
276
|
+
)
|
|
277
|
+
mock_binary.imagebase = 0x140000000
|
|
278
|
+
mock_binary.sections = [MockSection(0x20000000, 0x1000)]
|
|
279
|
+
|
|
280
|
+
with mock.patch("lief.PE.Binary", MockLiefBinary):
|
|
281
|
+
provider._update_pe(mock_binary, base_addr=0x400000)
|
|
282
|
+
|
|
283
|
+
self.assertEqual(provider.getSymbol(0x401000), "123foo::bar")
|
|
284
|
+
self.assertEqual(provider.getSymbol(0x401200), "foo::bar")
|
|
285
|
+
self.assertNotIn(0x400000, provider.getFunctionSymbols())
|
|
286
|
+
self.assertEqual(len(provider.getFunctionSymbols()), 2)
|
|
287
|
+
|
|
257
288
|
def test_detection_logic(self):
|
|
258
289
|
"""Test Rust binary detection based on signatures."""
|
|
259
290
|
provider = RustSymbolProvider(None)
|
smda-4.2.2/src/smda/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "4.2.2"
|
|
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
|
|
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
|