Go作为编译语言,天然存在跨平台的属性,我们在编译完成后,可以再不暴露源代码的情况下,运行在对应的平台中,但是
还是架不住有逆向工程师的反编译、反汇编的情形;(当然我们写的都不希望被别人偷了,以下内容简单做个攻防介绍,起个引导作用,不到之处,多多留言探讨)
- 反编码(decompilation)是指将机器代码或二进制代码还原为高级语言源代码的过程。虽然一些工具可以进行反编码,但要注意的是,反编码是一个复杂的任务,成功与否取决于多种因素,包括编译器优化、代码混淆和使用的编程语言。
- 在Go语言中,由于Go是一种静态编译语言,编译器会将Go代码编译为机器码,而不是保留高级语言结构。这使得从Go二进制文件中还原出与原始Go源代码一样的代码非常困难,甚至几乎是不可能的。
逆向工程(反汇编和反编译)
逆向工程(Reverse Engineering)是指将现有的二进制程序或者系统进行分析,还原出其设计或运行机制的一种技术手段
下面是一些逆向工程的常见技术:
- 反汇编技术:通过将二进制程序转化为汇编代码,以还原程序的运行过程和指令执行情况。Go语言提供了反汇编工具,可以通过命令行输入"go tool compile -S可执行文件名"来实现反汇编。
- 反编译技术:将已经编译好的程序进行还原,得到其源代码。Go语言的反编译工具是"go tool compile -S 源文件名",生成的代码需要通过代码分析和修改才能重新编译成可执行程序。
- 动态调试技术:通过调试器对程序进行分析,观察程序的运行情况和变量值的变化。Go语言的调试器是"dlv",可以使用命令行对程序进行单步调试。
- 内存分析技术:通过对程序运行时的内存进行分析,以了解程序的行为和数据结构。Go语言的内存分析工具是"pprof",可以对程序进行CPU、内存和goroutine的分析。
1、破解方案
谈到防破解,首先得知道怎么破解,才能更好的防护;
将Go二进制文件转换为与原始源代码结构相同的高级语言源代码是一项非常困难且不可靠的任务。Go是一种静态编译语言,编译器将Go源代码转换为机器码,优化并去除高级语言结构。因此,反向转换机器码到原始源代码是一个复杂的问题。
虽然有一些工具可以进行反汇编和逆向工程,但这些工具通常无法提供与原始Go源代码结构完全相同的高级语言代码。
这是因为编译器进行了各种优化,同时丢失了一些高级语言结构的信息。
以下是一些可能用到的工具:
IDA Pro: IDA Pro是一款反汇编和逆向工程工具,但它通常只能提供汇编级别的代码,并不能还原为原始的高级语言源代码。
Ghidra: Ghidra是一款开源的逆向工程工具,它提供反汇编和分析二进制文件的功能。与IDA Pro类似,Ghidra也更倾向于提供汇编级别的代码。
汇编代码还原: 逆向工程师可能会尝试还原机器码为汇编代码,但这通常只是一个近似的过程,并不会得到原始的高级语言源代码。
2、防破解方式
防破解一直是个很常见的需求,特别是不能做成服务的、或者需要二进制私部署的场景,Go 的编译特性天然就比脚本类语言更好防逆向,脚本类代码是藏不住的。
当然防破解这个事情主要看投入产出比。Go 在这里的优势主要是省心,同时提高了破解的技术门槛。多数时候,写一个时间戳检查、或者 MAC 比对就很够用了。
举例:比如你可以分离功能逻辑和校验逻辑,校验失败并不会立即触发异常,而是会正常执行功能逻辑,但附加一个随机延迟的取消机制,使得功能完成之前就异常退出。
上述逻辑用 Go 代码写出来大概就是 contextWithCancel 几行代码的事情。这样产生的程序无论静态反编译还是静态调试都会很令人迷惑,静态方面难以定位校验逻辑,动态方面上下文切换缺少规律,实际运行表现又是难以稳定复现的,大幅增加了逆向难度。
除此之外还可以配合 burrowers/garble 之类的自动化混淆工具。
以下是常规的增加破解难度的方式:
- 删除调试符号
正常情况,我们go代码,部署完成,如果出现报错和日志等,会有暴露目录内容的风险;
go build -ldflags "-s -w" [<your/package] (go version > 1.7 )
-
删除trace文件信息
放到自己的.bash_profile或.zshrc中即可
bashACTUAL_GOPATH="~/Programming/go" export GOPATH='/tmp/go' export GOROOT_FINAL=$GOPATH [ ! -d $GOPATH ] && ln -s "$ACTUAL_GOPATH" "$GOPATH" [[ ! $PATH =~ $GOPATH ]] && export PATH=$PATH:$GOPATH/bin
- 代码混淆:
- 使用工具对Go代码进行混淆,使代码更难以理解。例如,可以使用诸如
gobfuscate
等工具。
-
静态编译: 使用静态编译将依赖项嵌入到二进制文件中,以减少对外部文件的依赖。这可以增加分析和替换的难度。
-
加密关键部分:
- 使用加密算法:使用加密库对关键代码进行加密。运行时在解密后执行这些代码。
- 例如,您可以使用Go的crypto包进行加密和解密操作。
- 代码分割:
- 模块化代码:将代码组织成模块,然后使用Go的动态导入或插件系统进行加载。
- 使用Go的plugin包可以实现动态加载。
-
使用硬编码密钥:
将密钥硬编码:在代码中直接将密钥硬编码,而不是存储在配置文件或其他地方package main
var encryptionKey = []byte{0x01, 0x02, 0x03, 0x04, 0x05}
-
防调试技术:
使用防调试技术,防止二进制文件在调试器中运行。这可以增加对抗性,使得破解变得更加困难。
使用runtime/debug包:通过runtime/debug包的ReadBuildInfo函数检查二进制文件是否被构建,从而检测是否在调试模式下运行。package main
import (
"fmt"
"runtime/debug"
)func main() {
if info := debug.ReadBuildInfo(); info != nil {
fmt.Println("Debug information found:", info)
// Take appropriate action if running in debug mode
}
} -
使用代码签名: 对二进制文件进行数字签名,以确保它的完整性和来源。这可以防止恶意篡改和替换。
-
运行时检测: 在运行时检测二进制文件是否被修改。这可以通过计算文件哈希值等方式来实现。
-
虚拟化技术: 使用虚拟化技术将关键代码片段放在虚拟机中执行,增加攻击者分析的难度。
-
动态链接库: 将一些关键逻辑放入动态链接库,并在运行时动态加载。这可以减少整个二进制文件的复杂性。