ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Recaf 内置 Kotlin Metadata 精简库:由来、结构解析与去混淆实战

Recaf 内置 Kotlin Metadata 精简库:由来、结构解析与去混淆实战 逆向工程开发工具桌面应用【免费下载链接】RecafThe modern Java bytecode editor项目地址https://gitcode.com/gh_mirrors/re/Recaf点击查看免费下载Kotlin 编译器会在每个生成的 JVM 类文件上写入kotlin.Metadata注解其中以 protobuf 二进制编码的形式保存了类、属性、函数、构造器与类型参数等源码级信息。本文围绕 Recaf 仓库中 libs/README.md 所描述的内置精简依赖 ——kotlin-metadata.jar说明它为何以独立 jar 的形式被收录进仓库并结合 KotlinMetadata.java 与 KotlinMetadataVisitor.java 等源码完整拆解Metadata注解的字段结构、解码与建模流程最后介绍 Recaf 如何借助这些信息在 Kotlin 类反混淆中还原被混淆的类名与方法名。读完本文你将掌握 Kotlin 元数据在字节码层面的存储形态以及一套可用于任意 JVM 工具链的 Kotlin 元数据读取思路。libs 目录非标准 Maven 托管的依赖收录机制Recaf 仓库根目录下有一个专门的 libs 目录其用途在 libs/README.md 中有明确说明In some circumstances, libraries are not hosted on standard Maven hosts.即某些第三方库由于种种原因例如体积过大、发布渠道受限、或只存在于特定构建环境中无法从标准的 Maven 中央仓库或其他公共仓库直接拉取Recaf 便将这些库的 jar 直接收录进仓库源码树作为项目的“内置依赖”随仓库分发。这种做法的好处是构建结果可复现、不依赖外部网络可达性代价是这些二进制文件无法通过 Gradle 的依赖坐标机制自动更新。当前 libs 目录下实际收录的内容只有两个条目libs/README.md说明文件本身libs/kotlin-metadata.jar被精简过的 Kotlin Metadata 读取库。从仓库其余部分看Recaf 的常规第三方依赖统一由 gradle/libs.versions.toml 版本目录管理因此可以推断凡是无法通过该版本目录正常解析的依赖才会落入libs目录手工托管kotlin-metadata.jar就是这一机制的实例。Kotlin Metadata 精简依赖背景与来源Kotlin 官方提供的元数据解析能力通常附带在庞大的 Kotlin 编译器或kotlin-stdlib生态中而 Recaf 只需要其中“读取 Kotlin Metadata”这一小部分能力。为了不引入一个数百 MB 的完整编译器依赖Recaf 采用了精简方案。README 原文明确记录了两点事实裁剪范围该 jar 是某个更大的依赖完整 Kotlin 库中“仅与读取 Kotlin Metadata 相关的部分”来源该精简依赖由社区开发者SuperCoder79提供。在构建与运行层面Recaf 通过 recaf-core 模块的 Java 源码直接引用org.jetbrains.kotlin.metadata包下的类如ProtoBuf、MetadataNameResolver、JvmProtoBuf、BitEncoding因此kotlin-metadata.jar实际上充当了这些类的类库提供方。由于它随仓库分发开发者无需额外配置任何私有 Maven 仓库即可编译与运行整个项目。Metadata注解的结构八个关键字段Kotlin 编译器在每个由 Kotlin 源码编译出的类文件上都会生成一个运行时可见的Lkotlin/Metadata;注解。Recaf 在 KotlinMetadata.java 中将其描述为METADATA_DESC Lkotlin/Metadata;并通过 KotlinMetadataVisitor.java 定义了需要提取的全部字段名字段名注解元素含义kKIND_NAME元数据种类见下文枚举pnPACKAGE_NAMEKotlin 视角下的包全限定名若与 JVM 包名相同则为空字符串mvMETADATA_VERSION_NAME元数据协议版本int 数组bvBYTECODE_VERSION_NAME类文件字节码接口版本int 数组d1DATA1_NAME实际数据模型的二进制编码String 数组d2DATA2_NAME用于解析d1的字符串表String 数组xsEXTRA_STRING_NAME附加字符串多文件类部分场景下为 facade 类的内部名xiEXTRA_INT_NAME附加整数各 bit 位表示不同编译标志其中k字段getKind()的可取值及其对应的 protobuf 解析类型在 KotlinMetadataVisitor.java 中有完整注释Class→ 解析为ProtoBuf.ClassFile→ 解析为ProtoBuf.PackageSynthetic classLambda→ 解析为ProtoBuf.FunctionMulti-file class facadeMulti-file class part→ 与 File 一样解析为ProtoBuf.Package。对应地KtClassKind.java 定义了CLASS / FILE / SYNTHETIC_CLASS / MULTI_FILE_CLASS_FACADE / MULTI_FILE_CLASS_PART五个枚举值fromKindInt(int)会将整数k映射为对应枚举。而xi字段getExtraInt()的 bit 标志在 KotlinMetadataVisitor.java 中逐一说明并在 KtClass.java 中重复印证bit 0多文件类 facade/part以-Xmultifile-parts-inherit编译bit 1由 Kotlin 预发布版本编译bit 2编译自 Kotlin 脚本源文件.ktsbit 3严格元数据版本语义bit 4由 Kotlin 1.4 引入的 JVM IR 后端编译bit 5具备稳定元数据与 ABI仅对 JVM IR / FIR 类文件设置bit 6由 K2 编译器前端FIR编译元数据版本 2.0.0 起不再设置bit 7在内联函数作用域中使用、隐式属于公共 ABI元数据版本 ≥ 1.6.0 才有效。读取流程从注解到 KtClass 模型的三步管线Recaf 的 Kotlin 元数据读取整体分为三步全部集中在 KotlinMetadata.java第一步ASM 访问器扫描注解对外入口extractKtModel(...)有四个重载分别接受ClassInfo、byte[]、ClassReader与KotlinMetadataVisitorKotlinMetadata.java。其中extractMetadata(ClassReader)KotlinMetadata.java使用 ASM 的ClassVisitor扫描类文件当遇到Lkotlin/Metadata;注解时将默认的注解访问器替换为KotlinMetadataVisitor并以ClassReader.SKIP_CODE | SKIP_DEBUG跳过代码与调试信息仅专注注解读取。KotlinMetadataVisitor本身继承 ASM 的AnnotationVisitor在visit中捕获k/pn/xs/xi等标量在visitArray中通过 AnnotationArrayVisitor.java 收集d1、d2、mv、bv四个数组。代码注释还提到一个 ASM 的细节即便声明为int[]ASM 也可能以原始数组形式直接调用visit因此mv/bv在visit与visitArray两条路径中都会被处理KotlinMetadataVisitor.java。第二步d1 解码与按 kind 分发核心方法extractMetadata(KotlinMetadataVisitor)KotlinMetadata.java完成真正的解码先检查d1、d2是否都已填充任一为空则直接返回null使用BitEncoding.decodeBytes(d1)将d1的字符串数组还原为二进制字节流从字节流中先解析JvmProtoBuf.StringTableTypes字符串表类型用StringTableTypes与d2构造MetadataNameResolver用于后续把d1中的整数索引解析回人类可读的名字依据k字段分发解析1解析ProtoBuf.Class2/5解析ProtoBuf.Package3解析ProtoBuf.Function其余类型抛出UnsupportedOperationException并被捕获。值得注意的健壮性处理解析失败时如 protobuf 解析器不认可数据方法会记录一条警告日志且parseFailCount 5的阈值保证该警告最多输出 5 次避免对大量损坏类文件刷屏日志KotlinMetadata.java。静态初始化块中还会调用MetadataUtils.register(EXTENSIONS)注册扩展字段注册表KotlinMetadata.java。第三步protobuf 模型到 Kotlin 业务模型的映射三个私有构造器分别针对ProtoBuf.Class、ProtoBuf.Package、ProtoBuf.Function通过延迟加载的SupplierKtClass在extractKtModel()被调用时才完成映射KotlinMetadata.java。映射规则要点类名会执行fqName.replace(., $)即把点分隔的包名转换为 JVM 内部名风格类型的名字解析同样做该替换mapTypeKotlinMetadata.javaKtType还会记录可空性KtNullability.NULLABLE / NONNULL / UNKNOWN与泛型参数列表KtFunction记录函数名、返回类型与参数列表KtFunction.javaKtClass汇总了类种类、额外标志、类名、伴生对象名、父类型、构造器、属性与函数KtClass.java并提供toPrettyString()输出类似 Kotlin 源码风格的可读文本。最终产出的业务模型位于 recaf-core/src/main/java/software/coley/recaf/util/kotlin/model 目录共 10 个类KtClass、KtClassKind、KtConstructor、KtElement、KtFunction、KtNullability、KtParameter、KtProperty、KtType、KtVariable。实战应用一Kotlin 名字恢复去混淆内置精简库最直接的受益者是 Recaf 的去混淆模块。在 KotlinNameRestorationTransformer.java 中Recaf 通过元数据把混淆后的类名与成员名映射回 Kotlin 源码名从KotlinMetadataCollectionTransformer获取已收集的KtClass模型类名直接映射mappings.addClass(ownerName, ktClass.getName())属性映射先匹配字段getField再匹配 getter 方法若属性名不以get/is/do开头会自动补全为getXxx形式KotlinNameRestorationTransformer.java函数映射通过描述符精确匹配后addMethodKotlinNameRestorationTransformer.java该转换器声明依赖KotlinMetadataCollectionTransformerdependencies()方法保证元数据收集先于名字恢复执行。由于元数据本身无序代码注释明确了一个保守策略只有存在精确描述符匹配时才对成员做重命名避免误伤KotlinNameRestorationTransformer.java。实战应用二元数据收集与描述符反映射作为名字恢复的数据源KotlinMetadataCollectionTransformer.java 承担了更底层的职责transform()中对每个类调用KotlinMetadata.extractKtModel(...)将模型缓存进kotlinClassModels并把“混淆类名 → 元数据中的原始类名”写入AggregatedMappingsKotlinMetadataCollectionTransformer.java无元数据时回退检查Lkotlin/jvm/JvmName;注解用其name参数推导原类名mapKtDescriptor(KtType)把 Kotlin 类型映射为 JVM 描述符其中对kotlin.Array特判展开为数组描述符[ 元素描述符KotlinMetadataCollectionTransformer.javamapToJvm(String)完成 Kotlin 标准库类型到 JVM 描述符的替换表KotlinMetadataCollectionTransformer.java例如Lkotlin/Boolean;→Z、Lkotlin/String;→Ljava/lang/String;、Lkotlin/collections/List;→Ljava/util/List;、Lkotlin/Function1;→Lkotlin/jvm/functions/Function1;并注明KFunction1、KMutableProperty1等反射类型映射不稳定而无法处理reverseMapDescriptor(String)则反向应用收集到的映射把描述符中的混淆类名还原为元数据中的原始类名。三者的推荐调用顺序在类注释中给出先mapKtDescriptor得到 Kotlin 视角描述符再mapToJvm替换标准库类型最后reverseMapDescriptor还原类名KotlinMetadataCollectionTransformer.java。实战应用三UI 侧的可视化查看在 UI 模块中KotlinMetadataPane.java 借助同样的模型在编辑面板中以标签页形式展示类的 Kotlin 元数据视图入口由 SideTabsInjector.java 注入。也就是说从底层解析、到去混淆应用、再到用户界面展示同一套KtClass模型贯穿整个 Recaf体现了该内置精简库在项目中的核心复用地位。小结kotlin-metadata.jar是 Recaf 为规避标准 Maven 托管缺失而收录进 libs 目录的精简依赖它剥离了完整 Kotlin 库的冗余部分只保留读取Metadata注解所需的 protobuf 解析能力。围绕它Recaf 构建了一条完整链路 —— 用 ASM 访问器捕获注解字段、用BitEncoding解码d1、按k种类分发解析 protobuf、映射为统一的KtClass业务模型最终服务于 Kotlin 类的名字恢复与描述符还原。对于想要在自己的 JVM 工具中解析 Kotlin 元数据的开发者这份精简 jar 与 KotlinMetadata.java 的解析流程本身就是一份可直接参考的最小实现范本。赞分享逆向工程开发工具桌面应用【免费下载链接】RecafThe modern Java bytecode editor项目地址https://gitcode.com/gh_mirrors/re/Recaf点击查看免费下载相关推荐Anthropic-Cybersecurity-Skills 实战PowerShell 混淆恶意软件去混淆分析报告模板详解Anthropic Cybersecurity Skills 实战PowerShell 混淆恶意软件去混淆分析报告模板详解 本文是 Anthropic Cyb网络安全AI 技能/插件渗透测试红蓝对抗M8HeadlessFirmware 现场演出指南用Live模式玩转即兴与曲目切换M8HeadlessFirmware 现场演出指南用Live模式玩转即兴与曲目切换 如果你想让一台 Teensy 4.1 变成可以上台演出的音乐追踪器M8HKoin 与 R8 / ProGuard 混淆兼容性指南Kotlin 依赖注入的代码缩减与混淆实战Koin 与 R8 / ProGuard 混淆兼容性指南Kotlin 依赖注入的代码缩减与混淆实战 导读 本文以 Koin 官方文档中关于 R8 / ProG后端上一篇GoGoGo虚拟定位技术深度解析无需ROOT的系统级位置模拟解决方案下一篇告别手写签名烦恼signature_pad社区资源全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表