{"id":49182269,"url":"https://github.com/franckferman/ad-adminsdholder-toolkit","last_synced_at":"2026-04-23T02:01:13.030Z","repository":{"id":342586614,"uuid":"1174463637","full_name":"franckferman/AD-AdminSDHolder-Toolkit","owner":"franckferman","description":"Complete PowerShell toolkit to audit, detect backdoors, and clean orphaned AdminSDHolder (AdminCount=1) accounts. Language-agnostic using Well-Known SIDs. Works on any Active Directory.","archived":false,"fork":false,"pushed_at":"2026-03-25T22:49:55.000Z","size":38,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"stable","last_synced_at":"2026-03-26T17:11:08.235Z","etag":null,"topics":["active-directory","active-directory-audit","active-directory-exploitation","active-directory-script","active-directory-security","active-directory-tools","ad-pentesting","adminsdholder","backdoor","backdoor-detection"],"latest_commit_sha":null,"homepage":"https://franckferman.github.io/AD-AdminSDHolder-Toolkit/","language":"PowerShell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/franckferman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-06T13:24:02.000Z","updated_at":"2026-03-25T23:55:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/franckferman/AD-AdminSDHolder-Toolkit","commit_stats":null,"previous_names":["franckferman/adminsdholder-cleanup"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/franckferman/AD-AdminSDHolder-Toolkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2FAD-AdminSDHolder-Toolkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2FAD-AdminSDHolder-Toolkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2FAD-AdminSDHolder-Toolkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2FAD-AdminSDHolder-Toolkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/franckferman","download_url":"https://codeload.github.com/franckferman/AD-AdminSDHolder-Toolkit/tar.gz/refs/heads/stable","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2FAD-AdminSDHolder-Toolkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32162611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T17:06:48.269Z","status":"online","status_checked_at":"2026-04-23T02:00:06.710Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["active-directory","active-directory-audit","active-directory-exploitation","active-directory-script","active-directory-security","active-directory-tools","ad-pentesting","adminsdholder","backdoor","backdoor-detection"],"created_at":"2026-04-23T02:01:11.970Z","updated_at":"2026-04-23T02:01:13.019Z","avatar_url":"https://github.com/franckferman.png","language":"PowerShell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# AD-AdminSDHolder-Toolkit\n\n[![PowerShell](https://img.shields.io/badge/PowerShell-5.1+-5391FE?logo=powershell\u0026logoColor=white)](https://learn.microsoft.com/en-us/powershell/)\n[![Platform](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows\u0026logoColor=white)](https://github.com/franckferman/AD-AdminSDHolder-Toolkit)\n[![CI](https://github.com/franckferman/AD-AdminSDHolder-Toolkit/actions/workflows/ci.yml/badge.svg?branch=stable)](https://github.com/franckferman/AD-AdminSDHolder-Toolkit/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/License-AGPL--v3-16a34a)](LICENSE)\n\nPowerShell toolkit covering two AdminSDHolder attack surfaces: ACL backdoor detection/remediation, and orphaned `AdminCount=1` account cleanup.\n\n\u003e **Language-agnostic.** All group resolution goes through Well-Known SIDs and domain-relative RIDs — never hardcoded names. Works on any AD locale (EN, FR, DE, ES, ...).\n\n\u003c/div\u003e\n\n---\n\n## Table of Contents\n\n- [Background](#background)\n- [Project Structure](#project-structure)\n- [Requirements](#requirements)\n- [Usage](#usage)\n  - [In-Memory](#in-memory)\n  - [Standalone](#standalone)\n  - [Wrapper](#wrapper)\n  - [Module](#module)\n- [Script Reference](#script-reference)\n  - [Get-AdminSDHolderACL](#get-adminsdholderacl)\n  - [Repair-AdminSDHolderACL](#repair-adminsdholderacl)\n  - [Invoke-AdminSDHolderCleanup](#invoke-adminsdholdercleanup)\n  - [Add-AdminSDHolderBackdoor](#add-adminsdholderbackdoor)\n- [Tools](#tools)\n  - [Invoke-PSEncoder.py](#invoke-psencoderpy)\n  - [Sign-Scripts.ps1](#sign-scriptsps1)\n- [SID Reference](#sid-reference)\n- [License](#license)\n\n---\n\n## Background\n\n### SDProp and AdminSDHolder\n\nEvery 60 minutes, **SDProp** (running on the PDC Emulator) copies the Security Descriptor of `CN=AdminSDHolder,CN=System,\u003cdomain DN\u003e` onto every account that belongs to a protected group. It also sets `AdminCount=1` on those accounts and disables ACL inheritance.\n\nProtected groups covered by SDProp: Domain Admins, Enterprise Admins, Schema Admins, Cert Publishers, BUILTIN\\Administrators, Account Operators, Server Operators, Print Operators, Backup Operators.\n\n### AdminSDHolder ACL backdoor\n\nAn attacker with Domain Admin access grants a low-privilege account `GenericAll` (or `WriteDacl` / `WriteOwner`) on the AdminSDHolder object. SDProp then propagates that ACE to every protected account automatically — the target account gains persistent full control over the entire privileged tier without ever touching those accounts directly. The backdoor survives account password resets, DA session teardown, and most IR playbooks that focus on group membership rather than AdminSDHolder ACL.\n\n### Orphaned AdminCount accounts\n\nWhen a user is removed from a protected group, SDProp stops managing their ACL — but it does not revert `AdminCount` or restore inheritance. Over time this creates accounts with `AdminCount=1` and broken inheritance that are no longer in any protected group. Impact: PingCastle / BloodHound false positives, Helpdesk locked out of password resets and unlocks, operational noise masking real findings.\n\n---\n\n## Project Structure\n\n```\nAD-AdminSDHolder-Toolkit/\n├── AdminSDHolder.ps1           Interactive wrapper + -Action non-interactive mode\n├── AdminSDHolder.psm1          Optional PowerShell module\n│\n├── Private/\n│   ├── Constants.ps1           Shared SID constants, RID tables, dangerous-rights pattern\n│   └── Helpers.ps1             Shared functions: domain context, SID resolution, ACL backup\n│\n├── Public/\n│   ├── Get-AdminSDHolderACL.ps1         Audit AdminSDHolder ACL for unauthorized ACEs\n│   ├── Repair-AdminSDHolderACL.ps1      Remove unauthorized ACEs from AdminSDHolder\n│   ├── Invoke-AdminSDHolderCleanup.ps1  Remediate orphaned AdminCount=1 accounts\n│   └── Add-AdminSDHolderBackdoor.ps1    Insert a GenericAll backdoor ACE on AdminSDHolder\n│\n└── tools/\n    ├── Invoke-PSEncoder.py     Generate -EncodedCommand oneliners (execution policy bypass)\n    └── Sign-Scripts.ps1        Authenticode signing with RFC 3161 timestamp\n```\n\nEach `Public/` script auto-loads `Private/` when run from the standard layout, and falls back to inline definitions when run standalone from any directory.\n\n---\n\n## Requirements\n\n| | |\n|---|---|\n| PowerShell | 5.1+ |\n| AD module | `Import-Module ActiveDirectory` (RSAT) |\n| Privileges | Domain Admin for `-Remediate` and `Add-AdminSDHolderBackdoor` |\n| Network | Domain-joined machine, DC connectivity |\n\n```powershell\n# Windows Server\nInstall-WindowsFeature RSAT-AD-PowerShell\n\n# Windows 10/11\nAdd-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0\n```\n\n---\n\n## Usage\n\n### In-Memory\n\nRun directly from GitHub without cloning. `-EncodedCommand` bypasses `Restricted` and `AllSigned` execution policies.\n\n```powershell\n# IEX — detect AdminSDHolder ACL backdoors\niex (iwr 'https://raw.githubusercontent.com/franckferman/AD-AdminSDHolder-Toolkit/stable/Public/Get-AdminSDHolderACL.ps1' -UseBasicParsing).Content\n\n# IEX — audit orphaned AdminCount=1 accounts\niex (iwr 'https://raw.githubusercontent.com/franckferman/AD-AdminSDHolder-Toolkit/stable/Public/Invoke-AdminSDHolderCleanup.ps1' -UseBasicParsing).Content\n\n# IEX — audit AdminSDHolder ACL (repair mode, read-only by default)\niex (iwr 'https://raw.githubusercontent.com/franckferman/AD-AdminSDHolder-Toolkit/stable/Public/Repair-AdminSDHolderACL.ps1' -UseBasicParsing).Content\n\n# -EncodedCommand — execution policy not enforced (UTF-16LE Base64)\npowershell.exe -NonInteractive -NoProfile -NoLogo -EncodedCommand aQBlAHgAIAAoAGkAdwByACAAJwBoAHQAdABwAHMAOgAvAC8AcgBhAHcALgBnAGkAdABoAHUAYgB1AHMAZQByAGMAbwBuAHQAZQBuAHQALgBjAG8AbQAvAGYAcgBhAG4AYwBrAGYAZQByAG0AYQBuAC8AQQBEAC0AQQBkAG0AaQBuAFMARABIAG8AbABkAGUAcgAtAFQAbwBvAGwAawBpAHQALwBzAHQAYQBiAGwAZQAvAFAAdQBiAGwAaQBjAC8ARwBlAHQALQBBAGQAbQBpAG4AUwBEAEgAbwBsAGQAZQByAEEAQwBMAC4AcABzADEAJwAgAC0AVQBzAGUAQgBhAHMAaQBjAFAAYQByAHMAaQBuAGcAKQAuAEMAbwBuAHQAZQBuAHQA\n```\n\nGenerate custom `-EncodedCommand` oneliners with `tools/Invoke-PSEncoder.py`.\n\n### Standalone\n\n```powershell\n.\\Public\\Get-AdminSDHolderACL.ps1\n.\\Public\\Get-AdminSDHolderACL.ps1 -ExportCSV C:\\out\\acl.csv\n\n.\\Public\\Invoke-AdminSDHolderCleanup.ps1\n.\\Public\\Invoke-AdminSDHolderCleanup.ps1 -Remediate\n\n.\\Public\\Repair-AdminSDHolderACL.ps1\n.\\Public\\Repair-AdminSDHolderACL.ps1 -Remediate\n\n.\\Public\\Add-AdminSDHolderBackdoor.ps1 -Account \"svc_backup\"\n.\\Public\\Add-AdminSDHolderBackdoor.ps1 -Account \"svc_backup\" -Remove\n```\n\n### Wrapper\n\n**Interactive:**\n```powershell\n.\\AdminSDHolder.ps1\n```\n\n```\n  ==========================================================\n  =                                                        =\n  =         AdminSDHolder-Toolkit                         =\n  =         Active Directory Persistence Toolkit          =\n  =                                                        =\n  ==========================================================\n\n  ----------------------------------------------------------\n  |  AUDIT (read-only)                                     |\n  |    [1] Audit orphaned AdminCount accounts              |\n  |    [2] Detect AdminSDHolder ACL backdoors              |\n  |    [3] Full Audit (1 + 2)                              |\n  |                                                        |\n  |  REMEDIATION (modifies AD)                             |\n  |    [4] Cleanup orphaned AdminCount accounts            |\n  |    [5] Repair AdminSDHolder ACL                        |\n  |                                                        |\n  |  OFFENSIVE                                             |\n  |    [6] Insert AdminSDHolder backdoor ACE               |\n  |                                                        |\n  |    [Q] Quit                                            |\n  ----------------------------------------------------------\n```\n\n**Non-interactive (`-Action`):**\n```powershell\n.\\AdminSDHolder.ps1 -Action FullAudit\n.\\AdminSDHolder.ps1 -Action Detect\n.\\AdminSDHolder.ps1 -Action Repair\n.\\AdminSDHolder.ps1 -Action Cleanup\n.\\AdminSDHolder.ps1 -Action Backdoor\n```\n\n| Action | |\n|---|---|\n| `Audit` | Orphaned AdminCount audit — read-only |\n| `Detect` | AdminSDHolder ACL backdoor scan — read-only |\n| `FullAudit` | Audit + Detect — read-only |\n| `Cleanup` | Orphaned account remediation — writes to AD |\n| `Repair` | Remove unauthorized ACEs — writes to AD |\n| `Backdoor` | Insert GenericAll backdoor ACE — writes to AD |\n\n### Module\n\n```powershell\nImport-Module .\\AdminSDHolder.psm1\n\nGet-AdminSDHolderACL\nGet-AdminSDHolderACL -ExportCSV C:\\reports\\acl.csv\n\nInvoke-AdminSDHolderCleanup\nInvoke-AdminSDHolderCleanup -Remediate\n\nRepair-AdminSDHolderACL\nRepair-AdminSDHolderACL -Remediate -BackupPath C:\\backups\\acl_pre.csv\n\nAdd-AdminSDHolderBackdoor -Account \"svc_backup\"\nAdd-AdminSDHolderBackdoor -Account \"svc_backup\" -Remove\n```\n\n---\n\n## Script Reference\n\n### Get-AdminSDHolderACL\n\nReads the AdminSDHolder Security Descriptor and flags every ACE whose principal is not in the default whitelist.\n\n| Parameter | Type | |\n|---|---|---|\n| `-ExportCSV` | String | Export findings to CSV |\n\n**Threat detection:** any ACE with `GenericAll`, `WriteDacl`, or `WriteOwner` from a non-whitelisted SID is flagged `HIGH (Potential Backdoor)`.\n\n**Whitelist:**\n\n| SID | Principal |\n|---|---|\n| `S-1-5-18` | SYSTEM |\n| `S-1-5-10` | SELF |\n| `S-1-5-11` | Authenticated Users |\n| `S-1-1-0` | Everyone |\n| `S-1-5-32-544` | BUILTIN\\Administrators |\n| `S-1-5-32-554` | Pre-Windows 2000 Compatible Access |\n| `S-1-5-32-560` | Windows Authorization Access Group |\n| `S-1-5-32-561` | Terminal Server License Servers |\n| `\u003cDomainSID\u003e-512` | Domain Admins |\n| `\u003cDomainSID\u003e-519` | Enterprise Admins |\n| `\u003cDomainSID\u003e-517` | Cert Publishers |\n\n---\n\n### Repair-AdminSDHolderACL\n\nRemoves ACEs from AdminSDHolder that are not in the whitelist above.\n\n| Parameter | Type | |\n|---|---|---|\n| `-Remediate` | Switch | Remove entries after confirmation. Default: read-only. |\n| `-BackupPath` | String | Custom path for the pre-change ACL backup CSV. |\n\nAll removals are staged in-memory and committed in a single `CommitChanges()` call. A CSV backup of the ACL is written before any change is made. Uses `GetAccessRules($true, $true, ...)` — same scope as `Get-AdminSDHolderACL`.\n\n---\n\n### Invoke-AdminSDHolderCleanup\n\nIdentifies `AdminCount=1` users who are no longer in any SDProp-protected group and optionally remediates them.\n\n| Parameter | Type | |\n|---|---|---|\n| `-Remediate` | Switch | Reset AdminCount and restore ACL inheritance. Default: read-only. |\n\n**Protected groups resolved by SID:**\n\n| SID | Group |\n|---|---|\n| `\u003cDomainSID\u003e-512` | Domain Admins |\n| `\u003cDomainSID\u003e-517` | Cert Publishers |\n| `\u003cDomainSID\u003e-518` | Schema Admins |\n| `\u003cDomainSID\u003e-519` | Enterprise Admins |\n| `S-1-5-32-544` | BUILTIN\\Administrators |\n| `S-1-5-32-548` | Account Operators |\n| `S-1-5-32-549` | Server Operators |\n| `S-1-5-32-550` | Print Operators |\n| `S-1-5-32-551` | Backup Operators |\n\n**SafeList (never touched):** `-500` (Administrator), `-502` (krbtgt).\n\n**Primary group handling:** resolved by direct SID construction `$DomainSID-$PrimaryGroupId` against a HashSet — no LDAP roundtrip per user, no regex.\n\n**Per account remediation:**\n1. `Set-ADUser -Clear AdminCount`\n2. `SetAccessRuleProtection($false, $false)` — restores inheritance\n\n---\n\n### Add-AdminSDHolderBackdoor\n\nGrants `GenericAll` to a specified account on AdminSDHolder. SDProp propagates the ACE to every protected group member within 60 minutes.\n\n| Parameter | Type | |\n|---|---|---|\n| `-Account` | String (Mandatory) | SamAccountName of the target principal. |\n| `-Remove` | Switch | After insertion, wait for ENTER, then pull the ACE. |\n\nIdempotent — checks for an existing GenericAll ACE for the SID before inserting.\n\n```powershell\n# Insert — pull manually with Repair-AdminSDHolderACL -Remediate\n.\\Public\\Add-AdminSDHolderBackdoor.ps1 -Account \"svc_backup\"\n\n# Insert and pull on ENTER\n.\\Public\\Add-AdminSDHolderBackdoor.ps1 -Account \"svc_backup\" -Remove\n```\n\n**Validation chain:**\n```powershell\n# 1. Confirm clean baseline\n.\\AdminSDHolder.ps1 -Action Detect\n\n# 2. Insert backdoor ACE\n.\\Public\\Add-AdminSDHolderBackdoor.ps1 -Account \"svc_backup\"\n\n# 3. Confirm detection\n.\\AdminSDHolder.ps1 -Action Detect\n\n# 4. Remediate\n.\\AdminSDHolder.ps1 -Action Repair\n\n# 5. Confirm clean state\n.\\AdminSDHolder.ps1 -Action Detect\n```\n\n---\n\n## Tools\n\n### Invoke-PSEncoder.py\n\nEncodes PS1 files or inline commands as Base64 UTF-16LE and outputs a `powershell.exe -EncodedCommand` oneliner. `-EncodedCommand` is evaluated as an in-memory string — execution policy (`Restricted`, `AllSigned`) is not enforced.\n\n```\npython3 tools/Invoke-PSEncoder.py \u003cfile\u003e\npython3 tools/Invoke-PSEncoder.py -c \"STRING\"\n\nOptions:\n  --hidden    -WindowStyle Hidden\n  --bypass    -ExecutionPolicy Bypass\n  --32        SysWOW64 (32-bit PowerShell)\n  --noexit    -NoExit\n  -q          Oneliner only, no decoration\n```\n\n```bash\npython3 tools/Invoke-PSEncoder.py Public/Get-AdminSDHolderACL.ps1\n\npython3 tools/Invoke-PSEncoder.py -c \"Invoke-AdminSDHolderCleanup\"\n\npython3 tools/Invoke-PSEncoder.py --hidden Public/Repair-AdminSDHolderACL.ps1\n\n# Pipe to clipboard\npython3 tools/Invoke-PSEncoder.py -q Public/Get-AdminSDHolderACL.ps1 | xclip -selection clipboard\npython3 tools/Invoke-PSEncoder.py -q Public/Get-AdminSDHolderACL.ps1 | pbcopy\n```\n\n### Sign-Scripts.ps1\n\nAuthenticode signing with SHA-256 and RFC 3161 timestamp countersignature. A timestamped signature remains valid after the signing certificate expires — critical for EDR compatibility.\n\n```powershell\n# Sign with an existing cert (by thumbprint)\n.\\tools\\Sign-Scripts.ps1 -CertThumbprint \"ABCDEF1234...\"\n\n# Sign with a temporary self-signed cert (auto-created, auto-deleted after signing)\n.\\tools\\Sign-Scripts.ps1\n```\n\nIf no `-CertThumbprint` is provided, a `CodeSigningCert` valid for 24 hours is created in `Cert:\\CurrentUser\\My`, used for signing, then deleted immediately. The timestamp makes the signatures durable regardless.\n\n---\n\n## SID Reference\n\n### Domain-relative RIDs\n\n| RID | |\n|---|---|\n| `-500` | Built-in Administrator |\n| `-501` | Guest |\n| `-502` | krbtgt |\n| `-512` | Domain Admins |\n| `-513` | Domain Users |\n| `-514` | Domain Guests |\n| `-515` | Domain Computers |\n| `-516` | Domain Controllers |\n| `-517` | Cert Publishers |\n| `-518` | Schema Admins |\n| `-519` | Enterprise Admins |\n| `-520` | Group Policy Creator Owners |\n| `-521` | Read-only Domain Controllers |\n\n### BUILTIN\n\n| SID | |\n|---|---|\n| `S-1-5-32-544` | Administrators |\n| `S-1-5-32-545` | Users |\n| `S-1-5-32-546` | Guests |\n| `S-1-5-32-548` | Account Operators |\n| `S-1-5-32-549` | Server Operators |\n| `S-1-5-32-550` | Print Operators |\n| `S-1-5-32-551` | Backup Operators |\n| `S-1-5-32-554` | Pre-Windows 2000 Compatible Access |\n| `S-1-5-32-555` | Remote Desktop Users |\n| `S-1-5-32-556` | Network Configuration Operators |\n| `S-1-5-32-560` | Windows Authorization Access Group |\n| `S-1-5-32-561` | Terminal Server License Servers |\n\n### Universal\n\n| SID | |\n|---|---|\n| `S-1-1-0` | Everyone |\n| `S-1-5-10` | SELF |\n| `S-1-5-11` | Authenticated Users |\n| `S-1-5-18` | SYSTEM |\n| `S-1-5-19` | Local Service |\n| `S-1-5-20` | Network Service |\n\n---\n\n## License\n\nGNU Affero General Public License v3.0 — see [LICENSE](LICENSE).\n\n---\n\n**franckferman**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranckferman%2Fad-adminsdholder-toolkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffranckferman%2Fad-adminsdholder-toolkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranckferman%2Fad-adminsdholder-toolkit/lists"}