obfuskator/docs/techniques.md
loki5512344 2b19ed6096
passes: add mba scheme to Constants (and+or+add identity)
(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.
2026-10-14 01:30:10 +02:00

200 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Как работает: техники, плюсы, минусы
Каждая техника описана одинаково: **как работает → что даёт → что стоит /
где ломается**. Цена указана честно: обфускация - это всегда обмен
читаемости и времени против аналитика.
Итоговая таблица - в конце.
## 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 - единственная техника,
которая может сломать рантайм, и она же единственная, чья отдача
не покрывает риск без верификатора.