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.
5.6 KiB
Где защита 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) в этом билде не вызывается ни разу.