angr 9.2.124__py3-none-manylinux2014_x86_64.whl → 9.2.125__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/__init__.py +9 -1
- angr/analyses/codecave.py +77 -0
- angr/analyses/decompiler/clinic.py +31 -1
- angr/analyses/decompiler/decompiler.py +4 -0
- angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
- angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +6 -0
- angr/analyses/decompiler/optimization_passes/tag_slicer.py +41 -0
- angr/analyses/decompiler/peephole_optimizations/constant_derefs.py +2 -2
- angr/analyses/patchfinder.py +137 -0
- angr/analyses/pathfinder.py +282 -0
- angr/analyses/smc.py +159 -0
- angr/angrdb/models.py +1 -2
- angr/engines/vex/heavy/heavy.py +2 -0
- angr/exploration_techniques/spiller_db.py +1 -2
- angr/knowledge_plugins/functions/function.py +4 -0
- angr/knowledge_plugins/functions/function_manager.py +18 -9
- angr/knowledge_plugins/functions/function_parser.py +1 -1
- angr/knowledge_plugins/functions/soot_function.py +1 -0
- angr/misc/ux.py +2 -2
- angr/project.py +17 -1
- angr/state_plugins/history.py +6 -4
- angr/utils/bits.py +4 -0
- angr/utils/tagged_interval_map.py +112 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/METADATA +6 -6
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/RECORD +30 -24
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/LICENSE +0 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/WHEEL +0 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/entry_points.txt +0 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/top_level.txt +0 -0
angr/__init__.py
CHANGED
angr/analyses/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint:disable=wrong-import-position
|
|
1
|
+
# " pylint:disable=wrong-import-position
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from .analysis import Analysis, AnalysesHub
|
|
@@ -49,6 +49,10 @@ from .flirt import FlirtAnalysis
|
|
|
49
49
|
from .s_propagator import SPropagatorAnalysis
|
|
50
50
|
from .s_reaching_definitions import SReachingDefinitionsAnalysis
|
|
51
51
|
from .s_liveness import SLivenessAnalysis
|
|
52
|
+
from .codecave import CodeCaveAnalysis
|
|
53
|
+
from .patchfinder import PatchFinderAnalysis
|
|
54
|
+
from .pathfinder import Pathfinder
|
|
55
|
+
from .smc import SelfModifyingCodeAnalysis
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
__all__ = (
|
|
@@ -101,4 +105,8 @@ __all__ = (
|
|
|
101
105
|
"SPropagatorAnalysis",
|
|
102
106
|
"SReachingDefinitionsAnalysis",
|
|
103
107
|
"SLivenessAnalysis",
|
|
108
|
+
"CodeCaveAnalysis",
|
|
109
|
+
"PatchFinderAnalysis",
|
|
110
|
+
"Pathfinder",
|
|
111
|
+
"SelfModifyingCodeAnalysis",
|
|
104
112
|
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
from enum import Enum, auto
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from angr.analyses import Analysis, AnalysesHub
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from angr.knowledge_plugins import Function
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CodeCaveClassification(Enum):
|
|
18
|
+
"""
|
|
19
|
+
Type of code caves.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
ALIGNMENT = auto()
|
|
23
|
+
UNREACHABLE = auto()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CodeCave:
|
|
28
|
+
"""
|
|
29
|
+
Describes a code cave in a binary.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
func: Function | None
|
|
33
|
+
addr: int
|
|
34
|
+
size: int
|
|
35
|
+
classification: CodeCaveClassification
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CodeCaveAnalysis(Analysis):
|
|
39
|
+
"""
|
|
40
|
+
Best-effort static location of potential vacant code caves for possible code injection:
|
|
41
|
+
- Padding functions
|
|
42
|
+
- Unreachable code
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
codecaves: list[CodeCave]
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self.codecaves = []
|
|
49
|
+
|
|
50
|
+
if len(self.project.kb.functions) == 0 and self.project.kb.cfgs.get_most_accurate() is None:
|
|
51
|
+
log.warning("Please run CFGFast analysis first, to identify functions")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# Alignment functions
|
|
55
|
+
for func in self.project.kb.functions.values():
|
|
56
|
+
if func.is_alignment:
|
|
57
|
+
for block in func.blocks:
|
|
58
|
+
self.codecaves.append(CodeCave(func, block.addr, block.size, CodeCaveClassification.ALIGNMENT))
|
|
59
|
+
|
|
60
|
+
# Unreachable code
|
|
61
|
+
for func in self.project.kb.functions.values():
|
|
62
|
+
if func.is_alignment or func.is_plt or func.is_simprocedure or func.addr in self.project.kb.labels:
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
in_degree = self.project.kb.callgraph.in_degree(func.addr)
|
|
66
|
+
if in_degree == 0 or (
|
|
67
|
+
in_degree == 1
|
|
68
|
+
and self.project.kb.functions[next(self.project.kb.callgraph.predecessors(func.addr))].is_alignment
|
|
69
|
+
):
|
|
70
|
+
for block in func.blocks:
|
|
71
|
+
self.codecaves.append(CodeCave(func, block.addr, block.size, CodeCaveClassification.UNREACHABLE))
|
|
72
|
+
|
|
73
|
+
# FIXME: find dead blocks with argument propagation
|
|
74
|
+
# FIXME: find dead blocks with external coverage info
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
AnalysesHub.register_default("CodeCaves", CodeCaveAnalysis)
|
|
@@ -42,6 +42,7 @@ from .optimization_passes import (
|
|
|
42
42
|
OptimizationPassStage,
|
|
43
43
|
RegisterSaveAreaSimplifier,
|
|
44
44
|
StackCanarySimplifier,
|
|
45
|
+
TagSlicer,
|
|
45
46
|
DUPLICATING_OPTS,
|
|
46
47
|
CONDENSING_OPTS,
|
|
47
48
|
)
|
|
@@ -111,6 +112,7 @@ class Clinic(Analysis):
|
|
|
111
112
|
inlining_parents: set[int] | None = None,
|
|
112
113
|
vvar_id_start: int = 0,
|
|
113
114
|
optimization_scratch: dict[str, Any] | None = None,
|
|
115
|
+
desired_variables: set[str] | None = None,
|
|
114
116
|
):
|
|
115
117
|
if not func.normalized and mode == ClinicMode.DECOMPILE:
|
|
116
118
|
raise ValueError("Decompilation must work on normalized function graphs.")
|
|
@@ -154,6 +156,7 @@ class Clinic(Analysis):
|
|
|
154
156
|
self._inline_functions = inline_functions
|
|
155
157
|
self._inlined_counts = {} if inlined_counts is None else inlined_counts
|
|
156
158
|
self._inlining_parents = inlining_parents or ()
|
|
159
|
+
self._desired_variables = desired_variables
|
|
157
160
|
|
|
158
161
|
self._register_save_areas_removed: bool = False
|
|
159
162
|
|
|
@@ -220,6 +223,9 @@ class Clinic(Analysis):
|
|
|
220
223
|
ail_graph = self._inline_child_functions(ail_graph)
|
|
221
224
|
|
|
222
225
|
ail_graph = self._decompilation_simplifications(ail_graph)
|
|
226
|
+
|
|
227
|
+
if self._desired_variables:
|
|
228
|
+
ail_graph = self._slice_variables(ail_graph)
|
|
223
229
|
self.graph = ail_graph
|
|
224
230
|
|
|
225
231
|
def _decompilation_graph_recovery(self):
|
|
@@ -279,6 +285,27 @@ class Clinic(Analysis):
|
|
|
279
285
|
|
|
280
286
|
return ail_graph
|
|
281
287
|
|
|
288
|
+
def _slice_variables(self, ail_graph):
|
|
289
|
+
nodes_index = {(n.addr, n.idx): n for n in ail_graph.nodes()}
|
|
290
|
+
|
|
291
|
+
vfm = self.variable_kb.variables.function_managers[self.function.addr]
|
|
292
|
+
for v_name in self._desired_variables:
|
|
293
|
+
v = next(iter(vv for vv in vfm._unified_variables if vv.name == v_name))
|
|
294
|
+
for va in vfm.get_variable_accesses(v):
|
|
295
|
+
nodes_index[(va.location.block_addr, va.location.block_idx)].statements[va.location.stmt_idx].tags[
|
|
296
|
+
"keep_in_slice"
|
|
297
|
+
] = True
|
|
298
|
+
|
|
299
|
+
a = TagSlicer(
|
|
300
|
+
self.function,
|
|
301
|
+
graph=ail_graph,
|
|
302
|
+
variable_kb=self.variable_kb,
|
|
303
|
+
)
|
|
304
|
+
if a.out_graph:
|
|
305
|
+
# use the new graph
|
|
306
|
+
ail_graph = a.out_graph
|
|
307
|
+
return ail_graph
|
|
308
|
+
|
|
282
309
|
def _inline_child_functions(self, ail_graph):
|
|
283
310
|
for blk in ail_graph.nodes():
|
|
284
311
|
for idx, stmt in enumerate(blk.statements):
|
|
@@ -909,13 +936,16 @@ class Clinic(Analysis):
|
|
|
909
936
|
Convert a VEX block to an AIL block.
|
|
910
937
|
|
|
911
938
|
:param block_node: A BlockNode instance.
|
|
912
|
-
:return:
|
|
939
|
+
:return: A converted AIL block.
|
|
913
940
|
:rtype: ailment.Block
|
|
914
941
|
"""
|
|
915
942
|
|
|
916
943
|
if type(block_node) is not BlockNode:
|
|
917
944
|
return block_node
|
|
918
945
|
|
|
946
|
+
if block_node.size == 0:
|
|
947
|
+
return ailment.Block(block_node.addr, 0, statements=[])
|
|
948
|
+
|
|
919
949
|
block = self.project.factory.block(block_node.addr, block_node.size, cross_insn_opt=False)
|
|
920
950
|
if block.vex.jumpkind not in {"Ijk_Call", "Ijk_Boring", "Ijk_Ret"} and not block.vex.jumpkind.startswith(
|
|
921
951
|
"Ijk_Sys"
|
|
@@ -66,6 +66,7 @@ class Decompiler(Analysis):
|
|
|
66
66
|
decompile=True,
|
|
67
67
|
regen_clinic=True,
|
|
68
68
|
inline_functions=frozenset(),
|
|
69
|
+
desired_variables=frozenset(),
|
|
69
70
|
update_memory_data: bool = True,
|
|
70
71
|
generate_code: bool = True,
|
|
71
72
|
use_cache: bool = True,
|
|
@@ -103,6 +104,7 @@ class Decompiler(Analysis):
|
|
|
103
104
|
self._update_memory_data = update_memory_data
|
|
104
105
|
self._generate_code = generate_code
|
|
105
106
|
self._inline_functions = inline_functions
|
|
107
|
+
self._desired_variables = desired_variables
|
|
106
108
|
self._cache_parameters = (
|
|
107
109
|
{
|
|
108
110
|
"cfg": self._cfg,
|
|
@@ -118,6 +120,7 @@ class Decompiler(Analysis):
|
|
|
118
120
|
"ite_exprs": self._ite_exprs,
|
|
119
121
|
"binop_operators": self._binop_operators,
|
|
120
122
|
"inline_functions": self._inline_functions,
|
|
123
|
+
"desired_variables": self._desired_variables,
|
|
121
124
|
}
|
|
122
125
|
if use_cache
|
|
123
126
|
else None
|
|
@@ -226,6 +229,7 @@ class Decompiler(Analysis):
|
|
|
226
229
|
cache=cache,
|
|
227
230
|
progress_callback=progress_callback,
|
|
228
231
|
inline_functions=self._inline_functions,
|
|
232
|
+
desired_variables=self._desired_variables,
|
|
229
233
|
optimization_scratch=self._optimization_scratch,
|
|
230
234
|
**self.options_to_params(self.options_by_class["clinic"]),
|
|
231
235
|
)
|
|
@@ -26,6 +26,7 @@ from .cross_jump_reverter import CrossJumpReverter
|
|
|
26
26
|
from .code_motion import CodeMotionOptimization
|
|
27
27
|
from .switch_default_case_duplicator import SwitchDefaultCaseDuplicator
|
|
28
28
|
from .deadblock_remover import DeadblockRemover
|
|
29
|
+
from .tag_slicer import TagSlicer
|
|
29
30
|
from .inlined_string_transformation_simplifier import InlinedStringTransformationSimplifier
|
|
30
31
|
from .const_prop_reverter import ConstPropOptReverter
|
|
31
32
|
from .call_stmt_rewriter import CallStatementRewriter
|
|
@@ -59,6 +60,7 @@ ALL_OPTIMIZATION_PASSES = [
|
|
|
59
60
|
FlipBooleanCmp,
|
|
60
61
|
InlinedStringTransformationSimplifier,
|
|
61
62
|
CallStatementRewriter,
|
|
63
|
+
TagSlicer,
|
|
62
64
|
]
|
|
63
65
|
|
|
64
66
|
# these passes may duplicate code to remove gotos or improve the structure of the graph
|
|
@@ -118,6 +120,7 @@ __all__ = (
|
|
|
118
120
|
"ConstPropOptReverter",
|
|
119
121
|
"CallStatementRewriter",
|
|
120
122
|
"DuplicationReverter",
|
|
123
|
+
"TagSlicer",
|
|
121
124
|
"ALL_OPTIMIZATION_PASSES",
|
|
122
125
|
"DUPLICATING_OPTS",
|
|
123
126
|
"CONDENSING_OPTS",
|
|
@@ -240,6 +240,12 @@ class InlinedStringTransformationAILEngine(SimEngineLightAILMixin):
|
|
|
240
240
|
return None
|
|
241
241
|
|
|
242
242
|
def _handle_Neg(self, expr: UnaryOp):
|
|
243
|
+
v = self._expr(expr.operand)
|
|
244
|
+
if isinstance(v, claripy.ast.Bits):
|
|
245
|
+
return -v
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
def _handle_BitwiseNeg(self, expr: UnaryOp):
|
|
243
249
|
v = self._expr(expr.operand)
|
|
244
250
|
if isinstance(v, claripy.ast.Bits):
|
|
245
251
|
return ~v
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# pylint:disable=too-many-boolean-expressions
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ailment.statement import ConditionalJump, Jump, Label
|
|
6
|
+
from .optimization_pass import OptimizationPass, OptimizationPassStage
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_l = logging.getLogger(name=__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TagSlicer(OptimizationPass):
|
|
13
|
+
"""
|
|
14
|
+
Removes unmarked statements from the graph.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
ARCHES = None
|
|
18
|
+
PLATFORMS = None
|
|
19
|
+
STAGE = OptimizationPassStage.AFTER_VARIABLE_RECOVERY
|
|
20
|
+
NAME = "Remove unmarked statements from the graph."
|
|
21
|
+
DESCRIPTION = __doc__.strip()
|
|
22
|
+
|
|
23
|
+
def __init__(self, func, **kwargs):
|
|
24
|
+
super().__init__(func, **kwargs)
|
|
25
|
+
self.analyze()
|
|
26
|
+
|
|
27
|
+
def _check(self):
|
|
28
|
+
return True, {}
|
|
29
|
+
|
|
30
|
+
def _analyze(self, cache=None):
|
|
31
|
+
for n in self._graph.nodes():
|
|
32
|
+
for i, s in enumerate(n.statements):
|
|
33
|
+
if isinstance(s, (ConditionalJump, Jump, Label)):
|
|
34
|
+
continue
|
|
35
|
+
if not s.tags.get("keep_in_slice", False):
|
|
36
|
+
n.statements[i] = None
|
|
37
|
+
|
|
38
|
+
for n in self._graph.nodes():
|
|
39
|
+
n.statements = [s for s in n.statements if s is not None]
|
|
40
|
+
|
|
41
|
+
self.out_graph = self._graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from ailment.expression import Load, Const
|
|
3
|
-
from cle.backends import Blob
|
|
3
|
+
from cle.backends import Blob, Hex
|
|
4
4
|
|
|
5
5
|
from .base import PeepholeOptimizationExprBase
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ class ConstantDereferences(PeepholeOptimizationExprBase):
|
|
|
32
32
|
|
|
33
33
|
# is it loading from a blob?
|
|
34
34
|
obj = self.project.loader.find_object_containing(expr.addr.value)
|
|
35
|
-
if obj is not None and isinstance(obj, Blob):
|
|
35
|
+
if obj is not None and isinstance(obj, (Blob, Hex)):
|
|
36
36
|
# do we know the value that it's reading?
|
|
37
37
|
try:
|
|
38
38
|
val = self.project.loader.memory.unpack_word(expr.addr.value, size=self.project.arch.bytes)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# pylint:disable=missing-class-docstring
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from sortedcontainers import SortedDict
|
|
9
|
+
|
|
10
|
+
from angr.analyses import Analysis, AnalysesHub
|
|
11
|
+
from angr.utils.bits import ffs
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from angr.knowledge_plugins import Function
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OverlappingFunctionsAnalysis(Analysis):
|
|
21
|
+
"""
|
|
22
|
+
Identify functions with interleaved blocks.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
overlapping_functions: dict[int, list[int]]
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.overlapping_functions = defaultdict(list)
|
|
29
|
+
addr_to_func_max_addr = SortedDict()
|
|
30
|
+
|
|
31
|
+
for func in self.project.kb.functions.values():
|
|
32
|
+
if func.is_alignment:
|
|
33
|
+
continue
|
|
34
|
+
func_max_addr = max((block.addr + block.size) for block in func.blocks)
|
|
35
|
+
addr_to_func_max_addr[func.addr] = (func, func_max_addr)
|
|
36
|
+
|
|
37
|
+
for idx, (addr, (func, max_addr)) in enumerate(addr_to_func_max_addr.items()):
|
|
38
|
+
for other_addr in addr_to_func_max_addr.islice(idx + 1):
|
|
39
|
+
if other_addr >= max_addr:
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
self.overlapping_functions[addr].append(other_addr)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FunctionAlignmentAnalysis(Analysis):
|
|
46
|
+
"""
|
|
47
|
+
Determine typical function alignment
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
alignment: int | None
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.alignment = None
|
|
54
|
+
|
|
55
|
+
if len(self.project.kb.functions) == 0:
|
|
56
|
+
if self.project.kb.cfgs.get_most_accurate() is None:
|
|
57
|
+
log.warning("Please run CFGFast analysis first, to identify functions")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
alignment_bins = defaultdict(int)
|
|
61
|
+
count = 0
|
|
62
|
+
for func in self.project.kb.functions.values():
|
|
63
|
+
if not (func.is_alignment or func.is_plt or func.is_simprocedure):
|
|
64
|
+
alignment_bins[ffs(func.addr)] += 1
|
|
65
|
+
count += 1
|
|
66
|
+
|
|
67
|
+
# FIXME: Higher alignment values will be naturally aligned
|
|
68
|
+
|
|
69
|
+
typical_alignment = max(alignment_bins, key=lambda k: alignment_bins[k])
|
|
70
|
+
if count > 10 and alignment_bins[typical_alignment] >= count / 4: # XXX: cutoff
|
|
71
|
+
self.alignment = 1 << max(typical_alignment, 0)
|
|
72
|
+
log.debug("Function alignment appears to be %d bytes", self.alignment)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class AtypicallyAlignedFunction:
|
|
77
|
+
function: Function
|
|
78
|
+
expected_alignment: int
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class PatchedOutFunctionality:
|
|
83
|
+
patched_function: Function
|
|
84
|
+
patched_out_function: Function
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PatchFinderAnalysis(Analysis):
|
|
88
|
+
"""
|
|
89
|
+
Looks for binary patches using some basic heuristics:
|
|
90
|
+
- Looking for interleaved functions
|
|
91
|
+
- Looking for unaligned functions
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# FIXME: Possible additional heuristics:
|
|
95
|
+
# - Jumps out to end of function, then back
|
|
96
|
+
# - Looking for patch jumps, e.g. push <addr>; ret
|
|
97
|
+
# - Looking for instruction partials broken by a patch (nodecode)
|
|
98
|
+
# - Unusual stack manipulation
|
|
99
|
+
|
|
100
|
+
atypical_alignments: list[Function]
|
|
101
|
+
possibly_patched_out: list[PatchedOutFunctionality]
|
|
102
|
+
|
|
103
|
+
def __init__(self):
|
|
104
|
+
self.atypical_alignments = []
|
|
105
|
+
self.possibly_patched_out = []
|
|
106
|
+
|
|
107
|
+
if len(self.project.kb.functions) == 0:
|
|
108
|
+
if self.project.kb.cfgs.get_most_accurate() is None:
|
|
109
|
+
log.warning("Please run CFGFast analysis first, to identify functions")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# In CFGFast with scanning enabled, a function may be created from unreachable blocks within another function.
|
|
113
|
+
# Search for interleaved/overlapping functions to identify possible patches.
|
|
114
|
+
overlapping_functions = self.project.analyses.OverlappingFunctions().overlapping_functions
|
|
115
|
+
for addr, overlapping_func_addrs in overlapping_functions.items():
|
|
116
|
+
func = self.project.kb.functions[addr]
|
|
117
|
+
|
|
118
|
+
# Are the overlapping functions reachable?
|
|
119
|
+
for overlapping_addr in overlapping_func_addrs:
|
|
120
|
+
overlapping_func = self.project.kb.functions[overlapping_addr]
|
|
121
|
+
if self.project.kb.callgraph.in_degree(overlapping_addr) == 0:
|
|
122
|
+
self.possibly_patched_out.append(PatchedOutFunctionality(func, overlapping_func))
|
|
123
|
+
# FIXME: What does the patch do?
|
|
124
|
+
|
|
125
|
+
# Look for unaligned functions
|
|
126
|
+
expected_alignment = self.project.analyses.FunctionAlignment().alignment
|
|
127
|
+
if expected_alignment is not None and expected_alignment > self.project.arch.instruction_alignment:
|
|
128
|
+
for func in self.project.kb.functions.values():
|
|
129
|
+
if not (func.is_alignment or func.is_plt or func.is_simprocedure) and func.addr & (
|
|
130
|
+
expected_alignment - 1
|
|
131
|
+
):
|
|
132
|
+
self.atypical_alignments.append(AtypicallyAlignedFunction(func, expected_alignment))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
AnalysesHub.register_default("OverlappingFunctions", OverlappingFunctionsAnalysis)
|
|
136
|
+
AnalysesHub.register_default("FunctionAlignment", FunctionAlignmentAnalysis)
|
|
137
|
+
AnalysesHub.register_default("PatchFinder", PatchFinderAnalysis)
|