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:
commit
a641e69455
140 changed files with 17197 additions and 0 deletions
128
docs/architecture.md
Normal file
128
docs/architecture.md
Normal 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue