go install mvdan.cc/garble@latest # 或 @master
通过包装 Go 工具链来混淆 Go 代码。需要 Go 1.27 或更高版本。
garble build [构建标志] [包]
该工具还支持 garble test 以使用混淆后的代码运行测试,
garble run 以混淆并执行简单程序,
garble reverse 以对堆栈跟踪等文本进行反混淆,
以及 garble bug 以提交预填的 bug 报告。
运行 garble -h 查看所有可用命令和标志。
生成一个与常规构建一样可用的二进制文件,但尽可能少地包含有关原始源代码的信息。
该工具的设计目标是:
cmd/go 耦合,以支持模块和构建缓存该工具包装对 Go 编译器和链接器的调用,以转换 Go 构建,从而:
-literals 标志,则混淆字面量-tiny 标志,则移除额外信息该工具会混淆所有正在构建的受支持包,包括标准运行时。
不受支持的 runtime/cgo 和 crypto/internal/fips140 包被排除在外。
运行时混淆遵循 Go 支持的 GOOS/GOARCH 目标;Garble 不维护更窄的架构允许列表。
请注意,像 garble build 这样的命令将使用 $PATH 中找到的 go 版本。要使用不同版本的 Go,你可以使用 GOTOOLCHAIN。
一个常见的问题是,为什么像 Go 这样的编译型语言需要代码混淆器。 Go 二进制文件包含大量关于原始源代码的信息; 即使剥离了调试信息和符号表,许多名称和位置仍然保留,以便支持跟踪、反射和调试。
Go 的某些使用场景需要与最终用户共享 Go 二进制文件。 如果二进制文件的源代码是私有的或需要购买, 对其进行混淆有助于阻止逆向工程。
类似的使用场景是源代码为私有或购买的 Go 库。 由于 Go 库无法以二进制形式导入,而 Go 插件 有其缺点, 共享混淆后的源代码成为一种选择。 参见 #369。
混淆还可以帮助处理与许可完全无关的方面。
例如,-tiny 标志可以使二进制文件缩小 15%,
类似于 Android 中的常见做法以减小应用大小。
混淆还帮助一些开源开发者绕过
反病毒扫描错误地将 Go 二进制文件视为恶意软件的问题。
使用 -literals 标志会导致字符串等字面量表达式
被替换为更复杂的表达式,在运行时解析为相同的值。
通过 -ldflags=-X 注入的字符串字面量也会被此标志替换。
此功能是选择性启用的,因为它可能会根据输入代码导致速度变慢。
常量表达式中使用的字面量无法被混淆,因为它们
在编译时就被解析。例如,这包括 const
声明中的任何表达式。
请注意,只要有足够的努力,这个过程是可以被逆转的; 参见 #984。
使用 -tiny 标志,会从 Go 二进制文件中剥离更多信息。
位置信息被完全移除,而不是被混淆。
打印 panic、致命错误和跟踪/调试信息的运行时代码被移除。
许多符号名称在链接时也从二进制段中省略。
总而言之,这可以使二进制文件缩小约 15%。
使用此标志,将永远不会打印任何 panic 或致命运行时错误,但它们
仍然可以像往常一样通过 recover 在内部处理。
请注意,此标志会使调试崩溃更加困难,因为 panic 会直接
退出整个程序而不打印堆栈跟踪,并且源代码
位置和许多名称都被移除。
同样,garble reverse 在此模式下通常也没有用处。
garble build 的耗时大约是 go build 的两倍,因为它需要
完成两次构建。原始构建,以便能够加载和类型检查
输入代码,然后是混淆构建。
Garble 一次混淆一个包,与 Go 一次编译一个包的方式相同。
这使 Garble 能够完全支持 Go 的构建缓存;增量
garble build 调用应该只重新构建和重新混淆修改过的代码。
请注意,第一次调用 garble build 可能会相对较慢,
因为它必须首次混淆每个包。这类似于清除
GOCACHE(使用 go clean -cache)并从头运行 go build。
Garble 还利用自己的缓存来复用工作,类似于 Go 的 GOCACHE。
它默认位于用户缓存目录下的一个目录中,
例如 ~/.cache/garble,可以通过设置 GARBLE_CACHE 将其放置在其他位置。
就像 Go 一样,garble 构建在本质上是确定性和可重现的。
这有显著的好处,例如缓存构建以及能够使用
garble reverse 对堆栈跟踪进行反混淆。
默认情况下,garble 会以独特的方式混淆每个包, 如果其构建输入发生变化,这种方式也会改变:garble 的版本、Go 的版本、 包的源代码,或任何构建参数(如 GOOS 或 -tags)。 这是一个合理的默认值,因为猜测这些输入非常困难。
你可以使用 -seed 标志来提供自己的混淆随机种子。
重用相同的种子有助于产生相同的代码混淆,
这在调试或重现问题时很有帮助。
定期轮换种子从长远来看也有助于抵御逆向工程,
否则人们可以通过观察 Go 标准库混淆方式的变化
来猜测在一系列构建中 Go 或 garble 版本何时发生了变化。
要始终为每次构建使用不同的种子,请使用 -seed=random。
请注意,使用自定义种子时应格外小心:
如果构建中使用的 -seed 值丢失,garble reverse 将无法工作。
其中大部分可以随着时间和努力而改进。本节的目的 是记录此工具当前的不足之处。
目前,导出的方法永远不会被混淆,因为它们可能 被接口所需要。这一领域仍在进行中;参见 #3。
目前没有受支持的方法来排除对选定文件或包的混淆。 用户通常希望这样做来绕过 bug;请改为提交该 bug。
Go 程序一次初始化一个包, 其中被导入的包总是在其导入者之前初始化, 否则它们按其导入路径的字典序初始化。 由于 garble 混淆了导入路径,这个字典序可能会任意改变。
目前不支持 Go 插件;参见 #87。
Garble 需要 git 来修补链接器。一旦 go-gitdiff
支持非严格补丁,就可以避免这一点。
像 runtime.GOROOT
和 runtime/debug.ReadBuildInfo
这样的 API 在混淆后的二进制文件中将无法工作。例如,这可能会影响
加载时区。
我们欢迎新的贡献者。如果你想贡献,请参阅 CONTRIBUTING.md 作为起点。
在发送 PR 之前,请先提交一个 issue,无论是 bug 报告还是 功能请求,以便我们首先就问题及其解决方案达成一致。