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:
commit
a641e69455
140 changed files with 17197 additions and 0 deletions
167
docs/techniques.md
Normal file
167
docs/techniques.md
Normal 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 — единственная техника,
|
||||
которая может сломать рантайм, и она же единственная, чья отдача
|
||||
не покрывает риск без верификатора.
|
||||
Loading…
Add table
Add a link
Reference in a new issue