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.
This commit is contained in:
Loki 2026-10-08 22:01:03 +02:00
commit a641e69455
140 changed files with 17197 additions and 0 deletions

76
info/evo-weaknesses.md Normal file
View file

@ -0,0 +1,76 @@
# Где защита 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) в этом билде **не вызывается ни разу**.