obfuskator/info/evo-obfuscation-full.md
loki5512344 139aea9a8f
frames,jar,passes: verifier-driven hardening from real jars
Proven by paranoid runs over lorep (17 classes, fully green),
LoVisual (4081 classes) and oc2r (1278 classes) with per-class
java -Xverify:all, each fix first reproduced as a red test:

- merge: Top absorbs instead of Top-wins (javac declares Top for
  slots unwritten on some inflow; precise types reject those edges).
  Regression: loop_head_keeps_top_for_loop_local.
- emit: collapse [Long|Double, Top] pairs on operand stacks
  (javac writes one entry; the pair is rejected). Regression:
  wide_stack_join_emits_single_entries.
- xfer ArrayLoad: push the wide pair, fixing lcmp underflow.
  Regression: array_load_wide_pushes_pair.
- xfer <init>: convert only the receiver's own allocation site
  instead of every Uninitialized (nested construction clobbered
  the outer object). Regression: nested_construction_keeps_outer.
- xfer ANewArray: array operands gain one dimension ([B] -> [[B)
  instead of an illegal L-wrap ([L[B;). Covered by the same test.
- jar load: duplicate class names (multi-release module-info
  twins) keep the first occurrence instead of crashing save.
  Test: duplicate_class_names_keep_first.
- strings/constants: skip methods past MAX_METHOD_INSNS (8000)
  so giants cannot overflow the 64KB writer ceiling. Tests:
  oversized_methods_are_skipped in both passes.
- merge errors now name both types being merged.

Also: em-dash removed repo-wide (724 spots, rule added to
todo.md), allow-lints only in tests (scratch example deleted),
code comments migrated to English in every file this change
touches.
2026-10-09 13:47:27 +02:00

162 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Обфускация EvoVis / EvolutionDLC - полный сводный разбор
Всё проверено по артефактам в `LoVisual/ref/evo/` (jadx-декомпиляция `evovis-dec.jar`,
`javap -v` по 1277 классам корня, Rust-скрипты `scripts/`). Цифры, которые пересчитаны
вручную, помечены ✓; расхождения с исходным README - в конце.
Пакеты: 1405 `.class` всего = **1277 в default package** (корень jar) + **16 в `a/`**
+ **111 в `b/`** + 128 вложенных (`evo/**`, зависимости). jadx даёт 6991 `.java`
(включая бандл-зависимости), из них `defpackage/` ≈ **318 931 строк** кода.
---
## Уровень 1 - имена
- Все классы мода вынесены в default package (`defpackage/` в jadx) с именами
`a`…`zz` (1277 шт.); миксины разложены по `a/` (16) и `b/` (111).
- Миксины в `evolutiondlc.mixins.json` / `holdmyitems.mixins.json` тоже закодированы:
`"package": "b"`, список `"y"`, `"z"`, `"al"`, … - смысловых имён в манифесте нет.
- Часть имён - **ключевые слова Java** (`if`, `do`…): декомпилятор вынужден их
переименовывать (`Cif`, `Cdo`), в самом jar классов с такими именами нет.
- **Члены (поля/методы) НЕ переименовываются** ✓: `wb.onDraw`, `getProjectiles`,
`BreakingBad`, `aln.key()`, `setDaemon`, `REFRESH_THREAD` и т. д. читаются как есть.
Обфусцируются только типы и пакеты.
- **`SourceFile` частично сохранён** ✓ (27 классов в корне + все 127 миксинов):
`javap` даёт `Compiled from "StringRuntime.java"`, `"KeyAssembler.java"`,
`"AttestClient.java"`, `"PayloadCrypto.java"`, … у миксинов - `"GameRendererMixin.java"`.
160 файлов содержат `compiled from:` - по ним восстанавливаются оригинальные имена.
- **Отладочная информация осталась**: 10 218 `LineNumberTable` и 151
`LocalVariableTable` ✓ (локальные имены переменных живут у security-классов:
`awc` - 5, `aln` - 11).
- Аннотации модулей не тронуты: `@rn(name = "HitFix", category = rl.COMBAT,
description = "Makes attacks land on the hitbox you actually see…")` - 57 классов ✓,
то есть каталог модулей читается без всякой деобфускации.
- **Enum-константы не переименованы**: `SCALE`, `OPACITY`, `SPAWN`, `KEYFRAME`.
При этом 102 enum jadx не восстановил (`Failed to restore enum class`) - обфускатор
сломал декомпиляторский паттерн enum, но имена констант в байткоде остались.
## Уровень 2 - строки
Класс `awc` = `evovis.security.StringRuntime` (раскрыт по `SourceFile`):
```java
state = key; // int, 32-бит wrap
for (i = 0; i < cipher.length(); i++) {
state = state * 1103515245 + 12345; // LCG
bytes[i] = (byte)(cipher.charAt(i) ^ (state >>> 24));
}
return new String(bytes, UTF_8).intern(); // + ConcurrentHashMap CACHE
```
- `awc.d(cipher, key)` - обычная строка. **Ключ передаётся как opaque-выражение прямо
в вызове**, ни одного «настоящего» ключа в рантайме нет.
- `awc.g(cipher, key)` - то же, но session-gate: сначала
`Class.forName("aln").getMethod("key").invoke(null)` (рефлексией, имя класса открытым
текстом), если ключа сессии нет - возвращает `null`.
- `SecureStrings` (5 API-блобов) - гейт по `KeyAssembler.key()`, при отсутствии ключа
`get()` возвращает `null` и серверные фичи просто выключаются.
- Шифр одинаков для всех строк, кэш по шифротексту, `.intern()`.
## Уровень 3 - контрольный поток
Тело метода разложено в конечный автомат **с раздвоенным состоянием**:
```java
int i = (-513638579) ^ 765313817; // = A + B (полумладший)
int i2 = ((-513638579) & 765313817) << 1; // = (A & B) << 1
while (true) {
switch (i + i2) { // сумма = номер состояния
case -1866771718: setUser(...); return;
case -804841211: crVar = cq.load();
i = 1484979540 | 544286359;
i2 = 1484979540 & 544286359; break;
case 251675238: default: ...
}
}
```
- Переход - присваивание **двум** переменным (`i`, `i2`) и обратный `goto` в начало
цикла. Состояние хранится не одной константой, а суммой двух opaque-полей:
константная пропаганда обязана проследить пару, а не одно присваивание.
- Формы встреч ✓: `switch (i + i2)` - 1826, `switch (i2 + i3)` - 1067,
`switch (i3 + i4)` - 283, `switch (i4 + i5)` - 161, дальше хвост до `i15 + i16`.
В сумме ≈ **3530 автоматов в 505 файлах**.
- `while (true)` + `switch` вместе встречаются в 726 файлах из 6991 ✓.
- Из-за этого `defpackage/` раздувается до ~319k строк на ~1242 файла (реальный объём
кода в разы меньше; для сравнения у LoVisual 77 368 строк на 1928 файлов).
## Кодирование констант (opaque predicates)
Все целочисленные литералы - тождества по модулю 2³² (и 2⁶⁴):
| паттерн | равен |
|---|---|
| `(A ^ B) + ((A & B) << 1)` | `A + B` |
| `(A \| B) + (A & B)` | `A + B` |
| `(A ^ -A) + ((A & -A) << 1)` | `0` |
| `(A \| -A) + (A & -A)` | `0` |
Применяется **везде**, не только в ключах строк:
```java
// true / 0 / 1
setDaemon((A ^ B) + ((A & B) << 1));
strArr[((-702410340) | 702410340) + ((-702410340) & 702410340)] = ... // индекс 0
// long-вариант (Duration.ofSeconds(10))
(703617214384230201L ^ (-703617214384230191L)) + ((703617214384230201L & (-703617214384230191L)) << 1)
// сдвиговые константы тоже закодированы - см. «опровержение» ниже
```
## Мёртвый код / декой-классы
Reachability-BFS от bootstrap/`@rn`/`a`+`b`/security: **1370 классов → 1316 живых,
42 unreachable** ✓ (`analysis/structure.md`). Среди них `apc` (299 LOC, `@Deprecated`,
`implements aqg`), интерфейсы (`aa`), enum'ы (`bc`, `bd`, `be`). Ни один живой файл не
ссылается на отброшенный (`dangling=0`) - то есть это балласт, выключенный из графа,
а не случайные остатки.
---
## Лаунчер (.NET) - отдельная, очень слабая схема
- Один байтовый блоб `byte[15927] _4` в `PrivateImplementationDetails`,
в статическом конструкторе `_4[i] = (byte)(_4[i] ^ i ^ 0xAA)`;
чтение через аксессоры `return _5[N] ?? _6(off, len)` → `Encoding.UTF8`.
- **911 аксессоров** ✓ (все в одном файле), вызовы по всему коду - итог 1226 замен
(по `scripts/deobfuscate_launcher`).
- Имена сервисов не тронуты: `ModProvisioner`, `PayloadKeyClient`, `EvoVisAuth`,
`HardwareId`, `DeliveryMirror`. Никакой обфускации имён, никакой обфускации потока -
одна статическая XOR-таблица, восстанавливается одним проходом.
- `strings.txt` - 929 расшифрованных записей.
## Смежные слои защиты (не «обфускация кода», но часть контура)
1. **Payload**: `12B IV || AES-256-GCM(ct||tag)`, ключ из сервера, раскрывается цепочкой
HMAC/HKDF от `hwid` + `token` + доли из `java.dll` - см. `evo-payload-pipeline.md`.
2. **Антиотладка и порча ключа** - 3 независимых уровня XOR-масок (`0xA5`, `0x3C`, `0x5A`),
`IsDebuggerPresent`, `JAVA_TOOL_OPTIONS`, `JVM_EvoForeignAgentPresent` -
см. `evo-native-reverse.md`.
3. **HWID** = `sha256("evovis|" + MachineGuid)` - привязка к машине, Shamir-фрагменты
ключа, аттестация (`evovis-protect.vercel.app`).
4. **`evo-agent.jar` вообще не обфусцирован** ✓ - читаемые строки, `SourceFile:
"EvoAgent.java"`, т. е. расшифровщик payload открыт.
5. Скрипт-логирование с детектом `$ExecutionContext.SessionState.LanguageMode`
(защита от поломки PowerShell-обфускации у себя).
---
## Проверка исходных утверждений
| утверждение (obfuscation.md / README) | статус |
|---|---|
| 1277 классов в default package, 16 в `a/`, 111 в `b/` | ✓ подтверждено |
| LCG `1103515245` / `12345`, XOR со старшим байтом | ✓ подтверждено (`awc.java`) |
| «все строковые литералы зашифрованы `awc.d`» | частично: 8993 вызова `awc.d(` в `jar-src` ✓, но `awc.g(` - **0** вызовов в этом билде (описанная ветка не используется) |
| 8924 вызова / 5367 уникальных строк | расхождение: в файлах 8993 вызовов, `mod_strings.txt` - 5665 строк, `tsv` - 9248 пар. README, видимо, от усечённого прогона |
| opaque-тождества `(A^B)+((A&B)<<1)` и `(A\|B)+(A&B)` | ✓ подтверждено, есть и long-вариант |
| «сдвиговые константы в байткоде отрицательные (`>> -8` == `>> 24`)» | ✗ **не подтверждено**: по `javap` всех 1277 классов - 32 559 сдвигов, **ни одного** отрицательного непосредственного операнда (все константные сдвиги - это `iconst_1` из opaque-паттерна `<< 1`). В исходниках тоже 0 вхождений `>> -`. Скорее всего, наблюдение из другого билда/декомпилятора |
| «декой-классы - пустышки с бессмысленными членами» | ⚠ reachability (42 файла) подтверждается, но часть из них - осмысленный код (`apc`, 299 LOC, `@Deprecated`), часть - пустые интерфейсы/enum. «Пустышки» - интерпретация |
| «объём декомпилированного кода раздут ~25 раз» | ⚠ порядок величины верен (319k строк против, возможно, ~15k реальных), точный коэффициент не измерен |
| «911 accessor'ов, 1226 вызовов» в лаунчере | ✓ 911 аксессоров подтверждено |