lcsajdump 1.0.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.
- lcsajdump-1.0.0/LICENSE +7 -0
- lcsajdump-1.0.0/MANIFEST.in +21 -0
- lcsajdump-1.0.0/PKG-INFO +230 -0
- lcsajdump-1.0.0/README.md +200 -0
- lcsajdump-1.0.0/lcsajdump/__init__.py +0 -0
- lcsajdump-1.0.0/lcsajdump/cli.py +57 -0
- lcsajdump-1.0.0/lcsajdump/core/__init__.py +0 -0
- lcsajdump-1.0.0/lcsajdump/core/graph.py +82 -0
- lcsajdump-1.0.0/lcsajdump/core/loader.py +100 -0
- lcsajdump-1.0.0/lcsajdump/core/rainbowBFS.py +115 -0
- lcsajdump-1.0.0/lcsajdump.egg-info/SOURCES.txt +11 -0
- lcsajdump-1.0.0/requirements.txt +4 -0
- lcsajdump-1.0.0/setup.cfg +4 -0
- lcsajdump-1.0.0/setup.py +45 -0
lcsajdump-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Chris1sflaggin🅭
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE
|
|
3
|
+
include requirements.txt
|
|
4
|
+
|
|
5
|
+
graft lcsajdump
|
|
6
|
+
|
|
7
|
+
prune testCTFs
|
|
8
|
+
prune unitTest
|
|
9
|
+
prune _images
|
|
10
|
+
|
|
11
|
+
prune build
|
|
12
|
+
prune lcsajdump.egg-info
|
|
13
|
+
|
|
14
|
+
prune lcsajdump/venv
|
|
15
|
+
|
|
16
|
+
global-exclude *.pyc
|
|
17
|
+
global-exclude __pycache__
|
|
18
|
+
global-exclude *.so
|
|
19
|
+
global-exclude .DS_Store
|
|
20
|
+
global-exclude gadgets_found.txt
|
|
21
|
+
global-exclude PAPER.md
|
lcsajdump-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lcsajdump
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Graph-Based ROP Gadget Finder for RISC-V architectures
|
|
5
|
+
Home-page: https://chris1sflaggin.it/LCSAJdump/
|
|
6
|
+
Author: Chris1sFlaggin
|
|
7
|
+
Author-email: lcsajdump@chris1sflaggin.it
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Requires-Python: >=3.6
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: capstone
|
|
17
|
+
Requires-Dist: pyelftools
|
|
18
|
+
Requires-Dist: networkx
|
|
19
|
+
Requires-Dist: click
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
<div id="top">
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
<img src="_images/LOGO.png" width="60%" style="position: relative; top: 0; right: 0;" alt="Project Logo"/>
|
|
36
|
+
|
|
37
|
+
# LCSAJdump
|
|
38
|
+
|
|
39
|
+
<em>LCSAJDump: A Graph-Based Framework for Automated Gadget Discovery in RISC-V Environments.</em>
|
|
40
|
+
|
|
41
|
+
<img src="https://img.shields.io/badge/status-Thesis_Prototype-orange?style=for-the-badge" alt="Status">
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Table of Contents
|
|
46
|
+
|
|
47
|
+
- [Overview](#overview)
|
|
48
|
+
- [Features](#features)
|
|
49
|
+
- [Project Structure](#project-structure)
|
|
50
|
+
- [Project Index](#project-index)
|
|
51
|
+
- [Getting Started](#getting-started)
|
|
52
|
+
- [Prerequisites](#prerequisites)
|
|
53
|
+
- [Installation](#installation)
|
|
54
|
+
- [Usage](#usage)
|
|
55
|
+
- [Testing](#testing)
|
|
56
|
+
- [Roadmap](#roadmap)
|
|
57
|
+
- [Contributing](#contributing)
|
|
58
|
+
- [License](#license)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Overview
|
|
63
|
+
|
|
64
|
+
LCSAJdump is a static analysis framework designed to discover Return-Oriented Programming (ROP) and Jump-Oriented Programming (JOP) gadgets within RISC-V binaries.
|
|
65
|
+
|
|
66
|
+
Traditional ROP scanners typically employ a linear, sliding-window approach over raw executable bytes. While effective for standard instruction sequences, this method fails to identify **Shadow Gadgets**—executable chains that span non-contiguous memory blocks connected by unconditional jumps or conditional branches.
|
|
67
|
+
|
|
68
|
+
LCSAJdump overcomes this limitation by reconstructing the Control-Flow Graph (CFG) through **Linear Code Sequence and Jump (LCSAJ)** analysis. By modeling the binary as a directed graph of basic blocks, the tool identifies:
|
|
69
|
+
|
|
70
|
+
1. **Contiguous Gadgets:** Standard linear sequences terminating in a control-flow transfer.
|
|
71
|
+
2. **Non-Contiguous (Shadow) Gadgets:** Complex chains traversing multiple basic blocks, effectively bypassing "bad bytes" (e.g., null bytes) and utilizing instructions that would otherwise be unreachable by linear scanning.
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
* **Comprehensive Architecture Support:** Full support for RISC-V 64-bit (RV64) and Compressed (C) extensions. Handling 16-bit compressed instructions is critical for maximizing gadget coverage in modern RISC-V binaries.
|
|
76
|
+
* **Graph-Based Reconstruction:** The engine segments the `.text` section into basic blocks based on control-flow transfers (jumps, branches, returns) and reconstructs edges for both fallthrough and direct targets using NetworkX.
|
|
77
|
+
* **Heuristic Backward Search:** Implements a specialized backward Breadth-First Search (BFS) algorithm starting from control-flow sinks (`ret`, `jr`, `jalr`) to reconstruct valid execution paths in reverse.
|
|
78
|
+
* **Hybrid Discovery:** Capable of identifying both standard linear gadgets and complex, multi-block trampoline gadgets in a single pass.
|
|
79
|
+
* **Scoring and Classification:** Includes a heuristic scoring system that prioritizes gadgets involving critical registers (`ra`, `a0`, `sp`) and classifies results into functional categories (Linear, Trampoline, Conditional, Fallthrough).
|
|
80
|
+
* **Optimized Performance:** Features configurable pruning parameters ("Darkness" factor) to limit the search depth and node visitation, balancing analysis speed with coverage depth.
|
|
81
|
+
|
|
82
|
+
## Project Structure
|
|
83
|
+
|
|
84
|
+
The repository is organized into modular components responsible for binary loading, graph generation, and algorithmic search.
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
LCSAJdump/
|
|
88
|
+
├── loader.py # ELF parsing and Capstone disassembly wrapper
|
|
89
|
+
├── graph.py # LCSAJ basic block decomposition and DiGraph construction
|
|
90
|
+
├── rainbowBFS.py # Backward search algorithm, scoring, and classification logic
|
|
91
|
+
├── LCSAJdump.py # Main entry point and CLI argument parsing
|
|
92
|
+
└── utils.py # Helper functions for formatting and logging
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Project Index
|
|
97
|
+
|
|
98
|
+
* **`loader.py`**: Utilizes `pyelftools` to extract executable sections and `Capstone` to disassemble RV64GC instructions into a linear stream.
|
|
99
|
+
* **`graph.py`**: Converts the linear instruction stream into a directed graph. Nodes represent basic blocks (LCSAJs), and edges represent control flow (jumps, branches, and fallthroughs).
|
|
100
|
+
* **`rainbowBFS.py`**: The core analysis engine. It traverses the reverse graph from leaf nodes (returns) to find executable paths, applying heuristic scoring to filter non-viable chains.
|
|
101
|
+
* **`LCSAJdump.py`**: Orchestrates the analysis pipeline, handling user input, parameter tuning, and output generation.
|
|
102
|
+
|
|
103
|
+
## Getting Started
|
|
104
|
+
|
|
105
|
+
### Prerequisites
|
|
106
|
+
|
|
107
|
+
All of them listed in `requirements.txt`:
|
|
108
|
+
* Python 3.8 or higher
|
|
109
|
+
* `capstone` (Disassembly engine)
|
|
110
|
+
* `networkx` (Graph algorithms)
|
|
111
|
+
* `pyelftools` (ELF file parsing)
|
|
112
|
+
|
|
113
|
+
### Installation
|
|
114
|
+
|
|
115
|
+
#### GitHub
|
|
116
|
+
|
|
117
|
+
Clone the repository and install the required dependencies:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone [https://github.com/Chris1sFlaggin/LCSAJdump.git](https://github.com/Chris1sFlaggin/LCSAJdump.git)
|
|
121
|
+
cd LCSAJdump
|
|
122
|
+
pip install -r requirements.txt
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Pip
|
|
127
|
+
|
|
128
|
+
```zsh
|
|
129
|
+
pip install lcsajdump
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Usage
|
|
133
|
+
|
|
134
|
+
**Basic Scan**
|
|
135
|
+
Run the tool on a target binary using default parameters:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python LCSAJdump.py <path_to_binary>
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Advanced Configuration**
|
|
143
|
+
Users can tune the search depth and pruning thresholds to handle larger binaries or deeper gadget chains:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python LCSAJdump.py -d 15 -k 100 -l 20 --verbose <path_to_binary>
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**CLI Options:**
|
|
151
|
+
|
|
152
|
+
* `-d, --depth`: Maximum search depth (in blocks) for the BFS algorithm.
|
|
153
|
+
* `-k, --darkness`: Pruning threshold (maximum visits per node) to prevent infinite loops in cyclic graphs.
|
|
154
|
+
* `-l, --limit`: Maximum number of top-ranked gadgets to display.
|
|
155
|
+
* `-s, --min-score`: Minimum heuristic score threshold for reporting.
|
|
156
|
+
* `-v, --verbose`: Enable detailed output of instruction decoding.
|
|
157
|
+
|
|
158
|
+
### Testing
|
|
159
|
+
|
|
160
|
+
To verify the integrity of the graph reconstruction and gadget finding logic, run the unit tests provided in the `tests/` directory:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python -m pytest unitTest/*
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Output Example
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
❯ time python LCSAJdump/LCSAJdump.py testCTFs/rop/vuln
|
|
172
|
+
[*] Analisi Target: testCTFs/rop/vuln
|
|
173
|
+
[*] Caricamento binario: testCTFs/rop/vuln
|
|
174
|
+
[*] Sezione .text trovata.
|
|
175
|
+
Dimensione: 258236 bytes
|
|
176
|
+
Indirizzo Base: 0x10250
|
|
177
|
+
|
|
178
|
+
[*] Avvio disassemblaggio con Capstone...
|
|
179
|
+
Disassembling [████████████████████████████████████████████████████████████] 100.0%
|
|
180
|
+
[*] Disassemblaggio completato. 89354 istruzioni estratte.
|
|
181
|
+
|
|
182
|
+
[*] Costruzione Nodi LCSAJ...
|
|
183
|
+
Building Graph [████████████████████████████████████████████████████████████] 100.0%
|
|
184
|
+
|
|
185
|
+
[*] Configurazione Rainbow: Depth=12, Darkness=30
|
|
186
|
+
[*] Pruning effettuato: 0 rami tagliati.
|
|
187
|
+
|
|
188
|
+
============================================================
|
|
189
|
+
--- TOP 10 SEQUENTIAL GADGETS ---
|
|
190
|
+
============================================================
|
|
191
|
+
0x39ffe: c.ldsp ra, 0x48(sp); c.ldsp a0, 0x38(sp); c.addi16sp sp, 0x50; c.jr ra
|
|
192
|
+
0x4078c: c.ldsp a0, 0x20(sp); c.ldsp ra, 0x58(sp); c.addi16sp sp, 0x60; c.jr ra
|
|
193
|
+
0x45d2e: c.ld a0, 0x10(a5); c.ldsp ra, 0x38(sp); c.addi16sp sp, 0x40; c.jr ra
|
|
194
|
+
0x460b8: c.ldsp ra, 0x38(sp); c.ldsp a0, 0x20(sp); c.addi16sp sp, 0x40; c.jr ra
|
|
195
|
+
0x460e6: c.ldsp ra, 0x38(sp); c.ldsp a0, 0x20(sp); c.addi16sp sp, 0x40; c.jr ra
|
|
196
|
+
0x4618c: c.ldsp ra, 0x28(sp); c.ldsp a0, 0x10(sp); c.addi16sp sp, 0x30; c.jr ra
|
|
197
|
+
0x461b6: c.ldsp ra, 0x28(sp); c.ldsp a0, 0x10(sp); c.addi16sp sp, 0x30; c.jr ra
|
|
198
|
+
0x46386: c.ldsp a0, 0(sp); c.ldsp ra, 0x18(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
199
|
+
0x4aa1a: c.ldsp a0, 0x18(sp); c.ldsp ra, 0x28(sp); c.addi16sp sp, 0x30; c.jr ra
|
|
200
|
+
0x113be: c.ldsp a0, 8(sp); c.ldsp ra, 0x18(sp); c.sw s0, 0x70(a0); c.ldsp s0, 0x10(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
201
|
+
|
|
202
|
+
============================================================
|
|
203
|
+
--- TOP 10 JUMP-BASED GADGETS ---
|
|
204
|
+
============================================================
|
|
205
|
+
0x4060a: c.ld a0, 0x18(s0); jal -0x27b12; c.ldsp a1, 8(sp); addi a7, zero, 0x87; c.li a0, 2; c.li a2, 0; c.li a3, 8; ecall ; c.ldsp ra, 0x18(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
206
|
+
0x46fc0: ld s8, -0x500(s0); ld a0, -0x480(s0); ld a6, -0x4f8(s0); beq a0, s8, 0x10; sd a6, -0x4c8(s0); jal -0x2e4da; c.sd a4, 0x28(a5); c.ldsp ra, 0x78(sp); c.ldsp s8, 0x30(sp); c.addi16sp sp, 0x80; c.jr ra
|
|
207
|
+
0x2b9f4: auipc a3, 0x4e; ld a3, 0x504(a3); addi a2, sp, 0x4e0; c.mv a1, a2; c.add a3, tp; c.ld a0, 0(a3); jal 0x13ace; c.lw a4, 0(a5); c.andi a4, -0x11; c.sw a4, 0(a5); c.ldsp ra, 0x18(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
208
|
+
0x4146a: ld a0, 8(s10); jal -0x28974; c.mv a0, t3; bltz t3, 0x22e; c.ldsp s0, 0x50(sp); c.ldsp s1, 0x48(sp); c.ldsp s3, 0x38(sp); c.ldsp ra, 0x58(sp); c.ldsp s2, 0x40(sp); c.ldsp s4, 0x30(sp); c.ldsp s5, 0x28(sp); c.addi16sp sp, 0x60; c.jr ra
|
|
209
|
+
0x46224: c.addi16sp sp, -0x30; c.sdsp a0, 0(sp); auipc a0, 0x34; ld a0, -0x328(a0); c.sdsp ra, 0x28(sp); c.sdsp s0, 0x20(sp); c.sdsp a1, 8(sp); c.sdsp ra, 0x10(sp); jal -0x16152; c.ldsp ra, 0x18(sp); c.ldsp s0, 0x10(sp); c.li a0, -1; c.addi16sp sp, 0x20; c.jr ra
|
|
210
|
+
0x35a06: ld a0, 0x360(s1); c.li a5, -1; beq a0, a5, 8; jal -0x1cf16; c.ld a2, 0x58(s0); c.lw a1, 0x60(s0); auipc a0, 0x34; addi a0, a0, -0x1a; addi s0, s0, 0x80; jal 0x1b864; c.ldsp ra, 0x88(sp); c.ldsp s0, 0x80(sp); c.ldsp s1, 0x78(sp); c.addi16sp sp, 0x90; c.jr ra
|
|
211
|
+
0x46366: c.ld a0, 0x10(a0); c.sdsp a5, 8(sp); c.sdsp a4, 0(sp); jal -0x1245a; add a4, a2, a1; c.lw a3, 0(a5); c.sd a2, 0x18(a5); c.sd a4, 8(a5); c.andi a3, -0x11; c.sd a4, 0x10(a5); c.sd a0, 0x90(a5); c.sw a3, 0(a5); c.ldsp ra, 0x28(sp); c.mv a0, a1; c.addi16sp sp, 0x30; c.jr ra
|
|
212
|
+
0x1db48: c.ld a0, 8(s0); c.ldsp s0, 0x10(sp); c.ld a1, 8(s1); c.ldsp ra, 0x18(sp); c.ldsp s1, 8(sp); c.addi16sp sp, 0x20; j 0x141fe; c.lw a4, 0(a5); c.ld a3, 8(a5); andi a4, a4, 0x100; c.bnez a4, 0xe; c.ld a5, 0x18(a5); c.lw a0, 0x10(a0); sub a5, a3, a5; c.subw a0, a5; c.jr ra
|
|
213
|
+
0x4145e: lw a5, 0(s6); andi a5, a5, 0x40; bnez a5, 0x2c0; ld a0, 8(s10); jal -0x28974; c.mv a0, t3; bltz t3, 0x22e; c.ldsp s0, 0x50(sp); c.ldsp s1, 0x48(sp); c.ldsp s3, 0x38(sp); c.ldsp ra, 0x58(sp); c.ldsp s2, 0x40(sp); c.ldsp s4, 0x30(sp); c.ldsp s5, 0x28(sp); c.addi16sp sp, 0x60; c.jr ra
|
|
214
|
+
0x2bd72: c.mv a2, s8; addi a1, zero, 0x20; c.mv a0, s0; jal 0x109e8; slli a0, a4, 5; c.addi a0, 0x10; c.addi a4, 1; c.add a0, a2; c.sd a4, 8(a2); ld a5, 0xa0(gp); c.li a4, 1; c.ldsp ra, 0x18(sp); c.sd a4, 0(a0); c.add a5, a4; sd a5, 0xa0(gp); c.addi16sp sp, 0x20; c.jr ra
|
|
215
|
+
|
|
216
|
+
[+] Report salvato in: gadgets_found.txt (Trovati 4637 gadget)
|
|
217
|
+
python LCSAJdump/LCSAJdump.py testCTFs/rop/vuln 1,67s user 0,27s system 99% cpu 1,936 total
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Contributing
|
|
223
|
+
|
|
224
|
+
Contributions to improve the search algorithm or extend architecture support are welcome. Please ensure that any pull requests include relevant test cases and adhere to the existing coding standards.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
This project is licensed under the MIT License. See the [LICENSE](https://www.google.com/search?q=LICENSE) file for details.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<div id="top">
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<img src="_images/LOGO.png" width="60%" style="position: relative; top: 0; right: 0;" alt="Project Logo"/>
|
|
6
|
+
|
|
7
|
+
# LCSAJdump
|
|
8
|
+
|
|
9
|
+
<em>LCSAJDump: A Graph-Based Framework for Automated Gadget Discovery in RISC-V Environments.</em>
|
|
10
|
+
|
|
11
|
+
<img src="https://img.shields.io/badge/status-Thesis_Prototype-orange?style=for-the-badge" alt="Status">
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [Overview](#overview)
|
|
18
|
+
- [Features](#features)
|
|
19
|
+
- [Project Structure](#project-structure)
|
|
20
|
+
- [Project Index](#project-index)
|
|
21
|
+
- [Getting Started](#getting-started)
|
|
22
|
+
- [Prerequisites](#prerequisites)
|
|
23
|
+
- [Installation](#installation)
|
|
24
|
+
- [Usage](#usage)
|
|
25
|
+
- [Testing](#testing)
|
|
26
|
+
- [Roadmap](#roadmap)
|
|
27
|
+
- [Contributing](#contributing)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Overview
|
|
33
|
+
|
|
34
|
+
LCSAJdump is a static analysis framework designed to discover Return-Oriented Programming (ROP) and Jump-Oriented Programming (JOP) gadgets within RISC-V binaries.
|
|
35
|
+
|
|
36
|
+
Traditional ROP scanners typically employ a linear, sliding-window approach over raw executable bytes. While effective for standard instruction sequences, this method fails to identify **Shadow Gadgets**—executable chains that span non-contiguous memory blocks connected by unconditional jumps or conditional branches.
|
|
37
|
+
|
|
38
|
+
LCSAJdump overcomes this limitation by reconstructing the Control-Flow Graph (CFG) through **Linear Code Sequence and Jump (LCSAJ)** analysis. By modeling the binary as a directed graph of basic blocks, the tool identifies:
|
|
39
|
+
|
|
40
|
+
1. **Contiguous Gadgets:** Standard linear sequences terminating in a control-flow transfer.
|
|
41
|
+
2. **Non-Contiguous (Shadow) Gadgets:** Complex chains traversing multiple basic blocks, effectively bypassing "bad bytes" (e.g., null bytes) and utilizing instructions that would otherwise be unreachable by linear scanning.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
* **Comprehensive Architecture Support:** Full support for RISC-V 64-bit (RV64) and Compressed (C) extensions. Handling 16-bit compressed instructions is critical for maximizing gadget coverage in modern RISC-V binaries.
|
|
46
|
+
* **Graph-Based Reconstruction:** The engine segments the `.text` section into basic blocks based on control-flow transfers (jumps, branches, returns) and reconstructs edges for both fallthrough and direct targets using NetworkX.
|
|
47
|
+
* **Heuristic Backward Search:** Implements a specialized backward Breadth-First Search (BFS) algorithm starting from control-flow sinks (`ret`, `jr`, `jalr`) to reconstruct valid execution paths in reverse.
|
|
48
|
+
* **Hybrid Discovery:** Capable of identifying both standard linear gadgets and complex, multi-block trampoline gadgets in a single pass.
|
|
49
|
+
* **Scoring and Classification:** Includes a heuristic scoring system that prioritizes gadgets involving critical registers (`ra`, `a0`, `sp`) and classifies results into functional categories (Linear, Trampoline, Conditional, Fallthrough).
|
|
50
|
+
* **Optimized Performance:** Features configurable pruning parameters ("Darkness" factor) to limit the search depth and node visitation, balancing analysis speed with coverage depth.
|
|
51
|
+
|
|
52
|
+
## Project Structure
|
|
53
|
+
|
|
54
|
+
The repository is organized into modular components responsible for binary loading, graph generation, and algorithmic search.
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
LCSAJdump/
|
|
58
|
+
├── loader.py # ELF parsing and Capstone disassembly wrapper
|
|
59
|
+
├── graph.py # LCSAJ basic block decomposition and DiGraph construction
|
|
60
|
+
├── rainbowBFS.py # Backward search algorithm, scoring, and classification logic
|
|
61
|
+
├── LCSAJdump.py # Main entry point and CLI argument parsing
|
|
62
|
+
└── utils.py # Helper functions for formatting and logging
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Project Index
|
|
67
|
+
|
|
68
|
+
* **`loader.py`**: Utilizes `pyelftools` to extract executable sections and `Capstone` to disassemble RV64GC instructions into a linear stream.
|
|
69
|
+
* **`graph.py`**: Converts the linear instruction stream into a directed graph. Nodes represent basic blocks (LCSAJs), and edges represent control flow (jumps, branches, and fallthroughs).
|
|
70
|
+
* **`rainbowBFS.py`**: The core analysis engine. It traverses the reverse graph from leaf nodes (returns) to find executable paths, applying heuristic scoring to filter non-viable chains.
|
|
71
|
+
* **`LCSAJdump.py`**: Orchestrates the analysis pipeline, handling user input, parameter tuning, and output generation.
|
|
72
|
+
|
|
73
|
+
## Getting Started
|
|
74
|
+
|
|
75
|
+
### Prerequisites
|
|
76
|
+
|
|
77
|
+
All of them listed in `requirements.txt`:
|
|
78
|
+
* Python 3.8 or higher
|
|
79
|
+
* `capstone` (Disassembly engine)
|
|
80
|
+
* `networkx` (Graph algorithms)
|
|
81
|
+
* `pyelftools` (ELF file parsing)
|
|
82
|
+
|
|
83
|
+
### Installation
|
|
84
|
+
|
|
85
|
+
#### GitHub
|
|
86
|
+
|
|
87
|
+
Clone the repository and install the required dependencies:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git clone [https://github.com/Chris1sFlaggin/LCSAJdump.git](https://github.com/Chris1sFlaggin/LCSAJdump.git)
|
|
91
|
+
cd LCSAJdump
|
|
92
|
+
pip install -r requirements.txt
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Pip
|
|
97
|
+
|
|
98
|
+
```zsh
|
|
99
|
+
pip install lcsajdump
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Usage
|
|
103
|
+
|
|
104
|
+
**Basic Scan**
|
|
105
|
+
Run the tool on a target binary using default parameters:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python LCSAJdump.py <path_to_binary>
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Advanced Configuration**
|
|
113
|
+
Users can tune the search depth and pruning thresholds to handle larger binaries or deeper gadget chains:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
python LCSAJdump.py -d 15 -k 100 -l 20 --verbose <path_to_binary>
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**CLI Options:**
|
|
121
|
+
|
|
122
|
+
* `-d, --depth`: Maximum search depth (in blocks) for the BFS algorithm.
|
|
123
|
+
* `-k, --darkness`: Pruning threshold (maximum visits per node) to prevent infinite loops in cyclic graphs.
|
|
124
|
+
* `-l, --limit`: Maximum number of top-ranked gadgets to display.
|
|
125
|
+
* `-s, --min-score`: Minimum heuristic score threshold for reporting.
|
|
126
|
+
* `-v, --verbose`: Enable detailed output of instruction decoding.
|
|
127
|
+
|
|
128
|
+
### Testing
|
|
129
|
+
|
|
130
|
+
To verify the integrity of the graph reconstruction and gadget finding logic, run the unit tests provided in the `tests/` directory:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python -m pytest unitTest/*
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Output Example
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
❯ time python LCSAJdump/LCSAJdump.py testCTFs/rop/vuln
|
|
142
|
+
[*] Analisi Target: testCTFs/rop/vuln
|
|
143
|
+
[*] Caricamento binario: testCTFs/rop/vuln
|
|
144
|
+
[*] Sezione .text trovata.
|
|
145
|
+
Dimensione: 258236 bytes
|
|
146
|
+
Indirizzo Base: 0x10250
|
|
147
|
+
|
|
148
|
+
[*] Avvio disassemblaggio con Capstone...
|
|
149
|
+
Disassembling [████████████████████████████████████████████████████████████] 100.0%
|
|
150
|
+
[*] Disassemblaggio completato. 89354 istruzioni estratte.
|
|
151
|
+
|
|
152
|
+
[*] Costruzione Nodi LCSAJ...
|
|
153
|
+
Building Graph [████████████████████████████████████████████████████████████] 100.0%
|
|
154
|
+
|
|
155
|
+
[*] Configurazione Rainbow: Depth=12, Darkness=30
|
|
156
|
+
[*] Pruning effettuato: 0 rami tagliati.
|
|
157
|
+
|
|
158
|
+
============================================================
|
|
159
|
+
--- TOP 10 SEQUENTIAL GADGETS ---
|
|
160
|
+
============================================================
|
|
161
|
+
0x39ffe: c.ldsp ra, 0x48(sp); c.ldsp a0, 0x38(sp); c.addi16sp sp, 0x50; c.jr ra
|
|
162
|
+
0x4078c: c.ldsp a0, 0x20(sp); c.ldsp ra, 0x58(sp); c.addi16sp sp, 0x60; c.jr ra
|
|
163
|
+
0x45d2e: c.ld a0, 0x10(a5); c.ldsp ra, 0x38(sp); c.addi16sp sp, 0x40; c.jr ra
|
|
164
|
+
0x460b8: c.ldsp ra, 0x38(sp); c.ldsp a0, 0x20(sp); c.addi16sp sp, 0x40; c.jr ra
|
|
165
|
+
0x460e6: c.ldsp ra, 0x38(sp); c.ldsp a0, 0x20(sp); c.addi16sp sp, 0x40; c.jr ra
|
|
166
|
+
0x4618c: c.ldsp ra, 0x28(sp); c.ldsp a0, 0x10(sp); c.addi16sp sp, 0x30; c.jr ra
|
|
167
|
+
0x461b6: c.ldsp ra, 0x28(sp); c.ldsp a0, 0x10(sp); c.addi16sp sp, 0x30; c.jr ra
|
|
168
|
+
0x46386: c.ldsp a0, 0(sp); c.ldsp ra, 0x18(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
169
|
+
0x4aa1a: c.ldsp a0, 0x18(sp); c.ldsp ra, 0x28(sp); c.addi16sp sp, 0x30; c.jr ra
|
|
170
|
+
0x113be: c.ldsp a0, 8(sp); c.ldsp ra, 0x18(sp); c.sw s0, 0x70(a0); c.ldsp s0, 0x10(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
171
|
+
|
|
172
|
+
============================================================
|
|
173
|
+
--- TOP 10 JUMP-BASED GADGETS ---
|
|
174
|
+
============================================================
|
|
175
|
+
0x4060a: c.ld a0, 0x18(s0); jal -0x27b12; c.ldsp a1, 8(sp); addi a7, zero, 0x87; c.li a0, 2; c.li a2, 0; c.li a3, 8; ecall ; c.ldsp ra, 0x18(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
176
|
+
0x46fc0: ld s8, -0x500(s0); ld a0, -0x480(s0); ld a6, -0x4f8(s0); beq a0, s8, 0x10; sd a6, -0x4c8(s0); jal -0x2e4da; c.sd a4, 0x28(a5); c.ldsp ra, 0x78(sp); c.ldsp s8, 0x30(sp); c.addi16sp sp, 0x80; c.jr ra
|
|
177
|
+
0x2b9f4: auipc a3, 0x4e; ld a3, 0x504(a3); addi a2, sp, 0x4e0; c.mv a1, a2; c.add a3, tp; c.ld a0, 0(a3); jal 0x13ace; c.lw a4, 0(a5); c.andi a4, -0x11; c.sw a4, 0(a5); c.ldsp ra, 0x18(sp); c.addi16sp sp, 0x20; c.jr ra
|
|
178
|
+
0x4146a: ld a0, 8(s10); jal -0x28974; c.mv a0, t3; bltz t3, 0x22e; c.ldsp s0, 0x50(sp); c.ldsp s1, 0x48(sp); c.ldsp s3, 0x38(sp); c.ldsp ra, 0x58(sp); c.ldsp s2, 0x40(sp); c.ldsp s4, 0x30(sp); c.ldsp s5, 0x28(sp); c.addi16sp sp, 0x60; c.jr ra
|
|
179
|
+
0x46224: c.addi16sp sp, -0x30; c.sdsp a0, 0(sp); auipc a0, 0x34; ld a0, -0x328(a0); c.sdsp ra, 0x28(sp); c.sdsp s0, 0x20(sp); c.sdsp a1, 8(sp); c.sdsp ra, 0x10(sp); jal -0x16152; c.ldsp ra, 0x18(sp); c.ldsp s0, 0x10(sp); c.li a0, -1; c.addi16sp sp, 0x20; c.jr ra
|
|
180
|
+
0x35a06: ld a0, 0x360(s1); c.li a5, -1; beq a0, a5, 8; jal -0x1cf16; c.ld a2, 0x58(s0); c.lw a1, 0x60(s0); auipc a0, 0x34; addi a0, a0, -0x1a; addi s0, s0, 0x80; jal 0x1b864; c.ldsp ra, 0x88(sp); c.ldsp s0, 0x80(sp); c.ldsp s1, 0x78(sp); c.addi16sp sp, 0x90; c.jr ra
|
|
181
|
+
0x46366: c.ld a0, 0x10(a0); c.sdsp a5, 8(sp); c.sdsp a4, 0(sp); jal -0x1245a; add a4, a2, a1; c.lw a3, 0(a5); c.sd a2, 0x18(a5); c.sd a4, 8(a5); c.andi a3, -0x11; c.sd a4, 0x10(a5); c.sd a0, 0x90(a5); c.sw a3, 0(a5); c.ldsp ra, 0x28(sp); c.mv a0, a1; c.addi16sp sp, 0x30; c.jr ra
|
|
182
|
+
0x1db48: c.ld a0, 8(s0); c.ldsp s0, 0x10(sp); c.ld a1, 8(s1); c.ldsp ra, 0x18(sp); c.ldsp s1, 8(sp); c.addi16sp sp, 0x20; j 0x141fe; c.lw a4, 0(a5); c.ld a3, 8(a5); andi a4, a4, 0x100; c.bnez a4, 0xe; c.ld a5, 0x18(a5); c.lw a0, 0x10(a0); sub a5, a3, a5; c.subw a0, a5; c.jr ra
|
|
183
|
+
0x4145e: lw a5, 0(s6); andi a5, a5, 0x40; bnez a5, 0x2c0; ld a0, 8(s10); jal -0x28974; c.mv a0, t3; bltz t3, 0x22e; c.ldsp s0, 0x50(sp); c.ldsp s1, 0x48(sp); c.ldsp s3, 0x38(sp); c.ldsp ra, 0x58(sp); c.ldsp s2, 0x40(sp); c.ldsp s4, 0x30(sp); c.ldsp s5, 0x28(sp); c.addi16sp sp, 0x60; c.jr ra
|
|
184
|
+
0x2bd72: c.mv a2, s8; addi a1, zero, 0x20; c.mv a0, s0; jal 0x109e8; slli a0, a4, 5; c.addi a0, 0x10; c.addi a4, 1; c.add a0, a2; c.sd a4, 8(a2); ld a5, 0xa0(gp); c.li a4, 1; c.ldsp ra, 0x18(sp); c.sd a4, 0(a0); c.add a5, a4; sd a5, 0xa0(gp); c.addi16sp sp, 0x20; c.jr ra
|
|
185
|
+
|
|
186
|
+
[+] Report salvato in: gadgets_found.txt (Trovati 4637 gadget)
|
|
187
|
+
python LCSAJdump/LCSAJdump.py testCTFs/rop/vuln 1,67s user 0,27s system 99% cpu 1,936 total
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Contributing
|
|
193
|
+
|
|
194
|
+
Contributions to improve the search algorithm or extend architecture support are welcome. Please ensure that any pull requests include relevant test cases and adhere to the existing coding standards.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
This project is licensed under the MIT License. See the [LICENSE](https://www.google.com/search?q=LICENSE) file for details.
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sys
|
|
3
|
+
from .core.loader import BinaryLoader
|
|
4
|
+
from .core.graph import LCSAJGraph
|
|
5
|
+
from .core.rainbowBFS import RainbowFinder
|
|
6
|
+
|
|
7
|
+
@click.command()
|
|
8
|
+
@click.argument('binary_path', type=click.Path(exists=True))
|
|
9
|
+
@click.option('--depth', '-d', default=12, help='Profondità massima di ricerca (blocchi LCSAJ).')
|
|
10
|
+
@click.option('--darkness', '-k', default=30, help='Soglia di pruning (Max visite per nodo).')
|
|
11
|
+
@click.option('--limit', '-l', default=10, help='Numero di gadget da mostrare a video.')
|
|
12
|
+
@click.option('--min-score', '-s', default=0, help='Punteggio minimo per mostrare un gadget.')
|
|
13
|
+
@click.option('--verbose', '-v', is_flag=True, help='Mostra dettagli extra sui gadget trovati.')
|
|
14
|
+
@click.version_option(version='1.0.0', prog_name='LCSAJdump')
|
|
15
|
+
def main(binary_path, depth, darkness, limit, min_score, verbose):
|
|
16
|
+
"""
|
|
17
|
+
RISC-V LCSAJ ROP Finder.
|
|
18
|
+
Analizza un binario per trovare gadget ROP usando l'algoritmo Rainbow BFS.
|
|
19
|
+
"""
|
|
20
|
+
print('\33[33m'+r"""
|
|
21
|
+
██╗ ██████╗███████╗ █████╗ ██╗██████╗ ██╗ ██╗███╗ ███╗██████╗ ██╗ ██╗ ██╗ ██████╗ ██████╗
|
|
22
|
+
██║ ██╔════╝██╔════╝██╔══██╗ ██║██╔══██╗██║ ██║████╗ ████║██╔══██╗ ██║ ██║███║ ██╔═████╗ ██╔═████╗
|
|
23
|
+
██║ ██║ ███████╗███████║ ██║██║ ██║██║ ██║██╔████╔██║██████╔╝ █████╗ ██║ ██║╚██║ ██║██╔██║ ██║██╔██║
|
|
24
|
+
██║ ██║ ╚════██║██╔══██║██ ██║██║ ██║██║ ██║██║╚██╔╝██║██╔═══╝ ╚════╝ ╚██╗ ██╔╝ ██║ ████╔╝██║ ████╔╝██║
|
|
25
|
+
███████╗╚██████╗███████║██║ ██║╚█████╔╝██████╔╝╚██████╔╝██║ ╚═╝ ██║██║ ╚████╔╝ ██║██╗╚██████╔╝▄█╗╚██████╔╝
|
|
26
|
+
╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═════╝
|
|
27
|
+
"""+'\33[0m')
|
|
28
|
+
|
|
29
|
+
print(f"[*] Analizing Target: {binary_path}")
|
|
30
|
+
|
|
31
|
+
loader = BinaryLoader(binary_path)
|
|
32
|
+
insns = loader.disassemble()
|
|
33
|
+
|
|
34
|
+
gb = LCSAJGraph(insns)
|
|
35
|
+
gb.build()
|
|
36
|
+
|
|
37
|
+
finder = RainbowFinder(gb, max_depth=depth, max_darkness=darkness)
|
|
38
|
+
gadgets = finder.search()
|
|
39
|
+
|
|
40
|
+
# Output a video filtrato
|
|
41
|
+
finder.print_gadgets(limit=limit, min_score=min_score, verbose=verbose)
|
|
42
|
+
|
|
43
|
+
# Output su file
|
|
44
|
+
output_file = "gadgets_found.txt"
|
|
45
|
+
try:
|
|
46
|
+
with open(output_file, "w") as f:
|
|
47
|
+
sys.stdout = f
|
|
48
|
+
print(f"REPORT GADGET - Depth:{depth} Darkness:{darkness}\n")
|
|
49
|
+
finder.print_gadgets(limit=len(gadgets), min_score=min_score)
|
|
50
|
+
sys.stdout = sys.__stdout__
|
|
51
|
+
print(f"\n[+] Report saved in: {output_file} (Found {len(gadgets)} gadgets)")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
sys.stdout = sys.__stdout__
|
|
54
|
+
print(f"[!] Errore while saving file: {e}")
|
|
55
|
+
|
|
56
|
+
if __name__ == '__main__':
|
|
57
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from .loader import draw_progress
|
|
3
|
+
|
|
4
|
+
class LCSAJGraph:
|
|
5
|
+
def __init__(self, instructions):
|
|
6
|
+
self.instructions = instructions
|
|
7
|
+
self.graph = nx.DiGraph()
|
|
8
|
+
self.addr_to_node = {} # Inizio blocco -> Dati blocco
|
|
9
|
+
self.insn_to_block_start = {} # Indirizzo istruzione -> Inizio blocco di appartenenza
|
|
10
|
+
self.reverse_graph = {}
|
|
11
|
+
self.nodes = []
|
|
12
|
+
|
|
13
|
+
def build(self):
|
|
14
|
+
self._create_nodes()
|
|
15
|
+
self._build_edges()
|
|
16
|
+
|
|
17
|
+
def _create_nodes(self):
|
|
18
|
+
if not self.instructions: return
|
|
19
|
+
|
|
20
|
+
print("[*] Building LCSAJ Nodes...")
|
|
21
|
+
|
|
22
|
+
total_insns = len(self.instructions)
|
|
23
|
+
current_block_insns = []
|
|
24
|
+
block_start = self.instructions[0].address
|
|
25
|
+
|
|
26
|
+
for idx, insn in enumerate(self.instructions):
|
|
27
|
+
|
|
28
|
+
if idx % 1000 == 0:
|
|
29
|
+
draw_progress(idx, total_insns, "Building Graph")
|
|
30
|
+
|
|
31
|
+
current_block_insns.append(insn)
|
|
32
|
+
mnem = insn.mnemonic.lower()
|
|
33
|
+
|
|
34
|
+
is_jump = mnem in ['ret', 'c.jr', 'c.jalr', 'jr', 'jalr'] or \
|
|
35
|
+
mnem in ['j', 'jal', 'c.j', 'c.jal'] or \
|
|
36
|
+
mnem.startswith('b') or mnem.startswith('c.b')
|
|
37
|
+
|
|
38
|
+
if is_jump:
|
|
39
|
+
self._add_node(block_start, current_block_insns)
|
|
40
|
+
current_block_insns = []
|
|
41
|
+
if idx + 1 < total_insns:
|
|
42
|
+
block_start = self.instructions[idx+1].address
|
|
43
|
+
|
|
44
|
+
if current_block_insns:
|
|
45
|
+
self._add_node(block_start, current_block_insns)
|
|
46
|
+
|
|
47
|
+
draw_progress(total_insns, total_insns, "Building Graph")
|
|
48
|
+
|
|
49
|
+
def _add_node(self, start, insns):
|
|
50
|
+
node = {'start': start, 'end': insns[-1].address, 'insns': insns, 'last_insn': insns[-1]}
|
|
51
|
+
self.nodes.append(node)
|
|
52
|
+
self.addr_to_node[start] = node
|
|
53
|
+
for i in insns:
|
|
54
|
+
self.insn_to_block_start[i.address] = start
|
|
55
|
+
|
|
56
|
+
def _build_edges(self):
|
|
57
|
+
for node in self.nodes:
|
|
58
|
+
last = node['last_insn']
|
|
59
|
+
mnem = last.mnemonic.lower()
|
|
60
|
+
targets = []
|
|
61
|
+
|
|
62
|
+
if mnem not in ['j', 'jal', 'c.j', 'c.jal', 'ret', 'jr', 'c.jr']:
|
|
63
|
+
next_addr = last.address + last.size
|
|
64
|
+
if next_addr in self.insn_to_block_start:
|
|
65
|
+
targets.append(self.insn_to_block_start[next_addr])
|
|
66
|
+
|
|
67
|
+
if mnem.startswith('b') or mnem in ['jal', 'j', 'c.j', 'c.jal']:
|
|
68
|
+
try:
|
|
69
|
+
import re
|
|
70
|
+
hex_match = re.findall(r'0x[0-9a-fA-F]+', last.op_str)
|
|
71
|
+
if hex_match:
|
|
72
|
+
addr = int(hex_match[-1], 16)
|
|
73
|
+
if addr in self.insn_to_block_start:
|
|
74
|
+
targets.append(self.insn_to_block_start[addr])
|
|
75
|
+
except: pass
|
|
76
|
+
|
|
77
|
+
for t in set(targets):
|
|
78
|
+
if t not in self.reverse_graph: self.reverse_graph[t] = []
|
|
79
|
+
self.reverse_graph[t].append(node['start'])
|
|
80
|
+
|
|
81
|
+
def get_gadget_tails(self):
|
|
82
|
+
return [n for n in self.nodes if n['last_insn'].mnemonic.lower() in ['ret', 'c.jr', 'jr', 'jalr']]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import capstone
|
|
2
|
+
from elftools.elf.elffile import ELFFile
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
def draw_progress(current, total, label=""):
|
|
6
|
+
percent = float(current) / float(total) * 100
|
|
7
|
+
bar_length = 60
|
|
8
|
+
filled_length = int(bar_length * current // total)
|
|
9
|
+
|
|
10
|
+
bar = '█' * filled_length + '░' * (bar_length - filled_length)
|
|
11
|
+
|
|
12
|
+
sys.stdout.write(f"\r{label:15} \033[32m[{bar}]\033[0m {percent:>5.1f}%")
|
|
13
|
+
sys.stdout.flush()
|
|
14
|
+
|
|
15
|
+
if current == total:
|
|
16
|
+
print()
|
|
17
|
+
|
|
18
|
+
class BinaryLoader:
|
|
19
|
+
def __init__(self, path):
|
|
20
|
+
self.path = path
|
|
21
|
+
self.code_bytes = None
|
|
22
|
+
self.base_addr = 0
|
|
23
|
+
|
|
24
|
+
# --- CONFIGURAZIONE CAPSTONE ---
|
|
25
|
+
# CS_ARCH_RISCV: Architettura principale
|
|
26
|
+
# CS_MODE_RISCV64: Modalità a 64-bit
|
|
27
|
+
# CS_MODE_RISCVC: Abilita le istruzioni "Compressed" (16-bit).
|
|
28
|
+
self.md = capstone.Cs(capstone.CS_ARCH_RISCV,
|
|
29
|
+
capstone.CS_MODE_RISCV64 | capstone.CS_MODE_RISCVC)
|
|
30
|
+
|
|
31
|
+
# Abilitiamo i dettagli per poter analizzare gli operandi (registri, imm) dopo
|
|
32
|
+
self.md.detail = True
|
|
33
|
+
|
|
34
|
+
def load(self):
|
|
35
|
+
"""
|
|
36
|
+
Apre il file ELF, cerca la sezione .text (codice eseguibile)
|
|
37
|
+
e la carica in memoria.
|
|
38
|
+
"""
|
|
39
|
+
print(f"[*] Loadaing binary: {self.path}")
|
|
40
|
+
try:
|
|
41
|
+
with open(self.path, 'rb') as f:
|
|
42
|
+
elf = ELFFile(f)
|
|
43
|
+
text_section = elf.get_section_by_name('.text')
|
|
44
|
+
|
|
45
|
+
if not text_section:
|
|
46
|
+
raise ValueError("Error: Section .text not found in binary!")
|
|
47
|
+
|
|
48
|
+
self.code_bytes = text_section.data()
|
|
49
|
+
self.base_addr = text_section['sh_addr']
|
|
50
|
+
|
|
51
|
+
print(f"[*] Section .text found.")
|
|
52
|
+
print(f" Dimension: {len(self.code_bytes)} bytes")
|
|
53
|
+
print(f" Start Address: {hex(self.base_addr)}\n")
|
|
54
|
+
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
print(f"[!] Errore: File {self.path} non trovato.")
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"[!] Errore nel parsing ELF: {e}")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
def disassemble(self):
|
|
63
|
+
if self.code_bytes is None:
|
|
64
|
+
self.load()
|
|
65
|
+
|
|
66
|
+
print("[*] Capstone is disassembling...")
|
|
67
|
+
|
|
68
|
+
instructions = []
|
|
69
|
+
total_bytes = len(self.code_bytes)
|
|
70
|
+
ptr = 0
|
|
71
|
+
|
|
72
|
+
while ptr < total_bytes:
|
|
73
|
+
curr_addr = self.base_addr + ptr
|
|
74
|
+
|
|
75
|
+
# v6 OTTIMIZZAZIONE: Invece di chiederne 1 alla volta,
|
|
76
|
+
try:
|
|
77
|
+
# Slicing (solo con buco nel codice)
|
|
78
|
+
chunk = self.code_bytes[ptr:]
|
|
79
|
+
disasm_iter = self.md.disasm(chunk, curr_addr)
|
|
80
|
+
|
|
81
|
+
count = 0
|
|
82
|
+
for insn in disasm_iter:
|
|
83
|
+
instructions.append(insn)
|
|
84
|
+
ptr += insn.size
|
|
85
|
+
count += 1
|
|
86
|
+
|
|
87
|
+
if len(instructions) % 5000 == 0:
|
|
88
|
+
draw_progress(ptr, total_bytes, "Disassembling")
|
|
89
|
+
|
|
90
|
+
# Se count == 0, significa che Capstone si è bloccato SUBITO.
|
|
91
|
+
# Quindi il byte a 'ptr' è sporco.
|
|
92
|
+
if count == 0:
|
|
93
|
+
ptr += 2
|
|
94
|
+
|
|
95
|
+
except Exception:
|
|
96
|
+
ptr += 2
|
|
97
|
+
|
|
98
|
+
draw_progress(total_bytes, total_bytes, "Disassembling")
|
|
99
|
+
print(f"[*] Disassembling complete. {len(instructions)} instructions estracted.\n")
|
|
100
|
+
return instructions
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
|
|
3
|
+
class RainbowFinder:
|
|
4
|
+
jump_mnemonics = ['j', 'c.j', 'jal', 'c.jal']
|
|
5
|
+
|
|
6
|
+
def __init__(self, graph_manager, max_depth, max_darkness):
|
|
7
|
+
self.gm = graph_manager
|
|
8
|
+
self.gadgets = []
|
|
9
|
+
|
|
10
|
+
self.MAX_DEPTH = max_depth
|
|
11
|
+
self.MAX_DARKNESS = max_darkness
|
|
12
|
+
|
|
13
|
+
def score_gadget(self, path):
|
|
14
|
+
score = 100
|
|
15
|
+
full_insns = []
|
|
16
|
+
for addr in path:
|
|
17
|
+
if addr in self.gm.addr_to_node:
|
|
18
|
+
full_insns.extend(self.gm.addr_to_node[addr]['insns'])
|
|
19
|
+
|
|
20
|
+
# v4 fix: togliere anche (len(path) * 10) era troppo penalizzante per gadget LCSAJ
|
|
21
|
+
score -= (len(full_insns) * 2)
|
|
22
|
+
|
|
23
|
+
has_ra = any('ra' in i.op_str and 'ld' in i.mnemonic for i in full_insns)
|
|
24
|
+
has_a0 = any('a0' in i.op_str and 'ld' in i.mnemonic for i in full_insns)
|
|
25
|
+
# v4 fix: premiare i salti trampolino
|
|
26
|
+
has_J = any(i.mnemonic in self.jump_mnemonics for i in full_insns)
|
|
27
|
+
|
|
28
|
+
if has_ra: score += 50
|
|
29
|
+
if has_a0: score += 40
|
|
30
|
+
if has_J: score += 30
|
|
31
|
+
|
|
32
|
+
# Penalità JOP (Salti a registro non-ra)
|
|
33
|
+
if full_insns:
|
|
34
|
+
last = full_insns[-1]
|
|
35
|
+
if last.mnemonic in ['jr', 'jalr', 'c.jr', 'c.jalr'] and 'ra' not in last.op_str:
|
|
36
|
+
score -= 20
|
|
37
|
+
|
|
38
|
+
return score
|
|
39
|
+
|
|
40
|
+
def search(self):
|
|
41
|
+
print(f"\n[*] RainbowBFS config: Depth={self.MAX_DEPTH}, Darkness={self.MAX_DARKNESS}")
|
|
42
|
+
tails = self.gm.get_gadget_tails()
|
|
43
|
+
queue = collections.deque([([t['start']], {t['start']}) for t in tails])
|
|
44
|
+
node_darkness = collections.defaultdict(int)
|
|
45
|
+
pruned = 0
|
|
46
|
+
|
|
47
|
+
while queue:
|
|
48
|
+
path, visited = queue.popleft()
|
|
49
|
+
head = path[0]
|
|
50
|
+
# V4 fix: '>' was blocking sequential gadgets
|
|
51
|
+
if len(path) >= 1: self.gadgets.append(path)
|
|
52
|
+
if len(path) >= self.MAX_DEPTH: continue
|
|
53
|
+
|
|
54
|
+
for parent in self.gm.reverse_graph.get(head, []):
|
|
55
|
+
if parent in visited: continue
|
|
56
|
+
if node_darkness[parent] >= self.MAX_DARKNESS:
|
|
57
|
+
pruned += 1
|
|
58
|
+
continue
|
|
59
|
+
node_darkness[parent] += 1
|
|
60
|
+
queue.append(([parent] + path, visited | {parent}))
|
|
61
|
+
|
|
62
|
+
print(f"[*] Pruning: {pruned} pruned branches.")
|
|
63
|
+
return self.gadgets
|
|
64
|
+
|
|
65
|
+
def _classify_gadget(self, path):
|
|
66
|
+
"""Ritorna una etichetta e una categoria per il gadget"""
|
|
67
|
+
if len(path) == 1:
|
|
68
|
+
return "LINEAR", "Sequential"
|
|
69
|
+
|
|
70
|
+
# Analizziamo il tipo di salto
|
|
71
|
+
first_node = self.gm.addr_to_node[path[0]]
|
|
72
|
+
last_insn = first_node['last_insn']
|
|
73
|
+
mnem = last_insn.mnemonic.lower()
|
|
74
|
+
|
|
75
|
+
if mnem in ['j', 'c.j', 'jal', 'c.jal']:
|
|
76
|
+
return "TRAMPOLINE", "Jump-Based" # Salta sopra ostacoli
|
|
77
|
+
elif mnem.startswith('b') or mnem.startswith('c.b'):
|
|
78
|
+
return "CONDITIONAL", "Jump-Based" # Logica if/else
|
|
79
|
+
else:
|
|
80
|
+
return "FALLTHROUGH", "Jump-Based" # Discontinuità di memoria
|
|
81
|
+
|
|
82
|
+
def print_gadgets(self, limit, min_score, verbose=False):
|
|
83
|
+
categories = {'Sequential': [], 'Jump-Based': []}
|
|
84
|
+
|
|
85
|
+
for g in self.gadgets:
|
|
86
|
+
s = self.score_gadget(g)
|
|
87
|
+
if s < min_score: continue
|
|
88
|
+
|
|
89
|
+
tag, cat = self._classify_gadget(g)
|
|
90
|
+
categories[cat].append((s, g, tag))
|
|
91
|
+
|
|
92
|
+
for cat_name in ['Sequential', 'Jump-Based']:
|
|
93
|
+
gadgets = categories[cat_name]
|
|
94
|
+
gadgets.sort(key=lambda x: x[0], reverse=True)
|
|
95
|
+
|
|
96
|
+
print(f"\033[33m\n{'='*60}\033[0m")
|
|
97
|
+
print(f"\033[33m--- TOP {limit} {cat_name.upper()} GADGETS ---\033[0m")
|
|
98
|
+
print(f"\033[33m{'='*60}\033[0m")
|
|
99
|
+
|
|
100
|
+
for i, (s, p, tag) in enumerate(gadgets[:limit]):
|
|
101
|
+
if verbose:
|
|
102
|
+
print(f"\nRANK #{i+1} | SCORE: {s} | TYPE: {tag}")
|
|
103
|
+
for addr in p:
|
|
104
|
+
node = self.gm.addr_to_node[addr]
|
|
105
|
+
for insn in node['insns']:
|
|
106
|
+
print(f" \033[33m{hex(insn.address)}\033[0m: {insn.mnemonic} {insn.op_str}")
|
|
107
|
+
else:
|
|
108
|
+
full_gadget_str = []
|
|
109
|
+
for addr in p:
|
|
110
|
+
node = self.gm.addr_to_node[addr]
|
|
111
|
+
for insn in node['insns']:
|
|
112
|
+
full_gadget_str.append(f"{insn.mnemonic} {insn.op_str}")
|
|
113
|
+
|
|
114
|
+
start_addr = hex(p[0])
|
|
115
|
+
print(f"\033[33m{start_addr}\033[0m: {'; '.join(full_gadget_str)}")
|
lcsajdump-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="lcsajdump",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
author="Chris1sFlaggin",
|
|
10
|
+
author_email="lcsajdump@chris1sflaggin.it",
|
|
11
|
+
description="A Graph-Based ROP Gadget Finder for RISC-V architectures",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://chris1sflaggin.it/LCSAJdump/",
|
|
15
|
+
|
|
16
|
+
packages=find_packages(exclude=[
|
|
17
|
+
"testCTFs*",
|
|
18
|
+
"unitTest*",
|
|
19
|
+
"_images*",
|
|
20
|
+
"build*",
|
|
21
|
+
"dist*",
|
|
22
|
+
"venv*",
|
|
23
|
+
"lcsajdump.venv*"
|
|
24
|
+
]),
|
|
25
|
+
|
|
26
|
+
classifiers=[
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Topic :: Security",
|
|
31
|
+
"Environment :: Console",
|
|
32
|
+
],
|
|
33
|
+
python_requires='>=3.6',
|
|
34
|
+
install_requires=[
|
|
35
|
+
"capstone",
|
|
36
|
+
"pyelftools",
|
|
37
|
+
"networkx",
|
|
38
|
+
"click",
|
|
39
|
+
],
|
|
40
|
+
entry_points={
|
|
41
|
+
'console_scripts': [
|
|
42
|
+
'lcsajdump=lcsajdump.cli:main',
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
)
|