angr 9.2.177__cp310-abi3-macosx_11_0_arm64.whl → 9.2.179__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 (41) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/cfb.py +11 -0
  3. angr/analyses/cfg/cfg_fast.py +15 -0
  4. angr/analyses/decompiler/ail_simplifier.py +69 -1
  5. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +45 -7
  6. angr/analyses/decompiler/clinic.py +24 -10
  7. angr/analyses/decompiler/dirty_rewriters/__init__.py +7 -0
  8. angr/analyses/decompiler/dirty_rewriters/amd64_dirty.py +69 -0
  9. angr/analyses/decompiler/dirty_rewriters/rewriter_base.py +27 -0
  10. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  11. angr/analyses/decompiler/optimization_passes/optimization_pass.py +10 -8
  12. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier.py +44 -6
  13. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier_adv.py +198 -0
  14. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +111 -55
  15. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts_around_comparators.py +72 -1
  16. angr/analyses/decompiler/presets/basic.py +2 -0
  17. angr/analyses/decompiler/presets/fast.py +2 -0
  18. angr/analyses/decompiler/presets/full.py +2 -0
  19. angr/analyses/decompiler/region_simplifiers/expr_folding.py +38 -18
  20. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +10 -4
  21. angr/analyses/decompiler/structured_codegen/c.py +54 -12
  22. angr/analyses/decompiler/structuring/phoenix.py +129 -64
  23. angr/analyses/decompiler/utils.py +26 -8
  24. angr/analyses/disassembly.py +108 -52
  25. angr/analyses/proximity_graph.py +20 -19
  26. angr/analyses/s_propagator.py +23 -21
  27. angr/analyses/smc.py +2 -3
  28. angr/flirt/__init__.py +69 -42
  29. angr/knowledge_plugins/key_definitions/live_definitions.py +2 -1
  30. angr/knowledge_plugins/labels.py +4 -4
  31. angr/rustylib.abi3.so +0 -0
  32. angr/unicornlib.dylib +0 -0
  33. angr/utils/funcid.py +85 -0
  34. angr/utils/ssa/__init__.py +2 -6
  35. angr/utils/types.py +2 -0
  36. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/METADATA +9 -8
  37. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/RECORD +41 -37
  38. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/WHEEL +0 -0
  39. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/entry_points.txt +0 -0
  40. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/licenses/LICENSE +0 -0
  41. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/top_level.txt +0 -0
@@ -34,7 +34,7 @@ class DisassemblyPiece:
34
34
  addr = None
35
35
  ident = float("nan")
36
36
 
37
- def render(self, formatting=None):
37
+ def render(self, formatting=None) -> list[str]:
38
38
  x = self._render(formatting)
39
39
  if len(x) == 1:
40
40
  return [self.highlight(x[0], formatting)]
@@ -73,7 +73,7 @@ class DisassemblyPiece:
73
73
  pass
74
74
  return string
75
75
 
76
- def __eq__(self, other):
76
+ def __eq__(self, other) -> bool:
77
77
  return False
78
78
 
79
79
 
@@ -175,7 +175,7 @@ class Instruction(DisassemblyPiece):
175
175
  self.arch = self.project.arch
176
176
  self.format = ""
177
177
  self.components = ()
178
- self.opcode = None
178
+ self.opcode: Opcode = None # type:ignore
179
179
  self.operands = []
180
180
 
181
181
  # the following members will be filled in after dissecting the instruction
@@ -261,7 +261,11 @@ class Instruction(DisassemblyPiece):
261
261
  if i > 0 and opr_pieces[i - 1] == "-":
262
262
  v = -v
263
263
  cur_operand.pop()
264
- cur_operand.append(Value(v, with_sign))
264
+ with_pound_sign = False
265
+ if i > 0 and opr_pieces[i - 1] == "#":
266
+ with_pound_sign = True
267
+ cur_operand.pop()
268
+ cur_operand.append(Value(v, with_sign, render_with_pound_sign=with_pound_sign))
265
269
  elif p[0].isalpha() and p in self.arch.registers:
266
270
  cur_operand.append(Register(p))
267
271
  else:
@@ -592,7 +596,7 @@ class Opcode(DisassemblyPiece):
592
596
  class Operand(DisassemblyPiece):
593
597
  def __init__(self, op_num, children, parentinsn):
594
598
  self.addr = parentinsn.addr
595
- self.children = children
599
+ self.children: list = children
596
600
  self.parentinsn = parentinsn
597
601
  self.op_num = op_num
598
602
  self.ident = (self.addr, "operand", self.op_num)
@@ -639,14 +643,33 @@ class Operand(DisassemblyPiece):
639
643
  operand = cls(op_num, children, parentinsn)
640
644
 
641
645
  # Post-processing
642
- if cls is MemoryOperand and parentinsn.arch.name in {"AMD64"} and len(operand.values) == 2:
646
+ if cls is MemoryOperand:
647
+ operand = Operand._post_process_memory_operand(parentinsn, operand)
648
+
649
+ return operand
650
+
651
+ @staticmethod
652
+ def _post_process_memory_operand(parentinsn: Instruction, operand: MemoryOperand) -> Operand:
653
+ archname = parentinsn.arch.name
654
+ if archname in {"AMD64", "ARM", "ARMEL", "ARMHF", "ARMCortexM"} and len(operand.values) == 2:
643
655
  op0, op1 = operand.values
644
656
  if type(op0) is Register and op0.is_ip and type(op1) is Value:
645
- # Indirect addressing in x86_64
657
+ # Indirect addressing in x86-64 and ARM
646
658
  # 400520 push [rip+0x200782] ==> 400520 push [0x600ca8]
647
- absolute_addr = parentinsn.addr + parentinsn.size + op1.val
659
+ # 80410e6 ldr r1, [pc, #0x98] ==> 80410e6 ldr r1, [0x8041182]
660
+ match archname:
661
+ case "AMD64":
662
+ absolute_addr = parentinsn.addr + parentinsn.size + op1.val
663
+ case "ARM" | "ARMEL" | "ARMHF" | "ARMCortexM":
664
+ if parentinsn.addr & 1:
665
+ # thumb mode
666
+ absolute_addr = (parentinsn.addr & 0xFFFF_FFFE) + 4 + op1.val
667
+ else:
668
+ # arm mode
669
+ absolute_addr = parentinsn.addr + 8 + op1.val
670
+ case _:
671
+ raise AngrTypeError(f"Unsupported architecture {archname} for post-processing memory operand.")
648
672
  return MemoryOperand(1, [*operand.prefix, "[", Value(absolute_addr, False), "]"], parentinsn)
649
-
650
673
  return operand
651
674
 
652
675
 
@@ -680,13 +703,15 @@ class MemoryOperand(Operand):
680
703
  # [ '[', Register, ']' ]
681
704
  # or
682
705
  # [ Value, '(', Register, ')' ]
706
+ # or
707
+ # [ Register, ",", Value]
683
708
 
684
709
  # it will be converted into more meaningful and Pythonic properties
685
710
 
686
711
  self.segment_selector = None
687
712
  self.prefix = []
688
713
  self.suffix_str = "" # could be arm pre index mark "!"
689
- self.values = []
714
+ self.values: list[str | DisassemblyPiece] = []
690
715
  self.offset = []
691
716
  # offset_location
692
717
  # - prefix: -0xff00($gp)
@@ -698,6 +723,10 @@ class MemoryOperand(Operand):
698
723
  # - curly: {rax+0x10}
699
724
  # - paren: (rax+0x10)
700
725
  self.values_style = "square"
726
+ # separator (between register and value)
727
+ # - "": rax+0x10
728
+ # - "comma": r0, #0x10
729
+ self.separator = ""
701
730
 
702
731
  try:
703
732
  if "[" in self.children:
@@ -711,8 +740,8 @@ class MemoryOperand(Operand):
711
740
  l.error("Failed to parse operand children %s. Please report to Fish.", self.children)
712
741
 
713
742
  # setup all dummy properties
714
- self.prefix = None
715
- self.values = None
743
+ self.prefix = []
744
+ self.values = []
716
745
 
717
746
  def _parse_memop_squarebracket(self):
718
747
  if self.children[0] != "[":
@@ -746,6 +775,27 @@ class MemoryOperand(Operand):
746
775
  raise ValueError
747
776
 
748
777
  self.values = self.children[square_bracket_pos + 1 : close_square_pos]
778
+ if len(self.values) >= 3 and self.values[1] == ",":
779
+ # arm style memory operand: [r0, #0x10], or [pc, r3, lsl #1]
780
+ self.separator = "comma"
781
+ # remove all commas and consolidate all remaining strings
782
+ new_values = []
783
+ last_item = None
784
+ for v in self.values:
785
+ if v == ",":
786
+ if last_item is not None:
787
+ new_values.append(last_item)
788
+ last_item = None
789
+ elif isinstance(v, str):
790
+ last_item = v if last_item is None else last_item + v
791
+ else:
792
+ if last_item is not None:
793
+ new_values.append(last_item)
794
+ last_item = None
795
+ new_values.append(v)
796
+ if last_item is not None:
797
+ new_values.append(last_item)
798
+ self.values = new_values
749
799
 
750
800
  def _parse_memop_paren(self):
751
801
  offset = []
@@ -801,7 +851,8 @@ class MemoryOperand(Operand):
801
851
  if custom_values_str is not None:
802
852
  value_str = custom_values_str
803
853
  else:
804
- value_str = "".join(x.render(formatting)[0] if not isinstance(x, (bytes, str)) else x for x in self.values)
854
+ sep = "," if self.separator == "comma" else ""
855
+ value_str = sep.join(x.render(formatting)[0] if not isinstance(x, str) else x for x in self.values)
805
856
 
806
857
  if values_style == "curly":
807
858
  left_paren, right_paren = "{", "}"
@@ -811,7 +862,7 @@ class MemoryOperand(Operand):
811
862
  left_paren, right_paren = "[", "]"
812
863
 
813
864
  if self.offset:
814
- offset_str = "".join(x.render(formatting)[0] if not isinstance(x, (bytes, str)) else x for x in self.offset)
865
+ offset_str = "".join(x.render(formatting)[0] if not isinstance(x, str) else x for x in self.offset)
815
866
 
816
867
  # combine values and offsets according to self.offset_location
817
868
  if self.offset_location == "prefix":
@@ -829,9 +880,7 @@ class MemoryOperand(Operand):
829
880
  prefix_str += " "
830
881
 
831
882
  if self.offset:
832
- offset_str = "".join(
833
- x.render(formatting)[0] if not isinstance(x, (bytes, str)) else x for x in self.offset
834
- )
883
+ offset_str = "".join(x.render(formatting)[0] if not isinstance(x, str) else x for x in self.offset)
835
884
 
836
885
  # combine values and offsets according to self.offset_location
837
886
  if self.offset_location == "prefix":
@@ -873,12 +922,14 @@ class Register(OperandPiece):
873
922
 
874
923
 
875
924
  class Value(OperandPiece):
876
- def __init__(self, val, render_with_sign):
925
+ def __init__(self, val, render_with_sign, render_with_pound_sign: bool = False):
877
926
  self.val = val
878
927
  self.render_with_sign = render_with_sign
928
+ self.render_with_pound_sign = render_with_pound_sign
879
929
 
880
930
  @property
881
931
  def project(self):
932
+ assert self.parentop is not None
882
933
  return self.parentop.parentinsn.project
883
934
 
884
935
  def __eq__(self, other):
@@ -888,26 +939,26 @@ class Value(OperandPiece):
888
939
  if formatting is not None:
889
940
  try:
890
941
  style = formatting["int_styles"][self.ident]
891
- if style[0] == "hex":
892
- if self.render_with_sign:
893
- return [f"{self.val:+#x}"]
894
- return [f"{self.val:#x}"]
895
- if style[0] == "dec":
896
- if self.render_with_sign:
897
- return [f"{self.val:+d}"]
898
- return [str(self.val)]
899
942
  if style[0] == "label":
900
943
  labeloffset = style[1]
901
944
  if labeloffset == 0:
902
945
  lbl = self.project.kb.labels[self.val]
903
946
  return [lbl]
904
- return [
905
- "{}{}{:#+x}".format(
906
- "+" if self.render_with_sign else "",
907
- self.project.kb.labels[self.val + labeloffset],
908
- labeloffset,
909
- )
910
- ]
947
+ s = "{}{}{:#+x}".format(
948
+ "+" if self.render_with_sign else "",
949
+ self.project.kb.labels[self.val + labeloffset],
950
+ labeloffset,
951
+ )
952
+ elif style[0] == "hex":
953
+ s = f"{self.val:+#x}" if self.render_with_sign else f"{self.val:#x}"
954
+ if self.render_with_pound_sign:
955
+ s = "#" + s
956
+ else: # "dec"
957
+ s = f"{self.val:+d}" if self.render_with_sign else str(self.val)
958
+
959
+ if self.render_with_pound_sign:
960
+ s = "#" + s
961
+ return [s]
911
962
  except KeyError:
912
963
  pass
913
964
 
@@ -927,9 +978,10 @@ class Value(OperandPiece):
927
978
  return [("+" if self.render_with_sign else "") + lbl]
928
979
  if func is not None:
929
980
  return [func.demangled_name]
930
- if self.render_with_sign:
931
- return [f"{self.val:+#x}"]
932
- return [f"{self.val:#x}"]
981
+ s = f"{self.val:+#x}" if self.render_with_sign else f"{self.val:#x}"
982
+ if self.render_with_pound_sign:
983
+ s = "#" + s
984
+ return [s]
933
985
 
934
986
 
935
987
  class Comment(DisassemblyPiece):
@@ -1079,10 +1131,11 @@ class Disassembly(Analysis):
1079
1131
  if irsb.statements is not None:
1080
1132
  if pcode is not None and isinstance(self.project.factory.default_engine, pcode.HeavyPcodeMixin):
1081
1133
  addr = None
1082
- for stmt_idx, op in enumerate(irsb._ops):
1134
+ for stmt_idx, op in enumerate(irsb._ops): # type:ignore
1083
1135
  if op.opcode == pypcode.OpCode.IMARK:
1084
1136
  addr = op.inputs[0].offset
1085
1137
  else:
1138
+ assert addr is not None
1086
1139
  addr_to_ops_map[addr].append(IROp(addr, stmt_idx, op, irsb))
1087
1140
  else:
1088
1141
  for seq, stmt in enumerate(irsb.statements):
@@ -1166,22 +1219,23 @@ class Disassembly(Analysis):
1166
1219
  buf = []
1167
1220
 
1168
1221
  if formatting is None:
1222
+ colors = (
1223
+ {
1224
+ "address": "gray",
1225
+ "bytes": "cyan",
1226
+ "edge": "yellow",
1227
+ Label: "bright_yellow",
1228
+ ConstantOperand: "cyan",
1229
+ MemoryOperand: "yellow",
1230
+ Comment: "gray",
1231
+ Hook: "green",
1232
+ }
1233
+ if ansi_color_enabled and color
1234
+ else {}
1235
+ )
1169
1236
  formatting = {
1170
- "colors": (
1171
- {
1172
- "address": "gray",
1173
- "bytes": "cyan",
1174
- "edge": "yellow",
1175
- Label: "bright_yellow",
1176
- ConstantOperand: "cyan",
1177
- MemoryOperand: "yellow",
1178
- Comment: "gray",
1179
- Hook: "green",
1180
- }
1181
- if ansi_color_enabled and color
1182
- else {}
1183
- ),
1184
- "format_callback": lambda item, s: ansi_color(s, formatting["colors"].get(type(item), None)),
1237
+ "colors": colors,
1238
+ "format_callback": lambda item, s: ansi_color(s, colors.get(type(item), None)),
1185
1239
  }
1186
1240
 
1187
1241
  def col(item: Any) -> str | None:
@@ -1234,12 +1288,14 @@ class Disassembly(Analysis):
1234
1288
  # Format the instruction's address, bytes, disassembly, and comment
1235
1289
  s_plain = format_address(item.addr, False)
1236
1290
  s = format_address(item.addr)
1291
+ bytes_column = 0
1237
1292
  if show_bytes:
1238
1293
  bytes_column = len(s_plain)
1239
1294
  s_plain += format_bytes(insn_bytes[0], False)
1240
1295
  s += format_bytes(insn_bytes[0])
1241
1296
  s_plain += item.render()[0]
1242
1297
  s += item.render(formatting)[0]
1298
+ comment_column = 0
1243
1299
  if comment is not None:
1244
1300
  comment_column = len(s_plain)
1245
1301
  s += format_comment(comment.text[0])
@@ -357,26 +357,27 @@ class ProximityGraphAnalysis(Analysis):
357
357
  def _handle_Call(
358
358
  stmt_idx: int, stmt: ailment.Stmt.Call, block: ailment.Block | None # pylint:disable=unused-argument
359
359
  ): # pylint:disable=unused-argument
360
- func_node = self.kb.functions[stmt.target.value]
361
- ref_at = {stmt.ins_addr}
362
-
363
- # extract arguments
364
- args = []
365
- if stmt.args:
366
- for arg in stmt.args:
367
- self._arg_handler(arg, args, string_refs)
368
-
369
- if (
370
- self._expand_funcs and func_node.addr in self._expand_funcs
371
- ): # pylint:disable=unsupported-membership-test
372
- new_node = FunctionProxiNode(func_node, ref_at=ref_at)
373
- if new_node not in to_expand:
374
- to_expand.append(new_node)
375
- else:
376
- new_node = CallProxiNode(func_node, ref_at=ref_at, args=tuple(args) if args is not None else None)
360
+ if isinstance(stmt.target, ailment.Expr.Const) and self.kb.functions.contains_addr(stmt.target.value):
361
+ func_node = self.kb.functions[stmt.target.value]
362
+ ref_at = {stmt.ins_addr}
363
+
364
+ # extract arguments
365
+ args = []
366
+ if stmt.args:
367
+ for arg in stmt.args:
368
+ self._arg_handler(arg, args, string_refs)
369
+
370
+ if (
371
+ self._expand_funcs and func_node.addr in self._expand_funcs
372
+ ): # pylint:disable=unsupported-membership-test
373
+ new_node = FunctionProxiNode(func_node, ref_at=ref_at)
374
+ if new_node not in to_expand:
375
+ to_expand.append(new_node)
376
+ else:
377
+ new_node = CallProxiNode(func_node, ref_at=ref_at, args=tuple(args) if args is not None else None)
377
378
 
378
- # stmt has been properly handled, add the proxi node to the list
379
- self.handled_stmts.append(new_node)
379
+ # stmt has been properly handled, add the proxi node to the list
380
+ self.handled_stmts.append(new_node)
380
381
 
381
382
  # This should have the same functionality as the previous handler
382
383
  def _handle_CallExpr(
@@ -15,6 +15,7 @@ from angr.ailment.expression import (
15
15
  Load,
16
16
  Convert,
17
17
  Expression,
18
+ Tmp,
18
19
  )
19
20
  from angr.ailment.statement import Assignment, Store, Return, Jump, ConditionalJump
20
21
 
@@ -164,7 +165,7 @@ class SPropagatorAnalysis(Analysis):
164
165
  assert v is not None
165
166
  const_vvars[vvar_id] = v
166
167
  for vvar_at_use, useloc in vvar_uselocs[vvar_id]:
167
- replacements[useloc][vvar_at_use] = v
168
+ self.replace(replacements, useloc, vvar_at_use, v)
168
169
  continue
169
170
 
170
171
  v = phi_assignment_get_src(stmt)
@@ -185,7 +186,7 @@ class SPropagatorAnalysis(Analysis):
185
186
  const_value = const_vvars[next(iter(all_int_src_varids))]
186
187
  const_vvars[vvar.varid] = const_value
187
188
  for vvar_at_use, useloc in vvar_uselocs[vvar.varid]:
188
- replacements[useloc][vvar_at_use] = const_value
189
+ self.replace(replacements, useloc, vvar_at_use, const_value)
189
190
 
190
191
  # function mode only
191
192
  if self.mode == "function":
@@ -226,7 +227,7 @@ class SPropagatorAnalysis(Analysis):
226
227
  if can_replace:
227
228
  # we can propagate this load because there is no store between its def and use
228
229
  for vvar_used, vvar_useloc in vvar_uselocs_set:
229
- replacements[vvar_useloc][vvar_used] = stmt.src
230
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
230
231
  continue
231
232
 
232
233
  if (
@@ -241,7 +242,7 @@ class SPropagatorAnalysis(Analysis):
241
242
  uselocs = {loc for _, loc in vvar_uselocs_set}
242
243
  if self.is_vvar_used_for_addr_loading_switch_case(uselocs, blocks) and not has_tmp_expr(stmt.src):
243
244
  for vvar_used, vvar_useloc in vvar_uselocs_set:
244
- replacements[vvar_useloc][vvar_used] = stmt.src
245
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
245
246
  # mark the vvar as dead and should be removed
246
247
  self.model.dead_vvar_ids.add(vvar.varid)
247
248
  continue
@@ -255,7 +256,7 @@ class SPropagatorAnalysis(Analysis):
255
256
  and not has_tmp_expr(stmt.src)
256
257
  ):
257
258
  # we can propagate this load because there is no store between its def and use
258
- replacements[vvar_useloc][vvar_used] = stmt.src
259
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
259
260
  continue
260
261
 
261
262
  if is_const_and_vvar_assignment(stmt) and not has_tmp_expr(stmt.src):
@@ -272,9 +273,9 @@ class SPropagatorAnalysis(Analysis):
272
273
  and stmt.src.oident == useloc_stmt.dst.oident
273
274
  and stmt.src.category == useloc_stmt.dst.category
274
275
  ):
275
- replacements[vvar_useloc][vvar_used] = stmt.src
276
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
276
277
  else:
277
- replacements[vvar_useloc][vvar_used] = stmt.src
278
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
278
279
  continue
279
280
 
280
281
  else:
@@ -288,7 +289,7 @@ class SPropagatorAnalysis(Analysis):
288
289
  # this vvar is used once if we exclude its uses at ret sites or jump sites. we can
289
290
  # propagate it
290
291
  for vvar_used, vvar_useloc in vvar_uselocs_set:
291
- replacements[vvar_useloc][vvar_used] = stmt.src
292
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
292
293
  continue
293
294
 
294
295
  if (
@@ -304,7 +305,7 @@ class SPropagatorAnalysis(Analysis):
304
305
  # remove duplicate use locs (e.g., if the variable is used multiple times by the
305
306
  # same statement) - but ensure stmt is simple enough
306
307
  for vvar_used, vvar_useloc in vvar_uselocs_set:
307
- replacements[vvar_useloc][vvar_used] = stmt.src
308
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
308
309
  continue
309
310
 
310
311
  # special logic for global variables: if it's used once or multiple times, and the variable is never
@@ -332,7 +333,7 @@ class SPropagatorAnalysis(Analysis):
332
333
  )
333
334
  if not gv_updated:
334
335
  for vvar_used, vvar_useloc in vvar_uselocs_set:
335
- replacements[vvar_useloc][vvar_used] = stmt.src
336
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
336
337
  continue
337
338
 
338
339
  for vvar_id, uselocs in vvar_uselocs.items():
@@ -353,7 +354,7 @@ class SPropagatorAnalysis(Analysis):
353
354
  if sp_bits is not None and vvar.bits < sp_bits:
354
355
  # truncation needed
355
356
  v = Convert(None, sp_bits, vvar.bits, False, v)
356
- replacements[useloc][vvar_at_use] = v
357
+ self.replace(replacements, useloc, vvar_at_use, v)
357
358
  continue
358
359
  if not self._bp_as_gpr and vvar.oident == self.project.arch.bp_offset:
359
360
  bp_bits = (
@@ -368,7 +369,7 @@ class SPropagatorAnalysis(Analysis):
368
369
  if bp_bits is not None and vvar.bits < bp_bits:
369
370
  # truncation needed
370
371
  v = Convert(None, bp_bits, vvar.bits, False, v)
371
- replacements[useloc][vvar_at_use] = v
372
+ self.replace(replacements, useloc, vvar_at_use, v)
372
373
  continue
373
374
 
374
375
  # find all tmp definitions
@@ -390,9 +391,8 @@ class SPropagatorAnalysis(Analysis):
390
391
  if r:
391
392
  # we can propagate it!
392
393
  for tmp_used, tmp_use_stmtidx in tmp_uses:
393
- replacements[
394
- CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
395
- ][tmp_used] = stmt.src
394
+ loc = CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
395
+ self.replace(replacements, loc, tmp_used, stmt.src)
396
396
  continue
397
397
 
398
398
  r = is_const_vvar_tmp_assignment(stmt)
@@ -404,9 +404,8 @@ class SPropagatorAnalysis(Analysis):
404
404
  v = stmt.src
405
405
 
406
406
  for tmp_used, tmp_use_stmtidx in tmp_uses:
407
- replacements[
408
- CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
409
- ][tmp_used] = v
407
+ loc = CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
408
+ self.replace(replacements, loc, tmp_used, v)
410
409
  continue
411
410
 
412
411
  if len(tmp_uses) <= 2 and is_const_vvar_load_dirty_assignment(stmt):
@@ -422,9 +421,8 @@ class SPropagatorAnalysis(Analysis):
422
421
  # we can propagate this load because either we do not consider memory aliasing problem
423
422
  # within the same instruction (blocks must be originally lifted with
424
423
  # CROSS_INSN_OPT=False), or there is no store between its def and use.
425
- replacements[
426
- CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
427
- ][tmp_used] = stmt.src
424
+ loc = CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
425
+ self.replace(replacements, loc, tmp_used, stmt.src)
428
426
 
429
427
  self.model.replacements = replacements
430
428
 
@@ -538,5 +536,9 @@ class SPropagatorAnalysis(Analysis):
538
536
 
539
537
  return g
540
538
 
539
+ @staticmethod
540
+ def replace(replacements: dict, loc, expr: VirtualVariable | Tmp, value: Expression) -> None:
541
+ replacements[loc][expr] = value
542
+
541
543
 
542
544
  register_analysis(SPropagatorAnalysis, "SPropagator")
angr/analyses/smc.py CHANGED
@@ -112,9 +112,8 @@ class SelfModifyingCodeAnalysis(Analysis):
112
112
  if subject is None:
113
113
  subject = self.project.entry
114
114
  if isinstance(subject, str):
115
- try:
116
- addr = self.project.kb.labels.lookup(subject)
117
- except KeyError:
115
+ addr = self.project.kb.labels.lookup(subject)
116
+ if addr is None:
118
117
  addr = self.project.kb.functions[subject].addr
119
118
  elif isinstance(subject, Function):
120
119
  addr = subject.addr
angr/flirt/__init__.py CHANGED
@@ -26,6 +26,60 @@ LIBRARY_TO_SIGNATURES: dict[str, list[FlirtSignature]] = defaultdict(list)
26
26
  STRING_TO_LIBRARIES: dict[str, set[str]] = defaultdict(set)
27
27
 
28
28
 
29
+ def load_signature(sig_path: str, meta_path: str | None = None) -> tuple[str, FlirtSignature] | None:
30
+ """
31
+ Load a single FLIRT signature from a specific path.
32
+
33
+ :param sig_path: Location of the FLIRT signature.
34
+ :return: A FlirtSignature object if loading was successful, None otherwise.
35
+ """
36
+
37
+ # parse it
38
+ try:
39
+ with open(sig_path, "rb") as f:
40
+ sig_parsed = FlirtSignatureParsed.parse(f)
41
+ except FlirtSignatureError:
42
+ return None
43
+
44
+ # is there a meta data file?
45
+ if meta_path is not None and os.path.isfile(meta_path):
46
+ # yes!
47
+ with open(meta_path) as f:
48
+ meta = json.load(f)
49
+
50
+ arch = str(meta.get("arch", "Unknown"))
51
+ platform = str(meta.get("platform", "UnknownOS"))
52
+ os_name = meta.get("os", None)
53
+ os_version = meta.get("os_version", None)
54
+ compiler = meta.get("compiler", None)
55
+ compiler_version = meta.get("compiler_version", None)
56
+ unique_strings = meta.get("unique_strings", None)
57
+
58
+ else:
59
+ # nope... we need to extract information from the signature file
60
+ arch = flirt_arch_to_arch_name(sig_parsed.arch, sig_parsed.app_types)
61
+ platform = flirt_os_type_to_os_name(sig_parsed.os_types)
62
+ os_name = None
63
+ os_version = None
64
+ unique_strings = None
65
+ compiler = None
66
+ compiler_version = None
67
+
68
+ signature = FlirtSignature(
69
+ arch,
70
+ platform,
71
+ sig_parsed.libname,
72
+ sig_path,
73
+ unique_strings=unique_strings,
74
+ compiler=compiler,
75
+ compiler_version=compiler_version,
76
+ os_name=os_name,
77
+ os_version=os_version,
78
+ )
79
+
80
+ return arch, signature
81
+
82
+
29
83
  def load_signatures(path: str) -> None:
30
84
  """
31
85
  Recursively load all FLIRT signatures under a specific path.
@@ -40,52 +94,14 @@ def load_signatures(path: str) -> None:
40
94
  for root, _, filenames in os.walk(path):
41
95
  for filename in filenames:
42
96
  if filename.endswith(".sig"):
43
- # parse it
44
97
  sig_path = os.path.join(root, filename)
45
- try:
46
- with open(sig_path, "rb") as f:
47
- sig_parsed = FlirtSignatureParsed.parse(f)
48
- except FlirtSignatureError:
98
+ meta_path = os.path.join(root, filename[:-4] + ".meta")
99
+ r = load_signature(sig_path, meta_path=meta_path)
100
+ if r is None:
49
101
  _l.warning("Failed to load FLIRT signature file %s.", sig_path)
50
102
  continue
51
103
 
52
- # is there a meta data file?
53
- meta_path = os.path.join(root, filename[:-4] + ".meta")
54
- if os.path.isfile(meta_path):
55
- # yes!
56
- with open(meta_path) as f:
57
- meta = json.load(f)
58
-
59
- arch = str(meta.get("arch", "Unknown"))
60
- platform = str(meta.get("platform", "UnknownOS"))
61
- os_name = meta.get("os", None)
62
- os_version = meta.get("os_version", None)
63
- compiler = meta.get("compiler", None)
64
- compiler_version = meta.get("compiler_version", None)
65
- unique_strings = meta.get("unique_strings", None)
66
-
67
- else:
68
- # nope... we need to extract information from the signature file
69
- arch = flirt_arch_to_arch_name(sig_parsed.arch, sig_parsed.app_types)
70
- platform = flirt_os_type_to_os_name(sig_parsed.os_types)
71
- os_name = None
72
- os_version = None
73
- unique_strings = None
74
- compiler = None
75
- compiler_version = None
76
-
77
- signature = FlirtSignature(
78
- arch,
79
- platform,
80
- sig_parsed.libname,
81
- sig_path,
82
- unique_strings=unique_strings,
83
- compiler=compiler,
84
- compiler_version=compiler_version,
85
- os_name=os_name,
86
- os_version=os_version,
87
- )
88
-
104
+ arch, signature = r
89
105
  FLIRT_SIGNATURES_BY_ARCH[arch].append(signature)
90
106
 
91
107
  # fill in LIBRARY_TO_SIGNATURES and STRING_TO_LIBRARIES
@@ -95,3 +111,14 @@ def load_signatures(path: str) -> None:
95
111
  if sig.unique_strings:
96
112
  for us in sig.unique_strings:
97
113
  STRING_TO_LIBRARIES[us].add(sig.sig_name)
114
+
115
+
116
+ __all__ = (
117
+ "FLIRT_SIGNATURES_BY_ARCH",
118
+ "FS",
119
+ "LIBRARY_TO_SIGNATURES",
120
+ "STRING_TO_LIBRARIES",
121
+ "FlirtSignature",
122
+ "load_signature",
123
+ "load_signatures",
124
+ )