diff --git a/Plugins/local/minidump/plugin.go b/Plugins/local/minidump/plugin.go index a6ba968..8501698 100644 --- a/Plugins/local/minidump/plugin.go +++ b/Plugins/local/minidump/plugin.go @@ -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进行内存转储...") + + // 创建一个带超时的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) - // 尝试使用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) - - return &base.ScanResult{ - Success: false, - Error: fmt.Errorf("所有转储方法都失败了"), - }, nil - } + 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,34 +591,21 @@ 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) + if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil { + 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)) - } - - common.LogDebug(fmt.Sprintf("PowerShell输出: %s", string(output))) - return nil -} // 插件注册函数 func init() {