mirror of
https://github.com/cyber-defence-campus/mole
synced 2026-06-20 13:19:21 +00:00
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:
@@ -108,9 +108,13 @@ class FunctionHelper:
|
||||
data_var = bv.get_data_var_at(data_ref)
|
||||
if data_var is None:
|
||||
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
|
||||
name = data_var.type.name
|
||||
offset = data_ref - data_var.address
|
||||
for code_ref in bv.get_code_refs_for_type_field(name, offset):
|
||||
# Iterate all functions containing the code reference
|
||||
for ref_func in bv.get_functions_containing(code_ref.address):
|
||||
|
||||
+16
-16
@@ -14,11 +14,11 @@ class MediumLevelILCallFrame:
|
||||
|
||||
def __init__(self, func: bn.MediumLevelILFunction) -> None:
|
||||
self.func = func
|
||||
self.func_params: List[int] = []
|
||||
self.func_params: Set[int] = set()
|
||||
self.inst_stack: List[bn.MediumLevelILInstruction] = []
|
||||
self.last_inst: bn.MediumLevelILInstruction = None
|
||||
self.inst_graph: MediumLevelILInstructionGraph = MediumLevelILInstructionGraph()
|
||||
self.mem_def_insts: Set[bn.MediumLevelILInstruction] = set()
|
||||
self.mem_def_insts: List[bn.MediumLevelILInstruction] = []
|
||||
return
|
||||
|
||||
def __eq__(self, other: MediumLevelILCallFrame) -> bool:
|
||||
@@ -92,15 +92,14 @@ class MediumLevelILCallTracker:
|
||||
|
||||
def push_func(
|
||||
self,
|
||||
from_inst: bn.MediumLevelILInstruction,
|
||||
to_inst: bn.MediumLevelILInstruction,
|
||||
reverse: bool = False,
|
||||
) -> bool:
|
||||
"""
|
||||
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
|
||||
considered to be the caller (not the callee). The function returns True if in case of a
|
||||
recursion, False otherwise.
|
||||
the top of the call stack. Also, it updates the call graph. If `reverse` is True,
|
||||
`to_inst.function` is considered to be the caller (not the callee). The function returns
|
||||
True in case of recursion, False otherwise.
|
||||
"""
|
||||
# Get the return instruction's function
|
||||
func = to_inst.function
|
||||
@@ -128,10 +127,10 @@ class MediumLevelILCallTracker:
|
||||
self._call_graph.add_node(func)
|
||||
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
|
||||
parameter instructions that should be sliced further.
|
||||
This method pops the top call frame from the call stack and returns a set of parameter
|
||||
indices (`func_params`) that should be sliced further.
|
||||
"""
|
||||
if self._call_stack:
|
||||
# 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)
|
||||
# Return indices of parameters to be sliced further
|
||||
return old_call_frame.func_params
|
||||
return []
|
||||
return set()
|
||||
|
||||
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
|
||||
the call frame on the top of the call stack.
|
||||
"""
|
||||
if self._call_stack:
|
||||
self._call_stack[-1].mem_def_insts.add(inst)
|
||||
if self._call_stack and inst not in self._call_stack[-1].mem_def_insts:
|
||||
self._call_stack[-1].mem_def_insts.append(inst)
|
||||
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
|
||||
stack.
|
||||
This method adds the given parameter to the `func_params` set of the current call frame.
|
||||
`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:
|
||||
self._call_stack[-1].func_params.append(param_idx)
|
||||
self._call_stack[-1].func_params.add(param_idx)
|
||||
return
|
||||
|
||||
def print_call_stack(self) -> None:
|
||||
|
||||
+261
-140
@@ -6,7 +6,7 @@ from mole.common.helper.variable import VariableHelper
|
||||
from mole.common.log import log
|
||||
from mole.core.call import MediumLevelILCallTracker
|
||||
from mole.core.graph import MediumLevelILFunctionGraph, MediumLevelILInstructionGraph
|
||||
from typing import Callable, Dict
|
||||
from typing import Callable, Set
|
||||
import binaryninja as bn
|
||||
|
||||
|
||||
@@ -72,9 +72,7 @@ class MediumLevelILBackwardSlicer:
|
||||
for call_inst in direct_call_insts | indirect_call_insts:
|
||||
from_inst = call_inst
|
||||
to_inst = call_inst.params[param_idx - 1]
|
||||
recursion = self._call_tracker.push_func(
|
||||
from_inst, to_inst, reverse=True
|
||||
)
|
||||
recursion = self._call_tracker.push_func(to_inst, reverse=True)
|
||||
from_inst_info = InstructionHelper.get_inst_info(from_inst, False)
|
||||
if not recursion:
|
||||
log.debug(
|
||||
@@ -94,15 +92,18 @@ class MediumLevelILBackwardSlicer:
|
||||
self._tag,
|
||||
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
|
||||
|
||||
def _slice_backwards(
|
||||
self,
|
||||
inst: bn.MediumLevelILInstruction,
|
||||
self, inst: bn.MediumLevelILInstruction, call_params: Set[int] = set()
|
||||
) -> 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
|
||||
if self._cancelled():
|
||||
@@ -137,12 +138,11 @@ class MediumLevelILBackwardSlicer:
|
||||
segment = self._bv.get_segment_at(constant)
|
||||
if segment and segment.writable:
|
||||
# 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.ssa_memory_version,
|
||||
self._max_memory_slice_depth,
|
||||
)
|
||||
for mem_def_inst in mem_def_insts:
|
||||
):
|
||||
mem_def_inst_info = InstructionHelper.get_inst_info(
|
||||
mem_def_inst, False
|
||||
)
|
||||
@@ -162,14 +162,14 @@ class MediumLevelILBackwardSlicer:
|
||||
| bn.MediumLevelILTailcallUntypedSsa(params=params)
|
||||
):
|
||||
followed = False
|
||||
for param in params:
|
||||
for param_idx, param in enumerate(params, start=1):
|
||||
match param:
|
||||
case bn.MediumLevelILConstPtr(
|
||||
constant=constant
|
||||
) if constant == inst.constant:
|
||||
log.debug(
|
||||
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(
|
||||
mem_def_inst
|
||||
@@ -191,12 +191,11 @@ class MediumLevelILBackwardSlicer:
|
||||
case bn.MediumLevelILLoadSsa(src=load_src_inst, size=load_src_size):
|
||||
followed = False
|
||||
# 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.ssa_memory_version,
|
||||
self._max_memory_slice_depth,
|
||||
)
|
||||
for mem_def_inst in mem_def_insts:
|
||||
):
|
||||
mem_def_inst_info = InstructionHelper.get_inst_info(
|
||||
mem_def_inst, False
|
||||
)
|
||||
@@ -350,12 +349,11 @@ class MediumLevelILBackwardSlicer:
|
||||
):
|
||||
followed = False
|
||||
# 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.ssa_memory_version,
|
||||
self._max_memory_slice_depth,
|
||||
)
|
||||
for mem_def_inst in mem_def_insts:
|
||||
):
|
||||
mem_def_inst_info = InstructionHelper.get_inst_info(
|
||||
mem_def_inst, False
|
||||
)
|
||||
@@ -449,60 +447,23 @@ class MediumLevelILBackwardSlicer:
|
||||
)
|
||||
self._slice_backwards(load_src_inst)
|
||||
case (
|
||||
bn.MediumLevelILVarAliased()
|
||||
| bn.MediumLevelILVarAliasedField()
|
||||
| bn.MediumLevelILAddressOf()
|
||||
| bn.MediumLevelILAddressOfField()
|
||||
bn.MediumLevelILVarAliased(src=bn.SSAVariable(var=var))
|
||||
| bn.MediumLevelILVarAliasedField(src=bn.SSAVariable(var=var))
|
||||
| bn.MediumLevelILAddressOf(src=var)
|
||||
| bn.MediumLevelILAddressOfField(src=var)
|
||||
):
|
||||
# Get all assignment instructions of the form `var_x = &var_y` in the current
|
||||
# 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
|
||||
offset = getattr(inst, "offset", 0)
|
||||
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
|
||||
# version
|
||||
mem_def_insts = FunctionHelper.get_ssa_memory_definitions(
|
||||
for mem_def_inst in FunctionHelper.get_ssa_memory_definitions(
|
||||
inst.function,
|
||||
inst.ssa_memory_version,
|
||||
self._max_memory_slice_depth,
|
||||
)
|
||||
for mem_def_inst in mem_def_insts:
|
||||
):
|
||||
mem_def_inst_info = InstructionHelper.get_inst_info(
|
||||
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
|
||||
if self._call_tracker.is_in_current_mem_def_insts(mem_def_inst):
|
||||
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",
|
||||
)
|
||||
continue
|
||||
|
||||
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 (
|
||||
bn.MediumLevelILCallSsa(params=params)
|
||||
| bn.MediumLevelILCallUntypedSsa(params=params)
|
||||
| bn.MediumLevelILTailcallSsa(params=params)
|
||||
| bn.MediumLevelILTailcallUntypedSsa(params=params)
|
||||
):
|
||||
var_addr_ass_inst = dest_var_use_sites[mem_def_inst]
|
||||
var_addr_ass_inst_info = InstructionHelper.get_inst_info(
|
||||
var_addr_ass_inst, False
|
||||
)
|
||||
log.debug(
|
||||
self._tag,
|
||||
f"Follow call instruction '{mem_def_inst_info:s}' since it uses '{var_addr_ass_inst_info:s}'",
|
||||
)
|
||||
self._call_tracker.push_mem_def_inst(mem_def_inst)
|
||||
self._slice_backwards(mem_def_inst)
|
||||
# Find set of call parameters the slicer should follow
|
||||
call_params = set()
|
||||
for param_idx, param in enumerate(
|
||||
mem_def_inst.params, start=1
|
||||
):
|
||||
match param:
|
||||
# `param_var = &var`
|
||||
case bn.MediumLevelILVarSsa(var=param_var):
|
||||
# Get all assignments of the form `param_var = &var` in the
|
||||
# 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)
|
||||
# `¶m_var == var`
|
||||
case bn.MediumLevelILAddressOf(src=param_src_var):
|
||||
if param_src_var == var:
|
||||
call_params.add(param_idx)
|
||||
# `¶m_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 (
|
||||
bn.MediumLevelILVarSsa()
|
||||
| bn.MediumLevelILVarSsaField()
|
||||
@@ -560,83 +583,181 @@ class MediumLevelILBackwardSlicer:
|
||||
bn.MediumLevelILConstPtr(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)
|
||||
# Proceed slicing the parameters if we cannot go into the callee (callee is
|
||||
# not a valid function - e.g. external function)
|
||||
dest_symb = dest_func.symbol if dest_func else None
|
||||
# Slicer cannot go into the callee (proceed with function parameters)
|
||||
if (
|
||||
not dest_func
|
||||
or not dest_func.mlil
|
||||
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:
|
||||
self._slice_backwards(param)
|
||||
# Proceed slicing the callee's return instructions if we can go into the
|
||||
# callee (callee is a valid function)
|
||||
# Slicer can go into the callee
|
||||
else:
|
||||
# Get callee's SSA form
|
||||
dest_func = dest_func.mlil.ssa_form
|
||||
dest_symb = dest_func.source_function.symbol
|
||||
for dest_func_inst in dest_func.instructions:
|
||||
match dest_func_inst:
|
||||
case (
|
||||
bn.MediumLevelILRet()
|
||||
| bn.MediumLevelILTailcallSsa()
|
||||
# Iterate the callee's return instructions
|
||||
for ret_inst in FunctionHelper.get_mlil_return_insts(
|
||||
dest_func
|
||||
):
|
||||
ret_inst_info = InstructionHelper.get_inst_info(
|
||||
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
|
||||
if dest_symb.type in [
|
||||
bn.SymbolType.FunctionSymbol,
|
||||
bn.SymbolType.LibraryFunctionSymbol,
|
||||
]:
|
||||
from_inst = inst
|
||||
to_inst = dest_func_inst
|
||||
recursion = self._call_tracker.push_func(
|
||||
from_inst, to_inst
|
||||
mem_def_inst_info = (
|
||||
InstructionHelper.get_inst_info(
|
||||
mem_def_inst, False
|
||||
)
|
||||
to_inst_info = (
|
||||
InstructionHelper.get_inst_info(
|
||||
to_inst, False
|
||||
)
|
||||
)
|
||||
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
|
||||
)
|
||||
# Check if memory defining instruction was followed before
|
||||
if self._call_tracker.is_in_current_mem_def_insts(
|
||||
mem_def_inst
|
||||
):
|
||||
for param in inst.params:
|
||||
self._slice_backwards(param)
|
||||
log.debug(
|
||||
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
|
||||
case bn.MediumLevelILVarSsa():
|
||||
for param in inst.params:
|
||||
@@ -695,7 +816,7 @@ class MediumLevelILBackwardSlicer:
|
||||
This method backward slices the instruction `inst`.
|
||||
"""
|
||||
self._call_tracker = MediumLevelILCallTracker()
|
||||
self._call_tracker.push_func(None, inst, reverse=True)
|
||||
self._call_tracker.push_func(inst, reverse=True)
|
||||
deque(
|
||||
inst.ssa_form.traverse(lambda inst: self._slice_backwards(inst)),
|
||||
maxlen=0,
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
"Linux": "",
|
||||
"Windows": ""
|
||||
},
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"author": "Damian Pfammatter and Sergio Paganoni",
|
||||
"minimumbinaryninjaversion": 6455
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ include = ["mole*"]
|
||||
|
||||
[project]
|
||||
name = "mole"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
description = "A Binary Ninja plugin to identify interesting paths using static backward slicing"
|
||||
authors = [
|
||||
{name = "Damian Pfammatter"},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -44,7 +44,7 @@ class TestNameMangling(TestSlicing):
|
||||
self.assert_paths(
|
||||
src=[("getenv", None)],
|
||||
snk=[("system", 1)],
|
||||
call_chains=[["my_func<int>", "main"]],
|
||||
call_chains=[["my_func<int32_t>", "main"]],
|
||||
filenames=filenames,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestPointerAnalysis(TestSlicing):
|
||||
self.assert_paths(
|
||||
src=[("getenv", None)],
|
||||
snk=[("memcpy", 3)],
|
||||
call_chains=[["main", "modify_n"], ["main", "modify_n"]],
|
||||
call_chains=[["main", "modify_n"]],
|
||||
filenames=filenames,
|
||||
)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user