summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author5epi0l <91630053+5epi0l@users.noreply.github.com>2025-11-17 08:14:50 +0530
committerGitHub <noreply@github.com>2025-11-17 08:14:50 +0530
commitb7ddacd50407f3d15d96cbdf32e500798a007633 (patch)
tree33f5bd5a0f4764e93340ddd5e456f80fbb16b17d
parent4365cce1512a0edb3b00d8d82b23020eedcc2ebc (diff)
Add files via upload
-rw-r--r--Resurrect/requirements.txt2
-rw-r--r--Resurrect/resurrect.py285
2 files changed, 287 insertions, 0 deletions
diff --git a/Resurrect/requirements.txt b/Resurrect/requirements.txt
new file mode 100644
index 0000000..94fd5dd
--- /dev/null
+++ b/Resurrect/requirements.txt
@@ -0,0 +1,2 @@
+tabulate
+ldap3
diff --git a/Resurrect/resurrect.py b/Resurrect/resurrect.py
new file mode 100644
index 0000000..b788fc0
--- /dev/null
+++ b/Resurrect/resurrect.py
@@ -0,0 +1,285 @@
+# Author: x4c1s
+# LICENSE: WTFPL
+# Date: 2025-11-17
+
+#!/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
+)
+
+
+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:
+ 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 not conn.bind():
+ print("[!] Could not connect to the server")
+ sys.exit()
+ else:
+ print("[*] Authentication successful")
+
+ 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 = 5,
+ generator=False
+ )
+ if not entry_list:
+ print("[*] No deleted users found or your current user doesn't have the permissions to view them")
+ sys.exit()
+ else:
+ print("[*] Deleted user(s) found\r\t")
+ for entry in entry_list:
+ attrs = entry.get('attributes')
+ if not attrs:
+ continue
+ guid = attrs.get('cn').split('\n')[1].split(':')[1]
+ ou = attrs.get('lastKnownParent')
+ sam = attrs.get('sAMAccountName')
+ objectclass = attrs.get('objectClass')[3]
+ data = [[sam, guid, ou, objectclass]]
+ headers = ['username', 'GUID', 'OU', 'objectClass', 'DN']
+ print(tabulate(data, headers=headers, tablefmt='grid'))
+ except Exception as e:
+ print("[-] An error has occured", e)
+
+
+def restore_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:
+ 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 not conn.bind():
+ print("[-] Could not connect to the server")
+ sys.exit()
+ else:
+ print("[*] Authentication successful")
+
+ 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)
+ ],
+ paged_size = 5,
+ generator=False
+ )
+ if not entry_list:
+ print("[*] Could not find an object with the supplied GUID")
+ sys.exit()
+ else:
+ 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}"
+
+
+ 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(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}")
+ except Exception as e:
+ print("[!] Error during restore operation: ", e)
+
+ except Exception as e:
+ print("[-] An error has occured", e)
+
+
+
+
+
+def main():
+
+ parser = argparse.ArgumentParser(
+ description='Remotely find and restore TombStoned Users',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=f"""
+Examples:
+# Find Deleted Users and Computers
+python3 {sys.argv[0]} find --domain example.com --username admin --password password123 --target 10.10.11.70
+
+# Restore Objects to their OUs
+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
+ """
+)
+
+
+ subparsers = parser.add_subparsers(
+ title='commands',
+ dest='command',
+ help='Command to execute',
+ metavar='{find,restore}'
+ )
+
+ def add_common_args(subparsers):
+ 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("--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)
+
+ find_parser = subparsers.add_parser(
+ 'find',
+ help='Search for Deleted Users and Computers in Active Directory'
+ )
+
+ restore_parser = subparsers.add_parser(
+ 'restore',
+ help='Restore Deleted Objects to their respective OU'
+ )
+ add_common_args(find_parser)
+ find_parser.set_defaults(func=find_deleted_objects)
+
+
+ add_common_args(restore_parser)
+ restore_parser.add_argument('--guid', required=True, help='GUID of the deleted object')
+ restore_parser.add_argument('--ou', required=True, help='Target OU to restore to')
+ restore_parser.set_defaults(func=restore_deleted_objects)
+
+ args = parser.parse_args()
+ if not args.command:
+ parser.print_help()
+ sys.exit(1)
+ args.func(args)
+
+if __name__ == "__main__":
+ main()
+
+