obfuskator/info/evo-weaknesses.md
loki5512344 a641e69455 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.
2026-10-08 22:01:03 +02:00

5.6 KiB
Raw Blame History

Где защита EvoVis провалилась — чего нам делать нельзя

Разобрано полностью за один проход, без единого рантайм-хака. Каждый пункт — что именно дало ключ атаки.

1. Ключ шифрования строк лежит рядом со шифротекстом

awc.d("<cipher>", <opaque-выражение>) — ключ это константное выражение в вызове. Ни secrets, ни runtime-derivation: выражение просто сворачивается в int (любой парсер вроде scripts/expr.rs, ~200 строк). Итог: 8993 вызова расшифрованы детерминированно, 0 обращений к JVM.

Хуже: шифр один на весь продукт (LCG с фиксированными a/c), т. е. зная одну пару «шифротекст → текст», остальные не нужно даже сворачивать — можно учиться.

2. Обфусцированы только типы, но не члены

onDraw, getProjectiles, BreakingBad, key(), isAuthorized(), setUser — открыто. Семантика класса читается по одному javap -p. Класс с тремя понятными методами опознаётся за секунду, даже если имя wb.

3. Метаданные и отладочная информация не тронуты

  • SourceFile: "StringRuntime.java" / "KeyAssembler.java" / "PayloadCrypto.java" — оригинальные имена 160 классов (все миксины — 100%).
  • LineNumberTable (10 218) + LocalVariableTable (151) — имена локальных переменных.
  • @rn(name=..., description=...) — полный каталог всех 57 модулей с описаниями.
  • fabric.mod.json, *.mixins.json (пути и состав), accesswidener, ресурсы.

Все эти вещи дешевле всего чинить и дороже всего отдавать.

4. Flattening сделан «по учебнику»

Один шаблон на весь продукт: while(true) { switch (i + i2) }, номер состояния — сумма двух opaque-полей, переходы — присваивание пары. Один regex находит 3530 автоматов. Декомпилятор восстанавливает CFG, потому что:

  • switch по константным меткам (метки не закодированы);
  • все переходы — прямые присваивания в одном basic block;
  • нет вычислимых на входе предикатов, нет переходов через таблицы/массивы;
  • нет обфускации самих тел блоков (тела — обычный код).

5. Opaque-предикаты сводятся к A+B

(A^B)+((A&B)<<1) и (A|B)+(A&B) — это школьные тождества. Распознаются регуляркой, сворачиваются в один проход, заодно выдают «бесплатный» фолдинг остальных констант. Ни одного настоящего opaque-предиката (зависящего от состояния памяти/окружения и всегда истинного) в коде нет.

6. Декой-классы детектируются reachability

42 файла, ни одного входящего ссылока, dangling=0. Мгновенно вычисляются BFS'ом от entrypoint'ов (их, кстати, 4 и они раскрыты в fabric.mod.json). Балласт не смешан с графом и не создаёт ложных рёбер.

7. Каждая защита живёт отдельно и называется своим именем

StringRuntime, KeyAssembler, AttestClient, RuntimeGuard, SealedBoot, NativeGuard — названия прямо говорят атакующему, куда смотреть. Плюс open-source идентификаторы: aln упомянут в рефлексии строкой, awc — по имени вызова.

8. Вторичные компоненты вообще без защиты

  • evo-agent.jar (расшифровщик payload) — открытые строки, SourceFile, без обфускации.
  • Лаунчер .NET — статическая XOR-таблица b ^ i ^ 0xAA, имена сервисов не тронуты.
  • Защита ключа в нативе сильная, но код мода защищён слабее, чем ключ.

9. Шифр без аутентификации и без per-string salt

LCG+XOR: нет MAC, нет случайного сида на строку, нет индивидуального алгоритма. Частичное повреждение шифротекста не обнаруживается, анализ по частотам работает.

Опровержение (чтобы не повторять чужие ошибки в выводах)

  • «Сдвиги в байткоде могут быть отрицательными (>> -8 == >> 24)» — по всем 1277 классам 0 таких случаев (32 559 сдвигов, константные — всегда 1).
  • awc.g(...) (session-gate) в этом билде не вызывается ни разу.