ShadowAdmin
No description available
Install / Use
/learn @rbmm/ShadowAdminREADME
ShadowAdmin
based on https://specterops.io/blog/2025/06/18/administrator-protection/
New Api
samlib.dll (client dll to samsrv.dll) now export 2 new API
EXTERN_C_START
NTSYSCALLAPI
NTSTATUS
NTAPI
SamiFindOrCreateShadowAdminAccount(_In_ PSID UserSid, _Out_ PWSTR* AdminName, _Out_ PSID ShadowSid);
NTSYSCALLAPI
NTSTATUS
NTAPI
SamiIsShadowAdminAccount(_In_ PSID ShadowSid, _Out_ PBOOLEAN pbShadow, _Out_ PWSTR* AdminName, _Out_ PSID UserSid);
EXTERN_C_END
( AdminName, and UserSid on return, need free with SamFreeMemory api)
demo usage is
EXTERN_C_START
PVOID __imp_SamiIsShadowAdminAccount = 0, __imp_SamiFindOrCreateShadowAdminAccount = 0;
EXTERN_C_END
#define GetApi(hmod, name) (__imp_##name = GetProcAddress(hmod, #name))
void TestNewApi(PSID UserSid)
{
PSID ShadowSid;
PWSTR AdminName;
BOOLEAN bShadow;
NTSTATUS status = SamiFindOrCreateShadowAdminAccount(UserSid, &AdminName, &ShadowSid);
if (0 <= status)
{
DbgPrint("AdminName=\"%ws\"\n", AdminName);
SamFreeMemory(AdminName);
status = SamiIsShadowAdminAccount(ShadowSid, &bShadow, &AdminName, &UserSid);
SamFreeMemory(ShadowSid);
if (0 <= status && bShadow)
{
DbgPrint("AdminName=\"%ws\"\n", AdminName);
SamFreeMemory(AdminName);
SamFreeMemory(UserSid);
}
}
}
BOOLEAN IsShadowAdminApiPresent()
{
if (HMODULE hmod = GetModuleHandleW(L"samlib.dll"))
{
if (GetApi(hmod, SamiFindOrCreateShadowAdminAccount) && GetApi(hmod, SamiIsShadowAdminAccount))
{
return TRUE;
}
}
return FALSE;
}
Enum Shadow Admins
we can enum all Shadow Admins (Name, Sid and Linked User Sid) with next code:
NTSTATUS EnumShadowAdmins()
{
SAM_HANDLE ServerHandle, DomainHandle, AliasHandle;
OBJECT_ATTRIBUTES oa = { sizeof(oa) };
NTSTATUS status = SamConnect(0, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, &oa);
if (0 <= status)
{
SID BUILTIN = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, {SECURITY_BUILTIN_DOMAIN_RID } };
status = SamOpenDomain(ServerHandle, DOMAIN_EXECUTE|DOMAIN_READ, &BUILTIN, &DomainHandle);
SamCloseHandle(ServerHandle);
if (0 <= status)
{
status = SamOpenAlias(DomainHandle, ALIAS_LIST_MEMBERS, DOMAIN_ALIAS_RID_ADMINS, &AliasHandle);
SamCloseHandle(DomainHandle);
if (0 <= status)
{
ULONG MemberCount;
PSID *MemberIds, UserSid, Sid;
status = SamGetMembersInAlias(AliasHandle, &MemberIds, &MemberCount);
SamCloseHandle(AliasHandle);
if (0 <= status)
{
PVOID buf = MemberIds;
if (MemberCount)
{
do
{
BOOLEAN bShadowAdmin;
PWSTR Name;
if (0 <= (status = SamiIsShadowAdminAccount(Sid = *MemberIds++, &bShadowAdmin, &Name, &UserSid)))
{
if (bShadowAdmin)
{
WCHAR sz1[SECURITY_MAX_SID_STRING_CHARACTERS], sz2[SECURITY_MAX_SID_STRING_CHARACTERS];
UNICODE_STRING us1 = { 0, sizeof(sz1), sz1 }, us2 = { 0, sizeof(sz2), sz2 };
RtlConvertSidToUnicodeString(&us1, Sid, FALSE);
RtlConvertSidToUnicodeString(&us2, UserSid, FALSE);
DbgPrint("%ws: %wZ -> %wZ\n", Name, &us1, &us2);
SamFreeMemory(UserSid);
SamFreeMemory(Name);
}
}
} while (--MemberCount);
}
SamFreeMemory(buf);
}
}
}
}
return status;
}
How Shadow Account Created ?
by direct call SamiFindOrCreateShadowAdminAccount or .. by call NtQueryInformationToken with TokenLinkedToken on admin user. let look on call stack
ntoskrnl.exe!SwapContext + 4b
ntoskrnl.exe!KiSwapContext + 76
ntoskrnl.exe!KiSwapThread + 3c8
ntoskrnl.exe!KiCommitThreadWait + 370
ntoskrnl.exe!KeWaitForSingleObject + 4b2
ntoskrnl.exe!KiSchedulerApc + 12b
ntoskrnl.exe!KiDeliverApc + 2c9
ntoskrnl.exe!KiSwapThread + 45a
ntoskrnl.exe!KiCommitThreadWait + 370
ntoskrnl.exe!KeWaitForSingleObject + 4b2
ntoskrnl.exe!AlpcpWaitForSingleObject + 4b
ntoskrnl.exe!AlpcpSignalAndWait + 2a1
ntoskrnl.exe!AlpcpReceiveSynchronousReply + 5a
ntoskrnl.exe!AlpcpProcessSynchronousRequest + 1e1
ntoskrnl.exe!NtAlpcSendWaitReceivePort + 1f5
ntoskrnl.exe!KiSystemServiceCopyEnd + 25
ntoskrnl.exe!KiServiceLinkage
msrpc.sys!long LRPC_BASE_CCALL::DoSendReceive(void) + 72
msrpc.sys!virtual long LRPC_BASE_CCALL::SendReceive(_RPC_MESSAGE *) + 52
msrpc.sys!NdrSendReceive + 3a
msrpc.sys!NdrpClientCall3 + 128
msrpc.sys!NdrClientCall3 + 93
ksecdd.sys!SspipLogonSystemManagedAdmin + c9
ksecdd.sys!KsecLogonSystemManagedAdmin + 9
ntoskrnl.exe!SepLogonSystemManagedAdmin + 43
ntoskrnl.exe!NtQueryInformationToken + 1808
ntoskrnl.exe!KiSystemServiceCopyEnd + 25
ntdll.dll!ZwQueryInformationToken + 14
consent.exe!unsigned long CuipGetElevatedToken(void * *) + 8a
consent.exe!WinMain + 96e
consent.exe!__mainCRTStartup + 1ac
kernel32.dll!BaseThreadInitThunk + 17
ntdll.dll!RtlUserThreadStart + 20
so from kernel called SepLogonSystemManagedAdmin which call KsecLogonSystemManagedAdmin -> SspipLogonSystemManagedAdmin from ksecdd.sys
this make ALPC call to lsass. let look for stack in lsass now
ntdll.dll!ZwAlpcSendWaitReceivePort + 14
rpcrt4.dll!long LRPC_BASE_CCALL::DoSendReceive(void) + 152
rpcrt4.dll!virtual long LRPC_CCALL::SendReceive(_RPC_MESSAGE *) + 76
rpcrt4.dll!NdrSendReceive + 71
rpcrt4.dll!NdrpClientCall3 + 744
rpcrt4.dll!NdrClientCall3 + ed
samlib.dll!SamiFindOrCreateShadowAdminAccount + bd
lsasrv.dll!long LsapAuApiDispatchLogonSystemManagedAdmin(_LUID,void * *) + e8
lsasrv.dll!long SspiExLogonSystemManagedAdmin(void *,_CLIENT_ID *,_LUID,void * *) + a8
sspisrv.dll!SspirLogonSystemManagedAdmin + 82
rpcrt4.dll!Invoke + 73
rpcrt4.dll!long Ndr64StubWorker(void *,void *,_RPC_MESSAGE *,_MIDL_SERVER_INFO_ *,long (*const *)(void),_MIDL_SYNTAX_INFO *,unsigned long *) + 4e3
rpcrt4.dll!NdrServerCallAll + 3c
rpcrt4.dll!DispatchToStubInCNoAvrf + 17
rpcrt4.dll!long RPC_INTERFACE::DispatchToStubWorker(_RPC_MESSAGE *,unsigned int,int,long *) + 2b0
rpcrt4.dll!long RPC_INTERFACE::DispatchToStub(_RPC_MESSAGE *,unsigned int,int,long *,RPCP_INTERFACE_GROUP *) + 198
rpcrt4.dll!long LRPC_SCALL::DispatchRequest(int *) + 5f7
rpcrt4.dll!void LRPC_SCALL::QueueOrDispatchCall(void) + e9
rpcrt4.dll!void LRPC_SCALL::HandleRequest(_PORT_MESSAGE *,_PORT_MESSAGE *,void *,unsigned __int64,RPCP_ALPC_HANDLE_ATTR *) + 2af
rpcrt4.dll!void LRPC_ADDRESS::HandleRequest(_PORT_MESSAGE *,RPCP_ALPC_MESSAGE_ATTRIBUTES *,_PORT_MESSAGE *,int) + 3a2
rpcrt4.dll!void LRPC_ADDRESS::ProcessIO(void *) + 2f7
rpcrt4.dll!void LrpcIoComplete(_TP_CALLBACK_INSTANCE *,void *,_TP_ALPC *,void *) + dd
ntdll.dll!TppAlpcpExecuteCallback + 410
ntdll.dll!TppWorkerThread + 512
kernel32.dll!BaseThreadInitThunk + 17
ntdll.dll!RtlUserThreadStart + 20
the SspirLogonSystemManagedAdmin called from sspisrv.dll which call
NTSTATUS SspiExLogonSystemManagedAdmin(
_In_ PLSA_CLIENT_REQUEST ClientRequest,
_In_ PCLIENT_ID cid,
_In_ LUID Luid,
_Out_ PHANDLE phToken);
inside lsasrv.dll
and it call
NTSTATUS LsapAuApiDispatchLogonSystemManagedAdmin( _In_ LUID Luid, _Out_ PHANDLE phToken);
and inside it SamiFindOrCreateShadowAdminAccount(Sid, &AdminName, ) already called, and then LogonUserExExW(AdminName, L".", L"", ...)
( user Sid is taken from token associated with Luid )
SamiFindOrCreateShadowAdminAccount again do ALPC call, so let look for this serverthread stack
samsrv.dll!ShadowAdminAccount::ShadowAdminAccount(void) + 7
samsrv.dll!long SAAManager::LookupOrCreateShadowAdminAccount(void *,ShadowAdminAccount * *) + 3b
samsrv.dll!long SampFindOrCreateShadowAdminAccount(void *,unsigned short * *,void * *) + 15b
samsrv.dll!SamrFindOrCreateShadowAdminAccount + 10f
rpcrt4.dll!Invoke + 73
rpcrt4.dll!long Ndr64StubWorker(void *,void *,_RPC_MESSAGE *,_MIDL_SERVER_INFO_ *,long (*const *)(void),_MIDL_SYNTAX_INFO *,unsigned long *) + 4e3
rpcrt4.dll!NdrServerCallAll + 3c
rpcrt4.dll!DispatchToStubInCNoAvrf + 17
rpcrt4.dll!long RPC_INTERFACE::DispatchToStubWorker(_RPC_MESSAGE *,unsigned int,int,long *) + 2b0
rpcrt4.dll!long RPC_INTERFACE::DispatchToStub(_RPC_MESSAGE *,unsigned int,int,long *,RPCP_INTERFACE_GROUP *) + 198
rpcrt4.dll!long LRPC_SCALL::DispatchRequest(int *) + 5f7
rpcrt4.dll!void LRPC_SCALL::QueueOrDispatchCall(void) + e9
rpcrt4.dll!void LRPC_SCALL::HandleRequest(_PORT_MESSAGE *,_PORT_MESSAGE *,void *,unsigned __int64,RPCP_ALPC_HANDLE_ATTR *) + 2af
rpcrt4.dll!void LRPC_ADDRESS::HandleRequest(_PORT_MESSAGE *,RPCP_ALPC_MESSAGE_ATTRIBUTES *,_PORT_MESSAGE *,int) + 3a2
rpcrt4.dll!void LRPC_ADDRESS::ProcessIO(void *) + 2f7
rpcrt4.dll!void LrpcIoComplete(_TP_CALLBACK_INSTANCE *,void *,_TP_ALPC *,void *) + dd
ntdll.dll!TppAlpcpExecuteCallback + 410
ntdll.dll!TppWorkerThread + 512
kernel32.dll!BaseThreadInitThunk + 17
ntdll.dll!RtlUserThreadStart + 20
SamrFindOrCreateShadowAdminAccount is called inside samsrv.dll
so call to NtQueryInformationToken with TokenLinkedToken lead finally to this...
the SamrFindOrCreateShadowAdminAccount find or create shadow admin account for admin user.
and LogonUserExExW create token for this shadow admin, which returned as TokenLinkedToken
the complete trace of SspiExLogonSystemManagedAdmin it can be looked with tvi.exe tool

let now look for LogonUserExExW in more details. after LsapCallAuthPackageForLogon success, new code is added

(look postlogon.cpp for full code )
PCWSTR username; // user Name
PSID Sid; // user Sid
HANDLE hToken; // of process which call LsaLogonUser
ULONG dwProcessId; // of process which call LsaLogonUser
if (LsapIsShadowAdminUser(username) || LsapIsShadowAdminUser(Sid))
{
if (!LsapShadowAdminEnabled || LsapCanLogonShadowAdmin(hToken, dwProcessId))
{
return STATUS_ACCESS_DENIED;
}
}
so:
- checked are user is shadow admin by Sid and name (for Legacy Shadow Admin)
- if `Ls
