obfuskator/docs/architecture.md
loki5512344 139aea9a8f
frames,jar,passes: verifier-driven hardening from real jars
Proven by paranoid runs over lorep (17 classes, fully green),
LoVisual (4081 classes) and oc2r (1278 classes) with per-class
java -Xverify:all, each fix first reproduced as a red test:

- merge: Top absorbs instead of Top-wins (javac declares Top for
  slots unwritten on some inflow; precise types reject those edges).
  Regression: loop_head_keeps_top_for_loop_local.
- emit: collapse [Long|Double, Top] pairs on operand stacks
  (javac writes one entry; the pair is rejected). Regression:
  wide_stack_join_emits_single_entries.
- xfer ArrayLoad: push the wide pair, fixing lcmp underflow.
  Regression: array_load_wide_pushes_pair.
- xfer <init>: convert only the receiver's own allocation site
  instead of every Uninitialized (nested construction clobbered
  the outer object). Regression: nested_construction_keeps_outer.
- xfer ANewArray: array operands gain one dimension ([B] -> [[B)
  instead of an illegal L-wrap ([L[B;). Covered by the same test.
- jar load: duplicate class names (multi-release module-info
  twins) keep the first occurrence instead of crashing save.
  Test: duplicate_class_names_keep_first.
- strings/constants: skip methods past MAX_METHOD_INSNS (8000)
  so giants cannot overflow the 64KB writer ceiling. Tests:
  oversized_methods_are_skipped in both passes.
- merge errors now name both types being merged.

Also: em-dash removed repo-wide (724 spots, rule added to
todo.md), allow-lints only in tests (scratch example deleted),
code comments migrated to English in every file this change
touches.
2026-10-09 13:47:27 +02:00

129 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура
## Слои
```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).