Golang木马HTTPS证书验证绕过技术详解
一、背景与问题描述
在分析Golang编写的木马程序时,发现其使用HTTPS协议进行通信。为了在本地测试环境中完整复现攻击场景,需要构建一个HTTPS WEB服务器。但使用自签名证书时,Golang木马程序无法成功访问服务器,原因是Golang默认会对服务器证书进行严格验证。
二、Golang证书验证机制
1. InsecureSkipVerify标志
Golang的crypto/tls库中,通过InsecureSkipVerify标志控制客户端是否验证服务器的证书链和主机名:
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 设置为true可跳过证书验证
}
2. 验证流程
证书验证主要发生在crypto/tls/handshake_client.go文件中的verifyServerCertificate函数,该函数会根据InsecureSkipVerify标志决定是否执行验证。
三、解决方案
1. 源代码修改方案
如果有源代码,直接设置InsecureSkipVerify: true即可:
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{
Transport: transport,
}
2. 二进制修改方案(无源码情况)
当没有源代码时,可以通过修改二进制文件来绕过证书验证。
关键二进制特征
不同Golang版本的关键特征:
-
Go 1.21.0至Go 1.23.0 (windows-amd64):
原始: 41 80 BA A0 00 00 00 00 0F 85 修改: 41 80 BA A0 00 00 00 00 0F 84 -
Go 1.21.0至Go 1.22.6 (windows-386):
原始: 0F B6 7E 50 97 84 C0 97 0F 85 修改: 0F B6 7E 50 97 84 C0 97 0F 84 -
Go 1.23.0 (windows-386):
原始: 0F B6 4E 50 84 C9 0F 85 修改: 0F B6 4E 50 84 C9 0F 84
修改原理
将条件跳转指令jnz(0F 85)修改为jz(0F 84),即反转验证逻辑。
3. 自动化修改脚本
使用Go编写的批量修改脚本:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func WalkDir(dirPth, suffix string) (files []string, err error) {
files = make([]string, 0, 30)
suffix = strings.ToUpper(suffix)
err = filepath.Walk(dirPth, func(filename string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
files = append(files, filename)
}
return nil
})
return files, err
}
func replaceBinaryData(filePath string, oldData, newData []byte) error {
fileContent, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
modifiedContent := bytes.Replace(fileContent, oldData, newData, -1)
err = ioutil.WriteFile(filePath, modifiedContent, 0644)
if err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
return nil
}
func main() {
files, err := WalkDir("F:\\GolandProjects\\awesomeProject9", "exe")
if err != nil {
fmt.Println("Error:", err.Error())
}
for _, onefile := range files {
oldData1 := []byte{0x41, 0x80, 0xBA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x85}
newData1 := []byte{0x41, 0x80, 0xBA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x84}
oldData2 := []byte{0x0F, 0xB6, 0x7E, 0x50, 0x97, 0x84, 0xC0, 0x97, 0x0F, 0x85}
newData2 := []byte{0x0F, 0xB6, 0x7E, 0x50, 0x97, 0x84, 0xC0, 0x97, 0x0F, 0x84}
oldData3 := []byte{0x0F, 0xB6, 0x4E, 0x50, 0x84, 0xC9, 0x0F, 0x85}
newData3 := []byte{0x0F, 0xB6, 0x4E, 0x50, 0x84, 0xC9, 0x0F, 0x84}
datas, err := ioutil.ReadFile(onefile)
if err != nil {
fmt.Println(err.Error())
}
if bytes.Contains(datas, oldData1) {
err = replaceBinaryData(onefile, oldData1, newData1)
} else if bytes.Contains(datas, oldData2) {
err = replaceBinaryData(onefile, oldData2, newData2)
} else if bytes.Contains(datas, oldData3) {
err = replaceBinaryData(onefile, oldData3, newData3)
}
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Binary data replaced successfully.")
}
}
}
四、版本兼容性分析
1. 操作系统影响
测试发现二进制特征与操作系统类型无关,相同Golang版本在不同操作系统下的特征一致。
2. 编译方式影响
不同编译方式(普通编译、使用-ldflags、使用garble)生成的二进制特征相同。
3. Golang版本影响
不同Golang版本的关键特征有所不同,主要分为三类:
- Go 1.21.0至Go 1.23.0 (windows-amd64)
- Go 1.21.0至Go 1.22.6 (windows-386)
- Go 1.23.0 (windows-386)
五、实践步骤
- 确定Golang版本:使用工具分析目标二进制文件的Golang版本
- 查找特征码:根据版本选择对应的特征码进行搜索
- 修改二进制:将
jnz(0F 85)修改为jz(0F 84) - 测试验证:运行修改后的程序验证是否绕过证书验证
六、注意事项
- 此方法仅用于安全研究和分析目的
- 修改二进制可能违反软件许可协议
- 不同Golang版本的特征可能不同,需要针对性处理
- 未来Golang版本可能会改变验证机制,需要持续关注
七、总结
通过分析Golang的TLS验证机制,我们可以在没有源代码的情况下,通过二进制修改绕过HTTPS证书验证。关键在于识别不同版本的特征码,并正确修改条件跳转指令。这种方法对于分析Golang编写的恶意软件特别有用,可以方便地在测试环境中复现攻击场景。