angr 9.2.174__cp310-abi3-manylinux_2_28_x86_64.whl → 9.2.176__cp310-abi3-manylinux_2_28_x86_64.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 (54) 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/utils/constants.py +1 -1
  46. angr/utils/library.py +1 -0
  47. angr/utils/strings.py +20 -0
  48. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/METADATA +5 -5
  49. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/RECORD +53 -51
  50. angr/procedures/definitions/glibc.py +0 -8372
  51. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/WHEEL +0 -0
  52. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/entry_points.txt +0 -0
  53. {angr-9.2.174.dist-info → angr-9.2.176.dist-info}/licenses/LICENSE +0 -0
  54. {angr-9.2.174.dist-info → angr-9.2.176.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.174"
5
+ __version__ = "9.2.176"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
angr/__main__.py CHANGED
@@ -6,11 +6,14 @@ import re
6
6
  from typing import TYPE_CHECKING
7
7
  from collections.abc import Generator
8
8
 
9
+ from rich.syntax import Syntax
10
+ from rich.console import Console
11
+
9
12
  import angr
10
13
  from angr.analyses.decompiler import DECOMPILATION_PRESETS
11
14
  from angr.analyses.decompiler.structuring import STRUCTURER_CLASSES, DEFAULT_STRUCTURER
12
15
  from angr.analyses.decompiler.utils import decompile_functions
13
-
16
+ from angr.utils.formatting import ansi_color_enabled
14
17
 
15
18
  if TYPE_CHECKING:
16
19
  from angr.knowledge_plugins.functions import Function
@@ -82,11 +85,27 @@ def decompile(args):
82
85
  base_address=args.base_addr,
83
86
  preset=args.preset,
84
87
  )
85
- print(decompilation)
88
+
89
+ # Determine if we should use syntax highlighting
90
+ should_highlight = ansi_color_enabled and not args.no_colors
91
+
92
+ if should_highlight:
93
+ try:
94
+ console = Console()
95
+ syntax = Syntax(decompilation, "c", theme=args.theme, line_numbers=False)
96
+ console.print(syntax)
97
+ # pylint: disable=broad-exception-caught
98
+ except Exception as e:
99
+ log.warning("Syntax highlighting failed: %s", e)
100
+ # Fall back to plain text if syntax highlighting fails
101
+ print(decompilation)
102
+ else:
103
+ print(decompilation)
86
104
 
87
105
 
88
106
  def main():
89
107
  parser = argparse.ArgumentParser(description="The angr CLI allows you to decompile and analyze binaries.")
108
+ parser.add_argument("--version", action="version", version=angr.__version__)
90
109
  parser.add_argument("binary", help="The path to the binary to analyze.")
91
110
  parser.add_argument(
92
111
  "--catch-exceptions",
@@ -133,6 +152,17 @@ def main():
133
152
  symbols of the binary or as addresses like: 0x401000.""",
134
153
  nargs="+",
135
154
  )
155
+ decompile_cmd_parser.add_argument(
156
+ "--no-colors",
157
+ help="Disable syntax highlighting in the decompiled output.",
158
+ action="store_true",
159
+ default=False,
160
+ )
161
+ decompile_cmd_parser.add_argument(
162
+ "--theme",
163
+ help="The syntax highlighting theme to use (only if rich is installed and colors are enabled).",
164
+ default="dracula",
165
+ )
136
166
 
137
167
  disassemble_cmd_parser = subparsers.add_parser("disassemble", aliases=["dis"], help=disassemble.__doc__)
138
168
  disassemble_cmd_parser.set_defaults(func=disassemble)
@@ -95,6 +95,8 @@ class CallingConventionAnalysis(Analysis):
95
95
  calling convention and arguments. This can be time-consuming if there are many call
96
96
  sites to analyze.
97
97
  :ivar cc: The recovered calling convention for the function.
98
+ :ivar _collect_facts: True if we should run FunctionFactCollector to collect input arguments and return
99
+ value size. False if input arguments and return value size are provided by the user.
98
100
  """
99
101
 
100
102
  def __init__(
@@ -108,6 +110,7 @@ class CallingConventionAnalysis(Analysis):
108
110
  func_graph: networkx.DiGraph | None = None,
109
111
  input_args: list[SimRegArg | SimStackArg] | None = None,
110
112
  retval_size: int | None = None,
113
+ collect_facts: bool = False,
111
114
  ):
112
115
  if func is not None and not isinstance(func, Function):
113
116
  func = self.kb.functions[func]
@@ -121,6 +124,7 @@ class CallingConventionAnalysis(Analysis):
121
124
  self._func_graph = func_graph
122
125
  self._input_args = input_args
123
126
  self._retval_size = retval_size
127
+ self._collect_facts = collect_facts
124
128
 
125
129
  if self._retval_size is not None and self._input_args is None:
126
130
  # retval size will be ignored if input_args is not specified - user error?
@@ -132,6 +136,7 @@ class CallingConventionAnalysis(Analysis):
132
136
  self.cc: SimCC | None = None
133
137
  self.prototype: SimTypeFunction | None = None
134
138
  self.prototype_libname: str | None = None
139
+ self.proto_from_symbol: bool = False
135
140
 
136
141
  if self._cfg is None and "CFGFast" in self.kb.cfgs:
137
142
  self._cfg = self.kb.cfgs["CFGFast"]
@@ -168,6 +173,7 @@ class CallingConventionAnalysis(Analysis):
168
173
  r_demangled = self._analyze_demangled_name(demangled_name)
169
174
  if r_demangled is not None:
170
175
  self.cc, self.prototype, self.prototype_libname = r_demangled
176
+ self.proto_from_symbol = True
171
177
  return
172
178
 
173
179
  if self._function.is_simprocedure:
@@ -242,6 +248,12 @@ class CallingConventionAnalysis(Analysis):
242
248
  self.cc, self.prototype, self.prototype_libname = r_plt
243
249
  return
244
250
 
251
+ # we gotta analyze the function properly
252
+ if self._collect_facts and self._input_args is None and self._retval_size is None:
253
+ facts = self.project.analyses.FunctionFactCollector(self._function, kb=self.kb)
254
+ self._input_args = facts.input_args
255
+ self._retval_size = facts.retval_size
256
+
245
257
  r = self._analyze_function()
246
258
  if r is None:
247
259
  l.warning("Cannot determine calling convention for %r.", self._function)
@@ -937,7 +937,7 @@ class CFGBase(Analysis):
937
937
  """
938
938
 
939
939
  addrs = set()
940
- if isinstance(self._binary, ELF) and self._binary.has_dwarf_info:
940
+ if (isinstance(self._binary, ELF) and self._binary.has_dwarf_info) or isinstance(self._binary, PE):
941
941
  for function_hint in self._binary.function_hints:
942
942
  if function_hint.source == FunctionHintSource.EH_FRAME:
943
943
  addrs.add(function_hint.addr)
@@ -435,6 +435,7 @@ class CFGJobType(Enum):
435
435
  COMPLETE_SCANNING = 2
436
436
  IFUNC_HINTS = 3
437
437
  DATAREF_HINTS = 4
438
+ EH_FRAME_HINTS = 5
438
439
 
439
440
 
440
441
  class CFGJob:
@@ -612,7 +613,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
612
613
  low_priority=False,
613
614
  cfb=None,
614
615
  model=None,
615
- elf_eh_frame=True,
616
+ eh_frame=True,
616
617
  exceptions=True,
617
618
  skip_unmapped_addrs=True,
618
619
  nodecode_window_size=512,
@@ -625,6 +626,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
625
626
  end=None, # deprecated
626
627
  collect_data_references=None, # deprecated
627
628
  extra_cross_references=None, # deprecated
629
+ elf_eh_frame=None, # deprecated
628
630
  **extra_arch_options,
629
631
  ):
630
632
  """
@@ -664,8 +666,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
664
666
  types will be loaded.
665
667
  :param base_state: A state to use as a backer for all memory loads
666
668
  :param bool detect_tail_calls: Enable aggressive tail-call optimization detection.
667
- :param bool elf_eh_frame: Retrieve function starts (and maybe sizes later) from the .eh_frame of ELF
668
- binaries.
669
+ :param bool eh_frame: Retrieve function starts (and maybe sizes later) from the .eh_frame of ELF
670
+ binaries or exception records of PE binaries.
669
671
  :param skip_unmapped_addrs: Ignore all branches into unmapped regions. True by default. You may want to set
670
672
  it to False if you are analyzing manually patched binaries or malware samples.
671
673
  :param indirect_calls_always_return: Should CFG assume indirect calls must return or not. Assuming indirect
@@ -778,13 +780,17 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
778
780
  )
779
781
  force_complete_scan = False
780
782
 
783
+ if elf_eh_frame is not None:
784
+ l.warning('"elf_eh_frame" is deprecated and will be removed soon. Please use "eh_frame" instead.')
785
+ eh_frame = eh_frame or elf_eh_frame
786
+
781
787
  self._pickle_intermediate_results = pickle_intermediate_results
782
788
 
783
789
  self._use_symbols = symbols
784
790
  self._use_function_prologues = function_prologues
785
791
  self._force_smart_scan = force_smart_scan
786
792
  self._force_complete_scan = force_complete_scan
787
- self._use_elf_eh_frame = elf_eh_frame
793
+ self._use_eh_frame = eh_frame
788
794
  self._use_exceptions = exceptions
789
795
  self._check_funcret_max_job = check_funcret_max_job
790
796
 
@@ -841,7 +847,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
841
847
  self._read_addr_to_run = defaultdict(list)
842
848
  self._write_addr_to_run = defaultdict(list)
843
849
 
844
- self._remaining_function_prologue_addrs = None
850
+ self._remaining_eh_frame_addrs: list[int] | None = None
851
+ self._remaining_function_prologue_addrs: list[int] | None = None
845
852
 
846
853
  # exception handling
847
854
  self._exception_handling_by_endaddr = SortedDict()
@@ -1440,9 +1447,6 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1440
1447
  if self._use_symbols:
1441
1448
  starting_points |= self._function_addresses_from_symbols
1442
1449
 
1443
- if self._use_elf_eh_frame:
1444
- starting_points |= self._function_addresses_from_eh_frame
1445
-
1446
1450
  if self._extra_function_starts:
1447
1451
  starting_points |= set(self._extra_function_starts)
1448
1452
 
@@ -1467,6 +1471,9 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1467
1471
 
1468
1472
  self._updated_nonreturning_functions = set()
1469
1473
 
1474
+ if self._use_eh_frame:
1475
+ self._remaining_eh_frame_addrs = sorted(self._function_addresses_from_eh_frame)
1476
+
1470
1477
  if self._use_function_prologues and self.project.concrete_target is None:
1471
1478
  self._remaining_function_prologue_addrs = sorted(self._func_addrs_from_prologues())
1472
1479
 
@@ -1742,6 +1749,18 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1742
1749
  self._insert_job(job)
1743
1750
  return
1744
1751
 
1752
+ if self._use_eh_frame and self._remaining_eh_frame_addrs:
1753
+ while self._remaining_eh_frame_addrs:
1754
+ eh_addr = self._remaining_eh_frame_addrs[0]
1755
+ self._remaining_eh_frame_addrs = self._remaining_eh_frame_addrs[1:]
1756
+ if self._seg_list.is_occupied(eh_addr):
1757
+ continue
1758
+
1759
+ job = CFGJob(eh_addr, eh_addr, "Ijk_Boring", job_type=CFGJobType.EH_FRAME_HINTS)
1760
+ self._insert_job(job)
1761
+ self._register_analysis_job(eh_addr, job)
1762
+ return
1763
+
1745
1764
  if self._use_function_prologues and self._remaining_function_prologue_addrs:
1746
1765
  while self._remaining_function_prologue_addrs:
1747
1766
  prolog_addr = self._remaining_function_prologue_addrs[0]
@@ -17,7 +17,7 @@ from angr.utils.graph import GraphUtils
17
17
  from angr.simos import SimWindows
18
18
  from angr.utils.mp import mp_context, Initializer
19
19
  from angr.knowledge_plugins.cfg import CFGModel
20
- from . import Analysis, register_analysis, VariableRecoveryFast, CallingConventionAnalysis, FactCollector, CFGFast
20
+ from . import Analysis, register_analysis, VariableRecoveryFast, CallingConventionAnalysis, CFGFast
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from angr.calling_conventions import SimCC
@@ -191,10 +191,15 @@ class CompleteCallingConventionsAnalysis(Analysis):
191
191
  self._prioritize_func_addrs = None # no longer useful
192
192
 
193
193
  def _set_function_prototype(
194
- self, func: Function, prototype: SimTypeFunction | None, prototype_libname: str | None
194
+ self,
195
+ func: Function,
196
+ prototype: SimTypeFunction | None,
197
+ prototype_libname: str | None,
198
+ prototype_guessed: bool | None,
195
199
  ) -> None:
196
200
  if func.prototype is None or func.is_prototype_guessed or self._force:
197
- func.is_prototype_guessed = True
201
+ if prototype_guessed is not None:
202
+ func.is_prototype_guessed = prototype_guessed
198
203
  func.prototype = prototype
199
204
  func.prototype_libname = prototype_libname
200
205
 
@@ -204,12 +209,12 @@ class CompleteCallingConventionsAnalysis(Analysis):
204
209
  if self._workers == 0:
205
210
  self._update_progress(0)
206
211
  for idx, func_addr in enumerate(self._func_addrs):
207
- cc, proto, proto_libname, _ = self._analyze_core(func_addr)
212
+ cc, proto, proto_libname, proto_guessed, _ = self._analyze_core(func_addr)
208
213
 
209
214
  func = self.kb.functions.get_by_addr(func_addr)
210
215
  if cc is not None or proto is not None:
211
216
  func.calling_convention = cc
212
- self._set_function_prototype(func, proto, proto_libname)
217
+ self._set_function_prototype(func, proto, proto_libname, proto_guessed)
213
218
  if proto_libname is not None:
214
219
  self.prototype_libnames.add(proto_libname)
215
220
 
@@ -269,10 +274,13 @@ class CompleteCallingConventionsAnalysis(Analysis):
269
274
  # update progress
270
275
  self._update_progress(0)
271
276
  idx = 0
277
+ assert self._results_lock is not None
272
278
  while idx < total_funcs:
273
279
  try:
274
280
  with self._results_lock:
275
- func_addr, cc, proto, proto_libname, varman = self._results.get(True, timeout=0.01)
281
+ func_addr, cc, proto, proto_libname, proto_guessed, varman = self._results.get(
282
+ True, timeout=0.01
283
+ )
276
284
  except queue.Empty:
277
285
  time.sleep(0.1)
278
286
  continue
@@ -280,7 +288,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
280
288
  func = self.kb.functions.get_by_addr(func_addr)
281
289
  if cc is not None or proto is not None:
282
290
  func.calling_convention = cc
283
- self._set_function_prototype(func, proto, proto_libname)
291
+ self._set_function_prototype(func, proto, proto_libname, proto_guessed)
284
292
  if proto_libname is not None:
285
293
  self.prototype_libnames.add(proto_libname)
286
294
 
@@ -317,6 +325,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
317
325
  def _worker_routine(self, worker_id: int, initializer: Initializer):
318
326
  initializer.initialize()
319
327
  idx = 0
328
+ assert self._remaining_funcs is not None and self._func_queue is not None and self._results_lock is not None
320
329
  while self._remaining_funcs.value > 0:
321
330
  try:
322
331
  with self._func_queue_lock:
@@ -327,33 +336,39 @@ class CompleteCallingConventionsAnalysis(Analysis):
327
336
  continue
328
337
 
329
338
  if callee_info is not None:
330
- callee_info: dict[int, tuple[SimCC | None, SimTypeFunction | None, str | None]]
331
- for callee, (callee_cc, callee_proto, callee_proto_libname) in callee_info.items():
339
+ callee_info: dict[int, tuple[SimCC | None, SimTypeFunction | None, str | None, bool | None]]
340
+ for callee, (
341
+ callee_cc,
342
+ callee_proto,
343
+ callee_proto_libname,
344
+ callee_proto_guessed,
345
+ ) in callee_info.items():
332
346
  callee_func = self.kb.functions.get_by_addr(callee)
333
347
  callee_func.calling_convention = callee_cc
334
- self._set_function_prototype(callee_func, callee_proto, callee_proto_libname)
348
+ self._set_function_prototype(callee_func, callee_proto, callee_proto_libname, callee_proto_guessed)
335
349
 
336
350
  idx += 1
337
351
  if self._low_priority and idx % 3 == 0:
338
352
  time.sleep(0.1)
339
353
 
340
354
  try:
341
- cc, proto, proto_libname, varman = self._analyze_core(func_addr)
355
+ cc, proto, proto_libname, proto_guessed, varman = self._analyze_core(func_addr)
342
356
  except Exception: # pylint:disable=broad-except
343
357
  _l.error("Worker %d: Exception occurred during _analyze_core().", worker_id, exc_info=True)
344
- cc, proto, proto_libname, varman = None, None, None, None
358
+ cc, proto, proto_libname, proto_guessed, varman = None, None, None, None, None
345
359
  with self._results_lock:
346
- self._results.put((func_addr, cc, proto, proto_libname, varman))
360
+ self._results.put((func_addr, cc, proto, proto_libname, proto_guessed, varman))
347
361
 
348
362
  def _analyze_core(
349
363
  self, func_addr: int
350
- ) -> tuple[SimCC | None, SimTypeFunction | None, str | None, VariableManagerInternal | None]:
364
+ ) -> tuple[SimCC | None, SimTypeFunction | None, str | None, bool | None, VariableManagerInternal | None]:
351
365
  func = self.kb.functions.get_by_addr(func_addr)
352
366
  if func.ran_cca:
353
367
  return (
354
368
  func.calling_convention,
355
369
  func.prototype,
356
370
  func.prototype_libname,
371
+ func.is_prototype_guessed,
357
372
  self.kb.variables.get_function_manager(func_addr),
358
373
  )
359
374
 
@@ -365,7 +380,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
365
380
  # special case: we don't have a PCode-engine variable recovery analysis for PCode architectures!
366
381
  if ":" in self.project.arch.name and self._func_graphs and func.addr in self._func_graphs:
367
382
  # this is a pcode architecture
368
- return None, None, None, None
383
+ return None, None, None, None, None
369
384
 
370
385
  _l.info("Performing variable recovery on %r...", func)
371
386
  try:
@@ -378,17 +393,14 @@ class CompleteCallingConventionsAnalysis(Analysis):
378
393
  func.addr,
379
394
  exc_info=True,
380
395
  )
381
- return None, None, None, None
382
-
383
- kwargs = {}
384
- if self.mode == CallingConventionAnalysisMode.FAST:
385
- facts = self.project.analyses[FactCollector].prep(kb=self.kb)(func)
386
- kwargs["input_args"] = facts.input_args
387
- kwargs["retval_size"] = facts.retval_size
396
+ return None, None, None, None, None
388
397
 
389
398
  # determine the calling convention of each function
390
399
  cc_analysis = self.project.analyses[CallingConventionAnalysis].prep(kb=self.kb)(
391
- func, cfg=self._cfg, analyze_callsites=self._analyze_callsites, **kwargs
400
+ func,
401
+ cfg=self._cfg,
402
+ analyze_callsites=self._analyze_callsites,
403
+ collect_facts=self.mode == CallingConventionAnalysisMode.FAST,
392
404
  )
393
405
 
394
406
  if cc_analysis.cc is not None:
@@ -397,10 +409,11 @@ class CompleteCallingConventionsAnalysis(Analysis):
397
409
  cc_analysis.cc,
398
410
  cc_analysis.prototype,
399
411
  cc_analysis.prototype_libname if cc_analysis.prototype_libname is not None else func.prototype_libname,
412
+ not cc_analysis.proto_from_symbol,
400
413
  self.kb.variables.get_function_manager(func_addr),
401
414
  )
402
415
  _l.info("Cannot determine calling convention for %r.", func)
403
- return None, None, None, self.kb.variables.get_function_manager(func_addr)
416
+ return None, None, None, None, self.kb.variables.get_function_manager(func_addr)
404
417
 
405
418
  def prioritize_functions(self, func_addrs_to_prioritize: Iterable[int]):
406
419
  """
@@ -424,12 +437,12 @@ class CompleteCallingConventionsAnalysis(Analysis):
424
437
 
425
438
  def _get_callees_cc_prototypes(
426
439
  self, caller_func_addr: int
427
- ) -> dict[int, tuple[SimCC | None, SimTypeFunction | None, str | None]]:
440
+ ) -> dict[int, tuple[SimCC | None, SimTypeFunction | None, str | None, bool | None]]:
428
441
  d = {}
429
442
  for callee in self.kb.functions.callgraph.successors(caller_func_addr):
430
443
  if callee != caller_func_addr and callee not in d:
431
444
  func = self.kb.functions.get_by_addr(callee)
432
- tpl = func.calling_convention, func.prototype, func.prototype_libname
445
+ tpl = func.calling_convention, func.prototype, func.prototype_libname, func.is_prototype_guessed
433
446
  d[callee] = tpl
434
447
  return d
435
448
 
@@ -168,6 +168,7 @@ class AILSimplifier(Analysis):
168
168
  fold_callexprs_into_conditions=False,
169
169
  use_callee_saved_regs_at_return=True,
170
170
  rewrite_ccalls=True,
171
+ rename_ccalls=True,
171
172
  removed_vvar_ids: set[int] | None = None,
172
173
  arg_vvars: dict[int, tuple[VirtualVariable, SimVariable]] | None = None,
173
174
  avoid_vvar_ids: set[int] | None = None,
@@ -188,6 +189,7 @@ class AILSimplifier(Analysis):
188
189
  self._fold_callexprs_into_conditions = fold_callexprs_into_conditions
189
190
  self._use_callee_saved_regs_at_return = use_callee_saved_regs_at_return
190
191
  self._should_rewrite_ccalls = rewrite_ccalls
192
+ self._should_rename_ccalls = rename_ccalls
191
193
  self._removed_vvar_ids = removed_vvar_ids if removed_vvar_ids is not None else set()
192
194
  self._arg_vvars = arg_vvars
193
195
  self._avoid_vvar_ids = avoid_vvar_ids if avoid_vvar_ids is not None else set()
@@ -1992,20 +1994,20 @@ class AILSimplifier(Analysis):
1992
1994
 
1993
1995
  v = False
1994
1996
 
1995
- def _handle_expr(
1996
- expr_idx: int, expr: Expression, stmt_idx: int, stmt: Statement | None, block: Block | None
1997
+ def _handle_VEXCCallExpression(
1998
+ expr_idx: int, expr: VEXCCallExpression, stmt_idx: int, stmt: Statement, block: Block | None
1997
1999
  ) -> Expression | None:
1998
- if isinstance(expr, VEXCCallExpression):
1999
- rewriter = rewriter_cls(expr, self.project.arch)
2000
- if rewriter.result is not None:
2001
- _any_update.v = True
2002
- return rewriter.result
2003
- return None
2004
-
2005
- return AILBlockWalker._handle_expr(walker, expr_idx, expr, stmt_idx, stmt, block)
2000
+ r_expr = AILBlockWalker._handle_VEXCCallExpression(walker, expr_idx, expr, stmt_idx, stmt, block)
2001
+ if r_expr is None:
2002
+ r_expr = expr
2003
+ rewriter = rewriter_cls(r_expr, self.project.arch, rename_ccalls=self._should_rename_ccalls)
2004
+ if rewriter.result is not None:
2005
+ _any_update.v = True
2006
+ return rewriter.result
2007
+ return r_expr if r_expr is not expr else None
2006
2008
 
2007
2009
  blocks_by_addr_and_idx = {(node.addr, node.idx): node for node in self.func_graph.nodes()}
2008
- walker._handle_expr = _handle_expr
2010
+ walker.expr_handlers[VEXCCallExpression] = _handle_VEXCCallExpression
2009
2011
 
2010
2012
  updated = False
2011
2013
  for block in blocks_by_addr_and_idx.values():
@@ -12,9 +12,13 @@ class CCallRewriterBase:
12
12
  "result",
13
13
  )
14
14
 
15
- def __init__(self, ccall: ailment.Expr.VEXCCallExpression, arch):
15
+ def __init__(self, ccall: ailment.Expr.VEXCCallExpression, arch, rename_ccalls: bool = False):
16
16
  self.arch = arch
17
17
  self.result: ailment.Expr.Expression | None = self._rewrite(ccall)
18
+ if rename_ccalls and self.result is None and ccall.callee != "_ccall":
19
+ renamed = ccall.copy()
20
+ renamed.callee = "_ccall"
21
+ self.result = renamed
18
22
 
19
23
  def _rewrite(self, ccall: ailment.Expr.VEXCCallExpression) -> ailment.Expr.Expression | None:
20
24
  raise NotImplementedError
@@ -1231,52 +1231,62 @@ class Clinic(Analysis):
1231
1231
  @timethis
1232
1232
  def _replace_tail_jumps_with_calls(self, ail_graph: networkx.DiGraph) -> networkx.DiGraph:
1233
1233
  """
1234
- Replace tail jumps them with a return statement and a call expression.
1234
+ Rewrite tail jumps to functions as call statements.
1235
1235
  """
1236
1236
  for block in list(ail_graph.nodes()):
1237
- out_degree = ail_graph.out_degree[block]
1238
-
1239
- if out_degree != 0:
1237
+ if ail_graph.out_degree[block] > 1:
1240
1238
  continue
1241
1239
 
1242
1240
  last_stmt = block.statements[-1]
1243
1241
  if isinstance(last_stmt, ailment.Stmt.Jump):
1244
- # jumping to somewhere outside the current function
1245
- # rewrite it as a call *if and only if* the target is identified as a function
1246
- target = last_stmt.target
1247
- if isinstance(target, ailment.Const):
1248
- target_addr = target.value
1249
- if self.kb.functions.contains_addr(target_addr):
1250
- # replace the statement
1251
- target_func = self.kb.functions.get_by_addr(target_addr)
1252
- if target_func.returning and self.project.arch.ret_offset is not None:
1253
- ret_reg_offset = self.project.arch.ret_offset
1254
- ret_expr = ailment.Expr.Register(
1255
- None,
1256
- None,
1257
- ret_reg_offset,
1258
- self.project.arch.bits,
1259
- reg_name=self.project.arch.translate_register_name(
1260
- ret_reg_offset, size=self.project.arch.bits
1261
- ),
1262
- **target.tags,
1263
- )
1264
- call_stmt = ailment.Stmt.Call(
1265
- None,
1266
- target,
1267
- calling_convention=None, # target_func.calling_convention,
1268
- prototype=None, # target_func.prototype,
1269
- ret_expr=ret_expr,
1270
- **last_stmt.tags,
1271
- )
1272
- block.statements[-1] = call_stmt
1273
-
1274
- ret_stmt = ailment.Stmt.Return(None, [], **last_stmt.tags)
1275
- ret_block = ailment.Block(self.new_block_addr(), 1, statements=[ret_stmt])
1276
- ail_graph.add_edge(block, ret_block, type="fake_return")
1277
- else:
1278
- stmt = ailment.Stmt.Call(None, target, **last_stmt.tags)
1279
- block.statements[-1] = stmt
1242
+ targets = [last_stmt.target]
1243
+ replace_last_stmt = True
1244
+ elif isinstance(last_stmt, ailment.Stmt.ConditionalJump):
1245
+ targets = [last_stmt.true_target, last_stmt.false_target]
1246
+ replace_last_stmt = False
1247
+ else:
1248
+ continue
1249
+
1250
+ for target in targets:
1251
+ if not isinstance(target, ailment.Const) or not self.kb.functions.contains_addr(target.value):
1252
+ continue
1253
+
1254
+ target_func = self.kb.functions.get_by_addr(target.value)
1255
+
1256
+ ret_reg_offset = self.project.arch.ret_offset
1257
+ if target_func.returning and ret_reg_offset is not None:
1258
+ ret_expr = ailment.Expr.Register(
1259
+ None,
1260
+ None,
1261
+ ret_reg_offset,
1262
+ self.project.arch.bits,
1263
+ reg_name=self.project.arch.translate_register_name(ret_reg_offset, size=self.project.arch.bits),
1264
+ **target.tags,
1265
+ )
1266
+ else:
1267
+ ret_expr = None
1268
+
1269
+ call_stmt = ailment.Stmt.Call(
1270
+ None,
1271
+ target.copy(),
1272
+ calling_convention=None, # target_func.calling_convention,
1273
+ prototype=None, # target_func.prototype,
1274
+ ret_expr=ret_expr,
1275
+ **last_stmt.tags,
1276
+ )
1277
+
1278
+ if replace_last_stmt:
1279
+ call_block = block
1280
+ block.statements[-1] = call_stmt
1281
+ else:
1282
+ call_block = ailment.Block(self.new_block_addr(), 1, statements=[call_stmt])
1283
+ ail_graph.add_edge(block, call_block)
1284
+ target.value = call_block.addr
1285
+
1286
+ if target_func.returning:
1287
+ ret_stmt = ailment.Stmt.Return(None, [], **last_stmt.tags)
1288
+ ret_block = ailment.Block(self.new_block_addr(), 1, statements=[ret_stmt])
1289
+ ail_graph.add_edge(call_block, ret_block, type="fake_return")
1280
1290
 
1281
1291
  return ail_graph
1282
1292
 
@@ -1443,6 +1453,7 @@ class Clinic(Analysis):
1443
1453
  only_consts=False,
1444
1454
  fold_callexprs_into_conditions=False,
1445
1455
  rewrite_ccalls=True,
1456
+ rename_ccalls=True,
1446
1457
  removed_vvar_ids: set[int] | None = None,
1447
1458
  arg_vvars: dict[int, tuple[ailment.Expr.VirtualVariable, SimVariable]] | None = None,
1448
1459
  preserve_vvar_ids: set[int] | None = None,
@@ -1462,6 +1473,7 @@ class Clinic(Analysis):
1462
1473
  only_consts=only_consts,
1463
1474
  fold_callexprs_into_conditions=fold_callexprs_into_conditions,
1464
1475
  rewrite_ccalls=rewrite_ccalls,
1476
+ rename_ccalls=rename_ccalls,
1465
1477
  removed_vvar_ids=removed_vvar_ids,
1466
1478
  arg_vvars=arg_vvars,
1467
1479
  preserve_vvar_ids=preserve_vvar_ids,
@@ -1480,6 +1492,7 @@ class Clinic(Analysis):
1480
1492
  only_consts=False,
1481
1493
  fold_callexprs_into_conditions=False,
1482
1494
  rewrite_ccalls=True,
1495
+ rename_ccalls=True,
1483
1496
  removed_vvar_ids: set[int] | None = None,
1484
1497
  arg_vvars: dict[int, tuple[ailment.Expr.VirtualVariable, SimVariable]] | None = None,
1485
1498
  preserve_vvar_ids: set[int] | None = None,
@@ -1504,6 +1517,7 @@ class Clinic(Analysis):
1504
1517
  fold_callexprs_into_conditions=fold_callexprs_into_conditions,
1505
1518
  use_callee_saved_regs_at_return=not self._register_save_areas_removed,
1506
1519
  rewrite_ccalls=rewrite_ccalls,
1520
+ rename_ccalls=rename_ccalls,
1507
1521
  removed_vvar_ids=removed_vvar_ids,
1508
1522
  arg_vvars=arg_vvars,
1509
1523
  secondary_stackvars=self.secondary_stackvars,
@@ -296,7 +296,7 @@ class ITERegionConverter(OptimizationPass):
296
296
  )
297
297
 
298
298
  if len(new_src_and_vvars) == 1:
299
- new_assignment = Assignment(
299
+ new_stmt = Assignment(
300
300
  stmt.idx,
301
301
  stmt.dst,
302
302
  new_src_and_vvars[0][1],
@@ -309,13 +309,13 @@ class ITERegionConverter(OptimizationPass):
309
309
  new_src_and_vvars,
310
310
  **stmt.src.tags,
311
311
  )
312
- new_assignment = Assignment(
312
+ new_stmt = Assignment(
313
313
  stmt.idx,
314
314
  stmt.dst,
315
315
  new_phi,
316
316
  **stmt.tags,
317
317
  )
318
- stmts.append(new_assignment)
318
+ stmts.append(new_stmt)
319
319
  new_region_tail = Block(region_tail.addr, region_tail.original_size, statements=stmts, idx=region_tail.idx)
320
320
 
321
321
  #
@@ -864,7 +864,7 @@ class LoweredSwitchSimplifier(StructuringOptimizationPass):
864
864
  next_node_addr = last_block.addr
865
865
 
866
866
  while next_node_addr is not None and next_node_addr in ca_others:
867
- onode, value, target, target_idx, next_node_addr = ca_others[next_node_addr]
867
+ onode, _value, target, target_idx, next_node_addr = ca_others[next_node_addr]
868
868
  onode: Block
869
869
 
870
870
  if first_nonlabel_nonphi_statement(onode) is not onode.statements[-1]:
@@ -893,7 +893,7 @@ class LoweredSwitchSimplifier(StructuringOptimizationPass):
893
893
 
894
894
  # default nodes
895
895
  if ca_default:
896
- onode, value, target, target_idx, _ = ca_default[0]
896
+ onode, _value, target, target_idx, _ = ca_default[0]
897
897
  default_target = next(
898
898
  iter(
899
899
  nn