angr 9.2.162__cp310-abi3-win_amd64.whl → 9.2.164__cp310-abi3-win_amd64.whl

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.

Potentially problematic release.


This version of angr might be problematic. Click here for more details.

Files changed (44) hide show
  1. angr/__init__.py +1 -1
  2. angr/ailment/converter_vex.py +1 -1
  3. angr/ailment/expression.py +17 -1
  4. angr/analyses/cfg/cfg_base.py +17 -14
  5. angr/analyses/cfg/cfg_emulated.py +5 -1
  6. angr/analyses/cfg/cfg_fast.py +27 -4
  7. angr/analyses/cfg/indirect_jump_resolvers/arm_elf_fast.py +11 -1
  8. angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +194 -41
  9. angr/analyses/decompiler/ail_simplifier.py +19 -5
  10. angr/analyses/decompiler/callsite_maker.py +33 -17
  11. angr/analyses/decompiler/clinic.py +6 -6
  12. angr/analyses/decompiler/graph_region.py +19 -0
  13. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
  14. angr/analyses/decompiler/region_identifier.py +22 -1
  15. angr/analyses/decompiler/structuring/phoenix.py +72 -20
  16. angr/analyses/decompiler/structuring/recursive_structurer.py +3 -4
  17. angr/analyses/decompiler/structuring/structurer_nodes.py +3 -0
  18. angr/analyses/decompiler/utils.py +17 -5
  19. angr/analyses/fcp/fcp.py +11 -10
  20. angr/analyses/flirt/flirt_sig.py +5 -2
  21. angr/analyses/reaching_definitions/function_handler.py +1 -1
  22. angr/analyses/reaching_definitions/function_handler_library/stdio.py +7 -6
  23. angr/analyses/reaching_definitions/function_handler_library/stdlib.py +10 -4
  24. angr/analyses/reaching_definitions/function_handler_library/string.py +13 -2
  25. angr/analyses/reaching_definitions/function_handler_library/unistd.py +7 -0
  26. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -1
  27. angr/analyses/typehoon/typeconsts.py +3 -1
  28. angr/analyses/variable_recovery/engine_base.py +6 -10
  29. angr/blade.py +20 -15
  30. angr/engines/icicle.py +7 -2
  31. angr/knowledge_plugins/propagations/propagation_model.py +7 -0
  32. angr/project.py +5 -2
  33. angr/rustylib.pyd +0 -0
  34. angr/sim_type.py +18 -3
  35. angr/unicornlib.dll +0 -0
  36. angr/utils/constants.py +1 -1
  37. angr/utils/graph.py +1 -1
  38. angr/utils/vex.py +11 -0
  39. {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/METADATA +5 -5
  40. {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/RECORD +44 -43
  41. {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/WHEEL +0 -0
  42. {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/entry_points.txt +0 -0
  43. {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/licenses/LICENSE +0 -0
  44. {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/top_level.txt +0 -0
@@ -1216,6 +1216,7 @@ class Clinic(Analysis):
1216
1216
  ):
1217
1217
  # found a single successor - replace the last statement
1218
1218
  new_last_stmt = last_stmt.copy()
1219
+ assert isinstance(successors[0].addr, int)
1219
1220
  new_last_stmt.target = ailment.Expr.Const(None, None, successors[0].addr, last_stmt.target.bits)
1220
1221
  block.statements[-1] = new_last_stmt
1221
1222
 
@@ -1844,8 +1845,6 @@ class Clinic(Analysis):
1844
1845
  if v.offset in vr.stack_offset_typevars:
1845
1846
  tv = vr.stack_offset_typevars[v.offset]
1846
1847
  tv_max_sizes[tv] = s
1847
- # clean up existing types for this function
1848
- var_manager.remove_types()
1849
1848
  # TODO: Type inference for global variables
1850
1849
  # run type inference
1851
1850
  if self._must_struct:
@@ -2158,9 +2157,9 @@ class Clinic(Analysis):
2158
2157
  }
2159
2158
  else:
2160
2159
  # global variable?
2161
- global_vars = global_variables.get_global_variables(expr.value)
2160
+ global_vars = global_variables.get_global_variables(expr.value_int)
2162
2161
  # detect if there is a related symbol
2163
- if not global_vars and self.project.loader.find_object_containing(expr.value):
2162
+ if not global_vars and self.project.loader.find_object_containing(expr.value_int):
2164
2163
  symbol = self.project.loader.find_symbol(expr.value)
2165
2164
  if symbol is not None:
2166
2165
  # Create a new global variable if there isn't one already
@@ -3041,12 +3040,12 @@ class Clinic(Analysis):
3041
3040
  op0, op1 = addr.operands
3042
3041
  if (
3043
3042
  isinstance(op0, ailment.Expr.Const)
3044
- and self.project.loader.find_object_containing(op0.value) is not None
3043
+ and self.project.loader.find_object_containing(op0.value_int) is not None
3045
3044
  ):
3046
3045
  return op0, op1
3047
3046
  if (
3048
3047
  isinstance(op1, ailment.Expr.Const)
3049
- and self.project.loader.find_object_containing(op1.value) is not None
3048
+ and self.project.loader.find_object_containing(op1.value_int) is not None
3050
3049
  ):
3051
3050
  return op1, op0
3052
3051
  return op0, op1 # best-effort guess
@@ -3279,6 +3278,7 @@ class Clinic(Analysis):
3279
3278
  )
3280
3279
  ):
3281
3280
  # found it!
3281
+ assert self.project.arch.sp_offset is not None
3282
3282
  alloca_node = node
3283
3283
  sp_equal_to = ailment.Expr.BinaryOp(
3284
3284
  None,
@@ -271,6 +271,13 @@ class GraphRegion:
271
271
  else:
272
272
  replace_with_graph_with_successors = replace_with.graph_with_successors
273
273
 
274
+ # if complete_successors is True for RegionIdentifier, replace_with.graph_with_successors may include nodes
275
+ # and edges that are *only* reachable from immediate successors. we will want to remove these nodes and edges,
276
+ # otherwise we may end up structuring the same region twice!
277
+ replace_with_graph_with_successors = self._cleanup_graph_with_successors(
278
+ replace_with.graph, replace_with_graph_with_successors
279
+ )
280
+
274
281
  self._replace_node_in_graph_with_subgraph(
275
282
  self.graph,
276
283
  self.successors,
@@ -289,6 +296,18 @@ class GraphRegion:
289
296
  replace_with.head,
290
297
  )
291
298
 
299
+ @staticmethod
300
+ def _cleanup_graph_with_successors(
301
+ graph: networkx.DiGraph, graph_with_successors: networkx.DiGraph
302
+ ) -> networkx.DiGraph:
303
+ expected_nodes = set(graph)
304
+ for n in list(expected_nodes):
305
+ for succ in graph_with_successors.successors(n):
306
+ expected_nodes.add(succ)
307
+ if all(n in expected_nodes for n in graph_with_successors):
308
+ return graph_with_successors
309
+ return graph_with_successors.subgraph(expected_nodes).to_directed()
310
+
292
311
  @staticmethod
293
312
  def _replace_node_in_graph(graph: networkx.DiGraph, node, replace_with, removed_edges: set):
294
313
  in_edges = [(src, dst) for src, dst in graph.in_edges(node) if (src, dst) not in removed_edges]
@@ -60,7 +60,7 @@ class DeadblockRemover(OptimizationPass):
60
60
  blk
61
61
  for blk in self._graph.nodes()
62
62
  if (blk.addr != self._func.addr and self._graph.in_degree(blk) == 0)
63
- or claripy.is_false(cond_proc.reaching_conditions[blk])
63
+ or claripy.is_false(cond_proc.reaching_conditions.get(blk, claripy.true()))
64
64
  }
65
65
 
66
66
  # fix up predecessors
@@ -99,7 +99,7 @@ class RegionIdentifier(Analysis):
99
99
 
100
100
  def _analyze(self):
101
101
  # make a copy of the graph
102
- graph = networkx.DiGraph(self._graph)
102
+ graph = self._pick_one_connected_component(self._graph, as_copy=True)
103
103
 
104
104
  # preprocess: make it a super graph
105
105
  self._make_supergraph(graph)
@@ -113,6 +113,27 @@ class RegionIdentifier(Analysis):
113
113
  # make regions into block address lists
114
114
  self.regions_by_block_addrs = self._make_regions_by_block_addrs()
115
115
 
116
+ def _pick_one_connected_component(self, digraph: networkx.DiGraph, as_copy: bool = False) -> networkx.DiGraph:
117
+ g = networkx.Graph(digraph)
118
+ components = list(networkx.connected_components(g))
119
+ if len(components) <= 1:
120
+ return networkx.DiGraph(digraph) if as_copy else digraph
121
+
122
+ the_component = None
123
+ largest_component = None
124
+ for component in components:
125
+ if largest_component is None or len(component) > len(largest_component):
126
+ largest_component = component
127
+ if any((block.addr, block.idx) == self.entry_node_addr for block in component):
128
+ the_component = component
129
+ break
130
+
131
+ if the_component is None:
132
+ the_component = largest_component
133
+
134
+ assert the_component is not None
135
+ return digraph.subgraph(the_component).to_directed()
136
+
116
137
  @staticmethod
117
138
  def _compute_node_order(graph: networkx.DiGraph) -> dict[Any, tuple[int, int]]:
118
139
  sorted_nodes = GraphUtils.quasi_topological_sort_nodes(graph)
@@ -13,7 +13,7 @@ from angr.ailment.block import Block
13
13
  from angr.ailment.statement import Statement, ConditionalJump, Jump, Label, Return
14
14
  from angr.ailment.expression import Const, UnaryOp, MultiStatementExpression, BinaryOp
15
15
 
16
- from angr.utils.graph import GraphUtils
16
+ from angr.utils.graph import GraphUtils, Dominators, compute_dominance_frontier
17
17
  from angr.utils.ail import is_phi_assignment, is_head_controlled_loop_block
18
18
  from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
19
19
  from angr.utils.constants import SWITCH_MISSING_DEFAULT_NODE_ADDR
@@ -669,7 +669,7 @@ class PhoenixStructurer(StructurerBase):
669
669
  continue_node = loop_head
670
670
 
671
671
  is_while, result_while = self._refine_cyclic_is_while_loop(graph, fullgraph, loop_head, head_succs)
672
- is_dowhile, result_dowhile = self._refine_cyclic_is_dowhile_loop(graph, fullgraph, loop_head, head_succs)
672
+ is_dowhile, result_dowhile = self._refine_cyclic_is_dowhile_loop(graph, fullgraph, loop_head)
673
673
 
674
674
  continue_edges: list[tuple[BaseNode, BaseNode]] = []
675
675
  outgoing_edges: list = []
@@ -702,22 +702,12 @@ class PhoenixStructurer(StructurerBase):
702
702
 
703
703
  if loop_type is None:
704
704
  # natural loop. select *any* exit edge to determine the successor
705
- # well actually, to maintain determinism, we select the successor with the highest address
706
- successor_candidates = set()
707
- for node in networkx.descendants(graph, loop_head):
708
- for succ in fullgraph.successors(node):
709
- if succ not in graph:
710
- successor_candidates.add(succ)
711
- if loop_head is succ:
712
- continue_edges.append((node, succ))
713
- if successor_candidates:
714
- successor_candidates = sorted(successor_candidates, key=lambda x: x.addr)
715
- successor = successor_candidates[0]
716
- # virtualize all other edges
717
- for succ in successor_candidates:
718
- for pred in fullgraph.predecessors(succ):
719
- if pred in graph:
720
- outgoing_edges.append((pred, succ))
705
+ is_natural, result_natural = self._refine_cyclic_make_natural_loop(graph, fullgraph, loop_head)
706
+ if not is_natural:
707
+ # cannot refine this loop
708
+ return False
709
+ assert result_natural is not None
710
+ continue_edges, outgoing_edges, successor = result_natural
721
711
 
722
712
  if outgoing_edges:
723
713
  # if there is a single successor, we convert all out-going edges into breaks;
@@ -963,8 +953,8 @@ class PhoenixStructurer(StructurerBase):
963
953
  return True, (continue_edges, outgoing_edges, loop_head, successor)
964
954
  return False, None
965
955
 
966
- def _refine_cyclic_is_dowhile_loop( # pylint:disable=unused-argument
967
- self, graph, fullgraph, loop_head, head_succs
956
+ def _refine_cyclic_is_dowhile_loop(
957
+ self, graph, fullgraph, loop_head
968
958
  ) -> tuple[bool, tuple[list, list, BaseNode, BaseNode] | None]:
969
959
  # check if there is an out-going edge from the loop tail
970
960
  head_preds = list(fullgraph.predecessors(loop_head))
@@ -996,6 +986,64 @@ class PhoenixStructurer(StructurerBase):
996
986
  return True, (continue_edges, outgoing_edges, continue_node, successor)
997
987
  return False, None
998
988
 
989
+ def _refine_cyclic_make_natural_loop(
990
+ self, graph, fullgraph, loop_head
991
+ ) -> tuple[bool, tuple[list, list, Any] | None]:
992
+ continue_edges = []
993
+ outgoing_edges = []
994
+
995
+ # find dominance frontier
996
+ doms = Dominators(fullgraph, self._region.head)
997
+ dom_frontiers = compute_dominance_frontier(fullgraph, doms.dom)
998
+
999
+ if loop_head not in dom_frontiers:
1000
+ return False, None
1001
+ dom_frontier = dom_frontiers[loop_head]
1002
+
1003
+ # now this is a little complex
1004
+ dom_frontier = {node for node in dom_frontier if node is not loop_head}
1005
+ if len(dom_frontier) == 0:
1006
+ # the dominance frontier is empty (the loop head dominates all nodes in the full graph). however, this does
1007
+ # not mean that the loop head must dominate all the nodes, because we only have a limited view of the full
1008
+ # graph (e.g., some predecessors of the successor may not be in this full graph). as such, successors are
1009
+ # the ones that are in the fullgraph but not in the graph.
1010
+ successor_candidates = set()
1011
+ for node in networkx.descendants(graph, loop_head):
1012
+ for succ in fullgraph.successors(node):
1013
+ if succ not in graph:
1014
+ successor_candidates.add(succ)
1015
+ if loop_head is succ:
1016
+ continue_edges.append((node, succ))
1017
+
1018
+ else:
1019
+ # this loop has a single successor
1020
+ successor_candidates = dom_frontier
1021
+ # traverse the loop body to find all continue edges
1022
+ tmp_graph = networkx.DiGraph(graph)
1023
+ tmp_graph.remove_nodes_from(successor_candidates)
1024
+ for node in networkx.descendants(tmp_graph, loop_head):
1025
+ if tmp_graph.has_edge(node, loop_head):
1026
+ continue_edges.append((node, loop_head))
1027
+
1028
+ if len(successor_candidates) == 0:
1029
+ successor = None
1030
+ else:
1031
+ # one or multiple successors; try to pick a successor in graph, and prioritize the one with the lowest
1032
+ # address
1033
+ successor_candidates_in_graph = {nn for nn in successor_candidates if nn in graph}
1034
+ if successor_candidates_in_graph:
1035
+ # pick the one with the lowest address
1036
+ successor = next(iter(sorted(successor_candidates_in_graph, key=lambda x: x.addr)))
1037
+ else:
1038
+ successor = next(iter(sorted(successor_candidates, key=lambda x: x.addr)))
1039
+ # mark all edges as outgoing edges so they will be virtualized if they don't lead to the successor
1040
+ for node in successor_candidates:
1041
+ for pred in fullgraph.predecessors(node):
1042
+ if networkx.has_path(doms.dom, loop_head, pred):
1043
+ outgoing_edges.append((pred, node))
1044
+
1045
+ return True, (continue_edges, outgoing_edges, successor)
1046
+
999
1047
  def _analyze_acyclic(self) -> bool:
1000
1048
  # match against known schemas
1001
1049
  l.debug("Matching acyclic schemas for region %r.", self._region)
@@ -1219,6 +1267,10 @@ class PhoenixStructurer(StructurerBase):
1219
1267
  node_a = next(iter(nn for nn in graph.nodes if nn.addr == target), None)
1220
1268
  if node_a is None:
1221
1269
  return False
1270
+ if node_a is self._region.head:
1271
+ # avoid structuring if node_a is the region head; this means the current node is a duplicated switch-case
1272
+ # head (instead of the original one), which is not something we want to structure
1273
+ return False
1222
1274
 
1223
1275
  # the default case
1224
1276
  node_b_addr = next(iter(t for t in successor_addrs if t != target), None)
@@ -3,8 +3,6 @@ import itertools
3
3
  from typing import TYPE_CHECKING
4
4
  import logging
5
5
 
6
- import networkx
7
-
8
6
  from angr.analyses import Analysis, register_analysis
9
7
  from angr.analyses.decompiler.condition_processor import ConditionProcessor
10
8
  from angr.analyses.decompiler.graph_region import GraphRegion
@@ -12,6 +10,7 @@ from angr.analyses.decompiler.jumptable_entry_condition_rewriter import JumpTabl
12
10
  from angr.analyses.decompiler.empty_node_remover import EmptyNodeRemover
13
11
  from angr.analyses.decompiler.jump_target_collector import JumpTargetCollector
14
12
  from angr.analyses.decompiler.redundant_label_remover import RedundantLabelRemover
13
+ from angr.utils.graph import GraphUtils
15
14
  from .structurer_nodes import BaseNode
16
15
  from .structurer_base import StructurerBase
17
16
  from .dream import DreamStructurer
@@ -61,7 +60,7 @@ class RecursiveStructurer(Analysis):
61
60
  current_region = stack[-1]
62
61
 
63
62
  has_region = False
64
- for node in networkx.dfs_postorder_nodes(current_region.graph, current_region.head):
63
+ for node in GraphUtils.dfs_postorder_nodes_deterministic(current_region.graph, current_region.head):
65
64
  subnodes = []
66
65
  if type(node) is GraphRegion:
67
66
  if node.cyclic:
@@ -177,7 +176,7 @@ class RecursiveStructurer(Analysis):
177
176
  for node in region.graph.nodes:
178
177
  if not isinstance(node, BaseNode):
179
178
  continue
180
- if node.addr == self.function.addr:
179
+ if self.function is not None and node.addr == self.function.addr:
181
180
  return node
182
181
  if min_node is None or (min_node.addr is not None and node.addr is not None and min_node.addr < node.addr):
183
182
  min_node = node
@@ -392,6 +392,9 @@ class IncompleteSwitchCaseNode(BaseNode):
392
392
  self.head = head
393
393
  self.cases: list = cases
394
394
 
395
+ def __repr__(self):
396
+ return f"<IncompleteSwitchCase {self.addr:#x} with {len(self.cases)} cases>"
397
+
395
398
 
396
399
  #
397
400
  # The following classes are custom AIL statements (not nodes, unfortunately)
@@ -158,10 +158,14 @@ def switch_extract_cmp_bounds(
158
158
  return None
159
159
 
160
160
  # TODO: Add more operations
161
- if isinstance(last_stmt.condition, ailment.Expr.BinaryOp) and last_stmt.condition.op == "CmpLE":
161
+ if isinstance(last_stmt.condition, ailment.Expr.BinaryOp) and last_stmt.condition.op in {"CmpLE", "CmpLT"}:
162
162
  if not isinstance(last_stmt.condition.operands[1], ailment.Expr.Const):
163
163
  return None
164
- cmp_ub = last_stmt.condition.operands[1].value
164
+ cmp_ub = (
165
+ last_stmt.condition.operands[1].value
166
+ if last_stmt.condition.op == "CmpLE"
167
+ else last_stmt.condition.operands[1].value - 1
168
+ )
165
169
  cmp_lb = 0
166
170
  cmp = last_stmt.condition.operands[0]
167
171
  if (
@@ -250,6 +254,10 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
250
254
  size=4, endness=Iend_LE) + 0x4530e4<32>))
251
255
  )
252
256
 
257
+ Another example:
258
+
259
+ Load(addr=(((vvar_9{reg 36} & 0x3<32>) * 0x4<32>) + 0x42cd28<32>), size=4, endness=Iend_LE)
260
+
253
261
  :param last_stmt: The last statement of the switch-case header node.
254
262
  :return: A tuple of (index expression, lower bound, upper bound), or None
255
263
  """
@@ -269,16 +277,20 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
269
277
  continue
270
278
  if isinstance(target, ailment.Expr.BinaryOp) and target.op == "Add":
271
279
  if isinstance(target.operands[0], ailment.Expr.Const) and isinstance(target.operands[1], ailment.Expr.Load):
272
- jump_addr_offset = target.operands[0]
280
+ jump_addr_offset = target.operands[0].value
273
281
  jumptable_load_addr = target.operands[1].addr
274
282
  break
275
283
  if isinstance(target.operands[1], ailment.Expr.Const) and isinstance(target.operands[0], ailment.Expr.Load):
276
- jump_addr_offset = target.operands[1]
284
+ jump_addr_offset = target.operands[1].value
277
285
  jumptable_load_addr = target.operands[0].addr
278
286
  break
279
287
  return None
280
288
  if isinstance(target, ailment.Expr.Const):
281
289
  return None
290
+ if isinstance(target, ailment.Expr.Load):
291
+ jumptable_load_addr = target.addr
292
+ jump_addr_offset = 0
293
+ break
282
294
  break
283
295
 
284
296
  if jump_addr_offset is None or jumptable_load_addr is None:
@@ -655,7 +667,7 @@ def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailm
655
667
 
656
668
  def _find_node_in_graph(node: ailment.Block, graph: networkx.DiGraph) -> ailment.Block | None:
657
669
  for bb in graph:
658
- if bb.addr == node.addr and bb.idx == node.idx:
670
+ if isinstance(bb, ailment.Block) and bb.addr == node.addr and bb.idx == node.idx:
659
671
  return bb
660
672
  return None
661
673
 
angr/analyses/fcp/fcp.py CHANGED
@@ -343,16 +343,17 @@ class FastConstantPropagation(Analysis):
343
343
  engine.process(state, block=block)
344
344
 
345
345
  # if the node ends with a function call, call _handle_function
346
- succs = list(func_graph_with_callees.successors(node))
347
- if any(isinstance(succ, (Function, HookNode)) for succ in succs):
348
- callee = next(succ for succ in succs if isinstance(succ, (Function, HookNode)))
349
- if isinstance(callee, HookNode):
350
- # attempt to convert it into a function
351
- if self.kb.functions.contains_addr(callee.addr):
352
- callee = self.kb.functions.get_by_addr(callee.addr)
353
- else:
354
- callee = None
355
- state = self._handle_function(state, callee)
346
+ if node in func_graph_with_callees:
347
+ succs = list(func_graph_with_callees.successors(node))
348
+ if any(isinstance(succ, (Function, HookNode)) for succ in succs):
349
+ callee = next(succ for succ in succs if isinstance(succ, (Function, HookNode)))
350
+ if isinstance(callee, HookNode):
351
+ # attempt to convert it into a function
352
+ if self.kb.functions.contains_addr(callee.addr):
353
+ callee = self.kb.functions.get_by_addr(callee.addr)
354
+ else:
355
+ callee = None
356
+ state = self._handle_function(state, callee)
356
357
 
357
358
  states[node] = state
358
359
 
@@ -146,7 +146,7 @@ class FlirtSignatureParsed:
146
146
  name_lst = []
147
147
  name_end = False # in case the function name is too long...
148
148
  for _ in range(1024): # max length of a function name
149
- if next_byte < 0x20:
149
+ if next_byte < 0x20 or next_byte >= 0x80:
150
150
  name_end = True
151
151
  break
152
152
  name_lst.append(next_byte)
@@ -347,7 +347,10 @@ class FlirtSignatureParsed:
347
347
  # is it compressed?
348
348
  if obj.features & FlirtFeatureFlag.FEATURE_COMPRESSED:
349
349
  data = file_obj.read()
350
- decompressed = BytesIO(zlib.decompress(data))
350
+ try:
351
+ decompressed = BytesIO(zlib.decompress(data))
352
+ except zlib.error as ex:
353
+ raise FlirtSignatureError(f"Failed to decompress FLIRT signature: {ex}") from ex
351
354
  file_obj = decompressed
352
355
 
353
356
  root = obj.parse_tree(file_obj, root=True)
@@ -575,7 +575,7 @@ class FunctionHandler:
575
575
  sub_rda = state.analysis.project.analyses.ReachingDefinitions(
576
576
  data.function,
577
577
  observe_all=state.analysis._observe_all,
578
- observation_points=list(state.analysis._observation_points or []).extend(return_observation_points),
578
+ observation_points=list(state.analysis._observation_points or []) + return_observation_points,
579
579
  observe_callback=state.analysis._observe_callback,
580
580
  dep_graph=state.dep_graph,
581
581
  function_handler=self,
@@ -156,14 +156,14 @@ class LibcStdioHandlers(FunctionHandler):
156
156
  @FunctionCallDataUnwrapped.decorate
157
157
  def handle_impl_fread(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
158
158
  size = state.get_concrete_value(data.args_atoms[1]) or 1
159
- nmemb = state.get_concrete_value(data.args_atoms[1]) or 2
159
+ nmemb = state.get_concrete_value(data.args_atoms[2]) or 2
160
160
  dst_atom = state.deref(data.args_atoms[0], size * nmemb)
161
161
  data.depends(dst_atom, StdinAtom("fread", size * nmemb))
162
162
 
163
163
  @FunctionCallDataUnwrapped.decorate
164
164
  def handle_impl_fwrite(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
165
165
  size = state.get_concrete_value(data.args_atoms[1]) or 1
166
- nmemb = state.get_concrete_value(data.args_atoms[1]) or 2
166
+ nmemb = state.get_concrete_value(data.args_atoms[2]) or 2
167
167
  src_atom = state.deref(data.args_atoms[0], size * nmemb)
168
168
  data.depends(StdoutAtom("fwrite", size * nmemb), src_atom, value=state.get_values(src_atom))
169
169
 
@@ -206,8 +206,8 @@ def handle_printf(
206
206
  elif fmt == "%u":
207
207
  buf_atoms = atom
208
208
  buf_data = state.get_concrete_value(buf_atoms)
209
- if buf_data is not None:
210
- buf_data = str(buf_data).encode()
209
+ buf_data = str(buf_data).encode() if buf_data else b"0"
210
+
211
211
  elif fmt == "%d":
212
212
  buf_atoms = atom
213
213
  buf_data = state.get_concrete_value(buf_atoms)
@@ -215,11 +215,12 @@ def handle_printf(
215
215
  if buf_data >= 2**31:
216
216
  buf_data -= 2**32
217
217
  buf_data = str(buf_data).encode()
218
+ else:
219
+ buf_data = b"0"
218
220
  elif fmt == "%c":
219
221
  buf_atoms = atom
220
222
  buf_data = state.get_concrete_value(atom)
221
- if buf_data is not None:
222
- buf_data = chr(buf_data).encode()
223
+ buf_data = chr(buf_data).encode() if buf_data else b"0"
223
224
  else:
224
225
  _l.warning("Unimplemented printf format string %s", fmt)
225
226
  buf_atoms = set()
@@ -64,21 +64,25 @@ class LibcStdlibHandlers(FunctionHandler):
64
64
  buf_value = int(buf_value.decode().strip("\0"))
65
65
  except ValueError:
66
66
  buf_value = 0
67
+ else:
68
+ buf_value = 0 # Make the value concrete if it cannot be parsed
67
69
  data.depends(data.ret_atoms, buf_atoms, value=buf_value)
68
70
 
69
71
  @FunctionCallDataUnwrapped.decorate
70
72
  def handle_impl_malloc(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
71
73
  malloc_size = state.get_concrete_value(data.args_atoms[0]) or 48
72
74
  heap_ptr = state.heap_allocator.allocate(malloc_size)
73
- data.depends(data.ret_atoms, value=state.heap_address(heap_ptr))
75
+ heap_addr = state.heap_address(heap_ptr)
76
+ data.depends(data.ret_atoms, value=heap_addr)
74
77
 
75
78
  @FunctionCallDataUnwrapped.decorate
76
79
  def handle_impl_calloc(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
77
80
  nmemb = state.get_concrete_value(data.args_atoms[0]) or 48
78
81
  size = state.get_concrete_value(data.args_atoms[1]) or 1
79
- heap_ptr = state.heap_address(state.heap_allocator.allocate(nmemb * size))
80
- data.depends(state.deref(heap_ptr, nmemb * size), value=0)
81
- data.depends(data.ret_atoms, value=heap_ptr)
82
+ heap_ptr = state.heap_allocator.allocate(nmemb * size)
83
+ heap_addr = state.heap_address(heap_ptr)
84
+ data.depends(state.deref(heap_addr, nmemb * size), value=0)
85
+ data.depends(data.ret_atoms, value=heap_addr)
82
86
 
83
87
  @FunctionCallDataUnwrapped.decorate
84
88
  def handle_impl_getenv(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
@@ -156,6 +160,8 @@ class LibcStdlibHandlers(FunctionHandler):
156
160
  def handle_impl_execve(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
157
161
  argv_value = state.get_one_value(data.args_atoms[1])
158
162
  if argv_value is None:
163
+ # Model execve with unknown argv - still has system effects
164
+ data.depends(SystemAtom(1), data.args_atoms[0], value=state.get_values(data.args_atoms[0]))
159
165
  return
160
166
 
161
167
  nonce = random.randint(1, 999999999)
@@ -30,7 +30,7 @@ class LibcStringHandlers(FunctionHandler):
30
30
  dest_value = src0_value.concat(MultiValues(top_val))
31
31
  dest_atom = state.deref(data.args_atoms[0], len(dest_value) // 8, endness=archinfo.Endness.BE)
32
32
  else:
33
- dest_value = None
33
+ dest_value = state.top(state.arch.bits)
34
34
  dest_atom = src0_atom
35
35
  if src0_atom is not None and src1_atom is not None:
36
36
  data.depends(dest_atom, src0_atom, src1_atom, value=dest_value)
@@ -109,7 +109,8 @@ class LibcStringHandlers(FunctionHandler):
109
109
  src_str = state.get_values(src_atom)
110
110
  malloc_size = len(src_str) // 8 if src_str is not None else 1
111
111
  heap_ptr = state.heap_allocator.allocate(malloc_size)
112
- dst_atom = state.deref(heap_ptr, malloc_size)
112
+ heap_addr = state.heap_address(heap_ptr)
113
+ dst_atom = state.deref(heap_addr, malloc_size)
113
114
  data.depends(dst_atom, src_atom, value=src_str)
114
115
  data.depends(data.ret_atoms, data.args_atoms[0], value=state.get_values(data.args_atoms[0]))
115
116
 
@@ -121,6 +122,16 @@ class LibcStringHandlers(FunctionHandler):
121
122
  dst_atom = state.deref(data.args_atoms[0], size)
122
123
  if src_atom is not None:
123
124
  data.depends(dst_atom, src_atom, value=state.get_values(src_atom))
125
+ else:
126
+ # When size is unknown, use default size and create TOP value
127
+ default_size = state.arch.bytes
128
+ src_atom = state.deref(data.args_atoms[1], default_size)
129
+ dst_atom = state.deref(data.args_atoms[0], default_size)
130
+ if src_atom is not None:
131
+ top_val = state.top(default_size * 8)
132
+ for defn in state.get_definitions(src_atom):
133
+ top_val = state.annotate_with_def(top_val, defn)
134
+ data.depends(dst_atom, src_atom, value=MultiValues(top_val))
124
135
  data.depends(data.ret_atoms, data.args_atoms[0], value=state.get_values(data.args_atoms[0]))
125
136
 
126
137
  @FunctionCallDataUnwrapped.decorate
@@ -32,6 +32,9 @@ class LibcUnistdHandlers(FunctionHandler):
32
32
  buf_data = state.top(size * 8) if size is not None else state.top(state.arch.bits)
33
33
 
34
34
  data.depends(dst_atom, fd_atom, value=buf_data)
35
+ # Model return value - number of bytes read (could be 0 to size, or -1 for error)
36
+ ret_val = state.top(state.arch.bits) # Unknown number of bytes read
37
+ data.depends(data.ret_atoms, fd_atom, value=ret_val)
35
38
 
36
39
  handle_impl_recv = handle_impl_recvfrom = handle_impl_read
37
40
 
@@ -41,4 +44,8 @@ class LibcUnistdHandlers(FunctionHandler):
41
44
  src_atom = state.deref(data.args_atoms[1], size)
42
45
  data.depends(StdoutAtom(data.function.name, size), src_atom, value=state.get_values(src_atom))
43
46
 
47
+ # Model return value - number of bytes written (could be 0 to size, or -1 for error)
48
+ ret_val = state.top(state.arch.bits) # Unknown number of bytes written
49
+ data.depends(data.ret_atoms, value=ret_val)
50
+
44
51
  handle_impl_send = handle_impl_write
@@ -37,7 +37,8 @@ class RegVVarPredicate:
37
37
  if cc is not None:
38
38
  reg_list = cc.CALLER_SAVED_REGS
39
39
  if isinstance(cc.RETURN_VAL, SimRegArg):
40
- reg_list.append(cc.RETURN_VAL.reg_name)
40
+ # do not update reg_list directly, otherwise you may update cc.CALLER_SAVED_REGS!
41
+ reg_list = [*reg_list, cc.RETURN_VAL.reg_name]
41
42
  return {self.arch.registers[reg_name][0] for reg_name in reg_list}
42
43
  log.warning("Cannot determine registers that are clobbered by call statement %r.", stmt)
43
44
  return set()
@@ -245,7 +245,9 @@ class Struct(TypeConstant):
245
245
  if not self.fields:
246
246
  return 0
247
247
  max_field_off = max(self.fields.keys())
248
- return max_field_off + self.fields[max_field_off].size
248
+ return max_field_off + (
249
+ self.fields[max_field_off].size if not isinstance(self.fields[max_field_off], BottomType) else 1
250
+ )
249
251
 
250
252
  @memoize
251
253
  def __repr__(self, memo=None):
@@ -142,6 +142,8 @@ class SimEngineVRBase(
142
142
  ) -> list[tuple[SimVariable, int]]:
143
143
  data = richr_addr.data
144
144
 
145
+ variable: SimVariable | None = None
146
+
145
147
  if self.state.is_stack_address(data):
146
148
  # this is a stack address
147
149
  # extract stack offset
@@ -157,7 +159,6 @@ class SimEngineVRBase(
157
159
  for candidate, offset in var_candidates:
158
160
  if isinstance(candidate, SimStackVariable) and candidate.offset == stack_offset:
159
161
  existing_vars.append((candidate, offset))
160
- variable = None
161
162
  if existing_vars:
162
163
  variable, _ = existing_vars[0]
163
164
 
@@ -179,12 +180,7 @@ class SimEngineVRBase(
179
180
  existing_vars.append((var, var_stack_offset))
180
181
 
181
182
  if not existing_vars:
182
- existing_vars = [
183
- (v, 0)
184
- for v in self.state.variable_manager[self.func_addr].find_variables_by_stack_offset(
185
- stack_offset
186
- )
187
- ]
183
+ existing_vars = [(v, 0) for v in variable_manager.find_variables_by_stack_offset(stack_offset)]
188
184
 
189
185
  if not existing_vars:
190
186
  # no variables exist
@@ -193,10 +189,10 @@ class SimEngineVRBase(
193
189
  stack_offset,
194
190
  lea_size,
195
191
  base="bp",
196
- ident=self.state.variable_manager[self.func_addr].next_variable_ident("stack"),
192
+ ident=variable_manager.next_variable_ident("stack"),
197
193
  region=self.func_addr,
198
194
  )
199
- self.state.variable_manager[self.func_addr].add_variable("stack", stack_offset, variable)
195
+ variable_manager.add_variable("stack", stack_offset, variable)
200
196
  l.debug("Identified a new stack variable %s at %#x.", variable, self.ins_addr)
201
197
  existing_vars.append((variable, 0))
202
198
 
@@ -563,7 +559,7 @@ class SimEngineVRBase(
563
559
 
564
560
  if isinstance(stack_offset, int):
565
561
  expr = data.data
566
- if isinstance(expr, claripy.ast.Bits) and expr.size() > 1024:
562
+ if isinstance(expr, claripy.ast.BV) and expr.size() > 1024:
567
563
  # we don't write more than 256 bytes to the stack at a time for performance reasons
568
564
  expr = expr[expr.size() - 1 : expr.size() - 1024]
569
565
  expr = self.state.annotate_with_variables(expr, [(variable_offset, variable)])