230 not finding paths on functions using out variables (#233)

- Added unit-tests `function_out_params-XX.c`
- When the slicer enters a function, it now understands whether it followed the function's return value or entered due to following a pointer parameter. In the first case, the slicer proceeds at all possible return instructions of the callee. In the second case, the slicer proceeds at the instruction writing the output parameter.
- Few other improvements with respect to pointer tracking
This commit is contained in:
Damian Pfammatter
2025-11-07 15:02:41 +01:00
committed by GitHub
parent 2215fdecde
commit 5b1241d7f4
15 changed files with 554 additions and 162 deletions
+6 -2
View File
@@ -108,9 +108,13 @@ class FunctionHelper:
data_var = bv.get_data_var_at(data_ref) data_var = bv.get_data_var_at(data_ref)
if data_var is None: if data_var is None:
continue continue
# Get data variable's type name and offset
try:
name = data_var.type.name
offset = data_ref - data_var.address
except Exception:
continue
# Iterate all code references to the data variable's referenced field # Iterate all code references to the data variable's referenced field
name = data_var.type.name
offset = data_ref - data_var.address
for code_ref in bv.get_code_refs_for_type_field(name, offset): for code_ref in bv.get_code_refs_for_type_field(name, offset):
# Iterate all functions containing the code reference # Iterate all functions containing the code reference
for ref_func in bv.get_functions_containing(code_ref.address): for ref_func in bv.get_functions_containing(code_ref.address):
+16 -16
View File
@@ -14,11 +14,11 @@ class MediumLevelILCallFrame:
def __init__(self, func: bn.MediumLevelILFunction) -> None: def __init__(self, func: bn.MediumLevelILFunction) -> None:
self.func = func self.func = func
self.func_params: List[int] = [] self.func_params: Set[int] = set()
self.inst_stack: List[bn.MediumLevelILInstruction] = [] self.inst_stack: List[bn.MediumLevelILInstruction] = []
self.last_inst: bn.MediumLevelILInstruction = None self.last_inst: bn.MediumLevelILInstruction = None
self.inst_graph: MediumLevelILInstructionGraph = MediumLevelILInstructionGraph() self.inst_graph: MediumLevelILInstructionGraph = MediumLevelILInstructionGraph()
self.mem_def_insts: Set[bn.MediumLevelILInstruction] = set() self.mem_def_insts: List[bn.MediumLevelILInstruction] = []
return return
def __eq__(self, other: MediumLevelILCallFrame) -> bool: def __eq__(self, other: MediumLevelILCallFrame) -> bool:
@@ -92,15 +92,14 @@ class MediumLevelILCallTracker:
def push_func( def push_func(
self, self,
from_inst: bn.MediumLevelILInstruction,
to_inst: bn.MediumLevelILInstruction, to_inst: bn.MediumLevelILInstruction,
reverse: bool = False, reverse: bool = False,
) -> bool: ) -> bool:
""" """
This method creates a new call frame with the function `to_inst.function` and pushes it to This method creates a new call frame with the function `to_inst.function` and pushes it to
the top of the call stack. Also, it updates the call graph. If `reverse` is True, `func` is the top of the call stack. Also, it updates the call graph. If `reverse` is True,
considered to be the caller (not the callee). The function returns True if in case of a `to_inst.function` is considered to be the caller (not the callee). The function returns
recursion, False otherwise. True in case of recursion, False otherwise.
""" """
# Get the return instruction's function # Get the return instruction's function
func = to_inst.function func = to_inst.function
@@ -128,10 +127,10 @@ class MediumLevelILCallTracker:
self._call_graph.add_node(func) self._call_graph.add_node(func)
return recursion return recursion
def pop_func(self) -> List[int]: def pop_func(self) -> Set[int]:
""" """
This method pops the top call frame from the call stack and returns a list of function This method pops the top call frame from the call stack and returns a set of parameter
parameter instructions that should be sliced further. indices (`func_params`) that should be sliced further.
""" """
if self._call_stack: if self._call_stack:
# Pop old call frame and get its last instruction # Pop old call frame and get its last instruction
@@ -161,7 +160,7 @@ class MediumLevelILCallTracker:
self._inst_graph = nx.compose(self._inst_graph, old_inst_graph) self._inst_graph = nx.compose(self._inst_graph, old_inst_graph)
# Return indices of parameters to be sliced further # Return indices of parameters to be sliced further
return old_call_frame.func_params return old_call_frame.func_params
return [] return set()
def push_inst(self, inst: bn.MediumLevelILInstruction) -> None: def push_inst(self, inst: bn.MediumLevelILInstruction) -> None:
""" """
@@ -195,17 +194,18 @@ class MediumLevelILCallTracker:
This method pushes the given instruction `inst` to the memory definition instructions of This method pushes the given instruction `inst` to the memory definition instructions of
the call frame on the top of the call stack. the call frame on the top of the call stack.
""" """
if self._call_stack: if self._call_stack and inst not in self._call_stack[-1].mem_def_insts:
self._call_stack[-1].mem_def_insts.add(inst) self._call_stack[-1].mem_def_insts.append(inst)
return return
def push_param(self, param_idx: int) -> None: def add_func_param(self, param_idx: int) -> None:
""" """
This method pushes the given parameter index `param_idx` to the call frame on the top of the This method adds the given parameter to the `func_params` set of the current call frame.
stack. `func_params` is the set of parameters that should be sliced when returning back to the
caller of the current function.
""" """
if self._call_stack: if self._call_stack:
self._call_stack[-1].func_params.append(param_idx) self._call_stack[-1].func_params.add(param_idx)
return return
def print_call_stack(self) -> None: def print_call_stack(self) -> None:
+261 -140
View File
@@ -6,7 +6,7 @@ from mole.common.helper.variable import VariableHelper
from mole.common.log import log from mole.common.log import log
from mole.core.call import MediumLevelILCallTracker from mole.core.call import MediumLevelILCallTracker
from mole.core.graph import MediumLevelILFunctionGraph, MediumLevelILInstructionGraph from mole.core.graph import MediumLevelILFunctionGraph, MediumLevelILInstructionGraph
from typing import Callable, Dict from typing import Callable, Set
import binaryninja as bn import binaryninja as bn
@@ -72,9 +72,7 @@ class MediumLevelILBackwardSlicer:
for call_inst in direct_call_insts | indirect_call_insts: for call_inst in direct_call_insts | indirect_call_insts:
from_inst = call_inst from_inst = call_inst
to_inst = call_inst.params[param_idx - 1] to_inst = call_inst.params[param_idx - 1]
recursion = self._call_tracker.push_func( recursion = self._call_tracker.push_func(to_inst, reverse=True)
from_inst, to_inst, reverse=True
)
from_inst_info = InstructionHelper.get_inst_info(from_inst, False) from_inst_info = InstructionHelper.get_inst_info(from_inst, False)
if not recursion: if not recursion:
log.debug( log.debug(
@@ -94,15 +92,18 @@ class MediumLevelILBackwardSlicer:
self._tag, self._tag,
f"Follow parameter {param_idx:d} '{ssa_var_info:s}' when going back to specific caller", f"Follow parameter {param_idx:d} '{ssa_var_info:s}' when going back to specific caller",
) )
self._call_tracker.push_param(param_idx) self._call_tracker.add_func_param(param_idx)
return return
def _slice_backwards( def _slice_backwards(
self, self, inst: bn.MediumLevelILInstruction, call_params: Set[int] = set()
inst: bn.MediumLevelILInstruction,
) -> None: ) -> None:
""" """
This method backward slices instruction `inst` based on its type. This method backward slices instruction `inst` based on its type. `call_params` is a set of
parameters (indices) used to distinguish whether the slicer reached the last call
instruction due to hitting some parameters (in which case slicing proceeds at the definition
sites of these parameters) or due to hitting the call's return value (in which case slicing
proceeds at all possible return instructions).
""" """
# Check if slicing should be cancelled # Check if slicing should be cancelled
if self._cancelled(): if self._cancelled():
@@ -137,12 +138,11 @@ class MediumLevelILBackwardSlicer:
segment = self._bv.get_segment_at(constant) segment = self._bv.get_segment_at(constant)
if segment and segment.writable: if segment and segment.writable:
# Iterate all memory defining instructions # Iterate all memory defining instructions
mem_def_insts = FunctionHelper.get_ssa_memory_definitions( for mem_def_inst in FunctionHelper.get_ssa_memory_definitions(
inst.function, inst.function,
inst.ssa_memory_version, inst.ssa_memory_version,
self._max_memory_slice_depth, self._max_memory_slice_depth,
) ):
for mem_def_inst in mem_def_insts:
mem_def_inst_info = InstructionHelper.get_inst_info( mem_def_inst_info = InstructionHelper.get_inst_info(
mem_def_inst, False mem_def_inst, False
) )
@@ -162,14 +162,14 @@ class MediumLevelILBackwardSlicer:
| bn.MediumLevelILTailcallUntypedSsa(params=params) | bn.MediumLevelILTailcallUntypedSsa(params=params)
): ):
followed = False followed = False
for param in params: for param_idx, param in enumerate(params, start=1):
match param: match param:
case bn.MediumLevelILConstPtr( case bn.MediumLevelILConstPtr(
constant=constant constant=constant
) if constant == inst.constant: ) if constant == inst.constant:
log.debug( log.debug(
self._tag, self._tag,
f"Follow call instruction '{mem_def_inst_info:s}' since it uses '0x{inst.constant:x}'", f"Follow call instruction '{mem_def_inst_info:s}' since it uses '0x{inst.constant:x}' as parameter",
) )
self._call_tracker.push_mem_def_inst( self._call_tracker.push_mem_def_inst(
mem_def_inst mem_def_inst
@@ -191,12 +191,11 @@ class MediumLevelILBackwardSlicer:
case bn.MediumLevelILLoadSsa(src=load_src_inst, size=load_src_size): case bn.MediumLevelILLoadSsa(src=load_src_inst, size=load_src_size):
followed = False followed = False
# Iterate all memory defining instructions # Iterate all memory defining instructions
mem_def_insts = FunctionHelper.get_ssa_memory_definitions( for mem_def_inst in FunctionHelper.get_ssa_memory_definitions(
inst.function, inst.function,
inst.ssa_memory_version, inst.ssa_memory_version,
self._max_memory_slice_depth, self._max_memory_slice_depth,
) ):
for mem_def_inst in mem_def_insts:
mem_def_inst_info = InstructionHelper.get_inst_info( mem_def_inst_info = InstructionHelper.get_inst_info(
mem_def_inst, False mem_def_inst, False
) )
@@ -350,12 +349,11 @@ class MediumLevelILBackwardSlicer:
): ):
followed = False followed = False
# Iterate all memory defining instructions # Iterate all memory defining instructions
mem_def_insts = FunctionHelper.get_ssa_memory_definitions( for mem_def_inst in FunctionHelper.get_ssa_memory_definitions(
inst.function, inst.function,
inst.ssa_memory_version, inst.ssa_memory_version,
self._max_memory_slice_depth, self._max_memory_slice_depth,
) ):
for mem_def_inst in mem_def_insts:
mem_def_inst_info = InstructionHelper.get_inst_info( mem_def_inst_info = InstructionHelper.get_inst_info(
mem_def_inst, False mem_def_inst, False
) )
@@ -449,60 +447,23 @@ class MediumLevelILBackwardSlicer:
) )
self._slice_backwards(load_src_inst) self._slice_backwards(load_src_inst)
case ( case (
bn.MediumLevelILVarAliased() bn.MediumLevelILVarAliased(src=bn.SSAVariable(var=var))
| bn.MediumLevelILVarAliasedField() | bn.MediumLevelILVarAliasedField(src=bn.SSAVariable(var=var))
| bn.MediumLevelILAddressOf() | bn.MediumLevelILAddressOf(src=var)
| bn.MediumLevelILAddressOfField() | bn.MediumLevelILAddressOfField(src=var)
): ):
# Get all assignment instructions of the form `var_x = &var_y` in the current offset = getattr(inst, "offset", 0)
# function
var_addr_assignments = FunctionHelper.get_var_addr_assignments(
inst.function
)
# Get variable being referenced by `inst` (`var_y`)
# TODO: Should we consider the `offset` in MLIL_VAR_ALIASED_FIELD and
# MLIL_ADDRESS_OF_FIELD as well?
match inst:
case (
bn.MediumLevelILVarAliased(src=src)
| bn.MediumLevelILVarAliasedField(src=src)
):
var = src.var
case (
bn.MediumLevelILAddressOf(src=src)
| bn.MediumLevelILAddressOfField(src=src)
):
var = src
var_info = VariableHelper.get_var_info(var) var_info = VariableHelper.get_var_info(var)
# Get all assignment instructions (`var_x = &var_y`) using the address of the
# referenced variable (`var_y`) as a source
var_addr_ass_insts = var_addr_assignments.get(var, [])
# Get all use sites (e.g. `var_z = call(var_x)`) of assignment instructions'
# destinations (`var_x`)
dest_var_use_sites: Dict[
bn.MediumLevelILInstruction, bn.MediumLevelILSetVarSsa
] = {}
for var_addr_ass_inst in var_addr_ass_insts:
for dest_var_use_site in var_addr_ass_inst.dest.use_sites:
dest_var_use_sites[dest_var_use_site] = var_addr_ass_inst
# Iterate all instructions in the current function defining the current memory # Iterate all instructions in the current function defining the current memory
# version # version
mem_def_insts = FunctionHelper.get_ssa_memory_definitions( for mem_def_inst in FunctionHelper.get_ssa_memory_definitions(
inst.function, inst.function,
inst.ssa_memory_version, inst.ssa_memory_version,
self._max_memory_slice_depth, self._max_memory_slice_depth,
) ):
for mem_def_inst in mem_def_insts:
mem_def_inst_info = InstructionHelper.get_inst_info( mem_def_inst_info = InstructionHelper.get_inst_info(
mem_def_inst, False mem_def_inst, False
) )
# Check if memory defining instruction is in the use sites
if mem_def_inst not in dest_var_use_sites:
log.debug(
self._tag,
f"Do not follow instruction '{mem_def_inst_info:s}' since it not uses '&{var_info:s}'",
)
continue
# Check if memory defining instruction was followed before # Check if memory defining instruction was followed before
if self._call_tracker.is_in_current_mem_def_insts(mem_def_inst): if self._call_tracker.is_in_current_mem_def_insts(mem_def_inst):
log.debug( log.debug(
@@ -510,25 +471,87 @@ class MediumLevelILBackwardSlicer:
f"Do not follow instruction '{mem_def_inst_info:s}' since followed before in the current call frame", f"Do not follow instruction '{mem_def_inst_info:s}' since followed before in the current call frame",
) )
continue continue
match mem_def_inst: match mem_def_inst:
# Slice calls having the referenced variable address (`&var_y`) as parameter # Slice the source of assignments having an alias of `var` as destination
case bn.MediumLevelILSetVarAliased(
prev=prev_ssa_var, dest=dest_ssa_var
):
if prev_ssa_var.var == dest_ssa_var.var == var:
log.debug(
self._tag,
f"Follow source of instruction '{mem_def_inst_info:s}' since it writes to an alias of '{var_info:s}'",
)
self._call_tracker.push_mem_def_inst(mem_def_inst)
self._slice_backwards(mem_def_inst.src)
# Slice the source of assignments having an aliased field of `var` as destination
case bn.MediumLevelILSetVarAliasedField(
prev=prev_ssa_var, dest=dest_ssa_var, offset=dest_offset
):
if (
prev_ssa_var.var == dest_ssa_var.var == var
and dest_offset == offset
):
log.debug(
self._tag,
f"Follow source of instruction '{mem_def_inst_info:s}' since it writes to an alias of '{var_info:s}[{dest_offset:d}]'",
)
self._call_tracker.push_mem_def_inst(mem_def_inst)
self._slice_backwards(mem_def_inst.src)
# Slice calls having `&var` as parameter
case ( case (
bn.MediumLevelILCallSsa(params=params) bn.MediumLevelILCallSsa(params=params)
| bn.MediumLevelILCallUntypedSsa(params=params) | bn.MediumLevelILCallUntypedSsa(params=params)
| bn.MediumLevelILTailcallSsa(params=params) | bn.MediumLevelILTailcallSsa(params=params)
| bn.MediumLevelILTailcallUntypedSsa(params=params) | bn.MediumLevelILTailcallUntypedSsa(params=params)
): ):
var_addr_ass_inst = dest_var_use_sites[mem_def_inst] # Find set of call parameters the slicer should follow
var_addr_ass_inst_info = InstructionHelper.get_inst_info( call_params = set()
var_addr_ass_inst, False for param_idx, param in enumerate(
) mem_def_inst.params, start=1
log.debug( ):
self._tag, match param:
f"Follow call instruction '{mem_def_inst_info:s}' since it uses '{var_addr_ass_inst_info:s}'", # `param_var = &var`
) case bn.MediumLevelILVarSsa(var=param_var):
self._call_tracker.push_mem_def_inst(mem_def_inst) # Get all assignments of the form `param_var = &var` in the
self._slice_backwards(mem_def_inst) # current function
var_addr_assignments = (
FunctionHelper.get_var_addr_assignments(
inst.function
)
)
var_addr_ass_insts = var_addr_assignments.get(
var, []
)
# Ensure the memory defining call instruction uses parameter
# `param_var`
for var_addr_ass_inst in var_addr_ass_insts:
if (
param_var == var_addr_ass_inst.dest
and mem_def_inst
in var_addr_ass_inst.dest.use_sites
):
call_params.add(param_idx)
# `&param_var == var`
case bn.MediumLevelILAddressOf(src=param_src_var):
if param_src_var == var:
call_params.add(param_idx)
# `&param_var:0 == var`
case bn.MediumLevelILAddressOfField(
src=param_src_var, offset=param_offset
):
if (
param_src_var == var
and param_offset == offset
):
call_params.add(param_idx)
# Slice the call instruction if we need to follow any parameter
if call_params:
log.debug(
self._tag,
f"Follow call instruction '{mem_def_inst_info:s}' since it uses '&{var_info:s}' as parameter",
)
self._call_tracker.push_mem_def_inst(mem_def_inst)
self._slice_backwards(mem_def_inst, call_params)
case ( case (
bn.MediumLevelILVarSsa() bn.MediumLevelILVarSsa()
| bn.MediumLevelILVarSsaField() | bn.MediumLevelILVarSsaField()
@@ -560,83 +583,181 @@ class MediumLevelILBackwardSlicer:
bn.MediumLevelILConstPtr(constant=func_addr) bn.MediumLevelILConstPtr(constant=func_addr)
| bn.MediumLevelILImport(constant=func_addr) | bn.MediumLevelILImport(constant=func_addr)
): ):
# Get destination function # Get destination function and its symbol
dest_func = self._bv.get_function_at(func_addr) dest_func = self._bv.get_function_at(func_addr)
# Proceed slicing the parameters if we cannot go into the callee (callee is dest_symb = dest_func.symbol if dest_func else None
# not a valid function - e.g. external function) # Slicer cannot go into the callee (proceed with function parameters)
if ( if (
not dest_func not dest_func
or not dest_func.mlil or not dest_func.mlil
or not dest_func.mlil.ssa_form or not dest_func.mlil.ssa_form
or not dest_symb
or dest_symb.type
not in [
bn.SymbolType.FunctionSymbol,
bn.SymbolType.LibraryFunctionSymbol,
]
): ):
for param in inst.params: for param in inst.params:
self._slice_backwards(param) self._slice_backwards(param)
# Proceed slicing the callee's return instructions if we can go into the # Slicer can go into the callee
# callee (callee is a valid function)
else: else:
# Get callee's SSA form
dest_func = dest_func.mlil.ssa_form dest_func = dest_func.mlil.ssa_form
dest_symb = dest_func.source_function.symbol # Iterate the callee's return instructions
for dest_func_inst in dest_func.instructions: for ret_inst in FunctionHelper.get_mlil_return_insts(
match dest_func_inst: dest_func
case ( ):
bn.MediumLevelILRet() ret_inst_info = InstructionHelper.get_inst_info(
| bn.MediumLevelILTailcallSsa() ret_inst, False
)
# Proceed slicing the relevant output parameters, if we followed the
# call due to reaching them
if call_params:
# Iterate all memory defining instructions
for (
mem_def_inst
) in FunctionHelper.get_ssa_memory_definitions(
dest_func,
ret_inst.ssa_memory_version,
self._max_memory_slice_depth,
): ):
# Function mem_def_inst_info = (
if dest_symb.type in [ InstructionHelper.get_inst_info(
bn.SymbolType.FunctionSymbol, mem_def_inst, False
bn.SymbolType.LibraryFunctionSymbol,
]:
from_inst = inst
to_inst = dest_func_inst
recursion = self._call_tracker.push_func(
from_inst, to_inst
) )
to_inst_info = ( )
InstructionHelper.get_inst_info( # Check if memory defining instruction was followed before
to_inst, False if self._call_tracker.is_in_current_mem_def_insts(
) mem_def_inst
)
if not recursion:
log.debug(
self._tag,
f"Follow return instruction '{to_inst_info:s}' of function '{dest_inst_info:s}'",
)
self._slice_backwards(to_inst)
else:
log.debug(
self._tag,
f"Do not follow return instruction '{to_inst_info:s}' of function '{dest_inst_info:s}' since recursion detected",
)
# Get call level of the callee
call_level = (
self._call_tracker.get_call_level()
)
# Get parameters reached in the callee
param_idxs = self._call_tracker.pop_func()
# If maximum call level was reached in the callee, slice
# all parameters
if (
self._max_call_level >= 0
and abs(call_level)
> self._max_call_level
):
for param in inst.params:
self._slice_backwards(param)
# If maximum call level was not reached in the callee,
# slice only the specifically reached parameters
else:
for param_idx in param_idxs:
self._slice_backwards(
inst.params[param_idx - 1]
)
# Imported function
elif (
dest_symb.type
== bn.SymbolType.ImportedFunctionSymbol
): ):
for param in inst.params: log.debug(
self._slice_backwards(param) self._tag,
f"Do not follow instruction '{mem_def_inst_info:s}' since followed before in the current call frame",
)
continue
# Ensure store instruction
if not isinstance(
mem_def_inst,
bn.MediumLevelILStoreSsa
| bn.MediumLevelILStoreStructSsa,
):
continue
# Match HLIL instruction
if mem_def_inst.hlil is None:
continue
hlil_mem_def_inst = mem_def_inst.hlil.ssa_form
match hlil_mem_def_inst:
# Memory assignment to dereferenced variable
case bn.HighLevelILAssignMemSsa(
dest=(
bn.HighLevelILDerefSsa(
src=bn.HighLevelILVarSsa(
var=dest_var
)
)
| bn.HighLevelILDerefFieldSsa(
src=bn.HighLevelILVarSsa(
var=dest_var
)
)
)
):
# Ensure we store a parameter variable of interest
param_vars = list(
dest_func.source_function.parameter_vars
)
if dest_var.var not in param_vars:
continue
param_idx = (
param_vars.index(dest_var.var) + 1
)
if param_idx not in call_params:
continue
# Push callee and proceed slicing its output parameter writing instruction (if no recursion)
recursion = (
self._call_tracker.push_func(
mem_def_inst
)
)
if recursion:
log.debug(
self._tag,
f"Do not follow instruction '{mem_def_inst_info:s}' of function '{dest_inst_info:s}' since recursion detected",
)
else:
dest_var_info = (
VariableHelper.get_ssavar_info(
dest_var
)
)
log.debug(
self._tag,
f"Follow instruction '{mem_def_inst_info:s}' of function '{dest_inst_info:s}' since it writes the output parameter variable '{dest_var_info:s}'",
)
self._call_tracker.push_mem_def_inst(
mem_def_inst
)
self._slice_backwards(mem_def_inst)
# Get call level of the callee
call_level = (
self._call_tracker.get_call_level()
)
# Get parameters reached in the callee
param_idxs = (
self._call_tracker.pop_func()
)
# If maximum call level was reached in the callee, slice all
# parameters
if (
self._max_call_level >= 0
and abs(call_level)
> self._max_call_level
):
for param in inst.params:
self._slice_backwards(param)
# If maximum call level was not reached in the callee, slice only
# the specifically reached parameters
else:
for param_idx in param_idxs:
self._slice_backwards(
inst.params[param_idx - 1]
)
# Proceed slicing all possible return instructions, if we followed
# the call due to reaching its return value
else:
# Push callee and proceed slicing its return instruction (if no recursion)
recursion = self._call_tracker.push_func(ret_inst)
if recursion:
log.debug(
self._tag,
f"Do not follow return instruction '{ret_inst_info:s}' of function '{dest_inst_info:s}' since recursion detected",
)
else:
log.debug(
self._tag,
f"Follow return instruction '{ret_inst_info:s}' of function '{dest_inst_info:s}'",
)
self._slice_backwards(ret_inst)
# Get call level of the callee
call_level = self._call_tracker.get_call_level()
# Get parameters reached in the callee
param_idxs = self._call_tracker.pop_func()
# If maximum call level was reached in the callee, slice all
# parameters
if (
self._max_call_level >= 0
and abs(call_level) > self._max_call_level
):
for param in inst.params:
self._slice_backwards(param)
# If maximum call level was not reached in the callee, slice only
# the specifically reached parameters
else:
for param_idx in param_idxs:
self._slice_backwards(
inst.params[param_idx - 1]
)
# Indirect function calls # Indirect function calls
case bn.MediumLevelILVarSsa(): case bn.MediumLevelILVarSsa():
for param in inst.params: for param in inst.params:
@@ -695,7 +816,7 @@ class MediumLevelILBackwardSlicer:
This method backward slices the instruction `inst`. This method backward slices the instruction `inst`.
""" """
self._call_tracker = MediumLevelILCallTracker() self._call_tracker = MediumLevelILCallTracker()
self._call_tracker.push_func(None, inst, reverse=True) self._call_tracker.push_func(inst, reverse=True)
deque( deque(
inst.ssa_form.traverse(lambda inst: self._slice_backwards(inst)), inst.ssa_form.traverse(lambda inst: self._slice_backwards(inst)),
maxlen=0, maxlen=0,
+1 -1
View File
@@ -24,7 +24,7 @@
"Linux": "", "Linux": "",
"Windows": "" "Windows": ""
}, },
"version": "0.4.6", "version": "0.4.7",
"author": "Damian Pfammatter and Sergio Paganoni", "author": "Damian Pfammatter and Sergio Paganoni",
"minimumbinaryninjaversion": 6455 "minimumbinaryninjaversion": 6455
} }
+1 -1
View File
@@ -11,7 +11,7 @@ include = ["mole*"]
[project] [project]
name = "mole" name = "mole"
version = "0.4.6" version = "0.4.7"
description = "A Binary Ninja plugin to identify interesting paths using static backward slicing" description = "A Binary Ninja plugin to identify interesting paths using static backward slicing"
authors = [ authors = [
{name = "Damian Pfammatter"}, {name = "Damian Pfammatter"},
+33
View File
@@ -0,0 +1,33 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 8
/*
Testcase Description:
- Output parameter 1 (int*): written and influence on the sink
*/
char dest[BUF_SIZE];
char src[] = "Hello, World!";
__attribute__ ((noinline))
int get_size(int* size){
char* env_size = getenv("SIZE");
if(env_size != NULL) {
*size = atoi(env_size);
return 0;
}
return -1;
}
int main() {
int size = 0;
if (get_size(&size) == 0) {
memcpy(dest, src, size);
} else {
fprintf(stderr, "SIZE environment variable not set.\n");
}
return 0;
}
+27
View File
@@ -0,0 +1,27 @@
#include <stdio.h>
#include <stdlib.h>
/*
Testcase Description:
- Output parameter 1 (char**): written and influence on the sink
*/
__attribute__ ((noinline))
int get_cmd(char **out_cmd){
char *env_cmd = getenv("CMD");
if (env_cmd != NULL) {
*out_cmd = env_cmd;
return 0;
}
return -1;
}
int main() {
char *cmd = NULL;
if (get_cmd(&cmd) == 0) {
system(cmd);
} else {
fprintf(stderr, "CMD environment variable not set.\n");
}
return 0;
}
+30
View File
@@ -0,0 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
/*
Testcase Description:
- Output parameter 1 (char**): written and influence on the sink
*/
__attribute__ ((noinline, optimize("O0")))
int get_cmd(char **out_cmd){
char* env_cmd = getenv("CMD");
char** out_cmd_cpy = out_cmd;
if (env_cmd != NULL) {
*out_cmd_cpy = env_cmd;
return 0;
}
*out_cmd_cpy = "Test";
printf("%s\n", *out_cmd_cpy);
return -1;
}
int main() {
char *cmd = NULL;
if (get_cmd(&cmd) == 0) {
system(cmd);
} else {
fprintf(stderr, "CMD environment variable not set.\n");
}
return 0;
}
+31
View File
@@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdlib.h>
/*
Testcase Description:
- Function with output parameter (char**)
*/
typedef struct {
char* cmd;
} MyStruct;
__attribute__ ((noinline))
int get_cmd(MyStruct *s){
char *env_cmd = getenv("CMD");
if (env_cmd != NULL) {
s->cmd = env_cmd;
return 0;
}
return -1;
}
int main() {
MyStruct s;
if (get_cmd(&s) == 0) {
system(s.cmd);
} else {
fprintf(stderr, "CMD environment variable not set.\n");
}
return 0;
}
+30
View File
@@ -0,0 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
/*
Testcase Description:
- Output parameter 1 (char**): written but no influence on the sink
- Output parameter 2 (char**): written and influence on the sink
*/
__attribute__ ((noinline))
int get_cmd(char **out_msg, char **out_cmd){
char *env_cmd = getenv("CMD");
if (env_cmd != NULL) {
*out_cmd = env_cmd;
return 0;
}
*out_msg = getenv("MSG");
return -1;
}
int main() {
char *msg = NULL;
char *cmd = NULL;
if (get_cmd(&msg, &cmd) == 0) {
system(cmd);
} else {
fprintf(stderr, "CMD environment variable not set.\n");
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
/*
Testcase Description:
- Output parameter 1 (char**): not written and influence on the sink
*/
__attribute__ ((noinline, optimize("O0")))
int check_cmd(char** cmd){
if(*cmd != NULL){
return 0;
}
return -1;
}
int main() {
char *cmd = getenv("CMD");
if (check_cmd(&cmd) == 0) {
system(cmd);
} else {
fprintf(stderr, "CMD environment variable not set.\n");
}
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
#include <stdio.h>
#include <stdlib.h>
/*
Testcase Description:
- Output parameter 1 (char*) : written but no influence on the sink
- Output parameter 2 (char**): not written and influence on the sink
*/
__attribute__ ((noinline, optimize("O0")))
int check_cmd(char* msg, char** cmd){
if(*cmd != NULL){
return 0;
}
msg = getenv("MSG");
if(msg != NULL){
fprintf(stderr, "%s!\n", msg);
}
return -1;
}
int main() {
char *msg = NULL;
char *cmd = getenv("CMD");
if (check_cmd(msg, &cmd) == 0) {
system(cmd);
}
return 0;
}
+62
View File
@@ -0,0 +1,62 @@
from __future__ import annotations
from tests.slicing.conftest import TestSlicing
from typing import List
class TestFunctionOutParams(TestSlicing):
def test_function_out_params_01(
self, filenames: List[str] = ["function_out_params-01"]
) -> None:
self.assert_paths(
src=[("getenv", None)],
snk=[("memcpy", 3)],
call_chains=[["main", "get_size"]],
filenames=filenames,
)
return
def test_function_out_params_02(
self, filenames: List[str] = ["function_out_params-02"]
) -> None:
self.assert_paths(
src=[("getenv", None)],
snk=[("system", 1)],
call_chains=[["main", "get_cmd"]],
filenames=filenames,
)
return
def test_function_out_params_03(
self, filenames: List[str] = ["function_out_params-03"]
) -> None:
self.test_function_out_params_02(filenames)
return
def test_function_out_params_04(
self, filenames: List[str] = ["function_out_params-04"]
) -> None:
self.test_function_out_params_02(filenames)
return
def test_function_out_params_05(
self, filenames: List[str] = ["function_out_params-05"]
) -> None:
self.test_function_out_params_02(filenames)
return
def test_function_out_params_06(
self, filenames: List[str] = ["function_out_params-06"]
) -> None:
self.assert_paths(
src=[("getenv", None)],
snk=[("system", 1)],
call_chains=[["main"]],
filenames=filenames,
)
return
def test_function_out_params_07(
self, filenames: List[str] = ["function_out_params-07"]
) -> None:
self.test_function_out_params_06(filenames)
return
+1 -1
View File
@@ -44,7 +44,7 @@ class TestNameMangling(TestSlicing):
self.assert_paths( self.assert_paths(
src=[("getenv", None)], src=[("getenv", None)],
snk=[("system", 1)], snk=[("system", 1)],
call_chains=[["my_func<int>", "main"]], call_chains=[["my_func<int32_t>", "main"]],
filenames=filenames, filenames=filenames,
) )
return return
+1 -1
View File
@@ -56,7 +56,7 @@ class TestPointerAnalysis(TestSlicing):
self.assert_paths( self.assert_paths(
src=[("getenv", None)], src=[("getenv", None)],
snk=[("memcpy", 3)], snk=[("memcpy", 3)],
call_chains=[["main", "modify_n"], ["main", "modify_n"]], call_chains=[["main", "modify_n"]],
filenames=filenames, filenames=filenames,
) )
return return