angr 9.2.139__py3-none-manylinux2014_x86_64.whl → 9.2.141__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 +136 -53
- angr/analyses/calling_convention/fact_collector.py +44 -18
- angr/analyses/calling_convention/utils.py +3 -1
- angr/analyses/cfg/cfg_base.py +13 -0
- angr/analyses/cfg/cfg_fast.py +11 -0
- angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +9 -8
- angr/analyses/decompiler/ail_simplifier.py +115 -72
- angr/analyses/decompiler/callsite_maker.py +24 -11
- angr/analyses/decompiler/clinic.py +78 -43
- angr/analyses/decompiler/decompiler.py +18 -7
- angr/analyses/decompiler/expression_narrower.py +1 -1
- angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
- angr/analyses/decompiler/optimization_passes/duplication_reverter/duplication_reverter.py +3 -1
- angr/analyses/decompiler/optimization_passes/flip_boolean_cmp.py +21 -2
- angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +84 -15
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +92 -11
- angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +53 -9
- angr/analyses/decompiler/peephole_optimizations/eager_eval.py +44 -7
- angr/analyses/decompiler/region_identifier.py +6 -4
- angr/analyses/decompiler/region_simplifiers/expr_folding.py +287 -122
- angr/analyses/decompiler/region_simplifiers/region_simplifier.py +31 -13
- angr/analyses/decompiler/ssailification/rewriting.py +23 -15
- angr/analyses/decompiler/ssailification/rewriting_engine.py +105 -24
- angr/analyses/decompiler/ssailification/ssailification.py +22 -14
- angr/analyses/decompiler/structured_codegen/c.py +73 -137
- angr/analyses/decompiler/structuring/dream.py +22 -18
- angr/analyses/decompiler/structuring/phoenix.py +158 -41
- angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
- angr/analyses/decompiler/structuring/structurer_base.py +37 -10
- angr/analyses/decompiler/structuring/structurer_nodes.py +4 -1
- angr/analyses/decompiler/utils.py +106 -21
- angr/analyses/deobfuscator/api_obf_finder.py +8 -5
- angr/analyses/deobfuscator/api_obf_type2_finder.py +18 -10
- 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 +6 -41
- angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
- angr/analyses/s_reaching_definitions/s_rda_view.py +43 -25
- 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 +7 -5
- angr/analyses/variable_recovery/engine_vex.py +20 -4
- angr/block.py +69 -107
- angr/callable.py +14 -7
- angr/calling_conventions.py +30 -11
- angr/distributed/__init__.py +1 -1
- angr/engines/__init__.py +7 -8
- angr/engines/engine.py +1 -120
- angr/engines/failure.py +2 -2
- angr/engines/hook.py +2 -2
- angr/engines/light/engine.py +2 -2
- angr/engines/pcode/engine.py +2 -14
- 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 +124 -11
- angr/engines/syscall.py +2 -2
- angr/engines/unicorn.py +3 -3
- angr/engines/vex/heavy/heavy.py +3 -15
- angr/factory.py +12 -22
- angr/knowledge_plugins/key_definitions/atoms.py +8 -4
- angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
- angr/knowledge_plugins/variables/variable_manager.py +7 -5
- angr/sim_type.py +19 -17
- angr/simos/simos.py +3 -1
- 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/utils/types.py +48 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/METADATA +6 -6
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/RECORD +87 -86
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/LICENSE +0 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/WHEEL +0 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/entry_points.txt +0 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/top_level.txt +0 -0
|
@@ -20,6 +20,7 @@ from angr.analyses.decompiler.utils import (
|
|
|
20
20
|
)
|
|
21
21
|
from angr.analyses.decompiler.label_collector import LabelCollector
|
|
22
22
|
from angr.errors import AngrDecompilationError
|
|
23
|
+
from angr.knowledge_plugins.cfg import IndirectJump
|
|
23
24
|
from .structurer_nodes import (
|
|
24
25
|
MultiNode,
|
|
25
26
|
SequenceNode,
|
|
@@ -33,6 +34,7 @@ from .structurer_nodes import (
|
|
|
33
34
|
BreakNode,
|
|
34
35
|
LoopNode,
|
|
35
36
|
EmptyBlockNotice,
|
|
37
|
+
IncompleteSwitchCaseNode,
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
if TYPE_CHECKING:
|
|
@@ -60,6 +62,7 @@ class StructurerBase(Analysis):
|
|
|
60
62
|
func: Function | None = None,
|
|
61
63
|
case_entry_to_switch_head: dict[int, int] | None = None,
|
|
62
64
|
parent_region=None,
|
|
65
|
+
jump_tables: dict[int, IndirectJump] | None = None,
|
|
63
66
|
**kwargs,
|
|
64
67
|
):
|
|
65
68
|
self._region: GraphRegion = region
|
|
@@ -67,6 +70,7 @@ class StructurerBase(Analysis):
|
|
|
67
70
|
self.function = func
|
|
68
71
|
self._case_entry_to_switch_head = case_entry_to_switch_head
|
|
69
72
|
self._parent_region = parent_region
|
|
73
|
+
self.jump_tables = jump_tables or {}
|
|
70
74
|
|
|
71
75
|
self.cond_proc = (
|
|
72
76
|
condition_processor if condition_processor is not None else ConditionProcessor(self.project.arch)
|
|
@@ -304,6 +308,7 @@ class StructurerBase(Analysis):
|
|
|
304
308
|
jump_stmt = this_node.statements[-1] # type: ignore
|
|
305
309
|
|
|
306
310
|
if isinstance(jump_stmt, ailment.Stmt.Jump):
|
|
311
|
+
assert isinstance(this_node, ailment.Block)
|
|
307
312
|
next_node = node.nodes[i + 1]
|
|
308
313
|
if (
|
|
309
314
|
isinstance(jump_stmt.target, ailment.Expr.Const)
|
|
@@ -312,6 +317,7 @@ class StructurerBase(Analysis):
|
|
|
312
317
|
# this goto is useless
|
|
313
318
|
this_node.statements = this_node.statements[:-1]
|
|
314
319
|
elif isinstance(jump_stmt, ailment.Stmt.ConditionalJump):
|
|
320
|
+
assert isinstance(this_node, ailment.Block)
|
|
315
321
|
next_node = node.nodes[i + 1]
|
|
316
322
|
if (
|
|
317
323
|
isinstance(jump_stmt.true_target, ailment.Expr.Const)
|
|
@@ -366,6 +372,7 @@ class StructurerBase(Analysis):
|
|
|
366
372
|
this_node = this_node.nodes[-1]
|
|
367
373
|
|
|
368
374
|
if isinstance(jump_stmt, ailment.Stmt.Jump):
|
|
375
|
+
assert isinstance(this_node, ailment.Block)
|
|
369
376
|
next_node = node.nodes[i + 1]
|
|
370
377
|
if (
|
|
371
378
|
isinstance(jump_stmt.target, ailment.Expr.Const)
|
|
@@ -374,6 +381,7 @@ class StructurerBase(Analysis):
|
|
|
374
381
|
# this goto is useless
|
|
375
382
|
this_node.statements = this_node.statements[:-1]
|
|
376
383
|
elif isinstance(jump_stmt, ailment.Stmt.ConditionalJump):
|
|
384
|
+
assert isinstance(this_node, ailment.Block)
|
|
377
385
|
next_node = node.nodes[i + 1]
|
|
378
386
|
if (
|
|
379
387
|
isinstance(jump_stmt.true_target, ailment.Expr.Const)
|
|
@@ -785,10 +793,6 @@ class StructurerBase(Analysis):
|
|
|
785
793
|
|
|
786
794
|
return _Holder.merged, seq
|
|
787
795
|
|
|
788
|
-
#
|
|
789
|
-
# Util methods
|
|
790
|
-
#
|
|
791
|
-
|
|
792
796
|
def _reorganize_switch_cases(
|
|
793
797
|
self, cases: OrderedDict[int | tuple[int, ...], SequenceNode]
|
|
794
798
|
) -> OrderedDict[int | tuple[int, ...], SequenceNode]:
|
|
@@ -891,12 +895,12 @@ class StructurerBase(Analysis):
|
|
|
891
895
|
if isinstance(last_stmt.false_target, ailment.Expr.Const):
|
|
892
896
|
jump_targets.append((last_stmt.false_target.value, last_stmt.false_target_idx))
|
|
893
897
|
if any(tpl in addr_and_ids for tpl in jump_targets):
|
|
894
|
-
return remove_last_statement(node)
|
|
898
|
+
return remove_last_statement(node) # type: ignore
|
|
895
899
|
return None
|
|
896
900
|
|
|
897
901
|
@staticmethod
|
|
898
902
|
def _remove_last_statement_if_jump(
|
|
899
|
-
node: BaseNode | ailment.Block,
|
|
903
|
+
node: BaseNode | ailment.Block | MultiNode,
|
|
900
904
|
) -> ailment.Stmt.Jump | ailment.Stmt.ConditionalJump | None:
|
|
901
905
|
try:
|
|
902
906
|
last_stmts = ConditionProcessor.get_last_statements(node)
|
|
@@ -904,7 +908,7 @@ class StructurerBase(Analysis):
|
|
|
904
908
|
return None
|
|
905
909
|
|
|
906
910
|
if len(last_stmts) == 1 and isinstance(last_stmts[0], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump)):
|
|
907
|
-
return remove_last_statement(node)
|
|
911
|
+
return remove_last_statement(node) # type: ignore
|
|
908
912
|
return None
|
|
909
913
|
|
|
910
914
|
@staticmethod
|
|
@@ -994,8 +998,8 @@ class StructurerBase(Analysis):
|
|
|
994
998
|
@staticmethod
|
|
995
999
|
def replace_node_in_node(
|
|
996
1000
|
parent_node: BaseNode,
|
|
997
|
-
old_node: BaseNode | ailment.Block,
|
|
998
|
-
new_node: BaseNode | ailment.Block,
|
|
1001
|
+
old_node: BaseNode | ailment.Block | MultiNode,
|
|
1002
|
+
new_node: BaseNode | ailment.Block | MultiNode,
|
|
999
1003
|
) -> None:
|
|
1000
1004
|
if isinstance(parent_node, SequenceNode):
|
|
1001
1005
|
for i in range(len(parent_node.nodes)): # pylint:disable=consider-using-enumerate
|
|
@@ -1018,7 +1022,9 @@ class StructurerBase(Analysis):
|
|
|
1018
1022
|
raise TypeError(f"Unsupported node type {type(parent_node)}")
|
|
1019
1023
|
|
|
1020
1024
|
@staticmethod
|
|
1021
|
-
def is_a_jump_target(
|
|
1025
|
+
def is_a_jump_target(
|
|
1026
|
+
stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Jump | ailment.Stmt.Statement, addr: int
|
|
1027
|
+
) -> bool:
|
|
1022
1028
|
if isinstance(stmt, ailment.Stmt.ConditionalJump):
|
|
1023
1029
|
if isinstance(stmt.true_target, ailment.Expr.Const) and stmt.true_target.value == addr:
|
|
1024
1030
|
return True
|
|
@@ -1038,3 +1044,24 @@ class StructurerBase(Analysis):
|
|
|
1038
1044
|
if isinstance(node, SequenceNode):
|
|
1039
1045
|
return any(StructurerBase.has_nonlabel_nonphi_statements(nn) for nn in node.nodes)
|
|
1040
1046
|
return False
|
|
1047
|
+
|
|
1048
|
+
def _node_ending_with_jump_table_header(self, node: BaseNode) -> tuple[int | None, IndirectJump | None]:
|
|
1049
|
+
if isinstance(node, (ailment.Block, MultiNode, IncompleteSwitchCaseNode)):
|
|
1050
|
+
assert node.addr is not None
|
|
1051
|
+
return node.addr, self.jump_tables.get(node.addr, None)
|
|
1052
|
+
if isinstance(node, SequenceNode):
|
|
1053
|
+
return node.addr, self._node_ending_with_jump_table_header(node.nodes[-1])[1]
|
|
1054
|
+
return None, None
|
|
1055
|
+
|
|
1056
|
+
@staticmethod
|
|
1057
|
+
def _switch_find_default_node(
|
|
1058
|
+
graph: networkx.DiGraph, head_node: BaseNode, default_node_addr: int
|
|
1059
|
+
) -> BaseNode | None:
|
|
1060
|
+
# it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
|
|
1061
|
+
# and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
|
|
1062
|
+
# successor to node_a
|
|
1063
|
+
default_node_candidates = [nn for nn in graph.nodes if nn.addr == default_node_addr]
|
|
1064
|
+
node_default: BaseNode | None = next(
|
|
1065
|
+
iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
|
|
1066
|
+
)
|
|
1067
|
+
return node_default
|
|
@@ -231,7 +231,10 @@ class CascadingConditionNode(BaseNode):
|
|
|
231
231
|
)
|
|
232
232
|
|
|
233
233
|
def __init__(
|
|
234
|
-
self,
|
|
234
|
+
self,
|
|
235
|
+
addr,
|
|
236
|
+
condition_and_nodes: list[tuple[Any, BaseNode | ailment.Block | MultiNode]],
|
|
237
|
+
else_node: BaseNode = None,
|
|
235
238
|
):
|
|
236
239
|
self.addr = addr
|
|
237
240
|
self.condition_and_nodes = condition_and_nodes
|
|
@@ -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
|
|
@@ -575,6 +625,48 @@ def update_labels(graph: networkx.DiGraph):
|
|
|
575
625
|
return add_labels(remove_labels(graph))
|
|
576
626
|
|
|
577
627
|
|
|
628
|
+
def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailment.Block]:
|
|
629
|
+
if not packed_node or not packed_node.nodes:
|
|
630
|
+
return []
|
|
631
|
+
|
|
632
|
+
blocks = []
|
|
633
|
+
if packed_node.nodes is not None:
|
|
634
|
+
for _node in packed_node.nodes:
|
|
635
|
+
if isinstance(_node, (SequenceNode, MultiNode)):
|
|
636
|
+
blocks += _flatten_structured_node(_node)
|
|
637
|
+
else:
|
|
638
|
+
blocks.append(_node)
|
|
639
|
+
|
|
640
|
+
return blocks
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _find_node_in_graph(node: ailment.Block, graph: networkx.DiGraph) -> ailment.Block | None:
|
|
644
|
+
for bb in graph:
|
|
645
|
+
if bb.addr == node.addr and bb.idx == node.idx:
|
|
646
|
+
return bb
|
|
647
|
+
return None
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def structured_node_has_multi_predecessors(node: SequenceNode | MultiNode, graph: networkx.DiGraph) -> bool:
|
|
651
|
+
if graph is None:
|
|
652
|
+
return False
|
|
653
|
+
|
|
654
|
+
first_block = None
|
|
655
|
+
if isinstance(node, (SequenceNode, MultiNode)) and node.nodes:
|
|
656
|
+
flat_blocks = _flatten_structured_node(node)
|
|
657
|
+
node = flat_blocks[0]
|
|
658
|
+
|
|
659
|
+
if isinstance(node, ailment.Block):
|
|
660
|
+
first_block = node
|
|
661
|
+
|
|
662
|
+
if first_block is not None:
|
|
663
|
+
graph_node = _find_node_in_graph(first_block, graph)
|
|
664
|
+
if graph_node is not None:
|
|
665
|
+
return len(list(graph.predecessors(graph_node))) > 1
|
|
666
|
+
|
|
667
|
+
return False
|
|
668
|
+
|
|
669
|
+
|
|
578
670
|
def structured_node_is_simple_return(
|
|
579
671
|
node: SequenceNode | MultiNode, graph: networkx.DiGraph, use_packed_successors=False
|
|
580
672
|
) -> bool:
|
|
@@ -589,21 +681,6 @@ def structured_node_is_simple_return(
|
|
|
589
681
|
|
|
590
682
|
Returns true on any block ending in linear statements and a return.
|
|
591
683
|
"""
|
|
592
|
-
|
|
593
|
-
def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailment.Block]:
|
|
594
|
-
if not packed_node or not packed_node.nodes:
|
|
595
|
-
return []
|
|
596
|
-
|
|
597
|
-
blocks = []
|
|
598
|
-
if packed_node.nodes is not None:
|
|
599
|
-
for _node in packed_node.nodes:
|
|
600
|
-
if isinstance(_node, (SequenceNode, MultiNode)):
|
|
601
|
-
blocks += _flatten_structured_node(_node)
|
|
602
|
-
else:
|
|
603
|
-
blocks.append(_node)
|
|
604
|
-
|
|
605
|
-
return blocks
|
|
606
|
-
|
|
607
684
|
# sanity check: we need a graph to understand returning blocks
|
|
608
685
|
if graph is None:
|
|
609
686
|
return False
|
|
@@ -626,11 +703,10 @@ def structured_node_is_simple_return(
|
|
|
626
703
|
if valid_last_stmt:
|
|
627
704
|
# note that the block may not be the same block in the AIL graph post dephication. we must find the block again
|
|
628
705
|
# in the graph.
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
return not succs or succs == [bb]
|
|
706
|
+
last_graph_block = _find_node_in_graph(last_block, graph)
|
|
707
|
+
if last_graph_block is not None:
|
|
708
|
+
succs = list(graph.successors(last_graph_block))
|
|
709
|
+
return not succs or succs == [last_graph_block]
|
|
634
710
|
return False
|
|
635
711
|
|
|
636
712
|
|
|
@@ -973,6 +1049,15 @@ def sequence_to_statements(
|
|
|
973
1049
|
return statements
|
|
974
1050
|
|
|
975
1051
|
|
|
1052
|
+
def remove_edges_in_ailgraph(
|
|
1053
|
+
ail_graph: networkx.DiGraph, edges_to_remove: list[tuple[tuple[int, int | None], tuple[int, int | None]]]
|
|
1054
|
+
) -> None:
|
|
1055
|
+
d = {(bb.addr, bb.idx): bb for bb in ail_graph}
|
|
1056
|
+
for src_addr, dst_addr in edges_to_remove:
|
|
1057
|
+
if src_addr in d and dst_addr in d and ail_graph.has_edge(d[src_addr], d[dst_addr]):
|
|
1058
|
+
ail_graph.remove_edge(d[src_addr], d[dst_addr])
|
|
1059
|
+
|
|
1060
|
+
|
|
976
1061
|
# delayed import
|
|
977
1062
|
from .structuring.structurer_nodes import (
|
|
978
1063
|
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
|
|
@@ -35,7 +36,7 @@ class APIObfuscationType(IntEnum):
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class APIDeobFuncDescriptor:
|
|
38
|
-
def __init__(self, type_: APIObfuscationType, func_addr
|
|
39
|
+
def __init__(self, type_: APIObfuscationType, *, func_addr: int, libname_argidx: int, funcname_argidx: int):
|
|
39
40
|
self.type = type_
|
|
40
41
|
self.func_addr = func_addr
|
|
41
42
|
self.libname_argidx = libname_argidx
|
|
@@ -96,8 +97,9 @@ class APIObfuscationFinder(Analysis):
|
|
|
96
97
|
- Type 2: GetProcAddress(_, "api_name").
|
|
97
98
|
"""
|
|
98
99
|
|
|
99
|
-
def __init__(self):
|
|
100
|
+
def __init__(self, variable_kb: KnowledgeBase | None = None):
|
|
100
101
|
self.type1_candidates = []
|
|
102
|
+
self.variable_kb = variable_kb or self.project.kb
|
|
101
103
|
|
|
102
104
|
self.analyze()
|
|
103
105
|
|
|
@@ -109,7 +111,7 @@ class APIObfuscationFinder(Analysis):
|
|
|
109
111
|
type1_deobfuscated = self._analyze_type1(desc.func_addr, desc)
|
|
110
112
|
self.kb.obfuscations.type1_deobfuscated_apis.update(type1_deobfuscated)
|
|
111
113
|
|
|
112
|
-
APIObfuscationType2Finder(self.project).analyze()
|
|
114
|
+
APIObfuscationType2Finder(self.project, self.variable_kb).analyze()
|
|
113
115
|
|
|
114
116
|
def _find_type1(self):
|
|
115
117
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
@@ -195,6 +197,8 @@ class APIObfuscationFinder(Analysis):
|
|
|
195
197
|
callsite_node.instruction_addrs[-1],
|
|
196
198
|
ObservationPointType.OP_BEFORE,
|
|
197
199
|
)
|
|
200
|
+
if observ is None:
|
|
201
|
+
continue
|
|
198
202
|
args: list[tuple[int, Any]] = []
|
|
199
203
|
for arg_idx, func_arg in enumerate(func.arguments):
|
|
200
204
|
# FIXME: We are ignoring all non-register function arguments until we see a test case where
|
|
@@ -232,9 +236,8 @@ class APIObfuscationFinder(Analysis):
|
|
|
232
236
|
acceptable_args = False
|
|
233
237
|
break
|
|
234
238
|
arg_strs.append((idx, value.decode("utf-8")))
|
|
235
|
-
if acceptable_args:
|
|
239
|
+
if acceptable_args and len(arg_strs) == 2:
|
|
236
240
|
libname_arg_idx, funcname_arg_idx = None, None
|
|
237
|
-
assert len(arg_strs) == 2
|
|
238
241
|
for arg_idx, name in arg_strs:
|
|
239
242
|
if self.is_libname(name):
|
|
240
243
|
libname_arg_idx = arg_idx
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import cast
|
|
2
|
+
from typing import TYPE_CHECKING, cast
|
|
3
3
|
|
|
4
4
|
from collections.abc import Iterator
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
8
|
from angr.project import Project
|
|
9
|
-
from angr.
|
|
10
|
-
ReachingDefinitionsAnalysis,
|
|
11
|
-
FunctionCallRelationships,
|
|
12
|
-
)
|
|
9
|
+
from angr.knowledge_base import KnowledgeBase
|
|
13
10
|
from angr.knowledge_plugins.functions.function import Function
|
|
14
11
|
from angr.knowledge_plugins.key_definitions import DerefSize
|
|
15
12
|
from angr.knowledge_plugins.key_definitions.constants import ObservationPointType
|
|
16
13
|
from angr.knowledge_plugins.key_definitions.atoms import MemoryLocation
|
|
17
14
|
from angr.sim_variable import SimMemoryVariable
|
|
18
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from angr.analyses.reaching_definitions import (
|
|
18
|
+
ReachingDefinitionsAnalysis,
|
|
19
|
+
FunctionCallRelationships,
|
|
20
|
+
)
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
log = logging.getLogger(__name__)
|
|
21
24
|
|
|
@@ -40,8 +43,9 @@ class APIObfuscationType2Finder:
|
|
|
40
43
|
|
|
41
44
|
results: list[APIObfuscationType2]
|
|
42
45
|
|
|
43
|
-
def __init__(self, project: Project):
|
|
46
|
+
def __init__(self, project: Project, variable_kb: KnowledgeBase | None = None):
|
|
44
47
|
self.project = project
|
|
48
|
+
self.variable_kb = variable_kb or self.project.kb
|
|
45
49
|
self.results = []
|
|
46
50
|
|
|
47
51
|
def analyze(self) -> list[APIObfuscationType2]:
|
|
@@ -91,8 +95,12 @@ class APIObfuscationType2Finder:
|
|
|
91
95
|
log.debug("...Failed to resolve a function name")
|
|
92
96
|
return
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
try:
|
|
99
|
+
func_name = result.rstrip(b"\x00").decode("utf-8")
|
|
100
|
+
log.debug("...Resolved concrete function name: %s", func_name)
|
|
101
|
+
except UnicodeDecodeError:
|
|
102
|
+
log.debug("...Failed to decode utf-8 function name")
|
|
103
|
+
return
|
|
96
104
|
|
|
97
105
|
# Examine successor definitions to find where the function pointer is written
|
|
98
106
|
for successor in rda.dep_graph.find_all_successors(callsite_info.ret_defns):
|
|
@@ -121,7 +129,7 @@ class APIObfuscationType2Finder:
|
|
|
121
129
|
|
|
122
130
|
self.results.append(
|
|
123
131
|
APIObfuscationType2(
|
|
124
|
-
resolved_func_name=
|
|
132
|
+
resolved_func_name=func_name,
|
|
125
133
|
resolved_func_ptr=ptr,
|
|
126
134
|
resolved_in=caller,
|
|
127
135
|
resolved_by=callee,
|
|
@@ -139,7 +147,7 @@ class APIObfuscationType2Finder:
|
|
|
139
147
|
log.debug("...Created label %s for address %x", lbl, result.resolved_func_ptr.addr)
|
|
140
148
|
|
|
141
149
|
# Create a variable
|
|
142
|
-
global_variables = self.
|
|
150
|
+
global_variables = self.variable_kb.variables["global"]
|
|
143
151
|
variables = global_variables.get_global_variables(result.resolved_func_ptr.addr)
|
|
144
152
|
if not variables:
|
|
145
153
|
ident = global_variables.next_variable_ident("global")
|