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