SkillAgentSearch skills...

ShadowAdmin

No description available

Install / Use

/learn @rbmm/ShadowAdmin
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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

SspirLogonSystemManagedAdmin CreateShadowAdminAccount

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

LsapCanLogonShadowAdmin

(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:

  1. checked are user is shadow admin by Sid and name (for Legacy Shadow Admin)
  2. if `Ls
View on GitHub
GitHub Stars22
CategoryDevelopment
Updated1mo ago
Forks1

Languages

C

Security Score

70/100

Audited on Feb 27, 2026

No findings