angr 9.2.163__cp310-abi3-win_amd64.whl → 9.2.165__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 (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.pyd +0 -0
  31. angr/sim_type.py +16 -1
  32. angr/state_plugins/history.py +16 -0
  33. angr/unicornlib.dll +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 +42 -40
  39. {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/WHEEL +0 -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
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.163"
5
+ __version__ = "9.2.165"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
@@ -606,7 +606,7 @@ class VEXStmtConverter(Converter):
606
606
  expd_hi = VEXExprConverter.convert(stmt.expdHi, manager) if stmt.expdHi is not None else None
607
607
  old_lo = VEXExprConverter.tmp(stmt.oldLo, manager.tyenv.sizeof(stmt.oldLo), manager)
608
608
  old_hi = (
609
- VEXExprConverter.tmp(stmt.oldHi, stmt.oldHi.result_size(manager.tyenv), manager)
609
+ VEXExprConverter.tmp(stmt.oldHi, manager.tyenv.sizeof(stmt.oldHi), manager)
610
610
  if stmt.oldHi != 0xFFFFFFFF
611
611
  else None
612
612
  )
@@ -616,7 +616,11 @@ class Convert(UnaryOp):
616
616
  self.rounding_mode = rounding_mode
617
617
 
618
618
  def __str__(self):
619
- return f"Conv({self.from_bits}->{'s' if self.is_signed else ''}{self.to_bits}, {self.operand})"
619
+ from_type = "I" if self.from_type == Convert.TYPE_INT else "F"
620
+ to_type = "I" if self.to_type == Convert.TYPE_INT else "F"
621
+ return (
622
+ f"Conv({self.from_bits}{from_type}->{'s' if self.is_signed else ''}{self.to_bits}{to_type}, {self.operand})"
623
+ )
620
624
 
621
625
  def __repr__(self):
622
626
  return str(self)
angr/analyses/analysis.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import functools
3
+ import os
3
4
  import sys
4
5
  import contextlib
5
6
  from collections import defaultdict
@@ -14,6 +15,8 @@ import logging
14
15
  import time
15
16
  import typing
16
17
 
18
+ import psutil
19
+
17
20
  from rich import progress
18
21
 
19
22
  from angr.misc.plugins import PluginVendor, VendorPreset
@@ -287,6 +290,8 @@ class Analysis:
287
290
  _name: str
288
291
  errors: list[AnalysisLogEntry] = []
289
292
  named_errors: defaultdict[str, list[AnalysisLogEntry]] = defaultdict(list)
293
+ _ram_usage: float | None = None
294
+ _last_ramusage_update: float = 0.0
290
295
  _progress_callback = None
291
296
  _show_progressbar = False
292
297
  _progressbar = None
@@ -295,7 +300,7 @@ class Analysis:
295
300
  _PROGRESS_WIDGETS = [
296
301
  progress.TaskProgressColumn(),
297
302
  progress.BarColumn(),
298
- progress.TextColumn("Elapsed Time:"),
303
+ progress.TextColumn("Elapsed:"),
299
304
  progress.TimeElapsedColumn(),
300
305
  progress.TextColumn("Time:"),
301
306
  progress.TimeRemainingColumn(),
@@ -311,7 +316,9 @@ class Analysis:
311
316
  raise
312
317
  else:
313
318
  error = AnalysisLogEntry("exception occurred", exc_info=True)
314
- l.error("Caught and logged %s with resilience: %s", error.exc_type.__name__, error.exc_value)
319
+ l.error(
320
+ "Caught and logged %s with resilience: %s", error.exc_type.__name__, error.exc_value # type:ignore
321
+ )
315
322
  if name is None:
316
323
  self.errors.append(error)
317
324
  else:
@@ -342,10 +349,12 @@ class Analysis:
342
349
  if self._progressbar is None:
343
350
  self._initialize_progressbar()
344
351
 
352
+ assert self._task is not None
353
+ assert self._progressbar is not None
345
354
  self._progressbar.update(self._task, completed=percentage)
346
355
 
347
- if text is not None and self._progressbar:
348
- self._progressbar.update(self._task, description=text)
356
+ if text is not None and self._progressbar:
357
+ self._progressbar.update(self._task, description=text)
349
358
 
350
359
  if self._progress_callback is not None:
351
360
  self._progress_callback(percentage, text=text, **kwargs) # pylint:disable=not-callable
@@ -360,6 +369,7 @@ class Analysis:
360
369
  if self._progressbar is None:
361
370
  self._initialize_progressbar()
362
371
  if self._progressbar is not None:
372
+ assert self._task is not None
363
373
  self._progressbar.update(self._task, completed=100)
364
374
  self._progressbar.stop()
365
375
  self._progressbar = None
@@ -384,6 +394,19 @@ class Analysis:
384
394
  if ctr != 0 and ctr % freq == 0:
385
395
  time.sleep(sleep_time)
386
396
 
397
+ @property
398
+ def ram_usage(self) -> float:
399
+ """
400
+ Return the current RAM usage of the Python process, in bytes. The value is updated at most once per second.
401
+ """
402
+
403
+ if time.time() - self._last_ramusage_update > 1:
404
+ self._last_ramusage_update = time.time()
405
+ proc = psutil.Process(os.getpid())
406
+ meminfo = proc.memory_info()
407
+ self._ram_usage = meminfo.rss
408
+ return self._ram_usage if self._ram_usage is not None else -0.1
409
+
387
410
  def __getstate__(self):
388
411
  d = dict(self.__dict__)
389
412
  d.pop("_progressbar", None)
@@ -1952,11 +1952,11 @@ class CFGBase(Analysis):
1952
1952
  # skip empty blocks (that are usually caused by lifting failures)
1953
1953
  continue
1954
1954
  block = func_0.get_block(block_node.addr, block_node.size)
1955
- if block.vex_nostmt.jumpkind not in ("Ijk_Boring", "Ijk_InvalICache"):
1956
- continue
1957
1955
  # Skip alignment blocks
1958
1956
  if self._is_noop_block(self.project.arch, block):
1959
1957
  continue
1958
+ if block.vex_nostmt.jumpkind not in ("Ijk_Boring", "Ijk_InvalICache"):
1959
+ continue
1960
1960
 
1961
1961
  # does the first block transition to the next function?
1962
1962
  transition_found = False
@@ -2001,17 +2001,20 @@ class CFGBase(Analysis):
2001
2001
 
2002
2002
  cfgnode_1_merged = False
2003
2003
  # we only merge two CFG nodes if the first one does not end with a branch instruction
2004
- if (
2005
- len(func_0.block_addrs_set) == 1
2006
- and len(out_edges) == 1
2007
- and out_edges[0][0].addr == cfgnode_0.addr
2008
- and out_edges[0][0].size == cfgnode_0.size
2009
- and self.project.factory.block(cfgnode_0.addr, strict_block_end=True).size > cfgnode_0.size
2010
- ):
2011
- cfgnode_1_merged = True
2012
- self._merge_cfgnodes(cfgnode_0, cfgnode_1)
2013
- adjusted_cfgnodes.add(cfgnode_0)
2014
- adjusted_cfgnodes.add(cfgnode_1)
2004
+ if len(func_0.block_addrs_set) == 1 and len(out_edges) == 1:
2005
+ outedge_src, outedge_dst, outedge_data = out_edges[0]
2006
+ if (
2007
+ outedge_src.addr == cfgnode_0.addr
2008
+ and outedge_src.size == cfgnode_0.size
2009
+ and outedge_dst.addr == cfgnode_1.addr
2010
+ and cfgnode_0.addr + cfgnode_0.size == cfgnode_1.addr
2011
+ and outedge_data.get("type", None) == "transition"
2012
+ and outedge_data.get("stmt_idx", None) == DEFAULT_STATEMENT
2013
+ ):
2014
+ cfgnode_1_merged = True
2015
+ self._merge_cfgnodes(cfgnode_0, cfgnode_1)
2016
+ adjusted_cfgnodes.add(cfgnode_0)
2017
+ adjusted_cfgnodes.add(cfgnode_1)
2015
2018
 
2016
2019
  # Merge it
2017
2020
  func_1 = functions[addr_1]
@@ -28,6 +28,7 @@ from angr.errors import (
28
28
  AngrCFGError,
29
29
  AngrError,
30
30
  AngrSkipJobNotice,
31
+ AngrSyscallError,
31
32
  SimError,
32
33
  SimValueError,
33
34
  SimSolverModeError,
@@ -1806,7 +1807,10 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
1806
1807
 
1807
1808
  # Fix target_addr for syscalls
1808
1809
  if suc_jumpkind.startswith("Ijk_Sys"):
1809
- syscall_proc = self.project.simos.syscall(new_state)
1810
+ try:
1811
+ syscall_proc = self.project.simos.syscall(new_state)
1812
+ except AngrSyscallError:
1813
+ syscall_proc = None
1810
1814
  if syscall_proc is not None:
1811
1815
  target_addr = syscall_proc.addr
1812
1816
 
@@ -846,6 +846,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
846
846
  # exception handling
847
847
  self._exception_handling_by_endaddr = SortedDict()
848
848
 
849
+ self.stage: str = ""
850
+
849
851
  #
850
852
  # Variables used during analysis
851
853
  #
@@ -1077,12 +1079,12 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1077
1079
  # no wide string is found
1078
1080
  return 0
1079
1081
 
1080
- def _scan_for_repeating_bytes(self, start_addr: int, repeating_byte: int, threshold: int = 2) -> int:
1082
+ def _scan_for_repeating_bytes(self, start_addr: int, repeating_byte: int | None, threshold: int = 2) -> int:
1081
1083
  """
1082
1084
  Scan from a given address and determine the occurrences of a given byte.
1083
1085
 
1084
1086
  :param start_addr: The address in memory to start scanning.
1085
- :param repeating_byte: The repeating byte to scan for.
1087
+ :param repeating_byte: The repeating byte to scan for; None for *any* repeating byte.
1086
1088
  :param threshold: The minimum occurrences.
1087
1089
  :return: The occurrences of a given byte.
1088
1090
  """
@@ -1090,12 +1092,15 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1090
1092
  addr = start_addr
1091
1093
 
1092
1094
  repeating_length = 0
1095
+ last_byte = repeating_byte
1093
1096
 
1094
1097
  while self._inside_regions(addr):
1095
1098
  val = self._load_a_byte_as_int(addr)
1096
1099
  if val is None:
1097
1100
  break
1098
- if val == repeating_byte:
1101
+ if last_byte is None:
1102
+ last_byte = val
1103
+ elif val == last_byte:
1099
1104
  repeating_length += 1
1100
1105
  else:
1101
1106
  break
@@ -1249,6 +1254,16 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1249
1254
  self.model.memory_data[start_addr] = MemoryData(start_addr, zeros_length, MemoryDataSort.Alignment)
1250
1255
  start_addr += zeros_length
1251
1256
 
1257
+ # we consider over 16 bytes of any repeated bytes to be bad
1258
+ repeating_byte_length = self._scan_for_repeating_bytes(start_addr, None, threshold=16)
1259
+ if repeating_byte_length:
1260
+ matched_something = True
1261
+ self._seg_list.occupy(start_addr, repeating_byte_length, "nodecode")
1262
+ self.model.memory_data[start_addr] = MemoryData(
1263
+ start_addr, repeating_byte_length, MemoryDataSort.Unknown
1264
+ )
1265
+ start_addr += repeating_byte_length
1266
+
1252
1267
  if not matched_something:
1253
1268
  # umm now it's probably code
1254
1269
  break
@@ -1259,7 +1274,16 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1259
1274
  if start_addr % instr_alignment > 0:
1260
1275
  # occupy those few bytes
1261
1276
  size = instr_alignment - (start_addr % instr_alignment)
1262
- self._seg_list.occupy(start_addr, size, "alignment")
1277
+
1278
+ # to avoid extremely fragmented segmentation, we mark the current segment as the same type as the previous
1279
+ # adjacent segment if its type is nodecode
1280
+ segment_sort = "alignment"
1281
+ if start_addr >= 1:
1282
+ previous_segment_sort = self._seg_list.occupied_by_sort(start_addr - 1)
1283
+ if previous_segment_sort == "nodecode":
1284
+ segment_sort = "nodecode"
1285
+
1286
+ self._seg_list.occupy(start_addr, size, segment_sort)
1263
1287
  self.model.memory_data[start_addr] = MemoryData(start_addr, size, MemoryDataSort.Unknown)
1264
1288
  start_addr = start_addr - start_addr % instr_alignment + instr_alignment
1265
1289
  # trickiness: aligning the start_addr may create a new address that is outside any mapped region.
@@ -1339,6 +1363,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1339
1363
  return job.addr
1340
1364
 
1341
1365
  def _pre_analysis(self):
1366
+ self.stage = "Pre-analysis"
1367
+
1342
1368
  # Create a read-only memory view in loader for faster data loading
1343
1369
  self.project.loader.gen_ro_memview()
1344
1370
 
@@ -1424,6 +1450,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1424
1450
 
1425
1451
  self._job_ctr = 0
1426
1452
 
1453
+ self.stage = "Analysis (Stage 1)"
1454
+
1427
1455
  def _pre_job_handling(self, job: CFGJob): # pylint:disable=arguments-differ
1428
1456
  """
1429
1457
  Some pre job-processing tasks, like update progress bar.
@@ -1459,7 +1487,13 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1459
1487
  percentage = min(
1460
1488
  self._seg_list.occupied_size * max_percentage_stage_1 / self._regions_size, max_percentage_stage_1
1461
1489
  )
1462
- self._update_progress(percentage, cfg=self)
1490
+ ram_usage = self.ram_usage / (1024 * 1024)
1491
+ text = (
1492
+ f"{self.stage} | {len(self.functions)} funcs, {len(self.graph)} blocks | "
1493
+ f"{len(self._indirect_jumps_to_resolve)}/{len(self.indirect_jumps)} IJs | "
1494
+ f"{ram_usage:0.2f} MB RAM"
1495
+ )
1496
+ self._update_progress(percentage, text=text, cfg=self)
1463
1497
 
1464
1498
  def _intra_analysis(self):
1465
1499
  pass
@@ -1758,6 +1792,9 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1758
1792
  self._model.edges_to_repair = remaining_edges_to_repair
1759
1793
 
1760
1794
  def _post_analysis(self):
1795
+
1796
+ self.stage = "Analysis (Stage 2)"
1797
+
1761
1798
  self._repair_edges()
1762
1799
 
1763
1800
  self._make_completed_functions()
@@ -4504,6 +4541,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
4504
4541
 
4505
4542
  if not self._arch_options.has_arm_code and addr % 2 == 0:
4506
4543
  # No ARM code for this architecture!
4544
+ self._seg_list.occupy(real_addr, 2, "nodecode")
4507
4545
  return None, None, None, None
4508
4546
 
4509
4547
  initial_regs = self._get_initial_registers(addr, cfg_job, current_function_addr)
@@ -125,7 +125,17 @@ class ArmElfFastResolver(IndirectJumpResolver):
125
125
  # Note that this function assumes the IRSB is optimized (opt_level > 0)
126
126
  # the logic will be vastly different if the IRSB is not optimized (opt_level == 0)
127
127
 
128
- b = Blade(cfg.graph, addr, -1, cfg=cfg, project=self.project, ignore_sp=True, ignore_bp=True, max_level=2)
128
+ b = Blade(
129
+ cfg.graph,
130
+ addr,
131
+ -1,
132
+ cfg=cfg,
133
+ project=self.project,
134
+ ignore_sp=True,
135
+ ignore_bp=True,
136
+ max_level=2,
137
+ control_dependence=False,
138
+ )
129
139
  sources = [n for n in b.slice.nodes() if b.slice.in_degree(n) == 0]
130
140
  if not sources:
131
141
  return False, []
@@ -5,10 +5,12 @@ import logging
5
5
  import claripy
6
6
  import pyvex
7
7
 
8
+ from angr.knowledge_plugins.propagations import PropagationModel
8
9
  from angr.utils.constants import DEFAULT_STATEMENT
9
10
  from angr.code_location import CodeLocation
10
11
  from angr.blade import Blade
11
12
  from angr.analyses.propagator import vex_vars
13
+ from angr.utils.vex import get_tmp_def_stmt
12
14
  from .resolver import IndirectJumpResolver
13
15
  from .propagator_utils import PropagatorLoadCallback
14
16
 
@@ -47,6 +49,12 @@ class ConstantResolver(IndirectJumpResolver):
47
49
  super().__init__(project, timeless=False)
48
50
  self.max_func_nodes = max_func_nodes
49
51
 
52
+ # stats
53
+ self._resolved = 0
54
+ self._unresolved = 0
55
+ self._cache_hits = 0
56
+ self._props_saved = 0
57
+
50
58
  def filter(self, cfg, addr, func_addr, block, jumpkind):
51
59
  if not cfg.functions.contains_addr(func_addr):
52
60
  # the function does not exist
@@ -122,58 +130,203 @@ class ConstantResolver(IndirectJumpResolver):
122
130
  max_level=3,
123
131
  stop_at_calls=True,
124
132
  cross_insn_opt=True,
133
+ control_dependence=False,
125
134
  )
126
135
  stmt_loc = addr, DEFAULT_STATEMENT
127
- preds = list(b.slice.predecessors(stmt_loc))
128
- while preds:
129
- if len(preds) == 1:
130
- # skip all IMarks
131
- pred_addr, stmt_idx = preds[0]
132
- if stmt_idx != DEFAULT_STATEMENT:
133
- block = self.project.factory.block(pred_addr, cross_insn_opt=True).vex
134
- if isinstance(block.statements[stmt_idx], pyvex.IRStmt.IMark):
135
- preds = list(b.slice.predecessors(preds[0]))
136
- continue
136
+ if self._check_jump_target_is_loaded_from_dynamic_addr(b, stmt_loc):
137
+ # loading from memory - unsupported
138
+ return False, []
139
+ if self._check_jump_target_is_compared_against(b, stmt_loc):
140
+ # the jump/call target is compared against another value, which means it's not deterministic
141
+ # ConstantResolver does not support such cases by design
142
+ return False, []
137
143
 
138
- for pred_addr, stmt_idx in preds:
139
- block = self.project.factory.block(pred_addr, cross_insn_opt=True).vex
140
- if stmt_idx != DEFAULT_STATEMENT:
141
- stmt = block.statements[stmt_idx]
142
- if (
143
- isinstance(stmt, pyvex.IRStmt.WrTmp)
144
- and isinstance(stmt.data, pyvex.IRExpr.Load)
145
- and not isinstance(stmt.data.addr, pyvex.IRExpr.Const)
146
- ):
147
- # loading from memory - unsupported
148
- return False, []
149
- break
144
+ # first check the replacements cache
145
+ resolved_tmp = None
146
+ is_full_func_prop = None
147
+ block_loc = CodeLocation(block.addr, tmp_stmt_idx, ins_addr=tmp_ins_addr)
148
+ tmp_var = vex_vars.VEXTmp(vex_block.next.tmp)
149
+ prop_key = "FCP", func_addr
150
+ cached_prop = cfg.kb.propagations.get(prop_key)
151
+ if cached_prop is not None:
152
+ is_full_func_prop = len(func.block_addrs_set) == cached_prop.function_block_count
153
+ replacements = cached_prop.replacements
154
+ if exists_in_replacements(replacements, block_loc, tmp_var):
155
+ self._cache_hits += 1
156
+ resolved_tmp = replacements[block_loc][tmp_var]
150
157
 
151
- _l.debug("ConstantResolver: Propagating for %r at %#x.", func, addr)
152
- prop = self.project.analyses.FastConstantPropagation(
153
- func,
154
- vex_cross_insn_opt=False,
155
- load_callback=PropagatorLoadCallback(self.project).propagator_load_callback,
156
- )
158
+ if resolved_tmp is None and is_full_func_prop:
159
+ self._props_saved += 1
157
160
 
158
- replacements = prop.replacements
159
- if replacements:
160
- block_loc = CodeLocation(block.addr, tmp_stmt_idx, ins_addr=tmp_ins_addr)
161
- tmp_var = vex_vars.VEXTmp(vex_block.next.tmp)
161
+ if resolved_tmp is None and not is_full_func_prop:
162
+ _l.debug("ConstantResolver: Propagating for %r at %#x.", func, addr)
163
+ prop = self.project.analyses.FastConstantPropagation(
164
+ func,
165
+ vex_cross_insn_opt=False,
166
+ load_callback=PropagatorLoadCallback(self.project).propagator_load_callback,
167
+ )
168
+ # update the cache
169
+ model = PropagationModel(
170
+ prop_key, replacements=prop.replacements, function_block_count=len(func.block_addrs_set)
171
+ )
172
+ cfg.kb.propagations.update(prop_key, model)
162
173
 
163
- if exists_in_replacements(replacements, block_loc, tmp_var):
174
+ replacements = prop.replacements
175
+ if replacements and exists_in_replacements(replacements, block_loc, tmp_var):
164
176
  resolved_tmp = replacements[block_loc][tmp_var]
165
177
 
166
- if (
167
- isinstance(resolved_tmp, claripy.ast.Base)
168
- and resolved_tmp.op == "BVV"
169
- and self._is_target_valid(cfg, resolved_tmp.args[0])
170
- ):
171
- return True, [resolved_tmp.args[0]]
172
- if isinstance(resolved_tmp, int) and self._is_target_valid(cfg, resolved_tmp):
173
- return True, [resolved_tmp]
178
+ if resolved_tmp is not None:
179
+ if (
180
+ isinstance(resolved_tmp, claripy.ast.Base)
181
+ and resolved_tmp.op == "BVV"
182
+ and self._is_target_valid(cfg, resolved_tmp.args[0])
183
+ ):
184
+ self._resolved += 1
185
+ # print(f"{self._resolved} ({self._props_saved} saved, {self._cache_hits} cached) / "
186
+ # f"{self._resolved + self._unresolved}")
187
+ # print(f"+ Function: {func_addr:#x}, block {addr:#x}, target {resolved_tmp.args[0]:#x}")
188
+ return True, [resolved_tmp.args[0]]
189
+ if isinstance(resolved_tmp, int) and self._is_target_valid(cfg, resolved_tmp):
190
+ self._resolved += 1
191
+ # print(f"{self._resolved} ({self._props_saved} saved, {self._cache_hits} cached) / "
192
+ # f"{self._resolved + self._unresolved}")
193
+ # print(f"+ Function: {func_addr:#x}, block {addr:#x}, target {resolved_tmp:#x}")
194
+ return True, [resolved_tmp]
174
195
 
196
+ self._unresolved += 1
197
+ # print(f"{RESOLVED} ({SAVED_PROPS} saved, {HIT_CACHE} cached) / {RESOLVED + UNRESOLVED}")
198
+ # print(f"- Function: {func_addr:#x}, block {addr:#x}, FAILED")
175
199
  return False, []
176
200
 
201
+ def _check_jump_target_is_loaded_from_dynamic_addr(self, b, stmt_loc) -> bool:
202
+ queue: list[tuple[int, int, int]] = [] # depth, block_addr, stmt_idx
203
+ seen_locs: set[tuple[int, int]] = set()
204
+ for block_addr, stmt_idx in b.slice.predecessors(stmt_loc):
205
+ if (block_addr, stmt_idx) in seen_locs:
206
+ continue
207
+ seen_locs.add((block_addr, stmt_idx))
208
+ queue.append((0, block_addr, stmt_idx))
209
+ while queue:
210
+ depth, pred_addr, stmt_idx = queue.pop(0)
211
+ if depth >= 3:
212
+ break
213
+
214
+ # skip all IMarks
215
+ if stmt_idx != DEFAULT_STATEMENT:
216
+ block = self.project.factory.block(pred_addr, cross_insn_opt=True).vex
217
+ stmt = block.statements[stmt_idx]
218
+ if isinstance(stmt, pyvex.IRStmt.IMark):
219
+ for succ_addr, succ_stmt_idx in b.slice.predecessors((pred_addr, stmt_idx)):
220
+ if (succ_addr, succ_stmt_idx) in seen_locs:
221
+ continue
222
+ seen_locs.add((succ_addr, succ_stmt_idx))
223
+ queue.append((depth + 1 if succ_addr != pred_addr else depth, succ_addr, succ_stmt_idx))
224
+ continue
225
+
226
+ if (
227
+ isinstance(stmt, pyvex.IRStmt.WrTmp)
228
+ and isinstance(stmt.data, pyvex.IRExpr.Load)
229
+ and not isinstance(stmt.data.addr, pyvex.IRExpr.Const)
230
+ ):
231
+ # loading from memory
232
+ return True
233
+
234
+ for succ_addr, succ_stmt_idx in b.slice.predecessors((pred_addr, stmt_idx)):
235
+ if (succ_addr, succ_stmt_idx) in seen_locs:
236
+ continue
237
+ seen_locs.add((succ_addr, succ_stmt_idx))
238
+ queue.append((depth + 1 if succ_addr != pred_addr else depth, succ_addr, succ_stmt_idx))
239
+
240
+ return False
241
+
242
+ def _check_jump_target_is_compared_against(self, b, stmt_loc) -> bool:
243
+ # let's find which register the jump uses
244
+ jump_site = self.project.factory.block(stmt_loc[0], cross_insn_opt=True).vex
245
+ if not isinstance(jump_site.next, pyvex.IRExpr.RdTmp):
246
+ return False
247
+ next_tmp = jump_site.next.tmp
248
+ # find its definition
249
+ next_tmp_def = get_tmp_def_stmt(jump_site, next_tmp)
250
+ if next_tmp_def is None:
251
+ return False
252
+ next_tmp_def_stmt = jump_site.statements[next_tmp_def]
253
+ if not (
254
+ isinstance(next_tmp_def_stmt, pyvex.IRStmt.WrTmp) and isinstance(next_tmp_def_stmt.data, pyvex.IRExpr.Get)
255
+ ):
256
+ return False
257
+ next_reg = next_tmp_def_stmt.data.offset
258
+
259
+ # traverse back at most one level and check:
260
+ # - this register has never been updated
261
+ # - a comparison is conducted on this register (via a tmp, most likely)
262
+ queue = []
263
+ seen = set()
264
+ for block_addr, stmt_idx in b.slice.predecessors(stmt_loc):
265
+ if (block_addr, stmt_idx) in seen:
266
+ continue
267
+ seen.add((block_addr, stmt_idx))
268
+ queue.append((0, block_addr, stmt_idx))
269
+ while queue:
270
+ depth, pred_addr, stmt_idx = queue.pop(0)
271
+ if depth > 1:
272
+ continue
273
+
274
+ # skip all IMarks
275
+ pred = pred_addr, stmt_idx
276
+ if stmt_idx != DEFAULT_STATEMENT:
277
+ block = self.project.factory.block(pred_addr, cross_insn_opt=True).vex
278
+ stmt = block.statements[stmt_idx]
279
+ if isinstance(stmt, pyvex.IRStmt.IMark):
280
+ for succ_addr, succ_stmt_idx in b.slice.predecessors(pred):
281
+ if (succ_addr, succ_stmt_idx) in seen:
282
+ continue
283
+ seen.add((succ_addr, succ_stmt_idx))
284
+ queue.append((depth + 1 if succ_addr != pred_addr else depth, succ_addr, succ_stmt_idx))
285
+ continue
286
+
287
+ if isinstance(stmt, pyvex.IRStmt.Put) and stmt.offset == next_reg:
288
+ # this register has been updated before we find a comparison; do not continue along this path
289
+ continue
290
+
291
+ if (
292
+ isinstance(stmt, pyvex.IRStmt.WrTmp)
293
+ and isinstance(stmt.data, pyvex.IRExpr.Binop)
294
+ and stmt.data.op.startswith("Iop_Cmp")
295
+ ):
296
+ # what is it comparing against?
297
+ for arg in stmt.data.args:
298
+ if isinstance(arg, pyvex.IRExpr.RdTmp):
299
+ arg_tmp_def = get_tmp_def_stmt(block, arg.tmp)
300
+ if arg_tmp_def is not None:
301
+ arg_tmp_def_stmt = block.statements[arg_tmp_def]
302
+ if (
303
+ isinstance(arg_tmp_def_stmt, pyvex.IRStmt.WrTmp)
304
+ and isinstance(arg_tmp_def_stmt.data, pyvex.IRExpr.Get)
305
+ and arg_tmp_def_stmt.data.offset == next_reg
306
+ ):
307
+ # the jump target is compared against this register
308
+ return True
309
+ # another case: VEX optimization may have caused the tmp to be stored in the target
310
+ # register. we need handle this case as well.
311
+ if any(
312
+ isinstance(stmt_, pyvex.IRStmt.Put)
313
+ and stmt_.offset == next_reg
314
+ and isinstance(stmt_.data, pyvex.IRExpr.RdTmp)
315
+ and stmt_.data.tmp == arg.tmp
316
+ for stmt_ in block.statements[arg_tmp_def + 1 : stmt_idx]
317
+ ):
318
+ # the jump target is compared against this register
319
+ return True
320
+
321
+ # continue traversing predecessors
322
+ for succ_addr, succ_stmt_idx in b.slice.predecessors(pred):
323
+ if (succ_addr, succ_stmt_idx) in seen:
324
+ continue
325
+ seen.add((succ_addr, succ_stmt_idx))
326
+ queue.append((depth + 1 if succ_addr != pred_addr else depth, succ_addr, succ_stmt_idx))
327
+
328
+ return False
329
+
177
330
  @staticmethod
178
331
  def _find_tmp_write_stmt_and_ins(vex_block, tmp: int) -> tuple[int | None, int | None]:
179
332
  stmt_idx = None