Mimikatz源码:https://github.com/gentilkiwi/mimikatz
笔者调试环境:vs2022社区版
在将项目导入重新生成解决方案的时候可能会出现以下两个小问题:
1、类型强制转换”: 从“PVOID”到“DWORD”的指针截断报错
解决:在报错处修改将指针类型强行转换成DWORD
2、PRINTER_NOTIFY_CATEGORY_ALL”: 宏重定义
解决:将重复定义变量在报错处修改成重复定义变量的值即可
3、因官方项目默认没给出debug方案,所以需要手动添加debug配置
解决:
若遇其他问题可百度解决,具体没太记录
另外,若想做远程debug,可以在项目属性中这样配置
参考:https://blog.csdn.net/thebulesky/article/details/120852560
同项目名c文件中的wmain()
是整个mimikatz的入口函数
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()
函数,利用这个函数可以根据当前命令在不同场景下执行接下来的操作
例如:当我们想要做万能钥匙攻击的时候,键入!+
将利用mimidrv.sys
去执行当前命令的操作
否则通过mimikatz_doLocal()
函数默认进入常用命令的场景
在获取到命令进行命令分发之后,将获取到module
和command
两个参数,之后就进入命令执行的阶段
先来看看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
msv
功能模块的原理个人理解就是在lsasrv.dll
这个模块中找到LogonSessionListLock()
函数同时使用了LogonSessionList
和LogonSessionListCount
两个变量作为参数,这个LogonSessionList
中应该就保存当前活动的 Windows 登录会话列表。那么只要根据这个LogonSessionListLock()
这个函数位置,加上偏移位置,就可以获取两个全局变量的位置。以windows1803为例,通俗理解:LogonSessionListLock
函数的起始地址是80065926
LogonSessionListCount
变量的起始地址是80065922
,LogonSessionList
变量的起始地址8006593D
,那经过计算LogonSessionListCount
相对LogonSessionListLock
的偏移是-4
,LogonSessionList
相对LogonSessionListLock
的偏移是23
,这个也正好对于mimikatz
中的定义
至于如何找这两个全局变量可以学习:https://www.praetorian.com/blog/inside-mimikatz-part2/
那首先通过找到LSASS.exe
进程,然后列举进程中全部的dll
模块,计算出lsasrv.dll
模块的基址,然后根据LogonSessionListLock
函数在lsasrv.dll
模块中的偏移找到这个函数的位置(而这个函数在不同windows版本下的lsasrv.dll
模块中的位置也不同),然后再根据两个全局变量的相对位置,找到两个全局变量在内存中的位置
因为mimikatz
本身就需要高权限运行,所以远程调试中的msvsmon.exe
要以管理员运行,且选择无身份验证最合适
回到正题,当我们知道上面的命令分发、执行等操作就是为了通过命令去做指向性功能的操作时。那就直接单独看想看的功能模块就好
在mimikatz
中msv
模块的作用是枚举LM
和NTLM
凭证,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
模块功能入口点,打下断点开始分析
跟进到kuhl_m_sekurlsa_getLogonData()
函数里,可以看到将新赋值的结构体OptionalData
和kuhl_m_sekurlsa_enum_callback_logondata()
函数传入到kuhl_m_sekurlsa_enum()
函数中
跟进到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),就不深究先
当获取了lsass.exe
进程ID后,回到kuhl_m_sekurlsa_acquireLSA()
中,之后根据PID利用OpenProcess(processRights, FALSE, pid)
函数,获取进程句柄
打开句柄之后,首先调用kull_m_memory_open()
给&cLsass.hLsassMem
分配一块内存KUHL_M_SEKURLSA_CONTEXT cLsass = {NULL, {0, 0, 0}};
然后对cLsass.osContext.MajorVersion
等三个属性赋值,这个赋值保存的是windows当前版本
的相关信息。我用的机器是1803的,所以MIMIKATZ_NT_BUILD_NUMBER=17134,MIMIKATZ_NT_MINOR_VERSION=0,MIMIKATZ_NT_MAJOR_VERSION=10
,不同机器这些值会存在差异
现在来到了获取lssas.exe
进程中各模块、地址等信息的操作
先跟进到kull_m_process_getVeryBasicModuleInformations()
函数中,当中先调用kull_m_process_peb()
函数获取LSASS.exe
进程的PEB进程环境块
(https://bbs.pediy.com/thread-266678.htm),实际也是调用NtQueryInformationProcess()
函数
在PEB
的结构中有一个PEB.Ldr.InMemoryOrderModuleList
的列表,这个列表记录了进程加载的模块地址和大小,接下来通过遍历列表来查找需要的LSASRV.dll
模块
当查找到lsasrv.dll
模块时,进入callback回调函数->kuhl_m_sekurlsa_findlibs()
,将pModuleInformation
结构体中获取到的lsasrv.dll
模块地址、偏移量等信息存入kuhl_m_sekurlsa_msv_package
结构体当中
在成功查找到lsasrv.dll
模块的相关信息便返回后,进入kuhl_m_sekurlsa_utils_search
函数当中,这个函数继而调用kuhl_m_sekurlsa_utils_search\_generic
,通过搜索的是lsasrv.dll
模块的特征码,结合偏移量找到所有的登录会话信息即LogonSessionList
和LogonSessionListCount
这两个全局变量的地址,但是还需要调用kull_m_memory_copy
获取其值
之后会进入到lsassLocalHelper->AcquireKeys(&cLsass, &lsassPackages[0]->Module.Informations)
即调用kuhl_m_sekurlsa_nt6_acquireKeys()
函数去获取加密用户密码的密钥。用kull_m_patch_getGenericFromBuild()
函数通过识别不同系统版本返回相应系统的PTRN_WALL_LsaInitializeProtectedMemory_KEY
作为特征码进行搜索
再通过偏移量获取初始化向量和密钥本身
以上就是要分析的kuhl_m_sekurlsa_acquireLSA()
函数功能,回到kuhl_m_sekurlsa_enum()
函数里,就是枚举用户信息的时刻~先是通过识别不同系统找到相应的结构体以及偏移量
再通过LogonSessionList
会话活动信息根据以上偏移量得到会话用户的UserName、LogonDomain、LogonServer
等信息
跟进callback
回调函数即kuhl_m_sekurlsa_enum_callback_logondata()
函数
retCallback = callback(&sessionData, pOptionalData);
在该函数里先是调用kuhl_m_sekurlsa_printinfos_logonData()
函数将会话中获取到的用户信息打印出来
接着将会话信息传入kuhl_m_sekurlsa_enum_logon_callback_msv()
函数
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()
函数
以下将结合会话凭证和的NTLM等信息的特征码偏移量来在内存中获取会话列表中用户的ntlm hash值,再利用hex转string的方式打印出来
将通过以下凭证和地址偏移量找到NTLM等信息,例:
而NTLM这类信息则调用到kull_m_string_wprintf_hex()
函数根据凭证加地址偏移量,一位一位读出打印
之后的操作就是获取SHA1等值、遍历获取会话列表中获取以上信息的操作(不是目的,不关注咯)最后以释放内存结束
个人认为,mimikatz
在利用msv
获取NTLM
的原理是通过特征码定位lsass.exe
进程的lsasvr.dll
中的LogonSessionList
全局变量和LogonSessionListCount
全局变量的地址,然后解析LogonSessionList
结构体,通过结构体内的偏移量读取到凭证等用户信息(这不同系统的特征码及偏移量的收集是真牛b)
Tips:这种读取lsass.exe进程及其中模块获取内存内容的方式可能也会造成大多数杀软或EDR的告警拦截,那么主机弱口检测就不好以这种方式实现
12 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!