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

8.2 KiB
Raw Permalink Blame History

Архитектура

Слои

                 ┌──────────────────────────┐
   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)

Собственный IR

Пассы не работают с API стороннего парсера - они работают с моделью:

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 без потерь.

Контракт пасса

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 на одинаковой версии даёт байт-в-байт одинаковый выход. Это проверяется тестами.

Конвейер

Порядок пассов - не деталь:

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, план - ../todo.md.