票据导出
票据导出涉及到kirbi、ccache,kirbi主要是mimikatz使用,而ccache为impacket使用。在写这个之前需要了解ASN.1编码, A它是一种抽象语法描述语言,用于描述数据结构在传输或存储时的格式。它常用于通信协议中,例如 X.509证书、SNMP、LDAP、Kerberos等。 说白了就是将结构转化为二进制数据进行传输。咱们在这块不需要了解很多,了解如何用就好了。
转化为ccache格式
在上一篇文章已经提到,进行登录之后我们会对返回的ASRep返回包进行解密,如果解密成功会将解密出来的相关凭据,以及客户端相关信息保存回ASRep中:
之后直接使用以下函数,自己构造一个CCache:
func (ASRep *ASRep) ToCCache() (*credentials.CCache, error) {cc := new(credentials.CCache)cc.Version = 4// HEADERcc.Header.Length = 12cc.Header.Fields = []credentials.HeaderField{ // forge this for now{Tag: 1,Length: 8,Value: []byte{255, 255, 255, 255, 0, 0, 0, 0},},}// PRINCIPALcc.DefaultPrincipal = credentials.Principal{Realm: ASRep.CRealm,PrincipalName: ASRep.CName,}cc.Credentials = make([]*credentials.Credential, 0)cred := new(credentials.Credential)cred.Client = credentials.Principal{Realm: ASRep.CRealm,PrincipalName: ASRep.CName,}cred.Server = credentials.Principal{Realm: ASRep.KDCRepFields.DecryptedEncPart.SRealm,PrincipalName: types.PrincipalName{NameType: 2,NameString: ASRep.Ticket.SName.NameString,},}cred.AuthTime = ASRep.KDCRepFields.DecryptedEncPart.AuthTimecred.StartTime = ASRep.KDCRepFields.DecryptedEncPart.StartTimecred.EndTime = ASRep.KDCRepFields.DecryptedEncPart.EndTimecred.RenewTill = ASRep.KDCRepFields.DecryptedEncPart.RenewTillcred.Key = ASRep.KDCRepFields.DecryptedEncPart.Keycred.Ticket, _ = ASRep.Ticket.Marshal()tgtops := asn1.BitString{Bytes: []byte{0x50, 0xe1, 0x00, 0x00}}cred.TicketFlags = asn1.BitString(tgtops)cc.Credentials = append(cc.Credentials, cred)return cc, nil}
最后就可以将其导出了,导出过程中不能将它直接转换为二进制保存,需要改改格式的。另外,github上有一个项目给出了代码示例,要小改一个地方:
// SaveCCache saves a CCache type to a file.func (c *CCache) Export(cpath string) error {var out bytes.Bufferendian := binary.BigEndianbinary.Write(&out, endian, int8(5))// HEADERbinary.Write(&out, endian, c.Version)if c.Version == 4 {binary.Write(&out, endian, c.Header.Length)for _, f := range c.Header.Fields {binary.Write(&out, endian, f.Tag)binary.Write(&out, endian, f.Length)out.Write(f.Value)}}// // PRINCIPALbinary.Write(&out, endian, c.DefaultPrincipal.PrincipalName.NameType)binary.Write(&out, endian, int32(len(c.DefaultPrincipal.PrincipalName.NameString)))binary.Write(&out, endian, int32(len(c.DefaultPrincipal.Realm)))out.Write([]byte(c.DefaultPrincipal.Realm))for _, n := range c.DefaultPrincipal.PrincipalName.NameString {binary.Write(&out, endian, int32(len(n)))// binary.Write(&out, endian, []byte(n))out.Write([]byte(n))}// CREDENTIALSfor _, cred := range c.Credentials {binary.Write(&out, endian, cred.Client.PrincipalName.NameType)binary.Write(&out, endian, int32(len(cred.Client.PrincipalName.NameString)))binary.Write(&out, endian, int32(len(cred.Client.Realm)))out.Write([]byte(cred.Client.Realm))for _, n := range cred.Client.PrincipalName.NameString {binary.Write(&out, endian, int32(len(n)))out.Write([]byte(n))}binary.Write(&out, endian, cred.Server.PrincipalName.NameType) binary.Write(&out, endian, int32(len(cred.Server.PrincipalName.NameString)))binary.Write(&out, endian, int32(len(cred.Server.Realm)))out.Write([]byte(cred.Server.Realm))for _, n := range cred.Server.PrincipalName.NameString {binary.Write(&out, endian, int32(len(n)))out.Write([]byte(n))}binary.Write(&out, endian, int16(cred.Key.KeyType))binary.Write(&out, endian, int32(len(cred.Key.KeyValue))) out.Write(cred.Key.KeyValue)binary.Write(&out, endian, int32(cred.AuthTime.Unix()))binary.Write(&out, endian, int32(cred.StartTime.Unix()))binary.Write(&out, endian, int32(cred.EndTime.Unix()))binary.Write(&out, endian, int32(cred.RenewTill.Unix()))if cred.IsSKey {binary.Write(&out, endian, int8(1))} else {binary.Write(&out, endian, int8(0))}out.Write(cred.TicketFlags.Bytes)binary.Write(&out, endian, int32(len(cred.Addresses)))for _, a := range cred.Addresses {binary.Write(&out, endian, int16(a.AddrType))binary.Write(&out, endian, int32(len(a.Address)))out.Write(a.Address)}binary.Write(&out, endian, int32(len(cred.AuthData)))for _, ad := range cred.AuthData {binary.Write(&out, endian, ad.ADType)binary.Write(&out, endian, int32(len(ad.ADData)))out.Write(ad.ADData)}binary.Write(&out, endian, int32(len(cred.Ticket)))out.Write(cred.Ticket)binary.Write(&out, endian, int32(len(cred.SecondTicket)))out.Write(cred.SecondTicket)}// fmt.Println(hex.Dump(out.Bytes()))return os.WriteFile(cpath, out.Bytes(), 0600)}
在TGS的请求阶段需要进行以下过程,首先这个过程之中需要SPN,需要使用NewPrincipalName进行请求服务PrincipalName的初始化:
type PrincipalName struct { NameType int32`asn1:"explicit,tag:0"` NameString []string`asn1:"generalstring,explicit,tag:1"`}
接着使用func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error)获取TGT认购权证:
接着就可以调用TGSREQGenerateAndExchange进行请求了
和之前一样,请求成功之后也会将解密的数据暂存到TGSRep中:
接着使用以下代码,像上面一样就好了:
func(TGSRep *TGSRep)ToCCache()(*credentials.CCache, error) { cc := new(credentials.CCache) cc.Version = 4// HEADER cc.Header.Length = 12 cc.Header.Fields = []credentials.HeaderField{ // forge this for now { Tag: 1, Length: 8, Value: []byte{255, 255, 255, 255, 0, 0, 0, 0}, }, }// PRINCIPAL cc.DefaultPrincipal = credentials.Principal{ Realm: TGSRep.CRealm, PrincipalName: TGSRep.CName, }// spew.Dump(ASRep.KDCRepFields.DecryptedEncPart.Key)// spew.Dump(ASRep.Ticket) cc.Credentials = make([]*credentials.Credential, 0) cred := new(credentials.Credential) cred.Client = credentials.Principal{ Realm: TGSRep.CRealm, PrincipalName: TGSRep.CName, } cred.Server = credentials.Principal{ Realm: TGSRep.KDCRepFields.DecryptedEncPart.SRealm, PrincipalName: types.PrincipalName{ NameType: 2, NameString: TGSRep.Ticket.SName.NameString, }, } cred.AuthTime = TGSRep.KDCRepFields.DecryptedEncPart.AuthTime cred.StartTime = TGSRep.KDCRepFields.DecryptedEncPart.StartTime cred.EndTime = TGSRep.KDCRepFields.DecryptedEncPart.EndTime cred.RenewTill = TGSRep.KDCRepFields.DecryptedEncPart.RenewTill cred.Key = TGSRep.KDCRepFields.DecryptedEncPart.Key cred.Ticket, _ = TGSRep.Ticket.Marshal()//(0x50e10000) forwardable, proxiable, renewable, initial, pre_authent, enc_pa_rep// TODO need to fetch flags from options/existing ticket. tgtops := asn1.BitString{Bytes: []byte{0x50, 0xe1, 0x00, 0x00}} cred.TicketFlags = asn1.BitString(tgtops)// cred.TicketFlags = TGSRep.Ticket.DecryptedEncPart.Flags // TODO: verify this is the right place to pull flags from. cc.Credentials = append(cc.Credentials, cred)return cc, nil}// SaveCCache saves a CCache type to a file.func(c *CCache)Export(cpath string)error {var out bytes.Buffer endian := binary.BigEndian binary.Write(&out, endian, int8(5))// HEADER binary.Write(&out, endian, c.Version)if c.Version == 4 { binary.Write(&out, endian, c.Header.Length)for _, f := range c.Header.Fields { binary.Write(&out, endian, f.Tag) binary.Write(&out, endian, f.Length) out.Write(f.Value) } }// // PRINCIPAL binary.Write(&out, endian, c.DefaultPrincipal.PrincipalName.NameType) binary.Write(&out, endian, int32(len(c.DefaultPrincipal.PrincipalName.NameString))) binary.Write(&out, endian, int32(len(c.DefaultPrincipal.Realm))) out.Write([]byte(c.DefaultPrincipal.Realm))for _, n := range c.DefaultPrincipal.PrincipalName.NameString { binary.Write(&out, endian, int32(len(n)))// binary.Write(&out, endian, []byte(n)) out.Write([]byte(n)) }// CREDENTIALSfor _, cred := range c.Credentials { binary.Write(&out, endian, cred.Client.PrincipalName.NameType) binary.Write(&out, endian, int32(len(cred.Client.PrincipalName.NameString))) binary.Write(&out, endian, int32(len(cred.Client.Realm))) out.Write([]byte(cred.Client.Realm))for _, n := range cred.Client.PrincipalName.NameString { binary.Write(&out, endian, int32(len(n))) out.Write([]byte(n)) } binary.Write(&out, endian, cred.Server.PrincipalName.NameType) // DIFF TODO, 01 vs 02 binary.Write(&out, endian, int32(len(cred.Server.PrincipalName.NameString))) binary.Write(&out, endian, int32(len(cred.Server.Realm))) out.Write([]byte(cred.Server.Realm))for _, n := range cred.Server.PrincipalName.NameString { binary.Write(&out, endian, int32(len(n))) out.Write([]byte(n)) } binary.Write(&out, endian, int16(cred.Key.KeyType)) binary.Write(&out, endian, int32(len(cred.Key.KeyValue))) // DIFF TODO, idk where "00 00 00 20" comes from out.Write(cred.Key.KeyValue) binary.Write(&out, endian, int32(cred.AuthTime.Unix())) binary.Write(&out, endian, int32(cred.StartTime.Unix())) binary.Write(&out, endian, int32(cred.EndTime.Unix())) binary.Write(&out, endian, int32(cred.RenewTill.Unix()))if cred.IsSKey { binary.Write(&out, endian, int8(1)) } else { binary.Write(&out, endian, int8(0)) } out.Write(cred.TicketFlags.Bytes) binary.Write(&out, endian, int32(len(cred.Addresses)))for _, a := range cred.Addresses { binary.Write(&out, endian, int16(a.AddrType)) binary.Write(&out, endian, int32(len(a.Address))) out.Write(a.Address) } binary.Write(&out, endian, int32(len(cred.AuthData)))for _, ad := range cred.AuthData { binary.Write(&out, endian, ad.ADType) binary.Write(&out, endian, int32(len(ad.ADData))) out.Write(ad.ADData) } binary.Write(&out, endian, int32(len(cred.Ticket))) out.Write(cred.Ticket) binary.Write(&out, endian, int32(len(cred.SecondTicket))) out.Write(cred.SecondTicket) }// fmt.Println(hex.Dump(out.Bytes()))return os.WriteFile(cpath, out.Bytes(), 0600)}
参考项目:https://github.com/redt1de/gimp
转化为kirbi格式
这个有点麻烦,因为gokrb5使用的asn1编码与kirbi使用到的asn1编码很多细节有些不一样,导致了序列化时候会出现很多问题。下面给出参考项目:
https://github.com/1ight-2020/GoRottenTomato
给出需要注意的细节:
-
gokrb5与这个项目使用到的ans1包不一样,如果想快点构造出来直接导入这个包使用,补充一些函数之后会发现怎么样都出错,在asn1在线查看网站会发现有一个地方是没有解析出来的,不想找细节的就将创建有关结构体的副本,在单独的文件中使用特定的ASN1包(只要不要让它在序列化时用的走的原来的asn1包就好)。
Kerberoasting
相关的kirbi解析代码可以参考上面的项目。之前讲过,接收ASRep会用到EncryptionKey这样一个结构体,同样的,爆破服务账户密码也是这样,但是gokrb5没有提供相应的函数,所以咱们自己构建一个。
type EncryptionKey struct { KeyType int32`asn1:"explicit,tag:0"` KeyValue []byte`asn1:"explicit,tag:1" json:"-"`}
首先就是需要哦构造EncryptionKey这个密钥,这个过程之中可以加入一些自己的判断,假如我们获取的是AES256就使用相关的keyType,如果获取的是明文密码则根据加密类型构造EncryptionKey.keyValue
接着就是解密了,解密之前可以看下TGSRep的解密,它使用到了sessionkey进行解密,其中keyusage有说法,将其改一下,改为KDC_REP_TICKET就可以爆破服务账户密码了
圈子介绍
圈子内部致力于红蓝对抗,武器免杀与二开,不定期分享前沿技术文章,经验总结,学习笔记以及自研工具与插件,进圈联系~、
圈子已满165人,目前价格199,学生优惠30
200人后升价249
原文始发于微信公众号(半只红队):【fscan插件】| 用gokrb5构建你的fscan插件(二)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论