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.
This commit is contained in:
Loki 2026-10-08 22:01:03 +02:00
commit a641e69455
140 changed files with 17197 additions and 0 deletions

167
docs/techniques.md Normal file
View file

@ -0,0 +1,167 @@
# Как работает: техники, плюсы, минусы
Каждая техника описана одинаково: **как работает → что даёт → что стоит /
где ломается**. Цена указана честно: обфускация — это всегда обмен
читаемости и времени против аналитика.
Итоговая таблица — в конце.
## 1. Переименование (`Rename`)
**Как работает.** Классы получают случайные имена в случайных пакетах
(или в исходном пакете, если `spread_packages = false`). Поля и методы
переименовываются, если `method_rename_safety` считает их безопасными
(private/static/final — сразу; virtual — только «семейством» на всю
иерархию, чтобы не разъединить переопределения и мостовые методы).
Затем переписываются дескрипторы, `LDC`-классы, вызовы, поля,
исключительная таблица, `Signature`/`Exceptions`/`InnerClasses`.
**Плюсы.**
- Самая дешёвая и самая отдающая техника: читает `javap -p` любой анализатор.
- Не меняет семантику — риск сломать код минимален.
- Маппиинг пишется в отчёт → stack trace'и переводятся обратно.
**Минусы / цена.**
- Keep-список обязателен: рефлексия по имени (`Class.forName("pkg.C")`),
JNI, `serialVersionUID`, сериализация, entrypoint'ы. Пропустил — получил
`ClassNotFoundException` в рантайме.
- Рефлексия через строки мы ловим автоматически (анализ `Class.forName`,
`getMethod`/`getField` и т.п.), но динамическую конкатенацию — нет.
- Для библиотек `rename_public_members = false` по умолчанию: менять
публичный API библиотеки — сломать всех потребителей.
**Статус: готово.**
## 2. Шифрование строк (`Strings`)
**Как работает.** Каждый `LDC` со строкой короче `min_len` заменяется на
`LDC cipher; LDC salt; INVOKESTATIC d(String,I)String`. Ключ строки
выводится из мастер-секрета + salt, **не лежит рядом с шифротекстом**.
Между строками ротируются разные схемы (`xor-splitmix`, `xtea-ctr`), nonce
уникален на строку, MAC ловит порчу.
**Плюсы.**
- Строки — главный источник логики (`"admin"`, URL, SQL, regex).
- Ротация схем: один скрипт деобфускации не покрывает весь jar.
- MAC: подмена шифротекста не «молча» портит текст, а падает.
**Минусы / цена.**
- Декодер добавляет вызов на каждый доступ к строке → оверхед в рантайме.
- Целевой анализ декодера всё равно раскроет текст; задача — поднять цену,
а не сделать невозможным.
- Мастер-секрет собирается в декодере из двух частей (`k1 ^ k2`).
**Статус: готово** (подстановка + генератор декодера v49 + сквозной тест
исполнением под настоящим `java`).
## 3. Обфускация констант (`Constants`)
**Как работает.** Числовая константа не хранится как есть, а собирается из
разных выражений: `x ^ k`, `a + b`, `(a << 1) + c`. Плюс настоящие
opaque-предикаты — условия, истинность которых зависит от инварианта
(например, `i*(i+1) % 2 == 0`), а не от константы.
**Плюсы.**
- Дёшево, ломает поиск «магических чисел».
- Оpaque-предикаты добавляют ветки, по которым деобфускатор должен пройти.
**Минусы / цена.**
- Ребёнок числа раскрывается за минуты любым символном вычислителем
(`executor` для `LCONST/ICONST`-цепочек).
- Развдувание кода растёт по квадрату от числа вставок.
- Шаблонный opaque-предикат (всегда один и тот же `A+B`) узнаётся
паттерном — отсюда требование нескольких схем.
**Статус: готово** для `int`/`long` (`xor`/`add`/`shift` + opaque-предикаты);
`float`/`double` не трогаются.
## 4. Обфускация контрольного потока (`Flow`)
**Как работает.** CFG перестраивается одной из схем:
`state-spread` (таблица состояний + `switch`), `computed-switch`
(счётчик-переменная вместо меток), `butterfly` (обратные переходы),
`exception-goto` (управление через `throw/catch`).
**Плюсы.**
- Линейный проход кода больше не читается как исходник.
- В сочетании с opaque-предикатами резко поднимает стоимость трассировки.
**Минусы / цена.**
- **Самая дорогая и самая опасная техника.** Требует пересчёта
`StackMapTable` для классов версии ≥ 50 — иначе JVM не грузит класс.
- Ломает профилировщики, coverage-инструменты и отладку.
- Нужен верификатор: любая ошибка в CFG — это `VerifyError` в рантайме.
- Шаблонная схема (все методы перестроены одинаково) детектируется
гистограммой длины блоков.
**Статус: не реализован; блокер — `StackMapTable` (см. [`stack.md`](stack.md)).**
## 5. Шум (`Noise`)
**Как работает.** Junk-классы и junk-аннотации, вплетённые в граф
достижимости (`weave_reachable`): класс считается достижимым, если на него
есть ссылка из настоящего кода.
**Плюсы.**
- Дешёвый объём: файлов много, логики ноль.
- Мусор в дереве файлов ухудшает автоматический разбор.
**Минусы / цена.**
- Reachability-анализ отсеивает junk мгновенно: класс, на который никто
не ссылается, — мёртвый груз. Отсюда обязательное `weave_reachable`.
- Пустые методы/тела — признак, который ищут первым.
- Раздувание jar.
**Статус: готово** (junk-классы + `weave_reachable`; junk-аннотации
пропускаются — сырые атрибуты не несут свежие индексы пула).
## 6. Отладочная информация (`DebugInfo`)
**Как работает.** Strip `LineNumberTable`, `LocalVariableTable`; подмена
`SourceFile` на ложное имя; опционально strip `Signature`/`InnerClasses`.
Плюс *ложная* отладочная информация — чужие имена переменных и номера строк.
**Плюсы.**
- Практически бесплатно.
- Восстанавливает исходные имена переменных и файлов — прямая утечка.
**Минусы / цена.**
- Убивает нормальные stack trace'и (поэтому маппинги в отчёте обязательны).
- Слишком «чистый» класс (`LineNumberTable` есть, а `SourceFile` = `X.java`,
которого нет) — тоже признак.
**Статус: не реализован.**
## 7. Ресурсы (`Resources`)
**Как работает.** Патч текстовых ресурсов по маппингу: `fabric.mod.json`,
`*.mixins.json`, refmap, access widener, `META-INF/services/*`. Вырезание
старых подписей `*.SF/*.RSA/*.DSA/*.EC`. Ограничение дубликатов.
**Плюсы.**
- Без этого обфускация Fabric-мода просто не стартует.
- Убранные подписи — иначе jar считается подделанным.
**Минусы / цена.**
- Патч только текстовых ресурсов: бинарные (картинки, классы в `versions/`)
трогать нельзя.
- Промах в патче → тихо неработающий refmap, ловится только запуском.
**Статус: не реализован (крейт `noxis-jar` — план).**
## Сводная таблица
| техника | отдача | риск | цена | статус |
|---|---|---|---|---|
| Rename | ★★★★★ | низкий | низкая | готово |
| Strings | ★★★★ | средний | средняя | готово |
| Resources | ★★★★★ | низкий | низкая | план |
| DebugInfo | ★★★ | низкий | низкая | план |
| Constants | ★★ | низкий | низкая | готово (int/long) |
| Noise | ★ | низкий | раздувание | готово (без аннотаций) |
| Flow | ★★★★ | **высокий** | высокая | план, ждёт frames |
Вывод, который стоит держать в голове: **Rename + Resources + DebugInfo**
дают большую часть защиты за небольшую цену. Flow — единственная техника,
которая может сломать рантайм, и она же единственная, чья отдача
не покрывает риск без верификатора.