obfuskator/docs/architecture.md
loki5512344 a641e69455 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.
2026-10-08 22:01:03 +02:00

8.2 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
P2 пассы строк/констант/шума/потока, генератор декодера, CLI
P2 генерация класса-декодера, вывод мастер-секрета, остальные пассы
P3 CLI, метрики, интеграция с java -Xverify:all

Детали выбора — stack.md, план — ../todo.md.