angr 9.2.163__cp310-abi3-win_amd64.whl → 9.2.165__cp310-abi3-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/ailment/converter_vex.py +1 -1
- angr/ailment/expression.py +5 -1
- angr/analyses/analysis.py +27 -4
- angr/analyses/cfg/cfg_base.py +16 -13
- angr/analyses/cfg/cfg_emulated.py +5 -1
- angr/analyses/cfg/cfg_fast.py +43 -5
- angr/analyses/cfg/indirect_jump_resolvers/arm_elf_fast.py +11 -1
- angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +194 -41
- angr/analyses/decompiler/ail_simplifier.py +19 -5
- angr/analyses/decompiler/callsite_maker.py +33 -17
- angr/analyses/decompiler/condition_processor.py +9 -8
- angr/analyses/decompiler/graph_region.py +19 -0
- angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
- angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
- angr/analyses/decompiler/peephole_optimizations/inlined_memcpy.py +78 -0
- angr/analyses/decompiler/peephole_optimizations/inlined_strcpy.py +67 -10
- angr/analyses/decompiler/peephole_optimizations/inlined_strcpy_consolidation.py +10 -13
- angr/analyses/decompiler/region_identifier.py +22 -1
- angr/analyses/decompiler/structuring/phoenix.py +72 -20
- angr/analyses/decompiler/structuring/recursive_structurer.py +3 -4
- angr/analyses/decompiler/structuring/structurer_nodes.py +3 -0
- angr/analyses/decompiler/utils.py +17 -5
- angr/analyses/deobfuscator/string_obf_finder.py +130 -32
- angr/analyses/s_reaching_definitions/s_rda_view.py +2 -1
- angr/analyses/typehoon/typeconsts.py +3 -1
- angr/blade.py +20 -15
- angr/engines/icicle.py +16 -3
- angr/knowledge_plugins/propagations/propagation_model.py +7 -0
- angr/rustylib.pyd +0 -0
- angr/sim_type.py +16 -1
- angr/state_plugins/history.py +16 -0
- angr/unicornlib.dll +0 -0
- angr/utils/constants.py +1 -1
- angr/utils/graph.py +1 -1
- angr/utils/vex.py +11 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/METADATA +5 -5
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/RECORD +42 -40
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/WHEEL +0 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/entry_points.txt +0 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/licenses/LICENSE +0 -0
- {angr-9.2.163.dist-info → angr-9.2.165.dist-info}/top_level.txt +0 -0
|
@@ -13,7 +13,7 @@ from angr.ailment.block import Block
|
|
|
13
13
|
from angr.ailment.statement import Statement, ConditionalJump, Jump, Label, Return
|
|
14
14
|
from angr.ailment.expression import Const, UnaryOp, MultiStatementExpression, BinaryOp
|
|
15
15
|
|
|
16
|
-
from angr.utils.graph import GraphUtils
|
|
16
|
+
from angr.utils.graph import GraphUtils, Dominators, compute_dominance_frontier
|
|
17
17
|
from angr.utils.ail import is_phi_assignment, is_head_controlled_loop_block
|
|
18
18
|
from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
|
|
19
19
|
from angr.utils.constants import SWITCH_MISSING_DEFAULT_NODE_ADDR
|
|
@@ -669,7 +669,7 @@ class PhoenixStructurer(StructurerBase):
|
|
|
669
669
|
continue_node = loop_head
|
|
670
670
|
|
|
671
671
|
is_while, result_while = self._refine_cyclic_is_while_loop(graph, fullgraph, loop_head, head_succs)
|
|
672
|
-
is_dowhile, result_dowhile = self._refine_cyclic_is_dowhile_loop(graph, fullgraph, loop_head
|
|
672
|
+
is_dowhile, result_dowhile = self._refine_cyclic_is_dowhile_loop(graph, fullgraph, loop_head)
|
|
673
673
|
|
|
674
674
|
continue_edges: list[tuple[BaseNode, BaseNode]] = []
|
|
675
675
|
outgoing_edges: list = []
|
|
@@ -702,22 +702,12 @@ class PhoenixStructurer(StructurerBase):
|
|
|
702
702
|
|
|
703
703
|
if loop_type is None:
|
|
704
704
|
# natural loop. select *any* exit edge to determine the successor
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
if loop_head is succ:
|
|
712
|
-
continue_edges.append((node, succ))
|
|
713
|
-
if successor_candidates:
|
|
714
|
-
successor_candidates = sorted(successor_candidates, key=lambda x: x.addr)
|
|
715
|
-
successor = successor_candidates[0]
|
|
716
|
-
# virtualize all other edges
|
|
717
|
-
for succ in successor_candidates:
|
|
718
|
-
for pred in fullgraph.predecessors(succ):
|
|
719
|
-
if pred in graph:
|
|
720
|
-
outgoing_edges.append((pred, succ))
|
|
705
|
+
is_natural, result_natural = self._refine_cyclic_make_natural_loop(graph, fullgraph, loop_head)
|
|
706
|
+
if not is_natural:
|
|
707
|
+
# cannot refine this loop
|
|
708
|
+
return False
|
|
709
|
+
assert result_natural is not None
|
|
710
|
+
continue_edges, outgoing_edges, successor = result_natural
|
|
721
711
|
|
|
722
712
|
if outgoing_edges:
|
|
723
713
|
# if there is a single successor, we convert all out-going edges into breaks;
|
|
@@ -963,8 +953,8 @@ class PhoenixStructurer(StructurerBase):
|
|
|
963
953
|
return True, (continue_edges, outgoing_edges, loop_head, successor)
|
|
964
954
|
return False, None
|
|
965
955
|
|
|
966
|
-
def _refine_cyclic_is_dowhile_loop(
|
|
967
|
-
self, graph, fullgraph, loop_head
|
|
956
|
+
def _refine_cyclic_is_dowhile_loop(
|
|
957
|
+
self, graph, fullgraph, loop_head
|
|
968
958
|
) -> tuple[bool, tuple[list, list, BaseNode, BaseNode] | None]:
|
|
969
959
|
# check if there is an out-going edge from the loop tail
|
|
970
960
|
head_preds = list(fullgraph.predecessors(loop_head))
|
|
@@ -996,6 +986,64 @@ class PhoenixStructurer(StructurerBase):
|
|
|
996
986
|
return True, (continue_edges, outgoing_edges, continue_node, successor)
|
|
997
987
|
return False, None
|
|
998
988
|
|
|
989
|
+
def _refine_cyclic_make_natural_loop(
|
|
990
|
+
self, graph, fullgraph, loop_head
|
|
991
|
+
) -> tuple[bool, tuple[list, list, Any] | None]:
|
|
992
|
+
continue_edges = []
|
|
993
|
+
outgoing_edges = []
|
|
994
|
+
|
|
995
|
+
# find dominance frontier
|
|
996
|
+
doms = Dominators(fullgraph, self._region.head)
|
|
997
|
+
dom_frontiers = compute_dominance_frontier(fullgraph, doms.dom)
|
|
998
|
+
|
|
999
|
+
if loop_head not in dom_frontiers:
|
|
1000
|
+
return False, None
|
|
1001
|
+
dom_frontier = dom_frontiers[loop_head]
|
|
1002
|
+
|
|
1003
|
+
# now this is a little complex
|
|
1004
|
+
dom_frontier = {node for node in dom_frontier if node is not loop_head}
|
|
1005
|
+
if len(dom_frontier) == 0:
|
|
1006
|
+
# the dominance frontier is empty (the loop head dominates all nodes in the full graph). however, this does
|
|
1007
|
+
# not mean that the loop head must dominate all the nodes, because we only have a limited view of the full
|
|
1008
|
+
# graph (e.g., some predecessors of the successor may not be in this full graph). as such, successors are
|
|
1009
|
+
# the ones that are in the fullgraph but not in the graph.
|
|
1010
|
+
successor_candidates = set()
|
|
1011
|
+
for node in networkx.descendants(graph, loop_head):
|
|
1012
|
+
for succ in fullgraph.successors(node):
|
|
1013
|
+
if succ not in graph:
|
|
1014
|
+
successor_candidates.add(succ)
|
|
1015
|
+
if loop_head is succ:
|
|
1016
|
+
continue_edges.append((node, succ))
|
|
1017
|
+
|
|
1018
|
+
else:
|
|
1019
|
+
# this loop has a single successor
|
|
1020
|
+
successor_candidates = dom_frontier
|
|
1021
|
+
# traverse the loop body to find all continue edges
|
|
1022
|
+
tmp_graph = networkx.DiGraph(graph)
|
|
1023
|
+
tmp_graph.remove_nodes_from(successor_candidates)
|
|
1024
|
+
for node in networkx.descendants(tmp_graph, loop_head):
|
|
1025
|
+
if tmp_graph.has_edge(node, loop_head):
|
|
1026
|
+
continue_edges.append((node, loop_head))
|
|
1027
|
+
|
|
1028
|
+
if len(successor_candidates) == 0:
|
|
1029
|
+
successor = None
|
|
1030
|
+
else:
|
|
1031
|
+
# one or multiple successors; try to pick a successor in graph, and prioritize the one with the lowest
|
|
1032
|
+
# address
|
|
1033
|
+
successor_candidates_in_graph = {nn for nn in successor_candidates if nn in graph}
|
|
1034
|
+
if successor_candidates_in_graph:
|
|
1035
|
+
# pick the one with the lowest address
|
|
1036
|
+
successor = next(iter(sorted(successor_candidates_in_graph, key=lambda x: x.addr)))
|
|
1037
|
+
else:
|
|
1038
|
+
successor = next(iter(sorted(successor_candidates, key=lambda x: x.addr)))
|
|
1039
|
+
# mark all edges as outgoing edges so they will be virtualized if they don't lead to the successor
|
|
1040
|
+
for node in successor_candidates:
|
|
1041
|
+
for pred in fullgraph.predecessors(node):
|
|
1042
|
+
if networkx.has_path(doms.dom, loop_head, pred):
|
|
1043
|
+
outgoing_edges.append((pred, node))
|
|
1044
|
+
|
|
1045
|
+
return True, (continue_edges, outgoing_edges, successor)
|
|
1046
|
+
|
|
999
1047
|
def _analyze_acyclic(self) -> bool:
|
|
1000
1048
|
# match against known schemas
|
|
1001
1049
|
l.debug("Matching acyclic schemas for region %r.", self._region)
|
|
@@ -1219,6 +1267,10 @@ class PhoenixStructurer(StructurerBase):
|
|
|
1219
1267
|
node_a = next(iter(nn for nn in graph.nodes if nn.addr == target), None)
|
|
1220
1268
|
if node_a is None:
|
|
1221
1269
|
return False
|
|
1270
|
+
if node_a is self._region.head:
|
|
1271
|
+
# avoid structuring if node_a is the region head; this means the current node is a duplicated switch-case
|
|
1272
|
+
# head (instead of the original one), which is not something we want to structure
|
|
1273
|
+
return False
|
|
1222
1274
|
|
|
1223
1275
|
# the default case
|
|
1224
1276
|
node_b_addr = next(iter(t for t in successor_addrs if t != target), None)
|
|
@@ -3,8 +3,6 @@ import itertools
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
-
import networkx
|
|
7
|
-
|
|
8
6
|
from angr.analyses import Analysis, register_analysis
|
|
9
7
|
from angr.analyses.decompiler.condition_processor import ConditionProcessor
|
|
10
8
|
from angr.analyses.decompiler.graph_region import GraphRegion
|
|
@@ -12,6 +10,7 @@ from angr.analyses.decompiler.jumptable_entry_condition_rewriter import JumpTabl
|
|
|
12
10
|
from angr.analyses.decompiler.empty_node_remover import EmptyNodeRemover
|
|
13
11
|
from angr.analyses.decompiler.jump_target_collector import JumpTargetCollector
|
|
14
12
|
from angr.analyses.decompiler.redundant_label_remover import RedundantLabelRemover
|
|
13
|
+
from angr.utils.graph import GraphUtils
|
|
15
14
|
from .structurer_nodes import BaseNode
|
|
16
15
|
from .structurer_base import StructurerBase
|
|
17
16
|
from .dream import DreamStructurer
|
|
@@ -61,7 +60,7 @@ class RecursiveStructurer(Analysis):
|
|
|
61
60
|
current_region = stack[-1]
|
|
62
61
|
|
|
63
62
|
has_region = False
|
|
64
|
-
for node in
|
|
63
|
+
for node in GraphUtils.dfs_postorder_nodes_deterministic(current_region.graph, current_region.head):
|
|
65
64
|
subnodes = []
|
|
66
65
|
if type(node) is GraphRegion:
|
|
67
66
|
if node.cyclic:
|
|
@@ -177,7 +176,7 @@ class RecursiveStructurer(Analysis):
|
|
|
177
176
|
for node in region.graph.nodes:
|
|
178
177
|
if not isinstance(node, BaseNode):
|
|
179
178
|
continue
|
|
180
|
-
if node.addr == self.function.addr:
|
|
179
|
+
if self.function is not None and node.addr == self.function.addr:
|
|
181
180
|
return node
|
|
182
181
|
if min_node is None or (min_node.addr is not None and node.addr is not None and min_node.addr < node.addr):
|
|
183
182
|
min_node = node
|
|
@@ -392,6 +392,9 @@ class IncompleteSwitchCaseNode(BaseNode):
|
|
|
392
392
|
self.head = head
|
|
393
393
|
self.cases: list = cases
|
|
394
394
|
|
|
395
|
+
def __repr__(self):
|
|
396
|
+
return f"<IncompleteSwitchCase {self.addr:#x} with {len(self.cases)} cases>"
|
|
397
|
+
|
|
395
398
|
|
|
396
399
|
#
|
|
397
400
|
# The following classes are custom AIL statements (not nodes, unfortunately)
|
|
@@ -158,10 +158,14 @@ def switch_extract_cmp_bounds(
|
|
|
158
158
|
return None
|
|
159
159
|
|
|
160
160
|
# TODO: Add more operations
|
|
161
|
-
if isinstance(last_stmt.condition, ailment.Expr.BinaryOp) and last_stmt.condition.op
|
|
161
|
+
if isinstance(last_stmt.condition, ailment.Expr.BinaryOp) and last_stmt.condition.op in {"CmpLE", "CmpLT"}:
|
|
162
162
|
if not isinstance(last_stmt.condition.operands[1], ailment.Expr.Const):
|
|
163
163
|
return None
|
|
164
|
-
cmp_ub =
|
|
164
|
+
cmp_ub = (
|
|
165
|
+
last_stmt.condition.operands[1].value
|
|
166
|
+
if last_stmt.condition.op == "CmpLE"
|
|
167
|
+
else last_stmt.condition.operands[1].value - 1
|
|
168
|
+
)
|
|
165
169
|
cmp_lb = 0
|
|
166
170
|
cmp = last_stmt.condition.operands[0]
|
|
167
171
|
if (
|
|
@@ -250,6 +254,10 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
|
|
|
250
254
|
size=4, endness=Iend_LE) + 0x4530e4<32>))
|
|
251
255
|
)
|
|
252
256
|
|
|
257
|
+
Another example:
|
|
258
|
+
|
|
259
|
+
Load(addr=(((vvar_9{reg 36} & 0x3<32>) * 0x4<32>) + 0x42cd28<32>), size=4, endness=Iend_LE)
|
|
260
|
+
|
|
253
261
|
:param last_stmt: The last statement of the switch-case header node.
|
|
254
262
|
:return: A tuple of (index expression, lower bound, upper bound), or None
|
|
255
263
|
"""
|
|
@@ -269,16 +277,20 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
|
|
|
269
277
|
continue
|
|
270
278
|
if isinstance(target, ailment.Expr.BinaryOp) and target.op == "Add":
|
|
271
279
|
if isinstance(target.operands[0], ailment.Expr.Const) and isinstance(target.operands[1], ailment.Expr.Load):
|
|
272
|
-
jump_addr_offset = target.operands[0]
|
|
280
|
+
jump_addr_offset = target.operands[0].value
|
|
273
281
|
jumptable_load_addr = target.operands[1].addr
|
|
274
282
|
break
|
|
275
283
|
if isinstance(target.operands[1], ailment.Expr.Const) and isinstance(target.operands[0], ailment.Expr.Load):
|
|
276
|
-
jump_addr_offset = target.operands[1]
|
|
284
|
+
jump_addr_offset = target.operands[1].value
|
|
277
285
|
jumptable_load_addr = target.operands[0].addr
|
|
278
286
|
break
|
|
279
287
|
return None
|
|
280
288
|
if isinstance(target, ailment.Expr.Const):
|
|
281
289
|
return None
|
|
290
|
+
if isinstance(target, ailment.Expr.Load):
|
|
291
|
+
jumptable_load_addr = target.addr
|
|
292
|
+
jump_addr_offset = 0
|
|
293
|
+
break
|
|
282
294
|
break
|
|
283
295
|
|
|
284
296
|
if jump_addr_offset is None or jumptable_load_addr is None:
|
|
@@ -655,7 +667,7 @@ def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailm
|
|
|
655
667
|
|
|
656
668
|
def _find_node_in_graph(node: ailment.Block, graph: networkx.DiGraph) -> ailment.Block | None:
|
|
657
669
|
for bb in graph:
|
|
658
|
-
if bb.addr == node.addr and bb.idx == node.idx:
|
|
670
|
+
if isinstance(bb, ailment.Block) and bb.addr == node.addr and bb.idx == node.idx:
|
|
659
671
|
return bb
|
|
660
672
|
return None
|
|
661
673
|
|
|
@@ -27,6 +27,42 @@ STEP_LIMIT_FIND = 500
|
|
|
27
27
|
STEP_LIMIT_ANALYSIS = 5000
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
ALL_X64_XMM_REGS = {
|
|
31
|
+
capstone.x86.X86_REG_XMM0,
|
|
32
|
+
capstone.x86.X86_REG_XMM1,
|
|
33
|
+
capstone.x86.X86_REG_XMM2,
|
|
34
|
+
capstone.x86.X86_REG_XMM3,
|
|
35
|
+
capstone.x86.X86_REG_XMM4,
|
|
36
|
+
capstone.x86.X86_REG_XMM5,
|
|
37
|
+
capstone.x86.X86_REG_XMM6,
|
|
38
|
+
capstone.x86.X86_REG_XMM7,
|
|
39
|
+
capstone.x86.X86_REG_XMM8,
|
|
40
|
+
capstone.x86.X86_REG_XMM9,
|
|
41
|
+
capstone.x86.X86_REG_XMM10,
|
|
42
|
+
capstone.x86.X86_REG_XMM11,
|
|
43
|
+
capstone.x86.X86_REG_XMM12,
|
|
44
|
+
capstone.x86.X86_REG_XMM13,
|
|
45
|
+
capstone.x86.X86_REG_XMM14,
|
|
46
|
+
capstone.x86.X86_REG_XMM15,
|
|
47
|
+
capstone.x86.X86_REG_XMM16,
|
|
48
|
+
capstone.x86.X86_REG_XMM17,
|
|
49
|
+
capstone.x86.X86_REG_XMM18,
|
|
50
|
+
capstone.x86.X86_REG_XMM19,
|
|
51
|
+
capstone.x86.X86_REG_XMM20,
|
|
52
|
+
capstone.x86.X86_REG_XMM21,
|
|
53
|
+
capstone.x86.X86_REG_XMM22,
|
|
54
|
+
capstone.x86.X86_REG_XMM23,
|
|
55
|
+
capstone.x86.X86_REG_XMM24,
|
|
56
|
+
capstone.x86.X86_REG_XMM25,
|
|
57
|
+
capstone.x86.X86_REG_XMM26,
|
|
58
|
+
capstone.x86.X86_REG_XMM27,
|
|
59
|
+
capstone.x86.X86_REG_XMM28,
|
|
60
|
+
capstone.x86.X86_REG_XMM29,
|
|
61
|
+
capstone.x86.X86_REG_XMM30,
|
|
62
|
+
capstone.x86.X86_REG_XMM31,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
30
66
|
class StringDeobFuncDescriptor:
|
|
31
67
|
"""
|
|
32
68
|
Describes a string deobfuscation function.
|
|
@@ -478,10 +514,12 @@ class StringObfuscationFinder(Analysis):
|
|
|
478
514
|
actual_addrs = action.actual_addrs
|
|
479
515
|
if action.type == "mem":
|
|
480
516
|
if action.action == "read":
|
|
517
|
+
assert action.size is not None
|
|
481
518
|
for a in actual_addrs:
|
|
482
519
|
for size in range(action.size.ast // 8):
|
|
483
520
|
all_global_reads.append(a + size)
|
|
484
521
|
elif action.action == "write":
|
|
522
|
+
assert action.size is not None
|
|
485
523
|
for a in actual_addrs:
|
|
486
524
|
for size in range(action.size.ast // 8):
|
|
487
525
|
all_global_writes.append(a + size)
|
|
@@ -598,16 +636,16 @@ class StringObfuscationFinder(Analysis):
|
|
|
598
636
|
type3_functions = []
|
|
599
637
|
|
|
600
638
|
for func in function_candidates:
|
|
601
|
-
if not
|
|
639
|
+
if not 1 <= len(func.block_addrs_set) < 14:
|
|
602
640
|
continue
|
|
603
641
|
|
|
604
642
|
# if it has a prototype recovered, it must have four arguments
|
|
605
|
-
if func.prototype is not None and len(func.prototype.args)
|
|
643
|
+
if func.prototype is not None and len(func.prototype.args) not in {3, 4}:
|
|
606
644
|
continue
|
|
607
645
|
|
|
608
646
|
# the function must call some other functions
|
|
609
|
-
if callgraph_digraph.out_degree[func.addr] == 0:
|
|
610
|
-
|
|
647
|
+
# if callgraph_digraph.out_degree[func.addr] == 0:
|
|
648
|
+
# continue
|
|
611
649
|
|
|
612
650
|
# take a look at its call sites
|
|
613
651
|
func_node = cfg.get_any_node(func.addr)
|
|
@@ -635,30 +673,34 @@ class StringObfuscationFinder(Analysis):
|
|
|
635
673
|
continue
|
|
636
674
|
if dec.codegen is None or not dec.codegen.text:
|
|
637
675
|
continue
|
|
676
|
+
|
|
638
677
|
if not self._like_type3_deobfuscation_function(dec.codegen.text):
|
|
639
678
|
continue
|
|
640
679
|
|
|
641
680
|
# examine the first 100 call sites and see if any of them returns a valid string
|
|
642
681
|
valid = False
|
|
682
|
+
guessed_size = False
|
|
643
683
|
for i in range(min(100, len(call_sites))):
|
|
644
684
|
call_site_block = self.project.factory.block(call_sites[i].addr)
|
|
645
685
|
if not self._is_block_setting_constants_to_stack(call_site_block):
|
|
646
686
|
continue
|
|
647
687
|
|
|
648
688
|
# simulate an execution to see if it really works
|
|
649
|
-
data = self._type3_prepare_and_execute(
|
|
650
|
-
func.addr, call_sites[i].addr, call_sites[i].function_address, cfg
|
|
689
|
+
data, guessed_size = self._type3_prepare_and_execute(
|
|
690
|
+
func.addr, call_sites[i].addr, call_sites[i].function_address, cfg # type:ignore
|
|
651
691
|
)
|
|
652
692
|
if data is None:
|
|
653
693
|
continue
|
|
654
|
-
if len(data) > 3
|
|
655
|
-
|
|
656
|
-
|
|
694
|
+
if len(data) > 3:
|
|
695
|
+
consecutive_printable_strs = self._consecutive_printable_substrings(data, min_length=4)
|
|
696
|
+
if consecutive_printable_strs:
|
|
697
|
+
valid = True
|
|
698
|
+
break
|
|
657
699
|
|
|
658
700
|
if valid:
|
|
659
701
|
desc = StringDeobFuncDescriptor()
|
|
660
702
|
desc.string_output_arg_idx = 0
|
|
661
|
-
desc.string_length_arg_idx = 1
|
|
703
|
+
desc.string_length_arg_idx = 1 if not guessed_size else None
|
|
662
704
|
desc.string_null_terminating = False
|
|
663
705
|
type3_functions.append((func.addr, desc))
|
|
664
706
|
|
|
@@ -687,11 +729,15 @@ class StringObfuscationFinder(Analysis):
|
|
|
687
729
|
if cfg is None:
|
|
688
730
|
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
689
731
|
|
|
690
|
-
|
|
732
|
+
cfg_node = cfg.get_any_node(func_addr)
|
|
733
|
+
if cfg_node is None:
|
|
734
|
+
raise AngrAnalysisError(f"Cannot find the CFG node for function {func_addr:#x}")
|
|
735
|
+
call_sites = cfg.get_predecessors(cfg_node)
|
|
691
736
|
callinsn2content = {}
|
|
692
737
|
for idx, call_site in enumerate(call_sites):
|
|
693
738
|
_l.debug("Analyzing type 3 candidate call site %#x (%d/%d)...", call_site.addr, idx + 1, len(call_sites))
|
|
694
|
-
|
|
739
|
+
assert call_site.function_address is not None
|
|
740
|
+
data, _ = self._type3_prepare_and_execute(func_addr, call_site.addr, call_site.function_address, cfg)
|
|
695
741
|
if data:
|
|
696
742
|
callinsn2content[call_site.instruction_addrs[-1]] = data
|
|
697
743
|
# print(hex(call_site.addr), data)
|
|
@@ -722,12 +768,14 @@ class StringObfuscationFinder(Analysis):
|
|
|
722
768
|
|
|
723
769
|
@staticmethod
|
|
724
770
|
def _like_type3_deobfuscation_function(code: str) -> bool:
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
)
|
|
729
|
-
|
|
730
|
-
def _type3_prepare_and_execute(
|
|
771
|
+
has_bitwise_ops = "^" in code or ">>" in code or "<<" in code or "~" in code
|
|
772
|
+
has_loops = "do" in code or "while" in code or "for" in code
|
|
773
|
+
has_many_bitwise_ops = code.count("^") + code.count(">>") + code.count("<<") + code.count("~") > 5
|
|
774
|
+
return has_bitwise_ops and (has_loops or has_many_bitwise_ops)
|
|
775
|
+
|
|
776
|
+
def _type3_prepare_and_execute(
|
|
777
|
+
self, func_addr: int, call_site_addr: int, call_site_func_addr: int, cfg
|
|
778
|
+
) -> tuple[bytes | None, bool]:
|
|
731
779
|
blocks_at_callsite = [call_site_addr]
|
|
732
780
|
|
|
733
781
|
# backtrack from call site to include all previous consecutive blocks
|
|
@@ -773,6 +821,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
773
821
|
# setup sp and bp, just in case
|
|
774
822
|
state.regs._sp = 0x7FFF0000
|
|
775
823
|
bp_set = False
|
|
824
|
+
assert prop.model.input_states is not None
|
|
776
825
|
prop_state = prop.model.input_states.get(call_site_addr, None)
|
|
777
826
|
if prop_state is not None:
|
|
778
827
|
for reg_offset, reg_width in reg_reads:
|
|
@@ -798,7 +847,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
798
847
|
else:
|
|
799
848
|
simgr.step()
|
|
800
849
|
if not simgr.active:
|
|
801
|
-
return None
|
|
850
|
+
return None, False
|
|
802
851
|
|
|
803
852
|
in_state = simgr.active[0]
|
|
804
853
|
|
|
@@ -821,33 +870,63 @@ class StringObfuscationFinder(Analysis):
|
|
|
821
870
|
try:
|
|
822
871
|
ret_value = callable_0()
|
|
823
872
|
except (AngrCallableMultistateError, AngrCallableError):
|
|
824
|
-
return None
|
|
873
|
+
return None, False
|
|
825
874
|
|
|
826
875
|
out_state = callable_0.result_state
|
|
827
876
|
|
|
828
877
|
# figure out what was written
|
|
878
|
+
assert out_state is not None
|
|
829
879
|
ptr = out_state.memory.load(ret_value, size=self.project.arch.bytes, endness=self.project.arch.memory_endness)
|
|
880
|
+
if out_state.memory.load(ptr, size=4).concrete_value == 0:
|
|
881
|
+
# fall back to using the return value as the pointer
|
|
882
|
+
ptr = ret_value
|
|
883
|
+
if out_state.memory.load(ptr, size=4).concrete_value == 0:
|
|
884
|
+
# can't find a valid pointer
|
|
885
|
+
return None, False
|
|
886
|
+
|
|
830
887
|
size = out_state.memory.load(ret_value + 8, size=4, endness=self.project.arch.memory_endness)
|
|
888
|
+
guessed_size = False
|
|
889
|
+
if size.symbolic or size.concrete_value == 0 or size.concrete_value >= 1024:
|
|
890
|
+
size = 64
|
|
891
|
+
guessed_size = True
|
|
831
892
|
# TODO: Support lists with varied-length elements
|
|
832
893
|
data = out_state.memory.load(ptr, size=size, endness="Iend_BE")
|
|
833
894
|
if data.symbolic:
|
|
834
|
-
return None
|
|
895
|
+
return None, False
|
|
835
896
|
|
|
836
|
-
return out_state.solver.eval(data, cast_to=bytes)
|
|
897
|
+
return out_state.solver.eval(data, cast_to=bytes), guessed_size
|
|
837
898
|
|
|
838
899
|
@staticmethod
|
|
839
900
|
def _is_block_setting_constants_to_stack(block, threshold: int = 5) -> bool:
|
|
840
|
-
|
|
901
|
+
insn_setting_const_bytes = 0
|
|
902
|
+
xmm_has_const = False
|
|
841
903
|
for insn in block.capstone.insns:
|
|
842
|
-
if (
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
904
|
+
if insn.mnemonic.startswith("mov") and len(insn.operands) == 2:
|
|
905
|
+
if (
|
|
906
|
+
insn.operands[0].type == capstone.x86.X86_OP_MEM
|
|
907
|
+
and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
|
|
908
|
+
and insn.operands[1].type == capstone.x86.X86_OP_IMM
|
|
909
|
+
):
|
|
910
|
+
# mov [rsp|rbp + offset], imm
|
|
911
|
+
insn_setting_const_bytes += 1 # FIXME: How to get the size of the mov in capstone?
|
|
912
|
+
if (
|
|
913
|
+
insn.operands[0].type == capstone.x86.X86_OP_REG
|
|
914
|
+
and insn.operands[0].reg in ALL_X64_XMM_REGS
|
|
915
|
+
and insn.operands[1].type == capstone.x86.X86_OP_MEM
|
|
916
|
+
and insn.operands[1].mem.base == capstone.x86.X86_REG_RIP
|
|
917
|
+
):
|
|
918
|
+
xmm_has_const = True
|
|
919
|
+
if (
|
|
920
|
+
xmm_has_const
|
|
921
|
+
and insn.operands[0].type == capstone.x86.X86_OP_MEM
|
|
922
|
+
and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
|
|
923
|
+
and insn.operands[1].type == capstone.x86.X86_OP_REG
|
|
924
|
+
and insn.operands[1].reg in ALL_X64_XMM_REGS
|
|
925
|
+
):
|
|
926
|
+
# mov [rsp|rbp + offset], xmm0 - 31
|
|
927
|
+
insn_setting_const_bytes += 16
|
|
928
|
+
|
|
929
|
+
return insn_setting_const_bytes >= threshold
|
|
851
930
|
|
|
852
931
|
@staticmethod
|
|
853
932
|
def _is_string_reasonable(s: bytes) -> bool:
|
|
@@ -857,5 +936,24 @@ class StringObfuscationFinder(Analysis):
|
|
|
857
936
|
s = s.replace(b"\x00", b"")
|
|
858
937
|
return all(chr(ch) in string.printable for ch in s)
|
|
859
938
|
|
|
939
|
+
@staticmethod
|
|
940
|
+
def _consecutive_printable_substrings(s: bytes, min_length: int = 3) -> list[bytes]:
|
|
941
|
+
"""
|
|
942
|
+
Find all consecutive printable substrings in a string.
|
|
943
|
+
"""
|
|
944
|
+
substrings = []
|
|
945
|
+
current_substring = b""
|
|
946
|
+
for ch in s:
|
|
947
|
+
if chr(ch) in string.printable:
|
|
948
|
+
current_substring += bytes([ch])
|
|
949
|
+
else:
|
|
950
|
+
if current_substring:
|
|
951
|
+
if len(current_substring) >= min_length:
|
|
952
|
+
substrings.append(current_substring)
|
|
953
|
+
current_substring = b""
|
|
954
|
+
if current_substring:
|
|
955
|
+
substrings.append(current_substring)
|
|
956
|
+
return substrings
|
|
957
|
+
|
|
860
958
|
|
|
861
959
|
AnalysesHub.register_default("StringObfuscationFinder", StringObfuscationFinder)
|
|
@@ -37,7 +37,8 @@ class RegVVarPredicate:
|
|
|
37
37
|
if cc is not None:
|
|
38
38
|
reg_list = cc.CALLER_SAVED_REGS
|
|
39
39
|
if isinstance(cc.RETURN_VAL, SimRegArg):
|
|
40
|
-
reg_list
|
|
40
|
+
# do not update reg_list directly, otherwise you may update cc.CALLER_SAVED_REGS!
|
|
41
|
+
reg_list = [*reg_list, cc.RETURN_VAL.reg_name]
|
|
41
42
|
return {self.arch.registers[reg_name][0] for reg_name in reg_list}
|
|
42
43
|
log.warning("Cannot determine registers that are clobbered by call statement %r.", stmt)
|
|
43
44
|
return set()
|
|
@@ -245,7 +245,9 @@ class Struct(TypeConstant):
|
|
|
245
245
|
if not self.fields:
|
|
246
246
|
return 0
|
|
247
247
|
max_field_off = max(self.fields.keys())
|
|
248
|
-
return max_field_off +
|
|
248
|
+
return max_field_off + (
|
|
249
|
+
self.fields[max_field_off].size if not isinstance(self.fields[max_field_off], BottomType) else 1
|
|
250
|
+
)
|
|
249
251
|
|
|
250
252
|
@memoize
|
|
251
253
|
def __repr__(self, memo=None):
|
angr/blade.py
CHANGED
|
@@ -40,6 +40,7 @@ class Blade:
|
|
|
40
40
|
cross_insn_opt=False,
|
|
41
41
|
max_predecessors: int = 10,
|
|
42
42
|
include_imarks: bool = True,
|
|
43
|
+
control_dependence: bool = True,
|
|
43
44
|
):
|
|
44
45
|
"""
|
|
45
46
|
:param graph: A graph representing the control flow graph. Note that it does not take
|
|
@@ -56,6 +57,8 @@ class Blade:
|
|
|
56
57
|
:param stop_at_calls: Limit slicing within a single function. Do not proceed when encounters a call
|
|
57
58
|
edge.
|
|
58
59
|
:param include_imarks: Should IMarks (instruction boundaries) be included in the slice.
|
|
60
|
+
:param control_dependence: Whether to consider control dependencies. If True, the temps controlling
|
|
61
|
+
conditional exits will be added to the tainting set.
|
|
59
62
|
:return: None
|
|
60
63
|
"""
|
|
61
64
|
|
|
@@ -70,6 +73,7 @@ class Blade:
|
|
|
70
73
|
self._cross_insn_opt = cross_insn_opt
|
|
71
74
|
self._max_predecessors = max_predecessors
|
|
72
75
|
self._include_imarks = include_imarks
|
|
76
|
+
self._control_dependence = control_dependence
|
|
73
77
|
|
|
74
78
|
self._slice = networkx.DiGraph()
|
|
75
79
|
|
|
@@ -347,7 +351,7 @@ class Blade:
|
|
|
347
351
|
except (SimTranslationError, BadJumpkindNotification):
|
|
348
352
|
return
|
|
349
353
|
|
|
350
|
-
if exit_stmt_idx is None or exit_stmt_idx == DEFAULT_STATEMENT:
|
|
354
|
+
if self._control_dependence and (exit_stmt_idx is None or exit_stmt_idx == DEFAULT_STATEMENT):
|
|
351
355
|
# Initialize the temps set with whatever in the `next` attribute of this irsb
|
|
352
356
|
next_expr = self._get_irsb(run).next
|
|
353
357
|
if type(next_expr) is pyvex.IRExpr.RdTmp:
|
|
@@ -357,20 +361,21 @@ class Blade:
|
|
|
357
361
|
self._inslice_callback(DEFAULT_STATEMENT, None, {"irsb_addr": irsb_addr, "prev": prev})
|
|
358
362
|
prev = irsb_addr, DEFAULT_STATEMENT
|
|
359
363
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
364
|
+
if self._control_dependence:
|
|
365
|
+
# if there are conditional exits, we *always* add them into the slice (so if they should not be taken, we
|
|
366
|
+
# do not lose the condition)
|
|
367
|
+
for stmt_idx_, s_ in enumerate(self._get_irsb(run).statements):
|
|
368
|
+
if type(s_) is not pyvex.IRStmt.Exit:
|
|
369
|
+
continue
|
|
370
|
+
if s_.jumpkind != "Ijk_Boring":
|
|
371
|
+
continue
|
|
372
|
+
|
|
373
|
+
if type(s_.guard) is pyvex.IRExpr.RdTmp:
|
|
374
|
+
temps.add(s_.guard.tmp)
|
|
375
|
+
|
|
376
|
+
# Put it in our slice
|
|
377
|
+
self._inslice_callback(stmt_idx_, s_, {"irsb_addr": irsb_addr, "prev": prev})
|
|
378
|
+
prev = (irsb_addr, stmt_idx_)
|
|
374
379
|
|
|
375
380
|
infodict = {"irsb_addr": irsb_addr, "prev": prev, "has_statement": False}
|
|
376
381
|
|
angr/engines/icicle.py
CHANGED
|
@@ -123,7 +123,7 @@ class IcicleEngine(ConcreteEngine):
|
|
|
123
123
|
if proj is None:
|
|
124
124
|
raise ValueError("IcicleEngine requires a project to be set")
|
|
125
125
|
|
|
126
|
-
emu = Icicle(icicle_arch, PROCESSORS_DIR)
|
|
126
|
+
emu = Icicle(icicle_arch, PROCESSORS_DIR, True, True)
|
|
127
127
|
|
|
128
128
|
copied_registers = set()
|
|
129
129
|
|
|
@@ -174,6 +174,11 @@ class IcicleEngine(ConcreteEngine):
|
|
|
174
174
|
initial_cpu_icount=emu.cpu_icount,
|
|
175
175
|
)
|
|
176
176
|
|
|
177
|
+
# 3. Copy edge hitmap
|
|
178
|
+
edge_hitmap = state.history.last_edge_hitmap
|
|
179
|
+
if edge_hitmap is not None:
|
|
180
|
+
emu.edge_hitmap = edge_hitmap
|
|
181
|
+
|
|
177
182
|
return (emu, translation_data)
|
|
178
183
|
|
|
179
184
|
@staticmethod
|
|
@@ -194,7 +199,8 @@ class IcicleEngine(ConcreteEngine):
|
|
|
194
199
|
addr = page_num * state.memory.page_size
|
|
195
200
|
state.memory.store(addr, emu.mem_read(addr, state.memory.page_size))
|
|
196
201
|
|
|
197
|
-
# 3. Set history
|
|
202
|
+
# 3. Set history
|
|
203
|
+
# 3.1 history.jumpkind
|
|
198
204
|
exc = emu.exception_code
|
|
199
205
|
if status == VmExit.UnhandledException:
|
|
200
206
|
if exc in (
|
|
@@ -216,9 +222,16 @@ class IcicleEngine(ConcreteEngine):
|
|
|
216
222
|
else:
|
|
217
223
|
state.history.jumpkind = "Ijk_Boring"
|
|
218
224
|
|
|
219
|
-
#
|
|
225
|
+
# 3.2 history.recent_bbl_addrs
|
|
226
|
+
# Skip the last block, because it will be added by Successors
|
|
227
|
+
state.history.recent_bbl_addrs.extend([b[0] for b in emu.recent_blocks][:-1])
|
|
228
|
+
|
|
229
|
+
# 3.3. Set history.recent_instruction_count
|
|
220
230
|
state.history.recent_instruction_count = emu.cpu_icount - translation_data.initial_cpu_icount
|
|
221
231
|
|
|
232
|
+
# 3.4. Set edge hitmap
|
|
233
|
+
state.history.edge_hitmap = emu.edge_hitmap
|
|
234
|
+
|
|
222
235
|
return state
|
|
223
236
|
|
|
224
237
|
@override
|