snikrflow 0.1.0__tar.gz
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.
- snikrflow-0.1.0/LICENSE +21 -0
- snikrflow-0.1.0/PKG-INFO +240 -0
- snikrflow-0.1.0/README.md +187 -0
- snikrflow-0.1.0/pyproject.toml +51 -0
- snikrflow-0.1.0/setup.cfg +4 -0
- snikrflow-0.1.0/snikrflow/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/analysis/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/audit.py +145 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/callgraph.py +40 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/cfg.py +49 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/dataflow.py +205 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/functions.py +99 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/jni.py +45 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/reachability.py +84 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/sinks.py +74 -0
- snikrflow-0.1.0/snikrflow/analysis/backends/angr/symbolic.py +152 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/apk_audit.py +97 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/audit.py +14 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/callgraph.py +13 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/cfg.py +9 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/dataflow.py +31 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/functions.py +20 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/jni.py +23 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/reachability.py +14 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/sinks.py +22 -0
- snikrflow-0.1.0/snikrflow/analysis/interfaces/symbolic.py +30 -0
- snikrflow-0.1.0/snikrflow/android/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/android/apk.py +78 -0
- snikrflow-0.1.0/snikrflow/cli.py +590 -0
- snikrflow-0.1.0/snikrflow/core/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/core/exceptions.py +36 -0
- snikrflow-0.1.0/snikrflow/core/models.py +326 -0
- snikrflow-0.1.0/snikrflow/elf/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/elf/jni.py +54 -0
- snikrflow-0.1.0/snikrflow/elf/loader.py +105 -0
- snikrflow-0.1.0/snikrflow/elf/metadata.py +84 -0
- snikrflow-0.1.0/snikrflow/reporting/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/reporting/sarif.py +122 -0
- snikrflow-0.1.0/snikrflow/reporting/terminal.py +412 -0
- snikrflow-0.1.0/snikrflow/sinks/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow/sinks/database.py +30 -0
- snikrflow-0.1.0/snikrflow/sinks/signatures.py +40 -0
- snikrflow-0.1.0/snikrflow/utils/__init__.py +0 -0
- snikrflow-0.1.0/snikrflow.egg-info/PKG-INFO +240 -0
- snikrflow-0.1.0/snikrflow.egg-info/SOURCES.txt +50 -0
- snikrflow-0.1.0/snikrflow.egg-info/dependency_links.txt +1 -0
- snikrflow-0.1.0/snikrflow.egg-info/entry_points.txt +2 -0
- snikrflow-0.1.0/snikrflow.egg-info/requires.txt +9 -0
- snikrflow-0.1.0/snikrflow.egg-info/top_level.txt +1 -0
snikrflow-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stodachon (redhound)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
snikrflow-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snikrflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Native binary security triage and reachability platform, built on angr and Claripy.
|
|
5
|
+
Author: Stodachon
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Stodachon (redhound)
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/stodachon/snikrflow
|
|
29
|
+
Project-URL: Repository, https://github.com/stodachon/snikrflow
|
|
30
|
+
Project-URL: Issues, https://github.com/stodachon/snikrflow/issues
|
|
31
|
+
Keywords: security,reverse-engineering,android,jni,binary-analysis,angr,symbolic-execution,triage,reachability,elf
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Information Technology
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Security
|
|
40
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: angr
|
|
45
|
+
Requires-Dist: claripy
|
|
46
|
+
Requires-Dist: pyelftools
|
|
47
|
+
Requires-Dist: typer[all]
|
|
48
|
+
Requires-Dist: rich
|
|
49
|
+
Requires-Dist: networkx
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: pytest; extra == "dev"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
<p align="center">
|
|
55
|
+
<img src="logo.svg" width="140" alt="SnikrFlow logo"/>
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
<h1 align="center">SnikrFlow</h1>
|
|
59
|
+
|
|
60
|
+
<p align="center">
|
|
61
|
+
<b>Native binary security triage and reachability platform.</b><br/>
|
|
62
|
+
Stop wondering where to start reversing a binary. Let SnikrFlow tell you.
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<p align="center">
|
|
66
|
+
<a href="https://pypi.org/project/snikrflow/"><img alt="PyPI" src="https://img.shields.io/pypi/v/snikrflow.svg"></a>
|
|
67
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-orange.svg"></a>
|
|
68
|
+
<img alt="Python" src="https://img.shields.io/badge/python-3.11%2B-blue.svg">
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## The problem
|
|
74
|
+
|
|
75
|
+
You've got a large ELF binary or an Android `.so` library. Thousands of functions.
|
|
76
|
+
No idea where the interesting stuff is. You could spend two hours blindly
|
|
77
|
+
following callers, references, and branches in Ghidra... or you could ask:
|
|
78
|
+
|
|
79
|
+
> "Before I spend two hours reversing this binary, tell me where I should
|
|
80
|
+
> spend those two hours."
|
|
81
|
+
|
|
82
|
+
That's what SnikrFlow answers.
|
|
83
|
+
|
|
84
|
+
SnikrFlow is **not** a replacement for Ghidra, IDA, Binary Ninja, or GDB. It's
|
|
85
|
+
the step *before* those tools: automated triage, reachability analysis, and
|
|
86
|
+
symbolic execution that narrows "here are thousands of functions" down to
|
|
87
|
+
"here are the paths you should investigate first."
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Large ELF / Android .so
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
SnikrFlow
|
|
94
|
+
│
|
|
95
|
+
├── ELF analysis ├── Call graph analysis
|
|
96
|
+
├── Function discovery ├── Reachability
|
|
97
|
+
├── JNI discovery ├── Data-flow analysis
|
|
98
|
+
├── Security sink detect └── Symbolic execution
|
|
99
|
+
├── CFG analysis
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
Prioritized findings
|
|
103
|
+
│
|
|
104
|
+
▼
|
|
105
|
+
Ghidra
|
|
106
|
+
│
|
|
107
|
+
▼
|
|
108
|
+
Deep manual RE
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Install
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install snikrflow
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
(Or, for development: clone the repo and `pip install -e .`)
|
|
118
|
+
|
|
119
|
+
Requires Python 3.11+. Built on [angr](https://angr.io) + Claripy for the
|
|
120
|
+
underlying static/symbolic analysis engine.
|
|
121
|
+
|
|
122
|
+
## Quick start
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# The whole pipeline, orchestrated -- this is the one you actually want
|
|
126
|
+
snikrflow audit libfoo.so
|
|
127
|
+
|
|
128
|
+
# Or drill in stage by stage:
|
|
129
|
+
snikrflow info libfoo.so # ELF metadata
|
|
130
|
+
snikrflow functions libfoo.so # discovered functions (angr CFGFast + symbols)
|
|
131
|
+
snikrflow jni libfoo.so # statically-exported JNI entry points
|
|
132
|
+
snikrflow sinks libfoo.so # security-sensitive function calls
|
|
133
|
+
snikrflow cfg libfoo.so # control-flow graph summary
|
|
134
|
+
snikrflow callers libfoo.so 0x400d80 # who calls this address
|
|
135
|
+
snikrflow callees libfoo.so 0x400d80 # what this address calls
|
|
136
|
+
snikrflow reach libfoo.so --source 0x403a00 --sink 0x400d80 # static reachability
|
|
137
|
+
snikrflow solve libfoo.so --source 0x403a00 --target 0x400d80 # symbolic candidate input
|
|
138
|
+
snikrflow dataflow libfoo.so --source 0x403a00 --sink 0x400d80 # does tainted data reach the sink?
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Every command supports `--json` for scripting/piping into other tools.
|
|
142
|
+
|
|
143
|
+
## What `audit` actually does
|
|
144
|
+
|
|
145
|
+
`audit` is the flagship command -- it runs the full pipeline automatically:
|
|
146
|
+
|
|
147
|
+
1. Discovers all functions, JNI entry points, and security-sensitive sinks.
|
|
148
|
+
2. Checks static (call-graph) reachability across every JNI-to-sink pair --
|
|
149
|
+
cheap, so it covers everything.
|
|
150
|
+
3. Ranks the statically-reachable pairs by call-chain directness and picks
|
|
151
|
+
the most promising few for **deep analysis**.
|
|
152
|
+
4. Runs data-flow (symbolic) analysis on that shortlist -- expensive, so
|
|
153
|
+
it's spent only where it matters.
|
|
154
|
+
5. Scores risk transparently and prints ranked findings.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
SnikrFlow Security Audit
|
|
158
|
+
═══════════════════════════
|
|
159
|
+
Binary
|
|
160
|
+
Architecture: x86_64
|
|
161
|
+
Type: Shared Object
|
|
162
|
+
Functions
|
|
163
|
+
294 discovered
|
|
164
|
+
3 JNI candidates
|
|
165
|
+
Security-sensitive operations
|
|
166
|
+
2 identified
|
|
167
|
+
Interesting paths
|
|
168
|
+
1 statically reachable
|
|
169
|
+
1 selected for data-flow analysis
|
|
170
|
+
Findings
|
|
171
|
+
[MEDIUM] JNI -> memory
|
|
172
|
+
Source: Java_..._MainActivity_init @ 0x403a00
|
|
173
|
+
Sink: strncpy @ 0x400d80 (memory)
|
|
174
|
+
Status: statically_reachable
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Risk levels are a **prioritization aid, not a vulnerability verdict**. Manual
|
|
178
|
+
verification is still required -- SnikrFlow narrows the search space, it
|
|
179
|
+
doesn't replace the researcher.
|
|
180
|
+
|
|
181
|
+
## Architecture
|
|
182
|
+
|
|
183
|
+
SnikrFlow is built in layers, deliberately kept independent from its analysis
|
|
184
|
+
backend:
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
CLI ──┐
|
|
188
|
+
├──> Core Engine ──> SnikrFlow Models ──> Analysis Interfaces ──> Backends
|
|
189
|
+
Future GUI ──┘ (angr today;
|
|
190
|
+
Unicorn/Pin/Dyninst
|
|
191
|
+
possible later)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **SnikrFlow owns the concepts** (`Function`, `SecuritySink`, `AnalysisPath`,
|
|
195
|
+
`SymbolicResult`, `AnalysisFinding`, ...). The CLI never touches angr
|
|
196
|
+
objects directly.
|
|
197
|
+
- **`analysis/interfaces/`** define *what* SnikrFlow can do.
|
|
198
|
+
**`analysis/backends/angr/`** define *how* angr does it today. Adding a
|
|
199
|
+
future backend means writing a new backend module, not rewriting the CLI.
|
|
200
|
+
- The sink signature database (`sinks/signatures.py`) is a plain, editable
|
|
201
|
+
Python dict -- add your own categories/functions freely.
|
|
202
|
+
|
|
203
|
+
## Terminology (read this before trusting a finding)
|
|
204
|
+
|
|
205
|
+
SnikrFlow is deliberately precise about what different results mean --
|
|
206
|
+
these are never used interchangeably:
|
|
207
|
+
|
|
208
|
+
| Term | Meaning |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `statically_reachable` | A direct call-graph path exists. Doesn't see indirect calls (function pointers, vtables). |
|
|
211
|
+
| `SAT` (solve) | A concrete candidate input exists that walks this exact path. Not a confirmed exploit. |
|
|
212
|
+
| `POSSIBLE` (dataflow) | Symbolic input appears to reach the sink's argument. Heuristic, not sound taint analysis. |
|
|
213
|
+
| Risk level (audit) | A prioritization signal. Never a confirmed vulnerability. |
|
|
214
|
+
|
|
215
|
+
## Responsible use
|
|
216
|
+
|
|
217
|
+
SnikrFlow is for authorized security research only -- use it against binaries
|
|
218
|
+
you own or have explicit permission to analyze.
|
|
219
|
+
|
|
220
|
+
## Roadmap
|
|
221
|
+
|
|
222
|
+
- [x] M1 -- ELF foundation (`info`, `functions`)
|
|
223
|
+
- [x] M2 -- Security triage (`jni`, `sinks`)
|
|
224
|
+
- [x] M3 -- Program structure (`cfg`, `callers`, `callees`)
|
|
225
|
+
- [x] M4 -- Reachability (`reach`)
|
|
226
|
+
- [x] M5 -- Symbolic execution (`solve`)
|
|
227
|
+
- [x] M6 -- Data flow (`dataflow`)
|
|
228
|
+
- [x] M7 -- Audit engine (`audit`)
|
|
229
|
+
- [ ] M8 -- Android APK support (`audit app.apk`)
|
|
230
|
+
- [ ] M9 -- Advanced analysis (better taint tracking, caching, SARIF)
|
|
231
|
+
- [ ] M10 -- GUI (presentation layer over the same engine, no duplicated logic)
|
|
232
|
+
|
|
233
|
+
## Credits
|
|
234
|
+
|
|
235
|
+
Built on [angr](https://angr.io) and Claripy. CLI powered by
|
|
236
|
+
[Typer](https://typer.tiangolo.com) and [Rich](https://rich.readthedocs.io).
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" width="140" alt="SnikrFlow logo"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">SnikrFlow</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Native binary security triage and reachability platform.</b><br/>
|
|
9
|
+
Stop wondering where to start reversing a binary. Let SnikrFlow tell you.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://pypi.org/project/snikrflow/"><img alt="PyPI" src="https://img.shields.io/pypi/v/snikrflow.svg"></a>
|
|
14
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-orange.svg"></a>
|
|
15
|
+
<img alt="Python" src="https://img.shields.io/badge/python-3.11%2B-blue.svg">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The problem
|
|
21
|
+
|
|
22
|
+
You've got a large ELF binary or an Android `.so` library. Thousands of functions.
|
|
23
|
+
No idea where the interesting stuff is. You could spend two hours blindly
|
|
24
|
+
following callers, references, and branches in Ghidra... or you could ask:
|
|
25
|
+
|
|
26
|
+
> "Before I spend two hours reversing this binary, tell me where I should
|
|
27
|
+
> spend those two hours."
|
|
28
|
+
|
|
29
|
+
That's what SnikrFlow answers.
|
|
30
|
+
|
|
31
|
+
SnikrFlow is **not** a replacement for Ghidra, IDA, Binary Ninja, or GDB. It's
|
|
32
|
+
the step *before* those tools: automated triage, reachability analysis, and
|
|
33
|
+
symbolic execution that narrows "here are thousands of functions" down to
|
|
34
|
+
"here are the paths you should investigate first."
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Large ELF / Android .so
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
SnikrFlow
|
|
41
|
+
│
|
|
42
|
+
├── ELF analysis ├── Call graph analysis
|
|
43
|
+
├── Function discovery ├── Reachability
|
|
44
|
+
├── JNI discovery ├── Data-flow analysis
|
|
45
|
+
├── Security sink detect └── Symbolic execution
|
|
46
|
+
├── CFG analysis
|
|
47
|
+
│
|
|
48
|
+
▼
|
|
49
|
+
Prioritized findings
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
Ghidra
|
|
53
|
+
│
|
|
54
|
+
▼
|
|
55
|
+
Deep manual RE
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install snikrflow
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
(Or, for development: clone the repo and `pip install -e .`)
|
|
65
|
+
|
|
66
|
+
Requires Python 3.11+. Built on [angr](https://angr.io) + Claripy for the
|
|
67
|
+
underlying static/symbolic analysis engine.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# The whole pipeline, orchestrated -- this is the one you actually want
|
|
73
|
+
snikrflow audit libfoo.so
|
|
74
|
+
|
|
75
|
+
# Or drill in stage by stage:
|
|
76
|
+
snikrflow info libfoo.so # ELF metadata
|
|
77
|
+
snikrflow functions libfoo.so # discovered functions (angr CFGFast + symbols)
|
|
78
|
+
snikrflow jni libfoo.so # statically-exported JNI entry points
|
|
79
|
+
snikrflow sinks libfoo.so # security-sensitive function calls
|
|
80
|
+
snikrflow cfg libfoo.so # control-flow graph summary
|
|
81
|
+
snikrflow callers libfoo.so 0x400d80 # who calls this address
|
|
82
|
+
snikrflow callees libfoo.so 0x400d80 # what this address calls
|
|
83
|
+
snikrflow reach libfoo.so --source 0x403a00 --sink 0x400d80 # static reachability
|
|
84
|
+
snikrflow solve libfoo.so --source 0x403a00 --target 0x400d80 # symbolic candidate input
|
|
85
|
+
snikrflow dataflow libfoo.so --source 0x403a00 --sink 0x400d80 # does tainted data reach the sink?
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Every command supports `--json` for scripting/piping into other tools.
|
|
89
|
+
|
|
90
|
+
## What `audit` actually does
|
|
91
|
+
|
|
92
|
+
`audit` is the flagship command -- it runs the full pipeline automatically:
|
|
93
|
+
|
|
94
|
+
1. Discovers all functions, JNI entry points, and security-sensitive sinks.
|
|
95
|
+
2. Checks static (call-graph) reachability across every JNI-to-sink pair --
|
|
96
|
+
cheap, so it covers everything.
|
|
97
|
+
3. Ranks the statically-reachable pairs by call-chain directness and picks
|
|
98
|
+
the most promising few for **deep analysis**.
|
|
99
|
+
4. Runs data-flow (symbolic) analysis on that shortlist -- expensive, so
|
|
100
|
+
it's spent only where it matters.
|
|
101
|
+
5. Scores risk transparently and prints ranked findings.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
SnikrFlow Security Audit
|
|
105
|
+
═══════════════════════════
|
|
106
|
+
Binary
|
|
107
|
+
Architecture: x86_64
|
|
108
|
+
Type: Shared Object
|
|
109
|
+
Functions
|
|
110
|
+
294 discovered
|
|
111
|
+
3 JNI candidates
|
|
112
|
+
Security-sensitive operations
|
|
113
|
+
2 identified
|
|
114
|
+
Interesting paths
|
|
115
|
+
1 statically reachable
|
|
116
|
+
1 selected for data-flow analysis
|
|
117
|
+
Findings
|
|
118
|
+
[MEDIUM] JNI -> memory
|
|
119
|
+
Source: Java_..._MainActivity_init @ 0x403a00
|
|
120
|
+
Sink: strncpy @ 0x400d80 (memory)
|
|
121
|
+
Status: statically_reachable
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Risk levels are a **prioritization aid, not a vulnerability verdict**. Manual
|
|
125
|
+
verification is still required -- SnikrFlow narrows the search space, it
|
|
126
|
+
doesn't replace the researcher.
|
|
127
|
+
|
|
128
|
+
## Architecture
|
|
129
|
+
|
|
130
|
+
SnikrFlow is built in layers, deliberately kept independent from its analysis
|
|
131
|
+
backend:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
CLI ──┐
|
|
135
|
+
├──> Core Engine ──> SnikrFlow Models ──> Analysis Interfaces ──> Backends
|
|
136
|
+
Future GUI ──┘ (angr today;
|
|
137
|
+
Unicorn/Pin/Dyninst
|
|
138
|
+
possible later)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **SnikrFlow owns the concepts** (`Function`, `SecuritySink`, `AnalysisPath`,
|
|
142
|
+
`SymbolicResult`, `AnalysisFinding`, ...). The CLI never touches angr
|
|
143
|
+
objects directly.
|
|
144
|
+
- **`analysis/interfaces/`** define *what* SnikrFlow can do.
|
|
145
|
+
**`analysis/backends/angr/`** define *how* angr does it today. Adding a
|
|
146
|
+
future backend means writing a new backend module, not rewriting the CLI.
|
|
147
|
+
- The sink signature database (`sinks/signatures.py`) is a plain, editable
|
|
148
|
+
Python dict -- add your own categories/functions freely.
|
|
149
|
+
|
|
150
|
+
## Terminology (read this before trusting a finding)
|
|
151
|
+
|
|
152
|
+
SnikrFlow is deliberately precise about what different results mean --
|
|
153
|
+
these are never used interchangeably:
|
|
154
|
+
|
|
155
|
+
| Term | Meaning |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `statically_reachable` | A direct call-graph path exists. Doesn't see indirect calls (function pointers, vtables). |
|
|
158
|
+
| `SAT` (solve) | A concrete candidate input exists that walks this exact path. Not a confirmed exploit. |
|
|
159
|
+
| `POSSIBLE` (dataflow) | Symbolic input appears to reach the sink's argument. Heuristic, not sound taint analysis. |
|
|
160
|
+
| Risk level (audit) | A prioritization signal. Never a confirmed vulnerability. |
|
|
161
|
+
|
|
162
|
+
## Responsible use
|
|
163
|
+
|
|
164
|
+
SnikrFlow is for authorized security research only -- use it against binaries
|
|
165
|
+
you own or have explicit permission to analyze.
|
|
166
|
+
|
|
167
|
+
## Roadmap
|
|
168
|
+
|
|
169
|
+
- [x] M1 -- ELF foundation (`info`, `functions`)
|
|
170
|
+
- [x] M2 -- Security triage (`jni`, `sinks`)
|
|
171
|
+
- [x] M3 -- Program structure (`cfg`, `callers`, `callees`)
|
|
172
|
+
- [x] M4 -- Reachability (`reach`)
|
|
173
|
+
- [x] M5 -- Symbolic execution (`solve`)
|
|
174
|
+
- [x] M6 -- Data flow (`dataflow`)
|
|
175
|
+
- [x] M7 -- Audit engine (`audit`)
|
|
176
|
+
- [ ] M8 -- Android APK support (`audit app.apk`)
|
|
177
|
+
- [ ] M9 -- Advanced analysis (better taint tracking, caching, SARIF)
|
|
178
|
+
- [ ] M10 -- GUI (presentation layer over the same engine, no duplicated logic)
|
|
179
|
+
|
|
180
|
+
## Credits
|
|
181
|
+
|
|
182
|
+
Built on [angr](https://angr.io) and Claripy. CLI powered by
|
|
183
|
+
[Typer](https://typer.tiangolo.com) and [Rich](https://rich.readthedocs.io).
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "snikrflow"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Native binary security triage and reachability platform, built on angr and Claripy."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Stodachon" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"security", "reverse-engineering", "android", "jni", "binary-analysis",
|
|
17
|
+
"angr", "symbolic-execution", "triage", "reachability", "elf",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Environment :: Console",
|
|
22
|
+
"Intended Audience :: Information Technology",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Security",
|
|
28
|
+
"Topic :: Software Development :: Disassemblers",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"angr",
|
|
32
|
+
"claripy",
|
|
33
|
+
"pyelftools",
|
|
34
|
+
"typer[all]",
|
|
35
|
+
"rich",
|
|
36
|
+
"networkx",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = ["pytest"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/stodachon/snikrflow"
|
|
44
|
+
Repository = "https://github.com/stodachon/snikrflow"
|
|
45
|
+
Issues = "https://github.com/stodachon/snikrflow/issues"
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
snikrflow = "snikrflow.cli:app"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
include = ["snikrflow*"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SnikrFlow angr backend -- audit orchestration.
|
|
3
|
+
|
|
4
|
+
Combines every prior analysis stage into one pipeline, per the doc's
|
|
5
|
+
performance philosophy: cheap triage on everything, expensive symbolic/
|
|
6
|
+
data-flow analysis only on the most promising candidates.
|
|
7
|
+
|
|
8
|
+
Pipeline:
|
|
9
|
+
1. Discover functions, JNI candidates, sinks (cheap -- one CFGFast run,
|
|
10
|
+
reused across all three thanks to the caching guard in functions.py).
|
|
11
|
+
2. For every (JNI entry, sink) pair, check STATIC call-graph
|
|
12
|
+
reachability (cheap -- no symbolic execution).
|
|
13
|
+
3. Rank the statically-reachable pairs by call-chain length (shorter =
|
|
14
|
+
more direct = higher priority) and take the top N for deep analysis.
|
|
15
|
+
4. Run data-flow analysis (symbolic, bounded by a short per-pair
|
|
16
|
+
timeout) ONLY on that top-N subset.
|
|
17
|
+
5. Score risk transparently (see _score_risk) and sort findings by risk.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from snikrflow.analysis.backends.angr.dataflow import check_data_flow
|
|
25
|
+
from snikrflow.analysis.backends.angr.functions import discover_functions
|
|
26
|
+
from snikrflow.analysis.backends.angr.jni import discover_jni_functions
|
|
27
|
+
from snikrflow.analysis.backends.angr.reachability import check_static_reachability
|
|
28
|
+
from snikrflow.analysis.backends.angr.sinks import discover_sinks
|
|
29
|
+
from snikrflow.core.models import (
|
|
30
|
+
AnalysisFinding,
|
|
31
|
+
AuditResult,
|
|
32
|
+
DataFlowStatus,
|
|
33
|
+
ReachabilityType,
|
|
34
|
+
RiskLevel,
|
|
35
|
+
)
|
|
36
|
+
from snikrflow.elf.loader import LoadedBinary
|
|
37
|
+
from snikrflow.elf.metadata import extract_binary_info
|
|
38
|
+
|
|
39
|
+
# Cap on how many statically-reachable pairs get the expensive data-flow
|
|
40
|
+
# treatment. Keeps `audit` bounded on binaries with many JNI x sink
|
|
41
|
+
# combinations -- deep analysis is a scarce resource, spent on the most
|
|
42
|
+
# direct (shortest) paths first.
|
|
43
|
+
MAX_DEEP_ANALYSIS_PAIRS = 5
|
|
44
|
+
DEFAULT_DEEP_TIMEOUT_SECONDS = 30
|
|
45
|
+
|
|
46
|
+
# Rough, documented, and DELIBERATELY simple severity weighting per sink
|
|
47
|
+
# category. This is a transparent prioritization heuristic, not a
|
|
48
|
+
# vulnerability database -- edit freely.
|
|
49
|
+
_CATEGORY_SEVERITY = {
|
|
50
|
+
"process_execution": 3,
|
|
51
|
+
"dynamic_loading": 3,
|
|
52
|
+
"memory": 2,
|
|
53
|
+
"networking": 2,
|
|
54
|
+
"filesystem": 1,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _score_risk(
|
|
59
|
+
reachability_type: ReachabilityType,
|
|
60
|
+
data_flow_status: Optional[DataFlowStatus],
|
|
61
|
+
category: str,
|
|
62
|
+
) -> RiskLevel:
|
|
63
|
+
severity = _CATEGORY_SEVERITY.get(category, 1)
|
|
64
|
+
|
|
65
|
+
if reachability_type != ReachabilityType.STATICALLY_REACHABLE:
|
|
66
|
+
return RiskLevel.LOW
|
|
67
|
+
|
|
68
|
+
if data_flow_status == DataFlowStatus.POSSIBLE:
|
|
69
|
+
return RiskLevel.CRITICAL if severity >= 3 else RiskLevel.HIGH
|
|
70
|
+
|
|
71
|
+
# Reachable via call graph, but data flow wasn't confirmed possible
|
|
72
|
+
# (either not checked, or checked and came back inconclusive).
|
|
73
|
+
return RiskLevel.HIGH if severity >= 3 else RiskLevel.MEDIUM
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_audit(binary: LoadedBinary, deep_timeout_seconds: int = DEFAULT_DEEP_TIMEOUT_SECONDS) -> AuditResult:
|
|
77
|
+
binary_info = extract_binary_info(binary)
|
|
78
|
+
|
|
79
|
+
all_functions = discover_functions(binary) # runs CFGFast once
|
|
80
|
+
jni_functions = discover_jni_functions(binary) # reuses it
|
|
81
|
+
sinks = discover_sinks(binary) # reuses it
|
|
82
|
+
|
|
83
|
+
# Step 1: cheap static reachability across every JNI x sink pair.
|
|
84
|
+
reachable_pairs = []
|
|
85
|
+
for jni_func in jni_functions:
|
|
86
|
+
for sink in sinks:
|
|
87
|
+
path = check_static_reachability(binary, jni_func.address, sink.address)
|
|
88
|
+
if path.reachable and path.reachability_type == ReachabilityType.STATICALLY_REACHABLE:
|
|
89
|
+
reachable_pairs.append((jni_func, sink, path))
|
|
90
|
+
|
|
91
|
+
# Step 2: prioritize shortest (most direct) call chains for deep analysis.
|
|
92
|
+
reachable_pairs.sort(key=lambda item: len(item[2].function_path))
|
|
93
|
+
deep_pairs = reachable_pairs[:MAX_DEEP_ANALYSIS_PAIRS]
|
|
94
|
+
deep_keys = {(j.address, s.address) for j, s, _ in deep_pairs}
|
|
95
|
+
|
|
96
|
+
# Step 3: build findings, running data-flow only on the selected subset.
|
|
97
|
+
findings: list[AnalysisFinding] = []
|
|
98
|
+
for jni_func, sink, path in reachable_pairs:
|
|
99
|
+
data_flow_status: Optional[DataFlowStatus] = None
|
|
100
|
+
confidence = path.confidence
|
|
101
|
+
notes: list[str] = [path.note] if path.note else []
|
|
102
|
+
|
|
103
|
+
if (jni_func.address, sink.address) in deep_keys:
|
|
104
|
+
try:
|
|
105
|
+
df_result = check_data_flow(
|
|
106
|
+
binary, jni_func.address, sink.address,
|
|
107
|
+
timeout_seconds=deep_timeout_seconds,
|
|
108
|
+
)
|
|
109
|
+
data_flow_status = df_result.status
|
|
110
|
+
confidence = df_result.confidence
|
|
111
|
+
if df_result.note:
|
|
112
|
+
notes.append(df_result.note)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
notes.append(f"Data-flow analysis failed: {e}")
|
|
115
|
+
|
|
116
|
+
risk = _score_risk(path.reachability_type, data_flow_status, sink.category)
|
|
117
|
+
|
|
118
|
+
findings.append(
|
|
119
|
+
AnalysisFinding(
|
|
120
|
+
title=f"JNI -> {sink.category}",
|
|
121
|
+
source_address=jni_func.address,
|
|
122
|
+
source_name=jni_func.name,
|
|
123
|
+
sink_address=sink.address,
|
|
124
|
+
sink_name=sink.name,
|
|
125
|
+
sink_category=sink.category,
|
|
126
|
+
reachability_type=path.reachability_type,
|
|
127
|
+
data_flow_status=data_flow_status,
|
|
128
|
+
risk_level=risk,
|
|
129
|
+
confidence=confidence,
|
|
130
|
+
notes=notes,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
risk_order = {RiskLevel.CRITICAL: 0, RiskLevel.HIGH: 1, RiskLevel.MEDIUM: 2, RiskLevel.LOW: 3}
|
|
135
|
+
findings.sort(key=lambda f: risk_order.get(f.risk_level, 4))
|
|
136
|
+
|
|
137
|
+
return AuditResult(
|
|
138
|
+
binary_info=binary_info,
|
|
139
|
+
function_count=len(all_functions),
|
|
140
|
+
jni_count=len(jni_functions),
|
|
141
|
+
sink_count=len(sinks),
|
|
142
|
+
interesting_path_count=len(reachable_pairs),
|
|
143
|
+
symbolic_analysis_count=len(deep_pairs),
|
|
144
|
+
findings=findings,
|
|
145
|
+
)
|