obfuskator/docs/learnings/priorities.md
loki5512344 139aea9a8f
frames,jar,passes: verifier-driven hardening from real jars
Proven by paranoid runs over lorep (17 classes, fully green),
LoVisual (4081 classes) and oc2r (1278 classes) with per-class
java -Xverify:all, each fix first reproduced as a red test:

- merge: Top absorbs instead of Top-wins (javac declares Top for
  slots unwritten on some inflow; precise types reject those edges).
  Regression: loop_head_keeps_top_for_loop_local.
- emit: collapse [Long|Double, Top] pairs on operand stacks
  (javac writes one entry; the pair is rejected). Regression:
  wide_stack_join_emits_single_entries.
- xfer ArrayLoad: push the wide pair, fixing lcmp underflow.
  Regression: array_load_wide_pushes_pair.
- xfer <init>: convert only the receiver's own allocation site
  instead of every Uninitialized (nested construction clobbered
  the outer object). Regression: nested_construction_keeps_outer.
- xfer ANewArray: array operands gain one dimension ([B] -> [[B)
  instead of an illegal L-wrap ([L[B;). Covered by the same test.
- jar load: duplicate class names (multi-release module-info
  twins) keep the first occurrence instead of crashing save.
  Test: duplicate_class_names_keep_first.
- strings/constants: skip methods past MAX_METHOD_INSNS (8000)
  so giants cannot overflow the 64KB writer ceiling. Tests:
  oversized_methods_are_skipped in both passes.
- merge errors now name both types being merged.

Also: em-dash removed repo-wide (724 spots, rule added to
todo.md), allow-lints only in tests (scratch example deleted),
code comments migrated to English in every file this change
touches.
2026-10-09 13:47:27 +02:00

9.4 KiB
Raw Blame History

Что улучшим: стратегия и приоритеты

Материал собран из info/evo-improvements.md (план после разбора EvoVis) и info/evo-weaknesses.md (провалы). Здесь - что мы берём, в каком порядке и что осознанно не делаем.

Стратегия в одном абзаце

Максимум защиты при минимуме риска сломать рантайм. Дешёвые техники, дающие 80% результата, включаются по умолчанию; дорогие и опасные - включаются только после того, как для них есть верификатор. Каждая техника имеет измеримый критерий «сработала/не сработала», иначе это не защита, а мнение.

P0 - базовый уровень (дёшево, снимает главные утечки)

пункт что это пасс статус
A1 переименование всех символов: классы, поля, методы, локальные переменные, параметры, record-компоненты, enum-константы, SourceFile Rename ✅ готово
A2 вычистить LineNumberTable/LocalVariableTable/SourceFile/InnerClasses/Signature/MethodParameters DebugInfo ✅ готово
A3 никаких сигнатурных имён у защитного кода, защитный код смешан с обычным Rename + Noise ✅ готово
A4 обфусцировать весь архив, включая ресурсы, mixin-JSON, access widener, service loader'ы Resources ✅ готово
- маппинг в отчёт (иначе не читаем свои же stack trace) Rename ✅ готово

Это самый дешёвый блок и одновременно самый большой утечка в разобранном образце: оригинальные SourceFile у 160 классов, имена полей и методов, каталог модулей в атрибутах.

P1 - строки и константы

пункт что это пасс статус
B1 ключ на строку + ротация 3–5 схем + MAC + уникальный nonce Strings ✅ готово
B2 вывод ключа в рантайме, а не константа в вызове Strings + генератор декодера ✅ готово
B3 константы - не тождества, а вычисления, разнесённые по блокам Constants ✅ готово (int/long)
B4 ложная отладочная информация: чужие имена переменных и строк DebugInfo ✅ SourceFile

B2 - главное отличие от разобранного образца: у него ключ был константным выражением в вызове и расшифровывался статически, без JVM. Мастер-секрет теперь собирается в рантайме из двух частей (k1 ^ k2) декодером класса - см. ../stack.md §4. B4 пока покрывает только SourceFile; ложные номера строк (fake_line_numbers) - см. ../../todo.md §3.6.

P2 - контрольный поток

пункт что это статус
C1 несколько схем флаттенинга вместо одной, с мешаниной внутри метода план
C2 верификатор обязателен: ошибка CFG = VerifyError ✅ готово (кадры + java -Xverify:all)
C3 реальный объём ≠ раздувание: бюджет на размер и время ✅ размер; время - план

Приоритет здесь строго подчинён: C2 раньше C1. Кадры StackMapTable и приёмка java -Xverify:all уже готовы (см. ../stack.md §3) - единственное, чего не хватает, это сами схемы флаттенинга (C1).

P3 - измеримость (метрики, без которых ничего не доказано)

Обязательный блок, иначе фраза «обфускация есть» ничего не означает. Крейт noxis-verify реализован - вот что из списка уже цифра, а что ещё мнение:

  1. Утечки имён. ✅ collect_names/find_leaks - сверка идентификаторов с учётом keep-списка.
  2. Читаемость декомпилята. ⬜ Прогон JADX/CFR → доля распознанных структур - не начато.
  3. Противник. ✅ scan_secrets + RED_PATTERNS держим в репозитории; ⬜ полноценный скрипт-деобфускатор (свёртка констант) как тест - план.
  4. Семантика. ⬜ Эталонные программы до/после - не начато (сейчас семантику подтверждает только java -Xverify:all).
  5. Бюджеты. ✅ размер (inflation_ratio/check_inflation); ⬜ время исполнения - не начато.
  6. Стабильность между сборками. ✅ mappings_changed - маппинги обязаны отличаться между прогонами с разным seed.

Итог: noxis-verify закрывает красные паттерны, утечки имён, java -Xverify:all, бюджет размера и стабильность маппингов. Остаются декомпилят-метрика, бюджет времени и эталонная семантика - три пункта, где «нам кажется защищённым» всё ещё мнение, а не цифра.

P4 - долгий горизонт (пока не делаем)

пункт что это почему не сразу
D1 маленький VM/interpreter для ценных кусков цена окупается только на ключевом коде
D2 split-function / раскрываемое по шагам тело режет обычный профилировщик, сложная отладка
D3 мягкий анти-анализ в рантайме требует своего нативного компонента
D4 честный anti-tamper, а не «зашейрь ключ» целая инженерная задача, не пасс
E собственный нативный/Rust-цель вместо JVM другой продукт, не наша цель

D1 - явно ограничен: включать только для действительно ценных функций, остальное оправдания не имеет. E - отдельный продукт; если он когда-нибудь понадобится, наш IR как раз делает его возможным (те же пассы, другой writer).

Что мы добавили сверх исходного плана

  1. Структурный гейт при компиляции. Лимиты «250 строк / 4 файла» гоняются в build.rs - см. ../README.md. Без этого план P0–P3 превращается в один гигантский файл через месяц.
  2. Собственный IR с метками. Исходный план предполагал работу поверх готового парсера. Insn::Mark(Label) убирает пересчёт смещений из всех пассов - это меняет цену каждой техники, меняющей CFG.
  3. Детерминированный PRNG с доменным разделением. Без этого пункт «стабильность между сборками» невозможно проверить тестом.
  4. Keep как отдельный крейт (noxis-analysis), а не флаг CLI. Рефлексия, JNI, сериализация и entrypoint'ы - это анализ иерархии, а не настройка, которую надо помнить руками.

Порядок выполнения

Смотрите ../../todo.md. P0–P3 и P3-измеримость уже готовы; единственный крупный пункт, который остался, - схемы Flow (C1), потому что верификатор (C2) для них уже есть:

✅ P0 DebugInfo + Resources      → главные утечки сняты
✅ P1 Strings (B2) + Constants   → ключ выводится, схемы ротируются
✅ P1 backend (ristretto) + frames
✅ P2 Flow (C1) - готов (09.10.2026): 4 схемы + ротация + мешанина, приёмка настоящим `java`
✅ P3 метрики, красные паттерны, CLI