obfuskator/info/evo-payload-pipeline.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

5.2 KiB
Raw Permalink Blame History

Payload-раннер EvoVis: как мод попадает в игру

Разбор по коду (байт-код evo-agent.jar, payload.json, runtime.json, структура evojdk_x/). Источник: javap -p -constants -cp mod/evo-agent.jar ….

Участники

артефакт роль
mod/payload.json манифест: version, file, sha256, size
mod/evovis.payload (47.5 MB) зашифрованный jar мода (AES-GCM)
mod/evo-agent.jar (60 KB) Java-agent: расшифровывает payload и подсовывает его Fabric
mod/evojdk-win-x64.zip → bin/java.dll, classes/…/java/lang/EvoShare.class подменённый JVM: класс java.lang.EvoShare.compute(...) отдаёт вторую долю ключа
mod/jar/META-INF/jars/*.jar (7 шт.) вложенные зависимости, распаковываются агентом
mod/jar/META-INF/evo/native/{win-x64,win-arm64,macos-x64,macos-arm64}/evoguard.dll|dylib нативная защита мода (внутри уже загруженного мода)

Манифест (payload.json)

{ "version": "2026.10.1.994", "file": "evovis.payload",
  "sha256": "e118274a92181e2a8ef04d2f6c45c4f6d4a10b87119d9d41c6f8e39b1bd5f485",
  "size": 47491101 }

Лаунчер качает payload.json → сверяет sha256 → качает evovis.payload, evo-agent.jar, evojdk-*.zip → поднимает JVM с агентом.

Конфигурация агента (из констант EvoAgent)

константа значение
MOUNT_NAME "evovis"
payload свойство evo.agent.payload / env EVO_AGENT_PAYLOAD
ключ свойство evo.agent.key / env EVO_AGENT_KEY (hex, 32 байта)
токен / hwid env EVO_TOKEN, EVO_HWID
вложенные библиотеки evo.agent.libraries / EVO_AGENT_LIBRARIES, каталог libraries

Манифест агента: Premain-Class: evo.agent.EvoAgent, Launcher-Agent-Class: evo.agent.EvoAgent, Can-Redefine-Classes: true, Can-Retransform-Classes: true.

Цепочка запуска

laунчер
  ├─ sha256(payload.json) → качает evovis.payload / evo-agent.jar / evojdk
  ├─ env: EVO_AGENT_PAYLOAD, EVO_AGENT_KEY, EVO_TOKEN, EVO_HWID, EVO_AGENT_LIBRARIES
  └─ java -javaagent:evo-agent.jar …  (свой evojdk, в нём java.lang.EvoShare)
        │
        ▼
EvoAgent.premain/agentmain(args, inst)
  ├─ keyProvider(): LocalKeyProvider(hex-ключ из env)  +  JvmShare.share(hwid)
  │     └─ NativeJvmShare → reflect вызов java.lang.EvoShare.compute(...) → 32 байта
  ├─ PayloadCipher.decrypt(bytes(payload), key)   // AES/GCM/NoPadding, IV 12B, tag 128b
  ├─ NestedLibraries.extract(payloadBytes, dir)   // META-INF/jars/*.jar → <dir>/libraries
  ├─ PreparedMod(mount="evovis", candidate=Path, classpathUrl, libraries)
  └─ bootstrap(inst)
        ├─ appendToSystemClassLoaderSearch(classpathUrl)  → классы мода видны JVM
        └─ inst.addTransformer(FabricDiscoveryInjector)
              │
              ▼
FabricDiscoveryInjector.transform(...)  // java.lang.classfile API (байткод-в-байткоде)
  ├─ цель: net/fabricmc/loader/impl/discovery/ArgumentModCandidateFinder.findCandidates
  ├─ вставляет вызов EvoAgentHook.contribute(Consumer)  (слот CONSUMER_SLOT = 1)
  └─ consume.accept(PreparedMod) → Fabric видит мод-кандидат, которого нет на диске
        │
        ▼
Fabric загружает мод → мод сам распаковывает evoguard.dll из META-INF/evo/native/
  → SealedBoot / KeyAssembler / SessionHeartbeat / NativeGuard

Ключевые свойства:

  1. Мод не лежит на диске в открытом виде - расшифровка в памяти агента, candidate - временный mount-файл.
  2. Ключ собирается из двух мест: hex из env (лаунчер) + java.lang.EvoShare.compute(hwid) в подменённом JVM - поэтому мод гоняется только на их evojdk.
  3. Инъекция в Fabric - не мод в mods/, а байткод-трансформация внутреннего класса Fabric (ArgumentModCandidateFinder) через java.lang.classfile API. Классы: EvoAgent, EvoAgentHook, FabricDiscoveryInjector, PreparedMod, NestedLibraries.
  4. Далее внутри мода - свой слой: KeyAssembler (Shamir: сервер + integrity + native + hwid), SealedBoot (sealed.bin в этом билде отсутствует → fail-open), SessionHeartbeat.

Что здесь не проверено

  • Тело java.dll / EvoShare - натив не реверсился (план в todo.md).
  • Реальное поведение при запуске (нужен MC 26.2 + Fabric 0.19.5 + их evojdk + дисплей).
  • evoguard.dll - не реверсился.