【fscan插件】| 用gokrb5构建你的fscan插件(二)

admin 2025年4月7日23:57:17评论0 views字数 9761阅读32分32秒阅读模式

票据导出

票据导出涉及到kirbi、ccache,kirbi主要是mimikatz使用,而ccache为impacket使用。在写这个之前需要了解ASN.1编码, A它是一种抽象语法描述语言,用于描述数据结构在传输或存储时的格式。它常用于通信协议中,例如 X.509证书、SNMP、LDAP、Kerberos等。 说白了就是将结构转化为二进制数据进行传输。咱们在这块不需要了解很多,了解如何用就好了。

转化为ccache格式

在上一篇文章已经提到,进行登录之后我们会对返回的ASRep返回包进行解密,如果解密成功会将解密出来的相关凭据,以及客户端相关信息保存回ASRep中:

【fscan插件】| 用gokrb5构建你的fscan插件(二)

之后直接使用以下函数,自己构造一个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上有一个项目给出了代码示例,要小改一个地方:

【fscan插件】| 用gokrb5构建你的fscan插件(二)
// 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认购权证:

【fscan插件】| 用gokrb5构建你的fscan插件(二)

接着就可以调用TGSREQGenerateAndExchange进行请求了

【fscan插件】| 用gokrb5构建你的fscan插件(二)

和之前一样,请求成功之后也会将解密的数据暂存到TGSRep中:

【fscan插件】| 用gokrb5构建你的fscan插件(二)

接着使用以下代码,像上面一样就好了:

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{2552552552550000},        },    }// 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{0x500xe10x000x00}}    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

【fscan插件】| 用gokrb5构建你的fscan插件(二)

接着就是解密了,解密之前可以看下TGSRep的解密,它使用到了sessionkey进行解密,其中keyusage有说法,将其改一下,改为KDC_REP_TICKET就可以爆破服务账户密码了

【fscan插件】| 用gokrb5构建你的fscan插件(二)

圈子介绍

圈子内部致力于红蓝对抗,武器免杀与二开,不定期分享前沿技术文章,经验总结,学习笔记以及自研工具与插件,进圈联系~、

圈子已满165人,目前价格199,学生优惠30

200人后升价249

【fscan插件】| 用gokrb5构建你的fscan插件(二)

【fscan插件】| 用gokrb5构建你的fscan插件(二)

原文始发于微信公众号(半只红队):【fscan插件】| 用gokrb5构建你的fscan插件(二)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月7日23:57:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【fscan插件】| 用gokrb5构建你的fscan插件(二)https://cn-sec.com/archives/3924149.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息