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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING, cast
|
|
3
|
+
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from angr.project import Project
|
|
9
|
+
from angr.knowledge_base import KnowledgeBase
|
|
10
|
+
from angr.knowledge_plugins.functions.function import Function
|
|
11
|
+
from angr.knowledge_plugins.key_definitions import DerefSize
|
|
12
|
+
from angr.knowledge_plugins.key_definitions.constants import ObservationPointType
|
|
13
|
+
from angr.knowledge_plugins.key_definitions.atoms import MemoryLocation
|
|
14
|
+
from angr.sim_variable import SimMemoryVariable
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from angr.analyses.reaching_definitions import (
|
|
18
|
+
ReachingDefinitionsAnalysis,
|
|
19
|
+
FunctionCallRelationships,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class APIObfuscationType2:
|
|
28
|
+
"""
|
|
29
|
+
API Obfuscation Type 2 result.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
resolved_func_name: str
|
|
33
|
+
resolved_func_ptr: MemoryLocation
|
|
34
|
+
resolved_in: Function
|
|
35
|
+
resolved_by: Function
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class APIObfuscationType2Finder:
|
|
39
|
+
"""
|
|
40
|
+
Finds global function pointers initialized by calls to dlsym/GetProcAddress and names
|
|
41
|
+
them accordingly.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
results: list[APIObfuscationType2]
|
|
45
|
+
|
|
46
|
+
def __init__(self, project: Project, variable_kb: KnowledgeBase | None = None):
|
|
47
|
+
self.project = project
|
|
48
|
+
self.variable_kb = variable_kb or self.project.kb
|
|
49
|
+
self.results = []
|
|
50
|
+
|
|
51
|
+
def analyze(self) -> list[APIObfuscationType2]:
|
|
52
|
+
self.results = []
|
|
53
|
+
for caller, callee in self._get_candidates():
|
|
54
|
+
rda = self.project.analyses.ReachingDefinitions(caller, observe_all=True)
|
|
55
|
+
for info in rda.callsites_to(callee):
|
|
56
|
+
self._process_callsite(caller, callee, rda, info)
|
|
57
|
+
self._mark_globals()
|
|
58
|
+
return self.results
|
|
59
|
+
|
|
60
|
+
def _get_candidates(self) -> Iterator[tuple[Function, Function]]:
|
|
61
|
+
"""
|
|
62
|
+
Returns a tuple of (caller, callee) where callee is GetProcAddress/dlsym.
|
|
63
|
+
"""
|
|
64
|
+
targets = ["GetProcAddress"] if self.project.simos.name == "Win32" else ["dlsym", "dlvsym"]
|
|
65
|
+
for callee in self.project.kb.functions.values():
|
|
66
|
+
if callee.name not in targets:
|
|
67
|
+
continue
|
|
68
|
+
for caller_addr in self.project.kb.callgraph.predecessors(callee.addr):
|
|
69
|
+
caller = self.project.kb.functions[caller_addr]
|
|
70
|
+
yield (caller, callee)
|
|
71
|
+
|
|
72
|
+
def _process_callsite(
|
|
73
|
+
self,
|
|
74
|
+
caller: Function,
|
|
75
|
+
callee: Function,
|
|
76
|
+
rda: ReachingDefinitionsAnalysis,
|
|
77
|
+
callsite_info: FunctionCallRelationships,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Process a resolver function callsite looking for function name concrete string argument.
|
|
81
|
+
"""
|
|
82
|
+
func_name_arg_idx = 1
|
|
83
|
+
if len(callsite_info.args_defns) <= func_name_arg_idx:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
log.debug("Examining call to %r from %r at %r", callee, caller, callsite_info.callsite.ins_addr)
|
|
87
|
+
ld = rda.model.get_observation_by_insn(callsite_info.callsite, ObservationPointType.OP_BEFORE)
|
|
88
|
+
if ld is None:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Attempt resolving static function name from callsite
|
|
92
|
+
string_atom = ld.deref(callsite_info.args_defns[func_name_arg_idx], DerefSize.NULL_TERMINATE)
|
|
93
|
+
result = ld.get_concrete_value(string_atom, cast_to=bytes)
|
|
94
|
+
if result is None:
|
|
95
|
+
log.debug("...Failed to resolve a function name")
|
|
96
|
+
return
|
|
97
|
+
|
|
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
|
|
104
|
+
|
|
105
|
+
# Examine successor definitions to find where the function pointer is written
|
|
106
|
+
for successor in rda.dep_graph.find_all_successors(callsite_info.ret_defns):
|
|
107
|
+
if not (
|
|
108
|
+
isinstance(successor.atom, MemoryLocation)
|
|
109
|
+
and isinstance(successor.atom.addr, int)
|
|
110
|
+
and successor.atom.size == self.project.arch.bytes
|
|
111
|
+
):
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
ptr = successor.atom
|
|
115
|
+
ptr_addr: int = cast(int, ptr.addr)
|
|
116
|
+
log.debug("...Found function pointer %r", ptr)
|
|
117
|
+
|
|
118
|
+
sym = self.project.loader.find_symbol(ptr_addr)
|
|
119
|
+
if sym is not None:
|
|
120
|
+
log.debug("...Already have pointer symbol: %r. Skipping.", sym)
|
|
121
|
+
continue
|
|
122
|
+
if ptr_addr in self.project.kb.labels:
|
|
123
|
+
log.debug("...Already have pointer label. Skipping.")
|
|
124
|
+
continue
|
|
125
|
+
sec = self.project.loader.find_section_containing(ptr_addr)
|
|
126
|
+
if not sec or not sec.is_writable:
|
|
127
|
+
log.debug("...Bogus section. Skipping.")
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
self.results.append(
|
|
131
|
+
APIObfuscationType2(
|
|
132
|
+
resolved_func_name=func_name,
|
|
133
|
+
resolved_func_ptr=ptr,
|
|
134
|
+
resolved_in=caller,
|
|
135
|
+
resolved_by=callee,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _mark_globals(self):
|
|
140
|
+
"""
|
|
141
|
+
Create function pointer labels/variables.
|
|
142
|
+
"""
|
|
143
|
+
for result in self.results:
|
|
144
|
+
# Create a label
|
|
145
|
+
lbl = self.project.kb.labels.get_unique_label(f"p_{result.resolved_func_name}")
|
|
146
|
+
self.project.kb.labels[result.resolved_func_ptr.addr] = lbl
|
|
147
|
+
log.debug("...Created label %s for address %x", lbl, result.resolved_func_ptr.addr)
|
|
148
|
+
|
|
149
|
+
# Create a variable
|
|
150
|
+
global_variables = self.variable_kb.variables["global"]
|
|
151
|
+
variables = global_variables.get_global_variables(result.resolved_func_ptr.addr)
|
|
152
|
+
if not variables:
|
|
153
|
+
ident = global_variables.next_variable_ident("global")
|
|
154
|
+
var = SimMemoryVariable(
|
|
155
|
+
result.resolved_func_ptr.addr, result.resolved_func_ptr.size, name=lbl, ident=ident
|
|
156
|
+
)
|
|
157
|
+
global_variables.set_variable("global", result.resolved_func_ptr.addr, var)
|
|
158
|
+
log.debug("...Created variable %r", var)
|
|
159
|
+
elif len(variables) == 1:
|
|
160
|
+
(var,) = variables
|
|
161
|
+
log.debug("...Renaming variable %r -> %s", var, lbl)
|
|
162
|
+
var.name = lbl
|
|
163
|
+
|
|
164
|
+
self.project.kb.obfuscations.type2_deobfuscated_apis[result.resolved_func_ptr.addr] = (
|
|
165
|
+
result.resolved_func_name
|
|
166
|
+
)
|
|
@@ -9,11 +9,11 @@ import networkx
|
|
|
9
9
|
|
|
10
10
|
import claripy
|
|
11
11
|
|
|
12
|
-
from angr import sim_options
|
|
13
12
|
from angr.analyses import Analysis, AnalysesHub
|
|
14
|
-
from angr.errors import SimMemoryMissingError, AngrCallableMultistateError, AngrCallableError
|
|
13
|
+
from angr.errors import SimMemoryMissingError, AngrCallableMultistateError, AngrCallableError, AngrAnalysisError
|
|
15
14
|
from angr.calling_conventions import SimRegArg, default_cc
|
|
16
15
|
from angr.state_plugins.sim_action import SimActionData
|
|
16
|
+
from angr.sim_options import ZERO_FILL_UNCONSTRAINED_REGISTERS, ZERO_FILL_UNCONSTRAINED_MEMORY, TRACK_MEMORY_ACTIONS
|
|
17
17
|
from angr.sim_type import SimTypeFunction, SimTypeBottom, SimTypePointer
|
|
18
18
|
from angr.analyses.reaching_definitions import ObservationPointType
|
|
19
19
|
from angr.utils.graph import GraphUtils
|
|
@@ -23,12 +23,23 @@ from .irsb_reg_collector import IRSBRegisterCollector
|
|
|
23
23
|
_l = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
STEP_LIMIT_FIND = 500
|
|
27
|
+
STEP_LIMIT_ANALYSIS = 5000
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
class StringDeobFuncDescriptor:
|
|
31
|
+
"""
|
|
32
|
+
Describes a string deobfuscation function.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
string_input_arg_idx: int
|
|
36
|
+
string_output_arg_idx: int
|
|
37
|
+
string_length_arg_idx: int | None
|
|
38
|
+
string_null_terminating: bool | None
|
|
39
|
+
|
|
27
40
|
def __init__(self):
|
|
28
|
-
self.string_input_arg_idx = None
|
|
29
|
-
self.string_output_arg_idx = None
|
|
30
41
|
self.string_length_arg_idx = None
|
|
31
|
-
self.string_null_terminating
|
|
42
|
+
self.string_null_terminating = None
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
class StringObfuscationFinder(Analysis):
|
|
@@ -89,6 +100,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
89
100
|
# Type 1 string deobfuscation functions will decrypt each string once and for good.
|
|
90
101
|
|
|
91
102
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
103
|
+
if cfg is None:
|
|
104
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
105
|
+
|
|
92
106
|
arch = self.project.arch
|
|
93
107
|
|
|
94
108
|
type1_candidates: list[tuple[int, StringDeobFuncDescriptor]] = []
|
|
@@ -100,6 +114,10 @@ class StringObfuscationFinder(Analysis):
|
|
|
100
114
|
if func.prototype is None or len(func.prototype.args) < 1:
|
|
101
115
|
continue
|
|
102
116
|
|
|
117
|
+
if len(func.arguments) != len(func.prototype.args):
|
|
118
|
+
# function argument locations and function prototype arguments do not match
|
|
119
|
+
continue
|
|
120
|
+
|
|
103
121
|
if self.project.kb.functions.callgraph.out_degree[func.addr] != 0:
|
|
104
122
|
continue
|
|
105
123
|
|
|
@@ -123,14 +141,22 @@ class StringObfuscationFinder(Analysis):
|
|
|
123
141
|
dec = self.project.analyses.Decompiler(func, cfg=cfg)
|
|
124
142
|
except Exception: # pylint:disable=broad-exception-caught
|
|
125
143
|
continue
|
|
126
|
-
if
|
|
144
|
+
if (
|
|
145
|
+
dec.codegen is None
|
|
146
|
+
or not dec.codegen.text
|
|
147
|
+
or not self._like_type1_deobfuscation_function(dec.codegen.text)
|
|
148
|
+
):
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
func_node = cfg.get_any_node(func.addr)
|
|
152
|
+
if func_node is None:
|
|
127
153
|
continue
|
|
128
154
|
|
|
129
155
|
args_list = []
|
|
130
156
|
for caller in callers:
|
|
131
157
|
callsite_nodes = [
|
|
132
158
|
pred
|
|
133
|
-
for pred in cfg.get_predecessors(
|
|
159
|
+
for pred in cfg.get_predecessors(func_node)
|
|
134
160
|
if pred.function_address == caller and pred.instruction_addrs
|
|
135
161
|
]
|
|
136
162
|
observation_points = []
|
|
@@ -148,15 +174,21 @@ class StringObfuscationFinder(Analysis):
|
|
|
148
174
|
callsite_node.instruction_addrs[-1],
|
|
149
175
|
ObservationPointType.OP_BEFORE,
|
|
150
176
|
)
|
|
177
|
+
if observ is None:
|
|
178
|
+
continue
|
|
151
179
|
# load values for each function argument
|
|
152
180
|
args: list[tuple[int, Any]] = []
|
|
153
181
|
for arg_idx, func_arg in enumerate(func.arguments):
|
|
154
182
|
# FIXME: We are ignoring all non-register function arguments until we see a test case where
|
|
155
183
|
# FIXME: stack-passing arguments are used
|
|
184
|
+
real_arg = func.prototype.args[arg_idx]
|
|
156
185
|
if isinstance(func_arg, SimRegArg):
|
|
157
186
|
reg_offset, reg_size = arch.registers[func_arg.reg_name]
|
|
187
|
+
arg_size = (
|
|
188
|
+
real_arg.size if real_arg.size is not None else reg_size
|
|
189
|
+
) // self.project.arch.byte_width
|
|
158
190
|
try:
|
|
159
|
-
mv = observ.registers.load(reg_offset, size=
|
|
191
|
+
mv = observ.registers.load(reg_offset, size=arg_size)
|
|
160
192
|
except SimMemoryMissingError:
|
|
161
193
|
args.append((arg_idx, claripy.BVV(0xDEADBEEF, self.project.arch.bits)))
|
|
162
194
|
continue
|
|
@@ -185,7 +217,15 @@ class StringObfuscationFinder(Analysis):
|
|
|
185
217
|
# now that we have good arguments, let's test the function!
|
|
186
218
|
for args in args_list:
|
|
187
219
|
func_call = self.project.factory.callable(
|
|
188
|
-
func.addr,
|
|
220
|
+
func.addr,
|
|
221
|
+
concrete_only=True,
|
|
222
|
+
cc=func.calling_convention,
|
|
223
|
+
prototype=func.prototype,
|
|
224
|
+
add_options={
|
|
225
|
+
ZERO_FILL_UNCONSTRAINED_MEMORY,
|
|
226
|
+
ZERO_FILL_UNCONSTRAINED_REGISTERS,
|
|
227
|
+
},
|
|
228
|
+
step_limit=STEP_LIMIT_FIND,
|
|
189
229
|
)
|
|
190
230
|
|
|
191
231
|
# before calling the function, let's record the crime scene
|
|
@@ -202,6 +242,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
202
242
|
except (AngrCallableMultistateError, AngrCallableError):
|
|
203
243
|
continue
|
|
204
244
|
|
|
245
|
+
if func_call.result_state is None:
|
|
246
|
+
continue
|
|
247
|
+
|
|
205
248
|
# let's see what this amazing function has done
|
|
206
249
|
# TODO: Support cases where input and output are using different function arguments
|
|
207
250
|
for arg_idx, addr, old_value in values:
|
|
@@ -240,6 +283,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
240
283
|
|
|
241
284
|
arch = self.project.arch
|
|
242
285
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
286
|
+
if cfg is None:
|
|
287
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
288
|
+
|
|
243
289
|
func = self.kb.functions.get_by_addr(func_addr)
|
|
244
290
|
func_node = cfg.get_any_node(func_addr)
|
|
245
291
|
assert func_node is not None
|
|
@@ -260,14 +306,20 @@ class StringObfuscationFinder(Analysis):
|
|
|
260
306
|
callsite_node.instruction_addrs[-1],
|
|
261
307
|
ObservationPointType.OP_BEFORE,
|
|
262
308
|
)
|
|
309
|
+
if observ is None:
|
|
310
|
+
continue
|
|
263
311
|
args = []
|
|
264
|
-
|
|
312
|
+
assert func.prototype is not None and len(func.arguments) == len(func.prototype.args)
|
|
313
|
+
for func_arg, real_arg in zip(func.arguments, func.prototype.args):
|
|
265
314
|
# FIXME: We are ignoring all non-register function arguments until we see a test case where
|
|
266
315
|
# FIXME: stack-passing arguments are used
|
|
267
316
|
if isinstance(func_arg, SimRegArg):
|
|
268
317
|
reg_offset, reg_size = arch.registers[func_arg.reg_name]
|
|
318
|
+
arg_size = (
|
|
319
|
+
real_arg.size if real_arg.size is not None else reg_size
|
|
320
|
+
) // self.project.arch.byte_width
|
|
269
321
|
try:
|
|
270
|
-
mv = observ.registers.load(reg_offset, size=
|
|
322
|
+
mv = observ.registers.load(reg_offset, size=arg_size)
|
|
271
323
|
except SimMemoryMissingError:
|
|
272
324
|
args.append(claripy.BVV(0xDEADBEEF, self.project.arch.bits))
|
|
273
325
|
continue
|
|
@@ -286,7 +338,12 @@ class StringObfuscationFinder(Analysis):
|
|
|
286
338
|
|
|
287
339
|
# call the function
|
|
288
340
|
func_call = self.project.factory.callable(
|
|
289
|
-
func.addr,
|
|
341
|
+
func.addr,
|
|
342
|
+
concrete_only=True,
|
|
343
|
+
cc=func.calling_convention,
|
|
344
|
+
prototype=func.prototype,
|
|
345
|
+
add_options={ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
|
|
346
|
+
step_limit=STEP_LIMIT_ANALYSIS,
|
|
290
347
|
)
|
|
291
348
|
try:
|
|
292
349
|
func_call(*args)
|
|
@@ -303,6 +360,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
303
360
|
)
|
|
304
361
|
continue
|
|
305
362
|
|
|
363
|
+
if func_call.result_state is None:
|
|
364
|
+
continue
|
|
365
|
+
|
|
306
366
|
# dump the decrypted string!
|
|
307
367
|
output_addr = args[desc.string_output_arg_idx]
|
|
308
368
|
length = args[desc.string_length_arg_idx].concrete_value if desc.string_length_arg_idx is not None else 256
|
|
@@ -322,6 +382,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
322
382
|
xref_set = xrefs.get_xrefs_by_dst(str_addr)
|
|
323
383
|
block_addrs = {xref.block_addr for xref in xref_set}
|
|
324
384
|
for block_addr in block_addrs:
|
|
385
|
+
if block_addr is None:
|
|
386
|
+
continue
|
|
325
387
|
node = cfg.get_any_node(block_addr)
|
|
326
388
|
if node is not None:
|
|
327
389
|
callees = list(self.kb.functions.callgraph.successors(node.function_address))
|
|
@@ -340,6 +402,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
340
402
|
# Type 2 string deobfuscation functions will decrypt each string once and for good.
|
|
341
403
|
|
|
342
404
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
405
|
+
if cfg is None:
|
|
406
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
343
407
|
|
|
344
408
|
type2_candidates: list[tuple[int, StringDeobFuncDescriptor, list[tuple[int, int, bytes]]]] = []
|
|
345
409
|
|
|
@@ -374,7 +438,11 @@ class StringObfuscationFinder(Analysis):
|
|
|
374
438
|
dec = self.project.analyses.Decompiler(func, cfg=cfg, expr_collapse_depth=64)
|
|
375
439
|
except Exception: # pylint:disable=broad-exception-caught
|
|
376
440
|
continue
|
|
377
|
-
if
|
|
441
|
+
if (
|
|
442
|
+
dec.codegen is None
|
|
443
|
+
or not dec.codegen.text
|
|
444
|
+
or not self._like_type2_deobfuscation_function(dec.codegen.text)
|
|
445
|
+
):
|
|
378
446
|
continue
|
|
379
447
|
|
|
380
448
|
desc = StringDeobFuncDescriptor()
|
|
@@ -384,7 +452,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
384
452
|
concrete_only=True,
|
|
385
453
|
cc=func.calling_convention,
|
|
386
454
|
prototype=func.prototype,
|
|
387
|
-
add_options={
|
|
455
|
+
add_options={TRACK_MEMORY_ACTIONS, ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
|
|
456
|
+
step_limit=STEP_LIMIT_FIND,
|
|
388
457
|
)
|
|
389
458
|
|
|
390
459
|
try:
|
|
@@ -392,6 +461,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
392
461
|
except (AngrCallableMultistateError, AngrCallableError):
|
|
393
462
|
continue
|
|
394
463
|
|
|
464
|
+
if func_call.result_state is None:
|
|
465
|
+
continue
|
|
466
|
+
|
|
395
467
|
# where are the reads and writes?
|
|
396
468
|
all_global_reads = []
|
|
397
469
|
all_global_writes = []
|
|
@@ -399,7 +471,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
399
471
|
if not isinstance(action, SimActionData):
|
|
400
472
|
continue
|
|
401
473
|
if not action.actual_addrs:
|
|
402
|
-
if not action.addr.ast.concrete:
|
|
474
|
+
if action.addr is None or not action.addr.ast.concrete:
|
|
403
475
|
continue
|
|
404
476
|
actual_addrs = [action.addr.ast.concrete_value]
|
|
405
477
|
else:
|
|
@@ -469,6 +541,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
469
541
|
"""
|
|
470
542
|
|
|
471
543
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
544
|
+
if cfg is None:
|
|
545
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
472
546
|
|
|
473
547
|
# for each string table address, we find its string loader function
|
|
474
548
|
# an obvious candidate function is 0x140001b20
|
|
@@ -478,6 +552,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
478
552
|
xref_set = xrefs.get_xrefs_by_dst(table_addr)
|
|
479
553
|
block_addrs = {xref.block_addr for xref in xref_set}
|
|
480
554
|
for block_addr in block_addrs:
|
|
555
|
+
if block_addr is None:
|
|
556
|
+
continue
|
|
481
557
|
node = cfg.get_any_node(block_addr)
|
|
482
558
|
if node is not None:
|
|
483
559
|
callees = list(self.kb.functions.callgraph.successors(node.function_address))
|
|
@@ -496,6 +572,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
496
572
|
# not have a SimProcedure for)
|
|
497
573
|
|
|
498
574
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
575
|
+
if cfg is None:
|
|
576
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
577
|
+
|
|
499
578
|
functions = self.kb.functions
|
|
500
579
|
callgraph_digraph = networkx.DiGraph(functions.callgraph)
|
|
501
580
|
|
|
@@ -554,7 +633,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
554
633
|
except Exception: # pylint:disable=broad-exception-caught
|
|
555
634
|
# catch all exceptions
|
|
556
635
|
continue
|
|
557
|
-
if dec.codegen is None:
|
|
636
|
+
if dec.codegen is None or not dec.codegen.text:
|
|
558
637
|
continue
|
|
559
638
|
if not self._like_type3_deobfuscation_function(dec.codegen.text):
|
|
560
639
|
continue
|
|
@@ -605,6 +684,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
605
684
|
"""
|
|
606
685
|
|
|
607
686
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
687
|
+
if cfg is None:
|
|
688
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
608
689
|
|
|
609
690
|
call_sites = cfg.get_predecessors(cfg.get_any_node(func_addr))
|
|
610
691
|
callinsn2content = {}
|
|
@@ -687,7 +768,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
687
768
|
# execute the block at the call site
|
|
688
769
|
state = self.project.factory.blank_state(
|
|
689
770
|
addr=call_site_addr,
|
|
690
|
-
add_options={
|
|
771
|
+
add_options={ZERO_FILL_UNCONSTRAINED_REGISTERS, ZERO_FILL_UNCONSTRAINED_MEMORY},
|
|
691
772
|
)
|
|
692
773
|
# setup sp and bp, just in case
|
|
693
774
|
state.regs._sp = 0x7FFF0000
|
|
@@ -728,7 +809,13 @@ class StringObfuscationFinder(Analysis):
|
|
|
728
809
|
self.project.arch
|
|
729
810
|
)
|
|
730
811
|
callable_0 = self.project.factory.callable(
|
|
731
|
-
func_addr,
|
|
812
|
+
func_addr,
|
|
813
|
+
concrete_only=True,
|
|
814
|
+
base_state=in_state,
|
|
815
|
+
cc=cc,
|
|
816
|
+
prototype=prototype_0,
|
|
817
|
+
add_options={ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
|
|
818
|
+
step_limit=STEP_LIMIT_ANALYSIS,
|
|
732
819
|
)
|
|
733
820
|
|
|
734
821
|
try:
|
|
@@ -181,7 +181,7 @@ class ForwardAnalysis(Generic[AnalysisState, NodeType, JobType, JobKey]):
|
|
|
181
181
|
"""
|
|
182
182
|
return node
|
|
183
183
|
|
|
184
|
-
def _run_on_node(self, node: NodeType, state: AnalysisState) -> tuple[bool, AnalysisState]:
|
|
184
|
+
def _run_on_node(self, node: NodeType, state: AnalysisState) -> tuple[bool | None, AnalysisState]:
|
|
185
185
|
"""
|
|
186
186
|
The analysis routine that runs on each node in the graph.
|
|
187
187
|
|
|
@@ -31,7 +31,7 @@ class ClaripyDataEngineMixin(
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _vex_make_comparison(
|
|
34
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool]
|
|
34
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool],
|
|
35
35
|
) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
|
|
36
36
|
@SimEngineLightVEX.binop_handler
|
|
37
37
|
def inner(self, expr):
|
|
@@ -44,7 +44,7 @@ def _vex_make_comparison(
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def _vex_make_vec_comparison(
|
|
47
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool]
|
|
47
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool],
|
|
48
48
|
) -> Callable[[ClaripyDataEngineMixin, int, int, Binop], claripy.ast.BV]:
|
|
49
49
|
@SimEngineLightVEX.binopv_handler
|
|
50
50
|
def inner(self, size, count, expr):
|
|
@@ -56,7 +56,7 @@ def _vex_make_vec_comparison(
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def _vex_make_operation(
|
|
59
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
|
|
59
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
|
|
60
60
|
) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
|
|
61
61
|
@SimEngineLightVEX.binop_handler
|
|
62
62
|
def inner(self, expr: Binop):
|
|
@@ -70,7 +70,7 @@ def _vex_make_operation(
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _vex_make_unary_operation(
|
|
73
|
-
func: Callable[[claripy.ast.BV], claripy.ast.BV]
|
|
73
|
+
func: Callable[[claripy.ast.BV], claripy.ast.BV],
|
|
74
74
|
) -> Callable[[ClaripyDataEngineMixin, Unop], claripy.ast.BV]:
|
|
75
75
|
@SimEngineLightVEX.unop_handler
|
|
76
76
|
def inner(self, expr):
|
|
@@ -84,7 +84,7 @@ def _vex_make_unary_operation(
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def _vex_make_shift_operation(
|
|
87
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
|
|
87
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
|
|
88
88
|
) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
|
|
89
89
|
@_vex_make_operation
|
|
90
90
|
def inner(a, b):
|
|
@@ -99,7 +99,7 @@ def _vex_make_shift_operation(
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def _vex_make_vec_operation(
|
|
102
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
|
|
102
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
|
|
103
103
|
) -> Callable[[ClaripyDataEngineMixin, int, int, Binop], claripy.ast.BV]:
|
|
104
104
|
@SimEngineLightVEX.binopv_handler
|
|
105
105
|
def inner(self, size, count, expr):
|
|
@@ -15,7 +15,7 @@ from angr.knowledge_plugins.key_definitions.atoms import (
|
|
|
15
15
|
from angr.knowledge_plugins.key_definitions.definition import Definition
|
|
16
16
|
from angr.analyses import register_analysis
|
|
17
17
|
from .reaching_definitions import ReachingDefinitionsAnalysis, ReachingDefinitionsModel
|
|
18
|
-
from .function_handler import FunctionHandler, FunctionCallData
|
|
18
|
+
from .function_handler import FunctionHandler, FunctionCallData, FunctionCallRelationships
|
|
19
19
|
from .rd_state import ReachingDefinitionsState
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -29,6 +29,7 @@ __all__ = (
|
|
|
29
29
|
"ConstantSrc",
|
|
30
30
|
"Definition",
|
|
31
31
|
"FunctionCallData",
|
|
32
|
+
"FunctionCallRelationships",
|
|
32
33
|
"FunctionHandler",
|
|
33
34
|
"GuardUse",
|
|
34
35
|
"LiveDefinitions",
|
|
@@ -6,7 +6,6 @@ from typing import (
|
|
|
6
6
|
Any,
|
|
7
7
|
)
|
|
8
8
|
from collections.abc import Iterable, Iterator
|
|
9
|
-
from dataclasses import dataclass
|
|
10
9
|
|
|
11
10
|
import networkx
|
|
12
11
|
|
|
@@ -35,16 +34,6 @@ def _is_definition(node):
|
|
|
35
34
|
return isinstance(node, Definition)
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
@dataclass
|
|
39
|
-
class FunctionCallRelationships: # TODO this doesn't belong in this file anymore
|
|
40
|
-
callsite: CodeLocation
|
|
41
|
-
target: int | None
|
|
42
|
-
args_defns: list[set[Definition]]
|
|
43
|
-
other_input_defns: set[Definition]
|
|
44
|
-
ret_defns: set[Definition]
|
|
45
|
-
other_output_defns: set[Definition]
|
|
46
|
-
|
|
47
|
-
|
|
48
37
|
class DepGraph:
|
|
49
38
|
"""
|
|
50
39
|
The representation of a dependency graph: a directed graph, where nodes are definitions, and edges represent uses.
|
|
@@ -84,7 +73,7 @@ class DepGraph:
|
|
|
84
73
|
"""
|
|
85
74
|
self._graph.add_edge(source, destination, **labels)
|
|
86
75
|
|
|
87
|
-
def nodes(self) ->
|
|
76
|
+
def nodes(self) -> Iterator[Definition]:
|
|
88
77
|
return self._graph.nodes()
|
|
89
78
|
|
|
90
79
|
def predecessors(self, node: Definition) -> Iterator[Definition]:
|