Mimikatz:msv功能模块浅析

没学过逆向的web小白,起初想着在主机做弱口检测,但基本都是以爆破的方式实现。由于获取主机明文密码受系统版本和注册表配置影响,所以只能想到NTLM比对,那么NTLM怎么获取呢,一起看看mimikatz的msv功能模块吧

0x01 环境准备

Mimikatz源码:https://github.com/gentilkiwi/mimikatz
笔者调试环境:vs2022社区版
在将项目导入重新生成解决方案的时候可能会出现以下两个小问题:
1、类型强制转换”: 从“PVOID”到“DWORD”的指针截断报错
解决:在报错处修改将指针类型强行转换成DWORD
2、PRINTER_NOTIFY_CATEGORY_ALL”: 宏重定义
解决:将重复定义变量在报错处修改成重复定义变量的值即可
3、因官方项目默认没给出debug方案,所以需要手动添加debug配置
解决:

1.png

2.png

若遇其他问题可百度解决,具体没太记录
另外,若想做远程debug,可以在项目属性中这样配置

3.png

参考:https://blog.csdn.net/thebulesky/article/details/120852560

0x02 程序如何执行命令

同项目名c文件中的wmain()是整个mimikatz的入口函数

4.png

for(i = MIMIKATZ_AUTO_COMMAND_START ; (i < argc) && (status != STATUS_PROCESS_IS_TERMINATING) && (status != STATUS_THREAD_IS_TERMINATING) ; i++) 
{ 
    kprintf(L"\n" MIMIKATZ L"(" MIMIKATZ_AUTO_COMMAND_STRING L") # %s\n", argv[i]); 
    status = mimikatz_dispatchCommand(argv[i]); 
}

从上面的循环中可以看到执行文件获取到命令行参数后,会将命令传入mimikatz_dispatchCommand()函数,利用这个函数可以根据当前命令在不同场景下执行接下来的操作

5.png

例如:当我们想要做万能钥匙攻击的时候,键入!+将利用mimidrv.sys去执行当前命令的操作

6.png

否则通过mimikatz_doLocal()函数默认进入常用命令的场景

在获取到命令进行命令分发之后,将获取到modulecommand两个参数,之后就进入命令执行的阶段

7.png

先来看看mimikatz_modules数组中已定义的模块都有哪些,该数组里面存放的是每一个模块的结构体的指针。将module的值和每个模块结构体中所定义的shortName继续比较

const KUHL_M * mimikatz_modules[] = { 
&kuhl_m_standard, 
&kuhl_m_crypto, 
&kuhl_m_sekurlsa, 
&kuhl_m_kerberos, 
&kuhl_m_ngc, 
&kuhl_m_privilege, 
&kuhl_m_process, 
&kuhl_m_service, 
&kuhl_m_lsadump, 
&kuhl_m_ts, 
&kuhl_m_event, 
&kuhl_m_misc, 
&kuhl_m_token, 
&kuhl_m_vault, 
&kuhl_m_minesweeper, 
#if defined(NET_MODULE) 
&kuhl_m_net, 
#endif 
&kuhl_m_dpapi,
&kuhl_m_busylight,
&kuhl_m_sysenv, 
&kuhl_m_sid, 
&kuhl_m_iis, 
&kuhl_m_rpc, 
&kuhl_m_sr98, 
&kuhl_m_rdm, 
&kuhl_m_acr, 
};

然后也是以一样的方式,将command与模块结构体中的每个command做比较,去执行指定模块中的指定命令函数。例如我们做privilege::debug操作的时候,执行的函数是kuhl_m_privilege.c文件下调用的kuhl_m_privilege_simple()函数

NTSTATUS kuhl_m_privilege_debug(int argc, wchar_t * argv[]) 
{ 
return kuhl_m_privilege_simple(SE_DEBUG); 
}

而该函数将调用到系统的API

8.png

0x03 MSV模块

MSV介绍

msv功能模块的原理个人理解就是在lsasrv.dll这个模块中找到LogonSessionListLock()函数同时使用了LogonSessionListLogonSessionListCount两个变量作为参数,这个LogonSessionList中应该就保存当前活动的 Windows 登录会话列表。那么只要根据这个LogonSessionListLock()这个函数位置,加上偏移位置,就可以获取两个全局变量的位置。以windows1803为例,通俗理解:LogonSessionListLock函数的起始地址是80065926

LogonSessionListCount变量的起始地址是80065922LogonSessionList变量的起始地址8006593D,那经过计算LogonSessionListCount相对LogonSessionListLock的偏移是-4LogonSessionList相对LogonSessionListLock的偏移是23,这个也正好对于mimikatz中的定义

9.png

至于如何找这两个全局变量可以学习:https://www.praetorian.com/blog/inside-mimikatz-part2/

那首先通过找到LSASS.exe进程,然后列举进程中全部的dll模块,计算出lsasrv.dll模块的基址,然后根据LogonSessionListLock函数在lsasrv.dll模块中的偏移找到这个函数的位置(而这个函数在不同windows版本下的lsasrv.dll模块中的位置也不同),然后再根据两个全局变量的相对位置,找到两个全局变量在内存中的位置

MSV功能分析

远程Debug Tips

因为mimikatz本身就需要高权限运行,所以远程调试中的msvsmon.exe要以管理员运行,且选择无身份验证最合适

10.png

正题

回到正题,当我们知道上面的命令分发、执行等操作就是为了通过命令去做指向性功能的操作时。那就直接单独看想看的功能模块就好
mimikatzmsv模块的作用是枚举LMNTLM凭证,KUHL_M_C结构体中的描述是Lists LM & NTLM credentials,根据之前分析的命令分发过程,sekurlsa::msv最终通过函数指针调用函数kuhl_m_sekurlsa_msv()

根据模块名直接找到函数所在文件kuhl_m_seckurlsa_msv1_0.c,先看到c文件处定义的kuhl_m_sekurlsa_msv_package,将功能名、回调函数、需要找的进程模块等赋值到新的结构体中

KUHL_M_SEKURLSA_PACKAGE kuhl_m_sekurlsa_msv_package = {L"msv", kuhl_m_sekurlsa_enum_logon_callback_msv, TRUE, L"lsasrv.dll", {{{NULL, NULL}, 0, 0, NULL}, FALSE, FALSE}};

此处可以看到ModuleName的值设置为lsasrv.dll,这个也是抓取NTML的重点模块

typedef struct _KUHL_M_SEKURLSA_PACKAGE {
    const wchar_t * Name;
    PKUHL_M_SEKURLSA_ENUM_LOGONDATA CredsForLUIDFunc;
    BOOL isValid;
    const wchar_t * ModuleName;
    KUHL_M_SEKURLSA_LIB Module;
} KUHL_M_SEKURLSA_PACKAGE, *PKUHL_M_SEKURLSA_PACKAGE;

随后找到msv模块功能入口点,打下断点开始分析

11.png

跟进到kuhl_m_sekurlsa_getLogonData()函数里,可以看到将新赋值的结构体OptionalDatakuhl_m_sekurlsa_enum_callback_logondata()函数传入到kuhl_m_sekurlsa_enum()函数中

12.png

跟进到kuhl_m_sekurlsa_enum()函数中,可以看到一个关键函数kuhl_m_sekurlsa_acquireLSA()

跟进该函数又可以看到调用了kull_m_process_getProcessIdForName()函数通过lsass.exe进程名去获取其PID。其之后的调用路线就是kull_m_process_getProcessInformation() ->kull_m_process_NtQuerySystemInformation() ->NtQuerySystemInformation(),最后调用的是不公开的系统API(https://blog.csdn.net/qq_37232329/article/details/111401002),就不深究先

13.png

当获取了lsass.exe进程ID后,回到kuhl_m_sekurlsa_acquireLSA()中,之后根据PID利用OpenProcess(processRights, FALSE, pid)函数,获取进程句柄

14.png

打开句柄之后,首先调用kull_m_memory_open()&cLsass.hLsassMem分配一块内存KUHL_M_SEKURLSA_CONTEXT cLsass = {NULL, {0, 0, 0}};

15.png

然后对cLsass.osContext.MajorVersion等三个属性赋值,这个赋值保存的是windows当前版本的相关信息。我用的机器是1803的,所以MIMIKATZ_NT_BUILD_NUMBER=17134,MIMIKATZ_NT_MINOR_VERSION=0,MIMIKATZ_NT_MAJOR_VERSION=10,不同机器这些值会存在差异

现在来到了获取lssas.exe进程中各模块、地址等信息的操作

16.png

先跟进到kull_m_process_getVeryBasicModuleInformations()函数中,当中先调用kull_m_process_peb()函数获取LSASS.exe进程的PEB进程环境块(https://bbs.pediy.com/thread-266678.htm),实际也是调用NtQueryInformationProcess()函数

17.png

18.png

PEB的结构中有一个PEB.Ldr.InMemoryOrderModuleList的列表,这个列表记录了进程加载的模块地址和大小,接下来通过遍历列表来查找需要的LSASRV.dll模块

19.png

当查找到lsasrv.dll模块时,进入callback回调函数->kuhl_m_sekurlsa_findlibs(),将pModuleInformation结构体中获取到的lsasrv.dll模块地址、偏移量等信息存入kuhl_m_sekurlsa_msv_package结构体当中

20.png

21.png

在成功查找到lsasrv.dll模块的相关信息便返回后,进入kuhl_m_sekurlsa_utils_search函数当中,这个函数继而调用kuhl_m_sekurlsa_utils_search\_generic,通过搜索的是lsasrv.dll模块的特征码,结合偏移量找到所有的登录会话信息即LogonSessionListLogonSessionListCount这两个全局变量的地址,但是还需要调用kull_m_memory_copy获取其值

22.png

之后会进入到lsassLocalHelper->AcquireKeys(&cLsass, &lsassPackages[0]->Module.Informations)即调用kuhl_m_sekurlsa_nt6_acquireKeys()函数去获取加密用户密码的密钥。用kull_m_patch_getGenericFromBuild()函数通过识别不同系统版本返回相应系统的PTRN_WALL_LsaInitializeProtectedMemory_KEY作为特征码进行搜索

23.png

24.png

再通过偏移量获取初始化向量和密钥本身

25.png

以上就是要分析的kuhl_m_sekurlsa_acquireLSA()函数功能,回到kuhl_m_sekurlsa_enum()函数里,就是枚举用户信息的时刻~先是通过识别不同系统找到相应的结构体以及偏移量

26.png

再通过LogonSessionList会话活动信息根据以上偏移量得到会话用户的UserName、LogonDomain、LogonServer等信息

27.png

跟进callback回调函数即kuhl_m_sekurlsa_enum_callback_logondata()函数

retCallback = callback(&sessionData, pOptionalData);

在该函数里先是调用kuhl_m_sekurlsa_printinfos_logonData()函数将会话中获取到的用户信息打印出来

28.png

29.png

接着将会话信息传入kuhl_m_sekurlsa_enum_logon_callback_msv()函数

30.png

void CALLBACK kuhl_m_sekurlsa_enum_logon_callback_msv(IN PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA pData)
{
    kuhl_m_sekurlsa_msv_enum_cred(pData->cLsass, pData->pCredentials, kuhl_m_sekurlsa_msv_enum_cred_callback_std, pData);
}

我们直接跟进到credCallback回调函数即kuhl_m_sekurlsa_msv_enum_cred_callback_std()函数中的kuhl_m_sekurlsa_genericCredsOutput()函数

31.png

以下将结合会话凭证和的NTLM等信息的特征码偏移量来在内存中获取会话列表中用户的ntlm hash值,再利用hex转string的方式打印出来

32.png

将通过以下凭证和地址偏移量找到NTLM等信息,例:

33.png

34.png

而NTLM这类信息则调用到kull_m_string_wprintf_hex()函数根据凭证加地址偏移量,一位一位读出打印

35.png

之后的操作就是获取SHA1等值、遍历获取会话列表中获取以上信息的操作(不是目的,不关注咯)最后以释放内存结束

0x04 小结

个人认为,mimikatz在利用msv获取NTLM的原理是通过特征码定位lsass.exe进程的lsasvr.dll中的LogonSessionList全局变量和LogonSessionListCount全局变量的地址,然后解析LogonSessionList结构体,通过结构体内的偏移量读取到凭证等用户信息(这不同系统的特征码及偏移量的收集是真牛b)

Tips:这种读取lsass.exe进程及其中模块获取内存内容的方式可能也会造成大多数杀软或EDR的告警拦截,那么主机弱口检测就不好以这种方式实现

  • 发表于 2022-10-11 09:14:24
  • 阅读 ( 7324 )
  • 分类:安全工具

0 条评论

请先 登录 后评论
w1nk1
w1nk1

12 篇文章

站长统计