(a&b)+(a|b) = a+b, documented honestly: falls to the same symbolic
evaluator as xor/add/shift (every sub-expression is a foldable
LDC,LDC,BINOP triple), but defeats a pattern matcher hardcoded to one
opcode ("ldc,ldc,ixor = split constant"). Added to ConstantsConfig
default schemes.
Tests: mba_form_uses_and_or_add_not_a_single_binop, round-trip coverage
in int_forms_round_trip/long_forms_keep_shape.
200 lines
14 KiB
Markdown
200 lines
14 KiB
Markdown
# Как работает: техники, плюсы, минусы
|
||
|
||
Каждая техника описана одинаково: **как работает → что даёт → что стоит /
|
||
где ломается**. Цена указана честно: обфускация - это всегда обмен
|
||
читаемости и времени против аналитика.
|
||
|
||
Итоговая таблица - в конце.
|
||
|
||
## 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` и т.п.), но динамическую конкатенацию - нет.
|
||
- JSON-(де)сериализация по именам полей (Gson/Jackson/`Codec`) не ловится:
|
||
биндинг идёт через `X.class`-литерал, не строку - см. `todo.md` §2.
|
||
- Для библиотек `rename_public_members = false` по умолчанию: менять
|
||
публичный API библиотеки - сломать всех потребителей.
|
||
|
||
Шесть стилей имён (`ShortLatin`, `IlO0`, `Unicode`, `MixedLatin`,
|
||
`DecoyWords`, `Plausible`) - компромисс по-разному: `IlO0`/`Unicode` явно
|
||
сигнализируют «это обфускация», `Plausible` маскируется под обычный
|
||
прикладной код (`TextureCache`, `resolveToken`), ценой того, что при
|
||
ручном чтении он менее заметно отличается от настоящей логики, чем
|
||
намеренный мусор - скрыть обфускацию и скрыть её цену ценой чуть большей
|
||
путаницы для собственной команды при отладке маппингов.
|
||
|
||
**Статус: готово.**
|
||
|
||
## 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`/`mba` +
|
||
opaque-предикаты); `float`/`double` не трогаются. `mba` (`(a&b)+(a|b)=a+b`,
|
||
13.10.2026) не ломает честную оговорку выше - он раскрывается тем же
|
||
символьным вычислителем, что и `xor`/`add`/`shift`. Разница в том, что
|
||
бьёт: `xor`/`add` сворачиваются одним паттерном «два литерала + одна
|
||
операция»; `mba` требует опознать саму тождество `and+or+add`, а не просто
|
||
свернуть литералы - отсекает деобфускаторы с жёстко вшитым паттерном на
|
||
один опкод, но не символьные вычислители.
|
||
|
||
## 4. Обфускация контрольного потока (`Flow`)
|
||
|
||
**Как работает.** CFG перестраивается одной из схем:
|
||
`state-spread` (таблица состояний + `switch`), `computed-switch`
|
||
(счётчик-переменная вместо меток), `butterfly` (обратные переходы),
|
||
`exception-goto` (управление через `throw/catch`).
|
||
|
||
**Плюсы.**
|
||
- Линейный проход кода больше не читается как исходник.
|
||
- В сочетании с opaque-предикатами резко поднимает стоимость трассировки.
|
||
|
||
**Минусы / цена.**
|
||
- **Самая дорогая и самая опасная техника.** Требует пересчёта
|
||
`StackMapTable` для классов версии ≥ 50 - иначе JVM не грузит класс.
|
||
- Ломает профилировщики, coverage-инструменты и отладку.
|
||
- Нужен верификатор: любая ошибка в CFG - это `VerifyError` в рантайме.
|
||
- Шаблонная схема (все методы перестроены одинаково) детектируется
|
||
гистограммой длины блоков.
|
||
|
||
**Статус: готов** (`noxis-passes/src/flow/`). Одна схема на метод (ротация
|
||
через PRNG) плюс нарезка тела `goto`-сплайсами во всех схемах; кадр
|
||
`StackMapTable` пересчитывается после каждой перестройки. Приёмка -
|
||
настоящий `java -Xverify:all` и исполнение под JVM (`calc` печатает 45
|
||
после всех схем). Ограничения: `<init>` пропускается (форма
|
||
super-call-first); методы с `jsr`/`ret`, короче 3 инструкций и длиннее
|
||
`max_insns` пропускаются; `encrypt_block_bodies` добавляет opaque-guard
|
||
на входе. Два правила, выученных на крови (см. `StackMapTable`):
|
||
вставленная метка не должна делить байт-смещение с чужой (иначе кадры
|
||
мерджатся из несовместимых состояний - после каждой такой метки `nop`),
|
||
а инструкция после терминальной обязана иметь свой кадр (поэтому dead-блоки
|
||
входят через opaque-ветвь и кончаются `athrow`, а не висят недостижимыми).
|
||
|
||
## 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`,
|
||
которого нет) - тоже признак.
|
||
|
||
**Статус: готово** (strip + `spoof_source_file`, детерминированная подмена
|
||
от мастер-сида). Не реализовано: `fake_line_numbers` (ложные номера строк,
|
||
а не только `SourceFile`) и `strip_source_file` (полное удаление вместо
|
||
подмены) - см. [`todo.md`](../todo.md) §3.6.
|
||
|
||
## 7. Ресурсы (`Resources`)
|
||
|
||
**Как работает.** Патч текстовых ресурсов по маппингу: `fabric.mod.json`,
|
||
`*.mixins.json`, refmap, access widener, `META-INF/services/*`. Вырезание
|
||
старых подписей `*.SF/*.RSA/*.DSA/*.EC`. Ограничение дубликатов.
|
||
|
||
**Плюсы.**
|
||
- Без этого обфускация Fabric-мода просто не стартует.
|
||
- Убранные подписи - иначе jar считается подделанным.
|
||
|
||
**Минусы / цена.**
|
||
- Патч только текстовых ресурсов: бинарные (картинки, классы в `versions/`)
|
||
трогать нельзя.
|
||
- Промах в патче → тихо неработающий refmap, ловится только запуском.
|
||
|
||
**Статус: готово** (патч текстовых ресурсов по маппингу, включая
|
||
`fabric.mod.json`/`*.mixins.json`/`META-INF/services/*`; срез подписей
|
||
в `noxis-jar`). Не реализовано: типизированный патч бинарных/специфичных
|
||
форматов (refmap, access widener) сверх общего boundary-replace.
|
||
|
||
## Сводная таблица
|
||
|
||
| техника | отдача | риск | цена | статус |
|
||
|---|---|---|---|---|
|
||
| Rename | ★★★★★ | низкий | низкая | готово |
|
||
| Strings | ★★★★ | средний | средняя | готово |
|
||
| Resources | ★★★★★ | низкий | низкая | готово |
|
||
| DebugInfo | ★★★ | низкий | низкая | готово |
|
||
| Constants | ★★ | низкий | низкая | готово (int/long) |
|
||
| Noise | ★ | низкий | раздувание | готово (без аннотаций) |
|
||
| Flow | ★★★★ | **высокий** | высокая | план, кадры готовы |
|
||
|
||
Вывод, который стоит держать в голове: **Rename + Resources + DebugInfo**
|
||
дают большую часть защиты за небольшую цену. Flow - единственная техника,
|
||
которая может сломать рантайм, и она же единственная, чья отдача
|
||
не покрывает риск без верификатора.
|