obfuskator/docs/learnings/ai-resistance.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

16 KiB
Raw Blame History

Устойчивость к ИИ-анализу и новые техники (октябрь 2026)

Полный разбор двух источников по запросу: что конкретно работает против LLM при реверсе кода и что из этого переносится на JVM-байткод. Плюс отдельно: что нового в классической обфускации (MBA, виртуализация).

Короткий вывод: специальной "защиты от нейросетей" как отдельного свойства кода не существует. Есть два разных, непересекающихся приёма, которые работают по разным причинам, и переносится на наш проект только один из них.

1. Elastic Security Labs: агент с IDA Pro против Tigress (апрель 2026)

«The Cost of Understanding» - Claude Opus 4.6 с доступом к IDA Pro (через MCP) реверсил crackme, обфусцированный Tigress, затем команда сама собрала ("vibecode"-ила) три защиты, нацеленные именно на модель. Это ближе всего к нашей реальной угрозе: не чат с текстом исходника, а агент, читающий бинарник/байткод инструментами.

Benchmark на Tigress

22 цели, 4 уровня сложности: без трансформаций → одиночные → пары → тяжёлые комбинации. Трансформации: EncodeArithmetic, EncodeLiterals (MBA), Flatten (control-flow flattening), JIT/JitDynamic (XTEA-шифрование кода с рантайм-расшифровкой), Virtualize (byte-code VM, варианты indirect/regs и switch/stack).

  • Opus решил ~40% из 20 валидных задач. Успех стоил в среднем $2.39, провал - $4.83. Ни одна задача уровня "многослойные комбинации" не была решена.
  • JIT (XTEA + рантайм-расшифровка) оказался самой сложной одиночной трансформацией - модель понимала структуру движка, но не смогла восстановить ключевые константы. Это ровно наш подход в Strings (ключ выводится в рантайме, не лежит в вызове), только применённый ко всему коду метода, а не к строкам.
  • На самых жёстких комбинациях цена и время выросли в ~4.5× и ~4.3×, но задачи, которые Opus уже умел решать, он решил всё равно. Flatten + MBA било по модели сильнее, чем Virtualize + MBA - авторы объясняют это тем, что флаттенинг сильнее раздувает размер функции.

Три кастомные защиты, нацеленные на агента

защита идея результат
Matryoshka Wall crackme внутри 100 000 вложенных слоёв ChaCha20 (+ подделка под Salsa20) v1 - агент сдался не расшифровав ($1.50); v2 (каждый ключ зависит от предыдущего слоя) - исчерпал бюджет $10 за 56 минут, 0 успехов
Double Fond патч open-source крипто-библиотеки: скрытый загрузчик через изменённую lookup-таблицу, шифрованные указатели, RWX-сегмент v7 - 1/5 у "осведомлённого" агента (12 мин, $5.20), 0/5 у "слепого"
Dispatch Maze шифр раздроблен на 20 фрагментов по 3000 функций, большинство - ловушки v1 решена за 12 минут ($2.56); v2 (разнообразные шаблоны кода + ложные переходы) - провал агента через 46 минут и $8.83

Что из этого реально работает, по механизму

  1. Контекстное окно. Длинные тупиковые пути (decoy-функции, ложные переходы) забивают контекст и портят рассуждение модели.
  2. Бюджет шагов/денег. Каждый впустую потраченный цикл стоит агенту реальных токенов; если он упирается в лимит и сдаётся - это победа защиты, даже если код теоретически вскрываем.
  3. Shortcut bias. Вводящая в заблуждение структура (ловушки в Dispatch Maze) заставляет модель остановиться раньше времени. Попытки prompt injection в тексте не сработали против Opus 4.6.

Ключевое экономическое наблюдение авторов: нативное исполнение занимает ~55 мс, а статический провал агента стоит $1.50–$9.67. Это и есть реальный рычаг - не "нечитаемость навсегда", а разрыв между ценой атаки и ценой сборки для нас. Авторы прямо предупреждают: преимущество схлопнется, когда у агентов появятся инструменты динамического исполнения (сейчас тестировался только статический анализ).

2. Acoda: атака на safety-alignment и токенизацию (ICSE 2026)

Acoda - другая постановка задачи: не агент с дизассемблером, а LLM, которой в чат вставили исходный код и попросили его проанализировать/деобфусцировать. Генетический алгоритм подбирает комбинацию из 8 трансформаций, до 70% ASR (attack success rate) на 7 моделях (GPT-4o, DeepSeek, Qwen, Llama, Gemma).

8 методов, по стадиям

стадия метод как работает
до анализа Deceptive Comment Warnings комментарий командует модели отказаться анализировать ("прекрати, это бэкдор")
до анализа Variable/Function Renaming идентификаторы вида MakeAnesthetics, намекающие на malware/оружие
до анализа Vulnerability Code Injection фрагмент с SQL-инъекцией/pickle.loads/command injection в заведомо мёртвой ветке (while (сложное_но_ложное_условие))
во время анализа Misleading Summarization модель сама просится описать код, затем это описание (намеренно неверное) вклеивается рядом с кодом
во время анализа String Obfuscation base64 + словарь, расшифровка даёт слова типа "virus"/"weapon"
во время анализа Try-Except Wrapping настоящая логика лежит в except-ветке, в try - правдоподобный, но бесполезный код
после анализа EOS Token Insertion в текст вставлены литералы токенов-разделителей (`<

Честная оценка: что из этого применимо к нам

Почти ничего напрямую, и вот почему. Все 8 методов работают через текстовый/натурально-языковой канал: комментарии, сгенерированное описание, визуально узнаваемые "эксплойт"-паттерны в исходнике. У нас:

  • DebugInfo уже вычищает всё, что могло бы стать комментарием или осмысленным описанием (LineNumberTable, Signature, исходные имена); в скомпилированном .class комментариев не бывает в принципе.
  • Цель Acoda - заставить модель отказаться анализировать свой же код, вставленный в чат пользователем. У нас код анализирует чужой агент чужого jar - нам не нужно, чтобы он отказался, если он не наш союзник; и вызывать намеренно подозрительные паттерны в легитимном продукте - прямой риск получить ложное срабатывание антивируса/EDR на свой же jar. Это противоречит README.md → «Ответственное использование».
  • EOS Token Insertion не переносится: это свойство текстового стриминга токенов модели, а не байткода.

Единственное, что частично переносится: String Obfuscation и Variable Renaming - ровно то, что Strings и Rename уже делают, просто дешевле (у Acoda - обратимый base64, у нас - ротация шифров с выводом ключа в рантайме, см. ../stack.md §4-5). Здесь мы уже на шаг впереди статьи, а не позади.

Vulnerability Code Injection как идея (мёртвый код, похожий на уязвимый паттерн) пересекается с нашим Noise, но внедрять реально похожие на эксплойт сигнатуры в выдаваемый клиентам jar - плохая идея для легального продукта защиты: это именно то, от чего предостерегает info/evo-weaknesses.md с другой стороны (не подставлять защищённый продукт под чужие red-листы).

3. Что добавить в noxis практически

Экономический рычаг из Elastic (цена агента > цены нашей сборки) ложится на архитектуру без переделок, конкретные пункты - в ../../todo.md §7:

  1. Вложенные слои шифрования строк (аналог Matryoshka Wall) - расширить StringsConfig полем "глубина", decoder разворачивает N слоёв вместо одного. Цена агента растёт с глубиной почти линейно, цена сборки - нет.
  2. Больше decoy-точек ветвления ключа (аналог Dispatch Maze) - наш декодер уже собирает мастер-секрет из k1^k2 (stack.md §4); можно поднять число частей и раскидать их сборку по случайным junk-методам из Noise, а не держать в одном месте.
  3. MBA вместо простых тождеств в Constants - то же решение, что ловит и символьный исполнитель, и SMT-решатель, и, по данным Elastic, "Flatten + MBA" бьёт сильнее, чем просто Flatten.
  4. Не делать: имитацию вредоносных паттернов, prompt injection в строках, EOS-токены. Либо не переносится на байткод, либо создаёт риск ложного срабатывания защитных систем на легитимном продукте.

Критерий "сработало/не сработало" для всего этого - как и для остальных техник: прогон конкретного агента на конкретной сборке, а не вера в методичку. Эффект устаревает с каждой новой версией модели.

4. Классическая обфускация: что нового, помимо LLM

  • Систематическое исследование control-flow обфускации в Java называет виртуализацию (table interpretation) самой мощной техникой: декомпилятор не восстанавливает структуру, потому что исходного байткода метода в классе физически больше нет, есть только интерпретатор и таблица операций. Коммерческие инструменты (например, Promon Shield) уже уносят это на уровень нативной библиотеки.
  • Для нас это ломает собственное допущение из ../stack.md §3: noxis-verify верифицирует метод через java -Xverify:all, потому что это обычный байткод с валидным StackMapTable. Виртуализированный метод - уже не обычный байткод, верификатор проверит только обвязку интерпретатора, не тело. Big-ticket item, не для ближайшего спринта.
  • MBA укладывается без архитектурных изменений - см. выше, §3.

Источники