Search code examples
c++serviceimpersonation

How do I return the users to the state, they were before impersonation?


I want to use impersonation in my project to run it in Services correctly.

I see some code that do something like this:

Get explorer.exe PID, then OpenProcess with that PID and DuplicateTokenEx

after that, the code CreateThread and then SetThreadToken and ResumeThread

This is the code:

HANDLE ExplorerToken(VOID) 
{
    PROCESSENTRY32W procEntry;
    HANDLE  Snap = NULL; 
    HANDLE  Process = NULL;
    HANDLE  PToken = NULL;
    LPCWSTR TProce = L"explorer.exe";
    DWORD   TargetSID = -1;
    DWORD   SId = -1;
    DWORD   TargetPID = -1;
    BOOL    WellDone = TRUE;
    HANDLE ExplorerToken = NULL;
        if(ExplorerToken != NULL)
    {
        return ExplorerToken;
    }

    SId = WTSGetActiveConsoleSessionId();
    procEntry.dwSize = sizeof(PROCESSENTRY32W);
    Snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (!Process32FirstW(Snap, &procEntry))
    {
        WellDone = FALSE;
    }
    do
    {
        if (lstrcmpiW(procEntry.szExeFile, TProce) == 0)
        {
            if (ProcessIdToSessionId(procEntry.th32ProcessID, &TargetSID) && TargetSID == SId)
            {
                TargetPID = procEntry.th32ProcessID;
                break;
            }
        }
    } while (Process32NextW(Snap, &procEntry));


    if(TargetPID == -1)
    {
        WellDone = FALSE;
    }
    Process = OpenProcess(MAXIMUM_ALLOWED, FALSE, TargetPID);

    if(!OpenProcessToken(Process, MAXIMUM_ALLOWED, &PToken))
    {
        WellDone = FALSE;
    }

    if(!DuplicateTokenEx(PToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenImpersonation, &ExplorerToken))
    {
        WellDone = FALSE;
    }

    if(WellDone == FALSE)
    {
        return NULL;
    }
    else
    {
        return ExplorerToken;
    }
}

VOID CcCryptUnprotectData(LPVOID data)
{
    PCUD_PARAMS pCudParams;
    pCudParams = (PCUD_PARAMS)data;

    pCudParams->bRetVal = pCudParams->fp_CryptUnprotectData(
        pCudParams->pDataIn,
        pCudParams->ppszDataDescr,
        pCudParams->pOptionalEntropy,
        pCudParams->pvReserved,
        pCudParams->pPromptStruct,
        pCudParams->dwFlags,
        pCudParams->pDataOut);

}

BOOL _CryptUnprotectData(__in  DATA_BLOB *pDataIn, __out_opt  LPWSTR *ppszDataDescr, __in_opt   DATA_BLOB *pOptionalEntropy,
    __in  PVOID pvReserved, __in_opt   CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, __in DWORD dwFlags, __out DATA_BLOB *pDataOut )
{
    HANDLE ThreadHandle;
    CUD_PARAMS cudParams;
    DWORD   ThreadIdArray   = 0;
    HMODULE hmCrypt32 = NULL;
    HANDLE ExplorerToken;

    cudParams.pDataIn = pDataIn;
    cudParams.ppszDataDescr = ppszDataDescr;
    cudParams.pOptionalEntropy = pOptionalEntropy;
    cudParams.pvReserved = pvReserved;
    cudParams.pPromptStruct = pPromptStruct;
    cudParams.dwFlags = dwFlags;
    cudParams.pDataOut = pDataOut;

    cudParams.bRetVal = FALSE;

    if(MyCryptUnprotectData_OutlookPassword == NULL)
    {
        if(hmCrypt32 == NULL)
        {
            hmCrypt32 = LoadLibraryW(L"Crypt32.dll");
        }
        MyCryptUnprotectData_OutlookPassword = (BOOL (WINAPI* )(DATA_BLOB *pDataIn, LPWSTR *ppszDataDescr, DATA_BLOB *pOptionalEntropy, PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,  DWORD dwFlags, DATA_BLOB *pDataOut))GetProcAddress(hmCrypt32, "CryptUnprotectData");
    }

    cudParams.fp_CryptUnprotectData = MyCryptUnprotectData_OutlookPassword;

    ExplorerToken = ExplorerToken();

    if(ExplorerToken == NULL)
    {
        return FALSE;
    }

    ThreadHandle =  CreateThread(NULL,
        0,                                                   // use default stack size  
        (LPTHREAD_START_ROUTINE)CcCryptUnprotectData,       // thread function name
        (LPVOID)&cudParams,                                  // argument to thread function 
        CREATE_SUSPENDED,               
        &ThreadIdArray); 

    if(ThreadHandle == NULL)
    {
        return FALSE;
    }

    if(!SetThreadToken(&ThreadHandle, ExplorerToken))
    {
        return FALSE;
    }

    ResumeThread(ThreadHandle);

    WaitForSingleObject(ThreadHandle, INFINITE);

    return cudParams.bRetVal;
}

I use this code and do my work, now I want to return the users to the state, they were before impersonation

I know I should keep the handle before SetThreadToken and then after I've done my work, again SetThreadToken with the kept token.

Now I want you to help me to do that.


Solution

  • Get explorer.exe PID, then OpenProcess with that PID and DuplicateTokenEx

    That is a VERY OLD (Win9x) approach to getting a session's user token. Since you are already using the WTS API, use WTSQueryUserToken() instead, passing it the session ID that you want to impersonate.

    after that, the code CreateThread and then SetThreadToken and ResumeThread.

    Another option is to pass the user token to the thread in the lpParameter of CreateThread(), and then the thread can call ImpersonateLoggedOnUser() after it starts running.

    I use this code and do my work, now I want to return the users to the state, they were before impersonation

    If a thread needs to stop impersonating before it terminates, it should call RevertToSelf(). If the impersonation should last until the thread terminates, there is no need to stop impersonating manually.

    I know I should keep the handle before SetThreadToken and then after I've done my work, again SetThreadToken with the kept token.

    That is useful if the thread is already impersonating one user and then needs to stop impersonating, or to impersonate a different user, and then later needs to impersonate the first user again. But you are not doing that in your example.

    Now, with that said, your _CryptUnprotectData() function is leaking the thread object, as it is not calling CloseHandle() after WaitForSingleObject() is done. But more importantly, it is wasted overhead to create a new thread and then make the calling thread block until the new thread is terminated. The calling thread may as well just do the work directly. In your example, _CryptUnprotectData() can omit CreateThread() altogether and just call fp_CryptUnprotectData() directly, wrapped with calls to ImpersonateLoggedOnUser()/RevertToSelf(). You do not need to create a new thread just to use impersonation.