# Референс: Fabric Mod Obfuscator (Java + ASM) Разобранный образец лежит в `/tmp/opencode/obf_extract/OBF` (JAR из `OBF.rar`). Нужен не как «правильный ответ», а как **карта решений, которые уже были приняты и проверены на живых кейсах**. ## 1. Что это | | | |---|---| | язык | Java 21, Maven, сборка `mvnw package` | | движок | **ASM** (читает `.class`, строит маппинги, переписывает байткод) | | объём | 18 классов в `org.example`, 3224 строки | | цель | Fabric/Minecraft-моды | | интерфейс | CLI + Swing GUI (drag-and-drop) | ## 2. Карта модулей → наш план | модуль | LOC | что делает | у нас | |---|---:|---|---| | `JarRemapper` | 409 | обход архива, remap всех ссылок | `noxis-jar` + `rename::rewrite` | | `InheritanceAnalyzer` | 373 | иерархия, безопасность переименования | `noxis-analysis::hierarchy` ✅ | | `ObfuscatorGui` | 276 | GUI | не нужно (CLI) | | `MappingGenerator` | 262 | маппинг → файл | `noxis-core::mapping` ✅ | | `JunkClassGenerator` | 251 | junk-классы | пасс `noise` | | `StringDecoderGenerator` | 205 | генерит класс-декодер | `noxis-runtime` + P2 генератор | | `ObfuscatorRunner` | 202 | конвейер, оркестрация | `noxis-passes::standard_pipeline` ✅ | | `NumberObfuscator` | 185 | константы / opaque | пасс `constants` | | `ControlFlowObfuscator` | 183 | flattening | пасс `flow` (ждёт `StackMapTable`) | | `FabricKeepRuleScanner` | 147 | keep из конфига Fabric | `noxis-analysis::keep` ✅ | | `Main` | 134 | CLI-точки | `noxis` (clap) | | `StringEncryptor` | 132 | AES-128 строк + chunk'и | `noxis-runtime::scheme` ✅ | | `NameGenerator` | 131 | имена | `noxis-passes::namegen` ✅ | | `JunkAnnotationInjector` | 79 | junk-аннотации | пасс `noise` | | `PackageGenerator` | 78 | пакеты | `namegen::generate_packages` ✅ | | `AccessWidener` | 73 | патч access widener | `noxis-jar` + пасс `resources` | | `DebugInfoStripper` | 69 | strip отладки | пасс `debug` | | `StringFilter` | 35 | что считать строкой | функция внутри `strings` | **Вывод:** структура совпадает с нашей почти один в один — это подтверждает раскладку крейтов. Ключевое отличие: у них всё живёт в одном jar'е и одном проекте; у нас конвейер отделён от I/O и от CLI. ## 3. Что берём ### 3.1 Порядок пассов У них: remap → strings → numbers → flow → junk → debug. Тот же инвариант, что и у нас: **переименование раньше всех техник, порождающих новые ссылки**, а `debug` — последним. ### 3.2 Наследование как условие безопасности `InheritanceAnalyzer` — самый большой после remap'ера модуль. Совпадает с нашим выводом: переименование virtual-методов **без** иерархии ломает `invokevirtual`. У нас это `hierarchy` + семейства в `rename`. ### 3.3 Keep-сканер `FabricKeepRuleScanner` читает правило из конфигурации Fabric и не даёт сломать то, что JVM находит по имени. Мы делаем то же самое + расширяем рефлексией по строкам. ### 3.4 Подпись срезается `*.SF/*.RSA/*.DSA/*.EC` удаляются при перепаковке — после записи подпись всё равно невалидна, а её наличие заставляет JVM падать. Это входит в `noxis-jar`. ### 3.5 Маппинг в файл + ложные ограничения `--mappings` пишет соответствие «новое → старое» — без него автор сам не читает stack trace. Плюс честные off-ключи: `--no-strings`, `--no-flow`, `--no-junk`, `--keep-lines`, `--keep-source`, `--keep-vars`, `--no-widen`. Мы берём то же: **каждый пасс выключаем по отдельности**, иначе диагностика проблемы «что сломало» превращается в угадывание. ### 3.6 Seed `--seed` — детерминированный прогон. У нас это базовое свойство `noxis-core`, без отдельного флага. ## 4. Что не берём ### 4.1 Java + ASM Главное отличие стека. Пара Java+ASM даёт родную JVM, готовые visitor'ы и `CheckClassAdapter` — но и: - нужно раскатывать JDK для сборки и запуска тестов; - наш проект — Rust (`deny(unsafe_code)`, свои лимиты, свои гейты); - отсутствует возможность отдавать один бинарь. Цена, которую мы платим: `StackMapTable` придётся считать самим (через inference-верификатор — [`../stack.md`](../stack.md) §3). Это осознанно. ### 4.2 Swing GUI Лишняя точка входа: GUI дублирует CLI и удваивает площадь тестирования. У нас — только clap. ### 4.3 `StringDecoderGenerator` как «генератор текста» Они генерируют Java-исходник декодера и компилируют. Для нас это означало бы зависимость от `javac` на этапе обфускации. Наш путь — генерировать **байткод напрямую** через наш IR (`noxis-runtime` держит схемы, генератор собирает `ClassFile`). ### 4.4 AES-128 + Base64 как единственная схема Одна схема на весь архив = тот же провал, что у EvoVis §1/§9. У нас ротация схем, per-string salt, nonce и MAC. ### 4.5 `--flatten-flow` только «для простых методов» Ограничение из их README: агрессивный switch-gate включается только на простых методах. Это признаёт, что **верификация у них эмпирическая**. Мы вместо этого делаем верификатор обязательным условием включения `flow`. ## 5. Что проверяем на их smoke-тестах В `target/` референса лежат готовые проверочные кейсы — их стоит переиспользовать как наш regression-набор: | кейс | что ломает | |---|---| | `smoke-safe` | базовый round-trip, `Sample`/`MyMixin` | | `smoke-inheritance` | `Base`/`Child`/`InheritedChild`/`ExternalChild` — семейства virtual и мосты | | `smoke-flow` | `LoadAll` — загрузка всех классов, ловит `VerifyError` | | `smoke-fields` | переименование полей и статических полей | | `smoke-aes` | шифрование строк, `RunStrings` сверяет расшифровку | | `Probe.java` | точечная проверка одной гипотезы | Это ровно тот класс тестов, который нужен `noxis-verify`: **загрузить и выполнить**, а не «выглядит обфусцированным». ## 6. Итог | берём | не берём | |---|---| | порядок пассов | стек Java + ASM | | иерархия как условие rename | Swing GUI | | keep-сканер | генератор Java-исходника | | срез подписей, патч ресурсов | единственная схема шифра | | маппинг в отчёт, честные off-ключи | эмпирическая верификация flow | | их smoke-кейсы как regression | — | Полный перечень провалов защиты, которую они **не** закрывают, — в [`lessons.md`](lessons.md).