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.
8.5 KiB
Референс: 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 §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.