Add max_slice_depth config parameter (#55)

This commit is contained in:
Damian Pfammatter
2025-02-24 16:19:20 +01:00
committed by GitHub
parent 8292dba30c
commit 54d5adabcb
7 changed files with 77 additions and 15 deletions
+6
View File
@@ -5,6 +5,12 @@ settings:
value: 3
min_value: -1
max_value: 99
max_slice_depth:
name: max_slice_depth
help: maximum slice depth to stop the search
value: -1
min_value: -1
max_value: 9999
highlight_color:
name: highlight_color
help: color used to highlight paths
+27 -4
View File
@@ -174,6 +174,23 @@ class Controller:
max_value=mfd_max_value
)
})
msd_name = "max_slice_depth"
msd_settings = settings.get(msd_name, None)
if msd_settings:
msd_value = int(msd_settings.get("value", None))
msd_min_value = int(msd_settings.get("min_value", None))
msd_max_value = int(msd_settings.get("max_value", None))
msd_value = min(max(msd_value, msd_min_value), msd_max_value)
msd_help = msd_settings.get("help", "")
parsed_conf["settings"].update({
msd_name: SpinboxSetting(
name=msd_name,
value=msd_value,
help=msd_help,
min_value=msd_min_value,
max_value=msd_max_value
)
})
col_name = "highlight_color"
col_settings = settings.get(col_name, None)
if col_settings:
@@ -394,6 +411,7 @@ class Controller:
self,
bv: bn.BinaryView,
max_call_level: int = None,
max_slice_depth: int = None,
enable_all_funs: bool = False,
but: qtw.QPushButton = None,
tbl: qtw.QTableWidget = None
@@ -426,6 +444,7 @@ class Controller:
ctr=self,
runs_headless=self._runs_headless,
max_call_level=max_call_level,
max_slice_depth=max_slice_depth,
enable_all_funs=enable_all_funs,
log=self._log
)
@@ -794,6 +813,7 @@ class MediumLevelILBackwardSlicerThread(bn.BackgroundTaskThread):
ctr: Controller,
runs_headless: bool = False,
max_call_level: int = None,
max_slice_depth: int = None,
enable_all_funs: bool = False,
tag: str = "BackSlicer",
log: Logger = Logger()
@@ -806,6 +826,7 @@ class MediumLevelILBackwardSlicerThread(bn.BackgroundTaskThread):
self._ctr: Controller = ctr
self._runs_headless: bool = runs_headless
self._max_call_level: int = max_call_level
self._max_slice_depth: int = max_slice_depth
self._enable_all_funs: bool = enable_all_funs
self._tag: str = tag
self._log: Logger = log
@@ -833,11 +854,12 @@ class MediumLevelILBackwardSlicerThread(bn.BackgroundTaskThread):
if not snk_funs:
self._log.warn(self._tag, "No sink functions configured")
# Find paths
max_call_level = self._max_call_level
if max_call_level is None:
# Settings
settings = self._ctr.get_settings()
max_call_level = settings.get("max_call_level").value
max_call_level = self._max_call_level if self._max_call_level is not None else settings.get("max_call_level").value
max_slice_depth = self._max_slice_depth if self._max_slice_depth is not None else settings.get("max_slice_depth").value
# Find paths
if src_funs and snk_funs:
for i, snk_fun in enumerate(snk_funs):
if self.cancelled: break
@@ -846,6 +868,7 @@ class MediumLevelILBackwardSlicerThread(bn.BackgroundTaskThread):
bv=self._bv,
sources=src_funs,
max_call_level=max_call_level,
max_slice_depth=max_slice_depth,
found_path=self._ctr.add_path_to_view,
canceled=lambda:self.cancelled,
tag=self._tag,
+2 -1
View File
@@ -249,6 +249,7 @@ class SinkFunction(Function):
bv: bn.BinaryView,
sources: List[SourceFunction],
max_call_level: int,
max_slice_depth: int,
found_path: Callable[[Path], None],
canceled: Callable[[], bool],
tag: str = None,
@@ -309,7 +310,7 @@ class SinkFunction(Function):
for src_inst in src_insts:
if canceled(): break
# Find paths
for insts, call_graph in slicer.find_paths(par_var, src_inst):
for insts, call_graph in slicer.find_paths(par_var, src_inst, max_slice_depth):
# Prepend sink instruction
insts.insert(0, snk_inst)
# Find split between sink and source originating instructions
+9 -6
View File
@@ -467,19 +467,22 @@ class MediumLevelILBackwardSlicer:
def find_paths(
self,
snk_inst: bn.MediumLevelILInstruction,
src_inst: bn.MediumLevelILInstruction
src_inst: bn.MediumLevelILInstruction,
max_slice_depth: int
) -> List[Tuple[List[bn.MediumLevelILInstruction], MediumLevelILFunctionGraph]]:
"""
This method finds all simple paths from `snk_inst` to `src_inst`. For each found path, the
following is returned: First, a list of instructions belonging to the path. And second, a
function call graph, where nodes and edges belonging to the path, have an attribute
`in_path` set to `True`.
This method finds all simple paths from `snk_inst` to `src_inst`, with optionally limiting
path length by `max_slice_depth`. For each found path, the following is returned: First, a
list of instructions belonging to the path. And second, a function call graph, where nodes
and edges belonging to the path, have an attribute `in_path` set to `True`.
"""
paths = []
# Find all simple paths
try:
if max_slice_depth is not None and max_slice_depth < 0:
max_slice_depth = None
simple_paths: List[List[bn.MediumLevelILInstruction]] = list(
nx.all_simple_paths(self._inst_graph, snk_inst, src_inst)
nx.all_simple_paths(self._inst_graph, snk_inst, src_inst, max_slice_depth)
)
except (nx.NodeNotFound, nx.NetworkXNoPath):
return paths
+13
View File
@@ -323,6 +323,19 @@ class SidebarWidget(bnui.SidebarWidget):
mcl_lbl = qtw.QLabel(f"{mcl_name:s}:")
mcl_lbl.setToolTip(mcl.help)
com_lay.addRow(mcl_lbl, mcl.widget)
msd_name = "max_slice_depth"
msd = settings.get(msd_name, None)
if msd:
msd.widget = qtw.QSpinBox()
msd.widget.setRange(msd.min_value, msd.max_value)
msd.widget.setValue(msd.value)
msd.widget.setToolTip(msd.help)
msd.widget.valueChanged.connect(
lambda value, setting=msd: self._ctr.spinbox_change_value(setting, value)
)
msd_lbl = qtw.QLabel(f"{msd_name:s}:")
msd_lbl.setToolTip(msd.help)
com_lay.addRow(msd_lbl, msd.widget)
com_wid.setLayout(com_lay)
com_box_lay = qtw.QVBoxLayout()
com_box_lay.addWidget(com_wid)
+6 -1
View File
@@ -33,6 +33,11 @@ def main() -> None:
type=int, default=None,
help="backward slicing visits called functions up to the given level"
)
parser.add_argument(
"--max_slice_depth",
type=int, default=None,
help="maximum slice depth to stop the search"
)
parser.add_argument(
"--export_paths_to_json_file",
help="export identified paths in JSON format"
@@ -51,7 +56,7 @@ def main() -> None:
bv = bn.load(args.file)
bv.update_analysis_and_wait()
# Analyze binary with Mole
paths = ctr.find_paths(bv, args.max_call_level)
paths = ctr.find_paths(bv, args.max_call_level, args.max_slice_depth)
# Export identified paths
if args.export_paths_to_yml_file or args.export_paths_to_json_file:
# Calculate SHA1 hash of binary
+14 -3
View File
@@ -65,6 +65,13 @@ class TestData(unittest.TestCase):
max_value=99,
help="backward slicing visits called functions up to the given level"
),
"max_slice_depth": SpinboxSetting(
name="max_slice_depth",
value=-1,
min_value=-1,
max_value=9999,
help="maximum slice depth to stop the search"
),
"highlight_color": ComboboxSetting(
name="highlight_color",
value="Red",
@@ -176,10 +183,14 @@ class TestData(unittest.TestCase):
return
def test_serialize_spinbox_settings(self) -> None:
setting = self.conf.settings["max_call_level"]
settings = {
"max_call_level": self.conf.settings["max_call_level"].to_dict(),
"max_slice_depth": self.conf.settings["max_slice_depth"].to_dict()
}
# Serialize
yaml.safe_dump(
setting.to_dict(),
settings,
self.tf,
sort_keys=False,
default_style=None,
@@ -190,7 +201,7 @@ class TestData(unittest.TestCase):
self.tf.seek(0)
ydoc = yaml.safe_load(self.tf)
# Assert
self.assertEqual(ydoc, setting, "Serialization error of 'SpinboxSetting'")
self.assertEqual(ydoc, settings, "Serialization error of 'SpinboxSetting'")
return
def test_serialize_combobox_settings(self) -> None: