summaryrefslogtreecommitdiff
path: root/Resurrect/resurrect.py
diff options
context:
space:
mode:
Diffstat (limited to 'Resurrect/resurrect.py')
-rw-r--r--Resurrect/resurrect.py403
1 files changed, 233 insertions, 170 deletions
diff --git a/Resurrect/resurrect.py b/Resurrect/resurrect.py
index d06fae2..9126640 100644
--- a/Resurrect/resurrect.py
+++ b/Resurrect/resurrect.py
@@ -4,84 +4,142 @@
#!/usr/bin/env python3
-from ldap3 import Server, Connection, ALL, NTLM, BASE, MODIFY_DELETE, MODIFY_REPLACE
import argparse
import sys
from tabulate import tabulate
-from ldap3.core.exceptions import (
- LDAPInvalidCredentialsResult,
- LDAPBindError,
- LDAPSocketOpenError,
- LDAPException
-)
-
+import os
+from impacket.ldap import ldap, ldapasn1
+from impacket.ldap.ldapasn1 import ModifyRequest, Control
+from pyasn1.type.univ import SequenceOf,Sequence, OctetString, Integer, SetOf
+
+def create_ldap_connection(args):
+ dom = args.domain.split('.')[0]
+ tld = args.domain.split('.')[1]
+
+ if args.target:
+ server_host = args.target
+ print(f"[*] Connecting to {args.target}")
+ elif args.dc_host:
+ server_host = args.dc_host
+ print(f"[*] Connecting to {args.dc_host}")
+ else:
+ print("[!] Please specify either a DC or a target (--dc-host/--target)")
+ return None
+
+
+ if args.k:
+ if not args.dc_host:
+ print("[!] Kerberos Authentication Selected but --dc-host not specified")
+ sys.exit()
-def find_deleted_objects(args):
- try:
- if args.target:
- print(f"[*] Connecting to {args.target}")
- elif args.dc:
- print(f"[*] Connecting to {args.dc}")
- dom = args.domain.split('.')[0]
- tld = args.domain.split('.')[1]
- if args.target:
- if args.ldaps:
- s = Server(host=args.target, port=636,use_ssl=True, get_info='ALL')
- else:
- s = Server(host=args.target, port=389, use_ssl=False, get_info='ALL')
- elif args.dc:
+ print("[*] Attempting Kerberos authentication")
+
+ if os.environ.get('KRB5CCNAME'):
+ print(f"[*] Using Kerberos ccache from KRB5CCNAME: {os.environ.get('KRB5CCNAME')}")
+ else:
+ print("[*] Using default Kerberos ccache location")
+
+ try:
if args.ldaps:
- s = Server(host=args.dc, port=636,use_ssl=True, get_info='ALL')
+ conn = ldap.LDAPConnection(f'ldaps://{args.dc_host}')
+ conn.kerberosLogin(f'{args.username}', '', f'{args.domain}', '', '')
else:
- s = Server(host=args.dc, port=389,use_ssl=False, get_info='ALL')
- else:
- print("[!] Please Specify either a DC or a target")
- sys.exit()
+ conn = ldap.LDAPConnection(f'ldap://{args.dc_host}')
+ conn.kerberosLogin(f'{args.username}', '', f'{args.domain}', '', '')
+
+ print("[*] Kerberos Authentication successful")
+ return conn
+
+ except Exception as e:
+ print(f"[!] Kerberos authentication error: {e}")
+ return None
- if args.hash:
- if len(args.hash) == 32:
- try:
- conn = Connection(s, user=f"{dom}\\{args.username}", password=f"aad3b435b51404eeaad3b435b51404ee:{args.hash}", auto_bind=True, authentication=NTLM, version=3, check_names=True, raise_exceptions=True)
- except LDAPInvalidCredentialsResult as e:
- print("[!] Authentication failed: ", e)
- sys.exit()
-
- except LDAPSocketOpenError as e:
- print("[!] Unable to connect to the target: ",e)
- sys.exit()
- elif len(args.hash) != 32:
- print("[!] Hash Length mismatch")
- sys.exit()
- elif args.password:
+ elif args.hash:
+ if len(args.hash) == 32:
try:
- conn = Connection(s, user=f"{dom}\\{args.username}", password=args.password, auto_bind=True, authentication=NTLM, version=3, check_names=True, raise_exceptions=True)
- except LDAPInvalidCredentialsResult as e:
- print("[!] Authentication failed: ", e)
- sys.exit()
- except LDAPSocketOpenError as e:
- print("[!] Unable to connect to the target: ", e)
- sys.exit()
- else:
- print("[!] Please Specify either a hash or a password")
- sys.exit()
+ if args.ldaps:
+ conn = ldap.LDAPConnection(f'ldaps://{server_host}')
+ else:
+ conn = ldap.LDAPConnection(f'ldap://{server_host}')
- if not conn.bind():
- print("[!] Could not connect to the server")
- sys.exit()
+ lmhash = 'aad3b435b51404eeaad3b435b51404ee'
+ nthash = args.hash
+
+ conn.login(
+ user=args.username,
+ password='',
+ domain=args.domain,
+ lmhash=lmhash,
+ nthash=nthash
+
+ )
+ print("[*] Authentication successful")
+ return conn
+ except Exception as e:
+ print(f"[!] Authentication Error: {e}")
else:
+ print("[!] Hash length mismatch - should be 32 characters")
+ return None
+
+ elif args.password:
+ try:
+ if args.ldaps:
+ conn = ldap.LDAPConnection(f'ldaps://{server_host}')
+ else:
+ conn = ldap.LDAPConnection(f'ldap://{server_host}')
+
+ conn.login(
+ user=args.username,
+ password=args.password,
+ domain=args.domain,
+ )
print("[*] Authentication successful")
+ return conn
+ except Exception as e:
+ print(f"[!] Authentication Error: {e}")
+ else:
+ print("[!] Please specify an authentication method (password, hash, or Kerberos)")
+ return None
- entry_list = conn.extend.standard.paged_search(
- search_base = f'CN=Deleted Objects,DC={args.domain.split(".")[0]},DC={args.domain.split(".")[1]}',
- search_filter = '(&(|(objectClass=User)(objectCategory=Computer))(isDeleted=TRUE))',
- search_scope = 'SUBTREE',
- attributes = ['cn', 'sAMAccountName', 'objectClass', 'lastKnownParent'],
- controls= [
- ('1.2.840.113556.1.4.417', True, None)
- ],
- paged_size = args.page_size if args.page_size else 10,
- generator=False
- )
+
+
+def find_deleted_objects(args):
+ try:
+ conn = create_ldap_connection(args)
+ if not conn:
+ sys.exit(1)
+
+ search_base = f'CN=Deleted Objects,DC={args.domain.split(".")[0]},DC={args.domain.split(".")[1]}'
+ search_filter = '(&(|(objectClass=User)(objectCategory=Computer))(isDeleted=TRUE))'
+ attributes = ['cn', 'sAMAccountName', 'objectClass', 'lastKnownParent']
+ show_deleted_control = ldapasn1.Control()
+ show_deleted_control['controlType'] = ldapasn1.LDAPOID('1.2.840.113556.1.4.417')
+ show_deleted_control['criticality'] = True
+
+ entry_list = []
+ page_size = args.page_size if args.page_size else 10
+ cookie = b''
+
+ while True:
+ paging_control = ldapasn1.SimplePagedResultsControl(criticality=False, size=page_size, cookie=cookie)
+ try:
+ resp = conn.search(
+ searchBase = search_base,
+ searchFilter = search_filter,
+ scope=ldapasn1.Scope('wholeSubtree'),
+ attributes=attributes,
+ searchControls=[show_deleted_control, paging_control]
+ )
+
+ except Exception as e:
+ print("[!] Search Error: ", e)
+ break
+
+ for item in resp:
+ if isinstance(item, ldapasn1.SearchResultEntry):
+ entry_list.append(item)
+ if not cookie:
+ break
if not entry_list:
print("[*] No deleted users found or your current user doesn't have the permissions to view them")
sys.exit()
@@ -89,14 +147,22 @@ def find_deleted_objects(args):
print("[*] Deleted user(s) found\r\t")
data = []
for entry in entry_list:
- attrs = entry.get('attributes')
+ attrs = {}
+ for attr in entry['attributes']:
+ attr_name = str(attr['type'])
+ attr_values = [str(val) for val in attr['vals']]
+ attrs[attr_name] = attr_values
+
if not attrs:
continue
- guid = attrs.get('cn').split('\n')[1].split(':')[1]
- ou = attrs.get('lastKnownParent')
- sam = attrs.get('sAMAccountName')
+
+ cn = attrs.get('cn', [''])[0]
+ guid = cn.split('\n')[1].split(':')[1]
+ ou = attrs.get('lastKnownParent', [''])[0]
+ sam = attrs.get('sAMAccountName', [''])[0]
objectclass = attrs.get('objectClass')[3]
data.append([sam, guid, ou, objectclass])
+
headers = ['username', 'GUID', 'OU', 'objectClass']
print(tabulate(data, headers=headers, tablefmt='grid'))
except Exception as e:
@@ -104,118 +170,110 @@ def find_deleted_objects(args):
def restore_deleted_objects(args):
-
+ import uuid
try:
+ conn = create_ldap_connection(args)
+ if not conn:
+ sys.exit(1)
- if args.target:
- print(f"[*] Connecting to {args.target}")
- elif args.dc:
- print(f"[*] Connecting to {args.dc}")
- dom = args.domain.split('.')[0]
- tld = args.domain.split('.')[1]
- if args.target:
- if args.ldaps:
- s = Server(host=args.target, port=636,use_ssl=True, get_info='ALL')
- else:
- s = Server(host=args.target, port=389, use_ssl=False, get_info='ALL')
- elif args.dc:
- if args.ldaps:
- s = Server(host=args.dc, port=636,use_ssl=True, get_info='ALL')
- else:
- s = Server(host=args.dc, port=389,use_ssl=False, get_info='ALL')
- else:
- print("[!] Please Specify either a DC or a target")
- sys.exit()
-
- if args.hash:
- if len(args.hash) == 32:
- try:
- conn = Connection(s, user=f"{dom}\\{args.username}", password=f"aad3b435b51404eeaad3b435b51404ee:{args.hash}", auto_bind=True, authentication=NTLM, version=3, check_names=True, raise_exceptions=True)
- except LDAPInvalidCredentialsResult as e:
- print("[!] Authentication failed: ", e)
- sys.exit()
-
- except LDAPSocketOpenError as e:
- print("[!] Unable to connect to the target: ",e)
- sys.exit()
- elif len(args.hash) != 32:
- print("[-] Hash Length mismatch")
- sys.exit()
- elif args.password:
- try:
- conn = Connection(s, user=f"{dom}\\{args.username}", password=args.password, auto_bind=True, authentication=NTLM, version=3, check_names=True, raise_exceptions=True)
- except LDAPInvalidCredentialsResult as e:
- print("[!] Authentication failed: ", e)
- sys.exit()
- except LDAPSocketOpenError as e:
- print("[!] Unable to connect to the target: ", e)
- sys.exit()
- else:
- print("[!] Please Specify either a hash or a password")
- sys.exit()
+ if args.guid:
- if not conn.bind():
- print("[-] Could not connect to the server")
- sys.exit()
- else:
- print("[*] Authentication successful")
+
+ def guid_to_ldap_filter(g):
+ u = uuid.UUID(g)
+ return ''.join('\\%02X' % b for b in u.bytes_le)
+
+ guid_filter = guid_to_ldap_filter(args.guid)
+
+
+ search_base = f'CN=Deleted Objects,DC={args.domain.split(".")[0]},DC={args.domain.split(".")[1]}'
+ search_filter = f'(&(objectGuid={guid_filter})(isDeleted=TRUE))'
+ attributes = ['distinguishedName']
+ show_deleted_control = ldapasn1.Control()
+ show_deleted_control['controlType'] = ldapasn1.LDAPOID('1.2.840.113556.1.4.417')
+ show_deleted_control['criticality'] = True
+
+ entry_list = []
+ page_size = 10
+ cookie = b''
+
+ try:
+ resp = conn.search(
+ searchBase = search_base,
+ searchFilter = search_filter,
+ scope=ldapasn1.Scope('wholeSubtree'),
+ attributes=attributes,
+ searchControls=[show_deleted_control]
+ )
- if args.guid:
- entry_list = conn.extend.standard.paged_search(
- search_base = f'CN=Deleted Objects,DC={args.domain.split(".")[0]},DC={args.domain.split(".")[1]}',
- search_filter = f'(&(objectGuid={args.guid})(isDeleted=TRUE))',
- search_scope = 'SUBTREE',
- attributes = ['distinguishedName'],
- controls= [
- ('1.2.840.113556.1.4.417', True, None)
- ]
- )
+ except Exception as e:
+ print("[!] Search Error: ", e)
+
+ for item in resp:
+ if isinstance(item, ldapasn1.SearchResultEntry):
+ entry_list.append(item)
if not entry_list:
- print("[*] Could not find an object with the supplied GUID")
+ print("[!] Could not find any object with the supplied GUID")
sys.exit()
else:
+ data = []
for entry in entry_list:
- attrs = entry.get('attributes')
- if attrs:
- dn = attrs.get('distinguishedName')
- print(f"[*] Found Object : {dn}")
- cn = dn.split(':')[0].split('\\')[0]
-
- else:
- print("[!] Could not extract DN for the target object")
-
- new_dn = f"{cn},{args.ou}"
-
+ attrs = {}
+ for attr in entry['attributes']:
+ attr_name = str(attr['type'])
+ attr_values = [str(val) for val in attr['vals']]
+ attrs[attr_name] = attr_values
+
+ if not attrs:
+ continue
+
+ dn= attrs.get('distinguishedName', [''])[0]
+ print(f"[*] Object Found: {dn}")
+ cn = dn.split(':')[0].split('\\')[0]
+ new_dn = f"{cn},{args.ou}"
+
+
+
+
+
+ changes = SequenceOf()
+ change1 = Sequence()
+ change1.setComponentByPosition(0, Integer(1))
+ mod1 = Sequence()
+ mod1.setComponentByPosition(0, OctetString("isDeleted"))
+ mod1.setComponentByPosition(1, SetOf())
+ change1.setComponentByPosition(1, mod1)
+ changes.append(change1)
+ change2 = Sequence()
+ change2.setComponentByPosition(0, Integer(2))
+ mod2 = Sequence()
+ mod2.setComponentByPosition(0, OctetString("distinguishedName"))
+ vals2 = SetOf()
+ vals2.append(OctetString(new_dn))
+ mod2.setComponentByPosition(1, vals2)
+ change2.setComponentByPosition(1, mod2)
+ changes.append(change2)
+ req = ModifyRequest()
+ req.setComponentByName('object', OctetString(dn))
+ req.setComponentByName('changes', changes)
+ ctrl = Control()
+ ctrl['controlType'] = '1.2.840.113556.1.4.417'
+ ctrl['criticality'] = True
+ ctrl['controlValue'] = b''
+ print("[*] Restoring Object with GUID: ", args.guid)
try:
- changes = {
- 'isDeleted': [(MODIFY_DELETE, [])],
- 'distinguishedName': [(MODIFY_REPLACE, [new_dn])]
-
- }
-
- print("[*] Attempting to restore object")
- results = conn.modify(
- dn,
- changes,
- controls=[
- ('1.2.840.113556.1.4.417', True, None)
- ]
- )
- if results:
- print("[*] Object restored successfully")
- print(f"[*] New DN: {new_dn}")
- else:
- print(f"[!] Could not restore object: {conn.result} ")
- print(f"[!] Error: {conn.last_error}")
+ resp = conn.send(req, controls=[ctrl])
+ print("[+] Restore successful!")
+ print(f"[+] New DN = {new_dn}")
except Exception as e:
- print("[!] Error during restore operation: ", e)
-
- except Exception as e:
- print("[-] An error has occured", e)
+ print("[!] Restore failed:", e)
+
+ except Exception as e:
+ print("[!] Error during restore operation: ", e)
def main():
@@ -232,7 +290,11 @@ python3 {sys.argv[0]} find --domain example.com --username admin --password pass
python3 {sys.argv[0]} restore --domain example.com --username admin --password password123 --target 10.10.11.70 --guid f80369c8-96a2-4a7f-a56c-9c15edd7d1e3 --ou "OU=Staff,DC=evilcorp,DC=com"
# Pass-The-Hash Support
-python3 resurrect.py find --domain example.com --username admin --ldaps --target 10.10.11.70 --hash 149e0ed1f84c8fd4ecb11a9c2ab7af2
+python3 {sys.argv[0]} find --domain example.com --username admin --ldaps --target 10.10.11.70 --hash 149e0ed1f84c8fd4ecb11a9c2ab7af2
+
+# Kerberos Support
+python3 {sys.argv[0]} find --domain example.com --username admin --ldaps --dc-host dc.example.com -k
+python3 {sys.argv[0]} restore --domain example.com --username admin -k --dc-host dc.example.com --guid f80369c8-96a2-4a7f-a56c-9c15edd7d1e3 --ou "OU=Staff,DC=evilcorp,DC=com"
"""
)
@@ -248,10 +310,11 @@ python3 resurrect.py find --domain example.com --username admin --ldaps --target
subparsers.add_argument("--domain", help="Target Domain", required=True)
subparsers.add_argument("--username", help="Username", required=True)
subparsers.add_argument("--password", help="Password", required=False)
- subparsers.add_argument("--dc", help="Domain Controller", required=False)
+ subparsers.add_argument("--dc-host", help="Domain Controller FQDN", required=False)
subparsers.add_argument("--target", help="Target", required=False)
subparsers.add_argument("--ldaps", help="Force LDAP to authenticate over SSL", action="store_true", required=False)
subparsers.add_argument("--hash", help="LM:NTLM hash", required=False)
+ subparsers.add_argument("-k", help="Use Kerberos for Authentication",action="store_true",required=False)
find_parser = subparsers.add_parser(
'find',