obfuskator/docs/architecture.md
loki5512344 2172206e83
passes: implement control-flow pass (4 schemes + rotation)
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.
2026-10-09 11:11:55 +02:00

8.3 KiB
Raw 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.