Skip to content

Commit c11aabe

Browse files
Merge branch 'N0fix-fix_i60'
2 parents 578dfc1 + 04b2d0c commit c11aabe

File tree

11 files changed

+128
-16
lines changed

11 files changed

+128
-16
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ To take full advantage of SMDA's capabilities, make sure to (optionally) install
5656
* pdbparse (currently as fork from https://github.com/VPaulV/pdbparse to support Python3)
5757

5858
## Version History
59+
* 2025-02-21: v1.14.1 - Fixed changed field names in LIEF usage that broke ELF parsing, added tests for ELF+macOS parsing (THX to @N0fix for the update!)
5960
* 2025-01-29: v1.14.0 - Bump to LIEF 0.16.0+ (THX to @huettenhain for the ping!). Migrated tests to `pytest`, UTC datetime handling fixes.
6061
* 2025-01-26: v1.13.24 - Added functionality to import and export SMDA reports as JSON. Fixed byte patterns matching special regex chars (THX to @alexander-hanel!).
6162
* 2024-07-26: v1.13.23 - Now using OEP as symbol function candidate when available (THX to @alexander-hanel for reporting!).

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
setup(
2020
name='smda',
2121
# note to self: always change this in config as well.
22-
version='1.14.0',
22+
version='1.14.1',
2323
description='A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.',
2424
long_description_content_type="text/markdown",
2525
long_description=long_description,

smda/SmdaConfig.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
class SmdaConfig(object):
66

77
# note to self: always change this in setup.py as well!
8-
VERSION = "1.14.0"
8+
VERSION = "1.14.1"
99
ESCAPER_DOWNWARD_COMPATIBILITY = "1.13.16"
1010
CONFIG_FILE_PATH = str(os.path.abspath(__file__))
1111
PROJECT_ROOT = str(os.path.abspath(os.sep.join([CONFIG_FILE_PATH, "..", ".."])))

smda/common/labelprovider/ElfSymbolProvider.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def update(self, binary_info):
4343
self._parseOep(lief_binary)
4444
# TODO split resolution into API/dynamic part and local symbols
4545
self._parseExports(lief_binary)
46-
self._parseSymbols(lief_binary.static_symbols)
46+
self._parseSymbols(lief_binary.symtab_symbols)
4747
self._parseSymbols(lief_binary.dynamic_symbols)
4848
for reloc in lief_binary.relocations:
4949
if reloc.has_symbol:

smda/utility/ElfFileLoader.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import sys
21
import logging
2+
import sys
33

44
LOGGER = logging.getLogger(__name__)
55

@@ -99,7 +99,7 @@ def mapBinary(binary):
9999
min_virtual_address = min(min_virtual_address, section.virtual_address)
100100
min_raw_offset = min(min_raw_offset, section.file_offset)
101101
else:
102-
LOGGER.warn(f"ELF: found possibly bogus section information, trying to parse segments.")
102+
LOGGER.warn("ELF: found possibly bogus section information, trying to parse segments.")
103103
# parse segments regardless
104104
for segment in elffile.segments:
105105
if not segment.virtual_address:
@@ -110,7 +110,7 @@ def mapBinary(binary):
110110
min_raw_offset = min(min_raw_offset, segment.file_offset)
111111

112112
if (max_virtual_address - base_addr) > sys.maxsize:
113-
LOGGER.warn(f"ELF: found possibly bogus segment information, trying to parse segments.")
113+
LOGGER.warn("ELF: found possibly bogus segment information, trying to parse segments.")
114114

115115
if not max_virtual_address:
116116
LOGGER.debug("ELF: no section or segment data")
@@ -179,9 +179,9 @@ def getBitness(binary):
179179
# TODO add machine types whenever we add more architectures
180180
elffile = lief.parse(binary)
181181
machine_type = elffile.header.machine_type
182-
if machine_type == lief.ELF.ARCH.x86_64:
182+
if machine_type == lief.ELF.ARCH.X86_64:
183183
return 64
184-
elif machine_type == lief.ELF.ARCH.i386:
184+
elif machine_type == lief.ELF.ARCH.I386:
185185
return 32
186186
return 0
187187

@@ -207,7 +207,7 @@ def getCodeAreas(binary):
207207
code_areas = []
208208
for section in elffile.sections:
209209
# SHF_EXECINSTR = 4
210-
if section.flags & 0x4:
210+
if section.flags & lief.ELF.Section.FLAGS.EXECINSTR.value:
211211
section_start = section.virtual_address
212212
section_size = section.size
213213
if section_size % section.alignment != 0:
@@ -216,7 +216,7 @@ def getCodeAreas(binary):
216216
code_areas.append([section_start, section_end])
217217
for segment in sorted(elffile.segments, key=lambda segment: segment.physical_size, reverse=True):
218218
# SHF_EXECINSTR = 4
219-
if segment.flags & 0x4:
219+
if segment.flags.value & lief.ELF.Section.FLAGS.EXECINSTR.value:
220220
segment_start = segment.virtual_address
221221
segment_size = segment.virtual_size
222222
if segment_size % segment.alignment != 0:

smda/utility/MachoFileLoader.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,12 @@ def getBitness(binary):
139139
# TODO add machine types whenever we add more architectures
140140
macho_file = lief.parse(binary)
141141
machine_type = macho_file.header.cpu_type
142-
if machine_type == lief.MachO.CPU_TYPES.x86_64:
142+
if machine_type == lief.MachO.Header.CPU_TYPE.X86_64:
143143
return 64
144-
elif machine_type == lief.MachO.CPU_TYPES.x86:
144+
elif machine_type == lief.MachO.Header.CPU_TYPES.X86:
145145
return 32
146+
elif machine_type == Header.CPU_TYPE.ARM64:
147+
raise NotImplementedError("SMDA does not support ARM yet.")
146148
return 0
147149

148150
@staticmethod
@@ -165,14 +167,14 @@ def getCodeAreas(binary):
165167
# TODO add machine types whenever we add more architectures
166168
macho_file = lief.parse(binary)
167169
ins_flags = (
168-
lief.MachO.SECTION_FLAGS.PURE_INSTRUCTIONS.value +
169-
lief.MachO.SECTION_FLAGS.SELF_MODIFYING_CODE.value +
170-
lief.MachO.SECTION_FLAGS.SOME_INSTRUCTIONS.value
170+
lief.MachO.Section.FLAGS.PURE_INSTRUCTIONS.value +
171+
lief.MachO.Section.FLAGS.SELF_MODIFYING_CODE.value +
172+
lief.MachO.Section.FLAGS.SOME_INSTRUCTIONS.value
171173
)
172174
code_areas = []
173175
for section in macho_file.sections:
174176
# SHF_EXECINSTR = 4
175-
if section.flags & ins_flags:
177+
if section.flags.value & ins_flags:
176178
section_start = section.virtual_address
177179
section_size = section.size
178180
if section.alignment and section_size % section.alignment != 0:

tests/cat

38.5 KB
Binary file not shown.

tests/cat_xored

38.5 KB
Binary file not shown.

tests/komplex

63.3 KB
Binary file not shown.

tests/komplex_xored

63.3 KB
Binary file not shown.

tests/testFileFormatParsers.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/python
2+
3+
import logging
4+
import os
5+
import unittest
6+
7+
from smda.utility.FileLoader import FileLoader
8+
from smda.common.BinaryInfo import BinaryInfo
9+
from smda.Disassembler import Disassembler
10+
from smda.common.SmdaReport import SmdaReport
11+
from smda.common.SmdaFunction import SmdaFunction
12+
from .context import config
13+
14+
LOG = logging.getLogger(__name__)
15+
logging.basicConfig(level=logging.INFO, format="%(asctime)-15s %(message)s")
16+
logging.disable(logging.CRITICAL)
17+
18+
19+
class SmdaIntegrationTestSuite(unittest.TestCase):
20+
"""Run a full example on a memory dump"""
21+
22+
@classmethod
23+
def setUpClass(cls):
24+
super(SmdaIntegrationTestSuite, cls).setUpClass()
25+
26+
def testPeParsingWithCutwail(self):
27+
disasm = Disassembler(config)
28+
# load encrypted malicious win.cutwail
29+
with open(os.path.join(config.PROJECT_ROOT, "tests", "cutwail_xored"), "rb") as f_binary:
30+
binary = f_binary.read()
31+
decrypted_cutwail = bytearray()
32+
for index, byte in enumerate(binary):
33+
if isinstance(byte, str):
34+
byte = ord(byte)
35+
decrypted_cutwail.append(byte ^ (index % 256))
36+
cutwail_binary = bytes(decrypted_cutwail)
37+
# run FileLoader and disassemble as file
38+
loader = FileLoader("/", map_file=True)
39+
loader._loadFile(cutwail_binary)
40+
file_content = loader.getData()
41+
binary_info = BinaryInfo(file_content)
42+
binary_info.raw_data = loader.getRawData()
43+
binary_info.file_path = ""
44+
binary_info.base_addr = loader.getBaseAddress()
45+
binary_info.bitness = loader.getBitness()
46+
binary_info.code_areas = loader.getCodeAreas()
47+
binary_info.oep = binary_info.getOep()
48+
cutwail_binary_info = binary_info
49+
cutwail_disassembly = disasm._disassemble(binary_info)
50+
cutwail_unmapped_disassembly = disasm.disassembleUnmappedBuffer(cutwail_binary)
51+
assert cutwail_unmapped_disassembly.num_functions == 33
52+
53+
def testElfParsingWithBase64(self):
54+
disasm = Disassembler(config)
55+
# load encrypted benign /bin/cat
56+
with open(os.path.join(config.PROJECT_ROOT, "tests", "cat_xored"), "rb") as f_binary:
57+
binary = f_binary.read()
58+
decrypted_cat = bytearray()
59+
for index, byte in enumerate(binary):
60+
if isinstance(byte, str):
61+
byte = ord(byte)
62+
decrypted_cat.append(byte ^ (index % 256))
63+
cat_binary = bytes(decrypted_cat)
64+
# run FileLoader and disassemble as file
65+
loader = FileLoader("/", map_file=True)
66+
loader._loadFile(cat_binary)
67+
file_content = loader.getData()
68+
binary_info = BinaryInfo(file_content)
69+
binary_info.raw_data = loader.getRawData()
70+
binary_info.file_path = ""
71+
binary_info.base_addr = loader.getBaseAddress()
72+
binary_info.bitness = loader.getBitness()
73+
binary_info.code_areas = loader.getCodeAreas()
74+
binary_info.oep = binary_info.getOep()
75+
cat_binary_info = binary_info
76+
cat_disassembly = disasm._disassemble(binary_info)
77+
cat_unmapped_disassembly = disasm.disassembleUnmappedBuffer(cat_binary)
78+
assert cat_unmapped_disassembly.num_functions == 150
79+
80+
def testMacOsParsingWithKomplex(self):
81+
disasm = Disassembler(config)
82+
# load encrypted malicious osx.komplex
83+
with open(os.path.join(config.PROJECT_ROOT, "tests", "komplex_xored"), "rb") as f_binary:
84+
binary = f_binary.read()
85+
decrypted_komplex = bytearray()
86+
for index, byte in enumerate(binary):
87+
if isinstance(byte, str):
88+
byte = ord(byte)
89+
decrypted_komplex.append(byte ^ (index % 256))
90+
komplex_binary = bytes(decrypted_komplex)
91+
# run FileLoader and disassemble as file
92+
loader = FileLoader("/", map_file=True)
93+
loader._loadFile(komplex_binary)
94+
file_content = loader.getData()
95+
binary_info = BinaryInfo(file_content)
96+
binary_info.raw_data = loader.getRawData()
97+
binary_info.file_path = ""
98+
binary_info.base_addr = loader.getBaseAddress()
99+
binary_info.bitness = loader.getBitness()
100+
binary_info.code_areas = loader.getCodeAreas()
101+
binary_info.oep = binary_info.getOep()
102+
komplex_binary_info = binary_info
103+
komplex_disassembly = disasm._disassemble(binary_info)
104+
komplex_unmapped_disassembly = disasm.disassembleUnmappedBuffer(komplex_binary)
105+
komplex_unmapped_disassembly.num_functions == 208
106+
107+
108+
if __name__ == '__main__':
109+
unittest.main()

0 commit comments

Comments
 (0)