├── 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 | --------------------------------------------------------------------------------