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 );
 }