├── go.mod
├── go.sum
├── LICENSE
├── obfuscator
├── reflection.go
├── types.go
├── obfuscator.go
├── string_encryption.go
├── utils.go
├── junk_code.go
├── name_generator.go
├── name_obfuscation.go
├── scope.go
├── linker.go
└── pipeline.go
├── README.md
├── README_EN.md
└── cmd
└── main.go
/go.mod:
--------------------------------------------------------------------------------
1 | module cross-file-obfuscator
2 |
3 | go 1.22.0
4 |
5 | require golang.org/x/tools v0.26.0
6 |
7 | require (
8 | golang.org/x/mod v0.21.0 // indirect
9 | golang.org/x/sync v0.8.0 // indirect
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
2 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
3 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 MQ007
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/obfuscator/reflection.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "go/ast"
5 | "strings"
6 | )
7 |
8 | // detectReflectionUsage 检查文件是否使用反射
9 | func (o *Obfuscator) detectReflectionUsage(node *ast.File) bool {
10 | usesReflection := false
11 |
12 | for _, imp := range node.Imports {
13 | if imp.Path != nil && strings.Contains(imp.Path.Value, "reflect") {
14 | usesReflection = true
15 | break
16 | }
17 | }
18 |
19 | return usesReflection
20 | }
21 |
22 | // detectJSONUsage 检查文件是否使用 JSON 编码/解码
23 | func (o *Obfuscator) detectJSONUsage(node *ast.File) bool {
24 | usesJSON := false
25 |
26 | for _, imp := range node.Imports {
27 | if imp.Path != nil {
28 | path := strings.Trim(imp.Path.Value, "\"")
29 | if strings.Contains(path, "encoding/json") ||
30 | strings.Contains(path, "encoding/xml") ||
31 | strings.Contains(path, "gopkg.in/yaml") {
32 | usesJSON = true
33 | break
34 | }
35 | }
36 | }
37 |
38 | return usesJSON
39 | }
40 |
41 | // protectReflectionTypes 将反射上下文中使用的类型、字段和方法标记为受保护
42 | func (o *Obfuscator) protectReflectionTypes(node *ast.File) {
43 | if !o.Config.PreserveReflection {
44 | return
45 | }
46 |
47 | usesReflection := o.detectReflectionUsage(node)
48 | usesJSON := o.detectJSONUsage(node)
49 |
50 | if !usesReflection && !usesJSON {
51 | return
52 | }
53 |
54 | ast.Inspect(node, func(n ast.Node) bool {
55 | switch x := n.(type) {
56 | case *ast.TypeSpec:
57 | if usesReflection {
58 | o.protectedNames[x.Name.Name] = true
59 | }
60 |
61 | if structType, ok := x.Type.(*ast.StructType); ok {
62 | if structType.Fields != nil {
63 | for _, field := range structType.Fields.List {
64 | for _, fieldName := range field.Names {
65 | if usesReflection {
66 | o.protectedNames[fieldName.Name] = true
67 | } else if usesJSON {
68 | hasJSONTag := false
69 | if field.Tag != nil {
70 | tagValue := field.Tag.Value
71 | if strings.Contains(tagValue, "json:") {
72 | hasJSONTag = true
73 | }
74 | }
75 | if !hasJSONTag {
76 | o.protectedNames[fieldName.Name] = true
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | case *ast.FuncDecl:
84 | if x.Recv != nil && usesReflection {
85 | o.protectedNames[x.Name.Name] = true
86 | }
87 | }
88 | return true
89 | })
90 |
91 | if usesReflection {
92 | o.reflectionPackages[node.Name.Name] = true
93 | }
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/obfuscator/types.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "go/token"
5 | "math/big"
6 | )
7 |
8 | // Obfuscator 是混淆器的主结构体
9 | type Obfuscator struct {
10 | // 名称映射
11 | varMapping map[string]string
12 | funcMapping map[string]string
13 | exportedFuncMapping map[string]string
14 | importAliasMapping map[string]string
15 | importPathToName map[string]string
16 | fileNameMapping map[string]string
17 | filePathMapping map[string]string
18 |
19 | // 保护名称
20 | protectedNames map[string]bool
21 | packageNames map[string]bool
22 | reflectionPackages map[string]bool
23 | skippedFiles map[string]string
24 |
25 | // Token 文件集
26 | fset *token.FileSet
27 |
28 | // 随机种子和计数器
29 | randomSeed *big.Int
30 | encryptionKey string
31 | namingCounter int
32 |
33 | // 路径配置
34 | projectRoot string
35 | outputDir string
36 |
37 | // 配置选项
38 | Config *Config
39 |
40 | // 字符串加密追踪
41 | encryptedStrings map[string]bool
42 | decryptFuncAdded map[string]bool // 键为包的完整路径,用于多入口项目
43 | decryptFuncName string
44 | decryptPkgName string // 解密包的名称
45 | decryptPkgPath string // 解密包的导入路径
46 | decryptPkgCreated bool // 是否已创建解密包
47 |
48 | // 作用域分析
49 | fileScopes map[string]*ScopeAnalyzer // 文件路径 -> 作用域分析器
50 | objectMapping map[*Object]string // 对象 -> 混淆后的名称
51 | }
52 |
53 | // Config 存储混淆配置
54 | type Config struct {
55 | // 基础混淆选项
56 | ObfuscateExported bool // 是否混淆导出的函数(危险!)
57 | ObfuscateFileNames bool // 是否混淆文件名
58 | EncryptStrings bool // 是否加密字符串字面量
59 | InjectJunkCode bool // 是否注入垃圾代码
60 | RemoveComments bool // 是否移除注释
61 | PreserveReflection bool // 是否保留反射相关代码
62 | SkipGeneratedCode bool // 是否跳过自动生成的代码
63 | ExcludePatterns []string // 要排除的文件模式
64 | }
65 |
66 | // Statistics 存储混淆统计信息
67 | type Statistics struct {
68 | TotalFiles int
69 | ObfuscatedFiles int
70 | SkippedFiles int
71 | ProtectedNames int
72 | FunctionsObf int
73 | VariablesObf int
74 | StringsEncrypt int
75 | }
76 |
77 | // LinkConfig 链接器混淆配置
78 | type LinkConfig struct {
79 | RemoveFuncNames bool // 是否混淆函数名(替换包名前缀)
80 | EntryPackage string // 入口包路径,例如: "./cmd/server" 或 "." (当前目录)
81 | PackageReplacements map[string]string // 自定义包名替换映射,例如: {"github.com/user/project": "a", "main": "m"}
82 | AutoDiscoverPackages bool // 是否自动发现并替换项目中的所有包名
83 | ObfuscateThirdParty bool // 是否混淆第三方依赖包(谨慎使用)
84 | OnlyObfuscateProject bool // 只混淆项目包,不修改标准库(减少杀软误报)⭐ 新增
85 | DisablePclntab bool // 完全禁用 pclntab 修改(最安全)⭐ 新增
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/obfuscator/obfuscator.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "crypto/rand"
5 | "fmt"
6 | "go/token"
7 | "math/big"
8 | )
9 |
10 | // New 创建新的混淆器实例
11 | func New(projectRoot, outputDir string, config *Config) *Obfuscator {
12 | if config == nil {
13 | config = &Config{
14 | ObfuscateExported: false,
15 | ObfuscateFileNames: false,
16 | EncryptStrings: false,
17 | InjectJunkCode: false,
18 | RemoveComments: true,
19 | PreserveReflection: true,
20 | SkipGeneratedCode: true,
21 | ExcludePatterns: []string{},
22 | }
23 | }
24 |
25 | seed, _ := rand.Int(rand.Reader, big.NewInt(999999))
26 | encryptionKey := generateRandomString(64)
27 | // 生成完全随机的导出函数名(首字母大写)
28 | decryptFuncName := fmt.Sprintf("%c%s", 'A'+byte(seed.Int64()%26), generateRandomString(11))
29 | decryptPkgName := fmt.Sprintf("p%s", generateRandomString(8))
30 |
31 | return &Obfuscator{
32 | varMapping: make(map[string]string),
33 | funcMapping: make(map[string]string),
34 | exportedFuncMapping: make(map[string]string),
35 | fset: token.NewFileSet(),
36 | randomSeed: seed,
37 | encryptionKey: encryptionKey,
38 | namingCounter: 0,
39 | projectRoot: projectRoot,
40 | outputDir: outputDir,
41 | importAliasMapping: make(map[string]string),
42 | importPathToName: make(map[string]string),
43 | fileNameMapping: make(map[string]string),
44 | filePathMapping: make(map[string]string),
45 | protectedNames: make(map[string]bool),
46 | packageNames: make(map[string]bool),
47 | Config: config,
48 | encryptedStrings: make(map[string]bool),
49 | decryptFuncAdded: make(map[string]bool),
50 | decryptFuncName: decryptFuncName,
51 | decryptPkgName: decryptPkgName,
52 | decryptPkgCreated: false,
53 | skippedFiles: make(map[string]string),
54 | reflectionPackages: make(map[string]bool),
55 | fileScopes: make(map[string]*ScopeAnalyzer),
56 | objectMapping: make(map[*Object]string),
57 | }
58 | }
59 |
60 | // GetStatistics 返回混淆统计信息
61 | func (o *Obfuscator) GetStatistics() *Statistics {
62 | // 统计对象映射中的函数和变量
63 | funcCount := len(o.funcMapping)
64 | varCount := len(o.varMapping)
65 |
66 | // 如果使用了作用域分析,从objectMapping统计
67 | if len(o.objectMapping) > 0 {
68 | funcCount = 0
69 | varCount = 0
70 | for obj, obfName := range o.objectMapping {
71 | if obfName != "" {
72 | if obj.Kind == ObjFunc {
73 | funcCount++
74 | } else if obj.Kind == ObjVar || obj.Kind == ObjConst {
75 | varCount++
76 | }
77 | }
78 | }
79 | }
80 |
81 | return &Statistics{
82 | ProtectedNames: len(o.protectedNames),
83 | FunctionsObf: funcCount,
84 | VariablesObf: varCount,
85 | SkippedFiles: len(o.skippedFiles),
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/obfuscator/string_encryption.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "go/ast"
7 | "go/parser"
8 | "go/token"
9 | )
10 |
11 | // encryptString 使用 XOR 加密字符串
12 | func (o *Obfuscator) encryptString(text string) string {
13 | key := o.encryptionKey
14 | textBytes := []byte(text)
15 | encryptedBytes := make([]byte, len(textBytes))
16 |
17 | for i, b := range textBytes {
18 | keyByte := key[i%len(key)]
19 | encryptedBytes[i] = b ^ keyByte
20 | }
21 |
22 | return base64.StdEncoding.EncodeToString(encryptedBytes)
23 | }
24 |
25 | // generateDecryptFunction 生成解密函数源代码
26 | func (o *Obfuscator) generateDecryptFunction(base64Alias string) string {
27 | keyBytes := []byte(o.encryptionKey)
28 | keyLiteral := "[]byte{"
29 | for i, b := range keyBytes {
30 | if i > 0 {
31 | keyLiteral += ", "
32 | }
33 | keyLiteral += fmt.Sprintf("%d", b)
34 | }
35 | keyLiteral += "}"
36 |
37 | funcBody := fmt.Sprintf("func %s(encrypted string) string {\n", o.decryptFuncName)
38 | funcBody += fmt.Sprintf("\tdata, err := %s.StdEncoding.DecodeString(encrypted)\n", base64Alias)
39 | funcBody += "\tif err != nil {\n"
40 | funcBody += "\t\treturn \"\"\n"
41 | funcBody += "\t}\n"
42 | funcBody += fmt.Sprintf("\tkey := %s\n", keyLiteral)
43 | funcBody += "\tresult := make([]byte, len(data))\n"
44 | funcBody += "\tfor i, b := range data {\n"
45 | funcBody += "\t\tresult[i] = b ^ key[i%len(key)]\n"
46 | funcBody += "\t}\n"
47 | funcBody += "\treturn string(result)\n"
48 | funcBody += "}\n"
49 | return funcBody
50 | }
51 |
52 | // addDecryptFunction 添加解密函数到 AST
53 | func (o *Obfuscator) addDecryptFunction(node *ast.File) {
54 | // 获取 base64 别名
55 | base64Alias := "base64"
56 | for _, imp := range node.Imports {
57 | if imp.Path != nil && imp.Path.Value == `"encoding/base64"` {
58 | if imp.Name != nil {
59 | base64Alias = imp.Name.Name
60 | }
61 | break
62 | }
63 | }
64 |
65 | // 生成解密函数代码
66 | funcCode := o.generateDecryptFunction(base64Alias)
67 |
68 | // 解析函数代码为 AST
69 | fset := token.NewFileSet()
70 | funcNode, err := parser.ParseFile(fset, "", "package temp\n"+funcCode, 0)
71 | if err != nil {
72 | return
73 | }
74 |
75 | // 提取函数声明并添加到文件
76 | if len(funcNode.Decls) > 0 {
77 | if funcDecl, ok := funcNode.Decls[0].(*ast.FuncDecl); ok {
78 | node.Decls = append(node.Decls, funcDecl)
79 | }
80 | }
81 | }
82 |
83 | // ensureBase64Import 确保 AST 中存在 base64 导入
84 | func (o *Obfuscator) ensureBase64Import(node *ast.File) {
85 | hasBase64 := false
86 | for _, imp := range node.Imports {
87 | if imp.Path != nil && imp.Path.Value == `"encoding/base64"` {
88 | hasBase64 = true
89 | break
90 | }
91 | }
92 |
93 | if hasBase64 {
94 | return
95 | }
96 |
97 | base64Import := &ast.ImportSpec{
98 | Path: &ast.BasicLit{
99 | Kind: token.STRING,
100 | Value: `"encoding/base64"`,
101 | },
102 | }
103 |
104 | var importDecl *ast.GenDecl
105 | for _, decl := range node.Decls {
106 | if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.IMPORT {
107 | importDecl = genDecl
108 | break
109 | }
110 | }
111 |
112 | if importDecl != nil {
113 | importDecl.Specs = append(importDecl.Specs, base64Import)
114 | } else {
115 | importDecl = &ast.GenDecl{
116 | Tok: token.IMPORT,
117 | Specs: []ast.Spec{base64Import},
118 | }
119 | node.Decls = append([]ast.Decl{importDecl}, node.Decls...)
120 | }
121 |
122 | node.Imports = append(node.Imports, base64Import)
123 | }
124 |
--------------------------------------------------------------------------------
/obfuscator/utils.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "crypto/rand"
5 | "log"
6 | "math/big"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | // generateRandomString 生成随机字母数字字符串
12 | func generateRandomString(length int) string {
13 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
14 | result := make([]byte, length)
15 | for i := range result {
16 | num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
17 | if err != nil {
18 | log.Printf("Warning: Failed to generate random number: %v, using fallback", err)
19 | result[i] = charset[i%len(charset)]
20 | continue
21 | }
22 | result[i] = charset[num.Int64()]
23 | }
24 | return string(result)
25 | }
26 |
27 | // isStandardLibrary 检查导入路径是否属于 Go 标准库
28 | func isStandardLibrary(importPath string) bool {
29 | // 特殊情况:内部 Go 包
30 | if strings.HasPrefix(importPath, "internal/") || strings.HasPrefix(importPath, "vendor/") {
31 | return true
32 | }
33 |
34 | // 获取路径的第一个组件(第一个斜杠之前)
35 | firstComponent := importPath
36 | if idx := strings.Index(importPath, "/"); idx != -1 {
37 | firstComponent = importPath[:idx]
38 | }
39 |
40 | // 标准库包的第一个组件不包含点
41 | // 第三方包有域名(github.com, gopkg.in 等)
42 | return !strings.Contains(firstComponent, ".")
43 | }
44 |
45 | // isExported 检查名称是否为导出的(以大写字母开头)
46 | func isExported(name string) bool {
47 | if len(name) == 0 {
48 | return false
49 | }
50 | return name[0] >= 'A' && name[0] <= 'Z'
51 | }
52 |
53 | // shouldExcludeFile 检查文件是否应该被排除
54 | func (o *Obfuscator) shouldExcludeFile(filePath string) (bool, string) {
55 | // 获取相对于项目根目录的路径用于模式匹配
56 | relPath, err := filepath.Rel(o.projectRoot, filePath)
57 | if err != nil {
58 | relPath = filePath
59 | }
60 |
61 | // 检查排除模式
62 | for _, pattern := range o.Config.ExcludePatterns {
63 | // 尝试匹配相对路径(用于 "tools/*" 这样的模式)
64 | matched, err := filepath.Match(pattern, relPath)
65 | if err == nil && matched {
66 | return true, "matches pattern: " + pattern
67 | }
68 |
69 | // 也尝试只匹配文件名(用于 "*.pb.go" 这样的模式)
70 | matched, err = filepath.Match(pattern, filepath.Base(filePath))
71 | if err == nil && matched {
72 | return true, "matches pattern: " + pattern
73 | }
74 |
75 | // 检查模式是否包含路径分隔符,处理 "tools/*" 或 "**/test/**" 这样的模式
76 | if strings.Contains(pattern, string(filepath.Separator)) || strings.Contains(pattern, "/") {
77 | // 转换模式使用正确的分隔符
78 | normalizedPattern := filepath.FromSlash(pattern)
79 |
80 | // 检查相对路径是否以模式前缀开始或包含模式
81 | if strings.HasSuffix(normalizedPattern, "/*") {
82 | // 类似 "tools/*" 的模式 - 检查文件是否在此目录中
83 | dirPattern := strings.TrimSuffix(normalizedPattern, "/*")
84 | if strings.HasPrefix(relPath, dirPattern+string(filepath.Separator)) {
85 | return true, "matches pattern: " + pattern
86 | }
87 | // 也检查完全匹配目录名的情况
88 | if relPath == dirPattern {
89 | return true, "matches pattern: " + pattern
90 | }
91 | }
92 |
93 | // 处理 "*/certs/*" 这样的模式
94 | if strings.HasPrefix(normalizedPattern, "*/") {
95 | // 移除前缀 "*/"
96 | subPattern := strings.TrimPrefix(normalizedPattern, "*/")
97 | // 检查路径中是否包含这个子模式
98 | if strings.HasSuffix(subPattern, "/*") {
99 | // 模式是 "*/dirname/*" - 检查是否在任何 dirname 目录下
100 | dirName := strings.TrimSuffix(subPattern, "/*")
101 | pathParts := strings.Split(relPath, string(filepath.Separator))
102 | for _, part := range pathParts {
103 | if part == dirName {
104 | return true, "matches pattern: " + pattern
105 | }
106 | }
107 | } else {
108 | // 模式是 "*/filename" - 直接匹配
109 | if strings.Contains(relPath, string(filepath.Separator)+subPattern) {
110 | return true, "matches pattern: " + pattern
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
117 | return false, ""
118 | }
119 |
120 | // isGeneratedFile 检查文件是否为自动生成的
121 | func (o *Obfuscator) isGeneratedFile(path string) bool {
122 | // 检查文件名模式
123 | if strings.HasSuffix(path, ".pb.go") ||
124 | strings.HasSuffix(path, ".gen.go") ||
125 | strings.HasSuffix(path, "_generated.go") {
126 | return true
127 | }
128 | return false
129 | }
130 |
131 | // isExcluded 检查文件是否被排除
132 | func (o *Obfuscator) isExcluded(path string) bool {
133 | excluded, _ := o.shouldExcludeFile(path)
134 | return excluded
135 | }
136 |
--------------------------------------------------------------------------------
/obfuscator/junk_code.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "go/token"
7 | "strings"
8 | )
9 |
10 | // shouldSkipJunkCodeInjection 确定是否不应向函数注入垃圾代码
11 | func (o *Obfuscator) shouldSkipJunkCodeInjection(fn *ast.FuncDecl) bool {
12 | if fn.Name.Name == "init" {
13 | return true
14 | }
15 |
16 | if fn.Name.Name == "main" && fn.Recv == nil {
17 | return true
18 | }
19 |
20 | if fn.Doc != nil {
21 | for _, comment := range fn.Doc.List {
22 | if strings.HasPrefix(comment.Text, "//go:") {
23 | return true
24 | }
25 | }
26 | }
27 |
28 | if fn.Body != nil && len(fn.Body.List) <= 2 {
29 | return true
30 | }
31 |
32 | return false
33 | }
34 |
35 | // generateJunkStatements 生成带有不透明谓词的垃圾代码语句
36 | func (o *Obfuscator) generateJunkStatements(hasReturn bool) []ast.Stmt {
37 | junkVarName1 := fmt.Sprintf("l%s", generateRandomString(8))
38 | junkVarName2 := fmt.Sprintf("l%s", generateRandomString(8))
39 | junkVarName3 := fmt.Sprintf("l%s", generateRandomString(8))
40 |
41 | stmts := []ast.Stmt{
42 | // 不透明谓词 1: x*x >= 0 (总是为真)
43 | &ast.AssignStmt{
44 | Lhs: []ast.Expr{&ast.Ident{Name: junkVarName1}},
45 | Tok: token.DEFINE,
46 | Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "42"}},
47 | },
48 | &ast.IfStmt{
49 | Cond: &ast.BinaryExpr{
50 | X: &ast.BinaryExpr{
51 | X: &ast.Ident{Name: junkVarName1},
52 | Op: token.MUL,
53 | Y: &ast.Ident{Name: junkVarName1},
54 | },
55 | Op: token.GEQ,
56 | Y: &ast.BasicLit{Kind: token.INT, Value: "0"},
57 | },
58 | Body: &ast.BlockStmt{
59 | List: []ast.Stmt{
60 | &ast.AssignStmt{
61 | Lhs: []ast.Expr{&ast.Ident{Name: junkVarName1}},
62 | Tok: token.ASSIGN,
63 | Rhs: []ast.Expr{
64 | &ast.BinaryExpr{
65 | X: &ast.Ident{Name: junkVarName1},
66 | Op: token.ADD,
67 | Y: &ast.BasicLit{Kind: token.INT, Value: "1"},
68 | },
69 | },
70 | },
71 | },
72 | },
73 | },
74 |
75 | // 不透明谓词 2: (x^2 + x) % 2 == 0 (偶数 x 总是为真)
76 | &ast.AssignStmt{
77 | Lhs: []ast.Expr{&ast.Ident{Name: junkVarName2}},
78 | Tok: token.DEFINE,
79 | Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "10"}},
80 | },
81 | &ast.IfStmt{
82 | Cond: &ast.BinaryExpr{
83 | X: &ast.BinaryExpr{
84 | X: &ast.BinaryExpr{
85 | X: &ast.BinaryExpr{
86 | X: &ast.Ident{Name: junkVarName2},
87 | Op: token.MUL,
88 | Y: &ast.Ident{Name: junkVarName2},
89 | },
90 | Op: token.ADD,
91 | Y: &ast.Ident{Name: junkVarName2},
92 | },
93 | Op: token.REM,
94 | Y: &ast.BasicLit{Kind: token.INT, Value: "2"},
95 | },
96 | Op: token.EQL,
97 | Y: &ast.BasicLit{Kind: token.INT, Value: "0"},
98 | },
99 | Body: &ast.BlockStmt{
100 | List: []ast.Stmt{
101 | &ast.AssignStmt{
102 | Lhs: []ast.Expr{&ast.Ident{Name: junkVarName2}},
103 | Tok: token.ASSIGN,
104 | Rhs: []ast.Expr{
105 | &ast.BinaryExpr{
106 | X: &ast.Ident{Name: junkVarName2},
107 | Op: token.MUL,
108 | Y: &ast.BasicLit{Kind: token.INT, Value: "2"},
109 | },
110 | },
111 | },
112 | },
113 | },
114 | },
115 |
116 | // 不透明谓词 3: 2*x > x (正数 x 总是为真)
117 | &ast.AssignStmt{
118 | Lhs: []ast.Expr{&ast.Ident{Name: junkVarName3}},
119 | Tok: token.DEFINE,
120 | Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "5"}},
121 | },
122 | &ast.IfStmt{
123 | Cond: &ast.BinaryExpr{
124 | X: &ast.BinaryExpr{
125 | X: &ast.BasicLit{Kind: token.INT, Value: "2"},
126 | Op: token.MUL,
127 | Y: &ast.Ident{Name: junkVarName3},
128 | },
129 | Op: token.GTR,
130 | Y: &ast.Ident{Name: junkVarName3},
131 | },
132 | Body: &ast.BlockStmt{
133 | List: []ast.Stmt{
134 | &ast.AssignStmt{
135 | Lhs: []ast.Expr{&ast.Ident{Name: junkVarName3}},
136 | Tok: token.ASSIGN,
137 | Rhs: []ast.Expr{
138 | &ast.BinaryExpr{
139 | X: &ast.Ident{Name: junkVarName3},
140 | Op: token.SUB,
141 | Y: &ast.BasicLit{Kind: token.INT, Value: "1"},
142 | },
143 | },
144 | },
145 | },
146 | },
147 | },
148 |
149 | // 永远不会执行的死代码
150 | &ast.ForStmt{
151 | Cond: &ast.BinaryExpr{
152 | X: &ast.BinaryExpr{
153 | X: &ast.Ident{Name: junkVarName1},
154 | Op: token.LSS,
155 | Y: &ast.BasicLit{Kind: token.INT, Value: "0"},
156 | },
157 | Op: token.LAND,
158 | Y: &ast.BinaryExpr{
159 | X: &ast.Ident{Name: junkVarName2},
160 | Op: token.GTR,
161 | Y: &ast.BasicLit{Kind: token.INT, Value: "1000000"},
162 | },
163 | },
164 | Body: &ast.BlockStmt{
165 | List: []ast.Stmt{
166 | &ast.BranchStmt{Tok: token.BREAK},
167 | },
168 | },
169 | },
170 | }
171 |
172 | return stmts
173 | }
174 |
175 | // injectJunkCodeToAST 向 AST 注入垃圾代码
176 | func (o *Obfuscator) injectJunkCodeToAST(node ast.Node) {
177 | ast.Inspect(node, func(n ast.Node) bool {
178 | if fn, ok := n.(*ast.FuncDecl); ok {
179 | if o.shouldSkipJunkCodeInjection(fn) {
180 | return true
181 | }
182 |
183 | if fn.Body != nil && len(fn.Body.List) > 0 {
184 | hasReturn := fn.Type.Results != nil && len(fn.Type.Results.List) > 0
185 | junkStmts := o.generateJunkStatements(hasReturn)
186 | fn.Body.List = append(junkStmts, fn.Body.List...)
187 | }
188 | }
189 | return true
190 | })
191 | }
192 |
193 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Go 代码混淆器 (Cross-File Obfuscator)
2 |
3 | Go 代码混淆工具,使用 AST 技术实现跨文件代码混淆,保证混淆后代码**可编译和可执行**。
4 |
5 | [English][url-docen]
6 |
7 | ## 核心特性
8 |
9 | - **一键自动模式** - 全功能混淆 + 自动编译
10 | - **多层混淆** - 函数名 + 变量名 + 字符串加密 + 控制流混淆 + 二进制混淆
11 | - **智能保护** - 自动保护导出符号、反射、JSON/XML、接口、CGO
12 | - **链接器混淆** - 直接修改二进制文件混淆函数名和包路径
13 | - **自动包名发现** - 智能发现并混淆项目包名、标准库包名
14 |
15 | ## 快速开始
16 |
17 | ### 编译
18 |
19 | ```bash
20 | go build -o cross-file-obfuscator cmd/main.go
21 | ```
22 |
23 | ### 基本使用
24 |
25 | ```bash
26 | # 🚀 推荐:自动模式(一键完成所有混淆)
27 | ./cross-file-obfuscator -auto -output-bin myapp ./my-project
28 |
29 | # 多入口项目(main在子目录)
30 | ./cross-file-obfuscator -auto -entry "./cmd/server" -output-bin server ./my-project
31 |
32 | # 基础混淆(仅源码)
33 | ./cross-file-obfuscator ./my-project
34 |
35 | # 完整混淆(所有选项)
36 | ./cross-file-obfuscator -obfuscate-exported -obfuscate-filenames -encrypt-strings -inject-junk ./my-project
37 | ```
38 |
39 | ## 主要功能
40 |
41 | ### 1. 函数名和变量名混淆
42 | - 默认混淆未导出(小写开头)的函数和变量
43 | - 可选混淆导出符号(`-obfuscate-exported`)
44 | - 保持跨文件引用一致性
45 |
46 | ### 2. 字符串加密
47 | - XOR 加密所有字符串字面量
48 | - 运行时自动解密
49 | - 64字节随机密钥
50 |
51 | ### 3. 控制流混淆
52 | - 注入不透明谓词(永真/永假条件)
53 | - 增加逆向分析难度
54 |
55 | ### 4. 链接器级别混淆
56 | - 修改二进制文件中的函数名和包路径
57 | - 支持 ELF (Linux)、PE (Windows)、Mach-O (macOS)
58 | - 自动包名发现和替换
59 |
60 | ### 5. 智能保护机制
61 | - **自动保护**:结构体字段、方法、接口、内置标识符
62 | - **反射保护**:自动检测并保护使用 `reflect` 的代码
63 | - **序列化保护**:智能处理 JSON/XML 标签
64 | - **CGO 跳过**:自动跳过 CGO 代码
65 | - **生成代码跳过**:自动跳过 protobuf 等生成的代码
66 |
67 | ## 命令行选项
68 |
69 | ### 基础选项
70 | ```bash
71 | -o <目录> 输出目录(默认:项目名_obfuscated)
72 | -obfuscate-exported 混淆导出的函数和变量
73 | -obfuscate-filenames 混淆文件名
74 | -encrypt-strings 加密字符串
75 | -inject-junk 注入垃圾代码
76 | -remove-comments 删除注释(默认:true)
77 | -preserve-reflection 保护反射(默认:true)
78 | -exclude <模式> 排除文件(如:'*_test.go,*.pb.go')
79 | ```
80 |
81 | ### 自动模式选项
82 | ```bash
83 | -auto 自动模式(推荐)
84 | -output-bin <文件名> 输出二进制文件名
85 | -entry <包路径> 入口包路径(多入口项目必须指定)
86 | -auto-discover-pkgs 自动发现并替换项目包名(推荐)
87 | -obfuscate-third-party 混淆第三方依赖(谨慎使用)
88 | -only-project 只混淆项目包(Windows 推荐)
89 | ```
90 |
91 | ## 使用示例
92 |
93 | ### 标准项目
94 | ```bash
95 | # main.go 在根目录
96 | ./cross-file-obfuscator -auto -output-bin myapp ./my-project
97 | ```
98 |
99 | ### 多入口项目
100 | ```bash
101 | # main.go 在 cmd/server/main.go
102 | ./cross-file-obfuscator -auto -entry "./cmd/server" -output-bin server ./my-project
103 | ```
104 |
105 | ### Windows 平台(避免杀软误报)
106 | ```bash
107 | export GOOS=windows GOARCH=amd64
108 | ./cross-file-obfuscator -auto -output-bin app.exe ./my-project
109 | # 自动使用最小化混淆策略(只混淆项目包)
110 | ```
111 |
112 | ### 分步混淆
113 | ```bash
114 | # 第一步:源码混淆
115 | ./cross-file-obfuscator -encrypt-strings -inject-junk ./my-project
116 |
117 | # 第二步:链接器混淆
118 | ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs \
119 | -output-bin myapp ./my-project_obfuscated
120 | ```
121 |
122 | ## 重要说明
123 |
124 | ### 多入口项目必须指定 -entry
125 |
126 | **如何判断?**
127 | ```bash
128 | # 查找 main 函数位置
129 | find ./your-project -name "*.go" -exec grep -l "func main()" {} \;
130 | ```
131 |
132 | | 项目结构 | 是否需要-entry | 命令 |
133 | |---------|---------------|------|
134 | | `main.go` 在根目录 | ❌ | `./cross-file-obfuscator -auto -output-bin app ./project` |
135 | | `cmd/app/main.go` | ✅ | `./cross-file-obfuscator -auto -entry "./cmd/app" -output-bin app ./project` |
136 |
137 | ### pclntab 混淆策略
138 |
139 | | 策略 | 说明 | 适用场景 |
140 | |------|------|---------|
141 | | **完整混淆** | 混淆所有包(标准库+项目) | Linux/macOS(默认) |
142 | | **最小化混淆** | 只混淆项目包 | Windows(自动启用) |
143 | | **禁用混淆** | 不修改 pclntab | 保守方案 |
144 |
145 | ```bash
146 | # 手动指定最小化混淆
147 | ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs \
148 | -only-project -output-bin myapp ./my-project
149 |
150 | # 完全禁用 pclntab 混淆
151 | ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs \
152 | -disable-pclntab -output-bin myapp ./my-project
153 | ```
154 |
155 | ## 故障排除
156 |
157 | ### Archive 输出错误
158 |
159 | **症状**:
160 | ```bash
161 | $ file output
162 | output: current ar archive # 错误
163 | ```
164 |
165 | **原因**:main 包不在根目录,但没有指定 `-entry` 参数
166 |
167 | **解决**:
168 | ```bash
169 | # 1. 查找 main 包位置
170 | find ./project -name "*.go" -exec grep -l "func main()" {} \;
171 |
172 | # 2. 添加 -entry 参数
173 | ./cross-file-obfuscator -auto -entry "./cmd/gost" -output-bin output ./project
174 | ```
175 |
176 | ### 编译失败
177 |
178 | 1. 检查是否需要指定 `-entry` 参数
179 | 2. 确保 `-preserve-reflection=true`(默认)
180 | 3. 使用 `-exclude` 排除问题文件
181 | 4. 关闭高级功能测试:不使用 `-encrypt-strings` 和 `-inject-junk`
182 |
183 | ### 运行时错误
184 |
185 | 1. **反射问题**:检查反射相关的类型是否被保护
186 | 2. **接口实现**:工具会自动保护接口方法
187 | 3. **JSON/XML**:确保序列化字段有显式标签
188 | 4. **CGO 代码**:工具会自动跳过 CGO 文件
189 |
190 | ## 常见问题
191 |
192 | **Q: 为什么我的函数没有被混淆?**
193 | A: 默认只混淆未导出函数(小写开头)。导出函数需要使用 `-obfuscate-exported` 选项。
194 |
195 | **Q: 如何验证混淆是否成功?**
196 | A: 使用 `strings myapp | grep '^main\.'` 检查函数名前缀,应该返回 0。
197 |
198 | **Q: 如何排除某个目录?**
199 | A: 使用 `-exclude "dirname/*"` 模式。
200 |
201 | **Q: Windows 下被杀软误报?**
202 | A: AUTO 模式在 Windows 下自动使用最小化混淆策略,或手动添加 `-only-project` 选项。
203 |
204 | ## 技术原理
205 |
206 | ### 工作流程
207 | 1. **收集保护名称** - 识别需要保护的标识符(结构体字段、接口方法等)
208 | 2. **作用域分析** - 构建完整的作用域树,处理跨文件引用
209 | 3. **构建混淆映射** - 生成唯一的随机混淆名称
210 | 4. **复制项目文件** - 过滤 CGO、生成代码等特殊文件
211 | 5. **应用混淆** - 精确替换标识符,应用高级混淆技术
212 |
213 | ### 保护机制
214 | - **五层保护**:特殊标识符 → 内置标识符 → 导出名称 → 结构化保护 → 上下文保护
215 | - **作用域感知**:精确识别标识符作用域,避免错误替换
216 | - **Build 标签支持**:正确处理平台相关代码(`_linux.go`、`_windows.go`)
217 |
218 | ## 许可证
219 |
220 | MIT License
221 |
222 | [url-docen]: README_EN.md
223 |
--------------------------------------------------------------------------------
/obfuscator/name_generator.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "crypto/rand"
5 | "math/big"
6 | "strings"
7 | )
8 |
9 | // NaturalNameGenerator 生成看起来自然的包名和函数名
10 | type NaturalNameGenerator struct {
11 | usedNames map[string]bool
12 |
13 | // 包名片段
14 | pkgPrefixes []string
15 | pkgSuffixes []string
16 | pkgMiddles []string
17 |
18 | // 单词库(用于生成标识符)
19 | words []string
20 | }
21 |
22 | // NewNaturalNameGenerator 创建自然名称生成器
23 | func NewNaturalNameGenerator() *NaturalNameGenerator {
24 | return &NaturalNameGenerator{
25 | usedNames: make(map[string]bool),
26 |
27 | // 常见的包名前缀
28 | pkgPrefixes: []string{
29 | "app", "lib", "pkg", "sys", "web", "api", "net", "db",
30 | "svc", "core", "util", "data", "log", "auth", "cache",
31 | },
32 |
33 | // 常见的包名后缀
34 | pkgSuffixes: []string{
35 | "core", "util", "base", "main", "impl", "svc", "mgr",
36 | "handler", "service", "client", "server", "config",
37 | },
38 |
39 | // 中间词
40 | pkgMiddles: []string{
41 | "", "http", "grpc", "rest", "rpc", "sql", "store",
42 | },
43 |
44 | // 常见的英文单词(用于函数名等)
45 | words: []string{
46 | "handle", "process", "execute", "run", "start", "stop",
47 | "init", "setup", "config", "load", "save", "update",
48 | "create", "delete", "remove", "add", "set", "get",
49 | "fetch", "send", "receive", "parse", "format", "convert",
50 | },
51 | }
52 | }
53 |
54 | // GeneratePackageName 生成指定长度的看起来自然的包名
55 | // 例如: "runtime." (8 chars) → "libcore." (8 chars)
56 | func (g *NaturalNameGenerator) GeneratePackageName(originalName string, targetLength int) string {
57 | hasDot := strings.HasSuffix(originalName, ".")
58 |
59 | if hasDot {
60 | targetLength-- // 为末尾的点预留空间
61 | }
62 |
63 | var attempt int
64 | for attempt < 1000 { // 最多尝试1000次
65 | name := g.generateName(targetLength)
66 |
67 | if hasDot {
68 | name += "."
69 | }
70 |
71 | // 确保不重复
72 | if !g.usedNames[name] {
73 | g.usedNames[name] = true
74 | return name
75 | }
76 | attempt++
77 | }
78 |
79 | // 如果实在找不到,返回随机字符(但保持可读性)
80 | return g.generateRandomReadable(originalName, targetLength, hasDot)
81 | }
82 |
83 | // generateName 生成指定长度的名称
84 | func (g *NaturalNameGenerator) generateName(length int) string {
85 | if length <= 3 {
86 | // 短名称:使用缩写
87 | return g.generateShortName(length)
88 | }
89 |
90 | if length <= 8 {
91 | // 中等名称:使用前缀或后缀
92 | return g.generateMediumName(length)
93 | }
94 |
95 | // 长名称:使用前缀+后缀组合
96 | return g.generateLongName(length)
97 | }
98 |
99 | // generateShortName 生成短名称 (1-3 字符)
100 | func (g *NaturalNameGenerator) generateShortName(length int) string {
101 | shortNames := []string{
102 | "a", "b", "c", "x", "y", "z",
103 | "ab", "io", "os", "db", "fs", "ws",
104 | "app", "api", "sys", "lib", "net", "log",
105 | }
106 |
107 | // 过滤出符合长度的
108 | candidates := []string{}
109 | for _, name := range shortNames {
110 | if len(name) == length {
111 | candidates = append(candidates, name)
112 | }
113 | }
114 |
115 | if len(candidates) > 0 {
116 | idx := g.secureRandInt(len(candidates))
117 | return candidates[idx]
118 | }
119 |
120 | // 如果没有匹配的,生成随机字母
121 | return g.randomLetters(length)
122 | }
123 |
124 | // generateMediumName 生成中等长度名称 (4-8 字符)
125 | func (g *NaturalNameGenerator) generateMediumName(length int) string {
126 | // 尝试使用单个词
127 | for _, prefix := range g.pkgPrefixes {
128 | if len(prefix) == length {
129 | return prefix
130 | }
131 | }
132 |
133 | for _, suffix := range g.pkgSuffixes {
134 | if len(suffix) == length {
135 | return suffix
136 | }
137 | }
138 |
139 | // 如果没有完全匹配的,截断或组合
140 | if length >= 4 {
141 | word := g.pkgPrefixes[g.secureRandInt(len(g.pkgPrefixes))]
142 | if len(word) > length {
143 | return word[:length]
144 | } else if len(word) < length {
145 | // 添加后缀
146 | suffix := g.pkgMiddles[g.secureRandInt(len(g.pkgMiddles))]
147 | combined := word + suffix
148 | if len(combined) >= length {
149 | return combined[:length]
150 | }
151 | // 继续添加
152 | return g.padWithLetters(combined, length)
153 | }
154 | return word
155 | }
156 |
157 | return g.randomLetters(length)
158 | }
159 |
160 | // generateLongName 生成长名称 (9+ 字符)
161 | func (g *NaturalNameGenerator) generateLongName(length int) string {
162 | // 组合多个词
163 | prefix := g.pkgPrefixes[g.secureRandInt(len(g.pkgPrefixes))]
164 | suffix := g.pkgSuffixes[g.secureRandInt(len(g.pkgSuffixes))]
165 |
166 | combined := prefix + suffix
167 |
168 | if len(combined) > length {
169 | return combined[:length]
170 | } else if len(combined) < length {
171 | // 需要更多字符
172 | middle := g.pkgMiddles[g.secureRandInt(len(g.pkgMiddles))]
173 | combined = prefix + middle + suffix
174 |
175 | if len(combined) >= length {
176 | return combined[:length]
177 | }
178 |
179 | // 还是不够,继续填充
180 | return g.padWithLetters(combined, length)
181 | }
182 |
183 | return combined
184 | }
185 |
186 | // generateRandomReadable 生成随机但可读的名称
187 | func (g *NaturalNameGenerator) generateRandomReadable(original string, length int, hasDot bool) string {
188 | // 保留原始名称的第一个字符(如果可能)
189 | result := ""
190 | if len(original) > 0 && original[0] >= 'a' && original[0] <= 'z' {
191 | result = string(original[0])
192 | } else {
193 | result = string(rune('a' + g.secureRandInt(26)))
194 | }
195 |
196 | // 交替使用辅音和元音,使其更可读
197 | vowels := "aeiou"
198 | consonants := "bcdfghjklmnpqrstvwxyz"
199 |
200 | useVowel := false
201 | for len(result) < length {
202 | if useVowel {
203 | result += string(vowels[g.secureRandInt(len(vowels))])
204 | } else {
205 | result += string(consonants[g.secureRandInt(len(consonants))])
206 | }
207 | useVowel = !useVowel
208 | }
209 |
210 | if hasDot {
211 | result += "."
212 | }
213 |
214 | return result
215 | }
216 |
217 | // padWithLetters 用字母填充到指定长度
218 | func (g *NaturalNameGenerator) padWithLetters(base string, targetLength int) string {
219 | letters := "abcdefghijklmnopqrstuvwxyz"
220 | for len(base) < targetLength {
221 | base += string(letters[g.secureRandInt(len(letters))])
222 | }
223 | return base
224 | }
225 |
226 | // randomLetters 生成随机字母
227 | func (g *NaturalNameGenerator) randomLetters(length int) string {
228 | letters := "abcdefghijklmnopqrstuvwxyz"
229 | result := ""
230 | for i := 0; i < length; i++ {
231 | result += string(letters[g.secureRandInt(len(letters))])
232 | }
233 | return result
234 | }
235 |
236 | // secureRandInt 生成安全的随机整数 [0, max)
237 | func (g *NaturalNameGenerator) secureRandInt(max int) int {
238 | if max <= 0 {
239 | return 0
240 | }
241 | n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
242 | if err != nil {
243 | // 降级到不安全的随机数
244 | return int(n.Int64()) % max
245 | }
246 | return int(n.Int64())
247 | }
248 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # Go Code Obfuscator (Cross-File Obfuscator)
2 |
3 | Go code obfuscation tool using AST technology for cross-file obfuscation, ensuring obfuscated code is **compilable and executable**.
4 |
5 | [中文][url-doczh] | [Full Documentation](README_EN_FULL.md)
6 |
7 | ## Core Features
8 |
9 | - **One-Click Auto Mode** - Full obfuscation + automatic compilation
10 | - **Multi-Layer Obfuscation** - Function names + variable names + string encryption + control flow + binary obfuscation
11 | - **Smart Protection** - Auto-protect exported symbols, reflection, JSON/XML, interfaces, CGO
12 | - **Linker-Level Obfuscation** - Directly modify binary files to obfuscate function names and package paths
13 | - **Auto Package Discovery** - Intelligently discover and obfuscate project packages and standard library
14 |
15 | ## Quick Start
16 |
17 | ### Build
18 |
19 | ```bash
20 | go build -o cross-file-obfuscator cmd/main.go
21 | ```
22 |
23 | ### Basic Usage
24 |
25 | ```bash
26 | # 🚀 Recommended: Auto mode (one command for everything)
27 | ./cross-file-obfuscator -auto -output-bin myapp ./my-project
28 |
29 | # Multi-entry project (main in subdirectory)
30 | ./cross-file-obfuscator -auto -entry "./cmd/server" -output-bin server ./my-project
31 |
32 | # Basic obfuscation (source code only)
33 | ./cross-file-obfuscator ./my-project
34 |
35 | # Full obfuscation (all options)
36 | ./cross-file-obfuscator -obfuscate-exported -obfuscate-filenames -encrypt-strings -inject-junk ./my-project
37 | ```
38 |
39 | ## Main Features
40 |
41 | ### 1. Function and Variable Name Obfuscation
42 | - By default, obfuscate unexported (lowercase) functions and variables
43 | - Optionally obfuscate exported symbols (`-obfuscate-exported`)
44 | - Maintain cross-file reference consistency
45 |
46 | ### 2. String Encryption
47 | - XOR encrypt all string literals
48 | - Automatic runtime decryption
49 | - 64-byte random key
50 |
51 | ### 3. Control Flow Obfuscation
52 | - Inject opaque predicates (always-true/false conditions)
53 | - Increase reverse engineering difficulty
54 |
55 | ### 4. Linker-Level Obfuscation
56 | - Modify function names and package paths in binary files
57 | - Support ELF (Linux), PE (Windows), Mach-O (macOS)
58 | - Automatic package discovery and replacement
59 |
60 | ### 5. Smart Protection Mechanism
61 | - **Auto-protect**: Struct fields, methods, interfaces, built-in identifiers
62 | - **Reflection Protection**: Auto-detect and protect code using `reflect`
63 | - **Serialization Protection**: Smart handling of JSON/XML tags
64 | - **CGO Skip**: Automatically skip CGO code
65 | - **Generated Code Skip**: Automatically skip protobuf and other generated code
66 |
67 | ## Command Line Options
68 |
69 | ### Basic Options
70 | ```bash
71 | -o
Output directory (default: project_obfuscated)
72 | -obfuscate-exported Obfuscate exported functions and variables
73 | -obfuscate-filenames Obfuscate file names
74 | -encrypt-strings Encrypt strings
75 | -inject-junk Inject junk code
76 | -remove-comments Remove comments (default: true)
77 | -preserve-reflection Protect reflection (default: true)
78 | -exclude Exclude files (e.g., '*_test.go,*.pb.go')
79 | ```
80 |
81 | ### Auto Mode Options
82 | ```bash
83 | -auto Auto mode (recommended)
84 | -output-bin Output binary filename
85 | -entry Entry package path (required for multi-entry projects)
86 | -auto-discover-pkgs Auto-discover and replace project packages (recommended)
87 | -obfuscate-third-party Obfuscate third-party dependencies (use with caution)
88 | -only-project Only obfuscate project packages (recommended for Windows)
89 | ```
90 |
91 | ## Usage Examples
92 |
93 | ### Standard Project
94 | ```bash
95 | # main.go in root directory
96 | ./cross-file-obfuscator -auto -output-bin myapp ./my-project
97 | ```
98 |
99 | ### Multi-Entry Project
100 | ```bash
101 | # main.go in cmd/server/main.go
102 | ./cross-file-obfuscator -auto -entry "./cmd/server" -output-bin server ./my-project
103 | ```
104 |
105 | ### Windows Platform (Avoid Antivirus False Positives)
106 | ```bash
107 | export GOOS=windows GOARCH=amd64
108 | ./cross-file-obfuscator -auto -output-bin app.exe ./my-project
109 | # Automatically uses minimal obfuscation strategy (only project packages)
110 | ```
111 |
112 | ### Step-by-Step Obfuscation
113 | ```bash
114 | # Step 1: Source code obfuscation
115 | ./cross-file-obfuscator -encrypt-strings -inject-junk ./my-project
116 |
117 | # Step 2: Linker obfuscation
118 | ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs \
119 | -output-bin myapp ./my-project_obfuscated
120 | ```
121 |
122 | ## Important Notes
123 |
124 | ### Multi-Entry Projects Must Specify -entry
125 |
126 | **How to determine?**
127 | ```bash
128 | # Find main function location
129 | find ./your-project -name "*.go" -exec grep -l "func main()" {} \;
130 | ```
131 |
132 | | Project Structure | Need -entry? | Command |
133 | |------------------|--------------|---------|
134 | | `main.go` in root | ❌ | `./cross-file-obfuscator -auto -output-bin app ./project` |
135 | | `cmd/app/main.go` | ✅ | `./cross-file-obfuscator -auto -entry "./cmd/app" -output-bin app ./project` |
136 |
137 | ### pclntab Obfuscation Strategy
138 |
139 | | Strategy | Description | Use Case |
140 | |----------|-------------|----------|
141 | | **Full Obfuscation** | Obfuscate all packages (stdlib + project) | Linux/macOS (default) |
142 | | **Minimal Obfuscation** | Only obfuscate project packages | Windows (auto-enabled) |
143 | | **Disabled** | Don't modify pclntab | Conservative approach |
144 |
145 | ```bash
146 | # Manually specify minimal obfuscation
147 | ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs \
148 | -only-project -output-bin myapp ./my-project
149 |
150 | # Completely disable pclntab obfuscation
151 | ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs \
152 | -disable-pclntab -output-bin myapp ./my-project
153 | ```
154 |
155 | ## Troubleshooting
156 |
157 | ### Archive Output Error
158 |
159 | **Symptom**:
160 | ```bash
161 | $ file output
162 | output: current ar archive # Error
163 | ```
164 |
165 | **Cause**: Main package not in root directory, but `-entry` parameter not specified
166 |
167 | **Solution**:
168 | ```bash
169 | # 1. Find main package location
170 | find ./project -name "*.go" -exec grep -l "func main()" {} \;
171 |
172 | # 2. Add -entry parameter
173 | ./cross-file-obfuscator -auto -entry "./cmd/gost" -output-bin output ./project
174 | ```
175 |
176 | ### Compilation Failure
177 |
178 | 1. Check if `-entry` parameter is needed
179 | 2. Ensure `-preserve-reflection=true` (default)
180 | 3. Use `-exclude` to exclude problematic files
181 | 4. Disable advanced features for testing: don't use `-encrypt-strings` and `-inject-junk`
182 |
183 | ### Runtime Errors
184 |
185 | 1. **Reflection Issues**: Check if reflection-related types are protected
186 | 2. **Interface Implementation**: Tool automatically protects interface methods
187 | 3. **JSON/XML**: Ensure serialization fields have explicit tags
188 | 4. **CGO Code**: Tool automatically skips CGO files
189 |
190 | ## FAQ
191 |
192 | **Q: Why isn't my function obfuscated?**
193 | A: By default, only unexported functions (lowercase) are obfuscated. Use `-obfuscate-exported` for exported functions.
194 |
195 | **Q: How to verify obfuscation success?**
196 | A: Use `strings myapp | grep '^main\.'` to check function name prefixes, should return 0.
197 |
198 | **Q: How to exclude a directory?**
199 | A: Use `-exclude "dirname/*"` pattern.
200 |
201 | **Q: Antivirus false positive on Windows?**
202 | A: AUTO mode automatically uses minimal obfuscation strategy on Windows, or manually add `-only-project` option.
203 |
204 | ## Technical Principles
205 |
206 | ### Workflow
207 | 1. **Collect Protected Names** - Identify identifiers to protect (struct fields, interface methods, etc.)
208 | 2. **Scope Analysis** - Build complete scope tree, handle cross-file references
209 | 3. **Build Obfuscation Mapping** - Generate unique random obfuscated names
210 | 4. **Copy Project Files** - Filter special files like CGO, generated code
211 | 5. **Apply Obfuscation** - Precisely replace identifiers, apply advanced obfuscation techniques
212 |
213 | ### Protection Mechanism
214 | - **Five-Layer Protection**: Special identifiers → Built-in identifiers → Exported names → Structured protection → Context protection
215 | - **Scope-Aware**: Precisely identify identifier scopes, avoid incorrect replacements
216 | - **Build Tag Support**: Correctly handle platform-specific code (`_linux.go`, `_windows.go`)
217 |
218 | ## License
219 |
220 | MIT License
221 |
222 | [url-doczh]: README.md
223 |
--------------------------------------------------------------------------------
/obfuscator/name_obfuscation.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // obfuscateName 创建一个混淆后的名称(不暴露原始名称)
9 | func (o *Obfuscator) obfuscateName(name string, isFunc bool) string {
10 | mapping := o.varMapping
11 | prefix := "l" // 局部变量
12 | if isFunc {
13 | mapping = o.funcMapping
14 | prefix = "fn" // 函数
15 | }
16 |
17 | if obf, exists := mapping[name]; exists {
18 | return obf
19 | }
20 |
21 | // 检查是否为导出名称(首字母大写)
22 | isExported := len(name) > 0 && name[0] >= 'A' && name[0] <= 'Z'
23 |
24 | // 生成唯一的混淆名称,不暴露原始名称
25 | // 使用循环确保在所有映射中的唯一性
26 | maxAttempts := 100
27 | for attempt := 0; attempt < maxAttempts; attempt++ {
28 | randomPart := generateRandomString(12)
29 | var obf string
30 |
31 | if isExported {
32 | // 导出名称:首字母大写
33 | if isFunc {
34 | obf = fmt.Sprintf("Fn%s", randomPart)
35 | } else {
36 | obf = fmt.Sprintf("V%s", randomPart)
37 | }
38 | } else {
39 | // 私有名称:首字母小写
40 | obf = fmt.Sprintf("%s%s", prefix, randomPart)
41 | }
42 |
43 | // 检查此名称是否已在任何映射中使用
44 | if !o.isNameUsed(obf) {
45 | mapping[name] = obf
46 | return obf
47 | }
48 | }
49 |
50 | // 回退:使用基于计数器的方法
51 | o.namingCounter++
52 | var obf string
53 | if isExported {
54 | if isFunc {
55 | obf = fmt.Sprintf("Fn%d_%s", o.namingCounter, generateRandomString(8))
56 | } else {
57 | obf = fmt.Sprintf("V%d_%s", o.namingCounter, generateRandomString(8))
58 | }
59 | } else {
60 | obf = fmt.Sprintf("%s%d_%s", prefix, o.namingCounter, generateRandomString(8))
61 | }
62 | mapping[name] = obf
63 | return obf
64 | }
65 |
66 | // isNameUsed 检查名称是否已在任何映射中使用
67 | func (o *Obfuscator) isNameUsed(name string) bool {
68 | // 检查变量映射
69 | for _, obfName := range o.varMapping {
70 | if obfName == name {
71 | return true
72 | }
73 | }
74 |
75 | // 检查函数映射
76 | for _, obfName := range o.funcMapping {
77 | if obfName == name {
78 | return true
79 | }
80 | }
81 |
82 | // 检查导入别名映射
83 | for _, obfName := range o.importAliasMapping {
84 | if obfName == name {
85 | return true
86 | }
87 | }
88 |
89 | return false
90 | }
91 |
92 | // shouldProtect 检查名称是否应受保护而不被混淆
93 | func (o *Obfuscator) shouldProtect(name string) bool {
94 | // 保护特殊名称
95 | if name == "_" || name == "main" || name == "init" {
96 | return true
97 | }
98 | // 如果 obfuscateExported 为 false,保护所有导出的名称
99 | if !o.Config.ObfuscateExported && isExported(name) {
100 | return true
101 | }
102 | // 保护受保护列表中的名称(字段、方法、选择器)
103 | if o.protectedNames[name] {
104 | return true
105 | }
106 | // 保护包名称(来自导入)
107 | if o.packageNames[name] {
108 | return true
109 | }
110 | // 保护可能导致问题的常见 Go 标识符
111 | protectedIdentifiers := map[string]bool{
112 | "error": true, // 内置错误接口
113 | "string": true, // 内置类型
114 | "int": true,
115 | "bool": true,
116 | "byte": true,
117 | "rune": true,
118 | "float32": true,
119 | "float64": true,
120 | "complex64": true,
121 | "complex128": true,
122 | "uint": true,
123 | "int8": true,
124 | "int16": true,
125 | "int32": true,
126 | "int64": true,
127 | "uint8": true,
128 | "uint16": true,
129 | "uint32": true,
130 | "uint64": true,
131 | "uintptr": true,
132 | "len": true, // 内置函数
133 | "cap": true,
134 | "make": true,
135 | "new": true,
136 | "append": true,
137 | "copy": true,
138 | "delete": true,
139 | "panic": true,
140 | "recover": true,
141 | "print": true,
142 | "println": true,
143 | "close": true,
144 | "min": true, // Go 1.21+ 内置函数
145 | "max": true,
146 | "clear": true,
147 | "nil": true, // 内置常量
148 | "true": true,
149 | "false": true,
150 | "iota": true,
151 | }
152 | if protectedIdentifiers[name] {
153 | return true
154 | }
155 | return false
156 | }
157 |
158 | // obfuscateFileName 混淆 Go 文件名(不暴露原始名称)
159 | func (o *Obfuscator) obfuscateFileName(fileName string) string {
160 | // 不混淆特殊文件
161 | if fileName == "main.go" || fileName == "go.mod" || fileName == "go.sum" {
162 | return fileName
163 | }
164 |
165 | // 不混淆带平台后缀的 main 文件(如 main_windows.go, main_linux.go 等)
166 | if strings.HasPrefix(fileName, "main_") && strings.HasSuffix(fileName, ".go") {
167 | return fileName
168 | }
169 |
170 | // 检查是否已有此文件名的映射
171 | if obfuscated, exists := o.fileNameMapping[fileName]; exists {
172 | return obfuscated
173 | }
174 |
175 | // ✅ 提取平台特定后缀(如 _linux, _windows, _darwin 等)
176 | // 这些后缀用于build标签,必须保留以避免同名函数冲突
177 | platformSuffixes := []string{
178 | "_linux", "_windows", "_darwin", "_freebsd", "_openbsd", "_netbsd",
179 | "_dragonfly", "_solaris", "_plan9", "_aix", "_android", "_ios",
180 | "_js", "_wasm", "_unix", "_other",
181 | "_amd64", "_386", "_arm", "_arm64", "_ppc64", "_ppc64le",
182 | "_mips", "_mipsle", "_mips64", "_mips64le", "_s390x", "_riscv64",
183 | }
184 |
185 | suffix := ""
186 | if strings.HasSuffix(fileName, ".go") {
187 | nameWithoutExt := strings.TrimSuffix(fileName, ".go")
188 | for _, ps := range platformSuffixes {
189 | if strings.HasSuffix(nameWithoutExt, ps) {
190 | suffix = ps
191 | break
192 | }
193 | }
194 | }
195 |
196 | // 生成随机文件名
197 | maxAttempts := 100
198 | for attempt := 0; attempt < maxAttempts; attempt++ {
199 | randomPart := generateRandomString(10)
200 | var obfuscatedName string
201 | if suffix != "" {
202 | // 保留平台后缀
203 | obfuscatedName = fmt.Sprintf("f%s%s.go", randomPart, suffix)
204 | } else {
205 | obfuscatedName = fmt.Sprintf("f%s.go", randomPart)
206 | }
207 |
208 | // 检查此名称是否已被使用
209 | nameUsed := false
210 | for _, existingName := range o.fileNameMapping {
211 | if existingName == obfuscatedName {
212 | nameUsed = true
213 | break
214 | }
215 | }
216 |
217 | if !nameUsed {
218 | // 存储映射并返回
219 | o.fileNameMapping[fileName] = obfuscatedName
220 | return obfuscatedName
221 | }
222 | }
223 |
224 | // 回退:如果随机生成失败,使用基于计数器的方法
225 | var obfuscatedName string
226 | if suffix != "" {
227 | obfuscatedName = fmt.Sprintf("f%d_%s%s.go", len(o.fileNameMapping), generateRandomString(6), suffix)
228 | } else {
229 | obfuscatedName = fmt.Sprintf("f%d_%s.go", len(o.fileNameMapping), generateRandomString(6))
230 | }
231 | o.fileNameMapping[fileName] = obfuscatedName
232 | return obfuscatedName
233 | }
234 |
235 | // generateUniqueObfuscatedNameForObject 为对象生成全局唯一的混淆名称
236 | // 这是修复导出函数混淆冲突的核心函数
237 | func (o *Obfuscator) generateUniqueObfuscatedNameForObject(obj *Object) string {
238 | // 检查是否已经有混淆名
239 | if existingName, exists := o.objectMapping[obj]; exists && existingName != "" {
240 | return existingName
241 | }
242 |
243 | // 检查是否为导出名称
244 | isExported := len(obj.Name) > 0 && obj.Name[0] >= 'A' && obj.Name[0] <= 'Z'
245 |
246 | // 尝试生成唯一的混淆名称
247 | maxAttempts := 100
248 | for attempt := 0; attempt < maxAttempts; attempt++ {
249 | randomPart := generateRandomString(12)
250 | var obfName string
251 |
252 | if obj.Kind == ObjFunc {
253 | if isExported {
254 | obfName = fmt.Sprintf("Fn%s", randomPart)
255 | } else {
256 | obfName = fmt.Sprintf("fn%s", randomPart)
257 | }
258 | } else { // ObjVar or ObjConst
259 | if isExported {
260 | obfName = fmt.Sprintf("V%s", randomPart)
261 | } else {
262 | obfName = fmt.Sprintf("l%s", randomPart)
263 | }
264 | }
265 |
266 | // 检查此名称是否在整个项目中已被使用
267 | if !o.isObfuscatedNameUsedInProject(obfName) {
268 | return obfName
269 | }
270 | }
271 |
272 | // 回退:使用基于计数器的方法
273 | o.namingCounter++
274 | var obfName string
275 | if obj.Kind == ObjFunc {
276 | if isExported {
277 | obfName = fmt.Sprintf("Fn%d_%s", o.namingCounter, generateRandomString(8))
278 | } else {
279 | obfName = fmt.Sprintf("fn%d_%s", o.namingCounter, generateRandomString(8))
280 | }
281 | } else {
282 | if isExported {
283 | obfName = fmt.Sprintf("V%d_%s", o.namingCounter, generateRandomString(8))
284 | } else {
285 | obfName = fmt.Sprintf("l%d_%s", o.namingCounter, generateRandomString(8))
286 | }
287 | }
288 | return obfName
289 | }
290 |
291 | // isObfuscatedNameUsedInProject 检查混淆名在整个项目中是否已被使用
292 | func (o *Obfuscator) isObfuscatedNameUsedInProject(name string) bool {
293 | // 检查objectMapping中的所有混淆名
294 | for _, obfName := range o.objectMapping {
295 | if obfName == name {
296 | return true
297 | }
298 | }
299 |
300 | // 检查funcMapping和varMapping(向后兼容)
301 | for _, obfName := range o.funcMapping {
302 | if obfName == name {
303 | return true
304 | }
305 | }
306 | for _, obfName := range o.varMapping {
307 | if obfName == name {
308 | return true
309 | }
310 | }
311 |
312 | // 检查导入别名映射
313 | for _, obfName := range o.importAliasMapping {
314 | if obfName == name {
315 | return true
316 | }
317 | }
318 |
319 | return false
320 | }
321 |
322 |
--------------------------------------------------------------------------------
/obfuscator/scope.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | )
7 |
8 | // Scope 表示一个作用域
9 | type Scope struct {
10 | Parent *Scope // 父作用域
11 | Children []*Scope // 子作用域
12 | Objects map[string]*Object // 该作用域中定义的对象
13 | Node ast.Node // 关联的AST节点
14 | Start token.Pos // 作用域起始位置
15 | End token.Pos // 作用域结束位置
16 | }
17 |
18 | // Object 表示一个声明的对象(变量、函数等)
19 | type Object struct {
20 | Name string // 原始名称
21 | ObfuscatedName string // 混淆后的名称
22 | Kind ObjectKind // 对象类型
23 | Decl ast.Node // 声明节点
24 | Scope *Scope // 所属作用域
25 | IsExported bool // 是否导出
26 | Pos token.Pos // 声明位置
27 | FilePath string // 对象所在的文件路径(用于生成唯一标识)
28 | }
29 |
30 | // ObjectKind 对象类型
31 | type ObjectKind int
32 |
33 | const (
34 | ObjUnknown ObjectKind = iota
35 | ObjPackage // 包名
36 | ObjConst // 常量
37 | ObjVar // 变量
38 | ObjFunc // 函数
39 | ObjType // 类型
40 | ObjLabel // 标签
41 | ObjField // 结构体字段
42 | ObjMethod // 方法
43 | )
44 |
45 | // ScopeAnalyzer 作用域分析器
46 | type ScopeAnalyzer struct {
47 | fset *token.FileSet
48 | currentScope *Scope
49 | fileScope *Scope
50 | packageScope *Scope
51 | scopes map[ast.Node]*Scope // AST节点到作用域的映射
52 | objects map[token.Pos]*Object // 位置到对象的映射
53 | }
54 |
55 | // NewScopeAnalyzer 创建新的作用域分析器
56 | func NewScopeAnalyzer(fset *token.FileSet) *ScopeAnalyzer {
57 | packageScope := &Scope{
58 | Objects: make(map[string]*Object),
59 | }
60 |
61 | return &ScopeAnalyzer{
62 | fset: fset,
63 | packageScope: packageScope,
64 | currentScope: packageScope,
65 | scopes: make(map[ast.Node]*Scope),
66 | objects: make(map[token.Pos]*Object),
67 | }
68 | }
69 |
70 | // Analyze 分析文件的作用域
71 | func (sa *ScopeAnalyzer) Analyze(file *ast.File) {
72 | sa.fileScope = sa.newScope(file)
73 | sa.currentScope = sa.fileScope
74 |
75 | // 遍历所有声明
76 | for _, decl := range file.Decls {
77 | sa.analyzeDecl(decl)
78 | }
79 | }
80 |
81 | // newScope 创建新的作用域
82 | func (sa *ScopeAnalyzer) newScope(node ast.Node) *Scope {
83 | scope := &Scope{
84 | Parent: sa.currentScope,
85 | Objects: make(map[string]*Object),
86 | Node: node,
87 | }
88 |
89 | if node != nil {
90 | scope.Start = node.Pos()
91 | scope.End = node.End()
92 | }
93 |
94 | if sa.currentScope != nil {
95 | sa.currentScope.Children = append(sa.currentScope.Children, scope)
96 | }
97 |
98 | sa.scopes[node] = scope
99 | return scope
100 | }
101 |
102 | // enterScope 进入新作用域
103 | func (sa *ScopeAnalyzer) enterScope(node ast.Node) *Scope {
104 | scope := sa.newScope(node)
105 | sa.currentScope = scope
106 | return scope
107 | }
108 |
109 | // leaveScope 离开当前作用域
110 | func (sa *ScopeAnalyzer) leaveScope() {
111 | if sa.currentScope.Parent != nil {
112 | sa.currentScope = sa.currentScope.Parent
113 | }
114 | }
115 |
116 | // declareObject 在当前作用域中声明对象
117 | func (sa *ScopeAnalyzer) declareObject(name string, kind ObjectKind, decl ast.Node, pos token.Pos) *Object {
118 | obj := &Object{
119 | Name: name,
120 | Kind: kind,
121 | Decl: decl,
122 | Scope: sa.currentScope,
123 | IsExported: isExported(name),
124 | Pos: pos,
125 | }
126 |
127 | sa.currentScope.Objects[name] = obj
128 | sa.objects[pos] = obj
129 | return obj
130 | }
131 |
132 | // analyzeDecl 分析声明
133 | func (sa *ScopeAnalyzer) analyzeDecl(decl ast.Decl) {
134 | switch d := decl.(type) {
135 | case *ast.FuncDecl:
136 | sa.analyzeFuncDecl(d)
137 | case *ast.GenDecl:
138 | sa.analyzeGenDecl(d)
139 | }
140 | }
141 |
142 | // analyzeFuncDecl 分析函数声明
143 | func (sa *ScopeAnalyzer) analyzeFuncDecl(decl *ast.FuncDecl) {
144 | // 在当前作用域声明函数名(如果不是方法)
145 | if decl.Recv == nil {
146 | sa.declareObject(decl.Name.Name, ObjFunc, decl, decl.Name.Pos())
147 | }
148 |
149 | // 进入函数作用域
150 | funcScope := sa.enterScope(decl)
151 |
152 | // 分析接收者
153 | if decl.Recv != nil {
154 | sa.analyzeFieldList(decl.Recv, ObjVar)
155 | }
156 |
157 | // 分析参数
158 | if decl.Type.Params != nil {
159 | sa.analyzeFieldList(decl.Type.Params, ObjVar)
160 | }
161 |
162 | // 分析返回值
163 | if decl.Type.Results != nil {
164 | sa.analyzeFieldList(decl.Type.Results, ObjVar)
165 | }
166 |
167 | // 分析函数体
168 | if decl.Body != nil {
169 | sa.analyzeBlockStmt(decl.Body)
170 | }
171 |
172 | sa.leaveScope()
173 | _ = funcScope
174 | }
175 |
176 | // analyzeGenDecl 分析通用声明
177 | func (sa *ScopeAnalyzer) analyzeGenDecl(decl *ast.GenDecl) {
178 | for _, spec := range decl.Specs {
179 | switch s := spec.(type) {
180 | case *ast.ValueSpec:
181 | kind := ObjVar
182 | if decl.Tok == token.CONST {
183 | kind = ObjConst
184 | }
185 | for _, name := range s.Names {
186 | sa.declareObject(name.Name, kind, s, name.Pos())
187 | }
188 | // 分析初始化表达式
189 | for _, value := range s.Values {
190 | sa.analyzeExpr(value)
191 | }
192 |
193 | case *ast.TypeSpec:
194 | sa.declareObject(s.Name.Name, ObjType, s, s.Name.Pos())
195 | sa.analyzeExpr(s.Type)
196 | }
197 | }
198 | }
199 |
200 | // analyzeFieldList 分析字段列表(参数、返回值等)
201 | func (sa *ScopeAnalyzer) analyzeFieldList(fields *ast.FieldList, kind ObjectKind) {
202 | if fields == nil {
203 | return
204 | }
205 |
206 | for _, field := range fields.List {
207 | for _, name := range field.Names {
208 | sa.declareObject(name.Name, kind, field, name.Pos())
209 | }
210 | sa.analyzeExpr(field.Type)
211 | }
212 | }
213 |
214 | // analyzeBlockStmt 分析块语句
215 | func (sa *ScopeAnalyzer) analyzeBlockStmt(block *ast.BlockStmt) {
216 | // 块语句创建新作用域
217 | sa.enterScope(block)
218 |
219 | for _, stmt := range block.List {
220 | sa.analyzeStmt(stmt)
221 | }
222 |
223 | sa.leaveScope()
224 | }
225 |
226 | // analyzeStmt 分析语句
227 | func (sa *ScopeAnalyzer) analyzeStmt(stmt ast.Stmt) {
228 | if stmt == nil {
229 | return
230 | }
231 |
232 | switch s := stmt.(type) {
233 | case *ast.DeclStmt:
234 | sa.analyzeDecl(s.Decl)
235 |
236 | case *ast.AssignStmt:
237 | // 分析赋值语句
238 | for i, lhs := range s.Lhs {
239 | if s.Tok == token.DEFINE {
240 | // 短变量声明 :=
241 | if ident, ok := lhs.(*ast.Ident); ok && ident.Name != "_" {
242 | sa.declareObject(ident.Name, ObjVar, s, ident.Pos())
243 | }
244 | }
245 | if i < len(s.Rhs) {
246 | sa.analyzeExpr(s.Rhs[i])
247 | }
248 | }
249 | for _, lhs := range s.Lhs {
250 | sa.analyzeExpr(lhs)
251 | }
252 |
253 | case *ast.IfStmt:
254 | sa.enterScope(s)
255 | if s.Init != nil {
256 | sa.analyzeStmt(s.Init)
257 | }
258 | sa.analyzeExpr(s.Cond)
259 | sa.analyzeBlockStmt(s.Body)
260 | if s.Else != nil {
261 | sa.analyzeStmt(s.Else)
262 | }
263 | sa.leaveScope()
264 |
265 | case *ast.ForStmt:
266 | sa.enterScope(s)
267 | if s.Init != nil {
268 | sa.analyzeStmt(s.Init)
269 | }
270 | if s.Cond != nil {
271 | sa.analyzeExpr(s.Cond)
272 | }
273 | if s.Post != nil {
274 | sa.analyzeStmt(s.Post)
275 | }
276 | sa.analyzeBlockStmt(s.Body)
277 | sa.leaveScope()
278 |
279 | case *ast.RangeStmt:
280 | sa.enterScope(s)
281 | if s.Tok == token.DEFINE {
282 | if key, ok := s.Key.(*ast.Ident); ok && key.Name != "_" {
283 | sa.declareObject(key.Name, ObjVar, s, key.Pos())
284 | }
285 | if value, ok := s.Value.(*ast.Ident); ok && value.Name != "_" {
286 | sa.declareObject(value.Name, ObjVar, s, value.Pos())
287 | }
288 | }
289 | sa.analyzeExpr(s.X)
290 | sa.analyzeBlockStmt(s.Body)
291 | sa.leaveScope()
292 |
293 | case *ast.SwitchStmt:
294 | sa.enterScope(s)
295 | if s.Init != nil {
296 | sa.analyzeStmt(s.Init)
297 | }
298 | if s.Tag != nil {
299 | sa.analyzeExpr(s.Tag)
300 | }
301 | if s.Body != nil {
302 | for _, stmt := range s.Body.List {
303 | if clause, ok := stmt.(*ast.CaseClause); ok {
304 | sa.enterScope(clause)
305 | for _, expr := range clause.List {
306 | sa.analyzeExpr(expr)
307 | }
308 | for _, stmt := range clause.Body {
309 | sa.analyzeStmt(stmt)
310 | }
311 | sa.leaveScope()
312 | }
313 | }
314 | }
315 | sa.leaveScope()
316 |
317 | case *ast.TypeSwitchStmt:
318 | sa.enterScope(s)
319 | if s.Init != nil {
320 | sa.analyzeStmt(s.Init)
321 | }
322 | sa.analyzeStmt(s.Assign)
323 | if s.Body != nil {
324 | for _, stmt := range s.Body.List {
325 | if clause, ok := stmt.(*ast.CaseClause); ok {
326 | sa.enterScope(clause)
327 | for _, expr := range clause.List {
328 | sa.analyzeExpr(expr)
329 | }
330 | for _, stmt := range clause.Body {
331 | sa.analyzeStmt(stmt)
332 | }
333 | sa.leaveScope()
334 | }
335 | }
336 | }
337 | sa.leaveScope()
338 |
339 | case *ast.SelectStmt:
340 | sa.enterScope(s)
341 | if s.Body != nil {
342 | for _, stmt := range s.Body.List {
343 | if clause, ok := stmt.(*ast.CommClause); ok {
344 | sa.enterScope(clause)
345 | if clause.Comm != nil {
346 | sa.analyzeStmt(clause.Comm)
347 | }
348 | for _, stmt := range clause.Body {
349 | sa.analyzeStmt(stmt)
350 | }
351 | sa.leaveScope()
352 | }
353 | }
354 | }
355 | sa.leaveScope()
356 |
357 | case *ast.BlockStmt:
358 | sa.analyzeBlockStmt(s)
359 |
360 | case *ast.ExprStmt:
361 | sa.analyzeExpr(s.X)
362 |
363 | case *ast.SendStmt:
364 | sa.analyzeExpr(s.Chan)
365 | sa.analyzeExpr(s.Value)
366 |
367 | case *ast.IncDecStmt:
368 | sa.analyzeExpr(s.X)
369 |
370 | case *ast.ReturnStmt:
371 | for _, expr := range s.Results {
372 | sa.analyzeExpr(expr)
373 | }
374 |
375 | case *ast.BranchStmt:
376 | // break, continue, goto, fallthrough
377 |
378 | case *ast.GoStmt:
379 | sa.analyzeExpr(s.Call)
380 |
381 | case *ast.DeferStmt:
382 | sa.analyzeExpr(s.Call)
383 |
384 | case *ast.LabeledStmt:
385 | sa.analyzeStmt(s.Stmt)
386 | }
387 | }
388 |
389 | // analyzeExpr 分析表达式
390 | func (sa *ScopeAnalyzer) analyzeExpr(expr ast.Expr) {
391 | if expr == nil {
392 | return
393 | }
394 |
395 | switch e := expr.(type) {
396 | case *ast.FuncLit:
397 | // 函数字面量创建新作用域
398 | funcScope := sa.enterScope(e)
399 | if e.Type.Params != nil {
400 | sa.analyzeFieldList(e.Type.Params, ObjVar)
401 | }
402 | if e.Type.Results != nil {
403 | sa.analyzeFieldList(e.Type.Results, ObjVar)
404 | }
405 | sa.analyzeBlockStmt(e.Body)
406 | sa.leaveScope()
407 | _ = funcScope
408 |
409 | case *ast.CompositeLit:
410 | sa.analyzeExpr(e.Type)
411 | for _, elt := range e.Elts {
412 | if kv, ok := elt.(*ast.KeyValueExpr); ok {
413 | sa.analyzeExpr(kv.Key)
414 | sa.analyzeExpr(kv.Value)
415 | } else {
416 | sa.analyzeExpr(elt)
417 | }
418 | }
419 |
420 | case *ast.BinaryExpr:
421 | sa.analyzeExpr(e.X)
422 | sa.analyzeExpr(e.Y)
423 |
424 | case *ast.UnaryExpr:
425 | sa.analyzeExpr(e.X)
426 |
427 | case *ast.CallExpr:
428 | sa.analyzeExpr(e.Fun)
429 | for _, arg := range e.Args {
430 | sa.analyzeExpr(arg)
431 | }
432 |
433 | case *ast.IndexExpr:
434 | sa.analyzeExpr(e.X)
435 | sa.analyzeExpr(e.Index)
436 |
437 | case *ast.SliceExpr:
438 | sa.analyzeExpr(e.X)
439 | if e.Low != nil {
440 | sa.analyzeExpr(e.Low)
441 | }
442 | if e.High != nil {
443 | sa.analyzeExpr(e.High)
444 | }
445 | if e.Max != nil {
446 | sa.analyzeExpr(e.Max)
447 | }
448 |
449 | case *ast.SelectorExpr:
450 | sa.analyzeExpr(e.X)
451 |
452 | case *ast.StarExpr:
453 | sa.analyzeExpr(e.X)
454 |
455 | case *ast.TypeAssertExpr:
456 | sa.analyzeExpr(e.X)
457 | if e.Type != nil {
458 | sa.analyzeExpr(e.Type)
459 | }
460 |
461 | case *ast.ParenExpr:
462 | sa.analyzeExpr(e.X)
463 |
464 | case *ast.ArrayType:
465 | if e.Len != nil {
466 | sa.analyzeExpr(e.Len)
467 | }
468 | sa.analyzeExpr(e.Elt)
469 |
470 | case *ast.StructType:
471 | if e.Fields != nil {
472 | sa.analyzeFieldList(e.Fields, ObjField)
473 | }
474 |
475 | case *ast.FuncType:
476 | if e.Params != nil {
477 | sa.analyzeFieldList(e.Params, ObjVar)
478 | }
479 | if e.Results != nil {
480 | sa.analyzeFieldList(e.Results, ObjVar)
481 | }
482 |
483 | case *ast.InterfaceType:
484 | if e.Methods != nil {
485 | sa.analyzeFieldList(e.Methods, ObjMethod)
486 | }
487 |
488 | case *ast.MapType:
489 | sa.analyzeExpr(e.Key)
490 | sa.analyzeExpr(e.Value)
491 |
492 | case *ast.ChanType:
493 | sa.analyzeExpr(e.Value)
494 | }
495 | }
496 |
497 | // LookupObject 在作用域链中查找对象
498 | func (s *Scope) LookupObject(name string) *Object {
499 | for scope := s; scope != nil; scope = scope.Parent {
500 | if obj, ok := scope.Objects[name]; ok {
501 | return obj
502 | }
503 | }
504 | return nil
505 | }
506 |
507 | // GetScopeAt 获取指定位置的作用域
508 | func (sa *ScopeAnalyzer) GetScopeAt(pos token.Pos) *Scope {
509 | // 从当前作用域开始向上查找
510 | for scope := sa.currentScope; scope != nil; scope = scope.Parent {
511 | if pos >= scope.Start && pos <= scope.End {
512 | // 检查子作用域
513 | for _, child := range scope.Children {
514 | if pos >= child.Start && pos <= child.End {
515 | return sa.getScopeAtInTree(child, pos)
516 | }
517 | }
518 | return scope
519 | }
520 | }
521 | return sa.fileScope
522 | }
523 |
524 | // getScopeAtInTree 在作用域树中递归查找
525 | func (sa *ScopeAnalyzer) getScopeAtInTree(scope *Scope, pos token.Pos) *Scope {
526 | if pos < scope.Start || pos > scope.End {
527 | return nil
528 | }
529 |
530 | // 检查子作用域
531 | for _, child := range scope.Children {
532 | if pos >= child.Start && pos <= child.End {
533 | if result := sa.getScopeAtInTree(child, pos); result != nil {
534 | return result
535 | }
536 | }
537 | }
538 |
539 | return scope
540 | }
541 |
542 | // GetFileScope 获取文件作用域
543 | func (sa *ScopeAnalyzer) GetFileScope() *Scope {
544 | return sa.fileScope
545 | }
546 |
547 | // GetPackageScope 获取包作用域
548 | func (sa *ScopeAnalyzer) GetPackageScope() *Scope {
549 | return sa.packageScope
550 | }
551 |
552 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime"
9 | "strings"
10 |
11 | "cross-file-obfuscator/obfuscator"
12 | )
13 |
14 | func printLogo() {
15 | fmt.Println()
16 | fmt.Println("\033[1;35m ██████╗██████╗ ██████╗ ███████╗███████╗\033[0m")
17 | fmt.Println("\033[1;35m██╔════╝██╔══██╗██╔═══██╗██╔════╝██╔════╝\033[0m")
18 | fmt.Println("\033[1;35m██║ ██████╔╝██║ ██║███████╗███████╗\033[0m")
19 | fmt.Println("\033[1;35m██║ ██╔══██╗██║ ██║╚════██║╚════██║\033[0m")
20 | fmt.Println("\033[1;35m╚██████╗██║ ██║╚██████╔╝███████║███████║\033[0m")
21 | fmt.Println("\033[1;35m ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝\033[0m")
22 | fmt.Println()
23 | fmt.Println(" \033[1;33m━━━ File Obfuscator ━━━\033[0m")
24 | fmt.Println()
25 | fmt.Println(" \033[90mGo 代码混淆与保护工具\033[0m")
26 | fmt.Println(" \033[90mVersion 1.0.0 | By masterqiu01\033[0m")
27 | fmt.Println()
28 | }
29 |
30 | // checkAndHandleExistingDir 检查输出目录是否存在,如果存在则询问用户是否覆盖
31 | func checkAndHandleExistingDir(outDir string) error {
32 | if _, err := os.Stat(outDir); err == nil {
33 | // 目录存在
34 | fmt.Printf("\n\033[33m⚠️ 警告: 输出目录已存在: %s\033[0m\n", outDir)
35 | fmt.Print("是否删除现有目录并继续? [y/N]: ")
36 |
37 | var response string
38 | fmt.Scanln(&response)
39 | response = strings.ToLower(strings.TrimSpace(response))
40 |
41 | if response == "y" || response == "yes" {
42 | fmt.Printf("正在删除目录: %s\n", outDir)
43 | if err := os.RemoveAll(outDir); err != nil {
44 | return fmt.Errorf("删除目录失败: %v", err)
45 | }
46 | fmt.Println("\033[32m✓ 目录已删除\033[0m")
47 | } else {
48 | return fmt.Errorf("用户取消操作")
49 | }
50 | }
51 | return nil
52 | }
53 |
54 | func printUsage() {
55 | fmt.Println("用法: cross-file-obfuscator [选项] <项目目录>")
56 | fmt.Println()
57 | fmt.Println("基础选项:")
58 | fmt.Println(" -h 显示帮助信息")
59 | fmt.Println(" -o string 输出目录")
60 | fmt.Println(" -encrypt-strings 加密字符串字面量")
61 | fmt.Println(" -inject-junk 注入垃圾代码")
62 | fmt.Println(" -obfuscate-filenames 混淆文件名")
63 | fmt.Println(" -obfuscate-exported 混淆导出函数 (危险!)")
64 | fmt.Println(" -remove-comments 移除注释 (默认: true)")
65 | fmt.Println(" -preserve-reflection 保留反射类型 (默认: true)")
66 | fmt.Println(" -skip-generated 跳过生成的代码 (默认: true)")
67 | fmt.Println(" -exclude string 排除文件模式")
68 | fmt.Println()
69 | fmt.Println("高级选项:")
70 | fmt.Println(" -build-with-linker 直接编译并应用链接器混淆 ")
71 | fmt.Println(" -output-bin string 输出二进制文件名 (配合 -build-with-linker)")
72 | fmt.Println(" -entry string 入口包路径 (默认: '.')")
73 | fmt.Println(" -pkg-replace string 包名替换映射 (格式: 'pkg1=a,pkg2=b')")
74 | fmt.Println(" -auto-discover-pkgs 自动发现并替换项目中的所有包名")
75 | fmt.Println(" -obfuscate-third-party 混淆第三方依赖包 (谨慎使用)")
76 | fmt.Println(" -only-project 只混淆项目包,保留标准库 (最小化 pclntab)")
77 | fmt.Println(" -disable-pclntab 完全禁用 pclntab 修改 (最安全)")
78 | fmt.Println(" -auto 自动模式:全功能混淆 + 自动编译 (推荐!)")
79 | fmt.Println()
80 | fmt.Println("示例:")
81 | fmt.Println(" # 🚀 自动模式 - 一键全功能混淆 (推荐!)")
82 | fmt.Println(" ./cross-file-obfuscator -auto -output-bin myapp ./my-project")
83 | fmt.Println()
84 | fmt.Println(" # 基础混淆")
85 | fmt.Println(" ./cross-file-obfuscator ./my-project")
86 | fmt.Println()
87 | fmt.Println(" # 字符串加密 + 垃圾代码")
88 | fmt.Println(" ./cross-file-obfuscator -encrypt-strings -inject-junk ./my-project")
89 | fmt.Println()
90 | fmt.Println(" # 自动发现并替换所有项目包名")
91 | fmt.Println(" ./cross-file-obfuscator -build-with-linker -auto-discover-pkgs -output-bin myapp ./my-project")
92 | fmt.Println()
93 | fmt.Println(" # 编译为 Windows 64位程序")
94 | fmt.Println(" GOOS=windows GOARCH=amd64 ./cross-file-obfuscator -auto -output-bin app.exe ./my-project")
95 | fmt.Println()
96 | fmt.Println("详细文档: README.md")
97 | }
98 |
99 | func main() {
100 | // 显示 Logo
101 | printLogo()
102 |
103 | // 基础选项
104 | var (
105 | outputDir = flag.String("o", "", "输出目录 (默认: project_directory_obfuscated)")
106 | obfuscateExported = flag.Bool("obfuscate-exported", false, "混淆导出的函数和变量 (可能破坏外部引用)")
107 | obfuscateFileNames = flag.Bool("obfuscate-filenames", false, "混淆 Go 文件名")
108 | encryptStrings = flag.Bool("encrypt-strings", false, "加密字符串字面量并运行时解密")
109 | injectJunkCode = flag.Bool("inject-junk", false, "注入垃圾代码以混淆分析")
110 | removeComments = flag.Bool("remove-comments", true, "移除所有注释")
111 | preserveReflection = flag.Bool("preserve-reflection", true, "保留反射中使用的类型/方法")
112 | skipGeneratedCode = flag.Bool("skip-generated", true, "跳过自动生成的代码文件")
113 | excludePatterns = flag.String("exclude", "", "要排除的文件模式 (逗号分隔, 例如: -exclude '*_test.go,*.pb.go')")
114 | showHelp = flag.Bool("h", false, "显示帮助信息")
115 | )
116 |
117 | // 高级选项
118 | var (
119 | buildWithLinker = flag.Bool("build-with-linker", false, "直接编译并应用链接器混淆")
120 | outputBinary = flag.String("output-bin", "", "输出二进制文件名 (配合 -build-with-linker 使用)")
121 | entryPackage = flag.String("entry", ".", "入口包路径,例如: './cmd/server' ")
122 | packageReplacements = flag.String("pkg-replace", "", "包名替换映射 (格式: 'original1=new1,original2=new2')")
123 | autoDiscoverPkgs = flag.Bool("auto-discover-pkgs", false, "自动发现并替换项目中的所有包名")
124 | obfuscateThirdParty = flag.Bool("obfuscate-third-party", false, "混淆第三方依赖包(谨慎使用)")
125 | onlyObfuscateProject = flag.Bool("only-project", false, "只混淆项目包,保留标准库(最小化 pclntab,推荐 Windows)")
126 | disablePclntab = flag.Bool("disable-pclntab", false, "完全禁用 pclntab 修改(最安全但保护较弱)")
127 | autoMode = flag.Bool("auto", false, "自动模式:全功能混淆 + 自动编译(Windows 下自动使用最小化 pclntab)")
128 | )
129 |
130 | // 自定义 Usage 函数
131 | flag.Usage = printUsage
132 |
133 | flag.Parse()
134 |
135 | // 如果用户使用 -h 参数,显示帮助并退出
136 | if *showHelp || flag.NArg() < 1 {
137 | printUsage()
138 | os.Exit(0)
139 | }
140 |
141 | // 如果使用 auto 模式,执行全功能混淆
142 | if *autoMode {
143 | if flag.NArg() < 1 {
144 | log.Fatal("错误: 请指定项目目录")
145 | }
146 | projectRoot := flag.Arg(0)
147 |
148 | fmt.Println("╔══════════════════════════════════════════════════════════════╗")
149 | fmt.Println(" 🚀 自动模式:全功能混淆 + 自动编译 ")
150 | fmt.Println("╚══════════════════════════════════════════════════════════════╝")
151 | fmt.Println()
152 | fmt.Println("混淆配置(已启用所有功能):")
153 | fmt.Println(" ✅ 字符串加密")
154 | fmt.Println(" ✅ 垃圾代码注入")
155 | fmt.Println(" ✅ 导出符号混淆")
156 | fmt.Println(" ✅ 文件名混淆")
157 | fmt.Println(" ✅ 注释移除")
158 | fmt.Println(" ✅ 自动发现包名")
159 | fmt.Println(" ✅ 链接器混淆")
160 | fmt.Println(" ✅ 第三方包混淆")
161 | fmt.Println()
162 |
163 | // 第一步:源码混淆
164 | fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
165 | fmt.Println("第 1/2 步:源码混淆")
166 | fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
167 | fmt.Println()
168 |
169 | outDir := projectRoot + "_obfuscated"
170 | if *outputDir != "" {
171 | outDir = *outputDir
172 | }
173 |
174 | // 检查输出目录是否已存在
175 | if err := checkAndHandleExistingDir(outDir); err != nil {
176 | log.Fatalf("错误: %v", err)
177 | }
178 |
179 | // 解析排除模式
180 | var excludeList []string
181 | if *excludePatterns != "" {
182 | excludeList = strings.Split(*excludePatterns, ",")
183 | for i := range excludeList {
184 | excludeList[i] = strings.TrimSpace(excludeList[i])
185 | }
186 | }
187 |
188 | // 创建源码混淆器
189 | sourceObf := obfuscator.New(projectRoot, outDir, &obfuscator.Config{
190 | ObfuscateExported: true,
191 | ObfuscateFileNames: true,
192 | EncryptStrings: true,
193 | InjectJunkCode: true,
194 | RemoveComments: *removeComments,
195 | PreserveReflection: *preserveReflection,
196 | SkipGeneratedCode: *skipGeneratedCode,
197 | ExcludePatterns: excludeList,
198 | })
199 |
200 | // 执行源码混淆
201 | if err := sourceObf.Run(); err != nil {
202 | log.Fatalf("源码混淆失败: %v", err)
203 | }
204 |
205 | fmt.Println()
206 | fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
207 | fmt.Println("第 2/2 步:链接器混淆 + 编译")
208 | fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
209 | fmt.Println()
210 |
211 | // 第二步:链接器混淆
212 | binName := *outputBinary
213 | if binName == "" {
214 | binName = "output_obfuscated"
215 | }
216 |
217 | // 检测目标平台,智能选择 pclntab 混淆策略
218 | targetOS := os.Getenv("GOOS")
219 | if targetOS == "" {
220 | targetOS = runtime.GOOS
221 | }
222 |
223 | isWindows := (targetOS == "windows")
224 |
225 | fmt.Println()
226 | if isWindows {
227 | fmt.Println("⚠️ 检测到目标平台为 Windows")
228 | fmt.Println(" 为避免杀软误报,已自动启用最小化 pclntab 混淆")
229 | fmt.Println(" - 只混淆项目包(~772 个函数,-86%)")
230 | fmt.Println(" - 保留所有标准库(runtime.*, sync.*, fmt.* 等)")
231 | fmt.Println(" - 仍保留:字符串加密、垃圾代码、符号表移除")
232 | fmt.Println()
233 | fmt.Println(" 💡 如需更安全的方案,可使用:")
234 | fmt.Println(" ./main -build-with-linker -auto-discover-pkgs -disable-pclntab \\")
235 | fmt.Println(" --output-bin app.exe -entry <入口> <项目>")
236 | } else {
237 | fmt.Println("⚠️ 检测到目标平台为", targetOS)
238 | fmt.Println(" 已启用完整 pclntab 混淆(最强保护)")
239 | fmt.Println(" - 混淆所有包(标准库 + 项目包)")
240 | fmt.Println(" - 修改 ~5000+ 个函数名")
241 | fmt.Println()
242 | fmt.Println(" 💡 如被杀软识别,可切换为:")
243 | fmt.Println(" 方案1 (最小化): ./main -build-with-linker -auto-discover-pkgs \\")
244 | fmt.Println(" -only-project --output-bin app -entry <入口> <项目>")
245 | fmt.Println(" 方案2 (完全禁用): ./main -build-with-linker -auto-discover-pkgs \\")
246 | fmt.Println(" -disable-pclntab --output-bin app -entry <入口> <项目>")
247 | }
248 | fmt.Println()
249 |
250 | linkConfig := &obfuscator.LinkConfig{
251 | RemoveFuncNames: true,
252 | EntryPackage: *entryPackage,
253 | AutoDiscoverPackages: true,
254 | ObfuscateThirdParty: false, // AUTO 模式不混淆第三方包
255 | OnlyObfuscateProject: isWindows, // ⭐ Windows: 最小化,其他: 完整
256 | DisablePclntab: false, // 不完全禁用
257 | }
258 |
259 | linkerObf := obfuscator.NewLinkerObfuscator(outDir, binName, linkConfig)
260 |
261 | if err := linkerObf.BuildWithLinkerObfuscation(); err != nil {
262 | log.Fatalf("链接器混淆失败: %v", err)
263 | }
264 |
265 | fmt.Println()
266 | fmt.Println("╔══════════════════════════════════════════════════════════════╗")
267 | fmt.Println("║ ✅ 全功能混淆完成! ║")
268 | fmt.Println("╚══════════════════════════════════════════════════════════════╝")
269 | fmt.Printf("\n📦 混淆后的二进制文件: %s\n", binName)
270 | fmt.Printf("📁 混淆后的源码目录: %s\n", outDir)
271 | fmt.Println("\n验证混淆效果:")
272 | fmt.Printf(" strings %s | grep -i 'main\\.' | wc -l\n", binName)
273 | fmt.Printf(" strings %s | grep -i 'runtime\\.' | wc -l\n", binName)
274 | return
275 | }
276 |
277 | // 如果使用链接器构建模式
278 | if *buildWithLinker {
279 | if flag.NArg() < 1 {
280 | log.Fatal("错误: 请指定项目目录")
281 | }
282 | projectRoot := flag.Arg(0)
283 |
284 | // 设置输出二进制文件名
285 | binName := *outputBinary
286 | if binName == "" {
287 | binName = "output_obfuscated"
288 | }
289 |
290 | // 解析包名替换映射
291 | pkgReplaceMap := make(map[string]string)
292 | if *packageReplacements != "" {
293 | pairs := strings.Split(*packageReplacements, ",")
294 | for _, pair := range pairs {
295 | parts := strings.SplitN(strings.TrimSpace(pair), "=", 2)
296 | if len(parts) == 2 {
297 | original := strings.TrimSpace(parts[0])
298 | replacement := strings.TrimSpace(parts[1])
299 | if original != "" && replacement != "" {
300 | pkgReplaceMap[original] = replacement
301 | }
302 | }
303 | }
304 | }
305 |
306 | // 创建链接器混淆器
307 | linkConfig := &obfuscator.LinkConfig{
308 | RemoveFuncNames: true, // 混淆函数名
309 | EntryPackage: *entryPackage, // 入口包路径
310 | PackageReplacements: pkgReplaceMap, // 包名替换映射
311 | AutoDiscoverPackages: *autoDiscoverPkgs, // 自动发现包名
312 | ObfuscateThirdParty: *obfuscateThirdParty, // 混淆第三方包
313 | OnlyObfuscateProject: *onlyObfuscateProject, // 只混淆项目包
314 | DisablePclntab: *disablePclntab, // 完全禁用 pclntab
315 | }
316 |
317 | linkerObf := obfuscator.NewLinkerObfuscator(projectRoot, binName, linkConfig)
318 |
319 | // 执行构建和混淆
320 | if err := linkerObf.BuildWithLinkerObfuscation(); err != nil {
321 | log.Fatalf("链接器混淆失败: %v", err)
322 | }
323 |
324 | fmt.Printf("\n✅ 成功! 混淆后的二进制文件: %s\n", binName)
325 | fmt.Println("\n验证混淆效果:")
326 | fmt.Printf(" strings %s | grep -i 'main\\.' | head -20\n", binName)
327 | fmt.Printf(" strings %s | grep -i 'runtime\\.' | head -20\n", binName)
328 | return
329 | }
330 |
331 | projectRoot := flag.Arg(0)
332 |
333 | // 验证项目目录
334 | info, err := os.Stat(projectRoot)
335 | if err != nil {
336 | log.Fatalf("错误: 无法访问项目根目录 %s: %v", projectRoot, err)
337 | }
338 | if !info.IsDir() {
339 | log.Fatalf("错误: 项目根路径必须是一个目录: %s", projectRoot)
340 | }
341 |
342 | // 设置输出目录
343 | if *outputDir == "" {
344 | *outputDir = projectRoot + "_obfuscated"
345 | }
346 |
347 | // 检查输出目录是否已存在
348 | if err := checkAndHandleExistingDir(*outputDir); err != nil {
349 | log.Fatalf("错误: %v", err)
350 | }
351 |
352 | if err := os.MkdirAll(*outputDir, 0755); err != nil {
353 | log.Fatalf("错误: 无法创建输出目录 %s: %v", *outputDir, err)
354 | }
355 |
356 | // 解析排除模式
357 | var excludePatternsList []string
358 | if *excludePatterns != "" {
359 | excludePatternsList = strings.Split(*excludePatterns, ",")
360 | for i := range excludePatternsList {
361 | excludePatternsList[i] = strings.TrimSpace(excludePatternsList[i])
362 | }
363 | }
364 |
365 | // 创建配置
366 | config := &obfuscator.Config{
367 | ObfuscateExported: *obfuscateExported,
368 | ObfuscateFileNames: *obfuscateFileNames,
369 | EncryptStrings: *encryptStrings,
370 | InjectJunkCode: *injectJunkCode,
371 | RemoveComments: *removeComments,
372 | PreserveReflection: *preserveReflection,
373 | SkipGeneratedCode: *skipGeneratedCode,
374 | ExcludePatterns: excludePatternsList,
375 | }
376 |
377 | // 创建混淆器
378 | obf := obfuscator.New(projectRoot, *outputDir, config)
379 |
380 | // 打印配置
381 | printConfiguration(projectRoot, *outputDir, config, excludePatternsList)
382 |
383 | // 执行混淆
384 | fmt.Println("开始混淆...")
385 | if err := runObfuscation(obf, config, projectRoot, *outputDir); err != nil {
386 | log.Fatalf("错误: %v", err)
387 | }
388 |
389 | // 打印统计信息
390 | stats := obf.GetStatistics()
391 | printStatistics(stats)
392 |
393 | fmt.Println("\n✅ 混淆完成!")
394 | fmt.Println("请在输出目录中运行 'go build' 以验证编译。")
395 | fmt.Println("\n提示: 使用 -build-with-linker 可以直接编译并应用链接器级别混淆")
396 | }
397 |
398 | func printConfiguration(projectRoot, outputDir string, config *obfuscator.Config, excludePatterns []string) {
399 | fmt.Println("========================================")
400 | fmt.Println(" Go 代码混淆器")
401 | fmt.Println("========================================")
402 | fmt.Printf("输入: %s\n", projectRoot)
403 | fmt.Printf("输出: %s\n", outputDir)
404 | fmt.Println()
405 | fmt.Println("配置选项:")
406 | fmt.Printf(" 混淆导出函数: %v", config.ObfuscateExported)
407 | if config.ObfuscateExported {
408 | fmt.Printf(" ⚠️ 警告: 可能破坏外部引用!\n")
409 | } else {
410 | fmt.Println()
411 | }
412 | fmt.Printf(" 混淆文件名: %v\n", config.ObfuscateFileNames)
413 | fmt.Printf(" 加密字符串: %v\n", config.EncryptStrings)
414 | fmt.Printf(" 注入垃圾代码: %v\n", config.InjectJunkCode)
415 | fmt.Printf(" 移除注释: %v\n", config.RemoveComments)
416 | fmt.Printf(" 保留反射: %v\n", config.PreserveReflection)
417 | fmt.Printf(" 跳过生成代码: %v\n", config.SkipGeneratedCode)
418 | if len(excludePatterns) > 0 {
419 | fmt.Printf(" 排除模式: %v\n", excludePatterns)
420 | }
421 | fmt.Println()
422 | }
423 |
424 | func runObfuscation(obf *obfuscator.Obfuscator, config *obfuscator.Config, projectRoot, outputDir string) error {
425 | return obf.Run()
426 | }
427 |
428 | func printStatistics(stats *obfuscator.Statistics) {
429 | fmt.Println()
430 | fmt.Println("========================================")
431 | fmt.Println(" 混淆统计")
432 | fmt.Println("========================================")
433 | fmt.Printf("受保护名称: %d\n", stats.ProtectedNames)
434 | fmt.Printf("混淆函数: %d\n", stats.FunctionsObf)
435 | fmt.Printf("混淆变量: %d\n", stats.VariablesObf)
436 | if stats.SkippedFiles > 0 {
437 | fmt.Printf("跳过文件: %d\n", stats.SkippedFiles)
438 | }
439 | }
440 |
--------------------------------------------------------------------------------
/obfuscator/linker.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "bytes"
5 | "debug/elf"
6 | "debug/macho"
7 | "debug/pe"
8 | "encoding/binary"
9 | "fmt"
10 | "io/ioutil"
11 | "os"
12 | "os/exec"
13 | "path/filepath"
14 | "regexp"
15 | "sort"
16 | "strings"
17 | )
18 |
19 | // Go pclntab magic values
20 | const (
21 | go12magic = 0xfffffffb
22 | go116magic = 0xfffffffa
23 | go118magic = 0xfffffff0
24 | go120magic = 0xfffffff1
25 | )
26 |
27 | // LinkerObfuscator 处理链接器级别的混淆
28 | type LinkerObfuscator struct {
29 | config *LinkConfig
30 | projectDir string
31 | outputBin string
32 | }
33 |
34 | // NewLinkerObfuscator 创建新的链接器混淆器
35 | func NewLinkerObfuscator(projectDir, outputBin string, config *LinkConfig) *LinkerObfuscator {
36 | if config == nil {
37 | config = &LinkConfig{
38 | RemoveFuncNames: true, // 默认混淆函数名
39 | EntryPackage: ".", // 默认当前目录
40 | }
41 | }
42 | // 如果没有指定入口包,默认使用当前目录
43 | if config.EntryPackage == "" {
44 | config.EntryPackage = "."
45 | }
46 | return &LinkerObfuscator{
47 | config: config,
48 | projectDir: projectDir,
49 | outputBin: outputBin,
50 | }
51 | }
52 |
53 | // BuildWithLinkerObfuscation 使用链接器混淆构建项目
54 | func (lo *LinkerObfuscator) BuildWithLinkerObfuscation() error {
55 | fmt.Println("=== 链接器级别混淆 ===")
56 | fmt.Printf("项目目录: %s\n", lo.projectDir)
57 | fmt.Printf("入口包: %s\n", lo.config.EntryPackage)
58 |
59 | // 如果启用了自动包名发现且没有手动指定包名替换
60 | if lo.config.AutoDiscoverPackages && len(lo.config.PackageReplacements) == 0 {
61 | fmt.Println("第 0 步: 自动发现项目包名...")
62 | if err := lo.discoverAndGeneratePackageReplacements(); err != nil {
63 | fmt.Printf("⚠️ 警告: 自动发现包名失败: %v\n", err)
64 | fmt.Println(" 将继续使用默认包名替换模式")
65 | }
66 | }
67 |
68 | fmt.Println("第 1 步: 标准编译...")
69 |
70 | // 确保输出路径是绝对路径
71 | outputPath := lo.outputBin
72 | if !filepath.IsAbs(outputPath) {
73 | cwd, err := os.Getwd()
74 | if err != nil {
75 | return fmt.Errorf("获取当前目录失败: %v", err)
76 | }
77 | outputPath = filepath.Join(cwd, outputPath)
78 | }
79 |
80 | // 构建 go build 命令
81 | // 使用 -ldflags="-s -w" 移除符号表和调试信息
82 | // -s: 禁用符号表
83 | // -w: 禁用 DWARF 调试信息
84 | buildArgs := []string{"build", "-ldflags=-s -w", "-trimpath", "-o", outputPath}
85 |
86 | // 添加入口包路径
87 | buildArgs = append(buildArgs, lo.config.EntryPackage)
88 |
89 | buildCmd := exec.Command("go", buildArgs...)
90 | buildCmd.Dir = lo.projectDir
91 | buildCmd.Stdout = os.Stdout
92 | buildCmd.Stderr = os.Stderr
93 | buildCmd.Env = os.Environ() // 继承当前环境变量(包括 CGO_ENABLED 等)
94 |
95 | // 打印实际执行的命令(调试用)
96 | fmt.Printf(" 执行命令: cd %s && go %s\n", lo.projectDir, strings.Join(buildArgs, " "))
97 |
98 | if err := buildCmd.Run(); err != nil {
99 | return fmt.Errorf("编译失败: %v\n请尝试在项目目录手动执行: cd %s && go %s", err, lo.projectDir, strings.Join(buildArgs, " "))
100 | }
101 |
102 | fmt.Println("✅ 标准编译完成")
103 |
104 | // 更新 outputBin 为绝对路径
105 | lo.outputBin = outputPath
106 |
107 | // 第二步:后处理二进制文件
108 | fmt.Println("第 2 步: 后处理二进制文件...")
109 | if err := lo.postProcessBinary(); err != nil {
110 | return fmt.Errorf("后处理失败: %v", err)
111 | }
112 | fmt.Println("✅ 后处理完成")
113 |
114 | // 注意:由于编译时已使用 -ldflags="-s -w",无需再执行 strip
115 | fmt.Println("✅ 符号表已在编译时移除(-ldflags=\"-s -w\")")
116 |
117 | return nil
118 | }
119 |
120 | // postProcessBinary 后处理二进制文件
121 | func (lo *LinkerObfuscator) postProcessBinary() error {
122 | data, err := os.ReadFile(lo.outputBin)
123 | if err != nil {
124 | return err
125 | }
126 |
127 | // 检测二进制格式
128 | format := detectBinaryFormat(data)
129 | fmt.Printf(" 检测到二进制格式: %s\n", format)
130 |
131 | var modified bool
132 | var newData []byte
133 |
134 | switch format {
135 | case "ELF":
136 | newData, modified, err = lo.processELF(data)
137 | case "PE":
138 | newData, modified, err = lo.processPE(data)
139 | case "Mach-O":
140 | newData, modified, err = lo.processMachO(data)
141 | default:
142 | return fmt.Errorf("不支持的二进制格式: %s", format)
143 | }
144 |
145 | if err != nil {
146 | return err
147 | }
148 |
149 | if modified {
150 | // 备份原文件
151 | backupPath := lo.outputBin + ".backup"
152 | if err := os.WriteFile(backupPath, data, 0755); err != nil {
153 | return fmt.Errorf("备份失败: %v", err)
154 | }
155 |
156 | // 写入修改后的文件
157 | if err := os.WriteFile(lo.outputBin, newData, 0755); err != nil {
158 | return fmt.Errorf("写入失败: %v", err)
159 | }
160 |
161 | fmt.Printf(" ✅ 已修改 pclntab\n")
162 | fmt.Printf(" ✅ 原文件已备份到: %s\n", backupPath)
163 | } else {
164 | fmt.Println(" ⚠️ 未找到 pclntab 或无需修改")
165 | }
166 |
167 | return nil
168 | }
169 |
170 |
171 | // detectBinaryFormat 检测二进制文件格式
172 | func detectBinaryFormat(data []byte) string {
173 | if len(data) < 4 {
174 | return "Unknown"
175 | }
176 |
177 | // ELF magic: 0x7F 'E' 'L' 'F'
178 | if data[0] == 0x7F && data[1] == 'E' && data[2] == 'L' && data[3] == 'F' {
179 | return "ELF"
180 | }
181 |
182 | // PE magic: 'M' 'Z'
183 | if data[0] == 'M' && data[1] == 'Z' {
184 | return "PE"
185 | }
186 |
187 | // Mach-O magic (multiple variants)
188 | if len(data) >= 4 {
189 | magic := binary.LittleEndian.Uint32(data[0:4])
190 | switch magic {
191 | case 0xfeedface, 0xcefaedfe, 0xfeedfacf, 0xcffaedfe:
192 | return "Mach-O"
193 | }
194 | }
195 |
196 | return "Unknown"
197 | }
198 |
199 | // processELF 处理 ELF 格式的二进制文件
200 | func (lo *LinkerObfuscator) processELF(data []byte) ([]byte, bool, error) {
201 | // 打开 ELF 文件
202 | elfFile, err := elf.NewFile(bytes.NewReader(data))
203 | if err != nil {
204 | return data, false, fmt.Errorf("解析 ELF 失败: %v", err)
205 | }
206 | defer elfFile.Close()
207 |
208 | // 查找 .gopclntab 或 .data.rel.ro 段
209 | var pclntabData []byte
210 | var pclntabOffset int64
211 |
212 | for _, section := range elfFile.Sections {
213 | if section.Name == ".gopclntab" || section.Name == ".data.rel.ro" {
214 | sectionData, err := section.Data()
215 | if err != nil {
216 | continue
217 | }
218 |
219 | // 在段中搜索 pclntab magic
220 | offset := findPclntabMagic(sectionData)
221 | if offset >= 0 {
222 | pclntabData = sectionData
223 | pclntabOffset = int64(section.Offset) + int64(offset)
224 | fmt.Printf(" 找到 pclntab 在段 %s,偏移: 0x%x\n", section.Name, pclntabOffset)
225 | break
226 | }
227 | }
228 | }
229 |
230 | if pclntabData == nil {
231 | // 在整个文件中搜索
232 | offset := findPclntabMagic(data)
233 | if offset < 0 {
234 | return data, false, nil
235 | }
236 | pclntabOffset = int64(offset)
237 | fmt.Printf(" 找到 pclntab 在文件偏移: 0x%x\n", pclntabOffset)
238 | }
239 |
240 | // 修改二进制数据
241 | return lo.modifyPclntab(data, pclntabOffset)
242 | }
243 |
244 | // processPE 处理 PE 格式的二进制文件
245 | func (lo *LinkerObfuscator) processPE(data []byte) ([]byte, bool, error) {
246 | peFile, err := pe.NewFile(bytes.NewReader(data))
247 | if err != nil {
248 | return data, false, fmt.Errorf("解析 PE 失败: %v", err)
249 | }
250 | defer peFile.Close()
251 |
252 | // 在 .rdata 或 .data 段中查找 pclntab
253 | var pclntabOffset int64 = -1
254 |
255 | for _, section := range peFile.Sections {
256 | if section.Name == ".rdata" || section.Name == ".data" {
257 | sectionData, err := section.Data()
258 | if err != nil {
259 | continue
260 | }
261 |
262 | offset := findPclntabMagic(sectionData)
263 | if offset >= 0 {
264 | pclntabOffset = int64(section.Offset) + int64(offset)
265 | fmt.Printf(" 找到 pclntab 在段 %s,偏移: 0x%x\n", section.Name, pclntabOffset)
266 | break
267 | }
268 | }
269 | }
270 |
271 | if pclntabOffset < 0 {
272 | // 在整个文件中搜索
273 | offset := findPclntabMagic(data)
274 | if offset < 0 {
275 | return data, false, nil
276 | }
277 | pclntabOffset = int64(offset)
278 | fmt.Printf(" 找到 pclntab 在文件偏移: 0x%x\n", pclntabOffset)
279 | }
280 |
281 | return lo.modifyPclntab(data, pclntabOffset)
282 | }
283 |
284 | // processMachO 处理 Mach-O 格式的二进制文件
285 | func (lo *LinkerObfuscator) processMachO(data []byte) ([]byte, bool, error) {
286 | machoFile, err := macho.NewFile(bytes.NewReader(data))
287 | if err != nil {
288 | return data, false, fmt.Errorf("解析 Mach-O 失败: %v", err)
289 | }
290 | defer machoFile.Close()
291 |
292 | // 在 __gopclntab 或 __data 段中查找
293 | var pclntabOffset int64 = -1
294 |
295 | for _, section := range machoFile.Sections {
296 | if section.Name == "__gopclntab" || section.Name == "__data" {
297 | sectionData, err := section.Data()
298 | if err != nil {
299 | continue
300 | }
301 |
302 | offset := findPclntabMagic(sectionData)
303 | if offset >= 0 {
304 | pclntabOffset = int64(section.Offset) + int64(offset)
305 | fmt.Printf(" 找到 pclntab 在段 %s,偏移: 0x%x\n", section.Name, pclntabOffset)
306 | break
307 | }
308 | }
309 | }
310 |
311 | if pclntabOffset < 0 {
312 | // 在整个文件中搜索
313 | offset := findPclntabMagic(data)
314 | if offset < 0 {
315 | return data, false, nil
316 | }
317 | pclntabOffset = int64(offset)
318 | fmt.Printf(" 找到 pclntab 在文件偏移: 0x%x\n", pclntabOffset)
319 | }
320 |
321 | return lo.modifyPclntab(data, pclntabOffset)
322 | }
323 |
324 | // findPclntabMagic 在数据中查找 pclntab magic value
325 | func findPclntabMagic(data []byte) int {
326 | magics := []uint32{go12magic, go116magic, go118magic, go120magic}
327 |
328 | for i := 0; i <= len(data)-4; i++ {
329 | value := binary.LittleEndian.Uint32(data[i : i+4])
330 | for _, magic := range magics {
331 | if value == magic {
332 | return i
333 | }
334 | }
335 | }
336 |
337 | return -1
338 | }
339 |
340 | // modifyPclntab 修改 pclntab 的内容
341 | func (lo *LinkerObfuscator) modifyPclntab(data []byte, offset int64) ([]byte, bool, error) {
342 | if offset < 0 || offset+4 > int64(len(data)) {
343 | return data, false, fmt.Errorf("无效的 pclntab 偏移")
344 | }
345 |
346 | // 复制数据以避免修改原始数据
347 | newData := make([]byte, len(data))
348 | copy(newData, data)
349 |
350 | // 读取原始 magic value(仅用于显示)
351 | originalMagic := binary.LittleEndian.Uint32(newData[offset : offset+4])
352 | fmt.Printf(" 原始 magic value: 0x%08x\n", originalMagic)
353 |
354 | // 检查是否完全禁用 pclntab 修改
355 | if lo.config.DisablePclntab {
356 | fmt.Printf(" ⚠️ pclntab 修改已禁用(避免杀软误报)\n")
357 | return data, false, nil
358 | }
359 |
360 | // 混淆函数名
361 | if lo.config.RemoveFuncNames {
362 | if err := lo.obfuscateFunctionNames(newData, offset); err != nil {
363 | return data, false, fmt.Errorf("函数名混淆失败: %v", err)
364 | }
365 | fmt.Printf(" ✅ 已混淆函数名\n")
366 | return newData, true, nil
367 | }
368 |
369 | return data, false, nil
370 | }
371 |
372 | // obfuscateFunctionNames 混淆二进制中的函数名(使用等长自然混淆)
373 | func (lo *LinkerObfuscator) obfuscateFunctionNames(data []byte, pclntabOffset int64) error {
374 | var patterns []string
375 | var replacements []string
376 |
377 | // 创建自然名称生成器
378 | nameGen := NewNaturalNameGenerator()
379 |
380 | // 标准库包列表
381 | standardLibs := map[string]bool{
382 | "main": false, "runtime": true, "sync": true, "fmt": true,
383 | "os": true, "io": true, "net": true, "http": true,
384 | "bufio": true, "bytes": true, "strings": true, "strconv": true,
385 | "time": true, "math": true, "errors": true, "context": true,
386 | "encoding": true, "json": true, "xml": true, "base64": true,
387 | "hex": true, "unicode": true, "regexp": true, "log": true,
388 | "sort": true, "path": true, "filepath": true, "syscall": true,
389 | }
390 |
391 | // 如果用户提供了自定义包名替换映射,使用它
392 | if len(lo.config.PackageReplacements) > 0 {
393 | if lo.config.OnlyObfuscateProject {
394 | fmt.Println(" ⚠️ 最小化混淆模式:只混淆项目包,保留标准库")
395 | } else {
396 | fmt.Println(" 使用自定义包名替换映射(等长模式):")
397 | }
398 |
399 | for original, replacement := range lo.config.PackageReplacements {
400 | // 检查是否是标准库
401 | pkgName := strings.TrimSuffix(original, ".")
402 | isStdLib := standardLibs[pkgName]
403 |
404 | // 如果启用了 OnlyObfuscateProject,跳过标准库
405 | if lo.config.OnlyObfuscateProject && isStdLib {
406 | continue
407 | }
408 |
409 | // 确保包名以 "." 结尾(用于匹配函数名)
410 | originalPattern := original
411 | if !strings.HasSuffix(originalPattern, ".") {
412 | originalPattern += "."
413 | }
414 |
415 | // 确保替换后的名称与原始名称等长
416 | replacementPattern := replacement
417 | if !strings.HasSuffix(replacementPattern, ".") {
418 | replacementPattern += "."
419 | }
420 |
421 | // 如果长度不同,重新生成等长的名称
422 | if len(replacementPattern) != len(originalPattern) {
423 | replacementPattern = nameGen.GeneratePackageName(originalPattern, len(originalPattern))
424 | }
425 |
426 | patterns = append(patterns, originalPattern)
427 | replacements = append(replacements, replacementPattern)
428 |
429 | if !lo.config.OnlyObfuscateProject || (lo.config.OnlyObfuscateProject && !isStdLib) {
430 | fmt.Printf(" %s -> %s (均为 %d 字节)\n", originalPattern, replacementPattern, len(originalPattern))
431 | }
432 | }
433 |
434 | if lo.config.OnlyObfuscateProject {
435 | fmt.Printf(" ✅ 已过滤标准库,只混淆项目包(共 %d 个)\n", len(patterns))
436 | }
437 | } else {
438 | // 使用默认的包名替换模式(等长自然混淆)
439 | if lo.config.OnlyObfuscateProject {
440 | fmt.Println(" ⚠️ 最小化混淆模式:只混淆项目包,保留标准库(减少杀软误报)")
441 | // 只混淆 main 包,保留所有标准库
442 | defaultPatterns := []string{
443 | "main.",
444 | }
445 |
446 | for _, pattern := range defaultPatterns {
447 | replacement := nameGen.GeneratePackageName(pattern, len(pattern))
448 | patterns = append(patterns, pattern)
449 | replacements = append(replacements, replacement)
450 | }
451 | } else {
452 | fmt.Println(" 使用等长自然混淆模式(标准)")
453 | defaultPatterns := []string{
454 | "main.",
455 | "runtime.",
456 | "sync.",
457 | "fmt.",
458 | "os.",
459 | "io.",
460 | "net.",
461 | "http.",
462 | }
463 |
464 | for _, pattern := range defaultPatterns {
465 | // 生成等长的自然名称
466 | replacement := nameGen.GeneratePackageName(pattern, len(pattern))
467 | patterns = append(patterns, pattern)
468 | replacements = append(replacements, replacement)
469 | }
470 | }
471 |
472 | fmt.Println(" 等长替换映射:")
473 | for i, pattern := range patterns {
474 | fmt.Printf(" %s -> %s\n", pattern, replacements[i])
475 | }
476 | }
477 |
478 | count := 0
479 | replacedPatterns := make(map[string]int)
480 |
481 | // 第一阶段:替换 "包名." 模式(函数名)
482 | // 策略:只在 pclntab 区域内替换,避免破坏 embed 文件内容
483 | // pclntab 区域通常在文件的特定位置,我们需要更精确的定位
484 |
485 | // 估算 pclntab 的大小(通常不超过几MB)
486 | // 为了安全,我们只在 pclntabOffset 后的合理范围内搜索
487 | pclntabSearchEnd := int(pclntabOffset) + 10*1024*1024 // 10MB 应该足够大
488 | if pclntabSearchEnd > len(data) {
489 | pclntabSearchEnd = len(data)
490 | }
491 |
492 | for i, pattern := range patterns {
493 | if i >= len(replacements) {
494 | break
495 | }
496 |
497 | patternBytes := []byte(pattern)
498 | replacement := []byte(replacements[i])
499 | patternCount := 0
500 |
501 | // 只在 pclntab 区域内查找并替换
502 | for j := int(pclntabOffset); j < pclntabSearchEnd-len(patternBytes); j++ {
503 | if bytes.Equal(data[j:j+len(patternBytes)], patternBytes) {
504 | // 更严格的上下文检查
505 | if !lo.isSafeFunctionNamePrefix(data, j, patternBytes) {
506 | continue
507 | }
508 |
509 | // ⭐ 等长替换:确保替换字符串与原字符串长度完全相同
510 | if len(replacement) == len(patternBytes) {
511 | // 直接替换,无需填充
512 | copy(data[j:j+len(replacement)], replacement)
513 | count++
514 | patternCount++
515 | } else if len(replacement) < len(patternBytes) {
516 | // 如果替换字符串较短(不应该发生,但作为保护)
517 | // 使用原始字符串填充方式而不是 0x00
518 | copy(data[j:j+len(replacement)], replacement)
519 | // 不填充,保持原有字符(更安全)
520 | count++
521 | patternCount++
522 | }
523 | // 忽略替换字符串过长的情况
524 | }
525 | }
526 |
527 | if patternCount > 0 {
528 | replacedPatterns[pattern] = patternCount
529 | }
530 | }
531 |
532 | // 替换项目包路径(在整个二进制文件中替换,但有严格的安全检查)
533 | fmt.Println(" 替换项目包路径...")
534 | pathCount := lo.replaceProjectPackagePathsGlobal(data)
535 |
536 | if count > 0 {
537 | fmt.Printf(" ✅ 替换了 %d 个函数名前缀:\n", count)
538 | for pattern, cnt := range replacedPatterns {
539 | fmt.Printf(" %s: %d 次\n", pattern, cnt)
540 | }
541 | } else {
542 | fmt.Println(" ⚠️ 未找到匹配的包名前缀")
543 | }
544 |
545 | if pathCount > 0 {
546 | fmt.Printf(" ✅ 替换了 %d 个项目包路径引用\n", pathCount)
547 | }
548 |
549 | return nil
550 | }
551 |
552 | // replaceProjectPackagePathsGlobal 在整个二进制文件中替换项目包路径(全局版本,带严格安全检查)
553 | func (lo *LinkerObfuscator) replaceProjectPackagePathsGlobal(data []byte) int {
554 | if len(lo.config.PackageReplacements) == 0 {
555 | return 0
556 | }
557 |
558 | count := 0
559 | replacedPaths := make(map[string]int)
560 |
561 | // 标准库包名列表(这些不进行路径替换,只替换函数名前缀)
562 | standardLibs := map[string]bool{
563 | "main": true, "runtime": true, "sync": true, "fmt": true,
564 | "os": true, "io": true, "net": true, "http": true,
565 | "bufio": true, "bytes": true, "strings": true, "strconv": true,
566 | "time": true, "math": true, "errors": true, "context": true,
567 | "encoding": true, "json": true, "xml": true, "base64": true,
568 | "hex": true, "unicode": true, "regexp": true, "log": true,
569 | "sort": true, "path": true, "filepath": true, "syscall": true,
570 | }
571 |
572 | // 对每个包路径进行替换
573 | for original, replacement := range lo.config.PackageReplacements {
574 | // 移除尾部的 "." 如果有的话
575 | originalPath := strings.TrimSuffix(original, ".")
576 |
577 | // 跳过标准库包(标准库包名太短,可能误替换系统符号)
578 | if standardLibs[originalPath] {
579 | continue
580 | }
581 |
582 | // 只替换包含 "/" 的路径(项目包路径)
583 | // 这样可以避免替换单个单词,减少误替换风险
584 | if !strings.Contains(originalPath, "/") {
585 | continue
586 | }
587 |
588 | replacementPath := strings.TrimSuffix(replacement, ".")
589 | patternBytes := []byte(originalPath)
590 | replacementBytes := []byte(replacementPath)
591 |
592 | // 在整个二进制文件中搜索并替换,但要进行严格的安全检查
593 | for j := 0; j < len(data)-len(patternBytes); j++ {
594 | if bytes.Equal(data[j:j+len(patternBytes)], patternBytes) {
595 | // 严格的安全检查
596 | if !lo.isSafePackagePathReplacement(data, j, len(patternBytes)) {
597 | continue
598 | }
599 |
600 | // 只有当替换字符串不长于原字符串时才替换
601 | if len(replacementBytes) <= len(patternBytes) {
602 | copy(data[j:j+len(replacementBytes)], replacementBytes)
603 | // 用空字节填充剩余部分
604 | for k := j + len(replacementBytes); k < j + len(patternBytes); k++ {
605 | data[k] = 0
606 | }
607 | count++
608 | replacedPaths[originalPath]++
609 | // 跳过已替换的部分
610 | j += len(patternBytes) - 1
611 | }
612 | }
613 | }
614 | }
615 |
616 | if count > 0 {
617 | fmt.Printf(" ✅ 替换了 %d 个包路径:\n", count)
618 | for path, cnt := range replacedPaths {
619 | fmt.Printf(" %s: %d 次\n", path, cnt)
620 | }
621 | }
622 |
623 | return count
624 | }
625 |
626 | // isSafePackagePathReplacement 检查是否可以安全地替换包路径
627 | // 这个检查比 isSafeToReplace 更宽松,因为包路径可能出现在多种上下文中
628 | func (lo *LinkerObfuscator) isSafePackagePathReplacement(data []byte, pos int, length int) bool {
629 | // 不检查系统路径(因为包路径通常包含特殊域名如 github.com)
630 | // 只检查是否在合理的上下文中
631 |
632 | // 检查前面是否是合理的分隔符或开始
633 | if pos > 0 {
634 | prevChar := data[pos-1]
635 | // 放宽检查:只拒绝明确是标识符一部分的字符(字母、下划线)
636 | // 数字前缀是允许的(如 "0github.com/..."),这些是 Go 编译器添加的标记
637 | // 拒绝的字符:字母、下划线(说明是某个标识符的一部分)
638 | if (prevChar >= 'a' && prevChar <= 'z') ||
639 | (prevChar >= 'A' && prevChar <= 'Z') ||
640 | prevChar == '_' {
641 | return false
642 | }
643 | // 数字、分隔符和不可打印字符都允许
644 | }
645 |
646 | // 检查后面的字符
647 | if pos+length < len(data) {
648 | nextChar := data[pos+length]
649 | // 放宽检查:只拒绝明确是标识符一部分的字符
650 | // 拒绝的字符:字母、数字、下划线(说明是某个标识符的一部分)
651 | // 连字符不拒绝,因为包路径后面可能跟版本号
652 | if (nextChar >= 'a' && nextChar <= 'z') ||
653 | (nextChar >= 'A' && nextChar <= 'Z') ||
654 | (nextChar >= '0' && nextChar <= '9') ||
655 | nextChar == '_' {
656 | return false
657 | }
658 | // 其他字符都允许(包括 /、.、-、空格、null、引号等)
659 | }
660 |
661 | return true
662 | }
663 |
664 |
665 |
666 | // replaceBytesStrict 严格的字节替换,用于包路径替换
667 | func (lo *LinkerObfuscator) replaceBytesStrict(data []byte, pattern []byte, replacement []byte) int {
668 | if len(replacement) > len(pattern) {
669 | return 0
670 | }
671 |
672 | // 额外检查:pattern 必须足够长(至少 10 个字符)
673 | // 避免替换短字符串
674 | if len(pattern) < 10 {
675 | return 0
676 | }
677 |
678 | count := 0
679 | for i := 0; i < len(data)-len(pattern); i++ {
680 | if bytes.Equal(data[i:i+len(pattern)], pattern) {
681 | // 更严格的安全检查
682 | if !lo.isSafeToReplaceStrict(data, i, len(pattern)) {
683 | continue
684 | }
685 |
686 | // 执行替换
687 | copy(data[i:i+len(replacement)], replacement)
688 | for j := i + len(replacement); j < i+len(pattern); j++ {
689 | data[j] = 0
690 | }
691 | count++
692 | i += len(pattern) - 1
693 | }
694 | }
695 |
696 | return count
697 | }
698 |
699 | // isSafeToReplaceStrict 更严格的安全检查
700 | func (lo *LinkerObfuscator) isSafeToReplaceStrict(data []byte, pos int, length int) bool {
701 | // 首先使用基本的安全检查
702 | if !lo.isSafeToReplace(data, pos, length) {
703 | return false
704 | }
705 |
706 | // 额外检查:确保前后都是合理的字符
707 | // 检查前一个字符
708 | if pos > 0 {
709 | prevChar := data[pos-1]
710 | // 如果前一个字符是字母或数字,可能是其他标识符的一部分
711 | if (prevChar >= 'a' && prevChar <= 'z') ||
712 | (prevChar >= 'A' && prevChar <= 'Z') ||
713 | (prevChar >= '0' && prevChar <= '9') ||
714 | prevChar == '_' {
715 | return false
716 | }
717 | }
718 |
719 | // 检查后一个字符
720 | if pos+length < len(data) {
721 | nextChar := data[pos+length]
722 | // 如果后一个字符是字母或数字,可能是其他标识符的一部分
723 | if (nextChar >= 'a' && nextChar <= 'z') ||
724 | (nextChar >= 'A' && nextChar <= 'Z') ||
725 | (nextChar >= '0' && nextChar <= '9') ||
726 | nextChar == '_' {
727 | return false
728 | }
729 | }
730 |
731 | return true
732 | }
733 |
734 | // isSafeToReplace 检查是否可以安全地替换该位置的字节
735 | // 避免破坏系统路径(如 /System/Library/Frameworks/...)
736 | func (lo *LinkerObfuscator) isSafeToReplace(data []byte, pos int, length int) bool {
737 | // 检查前面的上下文(最多往前看 100 字节)
738 | contextStart := pos - 100
739 | if contextStart < 0 {
740 | contextStart = 0
741 | }
742 |
743 | contextBefore := data[contextStart:pos]
744 |
745 | // 危险模式:系统路径
746 | dangerousPatterns := [][]byte{
747 | []byte("/System/Library/"),
748 | []byte("/usr/lib/"),
749 | []byte("/usr/local/"),
750 | []byte("Library/Frameworks/"),
751 | []byte(".framework/"),
752 | []byte(".dylib"),
753 | []byte("/Cryptexes/"),
754 | }
755 |
756 | // 检查前面的上下文是否包含危险模式
757 | for _, dangerous := range dangerousPatterns {
758 | if bytes.Contains(contextBefore, dangerous) {
759 | return false
760 | }
761 | }
762 |
763 | // 检查后面的上下文(最多往后看 50 字节)
764 | contextEnd := pos + length + 50
765 | if contextEnd > len(data) {
766 | contextEnd = len(data)
767 | }
768 |
769 | contextAfter := data[pos+length : contextEnd]
770 |
771 | // 检查后面是否紧跟着系统路径特征
772 | afterDangerousPatterns := [][]byte{
773 | []byte(".framework"),
774 | []byte(".dylib"),
775 | }
776 |
777 | for _, dangerous := range afterDangerousPatterns {
778 | if bytes.HasPrefix(contextAfter, dangerous) {
779 | return false
780 | }
781 | }
782 |
783 | return true
784 | }
785 |
786 | // discoverAndGeneratePackageReplacements 自动发现项目包名并生成替换映射
787 | func (lo *LinkerObfuscator) discoverAndGeneratePackageReplacements() error {
788 | // 1. 读取 go.mod 获取模块名
789 | moduleName, err := lo.getModuleName()
790 | if err != nil {
791 | return fmt.Errorf("无法读取模块名: %v", err)
792 | }
793 |
794 | if moduleName == "" {
795 | return fmt.Errorf("go.mod 中未找到模块名")
796 | }
797 |
798 | fmt.Printf(" 发现模块名: %s\n", moduleName)
799 |
800 | // 2. 扫描项目目录,查找所有子包
801 | packages, err := lo.discoverProjectPackages(moduleName)
802 | if err != nil {
803 | return fmt.Errorf("扫描项目包失败: %v", err)
804 | }
805 |
806 | if len(packages) == 0 {
807 | return fmt.Errorf("未发现任何项目包")
808 | }
809 |
810 | fmt.Printf(" 发现 %d 个项目包:\n", len(packages))
811 | for _, pkg := range packages {
812 | fmt.Printf(" - %s\n", pkg)
813 | }
814 |
815 | // 3. 添加常见的标准库包名
816 | standardPackages := lo.getStandardPackages()
817 | fmt.Printf(" 添加 %d 个标准库包\n", len(standardPackages))
818 |
819 | // 4. 合并项目包和标准库包(项目包优先,确保子包在前)
820 | allPackages := append(packages, standardPackages...)
821 |
822 | // 5. 如果启用第三方包混淆,发现并添加第三方包
823 | var thirdPartyPackages []string
824 | if lo.config.ObfuscateThirdParty {
825 | thirdPartyPackages, err = lo.discoverThirdPartyPackages(moduleName)
826 | if err != nil {
827 | fmt.Printf(" ⚠️ 发现第三方包失败: %v\n", err)
828 | } else {
829 | fmt.Printf(" 发现 %d 个第三方包(包括子包):\n", len(thirdPartyPackages))
830 | // 显示前 10 个包
831 | displayCount := 10
832 | if len(thirdPartyPackages) < displayCount {
833 | displayCount = len(thirdPartyPackages)
834 | }
835 | for i := 0; i < displayCount; i++ {
836 | fmt.Printf(" - %s\n", thirdPartyPackages[i])
837 | }
838 | if len(thirdPartyPackages) > displayCount {
839 | fmt.Printf(" - ... 还有 %d 个\n", len(thirdPartyPackages)-displayCount)
840 | }
841 | allPackages = append(allPackages, thirdPartyPackages...)
842 | }
843 | }
844 |
845 | // 6. 生成替换映射
846 | replacements := lo.generateReplacements(allPackages)
847 |
848 | // 7. 应用替换映射
849 | lo.config.PackageReplacements = replacements
850 |
851 | if lo.config.ObfuscateThirdParty {
852 | fmt.Printf(" ✅ 生成了 %d 个包名替换映射 (项目包: %d, 标准库: %d, 第三方: %d)\n",
853 | len(replacements), len(packages), len(standardPackages), len(thirdPartyPackages))
854 | } else {
855 | fmt.Printf(" ✅ 生成了 %d 个包名替换映射 (项目包: %d, 标准库: %d)\n",
856 | len(replacements), len(packages), len(standardPackages))
857 | }
858 |
859 | return nil
860 | }
861 |
862 | // getStandardPackages 返回常见的标准库包名列表
863 | // 这些包名是经过验证的,替换后不会影响程序运行
864 | // 只替换函数名前缀(如 "fmt."),不替换包路径本身(如 "fmt")
865 | func (lo *LinkerObfuscator) getStandardPackages() []string {
866 | return []string{
867 | // 核心运行时(安全)
868 | "main",
869 | "runtime",
870 | "sync",
871 | "syscall", // 系统调用,函数名前缀可以安全替换
872 |
873 | // I/O 和格式化(安全)
874 | "fmt",
875 | "io",
876 | "bufio",
877 | "os",
878 | "log",
879 |
880 | // 网络相关(安全)
881 | "net",
882 | "http", // 实际是 net/http,但在符号表中可能显示为 http
883 |
884 | // 字符串和数据处理(安全)
885 | "strings",
886 | "bytes",
887 | "strconv",
888 | "unicode",
889 | "regexp",
890 |
891 | // 编码(安全)
892 | "encoding",
893 | "json", // encoding/json
894 | "xml", // encoding/xml
895 | "base64", // encoding/base64
896 | "hex", // encoding/hex
897 |
898 | // 时间和数学(安全)
899 | "time",
900 | "math",
901 |
902 | // 容器和算法(安全)
903 | "sort",
904 | "container",
905 | "list",
906 | "heap",
907 |
908 | // 路径处理(安全)
909 | "path",
910 | "filepath",
911 |
912 | // 错误处理(安全)
913 | "errors",
914 |
915 | // 上下文(安全)
916 | "context",
917 |
918 | // 压缩(安全)
919 | "compress",
920 | "gzip",
921 | "zlib",
922 |
923 | // 哈希(安全)
924 | "hash",
925 | "crc32",
926 | "crc64",
927 | "fnv",
928 |
929 | // 注意:以下包不包含,因为可能影响程序
930 | // - reflect: 反射包,可能依赖包名
931 | // - unsafe: 不安全操作
932 | // - crypto/*: 加密包,某些实现可能依赖包名
933 | // - runtime/debug: 调试相关
934 | // - plugin: 插件系统
935 | }
936 | }
937 |
938 | // getModuleName 从 go.mod 文件中读取模块名
939 | func (lo *LinkerObfuscator) getModuleName() (string, error) {
940 | goModPath := filepath.Join(lo.projectDir, "go.mod")
941 |
942 | data, err := ioutil.ReadFile(goModPath)
943 | if err != nil {
944 | return "", err
945 | }
946 |
947 | // 使用正则表达式匹配 module 行
948 | re := regexp.MustCompile(`(?m)^module\s+([^\s]+)`)
949 | matches := re.FindSubmatch(data)
950 |
951 | if len(matches) < 2 {
952 | return "", fmt.Errorf("go.mod 中未找到 module 声明")
953 | }
954 |
955 | return string(matches[1]), nil
956 | }
957 |
958 | // discoverProjectPackages 扫描项目目录,发现所有子包
959 | func (lo *LinkerObfuscator) discoverProjectPackages(moduleName string) ([]string, error) {
960 | packages := make(map[string]bool)
961 |
962 | // 添加主模块
963 | packages[moduleName] = true
964 |
965 | // 遍历项目目录
966 | err := filepath.Walk(lo.projectDir, func(path string, info os.FileInfo, err error) error {
967 | if err != nil {
968 | return err
969 | }
970 |
971 | // 跳过非目录
972 | if !info.IsDir() {
973 | return nil
974 | }
975 |
976 | // 跳过隐藏目录、vendor、测试数据等
977 | dirName := info.Name()
978 | if strings.HasPrefix(dirName, ".") ||
979 | dirName == "vendor" ||
980 | dirName == "testdata" ||
981 | dirName == "node_modules" {
982 | return filepath.SkipDir
983 | }
984 |
985 | // 检查目录中是否有 .go 文件
986 | hasGoFiles, err := lo.hasGoFiles(path)
987 | if err != nil || !hasGoFiles {
988 | return nil
989 | }
990 |
991 | // 计算相对路径
992 | relPath, err := filepath.Rel(lo.projectDir, path)
993 | if err != nil {
994 | return nil
995 | }
996 |
997 | // 跳过根目录(已经添加了主模块)
998 | if relPath == "." {
999 | return nil
1000 | }
1001 |
1002 | // 构建完整包路径
1003 | pkgPath := moduleName + "/" + filepath.ToSlash(relPath)
1004 | packages[pkgPath] = true
1005 |
1006 | return nil
1007 | })
1008 |
1009 | if err != nil {
1010 | return nil, err
1011 | }
1012 |
1013 | // 转换为切片并排序(长的包名在前,避免替换冲突)
1014 | result := make([]string, 0, len(packages))
1015 | for pkg := range packages {
1016 | result = append(result, pkg)
1017 | }
1018 |
1019 | // 按长度降序排序,确保子包先被替换
1020 | for i := 0; i < len(result); i++ {
1021 | for j := i + 1; j < len(result); j++ {
1022 | if len(result[j]) > len(result[i]) {
1023 | result[i], result[j] = result[j], result[i]
1024 | }
1025 | }
1026 | }
1027 |
1028 | return result, nil
1029 | }
1030 |
1031 | // hasGoFiles 检查目录中是否有 .go 文件
1032 | func (lo *LinkerObfuscator) hasGoFiles(dir string) (bool, error) {
1033 | files, err := ioutil.ReadDir(dir)
1034 | if err != nil {
1035 | return false, err
1036 | }
1037 |
1038 | for _, file := range files {
1039 | if !file.IsDir() && strings.HasSuffix(file.Name(), ".go") {
1040 | // 排除测试文件
1041 | if !strings.HasSuffix(file.Name(), "_test.go") {
1042 | return true, nil
1043 | }
1044 | }
1045 | }
1046 |
1047 | return false, nil
1048 | }
1049 |
1050 | // generateReplacements 为包名生成短替换名
1051 | func (lo *LinkerObfuscator) generateReplacements(packages []string) map[string]string {
1052 | replacements := make(map[string]string)
1053 |
1054 | // 生成简短的替换名
1055 | counter := 0
1056 | for _, pkg := range packages {
1057 | // 生成替换名: a, b, c, ..., z, aa, ab, ...
1058 | replacement := lo.generateShortName(counter)
1059 | replacements[pkg] = replacement
1060 | counter++
1061 | }
1062 |
1063 | return replacements
1064 | }
1065 |
1066 | // generateShortName 生成短名称 (a, b, c, ..., z, aa, ab, ...)
1067 | func (lo *LinkerObfuscator) generateShortName(index int) string {
1068 | if index < 26 {
1069 | return string(rune('a' + index))
1070 | }
1071 |
1072 | // 对于超过26的,使用两个字母
1073 | first := index / 26 - 1
1074 | second := index % 26
1075 | return string(rune('a'+first)) + string(rune('a'+second))
1076 | }
1077 |
1078 | // isSafeFunctionNamePrefix 检查是否是安全的函数名前缀位置
1079 | // 在 pclntab 中,函数名前缀通常前面是:
1080 | // 1. 空字节 (\x00)
1081 | // 2. 不可打印字符
1082 | // 3. 路径分隔符后的完整包名
1083 | func (lo *LinkerObfuscator) isSafeFunctionNamePrefix(data []byte, pos int, pattern []byte) bool {
1084 | // 检查前一个字符
1085 | if pos > 0 {
1086 | prevChar := data[pos-1]
1087 |
1088 | // 允许的前置字符:
1089 | // - 空字节 (0x00)
1090 | // - 不可打印字符 (< 0x20,除了空格)
1091 | // - 路径分隔符 (/)
1092 | //
1093 | // 不允许的前置字符:
1094 | // - 字母、数字(说明是某个标识符的一部分)
1095 | // - 点号(说明是包路径的一部分,如 commons.io.)
1096 | // - 其他可打印字符(说明可能是文本内容)
1097 |
1098 | if prevChar == 0 {
1099 | // 空字节,安全
1100 | return true
1101 | }
1102 |
1103 | if prevChar < 0x20 && prevChar != ' ' {
1104 | // 不可打印字符(除了空格),安全
1105 | return true
1106 | }
1107 |
1108 | // 如果是字母、数字、点号、斜杠、下划线、连字符,不安全
1109 | if (prevChar >= 'a' && prevChar <= 'z') ||
1110 | (prevChar >= 'A' && prevChar <= 'Z') ||
1111 | (prevChar >= '0' && prevChar <= '9') ||
1112 | prevChar == '.' ||
1113 | prevChar == '/' ||
1114 | prevChar == '_' ||
1115 | prevChar == '-' {
1116 | return false
1117 | }
1118 | }
1119 |
1120 | // 检查后一个字符(在点号之后)
1121 | // 函数名前缀后面应该是大写字母(导出函数)或小写字母(未导出函数)
1122 | dotPos := bytes.IndexByte(pattern, '.')
1123 | if dotPos >= 0 && pos+len(pattern) < len(data) {
1124 | nextChar := data[pos+len(pattern)]
1125 |
1126 | // 函数名后面通常是:
1127 | // - 大写或小写字母(函数名开始)
1128 | // - 空字节(字符串结束)
1129 | if nextChar == 0 {
1130 | return true
1131 | }
1132 |
1133 | if (nextChar >= 'a' && nextChar <= 'z') ||
1134 | (nextChar >= 'A' && nextChar <= 'Z') {
1135 | return true
1136 | }
1137 |
1138 | // 其他字符,可能不是函数名
1139 | return false
1140 | }
1141 |
1142 | return true
1143 | }
1144 |
1145 | // discoverThirdPartyPackages 发现第三方依赖包
1146 | func (lo *LinkerObfuscator) discoverThirdPartyPackages(moduleName string) ([]string, error) {
1147 | packages := make(map[string]bool)
1148 |
1149 | // 读取 go.mod 文件
1150 | goModPath := filepath.Join(lo.projectDir, "go.mod")
1151 | data, err := ioutil.ReadFile(goModPath)
1152 | if err != nil {
1153 | return nil, err
1154 | }
1155 |
1156 | // 改进的正则表达式:匹配所有 require 行(包括 indirect 注释)
1157 | // 匹配格式: github.com/xxx/yyy v1.2.3 或 github.com/xxx/yyy v1.2.3 // indirect
1158 | requireRe := regexp.MustCompile(`(?m)^\s*([a-zA-Z0-9\-_\.]+/[a-zA-Z0-9\-_\./]+?)\s+v[^\s]+`)
1159 | requireMatches := requireRe.FindAllSubmatch(data, -1)
1160 |
1161 | // 同时匹配 replace 指令,格式: replace github.com/xxx/yyy => github.com/aaa/bbb v1.2.3
1162 | replaceRe := regexp.MustCompile(`(?m)^replace\s+([a-zA-Z0-9\-_\.]+/[a-zA-Z0-9\-_\./]+)\s+[^\n]*=>\s+([a-zA-Z0-9\-_\.]+/[a-zA-Z0-9\-_\./]+)\s+v`)
1163 | replaceMatches := replaceRe.FindAllSubmatch(data, -1)
1164 |
1165 | // 处理 require 的包
1166 | for _, match := range requireMatches {
1167 | if len(match) >= 2 {
1168 | pkgPath := string(match[1])
1169 |
1170 | // 排除标准库(不包含域名)
1171 | if !strings.Contains(pkgPath, ".") {
1172 | continue
1173 | }
1174 |
1175 | // 排除项目自身
1176 | if strings.HasPrefix(pkgPath, moduleName) {
1177 | continue
1178 | }
1179 |
1180 | packages[pkgPath] = true
1181 |
1182 | // 对于有子包的路径(如 github.com/antlr/antlr4/runtime/Go/antlr)
1183 | // 也添加其父级路径,以便能匹配更多变体
1184 | parts := strings.Split(pkgPath, "/")
1185 | if len(parts) > 3 {
1186 | // 添加顶层包路径,例如 github.com/antlr/antlr4
1187 | topLevel := strings.Join(parts[:3], "/")
1188 | packages[topLevel] = true
1189 |
1190 | // 添加中间路径,例如 github.com/antlr/antlr4/runtime
1191 | for i := 3; i < len(parts); i++ {
1192 | midLevel := strings.Join(parts[:i+1], "/")
1193 | packages[midLevel] = true
1194 | }
1195 | }
1196 |
1197 | // 添加可能的 internal 子包路径
1198 | // 例如: github.com/xxx/yyy/internal, github.com/xxx/yyy/internal/pkg
1199 | lo.addCommonSubPackages(pkgPath, packages)
1200 | }
1201 | }
1202 |
1203 | // 处理 replace 指令中的包(原始包和替换后的包都添加)
1204 | for _, match := range replaceMatches {
1205 | if len(match) >= 3 {
1206 | originalPkg := string(match[1])
1207 | replacementPkg := string(match[2])
1208 |
1209 | // 处理原始包
1210 | if strings.Contains(originalPkg, ".") && !strings.HasPrefix(originalPkg, moduleName) {
1211 | packages[originalPkg] = true
1212 | parts := strings.Split(originalPkg, "/")
1213 | if len(parts) > 3 {
1214 | for i := 3; i <= len(parts); i++ {
1215 | packages[strings.Join(parts[:i], "/")] = true
1216 | }
1217 | }
1218 | lo.addCommonSubPackages(originalPkg, packages)
1219 | }
1220 |
1221 | // 处理替换后的包
1222 | if strings.Contains(replacementPkg, ".") && !strings.HasPrefix(replacementPkg, moduleName) {
1223 | packages[replacementPkg] = true
1224 | parts := strings.Split(replacementPkg, "/")
1225 | if len(parts) > 3 {
1226 | for i := 3; i <= len(parts); i++ {
1227 | packages[strings.Join(parts[:i], "/")] = true
1228 | }
1229 | }
1230 | lo.addCommonSubPackages(replacementPkg, packages)
1231 | }
1232 | }
1233 | }
1234 |
1235 | // 转换为切片并排序
1236 | result := make([]string, 0, len(packages))
1237 | for pkg := range packages {
1238 | result = append(result, pkg)
1239 | }
1240 |
1241 | // 按长度降序排序(确保子包在前,避免替换冲突)
1242 | sort.Slice(result, func(i, j int) bool {
1243 | return len(result[i]) > len(result[j])
1244 | })
1245 |
1246 | return result, nil
1247 | }
1248 |
1249 | // addCommonSubPackages 添加常见的子包路径(如 internal, pkg, cmd 等)
1250 | func (lo *LinkerObfuscator) addCommonSubPackages(basePath string, packages map[string]bool) {
1251 | // 常见的子包目录名
1252 | commonSubDirs := []string{
1253 | "internal",
1254 | "pkg",
1255 | "cmd",
1256 | "api",
1257 | "lib",
1258 | "core",
1259 | "common",
1260 | "util",
1261 | "utils",
1262 | "proto",
1263 | "protobuf",
1264 | }
1265 |
1266 | // 为基础路径添加常见子目录
1267 | for _, subDir := range commonSubDirs {
1268 | subPath := basePath + "/" + subDir
1269 | packages[subPath] = true
1270 |
1271 | // 对于 internal,还要添加一些常见的更深层路径
1272 | if subDir == "internal" {
1273 | internalCommon := []string{
1274 | "spnego",
1275 | "decimal",
1276 | "querytext",
1277 | "crypto",
1278 | "crypto/ccm",
1279 | "crypto/cmac",
1280 | "x",
1281 | "x/crypto",
1282 | "x/crypto/cryptobyte",
1283 | }
1284 | for _, inner := range internalCommon {
1285 | packages[subPath+"/"+inner] = true
1286 | }
1287 | }
1288 | }
1289 | }
1290 |
--------------------------------------------------------------------------------
/obfuscator/pipeline.go:
--------------------------------------------------------------------------------
1 | package obfuscator
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/ast"
7 | "go/format"
8 | "go/parser"
9 | "go/token"
10 | "io"
11 | "io/ioutil"
12 | "log"
13 | "os"
14 | "path/filepath"
15 | "strings"
16 | )
17 |
18 | // Run 执行整个混淆流程
19 | func (o *Obfuscator) Run() error {
20 | // 如果启用了字符串加密,创建解密包并保护相关名称
21 | if o.Config.EncryptStrings {
22 | // 提前保护解密函数名称和包名
23 | o.protectedNames[o.decryptFuncName] = true
24 | o.packageNames[o.decryptPkgName] = true
25 |
26 | if err := o.createDecryptPackage(); err != nil {
27 | return fmt.Errorf("创建解密包失败: %v", err)
28 | }
29 | }
30 |
31 | log.Println("阶段 0/5: 收集导入信息...")
32 | if err := o.collectImportInfo(); err != nil {
33 | return fmt.Errorf("收集导入信息失败: %v", err)
34 | }
35 |
36 | log.Println("阶段 1/5: 扫描项目并收集保护名称...")
37 | err := filepath.Walk(o.projectRoot, func(path string, info os.FileInfo, err error) error {
38 | if err != nil {
39 | return err
40 | }
41 | if info.IsDir() || !strings.HasSuffix(path, ".go") {
42 | return nil
43 | }
44 | if strings.Contains(path, "vendor/") {
45 | return nil
46 | }
47 |
48 | // 检查是否跳过生成代码
49 | if o.Config.SkipGeneratedCode && o.isGeneratedFile(path) {
50 | o.skippedFiles[path] = "Generated code"
51 | return nil
52 | }
53 |
54 | // 检查是否排除文件
55 | if o.isExcluded(path) {
56 | o.skippedFiles[path] = "Excluded by pattern"
57 | return nil
58 | }
59 |
60 | // 解析文件
61 | node, err := parser.ParseFile(o.fset, path, nil, parser.ParseComments)
62 | if err != nil {
63 | log.Printf("警告: 无法解析文件 %s: %v", path, err)
64 | o.skippedFiles[path] = fmt.Sprintf("Parse error: %v", err)
65 | return nil
66 | }
67 |
68 | // 收集保护名称
69 | o.collectProtectedNames(node)
70 |
71 | // 检查反射使用
72 | if o.Config.PreserveReflection {
73 | o.protectReflectionTypes(node)
74 | }
75 |
76 | return nil
77 | })
78 | if err != nil {
79 | return fmt.Errorf("扫描项目失败: %v", err)
80 | }
81 |
82 | log.Println("阶段 2/5: 构建作用域分析...")
83 | if err := o.buildScopeAnalysis(); err != nil {
84 | return fmt.Errorf("作用域分析失败: %v", err)
85 | }
86 |
87 | log.Println("阶段 3/5: 构建混淆映射...")
88 | o.buildObfuscationMapsWithScope()
89 |
90 | log.Println("阶段 4/5: 复制项目文件...")
91 | // 构建文件名映射(原始路径 -> 混淆后路径)
92 | fileMapping := make(map[string]string)
93 | if err := o.copyProjectAndBuildMapping(fileMapping); err != nil {
94 | return fmt.Errorf("复制项目失败: %v", err)
95 | }
96 |
97 | log.Println("阶段 5/5: 应用混淆...")
98 | // 第一遍:只处理非平台特定的文件(优先添加解密函数)
99 | err = filepath.Walk(o.outputDir, func(path string, info os.FileInfo, err error) error {
100 | if err != nil {
101 | return err
102 | }
103 | if info.IsDir() || !strings.HasSuffix(path, ".go") {
104 | return nil
105 | }
106 |
107 | // 检查是否跳过文件
108 | relPath, _ := filepath.Rel(o.outputDir, path)
109 | originalPath := filepath.Join(o.projectRoot, relPath)
110 | if _, skipped := o.skippedFiles[originalPath]; skipped {
111 | return nil
112 | }
113 |
114 | // 只处理非平台特定的文件
115 | if !o.isFilePlatformSpecific(path) {
116 | if err := o.obfuscateFileWithMapping(path, fileMapping); err != nil {
117 | return fmt.Errorf("混淆文件 %s 失败: %v", path, err)
118 | }
119 | }
120 |
121 | return nil
122 | })
123 | if err != nil {
124 | return fmt.Errorf("应用混淆失败(第一遍): %v", err)
125 | }
126 |
127 | // 第二遍:处理平台特定的文件(如果包还没有解密函数,在第一个遇到的文件中添加)
128 | err = filepath.Walk(o.outputDir, func(path string, info os.FileInfo, err error) error {
129 | if err != nil {
130 | return err
131 | }
132 | if info.IsDir() || !strings.HasSuffix(path, ".go") {
133 | return nil
134 | }
135 |
136 | // 检查是否跳过文件
137 | relPath, _ := filepath.Rel(o.outputDir, path)
138 | originalPath := filepath.Join(o.projectRoot, relPath)
139 | if _, skipped := o.skippedFiles[originalPath]; skipped {
140 | return nil
141 | }
142 |
143 | // 只处理平台特定的文件
144 | if o.isFilePlatformSpecific(path) {
145 | if err := o.obfuscateFileWithMapping(path, fileMapping); err != nil {
146 | return fmt.Errorf("混淆文件 %s 失败: %v", path, err)
147 | }
148 | }
149 |
150 | return nil
151 | })
152 | if err != nil {
153 | return fmt.Errorf("应用混淆失败(第二遍): %v", err)
154 | }
155 |
156 | return nil
157 | }
158 |
159 | // collectImportInfo 收集所有文件的导入信息
160 | func (o *Obfuscator) collectImportInfo() error {
161 | return filepath.Walk(o.projectRoot, func(path string, info os.FileInfo, err error) error {
162 | if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") {
163 | return err
164 | }
165 | if strings.Contains(path, "vendor/") {
166 | return nil
167 | }
168 |
169 | // 跳过排除的文件
170 | if shouldExclude, _ := o.shouldExcludeFile(path); shouldExclude {
171 | return nil
172 | }
173 |
174 | node, err := parser.ParseFile(o.fset, path, nil, parser.ParseComments)
175 | if err != nil {
176 | return nil // 跳过无法解析的文件
177 | }
178 |
179 | // 处理所有导入
180 | for _, imp := range node.Imports {
181 | if imp.Path == nil {
182 | continue
183 | }
184 |
185 | pkgPath := strings.Trim(imp.Path.Value, `"`)
186 |
187 | // 确定代码中使用的包名
188 | var pkgName string
189 | if imp.Name != nil {
190 | // 跳过空白导入和点导入
191 | if imp.Name.Name == "_" || imp.Name.Name == "." {
192 | continue
193 | }
194 | pkgName = imp.Name.Name
195 | } else {
196 | // 使用基础名称
197 | pkgName = filepath.Base(pkgPath)
198 | }
199 |
200 | // 存储映射
201 | if existingName, exists := o.importPathToName[pkgPath]; exists {
202 | if existingName != pkgName {
203 | log.Printf("警告: 包 %s 的名称不一致: %s vs %s", pkgPath, existingName, pkgName)
204 | }
205 | } else {
206 | o.importPathToName[pkgPath] = pkgName
207 | }
208 |
209 | // 标记包名为受保护
210 | o.packageNames[pkgName] = true
211 |
212 | // 只为标准库创建别名
213 | if isStandardLibrary(pkgPath) {
214 | if _, exists := o.importAliasMapping[pkgPath]; !exists {
215 | alias := fmt.Sprintf("p%s", generateRandomString(8))
216 | o.importAliasMapping[pkgPath] = alias
217 | }
218 | }
219 | }
220 |
221 | return nil
222 | })
223 | }
224 |
225 | // collectProtectedNames 收集所有不应被混淆的名称
226 | func (o *Obfuscator) collectProtectedNames(node *ast.File) {
227 | // 构建导入包的映射(包名/别名 -> 导入路径)
228 | importPaths := make(map[string]string)
229 | for _, imp := range node.Imports {
230 | if imp.Path == nil {
231 | continue
232 | }
233 | pkgPath := strings.Trim(imp.Path.Value, `"`)
234 | var pkgName string
235 | if imp.Name != nil {
236 | pkgName = imp.Name.Name
237 | } else {
238 | pkgName = filepath.Base(pkgPath)
239 | }
240 | importPaths[pkgName] = pkgPath
241 | }
242 |
243 | ast.Inspect(node, func(n ast.Node) bool {
244 | switch x := n.(type) {
245 | case *ast.TypeSpec:
246 | // 保护结构体字段名
247 | if structType, ok := x.Type.(*ast.StructType); ok {
248 | if structType.Fields != nil {
249 | for _, field := range structType.Fields.List {
250 | // 保护命名字段
251 | for _, fieldName := range field.Names {
252 | o.protectedNames[fieldName.Name] = true
253 | }
254 | // 保护匿名字段
255 | if len(field.Names) == 0 {
256 | if ident, ok := field.Type.(*ast.Ident); ok {
257 | o.protectedNames[ident.Name] = true
258 | }
259 | if starExpr, ok := field.Type.(*ast.StarExpr); ok {
260 | if ident, ok := starExpr.X.(*ast.Ident); ok {
261 | o.protectedNames[ident.Name] = true
262 | }
263 | }
264 | }
265 | }
266 | }
267 | }
268 | // 保护接口方法
269 | if interfaceType, ok := x.Type.(*ast.InterfaceType); ok {
270 | if interfaceType.Methods != nil {
271 | for _, method := range interfaceType.Methods.List {
272 | for _, methodName := range method.Names {
273 | o.protectedNames[methodName.Name] = true
274 | }
275 | }
276 | }
277 | }
278 |
279 | case *ast.SelectorExpr:
280 | // 只混淆项目内部包的选择器,其他所有选择器都保护
281 | shouldProtect := true
282 |
283 | if ident, ok := x.X.(*ast.Ident); ok {
284 | pkgName := ident.Name
285 | // 检查是否是导入的包
286 | if pkgPath, exists := importPaths[pkgName]; exists {
287 | // 如果是项目内部的包,不保护(允许混淆)
288 | if o.isProjectImportPath(pkgPath) {
289 | shouldProtect = false
290 | }
291 | }
292 | // 注意:如果不在importPaths中,可能是局部变量,保持保护
293 | }
294 |
295 | if shouldProtect {
296 | o.protectedNames[x.Sel.Name] = true
297 | }
298 |
299 | case *ast.FuncDecl:
300 | // 保护方法名
301 | if x.Recv != nil {
302 | o.protectedNames[x.Name.Name] = true
303 | }
304 | }
305 | return true
306 | })
307 | }
308 |
309 | // isProjectImportPath 检查导入路径是否属于项目内部
310 | func (o *Obfuscator) isProjectImportPath(importPath string) bool {
311 | // 项目内部的导入路径通常包含项目的模块路径
312 | // 例如:github.com/shadow1ng/fscan/Common
313 |
314 | // 标准库:不包含点号的路径(如fmt, os, net等)或golang.org/x/开头的
315 | if !strings.Contains(importPath, ".") {
316 | return false
317 | }
318 |
319 | // golang.org/x/ 开头的是Go官方扩展库,不是项目内部的
320 | if strings.HasPrefix(importPath, "golang.org/x/") {
321 | return false
322 | }
323 |
324 | // gopkg.in 是第三方包托管,不是项目内部的
325 | if strings.HasPrefix(importPath, "gopkg.in/") {
326 | return false
327 | }
328 |
329 | // 检查是否包含项目的模块路径
330 | // 从go.mod中读取模块路径会更准确,但这里我们使用简单的启发式方法
331 | // 如果导入路径包含项目根目录的名称,认为是项目内部的
332 | projectName := filepath.Base(o.projectRoot)
333 | if strings.Contains(importPath, "/"+projectName+"/") ||
334 | strings.HasSuffix(importPath, "/"+projectName) {
335 | return true
336 | }
337 |
338 | // 其他情况认为是外部包
339 | return false
340 | }
341 |
342 | // isProjectPackage 检查包名是否属于项目内部
343 | func (o *Obfuscator) isProjectPackage(pkgName string) bool {
344 | // 检查是否在项目的包名列表中
345 | // 项目内部的包通常不包含点号(如Common, Core等)
346 | // 而外部包通常有点号或是标准库名称
347 |
348 | // 如果包名包含点号,很可能是外部包
349 | if strings.Contains(pkgName, ".") {
350 | return false
351 | }
352 |
353 | // 检查是否是标准库包名
354 | stdLibPackages := map[string]bool{
355 | "fmt": true, "os": true, "io": true, "net": true, "http": true,
356 | "time": true, "strings": true, "bytes": true, "bufio": true,
357 | "encoding": true, "json": true, "xml": true, "base64": true,
358 | "crypto": true, "errors": true, "flag": true, "log": true,
359 | "math": true, "rand": true, "reflect": true, "regexp": true,
360 | "runtime": true, "sort": true, "strconv": true, "sync": true,
361 | "syscall": true, "testing": true, "unicode": true, "unsafe": true,
362 | "context": true, "database": true, "sql": true, "path": true,
363 | "filepath": true, "template": true, "text": true, "html": true,
364 | "url": true, "ioutil": true, "atomic": true, "binary": true,
365 | }
366 |
367 | if stdLibPackages[pkgName] {
368 | return false
369 | }
370 |
371 | // 其他情况认为是项目内部包
372 | return true
373 | }
374 |
375 | // buildObfuscationMaps 构建混淆映射(旧版本,保留用于向后兼容)
376 | func (o *Obfuscator) buildObfuscationMaps() {
377 | filepath.Walk(o.projectRoot, func(path string, info os.FileInfo, err error) error {
378 | if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") {
379 | return err
380 | }
381 | if strings.Contains(path, "vendor/") {
382 | return nil
383 | }
384 |
385 | // 跳过排除的文件
386 | if _, excluded := o.skippedFiles[path]; excluded {
387 | return nil
388 | }
389 |
390 | node, err := parser.ParseFile(o.fset, path, nil, parser.ParseComments)
391 | if err != nil {
392 | return nil
393 | }
394 |
395 | // 只收集包级别的函数和变量
396 | // 不收集局部变量以避免作用域冲突
397 | for _, decl := range node.Decls {
398 | switch d := decl.(type) {
399 | case *ast.FuncDecl:
400 | // 收集函数名(跳过方法)
401 | if d.Recv == nil && !o.shouldProtect(d.Name.Name) {
402 | // 根据配置决定是否混淆导出函数
403 | if !isExported(d.Name.Name) || o.Config.ObfuscateExported {
404 | o.obfuscateName(d.Name.Name, true)
405 | }
406 | }
407 |
408 | case *ast.GenDecl:
409 | // 只处理包级别的变量和常量声明
410 | if d.Tok == token.VAR || d.Tok == token.CONST {
411 | for _, spec := range d.Specs {
412 | if valueSpec, ok := spec.(*ast.ValueSpec); ok {
413 | for _, name := range valueSpec.Names {
414 | if !o.shouldProtect(name.Name) {
415 | if !isExported(name.Name) || o.Config.ObfuscateExported {
416 | o.obfuscateName(name.Name, false)
417 | }
418 | }
419 | }
420 | }
421 | }
422 | }
423 | }
424 | }
425 |
426 | return nil
427 | })
428 | }
429 |
430 | // buildScopeAnalysis 为所有文件构建作用域分析
431 | func (o *Obfuscator) buildScopeAnalysis() error {
432 | return filepath.Walk(o.projectRoot, func(path string, info os.FileInfo, err error) error {
433 | if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") {
434 | return err
435 | }
436 | if strings.Contains(path, "vendor/") {
437 | return nil
438 | }
439 |
440 | // 跳过排除的文件
441 | if _, excluded := o.skippedFiles[path]; excluded {
442 | return nil
443 | }
444 |
445 | node, err := parser.ParseFile(o.fset, path, nil, parser.ParseComments)
446 | if err != nil {
447 | return nil
448 | }
449 |
450 | // 创建作用域分析器并分析文件
451 | analyzer := NewScopeAnalyzer(o.fset)
452 | analyzer.Analyze(node)
453 | o.fileScopes[path] = analyzer
454 |
455 | return nil
456 | })
457 | }
458 |
459 | // buildObfuscationMapsWithScope 使用作用域分析构建混淆映射
460 | // 修复版本:为每个对象独立生成混淆名,避免同名冲突
461 | func (o *Obfuscator) buildObfuscationMapsWithScope() {
462 | // 第一步:收集所有文件中的包级别对象(不分组)
463 | var allPackageLevelObjects []*Object
464 |
465 | for filePath, analyzer := range o.fileScopes {
466 | fileScope := analyzer.GetFileScope()
467 | if fileScope == nil {
468 | log.Printf("警告: 文件 %s 没有文件作用域", filePath)
469 | continue
470 | }
471 |
472 | // 收集文件级别的对象,并记录文件路径
473 | for _, obj := range fileScope.Objects {
474 | if obj.Kind == ObjFunc || obj.Kind == ObjVar || obj.Kind == ObjConst {
475 | // 为对象添加文件路径信息(用于调试)
476 | if obj.FilePath == "" {
477 | obj.FilePath = filePath
478 | }
479 | allPackageLevelObjects = append(allPackageLevelObjects, obj)
480 | }
481 | // 跳过类型定义 (ObjType)
482 | }
483 |
484 | // 收集局部作用域的对象
485 | o.collectObjectsForObfuscation(fileScope)
486 | }
487 |
488 | // 第二步:按名称分组对象(方案1 + build-tag支持)
489 | // 同名的对象将使用相同的混淆名(支持build-tag场景)
490 | nameToObjects := make(map[string][]*Object)
491 | for _, obj := range allPackageLevelObjects {
492 | nameToObjects[obj.Name] = append(nameToObjects[obj.Name], obj)
493 | }
494 |
495 | // 第三步:为每个名称生成混淆名,同名对象使用相同的混淆名
496 | funcCount := 0
497 | varCount := 0
498 | nameCount := make(map[string]int) // 用于后续的同步逻辑
499 |
500 | for name, objects := range nameToObjects {
501 | if len(objects) == 0 {
502 | continue
503 | }
504 |
505 | // 检查是否应该保护
506 | if o.shouldProtect(name) {
507 | continue
508 | }
509 |
510 | // 检查是否应该混淆导出的名称
511 | firstObj := objects[0]
512 | if firstObj.IsExported && !o.Config.ObfuscateExported {
513 | continue
514 | }
515 |
516 | // ✅ 方案1:为所有同名对象生成相同的混淆名
517 | // 这样可以支持build-tag场景(不同文件的同名函数)
518 | obfName := o.generateUniqueObfuscatedNameForObject(firstObj)
519 |
520 | // 将相同的混淆名应用到所有同名对象
521 | for _, obj := range objects {
522 | o.objectMapping[obj] = obfName
523 | }
524 |
525 | // 记录名称计数(用于后续同步)
526 | nameCount[name] = len(objects)
527 |
528 | // 统计
529 | if firstObj.Kind == ObjFunc {
530 | funcCount++
531 | } else if firstObj.Kind == ObjVar || firstObj.Kind == ObjConst {
532 | varCount++
533 | }
534 |
535 | // 如果有多个同名对象,打印日志
536 | if len(objects) > 1 {
537 | log.Printf("同名对象使用相同混淆名: %s → %s (在 %d 个文件中定义)", name, obfName, len(objects))
538 | }
539 | }
540 |
541 | // 第三步:为局部变量生成混淆名称(每个对象独立生成)
542 | localVarCount := 0
543 | for obj, obfName := range o.objectMapping {
544 | // 如果已经有混淆名称,跳过
545 | if obfName != "" {
546 | continue
547 | }
548 |
549 | // 检查是否应该保护
550 | if o.shouldProtectObject(obj) {
551 | delete(o.objectMapping, obj)
552 | continue
553 | }
554 |
555 | // 为局部变量生成唯一的混淆名称
556 | obfuscatedName := o.generateUniqueObfuscatedNameForObject(obj)
557 | o.objectMapping[obj] = obfuscatedName
558 | localVarCount++
559 | }
560 |
561 | // 同步到funcMapping和varMapping(方案1版本):
562 | // 所有包级别的名称都同步(包括同名的,因为它们现在使用相同的混淆名)
563 | syncCount := 0
564 | for name, objects := range nameToObjects {
565 | if len(objects) == 0 {
566 | continue
567 | }
568 | firstObj := objects[0]
569 | obfName, exists := o.objectMapping[firstObj]
570 | if !exists || obfName == "" {
571 | continue
572 | }
573 |
574 | // 同步到funcMapping/varMapping
575 | if firstObj.Kind == ObjFunc {
576 | o.funcMapping[name] = obfName
577 | syncCount++
578 | } else if firstObj.Kind == ObjVar || firstObj.Kind == ObjConst {
579 | o.varMapping[name] = obfName
580 | syncCount++
581 | }
582 | }
583 |
584 | log.Printf("收集到 %d 个包级别名称(函数: %d, 变量: %d),%d 个局部变量",
585 | len(nameToObjects), funcCount, varCount, localVarCount)
586 | log.Printf("同步了 %d 个名称到名称映射(用于跨文件引用)", syncCount)
587 | }
588 |
589 | // collectObjectsForObfuscation 递归收集作用域中需要混淆的对象
590 | func (o *Obfuscator) collectObjectsForObfuscation(scope *Scope) {
591 | // 收集当前作用域的对象
592 | for _, obj := range scope.Objects {
593 | // 跳过类型定义,因为类型名可能在多个地方被引用
594 | if obj.Kind == ObjType {
595 | continue
596 | }
597 |
598 | // 判断是否为文件级别:检查是否为文件作用域(通过检查节点类型)
599 | isFileLevel := false
600 | if scope.Node != nil {
601 | _, isFileLevel = scope.Node.(*ast.File)
602 | }
603 |
604 | if isFileLevel {
605 | // 文件级别的声明(包级别)
606 | if obj.Kind == ObjFunc || obj.Kind == ObjVar || obj.Kind == ObjConst {
607 | o.objectMapping[obj] = "" // 先标记,稍后生成名称
608 | }
609 | } else {
610 | // 局部作用域的变量(函数内部、块内部等)
611 | // 也收集局部变量进行混淆
612 | if obj.Kind == ObjVar || obj.Kind == ObjConst {
613 | o.objectMapping[obj] = "" // 先标记,稍后生成名称
614 | }
615 | }
616 | }
617 |
618 | // 递归处理子作用域
619 | for _, child := range scope.Children {
620 | o.collectObjectsForObfuscation(child)
621 | }
622 | }
623 |
624 | // shouldProtectObject 检查对象是否应该被保护
625 | func (o *Obfuscator) shouldProtectObject(obj *Object) bool {
626 | return o.shouldProtect(obj.Name)
627 | }
628 |
629 | // generateObfuscatedNameForObject 为对象生成混淆名称
630 | func (o *Obfuscator) generateObfuscatedNameForObject(obj *Object) string {
631 | // 检查是否为导出名称(首字母大写)
632 | isExported := obj.IsExported
633 |
634 | // 根据对象类型和是否导出选择前缀
635 | var prefix string
636 | if isExported {
637 | // 导出名称:使用大写前缀
638 | if obj.Kind == ObjFunc {
639 | prefix = "Fn" // 导出函数
640 | } else if obj.Kind == ObjConst {
641 | prefix = "C" // 导出常量
642 | } else if obj.Kind == ObjVar {
643 | prefix = "V" // 导出变量
644 | } else {
645 | prefix = "X" // 其他导出对象
646 | }
647 | } else {
648 | // 私有名称:使用小写前缀
649 | if obj.Kind == ObjFunc {
650 | prefix = "fn" // 私有函数
651 | } else if obj.Kind == ObjConst {
652 | prefix = "c" // 私有常量
653 | } else if obj.Kind == ObjVar {
654 | prefix = "v" // 私有变量
655 | } else {
656 | prefix = "l" // 其他私有对象
657 | }
658 | }
659 |
660 | // 生成唯一的混淆名称
661 | maxAttempts := 100
662 | for attempt := 0; attempt < maxAttempts; attempt++ {
663 | obf := fmt.Sprintf("%s%s", prefix, generateRandomString(12))
664 |
665 | // 检查此名称是否已被使用
666 | if !o.isObfuscatedNameUsed(obf) {
667 | return obf
668 | }
669 | }
670 |
671 | // 回退:使用基于计数器的方法
672 | o.namingCounter++
673 | return fmt.Sprintf("%s%d_%s", prefix, o.namingCounter, generateRandomString(8))
674 | }
675 |
676 | // isObfuscatedNameUsed 检查混淆名称是否已被使用
677 | func (o *Obfuscator) isObfuscatedNameUsed(name string) bool {
678 | // 检查对象映射
679 | for _, obfName := range o.objectMapping {
680 | if obfName == name {
681 | return true
682 | }
683 | }
684 |
685 | // 检查旧的映射(向后兼容)
686 | for _, obfName := range o.varMapping {
687 | if obfName == name {
688 | return true
689 | }
690 | }
691 | for _, obfName := range o.funcMapping {
692 | if obfName == name {
693 | return true
694 | }
695 | }
696 | for _, obfName := range o.importAliasMapping {
697 | if obfName == name {
698 | return true
699 | }
700 | }
701 |
702 | return false
703 | }
704 |
705 | // copyProject 复制项目到输出目录
706 | func (o *Obfuscator) copyProject() error {
707 | return filepath.Walk(o.projectRoot, func(path string, info os.FileInfo, err error) error {
708 | if err != nil {
709 | return err
710 | }
711 |
712 | relPath, err := filepath.Rel(o.projectRoot, path)
713 | if err != nil {
714 | return err
715 | }
716 |
717 | // 处理文件名混淆(只对未被排除的 Go 文件)
718 | outputPath := filepath.Join(o.outputDir, relPath)
719 | if o.Config.ObfuscateFileNames && strings.HasSuffix(path, ".go") {
720 | // 检查文件是否被排除
721 | _, isSkipped := o.skippedFiles[path]
722 | if !isSkipped {
723 | dir := filepath.Dir(outputPath)
724 | base := filepath.Base(outputPath)
725 | // 使用 obfuscateFileName 函数,它会保护 main.go 等特殊文件
726 | obfuscatedName := o.obfuscateFileName(base)
727 | if obfuscatedName != base {
728 | outputPath = filepath.Join(dir, obfuscatedName)
729 | }
730 | }
731 | }
732 |
733 | if info.IsDir() {
734 | return os.MkdirAll(outputPath, info.Mode())
735 | }
736 |
737 | // 复制文件(包括被排除的文件)
738 | return o.copyFile(path, outputPath)
739 | })
740 | }
741 |
742 | // copyProjectAndBuildMapping 复制项目到输出目录并构建文件映射
743 | func (o *Obfuscator) copyProjectAndBuildMapping(fileMapping map[string]string) error {
744 | return filepath.Walk(o.projectRoot, func(path string, info os.FileInfo, err error) error {
745 | if err != nil {
746 | return err
747 | }
748 |
749 | relPath, err := filepath.Rel(o.projectRoot, path)
750 | if err != nil {
751 | return err
752 | }
753 |
754 | // 处理文件名混淆(只对未被排除的 Go 文件)
755 | outputPath := filepath.Join(o.outputDir, relPath)
756 | if o.Config.ObfuscateFileNames && strings.HasSuffix(path, ".go") {
757 | // 检查文件是否被排除
758 | _, isSkipped := o.skippedFiles[path]
759 | if !isSkipped {
760 | dir := filepath.Dir(outputPath)
761 | base := filepath.Base(outputPath)
762 | // 使用 obfuscateFileName 函数,它会保护 main.go 等特殊文件
763 | obfuscatedName := o.obfuscateFileName(base)
764 | if obfuscatedName != base {
765 | outputPath = filepath.Join(dir, obfuscatedName)
766 | }
767 | }
768 | }
769 |
770 | // 记录映射关系(混淆后路径 -> 原始路径)
771 | if strings.HasSuffix(path, ".go") {
772 | fileMapping[outputPath] = path
773 | }
774 |
775 | if info.IsDir() {
776 | return os.MkdirAll(outputPath, info.Mode())
777 | }
778 |
779 | // 复制文件(包括被排除的文件)
780 | return o.copyFile(path, outputPath)
781 | })
782 | }
783 |
784 | // copyFile 复制单个文件
785 | func (o *Obfuscator) copyFile(src, dst string) error {
786 | srcFile, err := os.Open(src)
787 | if err != nil {
788 | return err
789 | }
790 | defer srcFile.Close()
791 |
792 | if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
793 | return err
794 | }
795 |
796 | dstFile, err := os.Create(dst)
797 | if err != nil {
798 | return err
799 | }
800 | defer dstFile.Close()
801 |
802 | _, err = io.Copy(dstFile, srcFile)
803 | return err
804 | }
805 |
806 | // obfuscateFile 混淆单个文件
807 | func (o *Obfuscator) obfuscateFile(filePath string) error {
808 | // 解析文件
809 | node, err := parser.ParseFile(o.fset, filePath, nil, parser.ParseComments)
810 | if err != nil {
811 | return fmt.Errorf("解析文件失败: %v", err)
812 | }
813 |
814 | // 移除注释(保留构建标签和编译指令)
815 | if o.Config.RemoveComments {
816 | var filteredComments []*ast.CommentGroup
817 | for _, cg := range node.Comments {
818 | var keepComments []*ast.Comment
819 | for _, c := range cg.List {
820 | if o.shouldKeepComment(c.Text) {
821 | keepComments = append(keepComments, c)
822 | }
823 | }
824 | if len(keepComments) > 0 {
825 | cg.List = keepComments
826 | filteredComments = append(filteredComments, cg)
827 | }
828 | }
829 | node.Comments = filteredComments
830 |
831 | // 清除文档注释
832 | for _, decl := range node.Decls {
833 | if genDecl, ok := decl.(*ast.GenDecl); ok {
834 | genDecl.Doc = nil
835 | for _, spec := range genDecl.Specs {
836 | if typeSpec, ok := spec.(*ast.TypeSpec); ok {
837 | typeSpec.Doc = nil
838 | typeSpec.Comment = nil
839 | }
840 | if valueSpec, ok := spec.(*ast.ValueSpec); ok {
841 | valueSpec.Doc = nil
842 | valueSpec.Comment = nil
843 | }
844 | if importSpec, ok := spec.(*ast.ImportSpec); ok {
845 | importSpec.Doc = nil
846 | importSpec.Comment = nil
847 | }
848 | }
849 | }
850 | if funcDecl, ok := decl.(*ast.FuncDecl); ok {
851 | funcDecl.Doc = nil
852 | }
853 | }
854 | }
855 |
856 | // 获取原始文件路径(从输出目录映射回项目根目录)
857 | relPath, _ := filepath.Rel(o.outputDir, filePath)
858 | originalPath := filepath.Join(o.projectRoot, relPath)
859 |
860 | // 应用转换(使用作用域信息)
861 | o.applyTransformationsWithScope(node, originalPath)
862 |
863 | // 格式化并写入
864 | var buf bytes.Buffer
865 | if err := format.Node(&buf, o.fset, node); err != nil {
866 | return fmt.Errorf("格式化失败: %v", err)
867 | }
868 |
869 | source := buf.String()
870 |
871 | // 字符串加密
872 | if o.Config.EncryptStrings {
873 | hadEncryption := o.encryptStringsInAST(node)
874 |
875 | if hadEncryption {
876 | // 先加密字符串字面量(使用解密包的函数)
877 | var actuallyEncrypted bool
878 | source, actuallyEncrypted = o.encryptStringsInSourceWithPackage(source)
879 |
880 | // 只有在真正加密了字符串时才添加解密包导入
881 | if actuallyEncrypted {
882 | source = o.ensureDecryptPackageImport(source)
883 | }
884 | }
885 | }
886 |
887 | // 写回文件
888 | return ioutil.WriteFile(filePath, []byte(source), 0644)
889 | }
890 |
891 | // obfuscateFileWithMapping 使用文件映射混淆单个文件
892 | func (o *Obfuscator) obfuscateFileWithMapping(filePath string, fileMapping map[string]string) error {
893 | // 解析文件
894 | node, err := parser.ParseFile(o.fset, filePath, nil, parser.ParseComments)
895 | if err != nil {
896 | return fmt.Errorf("解析文件失败: %v", err)
897 | }
898 |
899 | // 移除注释(保留构建标签和编译指令)
900 | if o.Config.RemoveComments {
901 | var filteredComments []*ast.CommentGroup
902 | for _, cg := range node.Comments {
903 | var keepComments []*ast.Comment
904 | for _, c := range cg.List {
905 | if o.shouldKeepComment(c.Text) {
906 | keepComments = append(keepComments, c)
907 | }
908 | }
909 | if len(keepComments) > 0 {
910 | cg.List = keepComments
911 | filteredComments = append(filteredComments, cg)
912 | }
913 | }
914 | node.Comments = filteredComments
915 |
916 | // 清除文档注释
917 | for _, decl := range node.Decls {
918 | if genDecl, ok := decl.(*ast.GenDecl); ok {
919 | genDecl.Doc = nil
920 | for _, spec := range genDecl.Specs {
921 | if typeSpec, ok := spec.(*ast.TypeSpec); ok {
922 | typeSpec.Doc = nil
923 | typeSpec.Comment = nil
924 | }
925 | if valueSpec, ok := spec.(*ast.ValueSpec); ok {
926 | valueSpec.Doc = nil
927 | valueSpec.Comment = nil
928 | }
929 | if importSpec, ok := spec.(*ast.ImportSpec); ok {
930 | importSpec.Doc = nil
931 | importSpec.Comment = nil
932 | }
933 | }
934 | }
935 | if funcDecl, ok := decl.(*ast.FuncDecl); ok {
936 | funcDecl.Doc = nil
937 | }
938 | }
939 | }
940 |
941 | // 从文件映射获取原始文件路径
942 | originalPath, exists := fileMapping[filePath]
943 | if !exists {
944 | // 如果映射中没有,尝试从输出目录映射回项目根目录
945 | relPath, _ := filepath.Rel(o.outputDir, filePath)
946 | originalPath = filepath.Join(o.projectRoot, relPath)
947 | }
948 |
949 | // 应用转换(使用作用域信息)
950 | o.applyTransformationsWithScope(node, originalPath)
951 |
952 | // 格式化并写入
953 | var buf bytes.Buffer
954 | if err := format.Node(&buf, o.fset, node); err != nil {
955 | return fmt.Errorf("格式化失败: %v", err)
956 | }
957 |
958 | source := buf.String()
959 |
960 | // 字符串加密
961 | if o.Config.EncryptStrings {
962 | hadEncryption := o.encryptStringsInAST(node)
963 |
964 | if hadEncryption {
965 | // 先加密字符串字面量(使用解密包的函数)
966 | var actuallyEncrypted bool
967 | source, actuallyEncrypted = o.encryptStringsInSourceWithPackage(source)
968 |
969 | // 只有在真正加密了字符串时才添加解密包导入
970 | if actuallyEncrypted {
971 | source = o.ensureDecryptPackageImport(source)
972 | }
973 | }
974 | }
975 |
976 | // 写回文件
977 | return ioutil.WriteFile(filePath, []byte(source), 0644)
978 | }
979 |
980 | // applyTransformations 应用 AST 转换
981 | func (o *Obfuscator) applyTransformations(node *ast.File) {
982 | // 步骤 1: 构建此文件中的包映射
983 | filePackages := make(map[string]string) // 代码中的包名 -> 混淆别名
984 |
985 | // 更新导入语句并记录包
986 | for _, decl := range node.Decls {
987 | if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.IMPORT {
988 | for _, spec := range genDecl.Specs {
989 | if importSpec, ok := spec.(*ast.ImportSpec); ok {
990 | if importSpec.Path != nil {
991 | // 跳过空白导入和点导入
992 | if importSpec.Name != nil && (importSpec.Name.Name == "_" || importSpec.Name.Name == ".") {
993 | continue
994 | }
995 |
996 | pkgPath := strings.Trim(importSpec.Path.Value, `"`)
997 |
998 | // 确定代码中使用的包名
999 | var pkgNameInCode string
1000 | if importSpec.Name != nil {
1001 | pkgNameInCode = importSpec.Name.Name
1002 | } else {
1003 | pkgNameInCode = filepath.Base(pkgPath)
1004 | }
1005 |
1006 | // 只为标准库应用别名
1007 | if alias, exists := o.importAliasMapping[pkgPath]; exists {
1008 | // 更新导入语句
1009 | if importSpec.Name != nil {
1010 | importSpec.Name.Name = alias
1011 | } else {
1012 | importSpec.Name = &ast.Ident{Name: alias}
1013 | }
1014 | // 记录此包供后续使用
1015 | filePackages[pkgNameInCode] = alias
1016 | }
1017 | }
1018 | }
1019 | }
1020 | }
1021 | }
1022 |
1023 | // 步骤 2: 替换包引用(仅限此文件中导入的包)
1024 | ast.Inspect(node, func(n ast.Node) bool {
1025 | if sel, ok := n.(*ast.SelectorExpr); ok {
1026 | if ident, ok := sel.X.(*ast.Ident); ok {
1027 | // 只有当满足以下条件时才替换:
1028 | // 1. 此包名在此文件中被导入
1029 | // 2. 标识符没有 Object(包标识符没有设置 Obj)
1030 | // 如果 Obj 不为 nil,说明它是局部声明的变量/参数
1031 | if alias, exists := filePackages[ident.Name]; exists {
1032 | if ident.Obj == nil {
1033 | ident.Name = alias
1034 | }
1035 | }
1036 | }
1037 | }
1038 | return true
1039 | })
1040 |
1041 | // 步骤 3: 混淆函数声明
1042 | ast.Inspect(node, func(n ast.Node) bool {
1043 | if fn, ok := n.(*ast.FuncDecl); ok {
1044 | if fn.Recv == nil && !o.shouldProtect(fn.Name.Name) {
1045 | if obf, exists := o.funcMapping[fn.Name.Name]; exists {
1046 | fn.Name.Name = obf
1047 | }
1048 | }
1049 | }
1050 | return true
1051 | })
1052 |
1053 | // 步骤 4: 混淆变量声明和赋值
1054 | ast.Inspect(node, func(n ast.Node) bool {
1055 | switch x := n.(type) {
1056 | case *ast.ValueSpec:
1057 | for _, name := range x.Names {
1058 | if !o.shouldProtect(name.Name) {
1059 | if obf, exists := o.varMapping[name.Name]; exists {
1060 | name.Name = obf
1061 | }
1062 | }
1063 | }
1064 | case *ast.AssignStmt:
1065 | for _, lhs := range x.Lhs {
1066 | if ident, ok := lhs.(*ast.Ident); ok {
1067 | if !o.shouldProtect(ident.Name) {
1068 | if obf, exists := o.varMapping[ident.Name]; exists {
1069 | ident.Name = obf
1070 | }
1071 | }
1072 | }
1073 | }
1074 | case *ast.Ident:
1075 | // 混淆标识符引用
1076 | if !o.shouldProtect(x.Name) {
1077 | if obf, exists := o.varMapping[x.Name]; exists {
1078 | x.Name = obf
1079 | }
1080 | if obf, exists := o.funcMapping[x.Name]; exists {
1081 | x.Name = obf
1082 | }
1083 | }
1084 | }
1085 | return true
1086 | })
1087 |
1088 | // 步骤 5: 注入垃圾代码
1089 | if o.Config.InjectJunkCode {
1090 | for _, decl := range node.Decls {
1091 | if fn, ok := decl.(*ast.FuncDecl); ok {
1092 | if fn.Body != nil && len(fn.Body.List) > 0 && !o.shouldSkipJunkCodeInjection(fn) {
1093 | junkStmts := o.generateJunkStatements(false)
1094 | fn.Body.List = append(junkStmts, fn.Body.List...)
1095 | }
1096 | }
1097 | }
1098 | }
1099 | }
1100 |
1101 | // applyTransformationsWithScope 使用作用域信息应用 AST 转换
1102 | func (o *Obfuscator) applyTransformationsWithScope(node *ast.File, originalFilePath string) {
1103 | // 步骤 1: 构建此文件中的包映射
1104 | filePackages := make(map[string]string) // 代码中的包名 -> 混淆别名
1105 |
1106 | // 更新导入语句并记录包
1107 | for _, decl := range node.Decls {
1108 | if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.IMPORT {
1109 | for _, spec := range genDecl.Specs {
1110 | if importSpec, ok := spec.(*ast.ImportSpec); ok {
1111 | if importSpec.Path != nil {
1112 | // 跳过空白导入和点导入
1113 | if importSpec.Name != nil && (importSpec.Name.Name == "_" || importSpec.Name.Name == ".") {
1114 | continue
1115 | }
1116 |
1117 | pkgPath := strings.Trim(importSpec.Path.Value, `"`)
1118 |
1119 | // 确定代码中使用的包名
1120 | var pkgNameInCode string
1121 | if importSpec.Name != nil {
1122 | pkgNameInCode = importSpec.Name.Name
1123 | } else {
1124 | pkgNameInCode = filepath.Base(pkgPath)
1125 | }
1126 |
1127 | // 只为标准库应用别名
1128 | if alias, exists := o.importAliasMapping[pkgPath]; exists {
1129 | // 更新导入语句
1130 | if importSpec.Name != nil {
1131 | importSpec.Name.Name = alias
1132 | } else {
1133 | importSpec.Name = &ast.Ident{Name: alias}
1134 | }
1135 | // 记录此包供后续使用
1136 | filePackages[pkgNameInCode] = alias
1137 | }
1138 | }
1139 | }
1140 | }
1141 | }
1142 | }
1143 |
1144 | // 步骤 2: 替换包引用(仅限此文件中导入的包)
1145 | ast.Inspect(node, func(n ast.Node) bool {
1146 | if sel, ok := n.(*ast.SelectorExpr); ok {
1147 | if ident, ok := sel.X.(*ast.Ident); ok {
1148 | // 只有当满足以下条件时才替换:
1149 | // 1. 此包名在此文件中被导入
1150 | // 2. 标识符没有 Object(包标识符没有设置 Obj)
1151 | // 如果 Obj 不为 nil,说明它是局部声明的变量/参数
1152 | if alias, exists := filePackages[ident.Name]; exists {
1153 | if ident.Obj == nil {
1154 | ident.Name = alias
1155 | }
1156 | }
1157 | }
1158 | }
1159 | return true
1160 | })
1161 |
1162 | // 获取此文件的作用域分析器
1163 | analyzer, hasScope := o.fileScopes[originalFilePath]
1164 |
1165 | if hasScope {
1166 | // 步骤 3: 使用作用域信息混淆标识符
1167 | o.obfuscateIdentifiersWithScope(node, analyzer)
1168 | } else {
1169 | // 回退到旧的混淆方法(如果没有作用域信息)
1170 | log.Printf("警告: 文件 %s 没有作用域信息,使用旧的混淆方法", originalFilePath)
1171 |
1172 | // 步骤 3: 混淆函数声明
1173 | ast.Inspect(node, func(n ast.Node) bool {
1174 | if fn, ok := n.(*ast.FuncDecl); ok {
1175 | if fn.Recv == nil && !o.shouldProtect(fn.Name.Name) {
1176 | if obf, exists := o.funcMapping[fn.Name.Name]; exists {
1177 | fn.Name.Name = obf
1178 | }
1179 | }
1180 | }
1181 | return true
1182 | })
1183 |
1184 | // 步骤 4: 混淆变量声明和赋值
1185 | ast.Inspect(node, func(n ast.Node) bool {
1186 | switch x := n.(type) {
1187 | case *ast.ValueSpec:
1188 | for _, name := range x.Names {
1189 | if !o.shouldProtect(name.Name) {
1190 | if obf, exists := o.varMapping[name.Name]; exists {
1191 | name.Name = obf
1192 | }
1193 | }
1194 | }
1195 | case *ast.AssignStmt:
1196 | for _, lhs := range x.Lhs {
1197 | if ident, ok := lhs.(*ast.Ident); ok {
1198 | if !o.shouldProtect(ident.Name) {
1199 | if obf, exists := o.varMapping[ident.Name]; exists {
1200 | ident.Name = obf
1201 | }
1202 | }
1203 | }
1204 | }
1205 | case *ast.Ident:
1206 | // 混淆标识符引用
1207 | if !o.shouldProtect(x.Name) {
1208 | if obf, exists := o.varMapping[x.Name]; exists {
1209 | x.Name = obf
1210 | }
1211 | if obf, exists := o.funcMapping[x.Name]; exists {
1212 | x.Name = obf
1213 | }
1214 | }
1215 | }
1216 | return true
1217 | })
1218 | }
1219 |
1220 | // 步骤 5: 注入垃圾代码
1221 | if o.Config.InjectJunkCode {
1222 | for _, decl := range node.Decls {
1223 | if fn, ok := decl.(*ast.FuncDecl); ok {
1224 | if fn.Body != nil && len(fn.Body.List) > 0 && !o.shouldSkipJunkCodeInjection(fn) {
1225 | junkStmts := o.generateJunkStatements(false)
1226 | fn.Body.List = append(junkStmts, fn.Body.List...)
1227 | }
1228 | }
1229 | }
1230 | }
1231 | }
1232 |
1233 | // obfuscateIdentifiersWithScope 使用作用域信息混淆标识符
1234 | func (o *Obfuscator) obfuscateIdentifiersWithScope(node *ast.File, analyzer *ScopeAnalyzer) {
1235 | // 记录哪些标识符是类型引用,不应该被混淆
1236 | typeRefs := make(map[*ast.Ident]bool)
1237 |
1238 | // 第一遍:收集所有类型引用
1239 | ast.Inspect(node, func(n ast.Node) bool {
1240 | switch x := n.(type) {
1241 | case *ast.Field:
1242 | // 字段类型
1243 | o.markTypeIdents(x.Type, typeRefs)
1244 | case *ast.ValueSpec:
1245 | // 变量/常量类型
1246 | if x.Type != nil {
1247 | o.markTypeIdents(x.Type, typeRefs)
1248 | }
1249 | case *ast.TypeAssertExpr:
1250 | // 类型断言
1251 | if x.Type != nil {
1252 | o.markTypeIdents(x.Type, typeRefs)
1253 | }
1254 | case *ast.CallExpr:
1255 | // 类型转换:只有当Fun是类型标识符时才标记
1256 | // 例如:int(x), MyType(x)
1257 | // 但不包括函数调用:myFunc(x)
1258 | // 简单启发式:如果Fun是Ident且首字母小写,可能是内置类型转换
1259 | // 更准确的方法需要类型信息,这里我们跳过CallExpr的处理
1260 | // 因为大部分CallExpr是函数调用,不是类型转换
1261 | _ = x // 跳过
1262 | }
1263 | return true
1264 | })
1265 |
1266 | // 第二遍:替换标识符
1267 | ast.Inspect(node, func(n ast.Node) bool {
1268 | switch x := n.(type) {
1269 | case *ast.Ident:
1270 | // 跳过类型引用
1271 | if typeRefs[x] {
1272 | return true
1273 | }
1274 |
1275 | // 跳过保护的名称
1276 | if o.shouldProtect(x.Name) {
1277 | return true
1278 | }
1279 |
1280 | // ✅ 改进的替换策略(方案2 + 精确的作用域处理):
1281 | //
1282 | // 问题:简单的作用域查找无法区分"当前位置的作用域"和"文件级作用域"
1283 | // 例如:局部变量 transport 和 包级函数 transport 同名
1284 | //
1285 | // 解决方案:
1286 | // 1. 先尝试在文件级作用域查找(只查找包级对象,不查找子作用域)
1287 | // 2. 如果找到且在objectMapping中,使用它
1288 | // 3. 否则,使用funcMapping/varMapping(跨文件引用)
1289 | //
1290 | // 这样可以避免错误地匹配局部变量
1291 |
1292 | // 只在文件级作用域查找(不递归到子作用域)
1293 | fileScope := analyzer.GetFileScope()
1294 | var obj *Object
1295 | if fileScope != nil {
1296 | obj = fileScope.Objects[x.Name] // 直接查找,不递归
1297 | }
1298 |
1299 | if obj != nil {
1300 | // 跳过类型对象
1301 | if obj.Kind == ObjType {
1302 | return true
1303 | }
1304 | if obfName, hasObf := o.objectMapping[obj]; hasObf && obfName != "" {
1305 | x.Name = obfName
1306 | return true
1307 | }
1308 | }
1309 |
1310 | // 跨文件引用或找不到对象:使用名称映射
1311 | // funcMapping/varMapping 包含所有唯一名称(不包括同名的私有对象)
1312 | if obfName, exists := o.funcMapping[x.Name]; exists {
1313 | x.Name = obfName
1314 | return true
1315 | }
1316 | if obfName, exists := o.varMapping[x.Name]; exists {
1317 | x.Name = obfName
1318 | return true
1319 | }
1320 | }
1321 | return true
1322 | })
1323 | }
1324 |
1325 | // markTypeIdents 标记表达式中的所有类型标识符
1326 | func (o *Obfuscator) markTypeIdents(expr ast.Expr, typeRefs map[*ast.Ident]bool) {
1327 | if expr == nil {
1328 | return
1329 | }
1330 |
1331 | switch x := expr.(type) {
1332 | case *ast.Ident:
1333 | typeRefs[x] = true
1334 | case *ast.StarExpr:
1335 | o.markTypeIdents(x.X, typeRefs)
1336 | case *ast.ArrayType:
1337 | o.markTypeIdents(x.Elt, typeRefs)
1338 | case *ast.MapType:
1339 | o.markTypeIdents(x.Key, typeRefs)
1340 | o.markTypeIdents(x.Value, typeRefs)
1341 | case *ast.ChanType:
1342 | o.markTypeIdents(x.Value, typeRefs)
1343 | case *ast.SelectorExpr:
1344 | // 对于 pkg.Type,标记 Type
1345 | typeRefs[x.Sel] = true
1346 | case *ast.FuncType:
1347 | // 函数类型的参数和返回值
1348 | if x.Params != nil {
1349 | for _, field := range x.Params.List {
1350 | o.markTypeIdents(field.Type, typeRefs)
1351 | }
1352 | }
1353 | if x.Results != nil {
1354 | for _, field := range x.Results.List {
1355 | o.markTypeIdents(field.Type, typeRefs)
1356 | }
1357 | }
1358 | }
1359 | }
1360 |
1361 | // findObjectInScopeRecursive 递归查找对象
1362 | func (o *Obfuscator) findObjectInScopeRecursive(name string, scope *Scope) *Object {
1363 | if scope == nil {
1364 | return nil
1365 | }
1366 |
1367 | // 在当前作用域查找
1368 | if obj, exists := scope.Objects[name]; exists {
1369 | return obj
1370 | }
1371 |
1372 | // 在子作用域中查找
1373 | for _, child := range scope.Children {
1374 | if obj := o.findObjectInScopeRecursive(name, child); obj != nil {
1375 | return obj
1376 | }
1377 | }
1378 |
1379 | return nil
1380 | }
1381 |
1382 | // shouldKeepComment 判断是否应保留注释
1383 | func (o *Obfuscator) shouldKeepComment(text string) bool {
1384 | // 保留构建标签和编译指令
1385 | return strings.HasPrefix(text, "//go:") ||
1386 | strings.HasPrefix(text, "// +build") ||
1387 | strings.HasPrefix(text, "//+build")
1388 | }
1389 |
1390 | // hasPlatformSpecificBuildTag 检查文件是否有平台专用的 build tag 或文件名后缀
1391 | func (o *Obfuscator) hasPlatformSpecificBuildTag(node *ast.File) bool {
1392 | // 平台关键词列表
1393 | platformKeywords := []string{
1394 | "windows", "linux", "darwin", "freebsd", "openbsd",
1395 | "netbsd", "dragonfly", "solaris", "android", "aix",
1396 | "386", "amd64", "arm", "arm64", "mips", "mips64",
1397 | "ppc64", "ppc64le", "s390x", "wasm",
1398 | }
1399 |
1400 | // 检查所有注释
1401 | for _, cg := range node.Comments {
1402 | for _, c := range cg.List {
1403 | text := c.Text
1404 | // 检查 //go:build 和 // +build 标签
1405 | if strings.HasPrefix(text, "//go:build") || strings.HasPrefix(text, "// +build") || strings.HasPrefix(text, "//+build") {
1406 | // 检查是否包含平台关键词
1407 | lowerText := strings.ToLower(text)
1408 | for _, keyword := range platformKeywords {
1409 | if strings.Contains(lowerText, keyword) {
1410 | return true
1411 | }
1412 | }
1413 | }
1414 | }
1415 | }
1416 |
1417 | return false
1418 | }
1419 |
1420 | // isFilePlatformSpecific 检查文件是否平台特定(通过文件名后缀或build tag)
1421 | func (o *Obfuscator) isFilePlatformSpecific(filePath string) bool {
1422 | // 检查文件名后缀
1423 | if o.hasPlatformSpecificSuffix(filePath) {
1424 | return true
1425 | }
1426 |
1427 | // 检查build tag(需要解析文件)
1428 | node, err := parser.ParseFile(o.fset, filePath, nil, parser.ParseComments)
1429 | if err != nil {
1430 | return false // 解析失败,假设不是平台特定的
1431 | }
1432 |
1433 | return o.hasPlatformSpecificBuildTag(node)
1434 | }
1435 |
1436 | // hasPlatformSpecificSuffix 检查文件名是否有平台专用的后缀
1437 | func (o *Obfuscator) hasPlatformSpecificSuffix(filePath string) bool {
1438 | // 获取文件名(不含扩展名)
1439 | base := filepath.Base(filePath)
1440 | nameWithoutExt := strings.TrimSuffix(base, ".go")
1441 |
1442 | // 平台后缀列表
1443 | platformSuffixes := []string{
1444 | "_windows", "_linux", "_darwin", "_freebsd", "_openbsd",
1445 | "_netbsd", "_dragonfly", "_solaris", "_android", "_aix",
1446 | "_386", "_amd64", "_arm", "_arm64", "_mips", "_mips64",
1447 | "_ppc64", "_ppc64le", "_s390x", "_wasm",
1448 | // 组合后缀,如 _linux_amd64
1449 | "_windows_amd64", "_windows_386", "_windows_arm", "_windows_arm64",
1450 | "_linux_amd64", "_linux_386", "_linux_arm", "_linux_arm64",
1451 | "_darwin_amd64", "_darwin_arm64",
1452 | }
1453 |
1454 | // 检查文件名是否以任何平台后缀结尾
1455 | for _, suffix := range platformSuffixes {
1456 | if strings.HasSuffix(nameWithoutExt, suffix) {
1457 | return true
1458 | }
1459 | }
1460 |
1461 | return false
1462 | }
1463 |
1464 | // encryptStringsInAST 在 AST 中标记需要加密的字符串
1465 | func (o *Obfuscator) encryptStringsInAST(node *ast.File) bool {
1466 | hadEncryption := false
1467 |
1468 | // 收集需要跳过的字符串
1469 | skipLiterals := make(map[*ast.BasicLit]bool)
1470 |
1471 | // 标记导入路径
1472 | for _, imp := range node.Imports {
1473 | if imp.Path != nil {
1474 | skipLiterals[imp.Path] = true
1475 | }
1476 | }
1477 |
1478 | // 标记结构体标签
1479 | ast.Inspect(node, func(n ast.Node) bool {
1480 | if field, ok := n.(*ast.Field); ok {
1481 | if field.Tag != nil {
1482 | skipLiterals[field.Tag] = true
1483 | }
1484 | }
1485 | return true
1486 | })
1487 |
1488 | // 检查是否有字符串需要加密
1489 | ast.Inspect(node, func(n ast.Node) bool {
1490 | if lit, ok := n.(*ast.BasicLit); ok {
1491 | if skipLiterals[lit] {
1492 | return true
1493 | }
1494 | if lit.Kind == token.STRING && len(lit.Value) > 2 {
1495 | if !strings.HasPrefix(lit.Value, "`") {
1496 | hadEncryption = true
1497 | }
1498 | }
1499 | }
1500 | return true
1501 | })
1502 |
1503 | return hadEncryption
1504 | }
1505 |
1506 | // ensureBase64ImportInSource 确保源代码中有 base64 导入
1507 | func (o *Obfuscator) ensureBase64ImportInSource(source string) string {
1508 | if strings.Contains(source, `"encoding/base64"`) {
1509 | return source
1510 | }
1511 |
1512 | lines := strings.Split(source, "\n")
1513 | for i, line := range lines {
1514 | if strings.HasPrefix(strings.TrimSpace(line), "import (") {
1515 | // 在 import 块中添加
1516 | lines[i] = line + "\n\t\"encoding/base64\""
1517 | return strings.Join(lines, "\n")
1518 | }
1519 | if strings.HasPrefix(strings.TrimSpace(line), "import ") {
1520 | // 单行 import,转换为块
1521 | lines[i] = "import (\n\t\"encoding/base64\"\n" + strings.TrimPrefix(line, "import ") + "\n)"
1522 | return strings.Join(lines, "\n")
1523 | }
1524 | }
1525 |
1526 | // 没有找到 import,在 package 后添加
1527 | for i, line := range lines {
1528 | if strings.HasPrefix(strings.TrimSpace(line), "package ") {
1529 | lines = append(lines[:i+1], append([]string{"\nimport \"encoding/base64\"\n"}, lines[i+1:]...)...)
1530 | return strings.Join(lines, "\n")
1531 | }
1532 | }
1533 |
1534 | return source
1535 | }
1536 |
1537 | // encryptStringsInSource 在源代码中加密字符串
1538 | func (o *Obfuscator) encryptStringsInSource(source string) (string, bool) {
1539 | lines := strings.Split(source, "\n")
1540 | result := make([]string, len(lines))
1541 |
1542 | hadEncryption := false
1543 | inImportBlock := false
1544 | inConstBlock := false
1545 |
1546 | for i, line := range lines {
1547 | trimmed := strings.TrimSpace(line)
1548 |
1549 | // 跟踪 import 块
1550 | if strings.HasPrefix(trimmed, "import (") {
1551 | inImportBlock = true
1552 | result[i] = line
1553 | continue
1554 | }
1555 | if inImportBlock && trimmed == ")" {
1556 | inImportBlock = false
1557 | result[i] = line
1558 | continue
1559 | }
1560 |
1561 | // 跟踪 const 块
1562 | if strings.HasPrefix(trimmed, "const (") {
1563 | inConstBlock = true
1564 | result[i] = line
1565 | continue
1566 | }
1567 | if inConstBlock && trimmed == ")" {
1568 | inConstBlock = false
1569 | result[i] = line
1570 | continue
1571 | }
1572 |
1573 | // 跳过特殊行
1574 | if inImportBlock || inConstBlock || strings.Contains(trimmed, "package ") ||
1575 | strings.HasPrefix(trimmed, "import ") || strings.HasPrefix(trimmed, "const ") {
1576 | result[i] = line
1577 | continue
1578 | }
1579 |
1580 | // 跳过结构体标签
1581 | if strings.Contains(line, "`") && strings.Contains(line, ":") {
1582 | result[i] = line
1583 | continue
1584 | }
1585 |
1586 | // 处理行
1587 | newLine := line
1588 | inString := false
1589 | inRune := false
1590 | escaped := false
1591 | stringStart := -1
1592 |
1593 | for j := 0; j < len(line); j++ {
1594 | ch := line[j]
1595 |
1596 | if escaped {
1597 | escaped = false
1598 | continue
1599 | }
1600 |
1601 | if ch == '\\' {
1602 | escaped = true
1603 | continue
1604 | }
1605 |
1606 | // 处理单引号(rune)
1607 | if ch == '\'' && !inString {
1608 | if !inRune {
1609 | inRune = true
1610 | } else {
1611 | inRune = false
1612 | }
1613 | continue
1614 | }
1615 |
1616 | // 只处理双引号
1617 | if ch == '"' && !inRune {
1618 | if !inString {
1619 | inString = true
1620 | stringStart = j
1621 | } else {
1622 | inString = false
1623 | if stringStart >= 0 {
1624 | strWithQuotes := line[stringStart : j+1]
1625 | if len(strWithQuotes) > 4 {
1626 | strContent := strWithQuotes[1 : len(strWithQuotes)-1]
1627 | if len(strContent) > 2 && !strings.Contains(strContent, "\\") {
1628 | encrypted := o.encryptString(strContent)
1629 | replacement := fmt.Sprintf(`%s("%s")`, o.decryptFuncName, encrypted)
1630 | newLine = strings.Replace(newLine, strWithQuotes, replacement, 1)
1631 | hadEncryption = true
1632 | }
1633 | }
1634 | }
1635 | stringStart = -1
1636 | }
1637 | }
1638 | }
1639 |
1640 | result[i] = newLine
1641 | }
1642 |
1643 | return strings.Join(result, "\n"), hadEncryption
1644 | }
1645 |
1646 | // createDecryptPackage 创建独立的解密包
1647 | func (o *Obfuscator) createDecryptPackage() error {
1648 | if o.decryptPkgCreated {
1649 | return nil
1650 | }
1651 |
1652 | // 设置解密包路径(在输出目录下,而不是原始项目目录)
1653 | decryptPkgDir := filepath.Join(o.outputDir, o.decryptPkgName)
1654 | if err := os.MkdirAll(decryptPkgDir, 0755); err != nil {
1655 | return fmt.Errorf("创建解密包目录失败: %v", err)
1656 | }
1657 |
1658 | // 生成解密包的内容
1659 | keyBytes := []byte(o.encryptionKey)
1660 | keyLiteral := "[]byte{"
1661 | for i, b := range keyBytes {
1662 | if i > 0 {
1663 | keyLiteral += ", "
1664 | }
1665 | keyLiteral += fmt.Sprintf("%d", b)
1666 | }
1667 | keyLiteral += "}"
1668 |
1669 | // 创建解密包文件(不包含任何暴露用途的注释)
1670 | decryptFileContent := fmt.Sprintf(`package %s
1671 |
1672 | import "encoding/base64"
1673 |
1674 | func %s(s string) string {
1675 | d, e := base64.StdEncoding.DecodeString(s)
1676 | if e != nil {
1677 | return ""
1678 | }
1679 | k := %s
1680 | r := make([]byte, len(d))
1681 | for i, b := range d {
1682 | r[i] = b ^ k[i%%len(k)]
1683 | }
1684 | return string(r)
1685 | }
1686 | `, o.decryptPkgName, o.decryptFuncName, keyLiteral)
1687 |
1688 | // 写入文件(使用随机文件名)
1689 | randomFileName := fmt.Sprintf("%s.go", generateRandomString(10))
1690 | decryptFilePath := filepath.Join(decryptPkgDir, randomFileName)
1691 | if err := ioutil.WriteFile(decryptFilePath, []byte(decryptFileContent), 0644); err != nil {
1692 | return fmt.Errorf("写入解密文件失败: %v", err)
1693 | }
1694 |
1695 | // 读取go.mod获取模块名(从原始项目目录读取)
1696 | goModPath := filepath.Join(o.projectRoot, "go.mod")
1697 | goModContent, err := ioutil.ReadFile(goModPath)
1698 | if err != nil {
1699 | return fmt.Errorf("读取go.mod失败: %v", err)
1700 | }
1701 |
1702 | // 解析模块名
1703 | moduleName := ""
1704 | lines := strings.Split(string(goModContent), "\n")
1705 | for _, line := range lines {
1706 | line = strings.TrimSpace(line)
1707 | if strings.HasPrefix(line, "module ") {
1708 | moduleName = strings.TrimSpace(strings.TrimPrefix(line, "module"))
1709 | break
1710 | }
1711 | }
1712 |
1713 | if moduleName == "" {
1714 | return fmt.Errorf("无法从go.mod中获取模块名")
1715 | }
1716 |
1717 | // 设置解密包的导入路径
1718 | o.decryptPkgPath = moduleName + "/" + o.decryptPkgName
1719 | o.decryptPkgCreated = true
1720 |
1721 | // 保护解密函数名称和包名,防止被混淆
1722 | o.protectedNames[o.decryptFuncName] = true
1723 | o.packageNames[o.decryptPkgName] = true
1724 |
1725 | log.Printf("✅ 创建解密包: %s (导入路径: %s, 函数名: %s)", decryptPkgDir, o.decryptPkgPath, o.decryptFuncName)
1726 | return nil
1727 | }
1728 |
1729 | // ensureDecryptPackageImport 确保源代码中导入了解密包
1730 | func (o *Obfuscator) ensureDecryptPackageImport(source string) string {
1731 | importLine := fmt.Sprintf(`%s "%s"`, o.decryptPkgName, o.decryptPkgPath)
1732 |
1733 | // 检查是否已经导入
1734 | if strings.Contains(source, o.decryptPkgPath) {
1735 | return source
1736 | }
1737 |
1738 | lines := strings.Split(source, "\n")
1739 | for i, line := range lines {
1740 | if strings.HasPrefix(strings.TrimSpace(line), "import (") {
1741 | // 在 import 块中添加
1742 | lines[i] = line + "\n\t" + importLine
1743 | return strings.Join(lines, "\n")
1744 | }
1745 | if strings.HasPrefix(strings.TrimSpace(line), "import ") {
1746 | // 单行 import,转换为块
1747 | lines[i] = "import (\n\t" + importLine + "\n" + strings.TrimPrefix(line, "import ") + "\n)"
1748 | return strings.Join(lines, "\n")
1749 | }
1750 | }
1751 |
1752 | // 没有找到 import,在 package 后添加
1753 | for i, line := range lines {
1754 | if strings.HasPrefix(strings.TrimSpace(line), "package ") {
1755 | lines = append(lines[:i+1], append([]string{"\nimport " + importLine + "\n"}, lines[i+1:]...)...)
1756 | return strings.Join(lines, "\n")
1757 | }
1758 | }
1759 |
1760 | return source
1761 | }
1762 |
1763 | // encryptStringsInSourceWithPackage 在源代码中加密字符串(使用解密包)
1764 | func (o *Obfuscator) encryptStringsInSourceWithPackage(source string) (string, bool) {
1765 | lines := strings.Split(source, "\n")
1766 | result := make([]string, len(lines))
1767 |
1768 | hadEncryption := false
1769 | inImportBlock := false
1770 | inConstBlock := false
1771 |
1772 | for i, line := range lines {
1773 | trimmed := strings.TrimSpace(line)
1774 |
1775 | // 跟踪 import 块
1776 | if strings.HasPrefix(trimmed, "import (") {
1777 | inImportBlock = true
1778 | result[i] = line
1779 | continue
1780 | }
1781 | if inImportBlock && trimmed == ")" {
1782 | inImportBlock = false
1783 | result[i] = line
1784 | continue
1785 | }
1786 |
1787 | // 跟踪 const 块
1788 | if strings.HasPrefix(trimmed, "const (") {
1789 | inConstBlock = true
1790 | result[i] = line
1791 | continue
1792 | }
1793 | if inConstBlock && trimmed == ")" {
1794 | inConstBlock = false
1795 | result[i] = line
1796 | continue
1797 | }
1798 |
1799 | // 跳过特殊行
1800 | if inImportBlock || inConstBlock || strings.Contains(trimmed, "package ") ||
1801 | strings.HasPrefix(trimmed, "import ") || strings.HasPrefix(trimmed, "const ") {
1802 | result[i] = line
1803 | continue
1804 | }
1805 |
1806 | // 跳过结构体标签
1807 | if strings.Contains(line, "`") && strings.Contains(line, ":") {
1808 | result[i] = line
1809 | continue
1810 | }
1811 |
1812 | // 处理行
1813 | newLine := line
1814 | inString := false
1815 | inRune := false
1816 | escaped := false
1817 | stringStart := -1
1818 |
1819 | for j := 0; j < len(line); j++ {
1820 | ch := line[j]
1821 |
1822 | if escaped {
1823 | escaped = false
1824 | continue
1825 | }
1826 |
1827 | if ch == '\\' {
1828 | escaped = true
1829 | continue
1830 | }
1831 |
1832 | // 处理单引号(rune)
1833 | if ch == '\'' && !inString {
1834 | if !inRune {
1835 | inRune = true
1836 | } else {
1837 | inRune = false
1838 | }
1839 | continue
1840 | }
1841 |
1842 | // 只处理双引号
1843 | if ch == '"' && !inRune {
1844 | if !inString {
1845 | inString = true
1846 | stringStart = j
1847 | } else {
1848 | inString = false
1849 | if stringStart >= 0 {
1850 | strWithQuotes := line[stringStart : j+1]
1851 | if len(strWithQuotes) > 4 {
1852 | strContent := strWithQuotes[1 : len(strWithQuotes)-1]
1853 | if len(strContent) > 2 && !strings.Contains(strContent, "\\") {
1854 | encrypted := o.encryptString(strContent)
1855 | // 使用解密包的函数: pkgName.FuncName("encrypted")
1856 | replacement := fmt.Sprintf(`%s.%s("%s")`, o.decryptPkgName, o.decryptFuncName, encrypted)
1857 | newLine = strings.Replace(newLine, strWithQuotes, replacement, 1)
1858 | hadEncryption = true
1859 | }
1860 | }
1861 | }
1862 | stringStart = -1
1863 | }
1864 | }
1865 | }
1866 |
1867 | result[i] = newLine
1868 | }
1869 |
1870 | return strings.Join(result, "\n"), hadEncryption
1871 | }
1872 |
--------------------------------------------------------------------------------