angr 9.2.79__py3-none-win_amd64.whl → 9.2.80__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.
- angr/__init__.py +1 -1
- angr/__main__.py +30 -5
- angr/analyses/cfg/cfg_fast.py +128 -3
- angr/analyses/decompiler/ail_simplifier.py +8 -0
- angr/analyses/decompiler/clinic.py +6 -2
- angr/analyses/decompiler/decompilation_options.py +9 -0
- angr/analyses/decompiler/optimization_passes/__init__.py +4 -0
- angr/analyses/decompiler/optimization_passes/multi_simplifier.py +0 -12
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +8 -5
- angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +2 -25
- angr/analyses/decompiler/peephole_optimizations/eager_eval.py +14 -2
- angr/analyses/decompiler/region_simplifiers/ifelse.py +19 -10
- angr/analyses/decompiler/region_simplifiers/region_simplifier.py +4 -2
- angr/analyses/decompiler/utils.py +8 -5
- angr/analyses/propagator/engine_ail.py +3 -1
- angr/analyses/propagator/engine_vex.py +45 -0
- angr/analyses/propagator/propagator.py +24 -15
- angr/analyses/reaching_definitions/engine_ail.py +1 -1
- angr/analyses/stack_pointer_tracker.py +55 -0
- angr/callable.py +4 -4
- angr/engines/light/engine.py +30 -18
- angr/knowledge_plugins/propagations/propagation_model.py +4 -0
- angr/knowledge_plugins/propagations/states.py +54 -4
- angr/lib/angr_native.dll +0 -0
- angr/procedures/definitions/__init__.py +2 -1
- angr/procedures/definitions/ntoskrnl.py +9 -0
- angr/procedures/win32_kernel/ExAllocatePool.py +12 -0
- angr/procedures/win32_kernel/ExFreePoolWithTag.py +7 -0
- angr/procedures/win32_kernel/__init__.py +3 -0
- angr/storage/memory_mixins/__init__.py +1 -1
- angr/utils/funcid.py +128 -0
- {angr-9.2.79.dist-info → angr-9.2.80.dist-info}/METADATA +6 -6
- {angr-9.2.79.dist-info → angr-9.2.80.dist-info}/RECORD +40 -35
- tests/analyses/cfg/test_cfgfast.py +24 -0
- tests/analyses/decompiler/test_decompiler.py +128 -0
- tests/analyses/test_constantpropagation.py +34 -0
- {angr-9.2.79.dist-info → angr-9.2.80.dist-info}/LICENSE +0 -0
- {angr-9.2.79.dist-info → angr-9.2.80.dist-info}/WHEEL +0 -0
- {angr-9.2.79.dist-info → angr-9.2.80.dist-info}/entry_points.txt +0 -0
- {angr-9.2.79.dist-info → angr-9.2.80.dist-info}/top_level.txt +0 -0
angr/__init__.py
CHANGED
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
|
-
"
|
|
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(
|
|
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()
|
angr/analyses/cfg/cfg_fast.py
CHANGED
|
@@ -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
|
-
|
|
2012
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
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
|
-
|
|
45
|
+
true_last_stmts = ConditionProcessor.get_last_statements(node.true_node)
|
|
46
46
|
except EmptyBlockNotice:
|
|
47
|
-
|
|
47
|
+
true_last_stmts = None
|
|
48
48
|
if (
|
|
49
|
-
|
|
50
|
-
and None not in
|
|
51
|
-
and all(is_statement_terminating(stmt, self.functions) for stmt in
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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,6 +5,7 @@ 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
10
|
from ...calling_conventions import DEFAULT_CC, default_cc, SimRegArg
|
|
10
11
|
from .values import Top, Bottom
|
|
@@ -234,6 +235,16 @@ class SimEnginePropagatorVEX(
|
|
|
234
235
|
self.tmps[stmt.result] = 1
|
|
235
236
|
self.state.add_replacement(self._codeloc(block_only=True), VEXTmp(stmt.result), self.tmps[stmt.result])
|
|
236
237
|
|
|
238
|
+
def _handle_CmpEQ(self, expr):
|
|
239
|
+
arg0, arg1 = self._expr(expr.args[0]), self._expr(expr.args[1])
|
|
240
|
+
if arg1 is not None and arg1.concrete and arg0 is not None and len(arg0.annotations) == 1:
|
|
241
|
+
anno = arg0.annotations[0]
|
|
242
|
+
if isinstance(anno, RegisterAnnotation):
|
|
243
|
+
cmp_anno = RegisterComparisonAnnotation(anno.offset, anno.size, "eq", arg1.concrete_value)
|
|
244
|
+
bits = expr.result_size(self.tyenv)
|
|
245
|
+
return self.state.top(bits).annotate(cmp_anno)
|
|
246
|
+
return super()._handle_CmpEQ(expr)
|
|
247
|
+
|
|
237
248
|
#
|
|
238
249
|
# Expression handlers
|
|
239
250
|
#
|
|
@@ -259,3 +270,37 @@ class SimEnginePropagatorVEX(
|
|
|
259
270
|
r = super()._handle_Binop(expr)
|
|
260
271
|
# print(expr.op, r)
|
|
261
272
|
return r
|
|
273
|
+
|
|
274
|
+
def _handle_Conversion(self, expr):
|
|
275
|
+
expr_ = self._expr(expr.args[0])
|
|
276
|
+
to_size = expr.result_size(self.tyenv)
|
|
277
|
+
if expr_ is None:
|
|
278
|
+
return self._top(to_size)
|
|
279
|
+
if self._is_top(expr_):
|
|
280
|
+
return self._top(to_size).annotate(*expr_.annotations)
|
|
281
|
+
|
|
282
|
+
if isinstance(expr_, claripy.ast.Base) and expr_.op == "BVV":
|
|
283
|
+
if expr_.size() > to_size:
|
|
284
|
+
# truncation
|
|
285
|
+
return expr_[to_size - 1 : 0]
|
|
286
|
+
elif expr_.size() < to_size:
|
|
287
|
+
# extension
|
|
288
|
+
return claripy.ZeroExt(to_size - expr_.size(), expr_)
|
|
289
|
+
else:
|
|
290
|
+
return expr_
|
|
291
|
+
|
|
292
|
+
return self._top(to_size)
|
|
293
|
+
|
|
294
|
+
def _handle_Exit(self, stmt):
|
|
295
|
+
guard = self._expr(stmt.guard)
|
|
296
|
+
if guard is not None and len(guard.annotations) == 1:
|
|
297
|
+
dst = self._expr(stmt.dst)
|
|
298
|
+
if dst is not None and dst.concrete:
|
|
299
|
+
anno = guard.annotations[0]
|
|
300
|
+
if isinstance(anno, RegisterComparisonAnnotation):
|
|
301
|
+
if anno.cmp_op == "eq":
|
|
302
|
+
v = (anno.offset, anno.size, anno.value)
|
|
303
|
+
if v not in self.state.block_initial_reg_values[self.block.addr, dst.concrete_value]:
|
|
304
|
+
self.state.block_initial_reg_values[self.block.addr, dst.concrete_value].append(v)
|
|
305
|
+
|
|
306
|
+
super()._handle_Exit(stmt)
|