angr 9.2.138__py3-none-manylinux2014_x86_64.whl → 9.2.140__py3-none-manylinux2014_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.
- angr/__init__.py +1 -1
- angr/analyses/calling_convention/calling_convention.py +48 -21
- angr/analyses/calling_convention/fact_collector.py +59 -12
- angr/analyses/calling_convention/utils.py +2 -2
- angr/analyses/cfg/cfg_base.py +13 -0
- angr/analyses/cfg/cfg_fast.py +23 -4
- angr/analyses/decompiler/ail_simplifier.py +79 -53
- angr/analyses/decompiler/block_simplifier.py +0 -2
- angr/analyses/decompiler/callsite_maker.py +80 -14
- angr/analyses/decompiler/clinic.py +99 -80
- angr/analyses/decompiler/condition_processor.py +2 -2
- angr/analyses/decompiler/decompiler.py +19 -7
- angr/analyses/decompiler/dephication/rewriting_engine.py +16 -7
- angr/analyses/decompiler/expression_narrower.py +1 -1
- angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
- angr/analyses/decompiler/optimization_passes/condition_constprop.py +149 -0
- angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
- angr/analyses/decompiler/optimization_passes/deadblock_remover.py +12 -3
- angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +1 -1
- angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +21 -12
- angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +17 -9
- angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +7 -10
- angr/analyses/decompiler/peephole_optimizations/eager_eval.py +12 -1
- angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +61 -25
- angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +50 -1
- angr/analyses/decompiler/presets/fast.py +2 -0
- angr/analyses/decompiler/presets/full.py +2 -0
- angr/analyses/decompiler/region_simplifiers/expr_folding.py +259 -108
- angr/analyses/decompiler/region_simplifiers/region_simplifier.py +28 -9
- angr/analyses/decompiler/ssailification/rewriting_engine.py +20 -2
- angr/analyses/decompiler/ssailification/traversal_engine.py +4 -3
- angr/analyses/decompiler/structured_codegen/c.py +10 -3
- angr/analyses/decompiler/structuring/dream.py +28 -19
- angr/analyses/decompiler/structuring/phoenix.py +253 -89
- angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
- angr/analyses/decompiler/structuring/structurer_base.py +121 -46
- angr/analyses/decompiler/structuring/structurer_nodes.py +6 -1
- angr/analyses/decompiler/utils.py +60 -1
- angr/analyses/deobfuscator/api_obf_finder.py +13 -5
- angr/analyses/deobfuscator/api_obf_type2_finder.py +166 -0
- angr/analyses/deobfuscator/string_obf_finder.py +105 -18
- angr/analyses/forward_analysis/forward_analysis.py +1 -1
- angr/analyses/propagator/top_checker_mixin.py +6 -6
- angr/analyses/reaching_definitions/__init__.py +2 -1
- angr/analyses/reaching_definitions/dep_graph.py +1 -12
- angr/analyses/reaching_definitions/engine_vex.py +36 -31
- angr/analyses/reaching_definitions/function_handler.py +15 -2
- angr/analyses/reaching_definitions/rd_state.py +1 -37
- angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
- angr/analyses/s_propagator.py +129 -87
- angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
- angr/analyses/s_reaching_definitions/s_rda_view.py +2 -2
- angr/analyses/s_reaching_definitions/s_reaching_definitions.py +3 -1
- angr/analyses/stack_pointer_tracker.py +36 -22
- angr/analyses/typehoon/simple_solver.py +45 -7
- angr/analyses/typehoon/typeconsts.py +18 -5
- angr/analyses/variable_recovery/engine_ail.py +1 -1
- angr/analyses/variable_recovery/engine_base.py +62 -67
- angr/analyses/variable_recovery/engine_vex.py +1 -1
- angr/analyses/variable_recovery/irsb_scanner.py +2 -2
- angr/block.py +69 -107
- angr/callable.py +14 -7
- angr/calling_conventions.py +81 -10
- angr/distributed/__init__.py +1 -1
- angr/engines/__init__.py +7 -8
- angr/engines/engine.py +3 -138
- angr/engines/failure.py +2 -2
- angr/engines/hook.py +2 -2
- angr/engines/light/engine.py +5 -10
- angr/engines/pcode/emulate.py +2 -2
- angr/engines/pcode/engine.py +2 -14
- angr/engines/pcode/lifter.py +2 -2
- angr/engines/procedure.py +2 -2
- angr/engines/soot/engine.py +2 -2
- angr/engines/soot/statements/switch.py +1 -1
- angr/engines/successors.py +123 -17
- angr/engines/syscall.py +2 -2
- angr/engines/unicorn.py +3 -3
- angr/engines/vex/heavy/heavy.py +3 -15
- angr/engines/vex/lifter.py +2 -2
- angr/engines/vex/light/light.py +2 -2
- angr/factory.py +4 -19
- angr/knowledge_plugins/cfg/cfg_model.py +3 -2
- angr/knowledge_plugins/key_definitions/atoms.py +8 -4
- angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
- angr/knowledge_plugins/labels.py +2 -2
- angr/knowledge_plugins/obfuscations.py +1 -0
- angr/knowledge_plugins/xrefs/xref_manager.py +4 -0
- angr/sim_type.py +19 -17
- angr/state_plugins/plugin.py +19 -4
- angr/storage/memory_mixins/memory_mixin.py +1 -1
- angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
- angr/utils/ssa/__init__.py +119 -4
- {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/METADATA +6 -6
- {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/RECORD +100 -98
- {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/LICENSE +0 -0
- {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/WHEEL +0 -0
- {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/entry_points.txt +0 -0
- {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/top_level.txt +0 -0
|
@@ -92,6 +92,7 @@ class RecursiveStructurer(Analysis):
|
|
|
92
92
|
case_entry_to_switch_head=self._case_entry_to_switch_head,
|
|
93
93
|
func=self.function,
|
|
94
94
|
parent_region=parent_region,
|
|
95
|
+
jump_tables=self.kb.cfgs["CFGFast"].jump_tables,
|
|
95
96
|
**self.structurer_options,
|
|
96
97
|
)
|
|
97
98
|
# replace this region with the resulting node in its parent region... if it's not an orphan
|
|
@@ -19,6 +19,8 @@ from angr.analyses.decompiler.utils import (
|
|
|
19
19
|
has_nonlabel_nonphi_statements,
|
|
20
20
|
)
|
|
21
21
|
from angr.analyses.decompiler.label_collector import LabelCollector
|
|
22
|
+
from angr.errors import AngrDecompilationError
|
|
23
|
+
from angr.knowledge_plugins.cfg import IndirectJump
|
|
22
24
|
from .structurer_nodes import (
|
|
23
25
|
MultiNode,
|
|
24
26
|
SequenceNode,
|
|
@@ -32,6 +34,7 @@ from .structurer_nodes import (
|
|
|
32
34
|
BreakNode,
|
|
33
35
|
LoopNode,
|
|
34
36
|
EmptyBlockNotice,
|
|
37
|
+
IncompleteSwitchCaseNode,
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
if TYPE_CHECKING:
|
|
@@ -49,7 +52,7 @@ class StructurerBase(Analysis):
|
|
|
49
52
|
longer exist due to empty node removal during structuring or prior steps.
|
|
50
53
|
"""
|
|
51
54
|
|
|
52
|
-
NAME: str =
|
|
55
|
+
NAME: str = "StructurerBase"
|
|
53
56
|
|
|
54
57
|
def __init__(
|
|
55
58
|
self,
|
|
@@ -59,6 +62,7 @@ class StructurerBase(Analysis):
|
|
|
59
62
|
func: Function | None = None,
|
|
60
63
|
case_entry_to_switch_head: dict[int, int] | None = None,
|
|
61
64
|
parent_region=None,
|
|
65
|
+
jump_tables: dict[int, IndirectJump] | None = None,
|
|
62
66
|
**kwargs,
|
|
63
67
|
):
|
|
64
68
|
self._region: GraphRegion = region
|
|
@@ -66,6 +70,7 @@ class StructurerBase(Analysis):
|
|
|
66
70
|
self.function = func
|
|
67
71
|
self._case_entry_to_switch_head = case_entry_to_switch_head
|
|
68
72
|
self._parent_region = parent_region
|
|
73
|
+
self.jump_tables = jump_tables or {}
|
|
69
74
|
|
|
70
75
|
self.cond_proc = (
|
|
71
76
|
condition_processor if condition_processor is not None else ConditionProcessor(self.project.arch)
|
|
@@ -132,16 +137,9 @@ class StructurerBase(Analysis):
|
|
|
132
137
|
return seq
|
|
133
138
|
|
|
134
139
|
@staticmethod
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
:param dict cases: A dict of switch-cases.
|
|
140
|
-
:param default: The default node.
|
|
141
|
-
:param int|None node_b_addr: Address of the end of the switch.
|
|
142
|
-
:return: None
|
|
143
|
-
"""
|
|
144
|
-
|
|
140
|
+
def _switch_find_switch_end_addr(
|
|
141
|
+
cases: dict[int, BaseNode], default: BaseNode | ailment.Block | None, region_node_addrs: set[int]
|
|
142
|
+
) -> int | None:
|
|
145
143
|
goto_addrs = defaultdict(int)
|
|
146
144
|
|
|
147
145
|
def _find_gotos(block, **kwargs):
|
|
@@ -155,20 +153,54 @@ class StructurerBase(Analysis):
|
|
|
155
153
|
continue
|
|
156
154
|
goto_addrs[t] += 1
|
|
157
155
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
handlers = {ailment.Block: _find_gotos}
|
|
156
|
+
# we need to figure this out
|
|
157
|
+
handlers = {ailment.Block: _find_gotos}
|
|
161
158
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
159
|
+
walker = SequenceWalker(handlers=handlers)
|
|
160
|
+
for case_node in cases.values():
|
|
161
|
+
walker.walk(case_node)
|
|
162
|
+
if default is not None:
|
|
163
|
+
walker.walk(default)
|
|
167
164
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
165
|
+
if not goto_addrs:
|
|
166
|
+
# there is no Goto statement - perfect, we don't need a switch-end node
|
|
167
|
+
return None
|
|
168
|
+
if len(goto_addrs) > 1 and any(a in region_node_addrs for a in goto_addrs):
|
|
169
|
+
goto_addrs = {a: times for a, times in goto_addrs.items() if a in region_node_addrs}
|
|
170
|
+
return sorted(goto_addrs.items(), key=lambda x: x[1], reverse=True)[0][0]
|
|
171
|
+
|
|
172
|
+
def _switch_handle_gotos(self, cases: dict[int, BaseNode], default, switch_end_addr: int) -> None:
|
|
173
|
+
"""
|
|
174
|
+
For each case, convert the goto that goes outside of the switch-case to a break statement.
|
|
175
|
+
|
|
176
|
+
:param cases: A dict of switch-cases.
|
|
177
|
+
:param default: The default node.
|
|
178
|
+
:param node_b_addr: Address of the end of the switch.
|
|
179
|
+
:return: None
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
# ensure every case node ends with a control-flow transition statement
|
|
183
|
+
# FIXME: The following logic only handles one case. are there other cases?
|
|
184
|
+
for case_addr in cases:
|
|
185
|
+
case_node = cases[case_addr]
|
|
186
|
+
if (
|
|
187
|
+
isinstance(case_node, SequenceNode)
|
|
188
|
+
and case_node.nodes
|
|
189
|
+
and isinstance(case_node.nodes[-1], ConditionNode)
|
|
190
|
+
):
|
|
191
|
+
cond_node = case_node.nodes[-1]
|
|
192
|
+
if (cond_node.true_node is None and cond_node.false_node is not None) or (
|
|
193
|
+
cond_node.false_node is None and cond_node.true_node is not None
|
|
194
|
+
):
|
|
195
|
+
# the last node is a condition node and only has one branch - we need a goto statement to ensure it
|
|
196
|
+
# does not fall through to the next branch
|
|
197
|
+
goto_stmt = ailment.Stmt.Jump(
|
|
198
|
+
None,
|
|
199
|
+
ailment.Expr.Const(None, None, switch_end_addr, self.project.arch.bits),
|
|
200
|
+
target_idx=None,
|
|
201
|
+
ins_addr=cond_node.addr,
|
|
202
|
+
)
|
|
203
|
+
case_node.nodes.append(ailment.Block(cond_node.addr, 0, statements=[goto_stmt], idx=None))
|
|
172
204
|
|
|
173
205
|
# rewrite all _goto switch_end_addr_ to _break_
|
|
174
206
|
|
|
@@ -262,7 +294,7 @@ class StructurerBase(Analysis):
|
|
|
262
294
|
and this_node.statements
|
|
263
295
|
and isinstance(this_node.statements[-1], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump))
|
|
264
296
|
):
|
|
265
|
-
jump_stmt = this_node.statements[-1]
|
|
297
|
+
jump_stmt = this_node.statements[-1] # type: ignore
|
|
266
298
|
elif (
|
|
267
299
|
isinstance(this_node, MultiNode)
|
|
268
300
|
and this_node.nodes
|
|
@@ -273,9 +305,10 @@ class StructurerBase(Analysis):
|
|
|
273
305
|
)
|
|
274
306
|
):
|
|
275
307
|
this_node = this_node.nodes[-1]
|
|
276
|
-
jump_stmt = this_node.statements[-1]
|
|
308
|
+
jump_stmt = this_node.statements[-1] # type: ignore
|
|
277
309
|
|
|
278
310
|
if isinstance(jump_stmt, ailment.Stmt.Jump):
|
|
311
|
+
assert isinstance(this_node, ailment.Block)
|
|
279
312
|
next_node = node.nodes[i + 1]
|
|
280
313
|
if (
|
|
281
314
|
isinstance(jump_stmt.target, ailment.Expr.Const)
|
|
@@ -284,6 +317,7 @@ class StructurerBase(Analysis):
|
|
|
284
317
|
# this goto is useless
|
|
285
318
|
this_node.statements = this_node.statements[:-1]
|
|
286
319
|
elif isinstance(jump_stmt, ailment.Stmt.ConditionalJump):
|
|
320
|
+
assert isinstance(this_node, ailment.Block)
|
|
287
321
|
next_node = node.nodes[i + 1]
|
|
288
322
|
if (
|
|
289
323
|
isinstance(jump_stmt.true_target, ailment.Expr.Const)
|
|
@@ -337,6 +371,7 @@ class StructurerBase(Analysis):
|
|
|
337
371
|
jump_stmt = this_node.nodes[-1].statements[-1]
|
|
338
372
|
this_node = this_node.nodes[-1]
|
|
339
373
|
|
|
374
|
+
assert isinstance(this_node, ailment.Block)
|
|
340
375
|
if isinstance(jump_stmt, ailment.Stmt.Jump):
|
|
341
376
|
next_node = node.nodes[i + 1]
|
|
342
377
|
if (
|
|
@@ -387,7 +422,7 @@ class StructurerBase(Analysis):
|
|
|
387
422
|
return seq
|
|
388
423
|
|
|
389
424
|
def _rewrite_conditional_jumps_to_breaks(self, loop_node, successor_addrs):
|
|
390
|
-
def _rewrite_conditional_jump_to_break(node: ailment.Block, parent
|
|
425
|
+
def _rewrite_conditional_jump_to_break(node: ailment.Block, *, parent, index: int, label=None, **kwargs):
|
|
391
426
|
if not node.statements:
|
|
392
427
|
return
|
|
393
428
|
|
|
@@ -481,7 +516,7 @@ class StructurerBase(Analysis):
|
|
|
481
516
|
):
|
|
482
517
|
continue_node_addr = loop_node.condition.ins_addr
|
|
483
518
|
|
|
484
|
-
def _rewrite_jump_to_continue(node, parent
|
|
519
|
+
def _rewrite_jump_to_continue(node, *, parent, index: int, label=None, **kwargs):
|
|
485
520
|
if not node.statements:
|
|
486
521
|
return
|
|
487
522
|
stmt = node.statements[-1]
|
|
@@ -593,6 +628,7 @@ class StructurerBase(Analysis):
|
|
|
593
628
|
if (true_target_value is not None and true_target_value in loop_successor_addrs) and (
|
|
594
629
|
false_target_value is None or false_target_value not in loop_successor_addrs
|
|
595
630
|
):
|
|
631
|
+
assert last_stmt.true_target is not None
|
|
596
632
|
cond = last_stmt.condition
|
|
597
633
|
target = last_stmt.true_target.value
|
|
598
634
|
new_node = ConditionalBreakNode(
|
|
@@ -601,6 +637,7 @@ class StructurerBase(Analysis):
|
|
|
601
637
|
elif (false_target_value is not None and false_target_value in loop_successor_addrs) and (
|
|
602
638
|
true_target_value is None or true_target_value not in loop_successor_addrs
|
|
603
639
|
):
|
|
640
|
+
assert last_stmt.false_target is not None
|
|
604
641
|
cond = ailment.Expr.UnaryOp(last_stmt.condition.idx, "Not", last_stmt.condition)
|
|
605
642
|
target = last_stmt.false_target.value
|
|
606
643
|
new_node = ConditionalBreakNode(
|
|
@@ -611,10 +648,11 @@ class StructurerBase(Analysis):
|
|
|
611
648
|
):
|
|
612
649
|
# both targets are pointing outside the loop
|
|
613
650
|
# we should use just add a break node
|
|
651
|
+
assert last_stmt.false_target is not None
|
|
614
652
|
new_node = BreakNode(last_stmt.ins_addr, last_stmt.false_target.value)
|
|
615
653
|
else:
|
|
616
654
|
_l.warning("None of the branches is jumping to outside of the loop")
|
|
617
|
-
raise
|
|
655
|
+
raise AngrDecompilationError("Unexpected: None of the branches is jumping to outside of the loop")
|
|
618
656
|
|
|
619
657
|
return new_node
|
|
620
658
|
|
|
@@ -622,6 +660,13 @@ class StructurerBase(Analysis):
|
|
|
622
660
|
def _merge_conditional_breaks(seq):
|
|
623
661
|
# Find consecutive ConditionalBreakNodes and merge their conditions
|
|
624
662
|
|
|
663
|
+
class _Holder:
|
|
664
|
+
"""
|
|
665
|
+
Holds values so that handlers can access them directly.
|
|
666
|
+
"""
|
|
667
|
+
|
|
668
|
+
merged = False
|
|
669
|
+
|
|
625
670
|
def _handle_SequenceNode(seq_node, parent=None, index=0, label=None):
|
|
626
671
|
new_nodes = []
|
|
627
672
|
i = 0
|
|
@@ -642,7 +687,7 @@ class StructurerBase(Analysis):
|
|
|
642
687
|
claripy.Or(node.condition, prev_node.condition)
|
|
643
688
|
)
|
|
644
689
|
new_node = ConditionalBreakNode(node.addr, merged_condition, node.target)
|
|
645
|
-
|
|
690
|
+
_Holder.merged = True
|
|
646
691
|
else:
|
|
647
692
|
walker._handle(node, parent=seq_node, index=i)
|
|
648
693
|
|
|
@@ -659,13 +704,20 @@ class StructurerBase(Analysis):
|
|
|
659
704
|
}
|
|
660
705
|
|
|
661
706
|
walker = SequenceWalker(handlers=handlers)
|
|
662
|
-
|
|
707
|
+
_Holder.merged = False # this is just a hack
|
|
663
708
|
walker.walk(seq)
|
|
664
|
-
return
|
|
709
|
+
return _Holder.merged, seq
|
|
665
710
|
|
|
666
711
|
def _merge_nesting_conditionals(self, seq):
|
|
667
712
|
# find if(A) { if(B) { ... ] } and simplify them to if( A && B ) { ... }
|
|
668
713
|
|
|
714
|
+
class _Holder:
|
|
715
|
+
"""
|
|
716
|
+
Holds values so that handlers can access them directly.
|
|
717
|
+
"""
|
|
718
|
+
|
|
719
|
+
merged = False
|
|
720
|
+
|
|
669
721
|
def _condnode_truenode_only(node):
|
|
670
722
|
if type(node) is CodeNode:
|
|
671
723
|
# unpack
|
|
@@ -693,9 +745,11 @@ class StructurerBase(Analysis):
|
|
|
693
745
|
node = seq_node.nodes[i]
|
|
694
746
|
r, cond_node = _condnode_truenode_only(node)
|
|
695
747
|
if r:
|
|
748
|
+
assert cond_node is not None
|
|
696
749
|
r, cond_node_inner = _condnode_truenode_only(cond_node.true_node)
|
|
697
750
|
if r:
|
|
698
751
|
# amazing!
|
|
752
|
+
assert cond_node_inner is not None
|
|
699
753
|
merged_cond = ConditionProcessor.simplify_condition(
|
|
700
754
|
claripy.And(
|
|
701
755
|
self.cond_proc.claripy_ast_from_ail_condition(cond_node.condition),
|
|
@@ -704,13 +758,14 @@ class StructurerBase(Analysis):
|
|
|
704
758
|
)
|
|
705
759
|
new_node = ConditionNode(cond_node.addr, None, merged_cond, cond_node_inner.true_node, None)
|
|
706
760
|
seq_node.nodes[i] = new_node
|
|
707
|
-
|
|
761
|
+
_Holder.merged = True
|
|
708
762
|
i += 1
|
|
709
763
|
continue
|
|
710
764
|
# else:
|
|
711
765
|
r, condbreak_node = _condbreaknode(cond_node.true_node)
|
|
712
766
|
if r:
|
|
713
767
|
# amazing!
|
|
768
|
+
assert condbreak_node is not None
|
|
714
769
|
merged_cond = ConditionProcessor.simplify_condition(
|
|
715
770
|
claripy.And(
|
|
716
771
|
self.cond_proc.claripy_ast_from_ail_condition(cond_node.condition),
|
|
@@ -719,7 +774,7 @@ class StructurerBase(Analysis):
|
|
|
719
774
|
)
|
|
720
775
|
new_node = ConditionalBreakNode(condbreak_node.addr, merged_cond, condbreak_node.target)
|
|
721
776
|
seq_node.nodes[i] = new_node
|
|
722
|
-
|
|
777
|
+
_Holder.merged = True
|
|
723
778
|
i += 1
|
|
724
779
|
continue
|
|
725
780
|
|
|
@@ -732,14 +787,10 @@ class StructurerBase(Analysis):
|
|
|
732
787
|
}
|
|
733
788
|
|
|
734
789
|
walker = SequenceWalker(handlers=handlers)
|
|
735
|
-
|
|
790
|
+
_Holder.merged = False # this is just a hack
|
|
736
791
|
walker.walk(seq)
|
|
737
792
|
|
|
738
|
-
return
|
|
739
|
-
|
|
740
|
-
#
|
|
741
|
-
# Util methods
|
|
742
|
-
#
|
|
793
|
+
return _Holder.merged, seq
|
|
743
794
|
|
|
744
795
|
def _reorganize_switch_cases(
|
|
745
796
|
self, cases: OrderedDict[int | tuple[int, ...], SequenceNode]
|
|
@@ -747,10 +798,11 @@ class StructurerBase(Analysis):
|
|
|
747
798
|
new_cases = OrderedDict()
|
|
748
799
|
|
|
749
800
|
caseid2gotoaddrs = {}
|
|
750
|
-
addr2caseids: dict[int, list[int
|
|
801
|
+
addr2caseids: dict[int, list[int | tuple[int, ...]]] = defaultdict(list)
|
|
751
802
|
|
|
752
803
|
# collect goto locations
|
|
753
804
|
for idx, case_node in cases.items():
|
|
805
|
+
assert case_node.addr is not None
|
|
754
806
|
addr2caseids[case_node.addr].append(idx)
|
|
755
807
|
try:
|
|
756
808
|
last_stmt = self.cond_proc.get_last_statement(case_node)
|
|
@@ -842,12 +894,12 @@ class StructurerBase(Analysis):
|
|
|
842
894
|
if isinstance(last_stmt.false_target, ailment.Expr.Const):
|
|
843
895
|
jump_targets.append((last_stmt.false_target.value, last_stmt.false_target_idx))
|
|
844
896
|
if any(tpl in addr_and_ids for tpl in jump_targets):
|
|
845
|
-
return remove_last_statement(node)
|
|
897
|
+
return remove_last_statement(node) # type: ignore
|
|
846
898
|
return None
|
|
847
899
|
|
|
848
900
|
@staticmethod
|
|
849
901
|
def _remove_last_statement_if_jump(
|
|
850
|
-
node: BaseNode | ailment.Block,
|
|
902
|
+
node: BaseNode | ailment.Block | MultiNode,
|
|
851
903
|
) -> ailment.Stmt.Jump | ailment.Stmt.ConditionalJump | None:
|
|
852
904
|
try:
|
|
853
905
|
last_stmts = ConditionProcessor.get_last_statements(node)
|
|
@@ -855,7 +907,7 @@ class StructurerBase(Analysis):
|
|
|
855
907
|
return None
|
|
856
908
|
|
|
857
909
|
if len(last_stmts) == 1 and isinstance(last_stmts[0], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump)):
|
|
858
|
-
return remove_last_statement(node)
|
|
910
|
+
return remove_last_statement(node) # type: ignore
|
|
859
911
|
return None
|
|
860
912
|
|
|
861
913
|
@staticmethod
|
|
@@ -945,8 +997,8 @@ class StructurerBase(Analysis):
|
|
|
945
997
|
@staticmethod
|
|
946
998
|
def replace_node_in_node(
|
|
947
999
|
parent_node: BaseNode,
|
|
948
|
-
old_node: BaseNode | ailment.Block,
|
|
949
|
-
new_node: BaseNode | ailment.Block,
|
|
1000
|
+
old_node: BaseNode | ailment.Block | MultiNode,
|
|
1001
|
+
new_node: BaseNode | ailment.Block | MultiNode,
|
|
950
1002
|
) -> None:
|
|
951
1003
|
if isinstance(parent_node, SequenceNode):
|
|
952
1004
|
for i in range(len(parent_node.nodes)): # pylint:disable=consider-using-enumerate
|
|
@@ -969,7 +1021,9 @@ class StructurerBase(Analysis):
|
|
|
969
1021
|
raise TypeError(f"Unsupported node type {type(parent_node)}")
|
|
970
1022
|
|
|
971
1023
|
@staticmethod
|
|
972
|
-
def is_a_jump_target(
|
|
1024
|
+
def is_a_jump_target(
|
|
1025
|
+
stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Jump | ailment.Stmt.Statement, addr: int
|
|
1026
|
+
) -> bool:
|
|
973
1027
|
if isinstance(stmt, ailment.Stmt.ConditionalJump):
|
|
974
1028
|
if isinstance(stmt.true_target, ailment.Expr.Const) and stmt.true_target.value == addr:
|
|
975
1029
|
return True
|
|
@@ -989,3 +1043,24 @@ class StructurerBase(Analysis):
|
|
|
989
1043
|
if isinstance(node, SequenceNode):
|
|
990
1044
|
return any(StructurerBase.has_nonlabel_nonphi_statements(nn) for nn in node.nodes)
|
|
991
1045
|
return False
|
|
1046
|
+
|
|
1047
|
+
def _node_ending_with_jump_table_header(self, node: BaseNode) -> tuple[int | None, IndirectJump | None]:
|
|
1048
|
+
if isinstance(node, (ailment.Block, MultiNode, IncompleteSwitchCaseNode)):
|
|
1049
|
+
assert node.addr is not None
|
|
1050
|
+
return node.addr, self.jump_tables.get(node.addr, None)
|
|
1051
|
+
if isinstance(node, SequenceNode):
|
|
1052
|
+
return node.addr, self._node_ending_with_jump_table_header(node.nodes[-1])[1]
|
|
1053
|
+
return None, None
|
|
1054
|
+
|
|
1055
|
+
@staticmethod
|
|
1056
|
+
def _switch_find_default_node(
|
|
1057
|
+
graph: networkx.DiGraph, head_node: BaseNode, default_node_addr: int
|
|
1058
|
+
) -> BaseNode | None:
|
|
1059
|
+
# it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
|
|
1060
|
+
# and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
|
|
1061
|
+
# successor to node_a
|
|
1062
|
+
default_node_candidates = [nn for nn in graph.nodes if nn.addr == default_node_addr]
|
|
1063
|
+
node_default: BaseNode | None = next(
|
|
1064
|
+
iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
|
|
1065
|
+
)
|
|
1066
|
+
return node_default
|
|
@@ -230,7 +230,12 @@ class CascadingConditionNode(BaseNode):
|
|
|
230
230
|
"else_node",
|
|
231
231
|
)
|
|
232
232
|
|
|
233
|
-
def __init__(
|
|
233
|
+
def __init__(
|
|
234
|
+
self,
|
|
235
|
+
addr,
|
|
236
|
+
condition_and_nodes: list[tuple[Any, BaseNode | ailment.Block | MultiNode]],
|
|
237
|
+
else_node: BaseNode = None,
|
|
238
|
+
):
|
|
234
239
|
self.addr = addr
|
|
235
240
|
self.condition_and_nodes = condition_and_nodes
|
|
236
241
|
self.else_node = else_node
|
|
@@ -144,7 +144,9 @@ def extract_jump_targets(stmt):
|
|
|
144
144
|
return targets
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
def switch_extract_cmp_bounds(
|
|
147
|
+
def switch_extract_cmp_bounds(
|
|
148
|
+
last_stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Statement,
|
|
149
|
+
) -> tuple[Any, int, int] | None:
|
|
148
150
|
"""
|
|
149
151
|
Check the last statement of the switch-case header node, and extract lower+upper bounds for the comparison.
|
|
150
152
|
|
|
@@ -175,6 +177,54 @@ def switch_extract_cmp_bounds(last_stmt: ailment.Stmt.ConditionalJump) -> tuple[
|
|
|
175
177
|
return None
|
|
176
178
|
|
|
177
179
|
|
|
180
|
+
def switch_extract_switch_expr_from_jump_target(target: ailment.Expr.Expression) -> ailment.Expr.Expression | None:
|
|
181
|
+
"""
|
|
182
|
+
Extract the switch expression from the indirect jump target expression.
|
|
183
|
+
|
|
184
|
+
:param target: The target of the indirect jump statement.
|
|
185
|
+
:return: The extracted expression if successful, or None otherwise.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
# e.g.: Jump (Conv(32->64, (Load(addr=((0x140000000<64> + (vvar_229{reg 80} * 0x4<64>)) + 0x2290<64>),
|
|
189
|
+
# size=4,
|
|
190
|
+
# endness=Iend_LE
|
|
191
|
+
# ) + 0x140000000<32>)))
|
|
192
|
+
|
|
193
|
+
found_load = False
|
|
194
|
+
while True:
|
|
195
|
+
if isinstance(target, ailment.Expr.Convert):
|
|
196
|
+
if target.from_bits < target.to_bits:
|
|
197
|
+
target = target.operand
|
|
198
|
+
else:
|
|
199
|
+
return None
|
|
200
|
+
elif isinstance(target, ailment.Expr.BinaryOp):
|
|
201
|
+
if target.op == "Add":
|
|
202
|
+
# it must be adding the target expr with a constant
|
|
203
|
+
if isinstance(target.operands[0], ailment.Expr.Const):
|
|
204
|
+
target = target.operands[1]
|
|
205
|
+
elif isinstance(target.operands[1], ailment.Expr.Const):
|
|
206
|
+
target = target.operands[0]
|
|
207
|
+
else:
|
|
208
|
+
return None
|
|
209
|
+
elif target.op == "Mul":
|
|
210
|
+
# it must be multiplying the target expr with a constant
|
|
211
|
+
if isinstance(target.operands[0], ailment.Expr.Const):
|
|
212
|
+
target = target.operands[1]
|
|
213
|
+
elif isinstance(target.operands[1], ailment.Expr.Const):
|
|
214
|
+
target = target.operands[0]
|
|
215
|
+
else:
|
|
216
|
+
return None
|
|
217
|
+
elif isinstance(target, ailment.Expr.Load):
|
|
218
|
+
# we want the address!
|
|
219
|
+
found_load = True
|
|
220
|
+
target = target.addr
|
|
221
|
+
elif isinstance(target, ailment.Expr.VirtualVariable):
|
|
222
|
+
break
|
|
223
|
+
else:
|
|
224
|
+
return None
|
|
225
|
+
return target if found_load else None
|
|
226
|
+
|
|
227
|
+
|
|
178
228
|
def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tuple[Any, int, int] | None:
|
|
179
229
|
"""
|
|
180
230
|
Check the last statement of the switch-case header node (whose address is loaded from a jump table and computed
|
|
@@ -973,6 +1023,15 @@ def sequence_to_statements(
|
|
|
973
1023
|
return statements
|
|
974
1024
|
|
|
975
1025
|
|
|
1026
|
+
def remove_edges_in_ailgraph(
|
|
1027
|
+
ail_graph: networkx.DiGraph, edges_to_remove: list[tuple[tuple[int, int | None], tuple[int, int | None]]]
|
|
1028
|
+
) -> None:
|
|
1029
|
+
d = {(bb.addr, bb.idx): bb for bb in ail_graph}
|
|
1030
|
+
for src_addr, dst_addr in edges_to_remove:
|
|
1031
|
+
if src_addr in d and dst_addr in d and ail_graph.has_edge(d[src_addr], d[dst_addr]):
|
|
1032
|
+
ail_graph.remove_edge(d[src_addr], d[dst_addr])
|
|
1033
|
+
|
|
1034
|
+
|
|
976
1035
|
# delayed import
|
|
977
1036
|
from .structuring.structurer_nodes import (
|
|
978
1037
|
MultiNode,
|
|
@@ -12,6 +12,7 @@ import claripy
|
|
|
12
12
|
from angr import SIM_LIBRARIES
|
|
13
13
|
from angr.calling_conventions import SimRegArg
|
|
14
14
|
from angr.errors import SimMemoryMissingError
|
|
15
|
+
from angr.knowledge_base import KnowledgeBase
|
|
15
16
|
from angr.knowledge_plugins.key_definitions.constants import ObservationPointType
|
|
16
17
|
from angr.sim_type import SimTypePointer, SimTypeChar
|
|
17
18
|
from angr.analyses import Analysis, AnalysesHub
|
|
@@ -25,6 +26,8 @@ from angr.analyses.decompiler.structured_codegen.c import (
|
|
|
25
26
|
CVariable,
|
|
26
27
|
)
|
|
27
28
|
|
|
29
|
+
from .api_obf_type2_finder import APIObfuscationType2Finder
|
|
30
|
+
|
|
28
31
|
_l = logging.getLogger(name=__name__)
|
|
29
32
|
|
|
30
33
|
|
|
@@ -33,7 +36,7 @@ class APIObfuscationType(IntEnum):
|
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class APIDeobFuncDescriptor:
|
|
36
|
-
def __init__(self, type_: APIObfuscationType, func_addr
|
|
39
|
+
def __init__(self, type_: APIObfuscationType, *, func_addr: int, libname_argidx: int, funcname_argidx: int):
|
|
37
40
|
self.type = type_
|
|
38
41
|
self.func_addr = func_addr
|
|
39
42
|
self.libname_argidx = libname_argidx
|
|
@@ -90,11 +93,13 @@ class APIObfuscationFinder(Analysis):
|
|
|
90
93
|
|
|
91
94
|
Currently, we support the following API "obfuscation" styles:
|
|
92
95
|
|
|
93
|
-
- sub_A("dll_name", "api_name) where
|
|
96
|
+
- Type 1: sub_A("dll_name", "api_name") where sub_A ends up calling LoadLibrary.
|
|
97
|
+
- Type 2: GetProcAddress(_, "api_name").
|
|
94
98
|
"""
|
|
95
99
|
|
|
96
|
-
def __init__(self):
|
|
100
|
+
def __init__(self, variable_kb: KnowledgeBase | None = None):
|
|
97
101
|
self.type1_candidates = []
|
|
102
|
+
self.variable_kb = variable_kb or self.project.kb
|
|
98
103
|
|
|
99
104
|
self.analyze()
|
|
100
105
|
|
|
@@ -106,6 +111,8 @@ class APIObfuscationFinder(Analysis):
|
|
|
106
111
|
type1_deobfuscated = self._analyze_type1(desc.func_addr, desc)
|
|
107
112
|
self.kb.obfuscations.type1_deobfuscated_apis.update(type1_deobfuscated)
|
|
108
113
|
|
|
114
|
+
APIObfuscationType2Finder(self.project, self.variable_kb).analyze()
|
|
115
|
+
|
|
109
116
|
def _find_type1(self):
|
|
110
117
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
111
118
|
load_library_funcs = []
|
|
@@ -190,6 +197,8 @@ class APIObfuscationFinder(Analysis):
|
|
|
190
197
|
callsite_node.instruction_addrs[-1],
|
|
191
198
|
ObservationPointType.OP_BEFORE,
|
|
192
199
|
)
|
|
200
|
+
if observ is None:
|
|
201
|
+
continue
|
|
193
202
|
args: list[tuple[int, Any]] = []
|
|
194
203
|
for arg_idx, func_arg in enumerate(func.arguments):
|
|
195
204
|
# FIXME: We are ignoring all non-register function arguments until we see a test case where
|
|
@@ -227,9 +236,8 @@ class APIObfuscationFinder(Analysis):
|
|
|
227
236
|
acceptable_args = False
|
|
228
237
|
break
|
|
229
238
|
arg_strs.append((idx, value.decode("utf-8")))
|
|
230
|
-
if acceptable_args:
|
|
239
|
+
if acceptable_args and len(arg_strs) == 2:
|
|
231
240
|
libname_arg_idx, funcname_arg_idx = None, None
|
|
232
|
-
assert len(arg_strs) == 2
|
|
233
241
|
for arg_idx, name in arg_strs:
|
|
234
242
|
if self.is_libname(name):
|
|
235
243
|
libname_arg_idx = arg_idx
|