Figure 2 Running as Alice
HANDLE htok = 0;
LogonUser( "Alice", ".", "password",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&htok );
STARTUPINFO si = { sizeof si, 0, "" };
PROCESS_INFORMATION pi;
CreateProcessAsUser( htok, // the security context
0, "msdev.exe", // command line
0, 0, // security attributes
FALSE, // handle inheritance
0, // creation flags
0, // environment block
0, // current directory
&si, &pi );
CloseHandle( htok );
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
Figure 3 Member of Administrator?
bool bIsAdmin = false;
SID_IDENTIFIER_AUTHORITY ntauth =
SECURITY_NT_AUTHORITY;
void* psidAdmin = 0;
AllocateAndInitializeSid( &ntauth, 2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, &psidAdmin );
HANDLE htok = 0;
OpenProcessToken( GetCurrentProcess(),
TOKEN_QUERY, &htok );
DWORD cb = 0;
GetTokenInformation( htok, TokenGroups,
0, 0, &cb );
TOKEN_GROUPS* ptg = (TOKEN_GROUPS*) malloc(cb);
GetTokenInformation( htok, TokenGroups,
ptg, cb, &cb );
for ( DWORD i = 0; i < ptg->GroupCount; ++i )
if ( EqualSid( psidAdmin, ptg->Groups[i].Sid ) )
break;
bIsAdmin = i != ptg->GroupCount;
free( ptg );
CloseHandle( htok );
FreeSid( psidAdmin );
Figure 5 Accessing WinSta0
void GrantSessionSIDAccessToWinstationAndDesktop( HANDLE htok )
{
// get the list of groups from the token
DWORD cbtgs = 0;
GetTokenInformation( htok, TokenGroups, 0, 0, &cbtgs );
if ( ERROR_INSUFFICIENT_BUFFER != GetLastError() )
Err( L"GetTokenInformation" );
TOKEN_GROUPS* ptgs = reinterpret_cast<TOKEN_GROUPS*>(
HeapAlloc( g_hProcessHeap, 0, cbtgs ) );
if ( !GetTokenInformation( htok, TokenGroups, ptgs, cbtgs, &cbtgs ) )
Err( L"GetTokenInformation" );
// search for the the logon session SID
const TOKEN_GROUPS* ptgs = (TOKEN_GROUPS*)tgs;
const SID_AND_ATTRIBUTES* it = ptgs->Groups;
const SID_AND_ATTRIBUTES* end = it + ptgs->GroupCount;
while ( end != it )
{
if ( it->Attributes & SE_GROUP_LOGON_ID )
break;
++it;
}
if ( end == it )
Err( L"UNEXPECTED: No Logon SID in TokenGroups" );
// adjust the DACL on WinSta0 to grant access to the logon SID
HWINSTA hws = GetProcessWindowStation();
if ( !hws )
Err( L"GetProcessWindowStation" );
GrantAccess( hws, GENERIC_ALL, it->Sid );
CloseWindowStation( hws );
// adjust the DACL on the desktop to grant access to the logon SID
HDESK hd = GetThreadDesktop( GetCurrentThreadId() );
if ( !hd )
Err( L"GetThreadDesktop" );
GrantAccess( hd, GENERIC_ALL, it->Sid );
CloseDesktop( hd );
HeapFree( g_hProcessHeap, 0, ptgs );
}
// helper function that adjusts the DACL using SetEntriesInAcl
void GrantAccess( HANDLE h, DWORD grfAccess, void* psid )
{
// get the DACL we want to munge
// (I've omitted the implementation of GetUserObjectDacl for brevity,
// it only exists because GetSecurityInfo is broken on Windows NT 4.0 SP3)
void* psdToFree = 0;
ACL* pdaclOld = 0;
GetUserObjectDacl( h, pdaclOld, psdToFree );
// fill out a structure specifying how we want to change the DACL
EXPLICIT_ACCESS ea =
{
grfAccess,
GRANT_ACCESS,
NO_INHERITANCE,
{
0, NO_MULTIPLE_TRUSTEE,
TRUSTEE_IS_SID,
TRUSTEE_IS_GROUP,
reinterpret_cast<TCHAR*>( psid );
}
};
// create a new, modified DACL via our good friend SetEntriesInAcl
ACL* pdaclNew = 0;
DWORD nErr = SetEntriesInAcl( 1, &ea, pdaclOld, &pdaclNew );
if ( NO_ERROR != nErr )
Err( L"SetEntriesInAcl", nErr );
LocalFree( psdToFree );
// apply the change to the window station or desktop object
nErr = SetSecurityInfo( h, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION,
0, 0, pdaclNew, 0 );
if ( NO_ERROR != err )
Err( L"SetSecurityInfo", nErr );
HeapFree( g_hProcessHeap, pdaclNew );
}
Figure 6 load.cpp
class UserEnvInterface
{
public:
// signatures of entrypoints into userenv.dll
typedef BOOL (WINAPI * CEB_FCN) ( void**, HANDLE, BOOL );
typedef BOOL (WINAPI * DEB_FCN) ( void* );
typedef BOOL (WINAPI * LUP_FCN) ( HANDLE, PROFILEINFO* );
typedef BOOL (WINAPI * UUP_FCN) ( HANDLE, HANDLE );
// construtor loads the library,
// explicitly linking to each entry point
UserEnvInterface()
{
m_hModule = LoadLibrary( L"userenv.dll" );
if ( !m_hModule )
Err( L"LoadLibrary( userenv.dll )" );
_getAddr( "CreateEnvironmentBlock", &m_createEnvironmentBlock );
_getAddr( "DestroyEnvironmentBlock", &m_destroyEnvironmentBlock );
_getAddr( "LoadUserProfileW", &m_loadUserProfile );
_getAddr( "UnloadUserProfile", &m_unloadUserProfile );
}
// destructor unloads the library
~UserEnvInterface()
{
FreeLibrary( m_hModule );
}
// helper function avoids lots of ugly casting
template <typename T>
void _getAddr( const char* psz, T* pProc )
{
*pProc = reinterpret_cast<T>( GetProcAddress( m_hModule, psz ) );
if ( !*pProc )
Err( L"GetProcAddress" );
}
// here's the entrypoints we'll be able to use once constructed
CEB_FCN m_createEnvironmentBlock;
DEB_FCN m_destroyEnvironmentBlock;
LUP_FCN m_loadUserProfile;
UUP_FCN m_unloadUserProfile;
private:
HMODULE m_hModule;
};
Figure 7 final.cpp
void CmdAsUser( wchar_t* pszAccount, const wchar_t* pszPassword )
{
// called within monitor process to spawn target
// after loading user profile. When the target app exits,
// we unload the user profile and exit the monitor app.
const wchar_t* pszAuthority = L".";
const wchar_t* pszPrincipal = instr( pszAccount, L'\\' ) ?
ParseDomainAndPrincipal( pszAccount, pszAuthority )
: pszAccount;
HANDLE htok = 0;
if ( !LogonUser( const_cast<wchar_t*>( pszPrincipal ),
const_cast<wchar_t*>( pszAuthority ),
const_cast<wchar_t*>( pszPassword ),
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&htok ) )
Err( L"LogonUser" );
// adjust the interactive winsta/desktop DACLs
GrantSessionSIDAccessToWinstationAndDesktop( htok, true );
// load userenv.dll and map the entrypoints we need
UserEnvInterface userEnvInterface;
// for some very strange reason, LoadUserProfile needs
// not only the *token* for the user, but also the user's name...
// (LookupAccountSid is quite expensive for domain accounts,
// so we'll temporarily impersonate and call GetUserName,
// which is screwy, but never makes round-trips to the authority)
wchar_t szUserName[1024];
DWORD cchUserName = sizeof szUserName / sizeof *szUserName;
if ( !ImpersonateLoggedOnUser( htok ) )
Err( L"ImpersonateLoggedOnUser" );
if ( !GetUserName( szUserName, &cchUserName ) )
Err( L"GetUserName" );
RevertToSelf();
// load the user profile
PROFILEINFO profinfo = { sizeof profinfo, 0, szUserName };
if ( !userEnvInterface.m_loadUserProfile( htok, &profinfo ) )
Err( L"LoadUserProfile" );
// set up an environment block
void* pEnvBlock = 0;
if ( !userEnvInterface.m_createEnvironmentBlock( &pEnvBlock,
htok, FALSE ) )
Err( L"CreateEnvironmentBlock" );
// here's where we start the target process (cmd.exe)
// note that we give the command shell a custom title for clarity
wchar_t szcmd[256];
wsprintf( szcmd, L"cmd.exe /K title %s\\%s", pszAuthority,
pszPrincipal );
STARTUPINFO si = { sizeof si };
PROCESS_INFORMATION pi;
DWORD grfOptions = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
if ( !CreateProcessAsUser( htok, 0, szcmd, 0, 0, FALSE,
grfOptions, pEnvBlock, 0, &si, &pi ) )
Err( L"CreateProcessAsUser" );
// free the environment block (it's already been copied)
userEnvInterface.m_destroyEnvironmentBlock( pEnvBlock );
// hang around until the command prompt terminates
WaitForSingleObject( pi.hProcess, INFINITE );
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
// unload the user profile
userEnvInterface.m_unloadUserProfile( htok, profinfo.hProfile );
// clean up the interactive winsta/desktop DACLs
GrantSessionSIDAccessToWinstationAndDesktop( htok, false );
CloseHandle( htok );
}