Skip to content

Commit 22a2f52

Browse files
Merge pull request #301 from CarterPerez-dev/project/reverse-engineering-learning
Project/reverse engineering learning
2 parents 757f9d6 + 1d9668e commit 22a2f52

153 files changed

Lines changed: 11771 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# ©AngelaMos | 2026
2+
# .dockerignore
3+
4+
.git
5+
.venv
6+
data
7+
**/__pycache__
8+
**/*.pyc
9+
.pytest_cache
10+
**/.pytest_cache
11+
12+
frontend/node_modules
13+
frontend/dist
14+
frontend/.vite
15+
frontend/.biome_cache
16+
17+
docs/plans
18+
docs/context

PROJECTS/advanced/rveng/.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ©AngelaMos | 2026
2+
# .gitignore
3+
4+
docs/plans/
5+
docs/context/
6+
7+
__pycache__/
8+
.pytest_cache/
9+
.venv/
10+
*.pyc
11+
12+
data/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

PROJECTS/advanced/rveng/README.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<!-- ©AngelaMos | 2026 -->
2+
<!-- README.md -->
3+
4+
```
5+
______ _____ ____ ____ _
6+
/ ___/ | / / _ \/ __ \/ __ `/
7+
/ / | |/ / __/ / / / /_/ /
8+
/_/ |___/\___/_/ /_/\__, /
9+
/____/
10+
```
11+
12+
[![Cybersecurity Projects](https://img.shields.io/badge/Cybersecurity--Projects-Project%20%2337-red?style=flat&logo=github)](https://github.com/CarterPerez-dev/Cybersecurity-Projects/tree/main/PROJECTS/advanced/rveng)
13+
[![Python](https://img.shields.io/badge/Python-3.13-3776AB?style=flat&logo=python&logoColor=white)](https://www.python.org)
14+
[![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=flat&logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com)
15+
[![React](https://img.shields.io/badge/React-19-61DAFB?style=flat&logo=react&logoColor=black)](https://react.dev)
16+
[![capstone](https://img.shields.io/badge/disassembler-capstone-6d4aff?style=flat)](https://www.capstone-engine.org)
17+
[![No execution](https://img.shields.io/badge/binaries-never%20executed-2ea043?style=flat)](#it-never-runs-a-binary)
18+
[![License: AGPLv3](https://img.shields.io/badge/License-AGPL_v3-purple.svg)](https://www.gnu.org/licenses/agpl-3.0)
19+
20+
> An interactive reverse-engineering learning platform. It hands you a real compiled binary, asks a concrete question about it, gives you an in-browser hex viewer, disassembler, section map, and string scanner to answer it, and grades your answer. Only when you are right does it reveal the original C source, so you connect the machine code you just read back to the code that produced it. One framework-free Python analysis engine wears three faces: a web app, a read-only HTTP API, and an embeddable library.
21+
22+
## Why a reverse-engineering platform
23+
24+
A pile of worksheets and pre-compiled binaries can walk you through reverse engineering, but it cannot check your work and it cannot become anything more. rveng keeps the solve-then-reveal loop and makes it a real system. The engine that parses ELF, drives the disassembler, resolves imports, and grades answers is the core. The web app turns the worksheet into an interactive lab: an in-browser hex viewer, live disassembly, a section map, and a gradeable challenge runner. The engine and its lesson content are decoupled from the web framework, so they embed into a larger application as a standalone reverse-engineering feature. One core, three consumers, curated content.
25+
26+
## It never runs a binary
27+
28+
The single load-bearing decision, and the reason a web app that eats binaries is safe: the backend never executes any binary. Every operation is reading and parsing bytes.
29+
30+
- Hex dump, ELF header parse, section walk, symbol read, and string scan are all pure byte reads.
31+
- Disassembly is decoding, not running. capstone reads instruction bytes and returns their text form; it never transfers control to the decoded code.
32+
- Patch challenges are graded by a static byte diff against a known-good patched target. The patched binary is never executed.
33+
34+
Challenge binaries are curated and pre-compiled and shipped as static assets; nobody uploads an executable to run. So there is no arbitrary-code-execution surface, no sandbox to escape, and no resource-exhaustion path through a hostile binary, because nothing is ever run. This is a hard constraint, not a preference. Any feature that would require executing a binary is out of scope until it is redesigned against this posture, most likely by moving execution to an isolated, disposable sandbox that is explicitly not the analysis backend.
35+
36+
## Features
37+
38+
**The engine**
39+
- A hand-rolled ELF64 parser: header, section table, and symbol table read straight from raw bytes
40+
- x86-64 disassembly via capstone in Intel syntax, annotated with comparisons, conditional branches, call targets, and RIP-relative data references
41+
- Import resolution through the PLT, walking `.plt`, `.rela.plt`, `.dynsym`, and `.dynstr` the way the loader would, so a bare `call 0x401050` becomes `call atoi`
42+
- Cross-references (who calls this, what data it touches) and a basic-block control-flow graph for a single function
43+
- Function discovery in stripped binaries by scanning executable sections for the standard prologue, so a symbol-stripped binary is still navigable
44+
45+
**The platform**
46+
- Six curated challenges over one sample binary, spanning the five core reverse-engineering skills plus a stripped variant
47+
- Solve-then-reveal grading in three machine-checkable categories: found-value, identified-symbol, and patched-bytes
48+
- Progress persisted in SQLite behind a swappable interface, so it drops into a host application's own store without touching the engine
49+
- A React web app and a read-only FastAPI, served together for one-command self-hosting
50+
51+
## Quick Start
52+
53+
rveng self-hosts on localhost with Docker. No account, no secret, no external service, nothing to configure.
54+
55+
```bash
56+
curl -fsSL https://angelamos.com/rveng/install.sh | bash
57+
# then open http://localhost:8790
58+
```
59+
60+
One command takes a fresh machine to the app built and running: it installs Docker if it is missing, builds the engine image and the React app, brings the stack up, and waits until it answers. The first thing you do is open the browser.
61+
62+
Already have the repo cloned? Run it straight from the project directory:
63+
64+
```bash
65+
just up # build and serve on http://localhost:8790
66+
just dev-up # hot-reload dev stack on http://localhost:8791
67+
just test # engine tests, no server (uv run pytest -q)
68+
just typecheck # frontend type check, no server
69+
just down # stop
70+
```
71+
72+
> [!TIP]
73+
> This project uses [`just`](https://github.com/casey/just) as a command runner. Type `just` to see every recipe grouped by area: `dev` (dockerized Vite hot-reload), `prod` (the self-host stack), `verify` (tests and type check), and `cleanup`.
74+
>
75+
> Install: `curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin`
76+
77+
## Architecture
78+
79+
One analysis engine with three faces. The engine is framework-free Python that knows nothing about HTTP or React. A thin FastAPI layer adapts it to the web. A React app consumes that API. Progress lives behind a small interface so the whole thing embeds into a larger application without dragging a web framework along.
80+
81+
```
82+
+--------------------------+
83+
| rveng/engine/ (pure) |
84+
| elf disasm plt xref cfg |
85+
| hex strings patch discover|
86+
| challenge (grading) |
87+
+------------+--------------+
88+
|
89+
+-----------------+-----------------+
90+
| |
91+
+-----+------+ +------+------+
92+
| HTTP API | | library |
93+
| FastAPI | | (import it)|
94+
+-----+------+ +-------------+
95+
|
96+
+-----+------+
97+
| React app |
98+
+------------+
99+
```
100+
101+
In production, nginx serves the built frontend and proxies `/api` to the engine container, which stays a pure API and never learns to serve a SPA. Development mirrors that with nginx fronting the Vite dev server for hot reload. The two stacks use structural-literal project names (`rveng` and `rveng-dev`), so they are namespace-isolated by construction and never collide.
102+
103+
```
104+
PROD (compose.yml, "rveng") DEV (dev.compose.yml, "rveng-dev")
105+
106+
browser :8790 browser :8791
107+
| |
108+
[ nginx ] serves dist [ nginx ] --> [ vite HMR ]
109+
| /api | /api
110+
[ api ] uvicorn [ api ] uvicorn --reload
111+
| |
112+
rveng_data (sqlite progress) rveng_data_dev
113+
```
114+
115+
## Project Structure
116+
117+
```
118+
rveng/
119+
├── compose.yml # prod self-host: nginx + api (name: rveng)
120+
├── dev.compose.yml # dev: nginx + vite HMR + api --reload
121+
├── justfile # dev / prod / verify / cleanup recipes
122+
├── install.sh # one-shot curl|bash: installs Docker, builds, runs
123+
├── uninstall.sh # tears the stacks down and removes the cache
124+
├── infra/
125+
│ ├── docker/ # api.dockerfile, vite.dev, vite.prod (multistage -> nginx)
126+
│ └── nginx/ # dev.nginx (fronts Vite HMR), prod.nginx (serves dist)
127+
├── src/rveng/
128+
│ ├── engine/ # the pure analysis core (no HTTP, no framework)
129+
│ │ ├── elf.py # hand-rolled ELF64 header / sections / symbols
130+
│ │ ├── disasm.py # capstone x86-64 decode + annotation
131+
│ │ ├── plt.py # PLT / GOT import resolution
132+
│ │ ├── xref.py # cross-references from decoded instructions
133+
│ │ ├── cfg.py # basic-block control-flow graph
134+
│ │ ├── discover.py # stripped-binary function discovery
135+
│ │ ├── hex.py strings.py patch.py # dump, string scan, byte diff
136+
│ │ └── challenge.py # the challenge model and solve-then-reveal grader
137+
│ └── api/ # thin FastAPI adapter over the engine
138+
│ ├── app.py # create_app() and every read-only route
139+
│ ├── store.py # challenge loader + ProgressStore (in-memory / sqlite)
140+
│ ├── schemas.py limits.py middleware.py server.py
141+
├── challenges/ # the six curated challenges (target + source + answer)
142+
├── frontend/ # the React face (self-contained, extractable)
143+
└── learn/ # the teaching track
144+
```
145+
146+
## Learn
147+
148+
This project ships a full teaching track. Read it in order, or jump to what you need.
149+
150+
| Doc | What it covers |
151+
|-----|----------------|
152+
| [`learn/00-OVERVIEW.md`](learn/00-OVERVIEW.md) | What rveng is, the no-execution posture, and a quick tour |
153+
| [`learn/01-CONCEPTS.md`](learn/01-CONCEPTS.md) | Reverse-engineering theory: static analysis, ELF, symbols, the PLT, patching, grounded in real analysis |
154+
| [`learn/02-ARCHITECTURE.md`](learn/02-ARCHITECTURE.md) | The one-engine-three-faces design and how a request flows, with diagrams |
155+
| [`learn/03-IMPLEMENTATION.md`](learn/03-IMPLEMENTATION.md) | A code walkthrough of every engine module against the sample binary |
156+
| [`learn/04-CHALLENGES.md`](learn/04-CHALLENGES.md) | The six challenges, what each teaches, and how to add your own |
157+
158+
## License
159+
160+
[AGPL 3.0](LICENSE).
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "01-read-the-hex",
3+
"module": "hex-reading",
4+
"title": "Read the hex",
5+
"mission": "This binary hides a secret string in its read-only data. Open the hex viewer and read the ASCII column on the right to recover it. Submit the string exactly as it appears.",
6+
"answer": { "category": "found_value", "expected": "the_flag_is_here" }
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
4+
int check(int n) {
5+
if (n == 1337) {
6+
return 1;
7+
}
8+
return 0;
9+
}
10+
11+
int main(int argc, char **argv) {
12+
char *secret = "the_flag_is_here";
13+
int n = 0;
14+
if (argc > 1) {
15+
n = atoi(argv[1]);
16+
}
17+
if (check(n)) {
18+
printf("unlocked: %s\n", secret);
19+
} else {
20+
printf("wrong number\n");
21+
}
22+
return 0;
23+
}
15.5 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "02-find-the-entry",
3+
"module": "elf-anatomy",
4+
"title": "Find the entry point",
5+
"mission": "Every ELF file records the virtual address where execution begins. Read the ELF header and find the entry point (the e_entry field). Answer in hex or decimal.",
6+
"answer": { "category": "found_value", "expected": 4198496 }
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
4+
int check(int n) {
5+
if (n == 1337) {
6+
return 1;
7+
}
8+
return 0;
9+
}
10+
11+
int main(int argc, char **argv) {
12+
char *secret = "the_flag_is_here";
13+
int n = 0;
14+
if (argc > 1) {
15+
n = atoi(argv[1]);
16+
}
17+
if (check(n)) {
18+
printf("unlocked: %s\n", secret);
19+
} else {
20+
printf("wrong number\n");
21+
}
22+
return 0;
23+
}
15.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)