# Где защита EvoVis провалилась - чего нам делать нельзя Разобрано полностью за один проход, без единого рантайм-хака. Каждый пункт - что именно дало ключ атаки. ## 1. Ключ шифрования строк лежит рядом со шифротекстом `awc.d("", )` - ключ это **константное выражение в вызове**. Ни 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) в этом билде **не вызывается ни разу**.