FlowPass rebuilds one method per scheme picked from FlowConfig: state-spread (dense tableswitch dispatcher), computed-switch (sparse lookupswitch over a counter local), butterfly (branch inversion), exception-goto (dispatcher under try with a dead catch-all), plus opaque goto-splices inside every method and StackMapTable recomputation afterwards. Now in standard_pipeline (between constants and noise); --no-flow is an honest off-flag. Acceptance is real JVM, not shapes: per-scheme verify, rotation spread, calc-prints-45 execution, paranoid smoke on lorep-1.1.0 (131 methods rebuilt, no new failures over standard). Two verifier rules learned the hard way are now construction invariants: no inserted mark shares a bytecode offset with a foreign one (nop pads), and no frameless gap follows a terminal (dead blocks are opaque-entered and throw). Also fixes a latent frame-emitter bug the pass exposed: wide stack values were written as slot pairs ([Double, Top]) while javac writes one entry ([Double]); HotSpot rejects the pair. Stacks now collapse like locals (emit::stack_vars), with a regression test in classfile/tests/frames.rs.
129 lines
8.3 KiB
Markdown
129 lines
8.3 KiB
Markdown
# Архитектура
|
||
|
||
## Слои
|
||
|
||
```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` |
|
||
| **готово** | пассы rename/resources/strings/constants/noise/debug, генератор декодера, вывод мастер-секрета в рантайме |
|
||
| **готово** | `noxis-verify` (red-паттерны, `java -Xverify:all`, метрики), CLI (`obfuscate`/`analyze`/`patterns`) |
|
||
| **готово** | пасс `flow` (4 схемы + ротация + мешанина, приёмка настоящим `java`) |
|
||
| **план** | фикстуры и regression-тесты |
|
||
|
||
Детали выбора — [`stack.md`](stack.md), план — [`../todo.md`](../todo.md).
|