angr 9.2.160__cp310-abi3-win_amd64.whl → 9.2.162__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 (58) hide show
  1. angr/__init__.py +4 -1
  2. angr/analyses/analysis.py +0 -1
  3. angr/analyses/cfg/cfg_base.py +5 -1
  4. angr/analyses/decompiler/ail_simplifier.py +101 -2
  5. angr/analyses/decompiler/block_simplifier.py +13 -8
  6. angr/analyses/decompiler/clinic.py +1 -0
  7. angr/analyses/decompiler/condition_processor.py +24 -0
  8. angr/analyses/decompiler/counters/call_counter.py +11 -1
  9. angr/analyses/decompiler/decompiler.py +3 -1
  10. angr/analyses/decompiler/graph_region.py +11 -2
  11. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +1 -1
  12. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -0
  13. angr/analyses/decompiler/optimization_passes/optimization_pass.py +31 -11
  14. angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +2 -0
  15. angr/analyses/decompiler/peephole_optimizations/__init__.py +4 -4
  16. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +53 -0
  17. angr/analyses/decompiler/peephole_optimizations/modulo_simplifier.py +89 -0
  18. angr/analyses/decompiler/peephole_optimizations/{const_mull_a_shift.py → optimized_div_simplifier.py} +139 -25
  19. angr/analyses/decompiler/peephole_optimizations/remove_redundant_bitmasks.py +18 -9
  20. angr/analyses/decompiler/region_simplifiers/goto.py +3 -3
  21. angr/analyses/decompiler/region_simplifiers/if_.py +2 -2
  22. angr/analyses/decompiler/region_simplifiers/loop.py +2 -2
  23. angr/analyses/decompiler/structured_codegen/c.py +3 -3
  24. angr/analyses/decompiler/structuring/dream.py +1 -1
  25. angr/analyses/decompiler/structuring/phoenix.py +138 -99
  26. angr/analyses/decompiler/structuring/recursive_structurer.py +3 -2
  27. angr/analyses/decompiler/structuring/sailr.py +51 -43
  28. angr/analyses/decompiler/structuring/structurer_base.py +2 -3
  29. angr/analyses/deobfuscator/string_obf_opt_passes.py +1 -1
  30. angr/analyses/disassembly.py +1 -1
  31. angr/analyses/reaching_definitions/function_handler.py +1 -0
  32. angr/analyses/s_propagator.py +2 -2
  33. angr/analyses/s_reaching_definitions/s_rda_model.py +1 -0
  34. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +5 -2
  35. angr/analyses/variable_recovery/engine_base.py +17 -1
  36. angr/analyses/variable_recovery/variable_recovery_base.py +30 -2
  37. angr/analyses/variable_recovery/variable_recovery_fast.py +11 -2
  38. angr/emulator.py +143 -0
  39. angr/engines/concrete.py +66 -0
  40. angr/engines/icicle.py +66 -30
  41. angr/exploration_techniques/driller_core.py +2 -2
  42. angr/knowledge_plugins/functions/function.py +1 -1
  43. angr/knowledge_plugins/functions/function_manager.py +1 -2
  44. angr/project.py +7 -0
  45. angr/rustylib.pyd +0 -0
  46. angr/sim_type.py +16 -8
  47. angr/simos/javavm.py +1 -1
  48. angr/unicornlib.dll +0 -0
  49. angr/utils/graph.py +48 -13
  50. angr/utils/library.py +13 -12
  51. angr/utils/ssa/__init__.py +57 -5
  52. {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/METADATA +5 -5
  53. {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/RECORD +57 -55
  54. angr/analyses/decompiler/peephole_optimizations/a_sub_a_div_const_mul_const.py +0 -57
  55. {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/WHEEL +0 -0
  56. {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/entry_points.txt +0 -0
  57. {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/licenses/LICENSE +0 -0
  58. {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/top_level.txt +0 -0
angr/engines/icicle.py CHANGED
@@ -5,17 +5,16 @@ from __future__ import annotations
5
5
  import logging
6
6
  import os
7
7
  from dataclasses import dataclass
8
+ from typing_extensions import override
8
9
 
9
- import claripy
10
10
  import pypcode
11
11
  from archinfo import Arch, Endness
12
12
 
13
+ from angr.engines.concrete import ConcreteEngine, HeavyConcreteState
13
14
  from angr.engines.failure import SimEngineFailure
14
15
  from angr.engines.hook import HooksMixin
15
- from angr.engines.successors import SuccessorsEngine
16
16
  from angr.engines.syscall import SimEngineSyscall
17
17
  from angr.rustylib.icicle import Icicle, VmExit, ExceptionCode
18
- from angr.sim_state import SimState
19
18
 
20
19
  log = logging.getLogger(__name__)
21
20
 
@@ -30,12 +29,13 @@ class IcicleStateTranslationData:
30
29
  to an angr state.
31
30
  """
32
31
 
33
- base_state: SimState
32
+ base_state: HeavyConcreteState
34
33
  registers: set[str]
35
34
  writable_pages: set[int]
35
+ initial_cpu_icount: int
36
36
 
37
37
 
38
- class IcicleEngine(SuccessorsEngine):
38
+ class IcicleEngine(ConcreteEngine):
39
39
  """
40
40
  An angr engine that uses Icicle to execute concrete states. The purpose of
41
41
  this implementation is to provide a high-performance concrete execution
@@ -55,6 +55,16 @@ class IcicleEngine(SuccessorsEngine):
55
55
  intends to provide a more complete set of features, such as hooks and syscalls.
56
56
  """
57
57
 
58
+ breakpoints: set[int]
59
+
60
+ def __init__(self, *args, **kwargs):
61
+ """
62
+ Initialize the IcicleEngine. This sets up the breakpoints set and
63
+ initializes the parent class.
64
+ """
65
+ super().__init__(*args, **kwargs)
66
+ self.breakpoints = set()
67
+
58
68
  @staticmethod
59
69
  def __make_icicle_arch(arch: Arch) -> str | None:
60
70
  """
@@ -81,7 +91,7 @@ class IcicleEngine(SuccessorsEngine):
81
91
  return IcicleEngine.__is_arm(icicle_arch) and addr & 1 == 1
82
92
 
83
93
  @staticmethod
84
- def __get_pages(state: SimState) -> set[int]:
94
+ def __get_pages(state: HeavyConcreteState) -> set[int]:
85
95
  """
86
96
  Unfortunately, the memory model doesn't have a way to get all pages.
87
97
  Instead, we can get all of the backers from the loader, then all of the
@@ -104,7 +114,7 @@ class IcicleEngine(SuccessorsEngine):
104
114
  return pages
105
115
 
106
116
  @staticmethod
107
- def __convert_angr_state_to_icicle(state: SimState) -> tuple[Icicle, IcicleStateTranslationData]:
117
+ def __convert_angr_state_to_icicle(state: HeavyConcreteState) -> tuple[Icicle, IcicleStateTranslationData]:
108
118
  icicle_arch = IcicleEngine.__make_icicle_arch(state.arch)
109
119
  if icicle_arch is None:
110
120
  raise ValueError("Unsupported architecture")
@@ -161,12 +171,15 @@ class IcicleEngine(SuccessorsEngine):
161
171
  base_state=state,
162
172
  registers=copied_registers,
163
173
  writable_pages=writable_pages,
174
+ initial_cpu_icount=emu.cpu_icount,
164
175
  )
165
176
 
166
177
  return (emu, translation_data)
167
178
 
168
179
  @staticmethod
169
- def __convert_icicle_state_to_angr(emu: Icicle, translation_data: IcicleStateTranslationData) -> SimState:
180
+ def __convert_icicle_state_to_angr(
181
+ emu: Icicle, translation_data: IcicleStateTranslationData, status: VmExit
182
+ ) -> HeavyConcreteState:
170
183
  state = translation_data.base_state.copy()
171
184
 
172
185
  # 1. Copy the register values
@@ -181,20 +194,8 @@ class IcicleEngine(SuccessorsEngine):
181
194
  addr = page_num * state.memory.page_size
182
195
  state.memory.store(addr, emu.mem_read(addr, state.memory.page_size))
183
196
 
184
- return state
185
-
186
- def process_successors(self, successors, *, num_inst=0, **kwargs):
187
- if len(kwargs) > 0:
188
- log.warning("IcicleEngine.process_successors received unknown kwargs: %s", kwargs)
189
-
190
- emu, translation_data = self.__convert_angr_state_to_icicle(self.state)
191
-
192
- if num_inst > 0:
193
- emu.icount_limit = num_inst
194
-
195
- status = emu.run() # pylint: ignore=assignment-from-no-return (pylint bug)
197
+ # 3. Set history.jumpkind
196
198
  exc = emu.exception_code
197
-
198
199
  if status == VmExit.UnhandledException:
199
200
  if exc in (
200
201
  ExceptionCode.ReadUnmapped,
@@ -203,22 +204,57 @@ class IcicleEngine(SuccessorsEngine):
203
204
  ExceptionCode.WritePerm,
204
205
  ExceptionCode.ExecViolation,
205
206
  ):
206
- jumpkind = "Ijk_SigSEGV"
207
+ state.history.jumpkind = "Ijk_SigSEGV"
207
208
  elif exc == ExceptionCode.Syscall:
208
- jumpkind = "Ijk_Syscall"
209
+ state.history.jumpkind = "Ijk_Syscall"
209
210
  elif exc == ExceptionCode.Halt:
210
- jumpkind = "Ijk_Exit"
211
+ state.history.jumpkind = "Ijk_Exit"
211
212
  elif exc == ExceptionCode.InvalidInstruction:
212
- jumpkind = "Ijk_NoDecode"
213
+ state.history.jumpkind = "Ijk_NoDecode"
213
214
  else:
214
- jumpkind = "Ijk_EmFail"
215
+ state.history.jumpkind = "Ijk_EmFail"
215
216
  else:
216
- jumpkind = "Ijk_Boring"
217
+ state.history.jumpkind = "Ijk_Boring"
218
+
219
+ # 4. Set history.recent_instruction_count
220
+ state.history.recent_instruction_count = emu.cpu_icount - translation_data.initial_cpu_icount
221
+
222
+ return state
223
+
224
+ @override
225
+ def get_breakpoints(self) -> set[int]:
226
+ """Return the set of currently set breakpoints."""
227
+ return self.breakpoints
228
+
229
+ @override
230
+ def add_breakpoint(self, addr: int) -> None:
231
+ """Add a breakpoint at the given address."""
232
+ self.breakpoints.add(addr)
233
+
234
+ @override
235
+ def remove_breakpoint(self, addr: int) -> None:
236
+ """Remove a breakpoint at the given address, if present."""
237
+ self.breakpoints.discard(addr)
238
+
239
+ @override
240
+ def process_concrete(self, state: HeavyConcreteState, num_inst: int | None = None) -> HeavyConcreteState:
241
+ emu, translation_data = self.__convert_angr_state_to_icicle(state)
242
+
243
+ # Set breakpoints, skip the current PC. This assumes that if running
244
+ # with a breakpoint at the current PC, then the user has already done
245
+ # the necessary handling and is resuming execution.
246
+ for addr in self.breakpoints:
247
+ if emu.pc != addr:
248
+ emu.add_breakpoint(addr)
249
+
250
+ # Set the instruction count limit
251
+ if num_inst is not None and num_inst > 0:
252
+ emu.icount_limit = num_inst
217
253
 
218
- successor_state = IcicleEngine.__convert_icicle_state_to_angr(emu, translation_data)
219
- successors.add_successor(successor_state, successor_state.ip, claripy.true(), jumpkind, add_guard=False)
254
+ # Run it
255
+ status = emu.run()
220
256
 
221
- successors.processed = True
257
+ return IcicleEngine.__convert_icicle_state_to_angr(emu, translation_data, status)
222
258
 
223
259
 
224
260
  class UberIcicleEngine(SimEngineFailure, SimEngineSyscall, HooksMixin, IcicleEngine):
@@ -94,7 +94,7 @@ class DrillerCore(ExplorationTechnique):
94
94
  @staticmethod
95
95
  def _has_false(state):
96
96
  # Check if the state is unsat even if we remove preconstraints.
97
- if state.scratch.guard.identical(claripy.false()):
97
+ if claripy.is_false(state.scratch.guard):
98
98
  return True
99
99
 
100
- return any(c.identical(claripy.false()) for c in state.solver.constraints)
100
+ return any(claripy.is_false(c) for c in state.solver.constraints)
@@ -1666,7 +1666,7 @@ class Function(Serializable):
1666
1666
  if self.is_rust_function():
1667
1667
  ast = pydemumble.demangle(self.name)
1668
1668
  return Function._rust_fmt_node(ast.split("::")[-2])
1669
- func_name = get_cpp_function_name(self.demangled_name, specialized=False, qualified=True)
1669
+ func_name = get_cpp_function_name(self.demangled_name)
1670
1670
  return func_name.split("::")[-1]
1671
1671
 
1672
1672
  def get_unambiguous_name(self, display_name: str | None = None) -> str:
@@ -154,8 +154,7 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
154
154
  :return: None
155
155
  """
156
156
  with open(filepath, "w", encoding="utf-8") as f:
157
- for src, dst in self.callgraph.edges():
158
- f.write(f"{src:#x}\tDirectEdge\t{dst:#x}\n")
157
+ f.writelines(f"{src:#x}\tDirectEdge\t{dst:#x}\n" for src, dst in self.callgraph.edges())
159
158
 
160
159
  def _addr_in_plt_cached_ranges(self, addr: int) -> bool:
161
160
  if self._rplt_cache_ranges is None:
angr/project.py CHANGED
@@ -297,11 +297,18 @@ class Project:
297
297
 
298
298
  # Step 1: get the set of libraries we are allowed to use to resolve unresolved symbols
299
299
  missing_libs = []
300
+ missing_wincore_dlls = False
300
301
  for lib_name in self.loader.missing_dependencies:
301
302
  try:
302
303
  missing_libs.extend(SIM_LIBRARIES[lib_name])
303
304
  except KeyError:
304
305
  l.info("There are no simprocedures for missing library %s :(", lib_name)
306
+ if lib_name.startswith("api-ms-win-"):
307
+ missing_wincore_dlls = True
308
+ if missing_wincore_dlls and "kernel32.dll" not in self.loader.missing_dependencies:
309
+ # some of the missing api-ms-win-*.dll libraries are actually provided by kernel32.dll
310
+ missing_libs.extend(SIM_LIBRARIES["kernel32.dll"])
311
+
305
312
  # additionally provide libraries we _have_ loaded as a fallback fallback
306
313
  # this helps in the case that e.g. CLE picked up a linux arm libc to satisfy an android arm binary
307
314
  for lib in self.loader.all_objects:
angr/rustylib.pyd CHANGED
Binary file
angr/sim_type.py CHANGED
@@ -609,33 +609,41 @@ class SimTypeWideChar(SimTypeReg):
609
609
 
610
610
  _base_name = "char"
611
611
 
612
- def __init__(self, signed=True, label=None):
612
+ def __init__(self, signed=True, label=None, endness: Endness = Endness.BE):
613
613
  """
614
614
  :param label: the type label.
615
615
  """
616
616
  SimTypeReg.__init__(self, 16, label=label)
617
617
  self.signed = signed
618
+ self.endness = endness
618
619
 
619
620
  def __repr__(self):
620
621
  return "wchar"
621
622
 
622
623
  def store(self, state, addr, value: StoreType):
623
- self._size = state.arch.byte_width
624
624
  try:
625
625
  super().store(state, addr, value)
626
626
  except TypeError:
627
627
  if isinstance(value, bytes) and len(value) == 2:
628
- value = claripy.BVV(value[0], state.arch.byte_width)
628
+ inner = (
629
+ ((value[0] << state.arch.byte_width) | value[1])
630
+ if self.endness == Endness.BE
631
+ else ((value[1] << state.arch.byte_width) | value[0])
632
+ )
633
+ value = claripy.BVV(inner, state.arch.byte_width * 2)
629
634
  super().store(state, addr, value)
630
635
  else:
631
636
  raise
632
637
 
633
638
  def extract(self, state, addr, concrete=False) -> Any:
634
- self._size = state.arch.byte_width
635
-
636
- out = super().extract(state, addr, concrete)
639
+ out = state.memory.load(addr, 2)
637
640
  if concrete:
638
- return bytes([out])
641
+ data = state.solver.eval(out, cast_to=bytes)
642
+ fmt_str = "utf-16be" if self.endness == Endness.BE else "utf-16le"
643
+ try:
644
+ return data.decode(fmt_str)
645
+ except UnicodeDecodeError:
646
+ return data
639
647
  return out
640
648
 
641
649
  def _init_str(self):
@@ -645,7 +653,7 @@ class SimTypeWideChar(SimTypeReg):
645
653
  )
646
654
 
647
655
  def copy(self):
648
- return self.__class__(signed=self.signed, label=self.label)
656
+ return self.__class__(signed=self.signed, label=self.label, endness=self.endness)
649
657
 
650
658
 
651
659
  class SimTypeBool(SimTypeReg):
angr/simos/javavm.py CHANGED
@@ -255,7 +255,7 @@ class SimJavaVM(SimOS):
255
255
  upper = native_arg_value.get_bytes(0, 4)
256
256
  lower = native_arg_value.get_bytes(4, 4)
257
257
  idx = args.index(arg)
258
- args = args[:idx] + (SootArgument(upper, "int"), SootArgument(lower, "int")) + args[idx + 1 :]
258
+ args = (*args[:idx], SootArgument(upper, "int"), SootArgument(lower, "int"), *args[idx + 1 :])
259
259
  native_arg_values += [upper, lower]
260
260
  continue
261
261
  if type(arg.value) is BV and len(arg.value) > arg_ty.size:
angr/unicornlib.dll CHANGED
Binary file
angr/utils/graph.py CHANGED
@@ -55,7 +55,7 @@ def inverted_idoms(graph: networkx.DiGraph) -> tuple[networkx.DiGraph, dict | No
55
55
 
56
56
 
57
57
  def to_acyclic_graph(
58
- graph: networkx.DiGraph, ordered_nodes: list | None = None, loop_heads: list | None = None
58
+ graph: networkx.DiGraph, node_order: dict[Any, int] | None = None, loop_heads: list | None = None
59
59
  ) -> networkx.DiGraph:
60
60
  """
61
61
  Convert a given DiGraph into an acyclic graph.
@@ -66,21 +66,22 @@ def to_acyclic_graph(
66
66
  :return: The converted acyclic graph.
67
67
  """
68
68
 
69
- if ordered_nodes is None:
69
+ if node_order is None:
70
70
  # take the quasi-topological order of the graph
71
71
  ordered_nodes = GraphUtils.quasi_topological_sort_nodes(graph, loop_heads=loop_heads)
72
-
73
- acyclic_graph = networkx.DiGraph()
72
+ node_order = {n: i for i, n in enumerate(ordered_nodes)}
74
73
 
75
74
  # add each node and its edge into the graph
76
- visited = set()
77
- for node in ordered_nodes:
78
- visited.add(node)
79
- acyclic_graph.add_node(node)
80
- for successor in graph.successors(node):
81
- if successor not in visited:
82
- acyclic_graph.add_edge(node, successor)
83
-
75
+ edges_to_remove = []
76
+ for src, dst in graph.edges():
77
+ src_order = node_order[src]
78
+ dst_order = node_order[dst]
79
+ if src_order > dst_order:
80
+ # this is a back edge, we need to remove it
81
+ edges_to_remove.append((src, dst))
82
+
83
+ acyclic_graph = graph.copy()
84
+ acyclic_graph.remove_edges_from(edges_to_remove)
84
85
  return acyclic_graph
85
86
 
86
87
 
@@ -534,11 +535,30 @@ class Dominators:
534
535
 
535
536
  def _pd_eval(self, v):
536
537
  assert self._ancestor is not None
538
+ assert self._semi is not None
537
539
  assert self._label is not None
538
540
 
539
541
  if self._ancestor[v.index] is None:
540
542
  return v
541
- self._pd_compress(v)
543
+
544
+ # pd_compress without recursion
545
+ queue = []
546
+ current = v
547
+ ancestor = self._ancestor[current.index]
548
+ greater_ancestor = self._ancestor[ancestor.index]
549
+ while greater_ancestor is not None:
550
+ queue.append(current)
551
+ current, ancestor = ancestor, greater_ancestor
552
+ greater_ancestor = self._ancestor[ancestor.index]
553
+
554
+ for vv in reversed(queue):
555
+ if (
556
+ self._semi[self._label[self._ancestor[vv.index].index].index].index
557
+ < self._semi[self._label[vv.index].index].index
558
+ ):
559
+ self._label[vv.index] = self._label[self._ancestor[vv.index].index]
560
+ self._ancestor[vv.index] = self._ancestor[self._ancestor[vv.index].index]
561
+
542
562
  return self._label[v.index]
543
563
 
544
564
  def _pd_compress(self, v):
@@ -653,6 +673,21 @@ class GraphUtils:
653
673
 
654
674
  return list(widening_addrs)
655
675
 
676
+ @staticmethod
677
+ def dfs_postorder_nodes_deterministic(graph: networkx.DiGraph, source):
678
+ visited = set()
679
+ stack = [source]
680
+ while stack:
681
+ node = stack[-1]
682
+ if node not in visited:
683
+ visited.add(node)
684
+ for succ in sorted(graph.successors(node), key=GraphUtils._sort_node):
685
+ if succ not in visited:
686
+ stack.append(succ)
687
+ else:
688
+ yield node
689
+ stack.pop()
690
+
656
691
  @staticmethod
657
692
  def reverse_post_order_sort_nodes(graph, nodes=None):
658
693
  """
angr/utils/library.py CHANGED
@@ -168,7 +168,7 @@ def parsedcprotos2py(
168
168
  proto_.returnty = SimTypeFd(label=proto_.returnty.label)
169
169
  for i, arg in enumerate(proto_.args):
170
170
  if (func_name, i) in fd_spots:
171
- proto_.args = proto_.args[:i] + (SimTypeFd(label=arg.label),) + proto_.args[i + 1 :]
171
+ proto_.args = (*proto_.args[:i], SimTypeFd(label=arg.label), *proto_.args[i + 1 :])
172
172
 
173
173
  line1 = " " * 8 + "#" + ((" " + decl) if decl else "") + "\n"
174
174
  line2 = " " * 8 + repr(func_name) + ": " + (proto_._init_str() if proto_ is not None else "None") + "," + "\n"
@@ -195,17 +195,18 @@ def cprotos2py(cprotos: list[str], fd_spots=frozenset(), remove_sys_prefix=False
195
195
  return parsedcprotos2py(parsed_cprotos, fd_spots=fd_spots, remove_sys_prefix=remove_sys_prefix)
196
196
 
197
197
 
198
- def get_cpp_function_name(demangled_name, specialized=True, qualified=True):
199
- # remove "<???>"s
200
- name = normalize_cpp_function_name(demangled_name) if not specialized else demangled_name
198
+ def get_cpp_function_name(demangled_name: str) -> str:
199
+ """
200
+ Parse a demangled C++ declaration into a function name.
201
201
 
202
- if not qualified:
203
- # remove leading namespaces
204
- chunks = name.split("::")
205
- name = "::".join(chunks[2:])
202
+ Note that the extracted name may include template instantiation, for example:
206
203
 
207
- # remove arguments
208
- if "(" in name:
209
- name = name[: name.find("(")]
204
+ example_func<int>
210
205
 
211
- return name
206
+ :param demangled_name: The demangled C++ function name.
207
+ :return: The qualified function name, excluding return type and parameters.
208
+ """
209
+ func_decls, _ = parse_cpp_file(demangled_name)
210
+ if func_decls and len(func_decls) == 1:
211
+ return next(iter(func_decls))
212
+ return normalize_cpp_function_name(demangled_name)
@@ -6,7 +6,7 @@ from typing import Any, Literal, overload
6
6
  import networkx
7
7
 
8
8
  import archinfo
9
- from angr.ailment import Expression, Block
9
+ from angr.ailment import Expression, Block, UnaryOp
10
10
  from angr.ailment.expression import (
11
11
  VirtualVariable,
12
12
  Const,
@@ -42,7 +42,7 @@ def get_reg_offset_base_and_size(
42
42
 
43
43
  def get_reg_offset_base_and_size(
44
44
  reg_offset: int, arch: archinfo.Arch, size: int | None = None, resilient: bool = True
45
- ) -> tuple[int, int] | None:
45
+ ) -> tuple[int, int | None] | None:
46
46
  """
47
47
  Translate a given register offset into the offset of its full register and obtain the size of the full register.
48
48
 
@@ -89,7 +89,7 @@ def get_reg_offset_base(reg_offset, arch, size=None, resilient=True):
89
89
 
90
90
 
91
91
  def get_vvar_deflocs(
92
- blocks, phi_vvars: dict[int, set[int]] | None = None
92
+ blocks, phi_vvars: dict[int, set[int | None]] | None = None
93
93
  ) -> dict[int, tuple[VirtualVariable, CodeLocation]]:
94
94
  vvar_to_loc: dict[int, tuple[VirtualVariable, CodeLocation]] = {}
95
95
  for block in blocks:
@@ -100,7 +100,7 @@ def get_vvar_deflocs(
100
100
  )
101
101
  if phi_vvars is not None and isinstance(stmt.src, Phi):
102
102
  phi_vvars[stmt.dst.varid] = {
103
- vvar_.varid for src, vvar_ in stmt.src.src_and_vvars if vvar_ is not None
103
+ vvar_.varid if vvar_ is not None else None for src, vvar_ in stmt.src.src_and_vvars
104
104
  }
105
105
  elif isinstance(stmt, Call):
106
106
  if isinstance(stmt.ret_expr, VirtualVariable):
@@ -161,7 +161,7 @@ def get_tmp_uselocs(blocks) -> dict[CodeLocation, dict[atoms.Tmp, set[tuple[Tmp,
161
161
  return tmp_to_loc
162
162
 
163
163
 
164
- def is_const_assignment(stmt: Statement) -> tuple[bool, Const | None]:
164
+ def is_const_assignment(stmt: Statement) -> tuple[bool, Const | StackBaseOffset | None]:
165
165
  if isinstance(stmt, Assignment) and isinstance(stmt.src, (Const, StackBaseOffset)):
166
166
  return True, stmt.src
167
167
  return False, None
@@ -278,6 +278,31 @@ def has_tmp_expr(expr: Expression) -> bool:
278
278
  return walker.has_blacklisted_exprs
279
279
 
280
280
 
281
+ class AILReferenceFinder(AILBlockWalkerBase):
282
+ """
283
+ Walks an AIL expression or statement and finds if it contains references to certain expressions.
284
+ """
285
+
286
+ def __init__(self, vvar_id: int):
287
+ super().__init__()
288
+ self.vvar_id = vvar_id
289
+ self.has_references_to_vvar = False
290
+
291
+ def _handle_UnaryOp(
292
+ self, expr_idx: int, expr: UnaryOp, stmt_idx: int, stmt: Statement | None, block: Block | None
293
+ ) -> Any:
294
+ if expr.op == "Reference" and isinstance(expr.operand, VirtualVariable) and expr.operand.varid == self.vvar_id:
295
+ self.has_references_to_vvar = True
296
+ return None
297
+ return super()._handle_UnaryOp(expr_idx, expr, stmt_idx, stmt, block)
298
+
299
+
300
+ def has_reference_to_vvar(stmt: Statement, vvar_id: int) -> bool:
301
+ walker = AILReferenceFinder(vvar_id)
302
+ walker.walk_statement(stmt)
303
+ return walker.has_references_to_vvar
304
+
305
+
281
306
  def check_in_between_stmts(
282
307
  graph: networkx.DiGraph,
283
308
  blocks: dict[tuple[int, int | None], Block],
@@ -358,6 +383,33 @@ def has_load_expr_in_between_stmts(
358
383
  )
359
384
 
360
385
 
386
+ def is_vvar_propagatable(vvar: VirtualVariable, def_stmt: Statement | None) -> bool:
387
+ if vvar.was_tmp or vvar.was_reg or vvar.was_parameter:
388
+ return True
389
+ if vvar.was_stack and isinstance(def_stmt, Assignment):
390
+ if isinstance(def_stmt.src, Const):
391
+ return True
392
+ if (
393
+ isinstance(def_stmt.src, VirtualVariable)
394
+ and def_stmt.src.was_stack
395
+ and def_stmt.src.stack_offset == vvar.stack_offset
396
+ ):
397
+ # special case: the following block
398
+ # ## Block 401e98
399
+ # 00 | 0x401e98 | LABEL_401e98:
400
+ # 01 | 0x401e98 | vvar_227{stack -12} = 𝜙@32b [((4202088, None), vvar_277{stack -12}), ((4202076, None),
401
+ # vvar_278{stack -12})]
402
+ # 02 | 0x401ea0 | return Conv(32->64, vvar_227{stack -12});
403
+ # might be simplified to the following block after return duplication
404
+ # ## Block 401e98.1
405
+ # 00 | 0x401e98 | LABEL_401e98__1:
406
+ # 01 | 0x401e98 | vvar_279{stack -12} = vvar_277{stack -12}
407
+ # 02 | 0x401ea0 | return Conv(32->64, vvar_279{stack -12});
408
+ # in this case, vvar_279 is eliminatable.
409
+ return True
410
+ return False
411
+
412
+
361
413
  def is_vvar_eliminatable(vvar: VirtualVariable, def_stmt: Statement | None) -> bool:
362
414
  if vvar.was_tmp or vvar.was_reg or vvar.was_parameter:
363
415
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: angr
3
- Version: 9.2.160
3
+ Version: 9.2.162
4
4
  Summary: A multi-architecture binary analysis toolkit, with the ability to perform dynamic symbolic execution and various static analyses on binaries
5
5
  License: BSD-2-Clause
6
6
  Project-URL: Homepage, https://angr.io/
@@ -16,12 +16,12 @@ Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: cxxheaderparser
18
18
  Requires-Dist: GitPython
19
- Requires-Dist: archinfo==9.2.160
19
+ Requires-Dist: archinfo==9.2.162
20
20
  Requires-Dist: cachetools
21
21
  Requires-Dist: capstone==5.0.3
22
22
  Requires-Dist: cffi>=1.14.0
23
- Requires-Dist: claripy==9.2.160
24
- Requires-Dist: cle==9.2.160
23
+ Requires-Dist: claripy==9.2.162
24
+ Requires-Dist: cle==9.2.162
25
25
  Requires-Dist: mulpyplexer
26
26
  Requires-Dist: networkx!=2.8.1,>=2.0
27
27
  Requires-Dist: protobuf>=5.28.2
@@ -30,7 +30,7 @@ Requires-Dist: pycparser>=2.18
30
30
  Requires-Dist: pydemumble
31
31
  Requires-Dist: pyformlang
32
32
  Requires-Dist: pypcode<4.0,>=3.2.1
33
- Requires-Dist: pyvex==9.2.160
33
+ Requires-Dist: pyvex==9.2.162
34
34
  Requires-Dist: rich>=13.1.0
35
35
  Requires-Dist: sortedcontainers
36
36
  Requires-Dist: sympy