angr 9.2.79__py3-none-win_amd64.whl → 9.2.81__py3-none-win_amd64.whl

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

Potentially problematic release.


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

Files changed (42) hide show
  1. angr/__init__.py +1 -1
  2. angr/__main__.py +30 -5
  3. angr/analyses/cfg/cfg_fast.py +128 -3
  4. angr/analyses/decompiler/ail_simplifier.py +8 -0
  5. angr/analyses/decompiler/clinic.py +6 -2
  6. angr/analyses/decompiler/decompilation_options.py +9 -0
  7. angr/analyses/decompiler/optimization_passes/__init__.py +4 -0
  8. angr/analyses/decompiler/optimization_passes/multi_simplifier.py +0 -12
  9. angr/analyses/decompiler/optimization_passes/optimization_pass.py +8 -5
  10. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +2 -25
  11. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +14 -2
  12. angr/analyses/decompiler/region_simplifiers/ifelse.py +19 -10
  13. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +4 -2
  14. angr/analyses/decompiler/utils.py +8 -5
  15. angr/analyses/propagator/engine_ail.py +3 -1
  16. angr/analyses/propagator/engine_vex.py +61 -9
  17. angr/analyses/propagator/propagator.py +24 -15
  18. angr/analyses/reaching_definitions/engine_ail.py +1 -1
  19. angr/analyses/reaching_definitions/rd_state.py +4 -4
  20. angr/analyses/stack_pointer_tracker.py +55 -0
  21. angr/callable.py +4 -4
  22. angr/calling_conventions.py +11 -7
  23. angr/engines/light/engine.py +30 -18
  24. angr/knowledge_plugins/propagations/propagation_model.py +4 -0
  25. angr/knowledge_plugins/propagations/states.py +54 -4
  26. angr/lib/angr_native.dll +0 -0
  27. angr/procedures/definitions/__init__.py +2 -1
  28. angr/procedures/definitions/ntoskrnl.py +9 -0
  29. angr/procedures/win32_kernel/ExAllocatePool.py +12 -0
  30. angr/procedures/win32_kernel/ExFreePoolWithTag.py +7 -0
  31. angr/procedures/win32_kernel/__init__.py +3 -0
  32. angr/storage/memory_mixins/__init__.py +1 -1
  33. angr/utils/funcid.py +128 -0
  34. {angr-9.2.79.dist-info → angr-9.2.81.dist-info}/METADATA +6 -6
  35. {angr-9.2.79.dist-info → angr-9.2.81.dist-info}/RECORD +42 -37
  36. tests/analyses/cfg/test_cfgfast.py +24 -0
  37. tests/analyses/decompiler/test_decompiler.py +128 -0
  38. tests/analyses/test_constantpropagation.py +35 -0
  39. {angr-9.2.79.dist-info → angr-9.2.81.dist-info}/LICENSE +0 -0
  40. {angr-9.2.79.dist-info → angr-9.2.81.dist-info}/WHEEL +0 -0
  41. {angr-9.2.79.dist-info → angr-9.2.81.dist-info}/entry_points.txt +0 -0
  42. {angr-9.2.79.dist-info → angr-9.2.81.dist-info}/top_level.txt +0 -0
angr/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # pylint: disable=wildcard-import
2
2
  # pylint: disable=wrong-import-position
3
3
 
4
- __version__ = "9.2.79"
4
+ __version__ = "9.2.81"
5
5
 
6
6
  if bytes is str:
7
7
  raise Exception(
angr/__main__.py CHANGED
@@ -15,16 +15,41 @@ class COMMANDS:
15
15
 
16
16
  def main():
17
17
  parser = argparse.ArgumentParser(description="The angr CLI allows you to decompile and analyze binaries.")
18
- parser.add_argument("command", help="The command to run", choices=COMMANDS.ALL_COMMANDS)
19
- parser.add_argument("binary", help="The path to the binary to analyze")
20
- parser.add_argument("--functions", help="The functions to analyze", nargs="+")
21
18
  parser.add_argument(
22
- "--structurer", help="The structurer to use", choices=STRUCTURER_CLASSES.keys(), default="phoenix"
19
+ "command",
20
+ help="""
21
+ The analysis type to run on the binary. All analysis is output to stdout.""",
22
+ choices=COMMANDS.ALL_COMMANDS,
23
+ )
24
+ parser.add_argument("binary", help="The path to the binary to analyze.")
25
+ parser.add_argument(
26
+ "--functions",
27
+ help="""
28
+ The functions to analyze under the current command. Functions can either be expressed as names found in the
29
+ symbols of the binary or as addresses like: 0x401000.""",
30
+ nargs="+",
31
+ )
32
+ parser.add_argument(
33
+ "--catch-exceptions",
34
+ help="""
35
+ Catch exceptions during analysis. The scope of error handling may depend on the command used for analysis.
36
+ If multiple functions are specified for analysis, each function will be handled individually.""",
37
+ action="store_true",
38
+ default=False,
39
+ )
40
+ # decompilation-specific arguments
41
+ parser.add_argument(
42
+ "--structurer",
43
+ help="The structuring algorithm to use for decompilation.",
44
+ choices=STRUCTURER_CLASSES.keys(),
45
+ default="phoenix",
23
46
  )
24
47
 
25
48
  args = parser.parse_args()
26
49
  if args.command == COMMANDS.DECOMPILE:
27
- decompilation = decompile_functions(args.binary, functions=args.functions, structurer=args.structurer)
50
+ decompilation = decompile_functions(
51
+ args.binary, functions=args.functions, structurer=args.structurer, catch_errors=args.catch_exceptions
52
+ )
28
53
  print(decompilation)
29
54
  else:
30
55
  parser.print_help()
@@ -40,6 +40,12 @@ from angr.errors import (
40
40
  SimIRSBNoDecodeError,
41
41
  )
42
42
  from angr.utils.constants import DEFAULT_STATEMENT
43
+ from angr.utils.funcid import (
44
+ is_function_security_check_cookie,
45
+ is_function_security_init_cookie,
46
+ is_function_security_init_cookie_win8,
47
+ is_function_likely_security_init_cookie,
48
+ )
43
49
  from angr.analyses import ForwardAnalysis
44
50
  from .cfg_arch_options import CFGArchOptions
45
51
  from .cfg_base import CFGBase
@@ -1617,6 +1623,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1617
1623
  if self._collect_data_ref:
1618
1624
  self._post_process_string_references()
1619
1625
 
1626
+ self._rename_common_functions_and_symbols()
1627
+
1620
1628
  CFGBase._post_analysis(self)
1621
1629
 
1622
1630
  # Clean up
@@ -1653,6 +1661,69 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1653
1661
  else:
1654
1662
  l.exception("Error collecting XRefs for function %#x.", f_addr, exc_info=True)
1655
1663
 
1664
+ def _rename_common_functions_and_symbols(self):
1665
+ """
1666
+ This function implements logic for renaming some commonly seen functions in an architecture- and OS-specific
1667
+ way.
1668
+ """
1669
+
1670
+ if (
1671
+ self.project.simos is not None
1672
+ and self.project.arch.name == "AMD64"
1673
+ and self.project.simos.name == "Win32"
1674
+ and isinstance(self.project.loader.main_object, cle.PE)
1675
+ ):
1676
+ security_cookie_addr = self.project.loader.main_object.load_config.get("SecurityCookie", None)
1677
+ security_check_cookie_found = False
1678
+ security_init_cookie_found = False
1679
+ if security_cookie_addr is not None:
1680
+ if security_cookie_addr not in self.kb.labels:
1681
+ self.kb.labels[security_cookie_addr] = "_security_cookie"
1682
+ # identify _security_init_cookie and _security_check_cookie
1683
+ xrefs = self.kb.xrefs.get_xrefs_by_dst(security_cookie_addr)
1684
+ tested_func_addrs = set()
1685
+ for xref in xrefs:
1686
+ cfg_node = self.model.get_any_node(xref.block_addr)
1687
+ if cfg_node is None:
1688
+ continue
1689
+ func_addr = cfg_node.function_address
1690
+ if func_addr not in tested_func_addrs:
1691
+ func = self.kb.functions.get_by_addr(func_addr)
1692
+ if not security_check_cookie_found and is_function_security_check_cookie(
1693
+ func, self.project, security_cookie_addr
1694
+ ):
1695
+ security_check_cookie_found = True
1696
+ func.is_default_name = False
1697
+ func.name = "_security_check_cookie"
1698
+ elif not security_init_cookie_found and is_function_security_init_cookie(
1699
+ func, self.project, security_cookie_addr
1700
+ ):
1701
+ security_init_cookie_found = True
1702
+ func.is_default_name = False
1703
+ func.name = "_security_init_cookie"
1704
+ elif not security_init_cookie_found and is_function_security_init_cookie_win8(
1705
+ func, self.project, security_cookie_addr
1706
+ ):
1707
+ security_init_cookie_found = True
1708
+ func.is_default_name = False
1709
+ func.name = "_security_init_cookie"
1710
+ tested_func_addrs.add(func_addr)
1711
+ if security_init_cookie_found and security_check_cookie_found:
1712
+ # both are found. exit from the loop
1713
+ break
1714
+
1715
+ # special handling: some binaries do not have SecurityCookie set, but still contain _security_init_cookie
1716
+ if security_init_cookie_found is False:
1717
+ start_func = self.functions.get_by_addr(self.project.entry)
1718
+ if start_func is not None:
1719
+ for callee in start_func.transition_graph:
1720
+ if isinstance(callee, Function):
1721
+ if not security_init_cookie_found and is_function_likely_security_init_cookie(callee):
1722
+ security_init_cookie_found = True
1723
+ callee.is_default_name = False
1724
+ callee.name = "_security_init_cookie"
1725
+ break
1726
+
1656
1727
  def _post_process_string_references(self) -> None:
1657
1728
  """
1658
1729
  Finds overlapping string references and retrofit them so that we see full strings in memory data.
@@ -2008,9 +2079,10 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
2008
2079
  ins_addr = addr
2009
2080
  for i, stmt in enumerate(irsb.statements):
2010
2081
  if isinstance(stmt, pyvex.IRStmt.Exit):
2011
- successors.append(
2012
- (i, last_ins_addr if self.project.arch.branch_delay_slot else ins_addr, stmt.dst, stmt.jumpkind)
2013
- )
2082
+ branch_ins_addr = last_ins_addr if self.project.arch.branch_delay_slot else ins_addr
2083
+ if self._is_branch_vex_artifact_only(irsb, branch_ins_addr, stmt):
2084
+ continue
2085
+ successors.append((i, branch_ins_addr, stmt.dst, stmt.jumpkind))
2014
2086
  elif isinstance(stmt, pyvex.IRStmt.IMark):
2015
2087
  last_ins_addr = ins_addr
2016
2088
  ins_addr = stmt.addr + stmt.delta
@@ -2025,6 +2097,8 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
2025
2097
  idx_ = irsb.instruction_addresses.index(ins_addr)
2026
2098
  if idx_ > 0:
2027
2099
  branch_ins_addr = irsb.instruction_addresses[idx_ - 1]
2100
+ elif self._is_branch_vex_artifact_only(irsb, branch_ins_addr, exit_stmt):
2101
+ continue
2028
2102
  successors.append((stmt_idx, branch_ins_addr, exit_stmt.dst, exit_stmt.jumpkind))
2029
2103
 
2030
2104
  # default statement
@@ -4620,6 +4694,57 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
4620
4694
  queue.append(succ_addr)
4621
4695
  return to_remove
4622
4696
 
4697
+ def _is_branch_vex_artifact_only(self, irsb, branch_ins_addr: int, exit_stmt) -> bool:
4698
+ """
4699
+ Check if an exit is merely the result of VEX lifting. We should drop these exits.
4700
+ These exits point to the same instruction and do not terminate the block.
4701
+
4702
+ Example block:
4703
+
4704
+ 1400061c2 lock or byte ptr [rsp], 0x0
4705
+ 1400061c7 mov r9, r8
4706
+ 1400061ca shr r9, 0x5
4707
+ 1400061ce jne 0x1400060dc
4708
+
4709
+ VEX block:
4710
+
4711
+ 00 | ------ IMark(0x1400061c2, 5, 0) ------
4712
+ 01 | t3 = GET:I64(rsp)
4713
+ 02 | t2 = LDle:I8(t3)
4714
+ 03 | t(4,4294967295) = CASle(t3 :: (t2,None)->(t2,None))
4715
+ 04 | t13 = CasCmpNE8(t4,t2)
4716
+ 05 | if (t13) { PUT(rip) = 0x1400061c2; Ijk_Boring }
4717
+ 06 | ------ IMark(0x1400061c7, 3, 0) ------
4718
+ 07 | t15 = GET:I64(r8)
4719
+ 08 | ------ IMark(0x1400061ca, 4, 0) ------
4720
+ 09 | t9 = Shr64(t15,0x05)
4721
+ 10 | t16 = Shr64(t15,0x04)
4722
+ 11 | PUT(cc_op) = 0x0000000000000024
4723
+ 12 | PUT(cc_dep1) = t9
4724
+ 13 | PUT(cc_dep2) = t16
4725
+ 14 | PUT(r9) = t9
4726
+ 15 | PUT(rip) = 0x00000001400061ce
4727
+ 16 | ------ IMark(0x1400061ce, 6, 0) ------
4728
+ 17 | t29 = GET:I64(cc_ndep)
4729
+ 18 | t30 = amd64g_calculate_condition(0x0000000000000004,0x0000000000000024,t9,t16,t29):Ity_I64
4730
+ 19 | t25 = 64to1(t30)
4731
+ 20 | if (t25) { PUT(rip) = 0x1400061d4; Ijk_Boring }
4732
+ NEXT: PUT(rip) = 0x00000001400060dc; Ijk_Boring
4733
+
4734
+ Statement 5 should not introduce a new exit in the CFG.
4735
+ """
4736
+
4737
+ if (
4738
+ not self.project.arch.branch_delay_slot
4739
+ and irsb.instruction_addresses
4740
+ and branch_ins_addr != irsb.instruction_addresses[-1]
4741
+ and isinstance(exit_stmt.dst, pyvex.const.IRConst)
4742
+ and exit_stmt.dst.value == branch_ins_addr
4743
+ and exit_stmt.jumpkind == "Ijk_Boring"
4744
+ ):
4745
+ return True
4746
+ return False
4747
+
4623
4748
  def _remove_jobs_by_source_node_addr(self, addr: int):
4624
4749
  self._remove_job(lambda j: j.src_node is not None and j.src_node.addr == addr)
4625
4750
 
@@ -1044,6 +1044,14 @@ class AILSimplifier(Analysis):
1044
1044
  if u.block_addr not in {b.addr for b in super_node_blocks}:
1045
1045
  continue
1046
1046
 
1047
+ # check if the register has been overwritten by statements in between the def site and the use site
1048
+ usesite_atom_defs = set(rd.get_defs(the_def.atom, u, OP_BEFORE))
1049
+ if len(usesite_atom_defs) != 1:
1050
+ continue
1051
+ usesite_atom_def = next(iter(usesite_atom_defs))
1052
+ if usesite_atom_def != the_def:
1053
+ continue
1054
+
1047
1055
  # check if any atoms that the call relies on has been overwritten by statements in between the def site
1048
1056
  # and the use site.
1049
1057
  defsite_all_expr_uses = set(rd.all_uses.get_uses_by_location(the_def.codeloc))
@@ -260,6 +260,10 @@ class Clinic(Analysis):
260
260
  self._update_progress(50.0, text="Making callsites")
261
261
  _, stackarg_offsets = self._make_callsites(ail_graph, stack_pointer_tracker=spt)
262
262
 
263
+ # Run simplification passes
264
+ self._update_progress(65.0, text="Running simplifications 2")
265
+ ail_graph = self._run_simplification_passes(ail_graph, stage=OptimizationPassStage.AFTER_MAKING_CALLSITES)
266
+
263
267
  # Simplify the entire function for the second time
264
268
  self._update_progress(55.0, text="Simplifying function 2")
265
269
  self._simplify_function(
@@ -282,7 +286,7 @@ class Clinic(Analysis):
282
286
  )
283
287
 
284
288
  # Run simplification passes
285
- self._update_progress(65.0, text="Running simplifications 2")
289
+ self._update_progress(65.0, text="Running simplifications 3 ")
286
290
  ail_graph = self._run_simplification_passes(ail_graph, stage=OptimizationPassStage.AFTER_GLOBAL_SIMPLIFICATION)
287
291
 
288
292
  # Simplify the entire function for the third time
@@ -317,7 +321,7 @@ class Clinic(Analysis):
317
321
  self._make_function_prototype(arg_list, variable_kb)
318
322
 
319
323
  # Run simplification passes
320
- self._update_progress(95.0, text="Running simplifications 3")
324
+ self._update_progress(95.0, text="Running simplifications 4")
321
325
  ail_graph = self._run_simplification_passes(
322
326
  ail_graph, stage=OptimizationPassStage.AFTER_VARIABLE_RECOVERY, variable_kb=variable_kb
323
327
  )
@@ -97,6 +97,15 @@ options = [
97
97
  category="Graph",
98
98
  default_value=True,
99
99
  ),
100
+ O(
101
+ "Simplify if-else to remove terminating else scopes",
102
+ "Removes terminating else scopes to make the code appear more flat.",
103
+ bool,
104
+ "region_simplifier",
105
+ "simplify_ifelse",
106
+ category="Graph",
107
+ default_value=True,
108
+ ),
100
109
  O(
101
110
  "Show casts",
102
111
  "Disabling this option will blindly remove all C typecast constructs from pseudocode output.",
@@ -82,3 +82,7 @@ def get_default_optimization_passes(arch: Union[Arch, str], platform: Optional[s
82
82
  passes.append(pass_)
83
83
 
84
84
  return passes
85
+
86
+
87
+ def register_optimization_pass(opt_pass, enable_by_default: bool):
88
+ _all_optimization_passes.append((opt_pass, enable_by_default))
@@ -184,18 +184,6 @@ class MultiSimplifierAILEngine(SimplifierAILEngine):
184
184
  new_const = Expr.Const(const_.idx, None, const_.value * const_x0.value, const_.bits)
185
185
  new_expr = Expr.BinaryOp(expr.idx, "Mul", [x, new_const], expr.signed, **expr.tags)
186
186
  return new_expr
187
- elif (
188
- isinstance(operand_0, Expr.Convert)
189
- and isinstance(operand_0.operand, Expr.BinaryOp)
190
- and operand_0.operand.op == "Mul"
191
- and isinstance(operand_0.operand.operands[1], Expr.Const)
192
- ):
193
- x = operand_0.operand.operands[0]
194
- new_const = Expr.Const(
195
- operand_1.idx, None, operand_1.value * operand_0.operand.operands[1].value, operand_1.bits
196
- )
197
- new_expr = Expr.BinaryOp(expr.idx, "Mul", [x, new_const], expr.signed, **expr.tags)
198
- return new_expr
199
187
 
200
188
  if (operand_0, operand_1) != (expr.operands[0], expr.operands[1]):
201
189
  return Expr.BinaryOp(expr.idx, "Mul", [operand_0, operand_1], expr.signed, **expr.tags)
@@ -35,11 +35,12 @@ class OptimizationPassStage(Enum):
35
35
 
36
36
  AFTER_AIL_GRAPH_CREATION = 0
37
37
  AFTER_SINGLE_BLOCK_SIMPLIFICATION = 1
38
- AFTER_GLOBAL_SIMPLIFICATION = 2
39
- AFTER_VARIABLE_RECOVERY = 3
40
- BEFORE_REGION_IDENTIFICATION = 4
41
- DURING_REGION_IDENTIFICATION = 5
42
- AFTER_STRUCTURING = 6
38
+ AFTER_MAKING_CALLSITES = 2
39
+ AFTER_GLOBAL_SIMPLIFICATION = 3
40
+ AFTER_VARIABLE_RECOVERY = 4
41
+ BEFORE_REGION_IDENTIFICATION = 5
42
+ DURING_REGION_IDENTIFICATION = 6
43
+ AFTER_STRUCTURING = 7
43
44
 
44
45
 
45
46
  class BaseOptimizationPass:
@@ -53,6 +54,8 @@ class BaseOptimizationPass:
53
54
  STRUCTURING: Optional[
54
55
  str
55
56
  ] = None # specifies if this optimization pass is specific to a certain structuring algorithm
57
+ NAME = "N/A"
58
+ DESCRIPTION = "N/A"
56
59
 
57
60
  def __init__(self, func):
58
61
  self._func: "Function" = func
@@ -3,10 +3,10 @@ from typing import Set, Dict
3
3
  from collections import defaultdict
4
4
  import logging
5
5
 
6
- import capstone
7
6
  import ailment
8
7
  import cle
9
8
 
9
+ from angr.utils.funcid import is_function_security_check_cookie
10
10
  from .optimization_pass import OptimizationPass, OptimizationPassStage
11
11
 
12
12
 
@@ -268,29 +268,6 @@ class WinStackCanarySimplifier(OptimizationPass):
268
268
  return idx
269
269
  return None
270
270
 
271
- def _is_function_likely_security_check_cookie(self, func) -> bool:
272
- # disassemble the first instruction
273
- if func.is_plt or func.is_syscall or func.is_simprocedure:
274
- return False
275
- block = self.project.factory.block(func.addr)
276
- if block.instructions != 2:
277
- return False
278
- ins0 = block.capstone.insns[0]
279
- if (
280
- ins0.mnemonic == "cmp"
281
- and len(ins0.operands) == 2
282
- and ins0.operands[0].type == capstone.x86.X86_OP_REG
283
- and ins0.operands[0].reg == capstone.x86.X86_REG_RCX
284
- and ins0.operands[1].type == capstone.x86.X86_OP_MEM
285
- and ins0.operands[1].mem.base == capstone.x86.X86_REG_RIP
286
- and ins0.operands[1].mem.index == 0
287
- and ins0.operands[1].mem.disp + ins0.address + ins0.size == self._security_cookie_addr
288
- ):
289
- ins1 = block.capstone.insns[1]
290
- if ins1.mnemonic == "jne":
291
- return True
292
- return False
293
-
294
271
  def _find_stmt_calling_security_check_cookie(self, node):
295
272
  for idx, stmt in enumerate(node.statements):
296
273
  if isinstance(stmt, ailment.Stmt.Call) and isinstance(stmt.target, ailment.Expr.Const):
@@ -299,7 +276,7 @@ class WinStackCanarySimplifier(OptimizationPass):
299
276
  func = self.kb.functions.function(addr=const_target)
300
277
  if func.name == "_security_check_cookie":
301
278
  return idx
302
- elif self._is_function_likely_security_check_cookie(func):
279
+ elif is_function_security_check_cookie(func, self.project, self._security_cookie_addr):
303
280
  return idx
304
281
 
305
282
  return None
@@ -1,6 +1,6 @@
1
1
  from math import gcd
2
2
 
3
- from ailment.expression import BinaryOp, UnaryOp, Const
3
+ from ailment.expression import BinaryOp, UnaryOp, Const, Convert
4
4
 
5
5
  from .base import PeepholeOptimizationExprBase
6
6
 
@@ -13,11 +13,13 @@ class EagerEvaluation(PeepholeOptimizationExprBase):
13
13
  __slots__ = ()
14
14
 
15
15
  NAME = "Eager expression evaluation"
16
- expr_classes = (BinaryOp, UnaryOp)
16
+ expr_classes = (BinaryOp, UnaryOp, Convert)
17
17
 
18
18
  def optimize(self, expr, **kwargs):
19
19
  if isinstance(expr, BinaryOp):
20
20
  return self._optimize_binaryop(expr)
21
+ elif isinstance(expr, Convert):
22
+ return self._optimize_convert(expr)
21
23
  elif isinstance(expr, UnaryOp):
22
24
  return self._optimize_unaryop(expr)
23
25
  return None
@@ -192,3 +194,13 @@ class EagerEvaluation(PeepholeOptimizationExprBase):
192
194
  return new_expr
193
195
 
194
196
  return None
197
+
198
+ @staticmethod
199
+ def _optimize_convert(expr: Convert):
200
+ if isinstance(expr.operand, Const):
201
+ if expr.from_bits > expr.to_bits:
202
+ # truncation
203
+ mask = (1 << expr.to_bits) - 1
204
+ v = expr.operand.value & mask
205
+ return Const(expr.idx, expr.operand.variable, v, expr.to_bits, **expr.operand.tags)
206
+ return None
@@ -42,20 +42,29 @@ class IfElseFlattener(SequenceWalker):
42
42
 
43
43
  if node.true_node is not None and node.false_node is not None:
44
44
  try:
45
- last_stmts = ConditionProcessor.get_last_statements(node.true_node)
45
+ true_last_stmts = ConditionProcessor.get_last_statements(node.true_node)
46
46
  except EmptyBlockNotice:
47
- last_stmts = None
47
+ true_last_stmts = None
48
48
  if (
49
- last_stmts is not None
50
- and None not in last_stmts
51
- and all(is_statement_terminating(stmt, self.functions) for stmt in last_stmts)
49
+ true_last_stmts is not None
50
+ and None not in true_last_stmts
51
+ and all(is_statement_terminating(stmt, self.functions) for stmt in true_last_stmts)
52
52
  ):
53
53
  # all end points in the true node are returning
54
-
55
- # remove the else node and make it a new node following node
56
- else_node = node.false_node
57
- node.false_node = None
58
- insert_node(parent, "after", else_node, index, **kwargs)
54
+ try:
55
+ false_last_stmts = ConditionProcessor.get_last_statements(node.false_node)
56
+ except EmptyBlockNotice:
57
+ false_last_stmts = None
58
+ if (
59
+ false_last_stmts is not None
60
+ and None not in false_last_stmts
61
+ and not all(is_statement_terminating(stmt, self.functions) for stmt in false_last_stmts)
62
+ ):
63
+ # not all end points in the false node are returning. in this case, we remove the else node and
64
+ # make it a new node following node
65
+ else_node = node.false_node
66
+ node.false_node = None
67
+ insert_node(parent, "after", else_node, index, **kwargs)
59
68
 
60
69
  def _handle_CascadingCondition(self, node: CascadingConditionNode, parent=None, index=None, **kwargs):
61
70
  super()._handle_CascadingCondition(node, parent=parent, index=index, **kwargs)
@@ -24,11 +24,12 @@ class RegionSimplifier(Analysis):
24
24
  Simplifies a given region.
25
25
  """
26
26
 
27
- def __init__(self, func, region, variable_kb=None, simplify_switches: bool = True):
27
+ def __init__(self, func, region, variable_kb=None, simplify_switches: bool = True, simplify_ifelse: bool = True):
28
28
  self.func = func
29
29
  self.region = region
30
30
  self.variable_kb = variable_kb
31
31
  self._simplify_switches = simplify_switches
32
+ self._should_simplify_ifelses = simplify_ifelse
32
33
 
33
34
  self.goto_manager: Optional[GotoManager] = None
34
35
  self.result = None
@@ -70,7 +71,8 @@ class RegionSimplifier(Analysis):
70
71
  # Remove empty nodes
71
72
  r = self._remove_empty_nodes(r)
72
73
  # Remove unnecessary else branches if the if branch will always return
73
- r = self._simplify_ifelses(r)
74
+ if self._should_simplify_ifelses:
75
+ r = self._simplify_ifelses(r)
74
76
  #
75
77
  r = self._simplify_cascading_ifs(r)
76
78
  #
@@ -3,7 +3,6 @@ from typing import Optional, Tuple, Any, Union, List, Iterable
3
3
  import logging
4
4
 
5
5
  import networkx
6
- from rich.progress import track
7
6
 
8
7
  import ailment
9
8
  import angr
@@ -582,7 +581,7 @@ def peephole_optimize_multistmts(block, stmt_opts):
582
581
  return statements, any_update
583
582
 
584
583
 
585
- def decompile_functions(path, functions=None, structurer=None, catch_errors=True) -> Optional[str]:
584
+ def decompile_functions(path, functions=None, structurer=None, catch_errors=False) -> Optional[str]:
586
585
  """
587
586
  Decompile a binary into a set of functions.
588
587
 
@@ -616,16 +615,20 @@ def decompile_functions(path, functions=None, structurer=None, catch_errors=True
616
615
  functions = normalized_functions
617
616
 
618
617
  # verify that all functions exist
619
- for func in functions:
618
+ for func in list(functions):
620
619
  if func not in cfg.functions:
621
- raise ValueError(f"Function {func} does not exist in the CFG.")
620
+ if catch_errors:
621
+ _l.warning("Function %s does not exist in the CFG.", str(func))
622
+ functions.remove(func)
623
+ else:
624
+ raise ValueError(f"Function {func} does not exist in the CFG.")
622
625
 
623
626
  # decompile all functions
624
627
  decompilation = ""
625
628
  dec_options = [
626
629
  (PARAM_TO_OPTION["structurer_cls"], structurer),
627
630
  ]
628
- for func in track(functions, description="Decompiling functions", transient=True):
631
+ for func in functions:
629
632
  f = cfg.functions[func]
630
633
  if f is None or f.is_plt:
631
634
  continue
@@ -233,7 +233,9 @@ class SimEnginePropagatorAIL(
233
233
  sp_expr_new = sp_expr.copy()
234
234
  sp_expr_new.offset += self.arch.bytes
235
235
  else:
236
- sp_expr_new = sp_expr + self.arch.bytes
236
+ sp_expr_new = Expr.BinaryOp(
237
+ None, "Add", [sp_expr, Expr.Const(None, None, self.arch.bytes, sp_expr.bits)], False
238
+ )
237
239
  sp_value_new = PropValue(
238
240
  sp_value.value + self.arch.bytes,
239
241
  offset_and_details={
@@ -5,8 +5,9 @@ import claripy
5
5
  import pyvex
6
6
  import archinfo
7
7
 
8
+ from angr.knowledge_plugins.propagations.states import RegisterAnnotation, RegisterComparisonAnnotation
8
9
  from ...engines.light import SimEngineLightVEXMixin
9
- from ...calling_conventions import DEFAULT_CC, default_cc, SimRegArg
10
+ from ...calling_conventions import DEFAULT_CC, SYSCALL_CC, default_cc, SimRegArg
10
11
  from .values import Top, Bottom
11
12
  from .engine_base import SimEnginePropagatorBase
12
13
  from .top_checker_mixin import TopCheckerMixin
@@ -30,18 +31,18 @@ class SimEnginePropagatorVEX(
30
31
  # Private methods
31
32
  #
32
33
 
33
- def _process(self, state, successors, block=None, whitelist=None, **kwargs): # pylint:disable=arguments-differ
34
- super()._process(state, successors, block=block, whitelist=whitelist, **kwargs)
35
-
34
+ def _process_block_end(self):
35
+ super()._process_block_end()
36
36
  if self.block.vex.jumpkind == "Ijk_Call":
37
37
  if self.arch.call_pushes_ret:
38
38
  # pop ret from the stack
39
39
  sp_offset = self.arch.sp_offset
40
- sp_value = state.load_register(sp_offset, self.arch.bytes)
40
+ sp_value = self.state.load_register(sp_offset, self.arch.bytes)
41
41
  if sp_value is not None:
42
- state.store_register(sp_offset, self.arch.bytes, sp_value + self.arch.bytes)
42
+ self.state.store_register(sp_offset, self.arch.bytes, sp_value + self.arch.bytes)
43
43
 
44
- return state
44
+ if self.block.vex.jumpkind == "Ijk_Call" or self.block.vex.jumpkind.startswith("Ijk_Sys"):
45
+ self._handle_return_from_call()
45
46
 
46
47
  def _allow_loading(self, addr, size):
47
48
  if type(addr) in (Top, Bottom):
@@ -109,9 +110,16 @@ class SimEnginePropagatorVEX(
109
110
  # ret
110
111
  ebx_offset = self.arch.registers["ebx"][0]
111
112
  self.state.store_register(ebx_offset, 4, claripy.BVV(self.block.addr + self.block.size, 32))
112
- if self.arch.name in DEFAULT_CC:
113
+
114
+ def _handle_return_from_call(self):
115
+ # FIXME: Handle the specific function calling convention when known
116
+ syscall = self.block.vex.jumpkind.startswith("Ijk_Sys")
117
+ cc_map = SYSCALL_CC if syscall else DEFAULT_CC
118
+ if self.arch.name in cc_map:
113
119
  cc = default_cc(
114
- self.arch.name, platform=self.project.simos.name if self.project.simos is not None else None
120
+ self.arch.name,
121
+ platform=self.project.simos.name if self.project.simos is not None else None,
122
+ syscall=syscall,
115
123
  ) # don't instantiate the class for speed
116
124
  if isinstance(cc.RETURN_VAL, SimRegArg):
117
125
  offset, size = self.arch.registers[cc.RETURN_VAL.reg_name]
@@ -234,6 +242,16 @@ class SimEnginePropagatorVEX(
234
242
  self.tmps[stmt.result] = 1
235
243
  self.state.add_replacement(self._codeloc(block_only=True), VEXTmp(stmt.result), self.tmps[stmt.result])
236
244
 
245
+ def _handle_CmpEQ(self, expr):
246
+ arg0, arg1 = self._expr(expr.args[0]), self._expr(expr.args[1])
247
+ if arg1 is not None and arg1.concrete and arg0 is not None and len(arg0.annotations) == 1:
248
+ anno = arg0.annotations[0]
249
+ if isinstance(anno, RegisterAnnotation):
250
+ cmp_anno = RegisterComparisonAnnotation(anno.offset, anno.size, "eq", arg1.concrete_value)
251
+ bits = expr.result_size(self.tyenv)
252
+ return self.state.top(bits).annotate(cmp_anno)
253
+ return super()._handle_CmpEQ(expr)
254
+
237
255
  #
238
256
  # Expression handlers
239
257
  #
@@ -259,3 +277,37 @@ class SimEnginePropagatorVEX(
259
277
  r = super()._handle_Binop(expr)
260
278
  # print(expr.op, r)
261
279
  return r
280
+
281
+ def _handle_Conversion(self, expr):
282
+ expr_ = self._expr(expr.args[0])
283
+ to_size = expr.result_size(self.tyenv)
284
+ if expr_ is None:
285
+ return self._top(to_size)
286
+ if self._is_top(expr_):
287
+ return self._top(to_size).annotate(*expr_.annotations)
288
+
289
+ if isinstance(expr_, claripy.ast.Base) and expr_.op == "BVV":
290
+ if expr_.size() > to_size:
291
+ # truncation
292
+ return expr_[to_size - 1 : 0]
293
+ elif expr_.size() < to_size:
294
+ # extension
295
+ return claripy.ZeroExt(to_size - expr_.size(), expr_)
296
+ else:
297
+ return expr_
298
+
299
+ return self._top(to_size)
300
+
301
+ def _handle_Exit(self, stmt):
302
+ guard = self._expr(stmt.guard)
303
+ if guard is not None and len(guard.annotations) == 1:
304
+ dst = self._expr(stmt.dst)
305
+ if dst is not None and dst.concrete:
306
+ anno = guard.annotations[0]
307
+ if isinstance(anno, RegisterComparisonAnnotation):
308
+ if anno.cmp_op == "eq":
309
+ v = (anno.offset, anno.size, anno.value)
310
+ if v not in self.state.block_initial_reg_values[self.block.addr, dst.concrete_value]:
311
+ self.state.block_initial_reg_values[self.block.addr, dst.concrete_value].append(v)
312
+
313
+ super()._handle_Exit(stmt)