angr 9.2.125__py3-none-macosx_11_0_arm64.whl → 9.2.126__py3-none-macosx_11_0_arm64.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/__init__.py +4 -0
- angr/analyses/decompiler/ail_simplifier.py +1 -0
- angr/analyses/decompiler/callsite_maker.py +9 -1
- angr/analyses/decompiler/clinic.py +1 -1
- angr/analyses/decompiler/condition_processor.py +104 -66
- angr/analyses/decompiler/decompiler.py +3 -0
- angr/analyses/decompiler/optimization_passes/__init__.py +15 -1
- angr/analyses/decompiler/return_maker.py +1 -0
- angr/analyses/decompiler/ssailification/rewriting.py +4 -0
- angr/analyses/decompiler/ssailification/rewriting_engine.py +10 -3
- angr/analyses/decompiler/structured_codegen/c.py +18 -2
- angr/analyses/deobfuscator/__init__.py +18 -0
- angr/analyses/deobfuscator/api_obf_finder.py +313 -0
- angr/analyses/deobfuscator/api_obf_peephole_optimizer.py +51 -0
- angr/analyses/deobfuscator/irsb_reg_collector.py +85 -0
- angr/analyses/deobfuscator/string_obf_finder.py +774 -0
- angr/analyses/deobfuscator/string_obf_opt_passes.py +133 -0
- angr/analyses/deobfuscator/string_obf_peephole_optimizer.py +47 -0
- angr/analyses/reaching_definitions/function_handler_library/stdio.py +8 -1
- angr/analyses/unpacker/__init__.py +6 -0
- angr/analyses/unpacker/obfuscation_detector.py +103 -0
- angr/analyses/unpacker/packing_detector.py +138 -0
- angr/calling_conventions.py +3 -1
- angr/engines/vex/claripy/irop.py +10 -5
- angr/knowledge_plugins/__init__.py +2 -0
- angr/knowledge_plugins/obfuscations.py +36 -0
- angr/lib/angr_native.dylib +0 -0
- {angr-9.2.125.dist-info → angr-9.2.126.dist-info}/METADATA +6 -6
- {angr-9.2.125.dist-info → angr-9.2.126.dist-info}/RECORD +34 -23
- {angr-9.2.125.dist-info → angr-9.2.126.dist-info}/WHEEL +1 -1
- {angr-9.2.125.dist-info → angr-9.2.126.dist-info}/LICENSE +0 -0
- {angr-9.2.125.dist-info → angr-9.2.126.dist-info}/entry_points.txt +0 -0
- {angr-9.2.125.dist-info → angr-9.2.126.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# pylint:disable=too-many-boolean-expressions
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import archinfo
|
|
5
|
+
|
|
6
|
+
from ailment import Block
|
|
7
|
+
from ailment.statement import Statement, Call, Assignment
|
|
8
|
+
from ailment.expression import Const, Register, VirtualVariable
|
|
9
|
+
|
|
10
|
+
from angr.analyses.decompiler.optimization_passes.optimization_pass import OptimizationPass, OptimizationPassStage
|
|
11
|
+
from angr.analyses.decompiler.optimization_passes import register_optimization_pass
|
|
12
|
+
|
|
13
|
+
WIN64_REG_ARGS = {
|
|
14
|
+
archinfo.ArchAMD64().registers["rcx"][0],
|
|
15
|
+
archinfo.ArchAMD64().registers["rdx"][0],
|
|
16
|
+
archinfo.ArchAMD64().registers["r8"][0],
|
|
17
|
+
archinfo.ArchAMD64().registers["r9"][0],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StringObfType3Rewriter(OptimizationPass):
|
|
22
|
+
"""
|
|
23
|
+
Type-3 optimization pass replaces deobfuscate_string calls with the deobfuscated strings, and then removes
|
|
24
|
+
arguments on the stack.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
ARCHES = ["X86", "AMD64"]
|
|
28
|
+
PLATFORMS = ["windows"]
|
|
29
|
+
STAGE = OptimizationPassStage.AFTER_MAKING_CALLSITES
|
|
30
|
+
|
|
31
|
+
NAME = "Simplify Type 3 string deobfuscation calls"
|
|
32
|
+
DESCRIPTION = "Simplify Type 3 string deobfuscation calls"
|
|
33
|
+
stmt_classes = ()
|
|
34
|
+
|
|
35
|
+
def __init__(self, func, **kwargs):
|
|
36
|
+
super().__init__(func, **kwargs)
|
|
37
|
+
|
|
38
|
+
self.analyze()
|
|
39
|
+
|
|
40
|
+
def _check(self):
|
|
41
|
+
if self.kb.obfuscations.type3_deobfuscated_strings:
|
|
42
|
+
return True, None
|
|
43
|
+
return False, None
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def is_call_or_call_assignment(stmt) -> bool:
|
|
47
|
+
return isinstance(stmt, Call) or isinstance(stmt, Assignment) and isinstance(stmt.src, Call)
|
|
48
|
+
|
|
49
|
+
def _analyze(self, cache=None):
|
|
50
|
+
|
|
51
|
+
# find all blocks with type-3 deobfuscation calls
|
|
52
|
+
for block in list(self._graph):
|
|
53
|
+
if not block.statements:
|
|
54
|
+
continue
|
|
55
|
+
last_stmt = block.statements[-1]
|
|
56
|
+
if (
|
|
57
|
+
self.is_call_or_call_assignment(last_stmt)
|
|
58
|
+
and last_stmt.ins_addr in self.kb.obfuscations.type3_deobfuscated_strings
|
|
59
|
+
):
|
|
60
|
+
new_block = self._process_block(
|
|
61
|
+
block, self.kb.obfuscations.type3_deobfuscated_strings[block.statements[-1].ins_addr]
|
|
62
|
+
)
|
|
63
|
+
if new_block is not None:
|
|
64
|
+
self._update_block(block, new_block)
|
|
65
|
+
|
|
66
|
+
def _process_block(self, block: Block, deobf_content: bytes):
|
|
67
|
+
# FIXME: This rewriter is very specific to the implementation of the deobfuscation scheme. we can make it more
|
|
68
|
+
# generic when there are more cases available in the wild.
|
|
69
|
+
|
|
70
|
+
# TODO: Support multiple blocks
|
|
71
|
+
|
|
72
|
+
# replace the call
|
|
73
|
+
old_stmt: Statement = block.statements[-1]
|
|
74
|
+
str_id = self.kb.custom_strings.allocate(deobf_content)
|
|
75
|
+
old_call: Call = old_stmt.src if isinstance(old_stmt, Assignment) else old_stmt
|
|
76
|
+
new_call = Call(
|
|
77
|
+
old_call.idx,
|
|
78
|
+
"init_str",
|
|
79
|
+
args=[
|
|
80
|
+
old_call.args[0],
|
|
81
|
+
Const(None, None, str_id, self.project.arch.bits, custom_string=True),
|
|
82
|
+
Const(None, None, len(deobf_content), self.project.arch.bits),
|
|
83
|
+
],
|
|
84
|
+
ret_expr=old_call.ret_expr,
|
|
85
|
+
bits=old_call.bits,
|
|
86
|
+
**old_call.tags,
|
|
87
|
+
)
|
|
88
|
+
if isinstance(old_stmt, Assignment):
|
|
89
|
+
new_stmt = Assignment(old_stmt.idx, old_stmt.dst, new_call, **old_stmt.tags)
|
|
90
|
+
else:
|
|
91
|
+
new_stmt = new_call
|
|
92
|
+
|
|
93
|
+
statements = block.statements[:-1] + [new_stmt]
|
|
94
|
+
|
|
95
|
+
# remove N-2 continuous stack assignment
|
|
96
|
+
if len(deobf_content) > 2:
|
|
97
|
+
stack_offset_to_stmtid: dict[int, int] = {}
|
|
98
|
+
for idx, stmt in enumerate(statements):
|
|
99
|
+
if (
|
|
100
|
+
isinstance(stmt, Assignment)
|
|
101
|
+
and isinstance(stmt.dst, VirtualVariable)
|
|
102
|
+
and stmt.dst.was_stack
|
|
103
|
+
and isinstance(stmt.dst.stack_offset, int)
|
|
104
|
+
and isinstance(stmt.src, Const)
|
|
105
|
+
and stmt.src.value <= 0xFF
|
|
106
|
+
):
|
|
107
|
+
stack_offset_to_stmtid[stmt.dst.stack_offset] = idx
|
|
108
|
+
sorted_offsets = sorted(stack_offset_to_stmtid)
|
|
109
|
+
if sorted_offsets:
|
|
110
|
+
spacing = 8 # FIXME: Make it adjustable
|
|
111
|
+
distance = min(len(deobf_content) - 2, len(sorted_offsets) - 1)
|
|
112
|
+
for start_idx in range(len(sorted_offsets) - distance):
|
|
113
|
+
if sorted_offsets[start_idx] + spacing * distance == sorted_offsets[start_idx + distance]:
|
|
114
|
+
# found them
|
|
115
|
+
# remove these statements
|
|
116
|
+
for i in range(start_idx, start_idx + distance + 1):
|
|
117
|
+
statements[stack_offset_to_stmtid[sorted_offsets[i]]] = None
|
|
118
|
+
break
|
|
119
|
+
statements = [stmt for stmt in statements if stmt is not None]
|
|
120
|
+
|
|
121
|
+
# remove writes to rdx, rcx, r8, and r9
|
|
122
|
+
if self.project.arch.name == "AMD64":
|
|
123
|
+
statements = [stmt for stmt in statements if not self._stmt_sets_win64_reg_arg(stmt)]
|
|
124
|
+
|
|
125
|
+
# return the new block
|
|
126
|
+
return block.copy(statements=statements)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def _stmt_sets_win64_reg_arg(stmt) -> bool:
|
|
130
|
+
return isinstance(stmt, Assignment) and isinstance(stmt.dst, Register) and stmt.dst.reg_offset in WIN64_REG_ARGS
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
register_optimization_pass(StringObfType3Rewriter, presets=["fast", "full"])
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from ailment.statement import Call
|
|
3
|
+
from ailment.expression import Const
|
|
4
|
+
import claripy
|
|
5
|
+
|
|
6
|
+
from angr.analyses.decompiler.peephole_optimizations.base import PeepholeOptimizationExprBase
|
|
7
|
+
from angr.analyses.decompiler.peephole_optimizations import EXPR_OPTS
|
|
8
|
+
from angr.errors import AngrCallableMultistateError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StringObfType1PeepholeOptimizer(PeepholeOptimizationExprBase):
|
|
12
|
+
"""
|
|
13
|
+
Integrate type-1 deobfuscated strings into decompilation output.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__slots__ = ()
|
|
17
|
+
|
|
18
|
+
NAME = "Simplify Type 1/2 string deobfuscation references"
|
|
19
|
+
expr_classes = (Call,)
|
|
20
|
+
|
|
21
|
+
def optimize(self, expr: Call, **kwargs):
|
|
22
|
+
if isinstance(expr.target, Const) and ( # noqa: SIM102
|
|
23
|
+
expr.target.value in self.kb.obfuscations.type1_string_loader_candidates
|
|
24
|
+
or expr.target.value in self.kb.obfuscations.type2_string_loader_candidates
|
|
25
|
+
):
|
|
26
|
+
# this is a function calling a type1 or a type2 string loader
|
|
27
|
+
# optimize this call away if possible
|
|
28
|
+
if expr.args and all(isinstance(arg, Const) for arg in expr.args):
|
|
29
|
+
# execute the function with the given argument
|
|
30
|
+
func = self.kb.functions[expr.target.value]
|
|
31
|
+
func_call = self.project.factory.callable(
|
|
32
|
+
expr.target.value, concrete_only=True, cc=func.calling_convention, prototype=func.prototype
|
|
33
|
+
)
|
|
34
|
+
try:
|
|
35
|
+
out = func_call(*[claripy.BVV(arg.value, arg.bits) for arg in expr.args])
|
|
36
|
+
except AngrCallableMultistateError:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
if out.concrete:
|
|
40
|
+
return Const(
|
|
41
|
+
None, None, out.concrete_value, self.project.arch.bits, **expr.tags
|
|
42
|
+
) # FIXME: use out.bits when the function prototype recovery is more reliable
|
|
43
|
+
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
EXPR_OPTS.append(StringObfType1PeepholeOptimizer)
|
|
@@ -197,6 +197,11 @@ def handle_printf(
|
|
|
197
197
|
buf_data = state.get_values(buf_atoms)
|
|
198
198
|
if buf_data is not None:
|
|
199
199
|
buf_data = buf_data.extract(0, len(buf_data) // 8 - 1, archinfo.Endness.BE)
|
|
200
|
+
else:
|
|
201
|
+
top_val = state.top(state.arch.bits)
|
|
202
|
+
for defn in state.get_definitions(atom):
|
|
203
|
+
top_val = state.annotate_with_def(top_val, defn)
|
|
204
|
+
buf_data = MultiValues(top_val)
|
|
200
205
|
elif fmt == "%u":
|
|
201
206
|
buf_atoms = atom
|
|
202
207
|
buf_data = state.get_concrete_value(buf_atoms)
|
|
@@ -217,7 +222,9 @@ def handle_printf(
|
|
|
217
222
|
else:
|
|
218
223
|
_l.warning("Unimplemented printf format string %s", fmt)
|
|
219
224
|
buf_atoms = set()
|
|
220
|
-
|
|
225
|
+
top_val = state.top(state.arch.bits)
|
|
226
|
+
buf_data = MultiValues(top_val)
|
|
227
|
+
|
|
221
228
|
if result is not None and buf_data is not None:
|
|
222
229
|
result = result.concat(buf_data)
|
|
223
230
|
source_atoms.update(buf_atoms)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import networkx
|
|
5
|
+
|
|
6
|
+
from angr.analyses.analysis import Analysis, AnalysesHub
|
|
7
|
+
from angr.knowledge_plugins.cfg import CFGModel
|
|
8
|
+
|
|
9
|
+
_l = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ObfuscationDetector(Analysis):
|
|
13
|
+
"""
|
|
14
|
+
This analysis detects, usually in ways that are more robust than section name matching or signature matching, the
|
|
15
|
+
existence of obfuscation techniques in a binary.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, cfg: CFGModel | None = None):
|
|
19
|
+
self.obfuscated: bool = False
|
|
20
|
+
self.possible_obfuscators: list[str] = []
|
|
21
|
+
|
|
22
|
+
if cfg is None:
|
|
23
|
+
_l.warning(
|
|
24
|
+
"PackingDetector is using a most accurate CFG model in the knowledge base. We assume it is "
|
|
25
|
+
"generated with force_smart_scan=False and force_complete_scan=False."
|
|
26
|
+
)
|
|
27
|
+
self._cfg = self.kb.cfgs.get_most_accurate()
|
|
28
|
+
else:
|
|
29
|
+
self._cfg = cfg
|
|
30
|
+
|
|
31
|
+
self.analyze()
|
|
32
|
+
|
|
33
|
+
def analyze(self):
|
|
34
|
+
|
|
35
|
+
analysis_routines = [
|
|
36
|
+
self._analyze_vmprotect,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for routine in analysis_routines:
|
|
40
|
+
tool = routine()
|
|
41
|
+
if tool:
|
|
42
|
+
self.obfuscated = True
|
|
43
|
+
self.possible_obfuscators.append(tool)
|
|
44
|
+
|
|
45
|
+
def _analyze_vmprotect(self) -> str | None:
|
|
46
|
+
"""
|
|
47
|
+
We detect VMProtect v3 (with control-flow obfuscation) based on two main characteristics:
|
|
48
|
+
|
|
49
|
+
- In amd64 binaries, there exists a strongly connected component in the call graph with over 1,000 nodes.
|
|
50
|
+
Edge/node ratio is >= 1.3
|
|
51
|
+
- There is a high number of pushf and popf instructions in the visible functions.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
high_scc_node_edge_ratio = False
|
|
55
|
+
high_pushf = False
|
|
56
|
+
high_popf = False
|
|
57
|
+
high_clc = False # pylint:disable=unused-variable
|
|
58
|
+
|
|
59
|
+
if self.project.arch.name == "AMD64":
|
|
60
|
+
cg = self.kb.functions.callgraph
|
|
61
|
+
sccs = networkx.strongly_connected_components(cg)
|
|
62
|
+
|
|
63
|
+
for scc in sccs:
|
|
64
|
+
subgraph = networkx.subgraph(cg, scc)
|
|
65
|
+
node_count = len(scc)
|
|
66
|
+
if node_count > 1000:
|
|
67
|
+
edge_count = len(subgraph.edges)
|
|
68
|
+
|
|
69
|
+
if edge_count / node_count >= 1.3:
|
|
70
|
+
high_scc_node_edge_ratio = True
|
|
71
|
+
break
|
|
72
|
+
else:
|
|
73
|
+
high_scc_node_edge_ratio = True
|
|
74
|
+
|
|
75
|
+
pushf_ctr = 0
|
|
76
|
+
popf_ctr = 0
|
|
77
|
+
clc_ctr = 0 # only used for x86
|
|
78
|
+
is_x86 = self.project.arch.name == "X86"
|
|
79
|
+
cfg_node_count = len(self._cfg.graph)
|
|
80
|
+
for node in self._cfg.nodes():
|
|
81
|
+
if node.size > 0 and node.instruction_addrs:
|
|
82
|
+
block = node.block
|
|
83
|
+
for insn in block.capstone.insns:
|
|
84
|
+
if insn.mnemonic in {"pushf", "pushfd", "pushfq"}:
|
|
85
|
+
pushf_ctr += 1
|
|
86
|
+
elif insn.mnemonic in {"popf", "popfd", "popfq"}:
|
|
87
|
+
popf_ctr += 1
|
|
88
|
+
elif is_x86 and insn.mnemonic == "clc":
|
|
89
|
+
clc_ctr += 1
|
|
90
|
+
|
|
91
|
+
if pushf_ctr > cfg_node_count * 0.002:
|
|
92
|
+
high_pushf = True
|
|
93
|
+
if popf_ctr > cfg_node_count * 0.002:
|
|
94
|
+
high_popf = True
|
|
95
|
+
if not is_x86 or clc_ctr > cfg_node_count * 0.002:
|
|
96
|
+
high_clc = True # noqa: F841
|
|
97
|
+
|
|
98
|
+
if high_scc_node_edge_ratio and high_pushf and high_popf:
|
|
99
|
+
return "vmprotect"
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
AnalysesHub.register_default("ObfuscationDetector", ObfuscationDetector)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
import math
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from angr.analyses.analysis import Analysis, AnalysesHub
|
|
7
|
+
from angr.knowledge_plugins.cfg import CFGModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from cle import Section
|
|
12
|
+
|
|
13
|
+
_l = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PackingDetector(Analysis):
|
|
17
|
+
"""
|
|
18
|
+
This analysis detects if a binary is likely packed or not. We may extend it to identify which packer is in use in
|
|
19
|
+
the future.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
PACKED_MIN_BYTES = 256
|
|
23
|
+
PACKED_ENTROPY_MIN_THRESHOLD = 0.88
|
|
24
|
+
|
|
25
|
+
def __init__(self, cfg: CFGModel | None = None, region_size_threshold: int = 0x20):
|
|
26
|
+
self.packed: bool = False
|
|
27
|
+
self.region_size_threshold: int = region_size_threshold
|
|
28
|
+
|
|
29
|
+
if cfg is None:
|
|
30
|
+
_l.warning(
|
|
31
|
+
"PackingDetector is using a most accurate CFG model in the knowledge base. We assume it is "
|
|
32
|
+
"generated with force_smart_scan=False and force_complete_scan=False."
|
|
33
|
+
)
|
|
34
|
+
self._cfg = self.kb.cfgs.get_most_accurate()
|
|
35
|
+
else:
|
|
36
|
+
self._cfg = cfg
|
|
37
|
+
|
|
38
|
+
self.analyze()
|
|
39
|
+
|
|
40
|
+
def analyze(self):
|
|
41
|
+
# assume we already have a CFG with complete scanning disabled
|
|
42
|
+
# collect all regions that are not covered by the CFG in r+x sections, and then compute the entropy. we believe
|
|
43
|
+
# the binary is packed if it is beyond a threshold
|
|
44
|
+
|
|
45
|
+
covered_regions: list[tuple[int, int]] = []
|
|
46
|
+
last_known_section: Section | None = None
|
|
47
|
+
for node in sorted(self._cfg.nodes(), key=lambda n: n.addr):
|
|
48
|
+
section = None
|
|
49
|
+
if last_known_section is not None and last_known_section.contains_addr(node.addr):
|
|
50
|
+
section = last_known_section
|
|
51
|
+
if section is None:
|
|
52
|
+
section = self.project.loader.find_section_containing(node.addr)
|
|
53
|
+
if section is None:
|
|
54
|
+
# this node does not belong to any known section - ignore it
|
|
55
|
+
continue
|
|
56
|
+
if section.is_readable and section.is_executable:
|
|
57
|
+
last_known_section = section
|
|
58
|
+
|
|
59
|
+
if section is None:
|
|
60
|
+
# the node does not belong to any section. ignore it
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
if node.size == 0:
|
|
64
|
+
# ignore empty nodes
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
if not covered_regions:
|
|
68
|
+
covered_regions.append((node.addr, node.addr + node.size))
|
|
69
|
+
else:
|
|
70
|
+
last_item = covered_regions[-1]
|
|
71
|
+
if last_item[0] <= node.addr <= last_item[1] < node.addr + node.size:
|
|
72
|
+
# update the last item
|
|
73
|
+
covered_regions[-1] = last_item[0], node.addr + node.size
|
|
74
|
+
else:
|
|
75
|
+
# add a new item
|
|
76
|
+
covered_regions.append((node.addr, node.addr + node.size))
|
|
77
|
+
|
|
78
|
+
# now we get the uncovered regions
|
|
79
|
+
uncovered_regions: list[tuple[int, int]] = self._get_uncovered_regions(covered_regions)
|
|
80
|
+
|
|
81
|
+
# compute entropy
|
|
82
|
+
total_bytes, entropy = self._compute_entropy(uncovered_regions)
|
|
83
|
+
|
|
84
|
+
self.packed = total_bytes >= self.PACKED_MIN_BYTES and entropy >= self.PACKED_ENTROPY_MIN_THRESHOLD
|
|
85
|
+
|
|
86
|
+
def _get_uncovered_regions(self, covered_regions: list[tuple[int, int]]) -> list[tuple[int, int]]:
|
|
87
|
+
# FIXME: We only support binaries with sections. Add support for segments in the future
|
|
88
|
+
all_executable_sections = [
|
|
89
|
+
sec
|
|
90
|
+
for sec in self.project.loader.main_object.sections
|
|
91
|
+
if sec.is_executable and sec.is_readable and not sec.only_contains_uninitialized_data
|
|
92
|
+
]
|
|
93
|
+
all_executable_sections = sorted(all_executable_sections, key=lambda sec: sec.vaddr)
|
|
94
|
+
idx = 0
|
|
95
|
+
|
|
96
|
+
uncovered_regions: list[tuple[int, int]] = []
|
|
97
|
+
for section in all_executable_sections:
|
|
98
|
+
if idx >= len(covered_regions):
|
|
99
|
+
if section.memsize > self.region_size_threshold:
|
|
100
|
+
uncovered_regions.append((section.vaddr, section.vaddr + section.memsize))
|
|
101
|
+
else:
|
|
102
|
+
i = idx
|
|
103
|
+
last_end = section.vaddr
|
|
104
|
+
while i < len(covered_regions):
|
|
105
|
+
region_start, region_end = covered_regions[i]
|
|
106
|
+
if region_end >= section.vaddr + section.memsize:
|
|
107
|
+
# move on to the next section
|
|
108
|
+
break
|
|
109
|
+
if last_end < region_start and region_start - last_end > self.region_size_threshold:
|
|
110
|
+
uncovered_regions.append((last_end, region_start))
|
|
111
|
+
i += 1
|
|
112
|
+
last_end = max(last_end, region_end)
|
|
113
|
+
idx = i
|
|
114
|
+
|
|
115
|
+
return uncovered_regions
|
|
116
|
+
|
|
117
|
+
def _compute_entropy(self, regions: list[tuple[int, int]]) -> tuple[int, float]:
|
|
118
|
+
byte_counts = [0] * 256
|
|
119
|
+
|
|
120
|
+
for start, end in regions:
|
|
121
|
+
for b in self.project.loader.memory.load(start, end - start):
|
|
122
|
+
byte_counts[b] += 1
|
|
123
|
+
|
|
124
|
+
total = sum(byte_counts)
|
|
125
|
+
if total == 0:
|
|
126
|
+
return 0, 0.0
|
|
127
|
+
|
|
128
|
+
entropy = 0.0
|
|
129
|
+
for count in byte_counts:
|
|
130
|
+
if count == 0:
|
|
131
|
+
continue
|
|
132
|
+
p = 1.0 * count / total
|
|
133
|
+
entropy -= p * math.log(p, 256)
|
|
134
|
+
|
|
135
|
+
return total, entropy
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
AnalysesHub.register_default("PackingDetector", PackingDetector)
|
angr/calling_conventions.py
CHANGED
|
@@ -1061,7 +1061,9 @@ class SimCC:
|
|
|
1061
1061
|
if isinstance(arg, claripy.ast.BV):
|
|
1062
1062
|
if isinstance(ty, (SimTypeReg, SimTypeNum)):
|
|
1063
1063
|
if len(arg) != ty.size:
|
|
1064
|
-
|
|
1064
|
+
if arg.concrete:
|
|
1065
|
+
return claripy.BVV(arg.concrete_value, ty.size)
|
|
1066
|
+
raise TypeError("Type mismatch of symbolic data: expected %s, got %d bits" % (ty, len(arg)))
|
|
1065
1067
|
return arg
|
|
1066
1068
|
if isinstance(ty, (SimTypeFloat)):
|
|
1067
1069
|
raise TypeError(
|
angr/engines/vex/claripy/irop.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
This module contains symbolic implementations of VEX operations.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
# pylint:disable=no-member
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
from functools import partial
|
|
@@ -10,14 +11,17 @@ import itertools
|
|
|
10
11
|
import operator
|
|
11
12
|
import math
|
|
12
13
|
import re
|
|
13
|
-
|
|
14
14
|
import logging
|
|
15
15
|
|
|
16
|
-
l = logging.getLogger(name=__name__)
|
|
17
|
-
|
|
18
16
|
import pyvex
|
|
19
17
|
import claripy
|
|
20
18
|
|
|
19
|
+
from angr.errors import UnsupportedIROpError, SimOperationError, SimValueError, SimZeroDivisionException
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
l = logging.getLogger(name=__name__)
|
|
23
|
+
|
|
24
|
+
|
|
21
25
|
#
|
|
22
26
|
# The more sane approach
|
|
23
27
|
#
|
|
@@ -1044,6 +1048,9 @@ class SimIROp:
|
|
|
1044
1048
|
exp_threshold = (2 ** (exp_bits - 1) - 1) + mantissa_bits
|
|
1045
1049
|
return claripy.If(exp_bv >= exp_threshold, args[1].raw_to_fp(), rounded_fp)
|
|
1046
1050
|
|
|
1051
|
+
def _op_fgeneric_RSqrtEst(self, arg): # pylint:disable=no-self-use
|
|
1052
|
+
return claripy.BVS("RSqrtEst", arg.size())
|
|
1053
|
+
|
|
1047
1054
|
def _generic_pack_saturation(self, args, src_size, dst_size, src_signed, dst_signed):
|
|
1048
1055
|
"""
|
|
1049
1056
|
Generic pack with saturation.
|
|
@@ -1255,6 +1262,4 @@ def vexop_to_simop(op, extended=True, fp=True):
|
|
|
1255
1262
|
return res
|
|
1256
1263
|
|
|
1257
1264
|
|
|
1258
|
-
from angr.errors import UnsupportedIROpError, SimOperationError, SimValueError, SimZeroDivisionException
|
|
1259
|
-
|
|
1260
1265
|
make_operations()
|
|
@@ -18,6 +18,7 @@ from .types import TypesStore
|
|
|
18
18
|
from .callsite_prototypes import CallsitePrototypes
|
|
19
19
|
from .custom_strings import CustomStrings
|
|
20
20
|
from .decompilation import DecompilationManager
|
|
21
|
+
from .obfuscations import Obfuscations
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
__all__ = (
|
|
@@ -40,4 +41,5 @@ __all__ = (
|
|
|
40
41
|
"CallsitePrototypes",
|
|
41
42
|
"CustomStrings",
|
|
42
43
|
"DecompilationManager",
|
|
44
|
+
"Obfuscations",
|
|
43
45
|
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .plugin import KnowledgeBasePlugin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Obfuscations(KnowledgeBasePlugin):
|
|
7
|
+
"""
|
|
8
|
+
Store discovered information and artifacts about (string) obfuscation techniques in the project.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, kb):
|
|
12
|
+
super().__init__(kb)
|
|
13
|
+
|
|
14
|
+
self.obfuscated_strings_analyzed: bool = False
|
|
15
|
+
self.type1_deobfuscated_strings = {}
|
|
16
|
+
self.type1_string_loader_candidates = set()
|
|
17
|
+
self.type2_deobfuscated_strings = {}
|
|
18
|
+
self.type2_string_loader_candidates = set()
|
|
19
|
+
self.type3_deobfuscated_strings = {} # from the address of the call instruction to the actual string (in bytes)
|
|
20
|
+
|
|
21
|
+
self.obfuscated_apis_analyzed: bool = False
|
|
22
|
+
self.type1_deobfuscated_apis: dict[int, tuple[str, str]] = {}
|
|
23
|
+
|
|
24
|
+
def copy(self):
|
|
25
|
+
o = Obfuscations(self._kb)
|
|
26
|
+
o.type1_deobfuscated_strings = dict(self.type1_deobfuscated_strings)
|
|
27
|
+
o.type1_string_loader_candidates = self.type1_string_loader_candidates.copy()
|
|
28
|
+
o.type2_deobfuscated_strings = dict(self.type2_deobfuscated_strings)
|
|
29
|
+
o.type2_string_loader_candidates = self.type2_string_loader_candidates.copy()
|
|
30
|
+
o.type3_deobfuscated_strings = self.type3_deobfuscated_strings.copy()
|
|
31
|
+
|
|
32
|
+
o.type1_deobfuscated_apis = self.type1_deobfuscated_apis.copy()
|
|
33
|
+
return o
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
KnowledgeBasePlugin.register_default("obfuscations", Obfuscations)
|
angr/lib/angr_native.dylib
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: angr
|
|
3
|
-
Version: 9.2.
|
|
3
|
+
Version: 9.2.126
|
|
4
4
|
Summary: A multi-architecture binary analysis toolkit, with the ability to perform dynamic symbolic execution and various static analyses on binaries
|
|
5
5
|
Home-page: https://github.com/angr/angr
|
|
6
6
|
License: BSD-2-Clause
|
|
@@ -16,13 +16,13 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: CppHeaderParser
|
|
18
18
|
Requires-Dist: GitPython
|
|
19
|
-
Requires-Dist: ailment==9.2.
|
|
20
|
-
Requires-Dist: archinfo==9.2.
|
|
19
|
+
Requires-Dist: ailment==9.2.126
|
|
20
|
+
Requires-Dist: archinfo==9.2.126
|
|
21
21
|
Requires-Dist: cachetools
|
|
22
22
|
Requires-Dist: capstone==5.0.3
|
|
23
23
|
Requires-Dist: cffi>=1.14.0
|
|
24
|
-
Requires-Dist: claripy==9.2.
|
|
25
|
-
Requires-Dist: cle==9.2.
|
|
24
|
+
Requires-Dist: claripy==9.2.126
|
|
25
|
+
Requires-Dist: cle==9.2.126
|
|
26
26
|
Requires-Dist: itanium-demangler
|
|
27
27
|
Requires-Dist: mulpyplexer
|
|
28
28
|
Requires-Dist: nampa
|
|
@@ -31,7 +31,7 @@ Requires-Dist: protobuf>=5.28.2
|
|
|
31
31
|
Requires-Dist: psutil
|
|
32
32
|
Requires-Dist: pycparser>=2.18
|
|
33
33
|
Requires-Dist: pyformlang
|
|
34
|
-
Requires-Dist: pyvex==9.2.
|
|
34
|
+
Requires-Dist: pyvex==9.2.126
|
|
35
35
|
Requires-Dist: rich>=13.1.0
|
|
36
36
|
Requires-Dist: sortedcontainers
|
|
37
37
|
Requires-Dist: sympy
|