angr 9.2.174__cp310-abi3-macosx_11_0_arm64.whl → 9.2.176__cp310-abi3-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of angr might be problematic. Click here for more details.

Files changed (55) hide show
  1. angr/__init__.py +1 -1
  2. angr/__main__.py +32 -2
  3. angr/analyses/calling_convention/calling_convention.py +12 -0
  4. angr/analyses/cfg/cfg_base.py +1 -1
  5. angr/analyses/cfg/cfg_fast.py +27 -8
  6. angr/analyses/complete_calling_conventions.py +39 -26
  7. angr/analyses/decompiler/ail_simplifier.py +13 -11
  8. angr/analyses/decompiler/ccall_rewriters/rewriter_base.py +5 -1
  9. angr/analyses/decompiler/clinic.py +54 -40
  10. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +3 -3
  11. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +2 -2
  12. angr/analyses/decompiler/peephole_optimizations/__init__.py +4 -4
  13. angr/analyses/decompiler/peephole_optimizations/{inlined_wstrcpy.py → inlined_wcscpy.py} +16 -8
  14. angr/analyses/decompiler/peephole_optimizations/{inlined_wstrcpy_consolidation.py → inlined_wcscpy_consolidation.py} +13 -13
  15. angr/analyses/decompiler/ssailification/rewriting_engine.py +14 -1
  16. angr/analyses/decompiler/structured_codegen/c.py +6 -5
  17. angr/analyses/decompiler/structuring/dream.py +2 -2
  18. angr/analyses/decompiler/structuring/phoenix.py +101 -23
  19. angr/analyses/decompiler/utils.py +1 -1
  20. angr/analyses/smc.py +1 -1
  21. angr/analyses/stack_pointer_tracker.py +4 -3
  22. angr/analyses/typehoon/lifter.py +29 -18
  23. angr/analyses/typehoon/simple_solver.py +157 -50
  24. angr/analyses/typehoon/translator.py +34 -34
  25. angr/analyses/typehoon/typeconsts.py +33 -15
  26. angr/analyses/typehoon/typevars.py +9 -2
  27. angr/analyses/variable_recovery/engine_ail.py +4 -2
  28. angr/analyses/variable_recovery/engine_base.py +4 -1
  29. angr/analyses/variable_recovery/variable_recovery_fast.py +3 -1
  30. angr/calling_conventions.py +2 -1
  31. angr/engines/icicle.py +4 -4
  32. angr/engines/vex/claripy/ccall.py +3 -3
  33. angr/knowledge_plugins/functions/function.py +18 -1
  34. angr/misc/bug_report.py +11 -2
  35. angr/procedures/definitions/__init__.py +88 -20
  36. angr/procedures/definitions/common/glibc.json +3516 -0
  37. angr/procedures/definitions/parse_glibc.py +78 -0
  38. angr/procedures/libc/fgets.py +2 -1
  39. angr/procedures/posix/pthread.py +4 -4
  40. angr/procedures/stubs/format_parser.py +3 -3
  41. angr/rustylib.abi3.so +0 -0
  42. angr/sim_type.py +73 -11
  43. angr/simos/windows.py +1 -1
  44. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +1 -1
  45. angr/unicornlib.dylib +0 -0
  46. angr/utils/constants.py +1 -1
  47. angr/utils/library.py +1 -0
  48. angr/utils/strings.py +20 -0
  49. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/METADATA +5 -5
  50. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/RECORD +54 -52
  51. angr/procedures/definitions/glibc.py +0 -8372
  52. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/WHEEL +0 -0
  53. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/entry_points.txt +0 -0
  54. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/licenses/LICENSE +0 -0
  55. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/top_level.txt +0 -0
@@ -46,8 +46,8 @@ from .rol_ror import RolRorRewriter
46
46
  from .inlined_memcpy import InlinedMemcpy
47
47
  from .inlined_strcpy import InlinedStrcpy
48
48
  from .inlined_strcpy_consolidation import InlinedStrcpyConsolidation
49
- from .inlined_wstrcpy import InlinedWstrcpy
50
- from .inlined_wstrcpy_consolidation import InlinedWstrcpyConsolidation
49
+ from .inlined_wcscpy import InlinedWcscpy
50
+ from .inlined_wcscpy_consolidation import InlinedWcscpyConsolidation
51
51
  from .cmpord_rewriter import CmpORDRewriter
52
52
  from .coalesce_adjacent_shrs import CoalesceAdjacentShiftRights
53
53
  from .a_mul_const_sub_a import AMulConstSubA
@@ -104,8 +104,8 @@ ALL_PEEPHOLE_OPTS: list[type[PeepholeOptimizationExprBase]] = [
104
104
  InlinedMemcpy,
105
105
  InlinedStrcpy,
106
106
  InlinedStrcpyConsolidation,
107
- InlinedWstrcpy,
108
- InlinedWstrcpyConsolidation,
107
+ InlinedWcscpy,
108
+ InlinedWcscpyConsolidation,
109
109
  CmpORDRewriter,
110
110
  CoalesceAdjacentShiftRights,
111
111
  ShlToMul,
@@ -17,17 +17,20 @@ ASCII_PRINTABLES = {ord(x) for x in string.printable}
17
17
  ASCII_DIGITS = {ord(x) for x in string.digits}
18
18
 
19
19
 
20
- class InlinedWstrcpy(PeepholeOptimizationStmtBase):
20
+ class InlinedWcscpy(PeepholeOptimizationStmtBase):
21
21
  """
22
- Simplifies inlined wide string copying logic into calls to wstrcpy.
22
+ Simplifies inlined wide string copying logic into calls to wcscpy.
23
23
  """
24
24
 
25
25
  __slots__ = ()
26
26
 
27
- NAME = "Simplifying inlined wstrcpy"
27
+ NAME = "Simplifying inlined wcscpy"
28
28
  stmt_classes = (Assignment, Store)
29
29
 
30
30
  def optimize(self, stmt: Assignment | Store, stmt_idx: int | None = None, block=None, **kwargs):
31
+ assert self.project is not None
32
+ assert self.kb is not None
33
+
31
34
  if (
32
35
  isinstance(stmt, Assignment)
33
36
  and isinstance(stmt.dst, VirtualVariable)
@@ -48,11 +51,12 @@ class InlinedWstrcpy(PeepholeOptimizationStmtBase):
48
51
  r, s = self.is_integer_likely_a_wide_string(value, value_size, self.project.arch.memory_endness)
49
52
  if r:
50
53
  # replace it with a call to strncpy
54
+ assert s is not None
51
55
  str_id = self.kb.custom_strings.allocate(s)
52
56
  wstr_type = SimTypePointer(SimTypeWideChar()).with_arch(self.project.arch)
53
57
  return Call(
54
58
  stmt.idx,
55
- "wstrncpy",
59
+ "wcsncpy",
56
60
  args=[
57
61
  dst,
58
62
  Const(None, None, str_id, self.project.arch.bits, custom_string=True, type=wstr_type),
@@ -83,6 +87,7 @@ class InlinedWstrcpy(PeepholeOptimizationStmtBase):
83
87
  integer, size = self.stride_to_int(stride)
84
88
  r, s = self.is_integer_likely_a_wide_string(integer, size, Endness.BE, min_length=3)
85
89
  if r:
90
+ assert s is not None
86
91
  # we remove all involved statements whose statement IDs are greater than the current one
87
92
  for _, stmt_idx_, _ in reversed(stride):
88
93
  if stmt_idx_ <= stmt_idx:
@@ -94,7 +99,7 @@ class InlinedWstrcpy(PeepholeOptimizationStmtBase):
94
99
  wstr_type = SimTypePointer(SimTypeWideChar()).with_arch(self.project.arch)
95
100
  return Call(
96
101
  stmt.idx,
97
- "wstrncpy",
102
+ "wcsncpy",
98
103
  args=[
99
104
  dst,
100
105
  Const(None, None, str_id, self.project.arch.bits, custom_string=True, type=wstr_type),
@@ -112,11 +117,14 @@ class InlinedWstrcpy(PeepholeOptimizationStmtBase):
112
117
  size = 0
113
118
  for _, _, v in stride:
114
119
  size += v.size
120
+ assert isinstance(v.value, int)
115
121
  n <<= v.bits
116
122
  n |= v.value
117
123
  return n, size
118
124
 
119
125
  def collect_constant_stores(self, block, starting_stmt_idx: int) -> dict[int, tuple[int, Const | None]]:
126
+ assert self.project is not None
127
+
120
128
  r = {}
121
129
  expected_store_varid: int | None = None
122
130
  starting_stmt = block.statements[starting_stmt_idx]
@@ -224,7 +232,7 @@ class InlinedWstrcpy(PeepholeOptimizationStmtBase):
224
232
  # unsupported endness
225
233
  return False, None
226
234
 
227
- if not (InlinedWstrcpy.even_offsets_are_zero(chars) or InlinedWstrcpy.odd_offsets_are_zero(chars)):
235
+ if not (InlinedWcscpy.even_offsets_are_zero(chars) or InlinedWcscpy.odd_offsets_are_zero(chars)):
228
236
  return False, None
229
237
 
230
238
  if chars and len(chars) >= 2 and chars[-1] == 0 and chars[-2] == 0:
@@ -236,11 +244,11 @@ class InlinedWstrcpy(PeepholeOptimizationStmtBase):
236
244
  return False, None
237
245
 
238
246
  @staticmethod
239
- def is_inlined_wstrncpy(stmt: Statement) -> bool:
247
+ def is_inlined_wcsncpy(stmt: Statement) -> bool:
240
248
  return (
241
249
  isinstance(stmt, Call)
242
250
  and isinstance(stmt.target, str)
243
- and stmt.target == "wstrncpy"
251
+ and stmt.target == "wcsncpy"
244
252
  and stmt.args is not None
245
253
  and len(stmt.args) == 3
246
254
  and isinstance(stmt.args[1], Const)
@@ -6,31 +6,31 @@ from angr.ailment.statement import Call, Store
6
6
 
7
7
  from angr.sim_type import SimTypePointer, SimTypeWideChar
8
8
  from .base import PeepholeOptimizationMultiStmtBase
9
- from .inlined_wstrcpy import InlinedWstrcpy
9
+ from .inlined_wcscpy import InlinedWcscpy
10
10
 
11
11
 
12
- class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
12
+ class InlinedWcscpyConsolidation(PeepholeOptimizationMultiStmtBase):
13
13
  """
14
- Consolidate multiple inlined wstrcpy/wstrncpy calls.
14
+ Consolidate multiple inlined wcscpy/wcsncpy calls.
15
15
  """
16
16
 
17
17
  __slots__ = ()
18
18
 
19
- NAME = "Consolidate multiple inlined wstrncpy calls"
19
+ NAME = "Consolidate multiple inlined wcsncpy calls"
20
20
  stmt_classes = ((Call, Call), (Call, Store))
21
21
 
22
22
  def optimize( # type:ignore
23
23
  self, stmts: list[Call], stmt_idx: int | None = None, block=None, **kwargs
24
24
  ): # pylint:disable=unused-argument
25
25
  last_stmt, stmt = stmts
26
- if InlinedWstrcpy.is_inlined_wstrncpy(last_stmt):
26
+ if InlinedWcscpy.is_inlined_wcsncpy(last_stmt):
27
27
  assert last_stmt.args is not None
28
28
  assert self.kb is not None
29
29
  s_last: bytes = self.kb.custom_strings[last_stmt.args[1].value]
30
30
  addr_last = last_stmt.args[0]
31
31
  new_str = None # will be set if consolidation should happen
32
32
 
33
- if isinstance(stmt, Call) and InlinedWstrcpy.is_inlined_wstrncpy(stmt):
33
+ if isinstance(stmt, Call) and InlinedWcscpy.is_inlined_wcsncpy(stmt):
34
34
  assert stmt.args is not None
35
35
  # consolidating two calls
36
36
  s_curr: bytes = self.kb.custom_strings[stmt.args[1].value]
@@ -50,7 +50,7 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
50
50
  # it's probably the terminating null byte
51
51
  r, s = True, b"\x00\x00"
52
52
  else:
53
- r, s = InlinedWstrcpy.is_integer_likely_a_wide_string(
53
+ r, s = InlinedWcscpy.is_integer_likely_a_wide_string(
54
54
  stmt.data.value, stmt.size, stmt.endness, min_length=1 # type:ignore
55
55
  )
56
56
  if r and s is not None:
@@ -60,7 +60,7 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
60
60
  assert self.project is not None
61
61
  wstr_type = SimTypePointer(SimTypeWideChar()).with_arch(self.project.arch)
62
62
  if new_str.endswith(b"\x00\x00"):
63
- call_name = "wstrcpy"
63
+ call_name = "wcsncpy"
64
64
  new_str_idx = self.kb.custom_strings.allocate(new_str[:-2])
65
65
  args = [
66
66
  last_stmt.args[0],
@@ -68,7 +68,7 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
68
68
  ]
69
69
  prototype = None
70
70
  else:
71
- call_name = "wstrncpy"
71
+ call_name = "wcsncpy"
72
72
  new_str_idx = self.kb.custom_strings.allocate(new_str)
73
73
  args = [
74
74
  last_stmt.args[0],
@@ -96,18 +96,18 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
96
96
  return StackBaseOffset(None, addr.bits, 0), addr.operand.stack_offset
97
97
  if isinstance(addr, BinaryOp):
98
98
  if addr.op == "Add" and isinstance(addr.operands[1], Const) and isinstance(addr.operands[1].value, int):
99
- base_0, offset_0 = InlinedWstrcpyConsolidation._parse_addr(addr.operands[0])
99
+ base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr.operands[0])
100
100
  return base_0, offset_0 + addr.operands[1].value
101
101
  if addr.op == "Sub" and isinstance(addr.operands[1], Const) and isinstance(addr.operands[1].value, int):
102
- base_0, offset_0 = InlinedWstrcpyConsolidation._parse_addr(addr.operands[0])
102
+ base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr.operands[0])
103
103
  return base_0, offset_0 - addr.operands[1].value
104
104
 
105
105
  return addr, 0
106
106
 
107
107
  @staticmethod
108
108
  def _get_delta(addr_0: Expression, addr_1: Expression) -> int | None:
109
- base_0, offset_0 = InlinedWstrcpyConsolidation._parse_addr(addr_0)
110
- base_1, offset_1 = InlinedWstrcpyConsolidation._parse_addr(addr_1)
109
+ base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr_0)
110
+ base_1, offset_1 = InlinedWcscpyConsolidation._parse_addr(addr_1)
111
111
  if base_0.likes(base_1):
112
112
  return offset_1 - offset_0
113
113
  return None
@@ -97,6 +97,19 @@ class SimEngineSSARewriting(
97
97
  self._current_vvar_id += 1
98
98
  return self._current_vvar_id
99
99
 
100
+ #
101
+ # Util functions
102
+ #
103
+
104
+ @staticmethod
105
+ def _is_head_controlled_loop_jump(block, jump_stmt: ConditionalJump) -> bool:
106
+ concrete_targets = []
107
+ if isinstance(jump_stmt.true_target, Const):
108
+ concrete_targets.append(jump_stmt.true_target.value)
109
+ if isinstance(jump_stmt.false_target, Const):
110
+ concrete_targets.append(jump_stmt.false_target.value)
111
+ return not all(block.addr <= t < block.addr + block.original_size for t in concrete_targets)
112
+
100
113
  #
101
114
  # Handlers
102
115
  #
@@ -303,7 +316,7 @@ class SimEngineSSARewriting(
303
316
  new_true_target = self._expr(stmt.true_target) if stmt.true_target is not None else None
304
317
  new_false_target = self._expr(stmt.false_target) if stmt.false_target is not None else None
305
318
 
306
- if self.stmt_idx != len(self.block.statements) - 1:
319
+ if self.stmt_idx != len(self.block.statements) - 1 and self._is_head_controlled_loop_jump(self.block, stmt):
307
320
  # the conditional jump is in the middle of the block (e.g., the block generated from lifting rep stosq).
308
321
  # we need to make a copy of the state and use the state of this point in its successor
309
322
  self.head_controlled_loop_outstate = self.state.copy()
@@ -42,6 +42,7 @@ from angr.utils.constants import is_alignment_mask
42
42
  from angr.utils.library import get_cpp_function_name
43
43
  from angr.utils.loader import is_in_readonly_segment, is_in_readonly_section
44
44
  from angr.utils.types import unpack_typeref, unpack_pointer_and_array, dereference_simtype_by_lib
45
+ from angr.utils.strings import decode_utf16_string
45
46
  from angr.analyses.decompiler.utils import structured_node_is_simple_return
46
47
  from angr.analyses.decompiler.notes.deobfuscated_strings import DeobfuscatedStringsNote
47
48
  from angr.errors import UnsupportedNodeTypeError, AngrRuntimeError
@@ -2269,9 +2270,9 @@ class CConstant(CExpression):
2269
2270
  elif isinstance(self._type, SimTypePointer) and isinstance(self._type.pts_to, SimTypeWideChar):
2270
2271
  refval = self.reference_values[self._type]
2271
2272
  v = (
2272
- refval.content.decode("utf_16_le")
2273
+ decode_utf16_string(refval.content)
2273
2274
  if isinstance(refval, MemoryData)
2274
- else refval.decode("utf_16_le")
2275
+ else decode_utf16_string(refval)
2275
2276
  ) # it's a string
2276
2277
  yield CConstant.str_to_c_str(v, prefix="L"), self
2277
2278
  return
@@ -4076,9 +4077,9 @@ class MakeTypecastsImplicit(CStructuredCodeWalker):
4076
4077
  class FieldReferenceCleanup(CStructuredCodeWalker):
4077
4078
  def handle_CTypeCast(self, obj):
4078
4079
  if isinstance(obj.dst_type, SimTypePointer) and not isinstance(obj.dst_type.pts_to, SimTypeBottom):
4079
- obj = obj.codegen._access_reference(obj.expr, obj.dst_type.pts_to)
4080
- if not isinstance(obj, CTypeCast):
4081
- return self.handle(obj)
4080
+ new_obj = obj.codegen._access_reference(obj.expr, obj.dst_type.pts_to)
4081
+ if not isinstance(new_obj, CTypeCast):
4082
+ return self.handle(new_obj)
4082
4083
  return super().handle_CTypeCast(obj)
4083
4084
 
4084
4085
 
@@ -548,7 +548,7 @@ class DreamStructurer(StructurerBase):
548
548
  cmp = switch_extract_cmp_bounds(last_stmt)
549
549
  if not cmp:
550
550
  return False
551
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
551
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
552
552
 
553
553
  # the real indirect jump
554
554
  if len(addr2nodes[target]) != 1:
@@ -619,7 +619,7 @@ class DreamStructurer(StructurerBase):
619
619
  cmp = switch_extract_cmp_bounds(last_stmt)
620
620
  if not cmp:
621
621
  return False
622
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
622
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
623
623
 
624
624
  jumptable_entries = jump_table.jumptable_entries
625
625
  assert jumptable_entries is not None
@@ -929,23 +929,55 @@ class PhoenixStructurer(StructurerBase):
929
929
  )
930
930
  break_node = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
931
931
  else:
932
- break_stmt = Jump(
933
- None,
934
- Const(None, None, successor.addr, self.project.arch.bits),
935
- target_idx=successor.idx if isinstance(successor, Block) else None,
936
- ins_addr=last_src_stmt.ins_addr,
937
- )
938
- break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
939
- fallthrough_node = next(iter(succ for succ in fullgraph.successors(src) if succ is not dst))
940
- fallthrough_stmt = Jump(
941
- None,
942
- Const(None, None, fallthrough_node.addr, self.project.arch.bits),
943
- target_idx=successor.idx if isinstance(successor, Block) else None,
944
- ins_addr=last_src_stmt.ins_addr,
945
- )
946
- break_node_inner_fallthrough = Block(
947
- last_src_stmt.ins_addr, None, statements=[fallthrough_stmt]
932
+ fallthrough_node = next(
933
+ iter(succ for succ in fullgraph.successors(src) if succ is not dst), None
948
934
  )
935
+ if fallthrough_node is not None:
936
+ # we create a conditional jump that will be converted to a conditional break later
937
+ break_stmt = Jump(
938
+ None,
939
+ Const(None, None, successor.addr, self.project.arch.bits),
940
+ target_idx=successor.idx if isinstance(successor, Block) else None,
941
+ ins_addr=last_src_stmt.ins_addr,
942
+ )
943
+ break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
944
+ fallthrough_stmt = Jump(
945
+ None,
946
+ Const(None, None, fallthrough_node.addr, self.project.arch.bits),
947
+ target_idx=successor.idx if isinstance(successor, Block) else None,
948
+ ins_addr=last_src_stmt.ins_addr,
949
+ )
950
+ break_node_inner_fallthrough = Block(
951
+ last_src_stmt.ins_addr, None, statements=[fallthrough_stmt]
952
+ )
953
+ else:
954
+ # the fallthrough node does not exist in the graph. we create a conditional jump that
955
+ # jumps to an address
956
+ if not isinstance(last_src_stmt, ConditionalJump):
957
+ raise TypeError(f"Unexpected last_src_stmt type {type(last_src_stmt)}")
958
+ other_target = (
959
+ last_src_stmt.true_target
960
+ if isinstance(last_src_stmt.false_target, Const)
961
+ and last_src_stmt.false_target.value == successor.addr
962
+ else last_src_stmt.false_target
963
+ )
964
+ assert other_target is not None
965
+ break_stmt = Jump(
966
+ None,
967
+ Const(None, None, successor.addr, self.project.arch.bits),
968
+ target_idx=successor.idx if isinstance(successor, Block) else None,
969
+ ins_addr=last_src_stmt.ins_addr,
970
+ )
971
+ break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
972
+ fallthrough_stmt = Jump(
973
+ None,
974
+ other_target,
975
+ target_idx=successor.idx if isinstance(successor, Block) else None,
976
+ ins_addr=last_src_stmt.ins_addr,
977
+ )
978
+ break_node_inner_fallthrough = Block(
979
+ last_src_stmt.ins_addr, None, statements=[fallthrough_stmt]
980
+ )
949
981
  break_node = ConditionNode(
950
982
  last_src_stmt.ins_addr,
951
983
  None,
@@ -1454,7 +1486,7 @@ class PhoenixStructurer(StructurerBase):
1454
1486
  )
1455
1487
  if not cmp:
1456
1488
  return False
1457
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
1489
+ cmp_expr, cmp_lb, _cmp_ub = cmp
1458
1490
 
1459
1491
  assert cond_node.addr is not None
1460
1492
  switch_head_addr = cond_node.addr
@@ -1477,7 +1509,7 @@ class PhoenixStructurer(StructurerBase):
1477
1509
  cmp = switch_extract_cmp_bounds(last_stmt)
1478
1510
  if not cmp:
1479
1511
  return False
1480
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
1512
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
1481
1513
 
1482
1514
  switch_head_addr = last_stmt.ins_addr
1483
1515
 
@@ -1849,7 +1881,7 @@ class PhoenixStructurer(StructurerBase):
1849
1881
  cmp = switch_extract_cmp_bounds(last_stmt)
1850
1882
  if not cmp:
1851
1883
  return False
1852
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
1884
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
1853
1885
 
1854
1886
  if isinstance(last_stmt.false_target, Const):
1855
1887
  default_addr = last_stmt.false_target.value
@@ -2148,19 +2180,54 @@ class PhoenixStructurer(StructurerBase):
2148
2180
  jump_node = Block(out_src.addr, 0, statements=[jump_stmt])
2149
2181
  case_node.nodes.append(jump_node)
2150
2182
 
2151
- if out_edges_to_head: # noqa:SIM108
2183
+ # out_dst_succ is the successor within the current region
2184
+ # out_dst_succ_fullgraph is the successor outside the current region
2185
+ if out_edges_to_head:
2152
2186
  # add an edge from SwitchCaseNode to head so that a loop will be structured later
2153
2187
  out_dst_succ = head
2188
+ out_dst_succ_fullgraph = None
2154
2189
  else:
2155
2190
  # add an edge from SwitchCaseNode to its most immediate successor (if there is one)
2156
- out_dst_succ = other_out_edges[0][1] if other_out_edges else None
2191
+ # there might be an in-region successor and an out-of-region successor (especially due to the
2192
+ # introduction of self.dowhile_known_tail_nodes)!
2193
+ # example: 7995a0325b446c462bdb6ae10b692eee2ecadd8e888e9d7729befe4412007afb, function 1400EF820
2194
+ out_dst_succs = []
2195
+ out_dst_succs_fullgraph = []
2196
+ for _, o in other_out_edges:
2197
+ if o in graph:
2198
+ out_dst_succs.append(o)
2199
+ elif o in full_graph:
2200
+ out_dst_succs_fullgraph.append(o)
2201
+ out_dst_succ = sorted(out_dst_succs, key=lambda o: o.addr)[0] if out_dst_succs else None
2202
+ out_dst_succ_fullgraph = (
2203
+ sorted(out_dst_succs_fullgraph, key=lambda o: o.addr)[0] if out_dst_succs_fullgraph else None
2204
+ )
2205
+ if len(out_dst_succs) > 1:
2206
+ assert out_dst_succ is not None
2207
+ l.warning(
2208
+ "Multiple in-region successors detected for switch-case node at %#x. Picking %#x as the "
2209
+ "successor and dropping others.",
2210
+ scnode.addr,
2211
+ out_dst_succ.addr,
2212
+ )
2213
+ if len(out_dst_succs_fullgraph) > 1:
2214
+ assert out_dst_succ_fullgraph is not None
2215
+ l.warning(
2216
+ "Multiple out-of-region successors detected for switch-case node at %#x. Picking %#x as the "
2217
+ "successor and dropping others.",
2218
+ scnode.addr,
2219
+ out_dst_succ_fullgraph.addr,
2220
+ )
2157
2221
 
2158
2222
  if out_dst_succ is not None:
2159
- if out_dst_succ in graph:
2160
- graph.add_edge(scnode, out_dst_succ)
2223
+ graph.add_edge(scnode, out_dst_succ)
2161
2224
  full_graph.add_edge(scnode, out_dst_succ)
2162
2225
  if full_graph.has_edge(head, out_dst_succ):
2163
2226
  full_graph.remove_edge(head, out_dst_succ)
2227
+ if out_dst_succ_fullgraph is not None:
2228
+ full_graph.add_edge(scnode, out_dst_succ_fullgraph)
2229
+ if full_graph.has_edge(head, out_dst_succ_fullgraph):
2230
+ full_graph.remove_edge(head, out_dst_succ_fullgraph)
2164
2231
 
2165
2232
  # fix full_graph if needed: remove successors that are no longer needed
2166
2233
  for _out_src, out_dst in other_out_edges:
@@ -2925,6 +2992,17 @@ class PhoenixStructurer(StructurerBase):
2925
2992
  ordered_nodes.remove(postorder_head)
2926
2993
  acyclic_graph.remove_node(postorder_head)
2927
2994
  node_seq = {nn: (len(ordered_nodes) - idx) for (idx, nn) in enumerate(ordered_nodes)} # post-order
2995
+ if len(node_seq) < len(acyclic_graph):
2996
+ # some nodes are not reachable from head - add them to node_seq as well
2997
+ # but this is usually the result of incorrect structuring, so we may still fail at a later point
2998
+ l.warning("Adding %d unreachable nodes to node_seq", len(acyclic_graph) - len(node_seq))
2999
+ unreachable_nodes = sorted(
3000
+ (nn for nn in acyclic_graph if nn not in node_seq),
3001
+ key=lambda n: (n.addr, (-1 if n.idx is None else n.idx) if hasattr(n, "idx") else 0),
3002
+ )
3003
+ max_seq = max(node_seq.values(), default=0)
3004
+ for i, nn in enumerate(unreachable_nodes):
3005
+ node_seq[nn] = max_seq + i
2928
3006
 
2929
3007
  if all_edges_wo_dominance:
2930
3008
  all_edges_wo_dominance = self._order_virtualizable_edges(full_graph, all_edges_wo_dominance, node_seq)
@@ -989,7 +989,7 @@ def decompile_functions(
989
989
  show_casts: bool = True,
990
990
  base_address: int | None = None,
991
991
  preset: str | None = None,
992
- ) -> str | None:
992
+ ) -> str:
993
993
  """
994
994
  Decompile a binary into a set of functions.
995
995
 
angr/analyses/smc.py CHANGED
@@ -146,7 +146,7 @@ class SelfModifyingCodeAnalysis(Analysis):
146
146
 
147
147
  for n in range(100):
148
148
  self._update_progress(n)
149
- simgr.step(n=3)
149
+ simgr.run(n=3)
150
150
  random.shuffle(simgr.active)
151
151
  simgr.split(from_stash="active", to_stash=simgr.DROP, limit=10)
152
152
 
@@ -787,9 +787,10 @@ class StackPointerTracker(Analysis, ForwardAnalysis):
787
787
  ):
788
788
  sp_adjusted = True
789
789
  sp_v = state.regs[self.project.arch.sp_offset]
790
- sp_v -= Constant(stmt.data.con.value)
791
- state.put(self.project.arch.sp_offset, sp_v, force=True) # sp -= OFFSET
792
- state.put(stmt.offset, Constant(0), force=True) # rax = 0
790
+ if sp_v is not None:
791
+ sp_v -= Constant(stmt.data.con.value)
792
+ state.put(self.project.arch.sp_offset, sp_v, force=True) # sp -= OFFSET
793
+ state.put(stmt.offset, Constant(0), force=True) # rax = 0
793
794
  break
794
795
 
795
796
  callee_cleanups = [
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ import itertools
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  from angr.sim_type import (
@@ -26,45 +27,53 @@ class TypeLifter:
26
27
  Lift SimTypes to type constants.
27
28
  """
28
29
 
29
- __slots__ = ("bits", "memo")
30
+ __slots__ = ("bits", "memo", "named_struct_id_counter", "struct_name_to_idx")
30
31
 
31
32
  def __init__(self, bits: int):
32
33
  if bits not in (32, 64):
33
34
  raise ValueError("TypeLifter only supports 32-bit or 64-bit pointers.")
34
35
  self.bits = bits
35
36
  self.memo = {}
37
+ self.named_struct_id_counter = itertools.count(133337)
38
+ self.struct_name_to_idx = {}
36
39
 
37
40
  def lift(self, ty: SimType):
38
41
  handler = _mapping.get(type(ty), None)
39
42
  if handler is None:
40
- return BottomType()
43
+ return BottomType(name=ty.label)
41
44
 
42
45
  return handler(self, ty)
43
46
 
44
- def _lift_SimTypeChar(self, ty): # pylint:disable=unused-argument,no-self-use
45
- return Int8()
47
+ def _lift_SimTypeChar(self, ty): # pylint:disable=no-self-use
48
+ return Int8(name=ty.label)
46
49
 
47
- def _lift_SimTypeShort(self, ty): # pylint:disable=unused-argument,no-self-use
48
- return Int16()
50
+ def _lift_SimTypeShort(self, ty): # pylint:disable=no-self-use
51
+ return Int16(name=ty.label)
49
52
 
50
- def _lift_SimTypeInt(self, ty): # pylint:disable=unused-argument,no-self-use
51
- return Int32()
53
+ def _lift_SimTypeInt(self, ty): # pylint:disable=no-self-use
54
+ return Int32(name=ty.label)
52
55
 
53
- def _lift_SimTypeLongLong(self, ty): # pylint:disable=unused-argument,no-self-use
54
- return Int64()
56
+ def _lift_SimTypeLongLong(self, ty): # pylint:disable=no-self-use
57
+ return Int64(name=ty.label)
55
58
 
56
59
  def _lift_SimTypePointer(self, ty: SimTypePointer):
57
60
  if self.bits == 32:
58
- return Pointer32(self.lift(ty.pts_to))
61
+ return Pointer32(self.lift(ty.pts_to), name=ty.label)
59
62
  if self.bits == 64:
60
- return Pointer64(self.lift(ty.pts_to))
63
+ return Pointer64(self.lift(ty.pts_to), name=ty.label)
61
64
  raise ValueError(f"Unsupported bits {self.bits}.")
62
65
 
63
66
  def _lift_SimStruct(self, ty: SimStruct) -> TypeConstant | BottomType:
64
67
  if ty in self.memo:
65
68
  return BottomType()
66
69
 
67
- obj = Struct(fields={}, name=ty.name)
70
+ struct_idx = {}
71
+ if ty.name:
72
+ if ty.name not in self.struct_name_to_idx:
73
+ self.struct_name_to_idx[ty.name] = next(self.named_struct_id_counter)
74
+ struct_idx["idx"] = self.struct_name_to_idx[ty.name]
75
+
76
+ obj = Struct(fields={}, name=ty.name, **struct_idx)
68
77
  self.memo[ty] = obj
69
78
  converted_fields = {}
70
79
  field_names = {}
@@ -76,6 +85,7 @@ class TypeLifter:
76
85
  field_names[ty_offsets[field_name]] = field_name
77
86
  obj.fields = converted_fields
78
87
  obj.field_names = field_names
88
+ del self.memo[ty]
79
89
  return obj
80
90
 
81
91
  def _lift_SimCppClass(self, ty: SimCppClass) -> TypeConstant | BottomType:
@@ -94,17 +104,18 @@ class TypeLifter:
94
104
  field_names[ty_offsets[field_name]] = field_name
95
105
  obj.fields = converted_fields
96
106
  obj.field_names = field_names
107
+ del self.memo[ty]
97
108
  return obj
98
109
 
99
110
  def _lift_SimTypeArray(self, ty: SimTypeArray) -> Array:
100
111
  elem_type = self.lift(ty.elem_type)
101
- return Array(elem_type, count=ty.length)
112
+ return Array(elem_type, count=ty.length, name=ty.label)
102
113
 
103
- def _lift_SimTypeFloat(self, ty: SimTypeFloat) -> Float32: # pylint:disable=unused-argument,no-self-use
104
- return Float32()
114
+ def _lift_SimTypeFloat(self, ty: SimTypeFloat) -> Float32: # pylint:disable=no-self-use
115
+ return Float32(name=ty.label)
105
116
 
106
- def _lift_SimTypeDouble(self, ty: SimTypeDouble) -> Float64: # pylint:disable=unused-argument,no-self-use
107
- return Float64()
117
+ def _lift_SimTypeDouble(self, ty: SimTypeDouble) -> Float64: # pylint:disable=no-self-use
118
+ return Float64(name=ty.label)
108
119
 
109
120
 
110
121
  _mapping = {