惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
小众软件
小众软件
S
SegmentFault 最新的问题
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
博客园 - 三生石上(FineUI控件)
C
Check Point Blog
S
Schneier on Security
Microsoft Azure Blog
Microsoft Azure Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
罗磊的独立博客
有赞技术团队
有赞技术团队
V
V2EX
Y
Y Combinator Blog
博客园 - 叶小钗
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Fortinet All Blogs
W
WeLiveSecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Stack Overflow Blog
Stack Overflow Blog
The Cloudflare Blog
S
Security @ Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
PCI Perspectives
PCI Perspectives
H
Heimdal Security Blog
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
博客园 - Franky
Cisco Talos Blog
Cisco Talos Blog
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
T
Troy Hunt's Blog
N
News and Events Feed by Topic
Cloudbric
Cloudbric
Scott Helme
Scott Helme
云风的 BLOG
云风的 BLOG
Attack and Defense Labs
Attack and Defense Labs

See you soon

试试将文章版本化管理吧 | See you soon 使用 Quadlet 将 Podman 中的 Postgres 当作 systemd 服务运行 | See you soon 大他者,那个无时无刻都在盯着你的东西 | See you soon Laws of Software Engineering,软件工程定律 | See you soon 浅记多因素身份认证 | See you soon Linux 内核中的度量单位 | See you soon 重置 GPG 智能密钥 | See you soon 向 NAS 引入 samba | See you soon 无法重复键入的 Fcitx5 | See you soon ZFS 降级事故 | See you soon 记被 XanMod Kernel 和 AppArmor 联合坑的一次踩坑 | See you soon agent 的 skill 与 toolcall | See you soon 记一次服务器被挂恶意挖矿二进制 | See you soon 活着的 Arc | See you soon 令 acme.sh 使用 Cloudflare 的 DNS API 签发与续签证书 | See you soon 于 Tokio 中卸载 CPU Bound 任务 | See you soon 如我所见,梦破碎的时候 | See you soon 74LS 家族手册 | See you soon JDK Projects 备忘录 | See you soon 关于历史 | See you soon 用 curl 下载 OnePlus 的 ROM | See you soon 实用命令切片 | See you soon 再见,Oh My Zsh。 | See you soon 你不应该复用 strings.Builder | See you soon 博客的明日 | See you soon 被 AppArmor 击杀的 Dockge | See you soon AI 时代的自我 | See you soon 支持删除的布隆过滤器 | See you soon 基于栈的虚拟机与基于寄存器的虚拟机 | See you soon RVA23 包含了什么 | See you soon Rust 的边界检查是否已经有很大的进步 | See you soon 使用 Rust 编写操作系统:Barebones | See you soon 使用 Rust 编写操作系统:引言 | See you soon 编译器笔记:rope | See you soon 编译器笔记:CST | See you soon Paradoxical 札记 | See you soon 简单的相似去重算法(基于向量) | See you soon 更加现代的 PaperMC Minecraft 插件设计指南 | See you soon 简单的 CFG 语法分析方法 | See you soon 简单地使用 Caddy 实现 CORS 配置 | See you soon 让 OpenCV 可以被静态链接 | See you soon 图片搜索笔笺 | See you soon 电路板设计笔记-保护 | See you soon 使用 Rust 实现 SnowflakeId | See you soon 农夫乐事 FaQ 启示 | See you soon 使用 Rust 实现拓展系统札记 | See you soon 遗传学定律的代码实现 | See you soon EN:The Journey of Rust and Procps | See you soon ZH:Rust 与 Procps 之旅 | See you soon 使用 debootstrap 与 schroot 构建一个纯净环境 | See you soon GPG添加新的用户信息 | See you soon GSoC2024 笔记:使用 Rust 重新实现 procps | See you soon JWT 小册 | See you soon 使用密钥登录 SSH | See you soon Guava:Cache | See you soon 编译器笔记:增量编译 | See you soon Gradle秘境:添加一个类似modCompileOnly的依赖块 | See you soon Minecraft:原始经济系统设计简述 | See you soon MicroOS:进阶 | See you soon MicroOS:起步 | See you soon Swapfile 指北 | See you soon GPG 物理密钥的安装与密钥的迁移 | See you soon 用GPG签名告诉大家这就是我的提交 | See you soon DFS:深度优先搜索 | See you soon 文章翻译:从init.vim到init.lua | See you soon 目录遍历攻击 | See you soon 如何挂载.img文件,以及如何使用QEMU模拟arm64环境 | See you soon 在树莓派上编译 OpenCV | See you soon 关于卸载BlueStacks后遇到的问题 | See you soon
TinyRemapper笔记 | See you soon
Krysztal Huang · 2023-06-23 · via See you soon

记录一下探索 TinyRemapper 和 FabricMC 构建流程

TinyRemapper 是什么

这是一个隶属于 FabricMC 的构建流水线(Build Pipelines)的一部分,TinyRemapper 可以对 JAR 应用 Tiny 映射格式(Remapping format)。

NOTE

映射,顾名思义就是把一个东西等价称为另外一个东西。

关于为什么会有这个东西,是因为开发商对 JAR 构建结果应用保护措施而产生,在本文不再赘述。

在应用完毕后,我就可以让名为 A 的函数名可以称之为 getBlock这对反混淆来讲非常重要,而这是反混淆的最基础能力

FabricMC Build Pipelines:FabricMC 构建流水线

在这一部分需要单独提出,这对于理解 TinyRemapper 的功能与作用非常重要。

graph

loom(FabricLoom Build Logic)--构建-->YarnMappedBytecode--TinyRemapper应用Yarn到中间名映射-->IntermediaryMappedBytecode--TinyRemapper应用中间名到原始名映射-->TargetBytecode

src(模组源代码)-->loom

lib(YarnMappedMinecraft)-->src

mc(Minecraft)--TinyRemapper应用原始名到中间名的映射-->IntermediaryMappedMinecraft--TinyRemapper应用中间名到Yarn的映射-->lib

可以看出 TinyRemapper 负责的部分是把我们可读的代码重新映射成 JVM 虚拟机内执行需要的代码,或者帮我们把我们读不懂的代码重新映射成我们能读懂的代码。

在 JVM 程序执行机制里,我们运行的 Minecraft 程序是使用原始名,这对我们来讲是几乎不可读的。Yarn 提供了一套标准的、自由的名称,让我们的体验更加顺畅。如果我们在模组里面调用了 Minecraft 的某个函数,对于 JVM 而言是 INVOKE* 命令1 ,而这些命令的参数其实是 Minecraft 的原始名,比如 aabb/26390

但是我们其实不会这样写,我们写的通常是类似 world.getBlock 这样的代码。

在完成编译后,JVM 居然能应用正确的参数。

这其中发生了什么?

反混淆

顾名思义,反混淆就是把混沌的东西变成我们可以读懂的玩意儿

首先我们需要清楚,Mojang 在最早是不给我们提供资料的,我们只有猜。并且不是因为 Minecraft 才出现的反混淆,这是自 JVM 与 Java 诞生之初推广成功后便产生的东西。

在我们猜到这个非常混沌的方法名 26390 的大概作用后,我们就可以给他一个有意义的名字,并且记录下来。

记录下来后,映射便诞生了。

重映射(重混淆)

我们使用一些手段对字节码应用映射,这样我们就能顺利的写出 world.getBlock 这样的代码了,而不需要和 abjxc1.asobb 这样诡异的东西作斗争。

可是问题来了,我们前面说了,实际上这样做字节码指令的参数是不对的,是 world/getBlock ,JVM 是找不到 ClassPath 的——因为根本没有这个东西。

我们需要使用一个工具,用一种手段,和反混淆相反的手段,来纠正这些 ClassPath 和字节码指令参数,不如就叫做…重映射吧!

在经过这个流程后,我们的代码编译出来的模组终于可以被 JVM 正常加载,以及正常加载调用 Minecraft 的函数了。

中间名的诞生

在经过长期的经验中我们发现了一个大问题——他奶奶的,Mojang 一张混淆表给他妈的用的天荒地老2

而且两个版本之间经常一堆代码不变,那我是不是只用在意两个版本之间的新增和减少?

不对啊,聪明点的人肯定能想到这个玩意儿,而且早就在搞了。

确实是这样,并且 Yarn,MCP,Spigot 更新流程都是这样的。

问题是假如,我的意思是假如,假如我 Minecraft 的两个版本更新过快,你没时间反应,阁下又当如何应对?

又比如说,我需要编译前扫描整个 Minecraft 的字节码提取有用的信息,那编译可真的太慢了。

那我干脆直接早点提出来,要用的时候直接加载,快不快?非常快。

我假如说,我想 Mojmap 和 Yarn 混用,阁下又当如何应对?

没错,我会做一个转接层,而 Minecraft 反混淆后的各种方法名,字段名,类名,就叫做中间名

然后我一视同仁,我把依赖的模组,全部用中间名转换成我想用的映射名,非常完美。

理解 Tiny 格式

TinyRemapper 有它们自己的格式,这是这个笔记的重点

这篇笔记以 1.19.4 的 tiny 文件为例

第一行可以直接忽略,我们称之为 TinyRemapper Metadata

从第二行开始,就全是我们的主角了。

从分析角度来看,整个 tiny 文件格式应该以行为分析单位,因此我们也会以行作为例子来分析。

同时我也会用 Rust 代码来表达应有的储存结构,使用 TinyRemapper 的解析逻辑作为分析依据。

CLASS

CLASS a net/minecraft/class_4581

│───│ │─│ │──────────────────────│

│ │ │

│ │ │

│ │ │

│ │ └──────── 完全限定名

│ └────────────────────── 归属文件(混淆名)

└───────────────────────────── `CLASS` 类型描述

对于这行的解析,我会把他用 Rust 代码表述为

struct Class {

obf_name: String,

intermediary_name: String,

}

对应的 Java 代码为这里

if ("CLASS".equals(type)) {

out.acceptClass(splitLine[1 + fromIndex], splitLine[1 + toIndex]);

if (obfFrom != null && !splitLine[1 + fromIndex].isEmpty()) obfFrom.put(splitLine[1], splitLine[1 + fromIndex]);

}

FIELD

FIELD b F a field_21633

│───│ │─│ │─│ │─│ │─────────│

│ │ │ │ │

│ │ │ │ └── 中间名

│ │ │ └──────────── 混淆名

│ │ └───────────────── 类型方法描述符

│ └────────────────────── 归属文件

└───────────────────────────── `FIELD` 类型描述

struct Field {

class: String,

type_desc: String,

obf_name: String,

intermediary_name: String,

}

METHOD

METHOD a (F)Lorg/joml/Quaternionf; a method_46349

│────│ │─│ │───────────────────────│ │─│ │──────────│

│ │ │ │ │

│ │ │ │ └ 中间名

│ │ │ └─────────── 混淆名

│ │ └─────────────────────────── 返回值方法描述符

│ └───────────────────────────────────────────── 归属文件

└───────────────────────────────────────────────────── `METHOD` 类型描述

METHOD 和 FIELD 的处理逻辑是是一样的,所以直接看 CLASS 描述类型的代码就行。

对应的 Rust 代码为

struct Method {

class: String,

return_desc: String,

obf_name: String,

intermediary_name: String,

}

总结

除此之外,tiny 还有注释,是以 # 开头的。

以上代码的描述与处理我放在了 Gist 上,需要可以自行参考。

  1. 包含 INVOKEVIRTUALINVOKESPECIALINVOKESTATICINVOKEINTERFACEINVOKEDYNAMIC

  2. 起码从 1.7.10 到现在没有变过 ↩