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

2.8 KiB
Raw Permalink Blame History

Обфускация EvoVis / EvolutionDLC

Три уровня

  1. Имена - 1277 классов вынесены в default package с именами вида a…aa…; миксины разбросаны по пакетам a/ и b/. Смысловых имён классов в коде нет.

  2. Строки - все строковые литералы зашифрованы вызовом awc.d(cipher, key):

    • LCG: state = state * 1103515245 + 12345 (32-бит переполнение);
    • байт: cipherChar ^ (state >>> 24);
    • результат - UTF-8 строка. Ключи передаются «непрозрачными» целыми (см. ниже). Второй вариант awc.g(...)
    • то же самое плюс session-gate: строка расшифровывается только после проверки сессии через рефлексию aln.key().
  3. Контрольный поток - тела методов разложены в машину состояний: while (true) { switch (состояние) { case <обфуск-константа>: ...; состояние = <следующая>; } }. Переходы между блоками - через присваивание целому и обратный goto. Из-за этого объём декомпилированного кода раздут примерно в 25 раз относительно реального.

Кодировка целочисленных констант

Все литералы записаны через тождества (по модулю 2³²):

Паттерн Равен
(A ^ B) + ((A & B) << 1) A + B
(A | B) + (A & B) A + B

Так прячутся даже 0, 1, 16, 255. Например, true/false приходят как выражение, равное 1/0 (javac такого не генерирует - это работа обфускатора). Сдвиги и маски тоже закодированы: сдвиговые константы в байткоде могут быть отрицательными - JVM маскирует количество бит по младшим 5 битам (ishl/ishr со сдвигом -8 работает как сдвиг на 24), поэтому >> -8 в байткоде эквивалентен >> 24 в исходнике.

Прочее

  • Декой-классы обфускатора - пустышки с бессмысленными членами, в логике не участвуют.
  • Из-за имён-ключевых слов (if, do, …) декомпилятор переименовывает такие классы (Cif, Cdo) - в самом jar классов с такими именами нет.