├── README.md ├── examples └── capture.pcap.zip ├── requirements.txt └── winrm_decrypt.py /README.md: -------------------------------------------------------------------------------- 1 | # decrypt-winrm 2 | 3 | (use python3) 4 | 5 | modified/forked from this gist https://gist.github.com/jborean93/d6ff5e87f8a9f5cb215cd49826523045/ by @jborean93 6 | 7 | install requirements: 8 | `pip3 install -r requirements.txt` 9 | 10 | example usage: 11 | `python3 winrm_decrypt.py -n 8bb1f8635e5708eb95aedf142054fc95 ./capture.pcap` 12 | 13 | (you can find the working example pcap inside [examples](examples), capture.pcap from HTB Uni CTF Quals 2021 14 | 15 | or, use the password 16 | `python3 winrm_decrypt.py -p password123 ./capture.pcap` 17 | 18 | -------------------------------------------------------------------------------- /examples/capture.pcap.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4sh5/decrypt-winrm/91fbe5ac084d7c14e07d7fe77a52b29e1025fb0c/examples/capture.pcap.zip -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyshark==0.4.3 2 | cryptography==3.3.2 3 | -------------------------------------------------------------------------------- /winrm_decrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | # Copyright: (c) 2020 Jordan Borean (@jborean93) 6 | # MIT License (see LICENSE or https://opensource.org/licenses/MIT) 7 | 8 | # Fork / modifications by Haoxi Tan (haoxi.tan@gmail.com) 9 | # MIT License (see LICENSE or https://opensource.org/licenses/MIT) 10 | 11 | """ 12 | Script that can read a Wireshark capture .pcapng for a WinRM exchange and decrypt the messages. Currently only supports 13 | exchanges that were authenticated with NTLM. This is really a POC, a lot of things are missing like NTLMv1 support, 14 | shorter signing keys, better error handling, etc. 15 | """ 16 | 17 | from __future__ import (absolute_import, division, print_function) 18 | __metaclass__ = type 19 | 20 | import argparse 21 | import base64 22 | import hashlib 23 | import hmac 24 | import os 25 | import pyshark 26 | import binascii 27 | import sys 28 | import struct 29 | import xml.dom.minidom 30 | 31 | from cryptography.hazmat.primitives.ciphers import ( 32 | algorithms, 33 | Cipher, 34 | ) 35 | 36 | from cryptography.hazmat.backends import ( 37 | default_backend, 38 | ) 39 | 40 | try: 41 | import argcomplete 42 | except ImportError: 43 | argcomplete = None 44 | 45 | 46 | class SecurityContext: 47 | 48 | def __init__(self, port, nt_hash): 49 | self.port = port 50 | self.tokens = [] 51 | self.nt_hash = nt_hash 52 | self.complete = False 53 | 54 | self.key_exch = False 55 | self.session_key = None 56 | self.sign_key_initiate = None 57 | self.sign_key_accept = None 58 | self.seal_handle_initiate = None 59 | self.seal_handle_accept = None 60 | 61 | self.__initiate_seq_no = 0 62 | self.__accept_seq_no = 0 63 | 64 | @property 65 | def _initiate_seq_no(self): 66 | val = self.__initiate_seq_no 67 | self.__initiate_seq_no += 1 68 | return val 69 | 70 | @property 71 | def _accept_seq_no(self): 72 | val = self.__accept_seq_no 73 | self.__accept_seq_no += 1 74 | return val 75 | 76 | def add_token(self, token): 77 | self.tokens.append(token) 78 | 79 | if token.startswith(b"NTLMSSP\x00\x03"): 80 | # Extract the info required to build the session key 81 | nt_challenge = self._get_auth_field(20, token) 82 | b_domain = self._get_auth_field(28, token) or b"" 83 | b_username = self._get_auth_field(36, token) or b"" 84 | encrypted_random_session_key = self._get_auth_field(52, token) 85 | flags = struct.unpack(" 1: 233 | b64_token = parts[1] 234 | try: 235 | auth_token = base64.b64decode(b64_token) 236 | except Exception: 237 | continue 238 | else: 239 | continue 240 | 241 | context = None 242 | if auth_token: 243 | if not auth_token.startswith(b"NTLMSSP\x00"): 244 | continue 245 | 246 | if auth_token.startswith(b"NTLMSSP\x00\x01"): 247 | context = SecurityContext(unique_port, nt_hash) 248 | contexts.append(context) 249 | 250 | else: 251 | context = [c for c in contexts if c.port == unique_port][-1] 252 | if not context: 253 | raise ValueError("Missing exisitng NTLM security context") 254 | 255 | context.add_token(auth_token) 256 | 257 | if hasattr(cap.http, 'file_data'): 258 | if not context: 259 | context = next(c for c in contexts if c.port == unique_port) 260 | 261 | if not context.complete: 262 | raise ValueError("Cannot decode message without completed context") 263 | 264 | # file_data = cap.http.file_data #.binary_value 265 | # messages = unpack_message(file_data) 266 | length = int(cap.mime_multipart.data_len) 267 | msgdata = binascii.unhexlify(cap.mime_multipart.data) 268 | messages = [(length, msgdata)] 269 | 270 | unwrap_func = context.unwrap_accept if source_port == args.port else context.unwrap_initiate 271 | 272 | dec_msgs = [] 273 | for length, enc_data in messages: 274 | msg = unwrap_func(enc_data) 275 | # if len(msg) != length: 276 | # raise ValueError("Message decryption failed") 277 | # print(f'decrypted msg ({len(msg)}):',msg) 278 | try: 279 | dec_msgs.append(pretty_xml(msg.decode('utf-8'))) 280 | except Exception as e: 281 | print("Exception: ", e) 282 | dec_msgs.append("[bad message]") 283 | 284 | 285 | dec_msgs = "\n".join(dec_msgs) 286 | print("No: %s | Time: %s | Source: %s | Destination: %s\n%s\n" 287 | % (cap.number, cap.sniff_time.isoformat(), cap.ip.src_host, cap.ip.dst_host, dec_msgs)) 288 | 289 | except Exception as e: 290 | raise Exception("Failed to process frame: %s" % cap.number) from e 291 | 292 | 293 | def parse_args(): 294 | """Parse and return args.""" 295 | parser = argparse.ArgumentParser(description='Parse network captures from WireShark and decrypts the WinRM ' 296 | 'messages that were exchanged.') 297 | 298 | parser.add_argument('path', 299 | type=str, 300 | help='The path to the .pcapng file to decrypt.') 301 | 302 | parser.add_argument('--port', 303 | dest='port', 304 | default=5985, 305 | type=int, 306 | help='The port to scan for the WinRM HTTP packets (default: 5985).') 307 | 308 | secret = parser.add_mutually_exclusive_group() 309 | 310 | secret.add_argument('-p', '--password', 311 | dest='password', 312 | help='The password for the account that was used in the authentication.') 313 | 314 | secret.add_argument('-n', '--hash', 315 | dest='hash', 316 | help='The NT hash for the account that was used in the authentication.') 317 | 318 | if argcomplete: 319 | argcomplete.autocomplete(parser) 320 | 321 | return parser.parse_args() 322 | 323 | 324 | if __name__ == '__main__': 325 | main() 326 | --------------------------------------------------------------------------------