angr 9.2.163__cp310-abi3-macosx_11_0_arm64.whl → 9.2.165__cp310-abi3-macosx_11_0_arm64.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 (42) hide show
  1. angr/__init__.py +1 -1
  2. angr/ailment/converter_vex.py +1 -1
  3. angr/ailment/expression.py +5 -1
  4. angr/analyses/analysis.py +27 -4
  5. angr/analyses/cfg/cfg_base.py +16 -13
  6. angr/analyses/cfg/cfg_emulated.py +5 -1
  7. angr/analyses/cfg/cfg_fast.py +43 -5
  8. angr/analyses/cfg/indirect_jump_resolvers/arm_elf_fast.py +11 -1
  9. angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +194 -41
  10. angr/analyses/decompiler/ail_simplifier.py +19 -5
  11. angr/analyses/decompiler/callsite_maker.py +33 -17
  12. angr/analyses/decompiler/condition_processor.py +9 -8
  13. angr/analyses/decompiler/graph_region.py +19 -0
  14. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
  15. angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
  16. angr/analyses/decompiler/peephole_optimizations/inlined_memcpy.py +78 -0
  17. angr/analyses/decompiler/peephole_optimizations/inlined_strcpy.py +67 -10
  18. angr/analyses/decompiler/peephole_optimizations/inlined_strcpy_consolidation.py +10 -13
  19. angr/analyses/decompiler/region_identifier.py +22 -1
  20. angr/analyses/decompiler/structuring/phoenix.py +72 -20
  21. angr/analyses/decompiler/structuring/recursive_structurer.py +3 -4
  22. angr/analyses/decompiler/structuring/structurer_nodes.py +3 -0
  23. angr/analyses/decompiler/utils.py +17 -5
  24. angr/analyses/deobfuscator/string_obf_finder.py +130 -32
  25. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -1
  26. angr/analyses/typehoon/typeconsts.py +3 -1
  27. angr/blade.py +20 -15
  28. angr/engines/icicle.py +16 -3
  29. angr/knowledge_plugins/propagations/propagation_model.py +7 -0
  30. angr/rustylib.abi3.so +0 -0
  31. angr/sim_type.py +16 -1
  32. angr/state_plugins/history.py +16 -0
  33. angr/unicornlib.dylib +0 -0
  34. angr/utils/constants.py +1 -1
  35. angr/utils/graph.py +1 -1
  36. angr/utils/vex.py +11 -0
  37. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/METADATA +5 -5
  38. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/RECORD +1409 -1407
  39. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/WHEEL +1 -0
  40. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/entry_points.txt +0 -0
  41. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/licenses/LICENSE +0 -0
  42. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/top_level.txt +0 -0
@@ -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
 
@@ -27,6 +27,42 @@ STEP_LIMIT_FIND = 500
27
27
  STEP_LIMIT_ANALYSIS = 5000
28
28
 
29
29
 
30
+ ALL_X64_XMM_REGS = {
31
+ capstone.x86.X86_REG_XMM0,
32
+ capstone.x86.X86_REG_XMM1,
33
+ capstone.x86.X86_REG_XMM2,
34
+ capstone.x86.X86_REG_XMM3,
35
+ capstone.x86.X86_REG_XMM4,
36
+ capstone.x86.X86_REG_XMM5,
37
+ capstone.x86.X86_REG_XMM6,
38
+ capstone.x86.X86_REG_XMM7,
39
+ capstone.x86.X86_REG_XMM8,
40
+ capstone.x86.X86_REG_XMM9,
41
+ capstone.x86.X86_REG_XMM10,
42
+ capstone.x86.X86_REG_XMM11,
43
+ capstone.x86.X86_REG_XMM12,
44
+ capstone.x86.X86_REG_XMM13,
45
+ capstone.x86.X86_REG_XMM14,
46
+ capstone.x86.X86_REG_XMM15,
47
+ capstone.x86.X86_REG_XMM16,
48
+ capstone.x86.X86_REG_XMM17,
49
+ capstone.x86.X86_REG_XMM18,
50
+ capstone.x86.X86_REG_XMM19,
51
+ capstone.x86.X86_REG_XMM20,
52
+ capstone.x86.X86_REG_XMM21,
53
+ capstone.x86.X86_REG_XMM22,
54
+ capstone.x86.X86_REG_XMM23,
55
+ capstone.x86.X86_REG_XMM24,
56
+ capstone.x86.X86_REG_XMM25,
57
+ capstone.x86.X86_REG_XMM26,
58
+ capstone.x86.X86_REG_XMM27,
59
+ capstone.x86.X86_REG_XMM28,
60
+ capstone.x86.X86_REG_XMM29,
61
+ capstone.x86.X86_REG_XMM30,
62
+ capstone.x86.X86_REG_XMM31,
63
+ }
64
+
65
+
30
66
  class StringDeobFuncDescriptor:
31
67
  """
32
68
  Describes a string deobfuscation function.
@@ -478,10 +514,12 @@ class StringObfuscationFinder(Analysis):
478
514
  actual_addrs = action.actual_addrs
479
515
  if action.type == "mem":
480
516
  if action.action == "read":
517
+ assert action.size is not None
481
518
  for a in actual_addrs:
482
519
  for size in range(action.size.ast // 8):
483
520
  all_global_reads.append(a + size)
484
521
  elif action.action == "write":
522
+ assert action.size is not None
485
523
  for a in actual_addrs:
486
524
  for size in range(action.size.ast // 8):
487
525
  all_global_writes.append(a + size)
@@ -598,16 +636,16 @@ class StringObfuscationFinder(Analysis):
598
636
  type3_functions = []
599
637
 
600
638
  for func in function_candidates:
601
- if not 8 <= len(func.block_addrs_set) < 14:
639
+ if not 1 <= len(func.block_addrs_set) < 14:
602
640
  continue
603
641
 
604
642
  # if it has a prototype recovered, it must have four arguments
605
- if func.prototype is not None and len(func.prototype.args) != 4:
643
+ if func.prototype is not None and len(func.prototype.args) not in {3, 4}:
606
644
  continue
607
645
 
608
646
  # the function must call some other functions
609
- if callgraph_digraph.out_degree[func.addr] == 0:
610
- continue
647
+ # if callgraph_digraph.out_degree[func.addr] == 0:
648
+ # continue
611
649
 
612
650
  # take a look at its call sites
613
651
  func_node = cfg.get_any_node(func.addr)
@@ -635,30 +673,34 @@ class StringObfuscationFinder(Analysis):
635
673
  continue
636
674
  if dec.codegen is None or not dec.codegen.text:
637
675
  continue
676
+
638
677
  if not self._like_type3_deobfuscation_function(dec.codegen.text):
639
678
  continue
640
679
 
641
680
  # examine the first 100 call sites and see if any of them returns a valid string
642
681
  valid = False
682
+ guessed_size = False
643
683
  for i in range(min(100, len(call_sites))):
644
684
  call_site_block = self.project.factory.block(call_sites[i].addr)
645
685
  if not self._is_block_setting_constants_to_stack(call_site_block):
646
686
  continue
647
687
 
648
688
  # simulate an execution to see if it really works
649
- data = self._type3_prepare_and_execute(
650
- func.addr, call_sites[i].addr, call_sites[i].function_address, cfg
689
+ data, guessed_size = self._type3_prepare_and_execute(
690
+ func.addr, call_sites[i].addr, call_sites[i].function_address, cfg # type:ignore
651
691
  )
652
692
  if data is None:
653
693
  continue
654
- if len(data) > 3 and all(chr(x) in string.printable for x in data):
655
- valid = True
656
- break
694
+ if len(data) > 3:
695
+ consecutive_printable_strs = self._consecutive_printable_substrings(data, min_length=4)
696
+ if consecutive_printable_strs:
697
+ valid = True
698
+ break
657
699
 
658
700
  if valid:
659
701
  desc = StringDeobFuncDescriptor()
660
702
  desc.string_output_arg_idx = 0
661
- desc.string_length_arg_idx = 1
703
+ desc.string_length_arg_idx = 1 if not guessed_size else None
662
704
  desc.string_null_terminating = False
663
705
  type3_functions.append((func.addr, desc))
664
706
 
@@ -687,11 +729,15 @@ class StringObfuscationFinder(Analysis):
687
729
  if cfg is None:
688
730
  raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
689
731
 
690
- call_sites = cfg.get_predecessors(cfg.get_any_node(func_addr))
732
+ cfg_node = cfg.get_any_node(func_addr)
733
+ if cfg_node is None:
734
+ raise AngrAnalysisError(f"Cannot find the CFG node for function {func_addr:#x}")
735
+ call_sites = cfg.get_predecessors(cfg_node)
691
736
  callinsn2content = {}
692
737
  for idx, call_site in enumerate(call_sites):
693
738
  _l.debug("Analyzing type 3 candidate call site %#x (%d/%d)...", call_site.addr, idx + 1, len(call_sites))
694
- data = self._type3_prepare_and_execute(func_addr, call_site.addr, call_site.function_address, cfg)
739
+ assert call_site.function_address is not None
740
+ data, _ = self._type3_prepare_and_execute(func_addr, call_site.addr, call_site.function_address, cfg)
695
741
  if data:
696
742
  callinsn2content[call_site.instruction_addrs[-1]] = data
697
743
  # print(hex(call_site.addr), data)
@@ -722,12 +768,14 @@ class StringObfuscationFinder(Analysis):
722
768
 
723
769
  @staticmethod
724
770
  def _like_type3_deobfuscation_function(code: str) -> bool:
725
- return bool(
726
- ("^" in code or ">>" in code or "<<" in code or "~" in code)
727
- and ("do" in code or "while" in code or "for" in code)
728
- )
729
-
730
- def _type3_prepare_and_execute(self, func_addr: int, call_site_addr: int, call_site_func_addr: int, cfg):
771
+ has_bitwise_ops = "^" in code or ">>" in code or "<<" in code or "~" in code
772
+ has_loops = "do" in code or "while" in code or "for" in code
773
+ has_many_bitwise_ops = code.count("^") + code.count(">>") + code.count("<<") + code.count("~") > 5
774
+ return has_bitwise_ops and (has_loops or has_many_bitwise_ops)
775
+
776
+ def _type3_prepare_and_execute(
777
+ self, func_addr: int, call_site_addr: int, call_site_func_addr: int, cfg
778
+ ) -> tuple[bytes | None, bool]:
731
779
  blocks_at_callsite = [call_site_addr]
732
780
 
733
781
  # backtrack from call site to include all previous consecutive blocks
@@ -773,6 +821,7 @@ class StringObfuscationFinder(Analysis):
773
821
  # setup sp and bp, just in case
774
822
  state.regs._sp = 0x7FFF0000
775
823
  bp_set = False
824
+ assert prop.model.input_states is not None
776
825
  prop_state = prop.model.input_states.get(call_site_addr, None)
777
826
  if prop_state is not None:
778
827
  for reg_offset, reg_width in reg_reads:
@@ -798,7 +847,7 @@ class StringObfuscationFinder(Analysis):
798
847
  else:
799
848
  simgr.step()
800
849
  if not simgr.active:
801
- return None
850
+ return None, False
802
851
 
803
852
  in_state = simgr.active[0]
804
853
 
@@ -821,33 +870,63 @@ class StringObfuscationFinder(Analysis):
821
870
  try:
822
871
  ret_value = callable_0()
823
872
  except (AngrCallableMultistateError, AngrCallableError):
824
- return None
873
+ return None, False
825
874
 
826
875
  out_state = callable_0.result_state
827
876
 
828
877
  # figure out what was written
878
+ assert out_state is not None
829
879
  ptr = out_state.memory.load(ret_value, size=self.project.arch.bytes, endness=self.project.arch.memory_endness)
880
+ if out_state.memory.load(ptr, size=4).concrete_value == 0:
881
+ # fall back to using the return value as the pointer
882
+ ptr = ret_value
883
+ if out_state.memory.load(ptr, size=4).concrete_value == 0:
884
+ # can't find a valid pointer
885
+ return None, False
886
+
830
887
  size = out_state.memory.load(ret_value + 8, size=4, endness=self.project.arch.memory_endness)
888
+ guessed_size = False
889
+ if size.symbolic or size.concrete_value == 0 or size.concrete_value >= 1024:
890
+ size = 64
891
+ guessed_size = True
831
892
  # TODO: Support lists with varied-length elements
832
893
  data = out_state.memory.load(ptr, size=size, endness="Iend_BE")
833
894
  if data.symbolic:
834
- return None
895
+ return None, False
835
896
 
836
- return out_state.solver.eval(data, cast_to=bytes)
897
+ return out_state.solver.eval(data, cast_to=bytes), guessed_size
837
898
 
838
899
  @staticmethod
839
900
  def _is_block_setting_constants_to_stack(block, threshold: int = 5) -> bool:
840
- insn_setting_consts = 0
901
+ insn_setting_const_bytes = 0
902
+ xmm_has_const = False
841
903
  for insn in block.capstone.insns:
842
- if (
843
- insn.mnemonic.startswith("mov")
844
- and len(insn.operands) == 2
845
- and insn.operands[0].type == capstone.x86.X86_OP_MEM
846
- and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
847
- and insn.operands[1].type == capstone.x86.X86_OP_IMM
848
- ):
849
- insn_setting_consts += 1
850
- return insn_setting_consts >= threshold
904
+ if insn.mnemonic.startswith("mov") and len(insn.operands) == 2:
905
+ if (
906
+ insn.operands[0].type == capstone.x86.X86_OP_MEM
907
+ and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
908
+ and insn.operands[1].type == capstone.x86.X86_OP_IMM
909
+ ):
910
+ # mov [rsp|rbp + offset], imm
911
+ insn_setting_const_bytes += 1 # FIXME: How to get the size of the mov in capstone?
912
+ if (
913
+ insn.operands[0].type == capstone.x86.X86_OP_REG
914
+ and insn.operands[0].reg in ALL_X64_XMM_REGS
915
+ and insn.operands[1].type == capstone.x86.X86_OP_MEM
916
+ and insn.operands[1].mem.base == capstone.x86.X86_REG_RIP
917
+ ):
918
+ xmm_has_const = True
919
+ if (
920
+ xmm_has_const
921
+ and insn.operands[0].type == capstone.x86.X86_OP_MEM
922
+ and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
923
+ and insn.operands[1].type == capstone.x86.X86_OP_REG
924
+ and insn.operands[1].reg in ALL_X64_XMM_REGS
925
+ ):
926
+ # mov [rsp|rbp + offset], xmm0 - 31
927
+ insn_setting_const_bytes += 16
928
+
929
+ return insn_setting_const_bytes >= threshold
851
930
 
852
931
  @staticmethod
853
932
  def _is_string_reasonable(s: bytes) -> bool:
@@ -857,5 +936,24 @@ class StringObfuscationFinder(Analysis):
857
936
  s = s.replace(b"\x00", b"")
858
937
  return all(chr(ch) in string.printable for ch in s)
859
938
 
939
+ @staticmethod
940
+ def _consecutive_printable_substrings(s: bytes, min_length: int = 3) -> list[bytes]:
941
+ """
942
+ Find all consecutive printable substrings in a string.
943
+ """
944
+ substrings = []
945
+ current_substring = b""
946
+ for ch in s:
947
+ if chr(ch) in string.printable:
948
+ current_substring += bytes([ch])
949
+ else:
950
+ if current_substring:
951
+ if len(current_substring) >= min_length:
952
+ substrings.append(current_substring)
953
+ current_substring = b""
954
+ if current_substring:
955
+ substrings.append(current_substring)
956
+ return substrings
957
+
860
958
 
861
959
  AnalysesHub.register_default("StringObfuscationFinder", StringObfuscationFinder)
@@ -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):
angr/blade.py CHANGED
@@ -40,6 +40,7 @@ class Blade:
40
40
  cross_insn_opt=False,
41
41
  max_predecessors: int = 10,
42
42
  include_imarks: bool = True,
43
+ control_dependence: bool = True,
43
44
  ):
44
45
  """
45
46
  :param graph: A graph representing the control flow graph. Note that it does not take
@@ -56,6 +57,8 @@ class Blade:
56
57
  :param stop_at_calls: Limit slicing within a single function. Do not proceed when encounters a call
57
58
  edge.
58
59
  :param include_imarks: Should IMarks (instruction boundaries) be included in the slice.
60
+ :param control_dependence: Whether to consider control dependencies. If True, the temps controlling
61
+ conditional exits will be added to the tainting set.
59
62
  :return: None
60
63
  """
61
64
 
@@ -70,6 +73,7 @@ class Blade:
70
73
  self._cross_insn_opt = cross_insn_opt
71
74
  self._max_predecessors = max_predecessors
72
75
  self._include_imarks = include_imarks
76
+ self._control_dependence = control_dependence
73
77
 
74
78
  self._slice = networkx.DiGraph()
75
79
 
@@ -347,7 +351,7 @@ class Blade:
347
351
  except (SimTranslationError, BadJumpkindNotification):
348
352
  return
349
353
 
350
- if exit_stmt_idx is None or exit_stmt_idx == DEFAULT_STATEMENT:
354
+ if self._control_dependence and (exit_stmt_idx is None or exit_stmt_idx == DEFAULT_STATEMENT):
351
355
  # Initialize the temps set with whatever in the `next` attribute of this irsb
352
356
  next_expr = self._get_irsb(run).next
353
357
  if type(next_expr) is pyvex.IRExpr.RdTmp:
@@ -357,20 +361,21 @@ class Blade:
357
361
  self._inslice_callback(DEFAULT_STATEMENT, None, {"irsb_addr": irsb_addr, "prev": prev})
358
362
  prev = irsb_addr, DEFAULT_STATEMENT
359
363
 
360
- # if there are conditional exits, we *always* add them into the slice (so if they should not be taken, we do not
361
- # lose the condition)
362
- for stmt_idx_, s_ in enumerate(self._get_irsb(run).statements):
363
- if type(s_) is not pyvex.IRStmt.Exit:
364
- continue
365
- if s_.jumpkind != "Ijk_Boring":
366
- continue
367
-
368
- if type(s_.guard) is pyvex.IRExpr.RdTmp:
369
- temps.add(s_.guard.tmp)
370
-
371
- # Put it in our slice
372
- self._inslice_callback(stmt_idx_, s_, {"irsb_addr": irsb_addr, "prev": prev})
373
- prev = (irsb_addr, stmt_idx_)
364
+ if self._control_dependence:
365
+ # if there are conditional exits, we *always* add them into the slice (so if they should not be taken, we
366
+ # do not lose the condition)
367
+ for stmt_idx_, s_ in enumerate(self._get_irsb(run).statements):
368
+ if type(s_) is not pyvex.IRStmt.Exit:
369
+ continue
370
+ if s_.jumpkind != "Ijk_Boring":
371
+ continue
372
+
373
+ if type(s_.guard) is pyvex.IRExpr.RdTmp:
374
+ temps.add(s_.guard.tmp)
375
+
376
+ # Put it in our slice
377
+ self._inslice_callback(stmt_idx_, s_, {"irsb_addr": irsb_addr, "prev": prev})
378
+ prev = (irsb_addr, stmt_idx_)
374
379
 
375
380
  infodict = {"irsb_addr": irsb_addr, "prev": prev, "has_statement": False}
376
381
 
angr/engines/icicle.py CHANGED
@@ -123,7 +123,7 @@ class IcicleEngine(ConcreteEngine):
123
123
  if proj is None:
124
124
  raise ValueError("IcicleEngine requires a project to be set")
125
125
 
126
- emu = Icicle(icicle_arch, PROCESSORS_DIR)
126
+ emu = Icicle(icicle_arch, PROCESSORS_DIR, True, True)
127
127
 
128
128
  copied_registers = set()
129
129
 
@@ -174,6 +174,11 @@ class IcicleEngine(ConcreteEngine):
174
174
  initial_cpu_icount=emu.cpu_icount,
175
175
  )
176
176
 
177
+ # 3. Copy edge hitmap
178
+ edge_hitmap = state.history.last_edge_hitmap
179
+ if edge_hitmap is not None:
180
+ emu.edge_hitmap = edge_hitmap
181
+
177
182
  return (emu, translation_data)
178
183
 
179
184
  @staticmethod
@@ -194,7 +199,8 @@ class IcicleEngine(ConcreteEngine):
194
199
  addr = page_num * state.memory.page_size
195
200
  state.memory.store(addr, emu.mem_read(addr, state.memory.page_size))
196
201
 
197
- # 3. Set history.jumpkind
202
+ # 3. Set history
203
+ # 3.1 history.jumpkind
198
204
  exc = emu.exception_code
199
205
  if status == VmExit.UnhandledException:
200
206
  if exc in (
@@ -216,9 +222,16 @@ class IcicleEngine(ConcreteEngine):
216
222
  else:
217
223
  state.history.jumpkind = "Ijk_Boring"
218
224
 
219
- # 4. Set history.recent_instruction_count
225
+ # 3.2 history.recent_bbl_addrs
226
+ # Skip the last block, because it will be added by Successors
227
+ state.history.recent_bbl_addrs.extend([b[0] for b in emu.recent_blocks][:-1])
228
+
229
+ # 3.3. Set history.recent_instruction_count
220
230
  state.history.recent_instruction_count = emu.cpu_icount - translation_data.initial_cpu_icount
221
231
 
232
+ # 3.4. Set edge hitmap
233
+ state.history.edge_hitmap = emu.edge_hitmap
234
+
222
235
  return state
223
236
 
224
237
  @override