angr 9.2.143__py3-none-manylinux2014_aarch64.whl → 9.2.144__py3-none-manylinux2014_aarch64.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 (46) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +13 -1
  3. angr/analyses/calling_convention/fact_collector.py +41 -5
  4. angr/analyses/cfg/cfg_base.py +7 -2
  5. angr/analyses/cfg/cfg_emulated.py +13 -4
  6. angr/analyses/cfg/cfg_fast.py +21 -60
  7. angr/analyses/cfg/indirect_jump_resolvers/__init__.py +2 -0
  8. angr/analyses/cfg/indirect_jump_resolvers/constant_value_manager.py +107 -0
  9. angr/analyses/cfg/indirect_jump_resolvers/default_resolvers.py +2 -1
  10. angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +2 -101
  11. angr/analyses/cfg/indirect_jump_resolvers/syscall_resolver.py +92 -0
  12. angr/analyses/decompiler/ail_simplifier.py +5 -0
  13. angr/analyses/decompiler/clinic.py +162 -68
  14. angr/analyses/decompiler/decompiler.py +4 -4
  15. angr/analyses/decompiler/optimization_passes/base_ptr_save_simplifier.py +1 -1
  16. angr/analyses/decompiler/optimization_passes/optimization_pass.py +5 -5
  17. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +5 -0
  18. angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
  19. angr/analyses/decompiler/peephole_optimizations/a_sub_a_shr_const_shr_const.py +37 -0
  20. angr/analyses/decompiler/ssailification/rewriting_engine.py +2 -0
  21. angr/analyses/decompiler/ssailification/ssailification.py +10 -2
  22. angr/analyses/decompiler/ssailification/traversal_engine.py +17 -2
  23. angr/analyses/decompiler/structured_codegen/c.py +25 -4
  24. angr/analyses/disassembly.py +3 -3
  25. angr/analyses/fcp/fcp.py +1 -4
  26. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +21 -22
  27. angr/analyses/typehoon/dfa.py +13 -3
  28. angr/analyses/typehoon/typehoon.py +60 -18
  29. angr/analyses/typehoon/typevars.py +11 -7
  30. angr/analyses/variable_recovery/engine_ail.py +13 -17
  31. angr/analyses/variable_recovery/engine_base.py +26 -30
  32. angr/analyses/variable_recovery/variable_recovery_fast.py +17 -21
  33. angr/knowledge_plugins/functions/function.py +29 -15
  34. angr/knowledge_plugins/key_definitions/constants.py +2 -2
  35. angr/knowledge_plugins/key_definitions/liveness.py +4 -4
  36. angr/lib/angr_native.so +0 -0
  37. angr/state_plugins/unicorn_engine.py +24 -8
  38. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +1 -2
  39. angr/storage/memory_mixins/paged_memory/pages/mv_list_page.py +2 -2
  40. angr/utils/graph.py +26 -20
  41. {angr-9.2.143.dist-info → angr-9.2.144.dist-info}/METADATA +11 -8
  42. {angr-9.2.143.dist-info → angr-9.2.144.dist-info}/RECORD +46 -43
  43. {angr-9.2.143.dist-info → angr-9.2.144.dist-info}/WHEEL +1 -1
  44. {angr-9.2.143.dist-info → angr-9.2.144.dist-info}/LICENSE +0 -0
  45. {angr-9.2.143.dist-info → angr-9.2.144.dist-info}/entry_points.txt +0 -0
  46. {angr-9.2.143.dist-info → angr-9.2.144.dist-info}/top_level.txt +0 -0
angr/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # pylint: disable=wrong-import-position
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "9.2.143"
5
+ __version__ = "9.2.144"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
@@ -864,7 +864,19 @@ class CallingConventionAnalysis(Analysis):
864
864
  else:
865
865
  int_args.append(arg)
866
866
 
867
- stack_args = sorted([a for a in args if isinstance(a, SimStackArg)], key=lambda a: a.stack_offset)
867
+ initial_stack_args = sorted([a for a in args if isinstance(a, SimStackArg)], key=lambda a: a.stack_offset)
868
+ # ensure stack args are consecutive if necessary
869
+ if cc.STACKARG_SP_DIFF is not None and initial_stack_args:
870
+ arg_by_offset = {a.stack_offset: a for a in initial_stack_args}
871
+ init_stackarg_offset = cc.STACKARG_SP_DIFF + cc.STACKARG_SP_BUFF
872
+ int_arg_size = self.project.arch.bytes
873
+ for stackarg_offset in range(init_stackarg_offset, max(arg_by_offset), int_arg_size):
874
+ if stackarg_offset not in arg_by_offset:
875
+ arg_by_offset[stackarg_offset] = SimStackArg(stackarg_offset, int_arg_size)
876
+ stack_args = [arg_by_offset[offset] for offset in sorted(arg_by_offset)]
877
+ else:
878
+ stack_args = initial_stack_args
879
+
868
880
  stack_int_args = [a for a in stack_args if not a.is_fp]
869
881
  stack_fp_args = [a for a in stack_args if a.is_fp]
870
882
  # match int args first
@@ -1,6 +1,7 @@
1
1
  # pylint:disable=too-many-boolean-expressions
2
2
  from __future__ import annotations
3
3
  from typing import Any, TYPE_CHECKING
4
+ from collections import defaultdict
4
5
 
5
6
  import pyvex
6
7
  import claripy
@@ -30,6 +31,7 @@ class FactCollectorState:
30
31
  "bp_value",
31
32
  "callee_stored_regs",
32
33
  "reg_reads",
34
+ "reg_reads_count",
33
35
  "reg_writes",
34
36
  "simple_stack",
35
37
  "sp_value",
@@ -44,6 +46,7 @@ class FactCollectorState:
44
46
 
45
47
  self.callee_stored_regs: dict[int, int] = {} # reg offset -> stack offset
46
48
  self.reg_reads = {}
49
+ self.reg_reads_count = defaultdict(int)
47
50
  self.reg_writes: set[int] = set()
48
51
  self.stack_reads = {}
49
52
  self.stack_writes: set[int] = set()
@@ -51,6 +54,7 @@ class FactCollectorState:
51
54
  self.bp_value = 0
52
55
 
53
56
  def register_read(self, offset: int, size_in_bytes: int):
57
+ self.reg_reads_count[offset] += 1
54
58
  if offset in self.reg_writes:
55
59
  return
56
60
  if offset not in self.reg_reads:
@@ -58,6 +62,14 @@ class FactCollectorState:
58
62
  else:
59
63
  self.reg_reads[offset] = max(self.reg_reads[offset], size_in_bytes)
60
64
 
65
+ def register_read_undo(self, offset: int) -> None:
66
+ if offset not in self.reg_reads or offset not in self.reg_reads_count:
67
+ return
68
+ self.reg_reads_count[offset] -= 1
69
+ if self.reg_reads_count[offset] == 0:
70
+ self.reg_reads.pop(offset)
71
+ self.reg_reads_count.pop(offset)
72
+
61
73
  def register_written(self, offset: int, size_in_bytes: int):
62
74
  for o in range(size_in_bytes):
63
75
  self.reg_writes.add(offset + o)
@@ -84,6 +96,7 @@ class FactCollectorState:
84
96
  new_state.sp_value = self.sp_value
85
97
  new_state.bp_value = self.bp_value
86
98
  new_state.simple_stack = self.simple_stack.copy()
99
+ new_state.reg_reads_count = self.reg_reads_count.copy()
87
100
  if with_tmps:
88
101
  new_state.tmps = self.tmps.copy()
89
102
  return new_state
@@ -119,6 +132,26 @@ class SimEngineFactCollectorVEX(
119
132
 
120
133
  def _handle_stmt_Put(self, stmt):
121
134
  v = self._expr(stmt.data)
135
+ # there are cases like VMOV.F32 S0, S0
136
+ # so we need to check if this register write is actually a no-op
137
+ if isinstance(stmt.data, pyvex.IRExpr.RdTmp):
138
+ t = self.state.tmps.get(stmt.data.tmp, None)
139
+ if isinstance(t, RegisterOffset) and t.reg == stmt.offset:
140
+ same_ins_read = False
141
+ for i in range(self.stmt_idx, -1, -1):
142
+ if i >= self.block.vex.stmts_used:
143
+ break
144
+ prev_stmt = self.block.vex.statements[i]
145
+ if isinstance(prev_stmt, pyvex.IRStmt.IMark):
146
+ break
147
+ if isinstance(prev_stmt, pyvex.IRStmt.WrTmp) and prev_stmt.tmp == stmt.data.tmp:
148
+ same_ins_read = True
149
+ break
150
+ if same_ins_read:
151
+ # we need to revert the read operation as well
152
+ self.state.register_read_undo(stmt.offset)
153
+ return
154
+
122
155
  if stmt.offset == self.arch.sp_offset and isinstance(v, SpOffset):
123
156
  self.state.sp_value = v.offset
124
157
  elif stmt.offset == self.arch.bp_offset and isinstance(v, SpOffset):
@@ -210,7 +243,7 @@ class FactCollector(Analysis):
210
243
  decision on the calling convention and prototype of a function.
211
244
  """
212
245
 
213
- def __init__(self, func: Function, max_depth: int = 5):
246
+ def __init__(self, func: Function, max_depth: int = 30):
214
247
  self.function = func
215
248
  self._max_depth = max_depth
216
249
 
@@ -285,14 +318,17 @@ class FactCollector(Analysis):
285
318
  for _, succ, data in func_graph.out_edges(node, data=True):
286
319
  edge_type = data.get("type")
287
320
  outside = data.get("outside", False)
288
- if succ not in traversed and depth + 1 <= self._max_depth:
321
+ if depth + 1 <= self._max_depth:
289
322
  if edge_type == "fake_return":
290
- ret_succ = succ
323
+ if succ not in traversed:
324
+ ret_succ = succ
291
325
  elif edge_type == "transition" and not outside:
292
- successor_added = True
293
- queue.append((depth + 1, state.copy(), succ, None))
326
+ if succ not in traversed:
327
+ successor_added = True
328
+ queue.append((depth + 1, state.copy(), succ, None))
294
329
  elif edge_type == "call" or (edge_type == "transition" and outside):
295
330
  # a call or a tail-call
331
+ # note that it's ok to traverse a called function multiple times
296
332
  if not isinstance(succ, Function):
297
333
  if self.kb.functions.contains_addr(succ.addr):
298
334
  succ = self.kb.functions.get_by_addr(succ.addr)
@@ -1701,7 +1701,12 @@ class CFGBase(Analysis):
1701
1701
  self._update_progress(progress)
1702
1702
 
1703
1703
  self._graph_bfs_custom(
1704
- self.graph, [fn], self._graph_traversal_handler, blockaddr_to_function, tmp_functions
1704
+ self.graph,
1705
+ [fn],
1706
+ self._graph_traversal_handler,
1707
+ blockaddr_to_function,
1708
+ tmp_functions,
1709
+ traversed_cfg_nodes,
1705
1710
  )
1706
1711
 
1707
1712
  to_remove = set()
@@ -2731,7 +2736,7 @@ class CFGBase(Analysis):
2731
2736
  relifted = self.project.factory.block(block.addr, size=block.size, opt_level=1, cross_insn_opt=True).vex
2732
2737
  except SimError:
2733
2738
  return False, []
2734
- if isinstance(relifted.next, pyvex.IRExpr.Const):
2739
+ if not relifted.jumpkind.startswith("Ijk_Sys") and isinstance(relifted.next, pyvex.IRExpr.Const):
2735
2740
  # yes!
2736
2741
  return True, [relifted.next.con.value]
2737
2742
 
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
+ from typing import TYPE_CHECKING
2
3
  import itertools
3
4
  import logging
4
5
  import sys
5
6
  from collections import defaultdict
6
7
  from functools import reduce
8
+ import contextlib
7
9
 
8
10
  import angr
9
11
  import claripy
@@ -45,7 +47,10 @@ from angr.analyses.backward_slice import BackwardSlice
45
47
  from angr.analyses.loopfinder import LoopFinder, Loop
46
48
  from .cfg_base import CFGBase
47
49
  from .cfg_job_base import BlockID, CFGJobBase
48
- import contextlib
50
+
51
+ if TYPE_CHECKING:
52
+ from angr.knowledge_plugins.cfg import CFGNode
53
+
49
54
 
50
55
  l = logging.getLogger(name=__name__)
51
56
 
@@ -505,6 +510,8 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
505
510
  :return: None
506
511
  """
507
512
 
513
+ assert self._starts is not None
514
+
508
515
  if not isinstance(max_loop_unrolling_times, int) or max_loop_unrolling_times < 0:
509
516
  raise AngrCFGError(
510
517
  "Max loop unrolling times must be set to an integer greater than or equal to 0 if "
@@ -586,6 +593,7 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
586
593
 
587
594
  graph_copy.remove_node(new_end_node)
588
595
  src, dst = loop_backedge
596
+ assert src is not None and dst is not None
589
597
  if graph_copy.has_edge(src, dst): # It might have been removed before
590
598
  # Duplicate the dst node
591
599
  new_dst = dst.copy()
@@ -713,9 +721,10 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
713
721
  # FIXME: start should also take a CFGNode instance
714
722
 
715
723
  start_node = self.get_any_node(start)
724
+ assert start_node is not None
716
725
 
717
726
  node_wrapper = (start_node, 0)
718
- stack = [node_wrapper]
727
+ stack: list[tuple[CFGNode, int]] = [node_wrapper]
719
728
  traversed_nodes = {start_node}
720
729
  subgraph_nodes = {start_node}
721
730
 
@@ -727,6 +736,7 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
727
736
  edges = self.graph.out_edges(n, data=True)
728
737
 
729
738
  for _, dst, data in edges:
739
+ assert dst is not None
730
740
  if dst not in traversed_nodes:
731
741
  # We see a new node!
732
742
  traversed_nodes.add(dst)
@@ -1687,9 +1697,8 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
1687
1697
 
1688
1698
  for block_id in pending_exits_to_remove:
1689
1699
  l.debug(
1690
- "Removing all pending exits to %#x since the target function %#x does not return",
1700
+ "Removing all pending exits to %#x since the target function does not return",
1691
1701
  self._block_id_addr(block_id),
1692
- next(iter(self._pending_jobs[block_id])).returning_source,
1693
1702
  )
1694
1703
 
1695
1704
  for to_remove in self._pending_jobs[block_id]:
@@ -31,13 +31,10 @@ from angr import sim_options as o
31
31
  from angr.errors import (
32
32
  AngrCFGError,
33
33
  AngrSkipJobNotice,
34
- AngrUnsupportedSyscallError,
35
34
  SimEngineError,
36
35
  SimMemoryError,
37
36
  SimTranslationError,
38
37
  SimValueError,
39
- SimOperationError,
40
- SimError,
41
38
  SimIRSBNoDecodeError,
42
39
  )
43
40
  from angr.utils.constants import DEFAULT_STATEMENT
@@ -200,7 +197,7 @@ class PendingJobs:
200
197
  return self._pop_job(next(reversed(self._jobs.keys())))
201
198
 
202
199
  # Prioritize returning functions
203
- for func_addr in reversed(self._jobs.keys()):
200
+ for func_addr in reversed(self._jobs):
204
201
  if func_addr not in self._returning_functions:
205
202
  continue
206
203
  return self._pop_job(func_addr)
@@ -621,6 +618,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
621
618
  nodecode_window_size=512,
622
619
  nodecode_threshold=0.3,
623
620
  nodecode_step=16483,
621
+ check_funcret_max_job=500,
624
622
  indirect_calls_always_return: bool | None = None,
625
623
  jumptable_resolver_resolves_calls: bool | None = None,
626
624
  start=None, # deprecated
@@ -680,6 +678,12 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
680
678
  table resolver and must be resolved using their specific resolvers. By default,
681
679
  we will only disable JumpTableResolver from resolving indirect calls for large
682
680
  binaries (region > 50 KB).
681
+ :param check_funcret_max_job When popping return-site jobs out of the job queue, angr will prioritize jobs
682
+ for which the callee is known to return. This check may be slow when there are
683
+ a large amount of jobs in different caller functions, and this situation often
684
+ occurs in obfuscated binaries where many functions never return. This parameter
685
+ acts as a threshold to disable this check when the number of jobs in the queue
686
+ exceeds this threshold.
683
687
  :param int start: (Deprecated) The beginning address of CFG recovery.
684
688
  :param int end: (Deprecated) The end address of CFG recovery.
685
689
  :param CFGArchOptions arch_options: Architecture-specific options.
@@ -768,6 +772,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
768
772
  self._force_complete_scan = force_complete_scan
769
773
  self._use_elf_eh_frame = elf_eh_frame
770
774
  self._use_exceptions = exceptions
775
+ self._check_funcret_max_job = check_funcret_max_job
771
776
 
772
777
  self._nodecode_window_size = nodecode_window_size
773
778
  self._nodecode_threshold = nodecode_threshold
@@ -2576,38 +2581,16 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
2576
2581
  jobs: list[CFGJob] = []
2577
2582
 
2578
2583
  if is_syscall:
2579
- # Fix the target_addr for syscalls
2580
- tmp_state = self.project.factory.blank_state(
2581
- mode="fastpath",
2582
- addr=cfg_node.addr,
2583
- add_options={o.SYMBOL_FILL_UNCONSTRAINED_MEMORY, o.SYMBOL_FILL_UNCONSTRAINED_REGISTERS},
2584
+ resolved, resolved_targets, ij = self._indirect_jump_encountered(
2585
+ addr, cfg_node, irsb, current_function_addr, stmt_idx
2584
2586
  )
2585
- # Find the first successor with a syscall jumpkind
2586
- successors = self._simulate_block_with_resilience(tmp_state)
2587
- if successors is not None:
2588
- succ = next(
2589
- iter(
2590
- succ
2591
- for succ in successors.flat_successors
2592
- if succ.history.jumpkind and succ.history.jumpkind.startswith("Ijk_Sys")
2593
- ),
2594
- None,
2595
- )
2596
- else:
2597
- succ = None
2598
- if succ is None:
2599
- # For some reason, there is no such successor with a syscall jumpkind
2600
- target_addr = self._unresolvable_call_target_addr
2587
+ target_addr = None
2588
+ if resolved:
2589
+ if len(resolved_targets) == 1:
2590
+ (target_addr,) = resolved_targets
2601
2591
  else:
2602
- try:
2603
- syscall_stub = self.project.simos.syscall(succ)
2604
- if syscall_stub: # can be None if simos is not a subclass of SimUserspace
2605
- syscall_addr = syscall_stub.addr
2606
- target_addr = syscall_addr
2607
- else:
2608
- target_addr = self._unresolvable_call_target_addr
2609
- except AngrUnsupportedSyscallError:
2610
- target_addr = self._unresolvable_call_target_addr
2592
+ if ij is not None:
2593
+ self._indirect_jumps_to_resolve.add(ij)
2611
2594
 
2612
2595
  new_function_addr = target_addr.method if isinstance(target_addr, SootAddressDescriptor) else target_addr
2613
2596
 
@@ -2732,30 +2715,6 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
2732
2715
 
2733
2716
  return jobs
2734
2717
 
2735
- def _simulate_block_with_resilience(self, state):
2736
- """
2737
- Execute a basic block with "On Error Resume Next". Give up when there is no way moving forward.
2738
-
2739
- :param SimState state: The initial state to start simulation with.
2740
- :return: A SimSuccessors instance or None if we are unable to resume execution with resilience.
2741
- :rtype: SimSuccessors or None
2742
- """
2743
-
2744
- stmt_idx = 0
2745
- successors = None # make PyCharm's linting happy
2746
-
2747
- while True:
2748
- try:
2749
- successors = self.project.factory.successors(state, skip_stmts=stmt_idx)
2750
- break
2751
- except SimOperationError as ex:
2752
- stmt_idx = ex.stmt_idx + 1
2753
- continue
2754
- except SimError:
2755
- return None
2756
-
2757
- return successors
2758
-
2759
2718
  def _is_branching_to_outside(self, src_addr, target_addr, current_function_addr):
2760
2719
  """
2761
2720
  Determine if a branch is branching to a different function (i.e., branching to outside the current function).
@@ -3236,7 +3195,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
3236
3195
  if jump.jumpkind == "Ijk_Boring":
3237
3196
  unresolvable_target_addr = self._unresolvable_jump_target_addr
3238
3197
  simprocedure_name = "UnresolvableJumpTarget"
3239
- elif jump.jumpkind == "Ijk_Call":
3198
+ elif jump.jumpkind == "Ijk_Call" or jump.jumpkind.startswith("Ijk_Sys"):
3240
3199
  unresolvable_target_addr = self._unresolvable_call_target_addr
3241
3200
  simprocedure_name = "UnresolvableCallTarget"
3242
3201
  else:
@@ -3707,7 +3666,9 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
3707
3666
 
3708
3667
  def _pop_pending_job(self, returning=True) -> CFGJob | None:
3709
3668
  while self._pending_jobs:
3710
- job = self._pending_jobs.pop_job(returning=returning)
3669
+ job = self._pending_jobs.pop_job(
3670
+ returning=returning if len(self._pending_jobs) < self._check_funcret_max_job else False
3671
+ )
3711
3672
  if job is not None and job.job_type == CFGJobType.DATAREF_HINTS and self._seg_list.is_occupied(job.addr):
3712
3673
  # ignore this hint from data refs because the target address has already been analyzed
3713
3674
  continue
@@ -10,6 +10,7 @@ from .arm_elf_fast import ArmElfFastResolver
10
10
  from .const_resolver import ConstantResolver
11
11
  from .amd64_pe_iat import AMD64PeIatResolver
12
12
  from .memload_resolver import MemoryLoadResolver
13
+ from .syscall_resolver import SyscallResolver
13
14
 
14
15
 
15
16
  __all__ = (
@@ -21,6 +22,7 @@ __all__ = (
21
22
  "MemoryLoadResolver",
22
23
  "MipsElfFastResolver",
23
24
  "MipsElfGotResolver",
25
+ "SyscallResolver",
24
26
  "X86ElfPicPltResolver",
25
27
  "X86PeIatResolver",
26
28
  )
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Any
3
+ import logging
4
+
5
+ import claripy
6
+
7
+ from angr.code_location import CodeLocation
8
+ from angr.project import Project
9
+ from angr.analyses.propagator.vex_vars import VEXReg
10
+ from .propagator_utils import PropagatorLoadCallback
11
+
12
+ if TYPE_CHECKING:
13
+ from angr import SimState
14
+ from angr.knowledge_plugins import Function
15
+
16
+
17
+ l = logging.getLogger(name=__name__)
18
+
19
+
20
+ class ConstantValueManager:
21
+ """
22
+ Manages the loading of registers who hold constant values.
23
+ """
24
+
25
+ __slots__ = (
26
+ "func",
27
+ "indirect_jump_addr",
28
+ "kb",
29
+ "mapping",
30
+ "project",
31
+ )
32
+
33
+ def __init__(self, project: Project, kb, func: Function, ij_addr: int):
34
+ self.project = project
35
+ self.kb = kb
36
+ self.func = func
37
+ self.indirect_jump_addr = ij_addr
38
+
39
+ self.mapping: dict[Any, dict[Any, claripy.ast.Base]] | None = None
40
+
41
+ def reg_read_callback(self, state: SimState):
42
+ if self.mapping is None:
43
+ self._build_mapping()
44
+ assert self.mapping is not None
45
+
46
+ codeloc = CodeLocation(state.scratch.bbl_addr, state.scratch.stmt_idx, ins_addr=state.scratch.ins_addr)
47
+ if codeloc in self.mapping:
48
+ reg_read_offset = state.inspect.reg_read_offset
49
+ if isinstance(reg_read_offset, claripy.ast.BV) and reg_read_offset.op == "BVV":
50
+ reg_read_offset = reg_read_offset.args[0]
51
+ variable = VEXReg(reg_read_offset, state.inspect.reg_read_length)
52
+ if variable in self.mapping[codeloc]:
53
+ v = self.mapping[codeloc][variable]
54
+ if isinstance(v, int):
55
+ v = claripy.BVV(v, state.inspect.reg_read_length * state.arch.byte_width)
56
+ state.inspect.reg_read_expr = v
57
+
58
+ def _build_mapping(self):
59
+ # constant propagation
60
+ l.debug("JumpTable: Propagating for %r at %#x.", self.func, self.indirect_jump_addr)
61
+
62
+ # determine blocks to run FCP on
63
+
64
+ # - include at most three levels of superblock successors from the entrypoint
65
+ self.mapping = {}
66
+ startpoint = self.func.startpoint
67
+ if startpoint is None:
68
+ return
69
+
70
+ blocks = set()
71
+ succ_and_levels = [(startpoint, 0)]
72
+ while succ_and_levels:
73
+ new_succs = []
74
+ for node, level in succ_and_levels:
75
+ if node in blocks:
76
+ continue
77
+ blocks.add(node)
78
+ if node.addr == self.indirect_jump_addr:
79
+ # stop at the indirect jump block
80
+ continue
81
+ for _, succ, data in self.func.graph.out_edges(node, data=True):
82
+ new_level = level if data.get("type") == "fake_return" else level + 1
83
+ if new_level <= 3:
84
+ new_succs.append((succ, new_level))
85
+ succ_and_levels = new_succs
86
+
87
+ # - include at most six levels of predecessors from the indirect jump block
88
+ ij_block = self.func.get_node(self.indirect_jump_addr)
89
+ preds = [ij_block]
90
+ for _ in range(6):
91
+ new_preds = []
92
+ for node in preds:
93
+ if node in blocks:
94
+ continue
95
+ blocks.add(node)
96
+ new_preds += list(self.func.graph.predecessors(node))
97
+ preds = new_preds
98
+ if not preds:
99
+ break
100
+
101
+ prop = self.project.analyses.FastConstantPropagation(
102
+ self.func,
103
+ blocks=blocks,
104
+ vex_cross_insn_opt=True,
105
+ load_callback=PropagatorLoadCallback(self.project).propagator_load_callback,
106
+ )
107
+ self.mapping = prop.replacements
@@ -11,6 +11,7 @@ from . import ConstantResolver
11
11
  from . import ArmElfFastResolver
12
12
  from . import AMD64PeIatResolver
13
13
  from . import MipsElfGotResolver
14
+ from . import SyscallResolver
14
15
 
15
16
  DEFAULT_RESOLVERS = {
16
17
  "X86": {
@@ -58,7 +59,7 @@ DEFAULT_RESOLVERS = {
58
59
  ArmElfFastResolver,
59
60
  ]
60
61
  },
61
- "ALL": [MemoryLoadResolver, JumpTableResolver, ConstantResolver],
62
+ "ALL": [MemoryLoadResolver, JumpTableResolver, ConstantResolver, SyscallResolver],
62
63
  }
63
64
 
64
65
 
@@ -1,7 +1,7 @@
1
1
  # pylint:disable=wrong-import-position,wrong-import-order
2
2
  from __future__ import annotations
3
3
  import enum
4
- from typing import TYPE_CHECKING, Any, Literal, cast
4
+ from typing import TYPE_CHECKING, Literal, cast
5
5
  from collections.abc import Sequence
6
6
  from collections import defaultdict, OrderedDict
7
7
  import logging
@@ -16,7 +16,6 @@ from claripy.annotation import UninitializedAnnotation
16
16
  from angr import sim_options as o
17
17
  from angr import BP, BP_BEFORE, BP_AFTER
18
18
  from angr.misc.ux import once
19
- from angr.code_location import CodeLocation
20
19
  from angr.concretization_strategies import SimConcretizationStrategyAny
21
20
  from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
22
21
  from angr.engines.vex.claripy import ccall
@@ -27,13 +26,11 @@ from angr.annocfg import AnnotatedCFG
27
26
  from angr.exploration_techniques.slicecutor import Slicecutor
28
27
  from angr.exploration_techniques.local_loop_seer import LocalLoopSeer
29
28
  from angr.exploration_techniques.explorer import Explorer
30
- from angr.project import Project
31
29
  from angr.utils.constants import DEFAULT_STATEMENT
32
- from angr.analyses.propagator.vex_vars import VEXReg
33
30
  from angr.analyses.propagator.top_checker_mixin import ClaripyDataVEXEngineMixin
34
31
  from angr.engines.vex.claripy.datalayer import value
35
32
  from .resolver import IndirectJumpResolver
36
- from .propagator_utils import PropagatorLoadCallback
33
+ from .constant_value_manager import ConstantValueManager
37
34
 
38
35
  try:
39
36
  from angr.engines import pcode
@@ -41,7 +38,6 @@ except ImportError:
41
38
  pcode = None
42
39
 
43
40
  if TYPE_CHECKING:
44
- from angr import SimState
45
41
  from angr.knowledge_plugins import Function
46
42
 
47
43
  l = logging.getLogger(name=__name__)
@@ -134,101 +130,6 @@ class JumpTargetBaseAddr:
134
130
  return self.base_addr is not None
135
131
 
136
132
 
137
- #
138
- # Constant register resolving support
139
- #
140
-
141
-
142
- class ConstantValueManager:
143
- """
144
- Manages the loading of registers who hold constant values.
145
- """
146
-
147
- __slots__ = (
148
- "func",
149
- "indirect_jump_addr",
150
- "kb",
151
- "mapping",
152
- "project",
153
- )
154
-
155
- def __init__(self, project: Project, kb, func: Function, ij_addr: int):
156
- self.project = project
157
- self.kb = kb
158
- self.func = func
159
- self.indirect_jump_addr = ij_addr
160
-
161
- self.mapping: dict[Any, dict[Any, claripy.ast.Base]] | None = None
162
-
163
- def reg_read_callback(self, state: SimState):
164
- if self.mapping is None:
165
- self._build_mapping()
166
- assert self.mapping is not None
167
-
168
- codeloc = CodeLocation(state.scratch.bbl_addr, state.scratch.stmt_idx, ins_addr=state.scratch.ins_addr)
169
- if codeloc in self.mapping:
170
- reg_read_offset = state.inspect.reg_read_offset
171
- if isinstance(reg_read_offset, claripy.ast.BV) and reg_read_offset.op == "BVV":
172
- reg_read_offset = reg_read_offset.args[0]
173
- variable = VEXReg(reg_read_offset, state.inspect.reg_read_length)
174
- if variable in self.mapping[codeloc]:
175
- v = self.mapping[codeloc][variable]
176
- if isinstance(v, int):
177
- v = claripy.BVV(v, state.inspect.reg_read_length * state.arch.byte_width)
178
- state.inspect.reg_read_expr = v
179
-
180
- def _build_mapping(self):
181
- # constant propagation
182
- l.debug("JumpTable: Propagating for %r at %#x.", self.func, self.indirect_jump_addr)
183
-
184
- # determine blocks to run FCP on
185
-
186
- # - include at most three levels of superblock successors from the entrypoint
187
- self.mapping = {}
188
- startpoint = self.func.startpoint
189
- if startpoint is None:
190
- return
191
-
192
- blocks = set()
193
- succ_and_levels = [(startpoint, 0)]
194
- while succ_and_levels:
195
- new_succs = []
196
- for node, level in succ_and_levels:
197
- if node in blocks:
198
- continue
199
- blocks.add(node)
200
- if node.addr == self.indirect_jump_addr:
201
- # stop at the indirect jump block
202
- continue
203
- for _, succ, data in self.func.graph.out_edges(node, data=True):
204
- new_level = level if data.get("type") == "fake_return" else level + 1
205
- if new_level <= 3:
206
- new_succs.append((succ, new_level))
207
- succ_and_levels = new_succs
208
-
209
- # - include at most six levels of predecessors from the indirect jump block
210
- ij_block = self.func.get_node(self.indirect_jump_addr)
211
- preds = [ij_block]
212
- for _ in range(6):
213
- new_preds = []
214
- for node in preds:
215
- if node in blocks:
216
- continue
217
- blocks.add(node)
218
- new_preds += list(self.func.graph.predecessors(node))
219
- preds = new_preds
220
- if not preds:
221
- break
222
-
223
- prop = self.project.analyses.FastConstantPropagation(
224
- self.func,
225
- blocks=blocks,
226
- vex_cross_insn_opt=True,
227
- load_callback=PropagatorLoadCallback(self.project).propagator_load_callback,
228
- )
229
- self.mapping = prop.replacements
230
-
231
-
232
133
  #
233
134
  # Jump table pre-check
234
135
  #