Initial commit: noxis JVM bytecode obfuscator

Workspace of 9 crates (noxis, noxis-core, noxis-classfile, noxis-analysis,
noxis-passes, noxis-runtime, noxis-jar, noxis-verify, noxis-structure-check).
AGPL-3.0-only license, Rust 2024 edition, unsafe_code denied.
This commit is contained in:
Loki 2026-10-08 22:01:03 +02:00
commit a641e69455
140 changed files with 17197 additions and 0 deletions

128
docs/architecture.md Normal file
View file

@ -0,0 +1,128 @@
# Архитектура
## Слои
```text
┌──────────────────────────┐
CLI (noxis) │ clap → конфиг → отчёт │
└────────────┬─────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ noxis-jar │ │noxis-passes │ │noxis-verify │
│ ZIP, ресурсы│ │ трансформации│ │ red-паттерны │
└──────┬──────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ ┌──────┴───────┐ │
│ │ noxis-core │ │
│ │ конфиг, seed │ │
│ │ маппинги, Pass│ │
│ └──────┬───────┘ │
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────┐
│ noxis-classfile (IR) │
│ Program → ClassFile → Code → Insn (метки, не смещения)│
│ model/ format/ insn error │
└────────────────────────────────────────────────────────┘
▲
┌──────┴──────┐ ┌───────────────┐ ┌──────────────────┐
│noxis- │ │noxis-runtime │ │noxis-structure- │
│analysis │ │ шифры, ABI │ │check (build-time)│
└─────────────┘ └───────────────┘ └──────────────────┘
```
Зависимости направлены только вниз. Циклов нет: сборка конвейера живёт в
`noxis-passes`, потому что `noxis-core` знает только контракт `Pass`.
## Крейты
| крейт | ответственность |
|---|---|
| `noxis-classfile` | собственный IR классовых файлов, чтение/запись, `StackMapTable` |
| `noxis-core` | конфигурация, детерминированный seed, маппинги, контекст и контракт `Pass` |
| `noxis-analysis` | иерархия наследования, entrypoint'ы, рефлексия по имени, keep-правила |
| `noxis-passes` | сами трансформации + сборка стандартного конвейера |
| `noxis-runtime` | схемы шифрования строк и ABI класса-декодера |
| `noxis-jar` | ZIP: чтение, перепаковка, вырезание подписей, патч ресурсов |
| `noxis-verify` | red-паттерны, `java -Xverify:all`, метрики |
| `noxis` | CLI |
| `noxis-structure-check` | автогейт структурных правил (см. [`README.md`](README.md)) |
## Собственный IR
Пассы не работают с API стороннего парсера — они работают с моделью:
```text
Program все классы + ресурсы архива
├─ ClassFile версия, флаги, this/super, поля, методы, атрибуты
│ ├─ Field
│ └─ Method
│ └─ Code max_stack/max_locals, insns, exception_table, frames
└─ Resource путь + байты внутри архива
```
Ключевое решение — **`Insn::Mark(Label)` вместо байтовых смещений**:
- любой пасс, меняющий порядок или длину инструкций, не пересчитывает
jump-офсеты;
- пересчёт делается ровно один раз — в writer'е;
- в IR нет состояния, в котором «смещение уехало, а метка осталась».
Атрибуты, которые мы не понимаем, хранятся как `Attribute::Raw { name, data }`
и возвращаются на место без изменений — round-trip без потерь.
## Контракт пасса
```rust
pub trait Pass: Send + Sync {
fn id(&self) -> PassId;
fn apply(&self, ctx: &mut Context, program: &mut Program)
-> Result<PassReport, CoreError>;
}
```
- `Context` — всё состояние прогона: конфиг, мастер-сид, маппинги, заметки.
Из него берётся RNG: `ctx.rng("rename/classes")` — доменное разделение,
чтобы случайности разных пассов не были статистически коррелированы.
- `PassReport` — что трансформировано, что сознательно пропущено и почему.
- `Report` — собирает отчёты всех пассов + счётчики, сериализуется в JSON.
Детерминированность — свойство, а не пожелание: одинаковый seed на одинаковой
версии даёт байт-в-байт одинаковый выход. Это проверяется тестами.
## Конвейер
Порядок пассов — не деталь:
```text
Rename → Resources → Strings → Constants → Flow → Noise → DebugInfo
```
почему именно так:
1. **Rename первым** — все остальные техники порождают новые ссылки
(`LDC`-шум, вызовы декодера, junk-классы), и они уже должны быть в
обфусцированных именах.
2. **Resources сразу после Rename** — патч `fabric.mod.json`, `*.mixins.json`,
refmap и access widener должен видеть *итоговые* имена классов.
3. **Strings и Constants** — работают по телам методов, ещё не искажённым
control-flow'ем.
4. **Flow** — искажает CFG. Если поставить его раньше, Strings/Constants
перестанут находить свои шаблоны в инструкциях.
5. **Noise** — junk-классы и аннотации добавляются в самом конце, когда
целевой граф уже финален.
6. **DebugInfo последним** — strip/spoof отладочной информации после всех
трансформаций, чтобы не породить свежие `LineNumberTable`.
## Статус
| этап | что |
|---|---|
| **готово** | IR, модель, конфиг, seed/PRNG, маппинги, анализ, keep, переименование, шифры строк, гейт правил, `format::parse`/`write` поверх ристретто, вывод кадров `StackMapTable` |
| **P2** | пассы строк/констант/шума/потока, генератор декодера, CLI |
| **P2** | генерация класса-декодера, вывод мастер-секрета, остальные пассы |
| **P3** | CLI, метрики, интеграция с `java -Xverify:all` |
Детали выбора — [`stack.md`](stack.md), план — [`../todo.md`](../todo.md).