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.
- angr/__init__.py +1 -1
- angr/ailment/converter_vex.py +1 -1
- angr/ailment/expression.py +5 -1
- angr/analyses/analysis.py +27 -4
- angr/analyses/cfg/cfg_base.py +16 -13
- angr/analyses/cfg/cfg_emulated.py +5 -1
- angr/analyses/cfg/cfg_fast.py +43 -5
- angr/analyses/cfg/indirect_jump_resolvers/arm_elf_fast.py +11 -1
- angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +194 -41
- angr/analyses/decompiler/ail_simplifier.py +19 -5
- angr/analyses/decompiler/callsite_maker.py +33 -17
- angr/analyses/decompiler/condition_processor.py +9 -8
- angr/analyses/decompiler/graph_region.py +19 -0
- angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
- angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
- angr/analyses/decompiler/peephole_optimizations/inlined_memcpy.py +78 -0
- angr/analyses/decompiler/peephole_optimizations/inlined_strcpy.py +67 -10
- angr/analyses/decompiler/peephole_optimizations/inlined_strcpy_consolidation.py +10 -13
- angr/analyses/decompiler/region_identifier.py +22 -1
- angr/analyses/decompiler/structuring/phoenix.py +72 -20
- angr/analyses/decompiler/structuring/recursive_structurer.py +3 -4
- angr/analyses/decompiler/structuring/structurer_nodes.py +3 -0
- angr/analyses/decompiler/utils.py +17 -5
- angr/analyses/deobfuscator/string_obf_finder.py +130 -32
- angr/analyses/s_reaching_definitions/s_rda_view.py +2 -1
- angr/analyses/typehoon/typeconsts.py +3 -1
- angr/blade.py +20 -15
- angr/engines/icicle.py +16 -3
- angr/knowledge_plugins/propagations/propagation_model.py +7 -0
- angr/rustylib.pyd +0 -0
- angr/sim_type.py +16 -1
- angr/state_plugins/history.py +16 -0
- angr/unicornlib.dll +0 -0
- angr/utils/constants.py +1 -1
- angr/utils/graph.py +1 -1
- angr/utils/vex.py +11 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/METADATA +5 -5
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/RECORD +42 -40
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/WHEEL +0 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/entry_points.txt +0 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/licenses/LICENSE +0 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/top_level.txt +0 -0
angr/__init__.py
CHANGED
angr/ailment/converter_vex.py
CHANGED
|
@@ -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,
|
|
609
|
+
VEXExprConverter.tmp(stmt.oldHi, manager.tyenv.sizeof(stmt.oldHi), manager)
|
|
610
610
|
if stmt.oldHi != 0xFFFFFFFF
|
|
611
611
|
else None
|
|
612
612
|
)
|
angr/ailment/expression.py
CHANGED
|
@@ -616,7 +616,11 @@ class Convert(UnaryOp):
|
|
|
616
616
|
self.rounding_mode = rounding_mode
|
|
617
617
|
|
|
618
618
|
def __str__(self):
|
|
619
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
348
|
-
|
|
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)
|
angr/analyses/cfg/cfg_base.py
CHANGED
|
@@ -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
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
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
|
|
angr/analyses/cfg/cfg_fast.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|