WinInet used in Thread Impersonation

First and foremost, this is NOT supported for reasons stated in this article: https://support.microsoft.com/default.aspx/kb/238425. Primarily, problems occur because of information that is stored in the HKEY_CURRENT_USERS registry key and because of threading concerns. Furthermore, due to security implications and the design of WinInet in Windows 7, this absolutely will not work in Windows 7. Finally, in Vista, there is a Low IL cache as well as the normal (Medium IL) cache so this sample is ineffective at best. This blog entry is mearly an excercise to help you understand how the Registry works with thread impersonation as I discovered when working on a WinInet problem.

That said, if you have a service running in the Local System Account, you might be able to work around some of the problems by ensuring the registry is loaded with the currently logged on user's registry information. If the currently logged on user has used the browser, most information should be populated in that user's registry.

If running with UAC enabled and you attempt to enumerate the cache while running under the Local System account you will fail and GetLastError will return ERROR_INVALID_PARAMETER. This error is thrown because internally there are some registry settings missing that WinInet expects to be there for every user (again, WinInet is not designed, tested or supported when running under the System Account or when using thread impersonation). I found however if you use thread impersonation and load the registry HKCU for the currently logged on user, you can access the cache. I do not know if this will work in all scenarios and in future WinInet releases, but I was able to get it to work for me. I used PsExec from Sysinternals to run my tests.

Details
I downloaded and installed PsExec from https://technet.microsoft.com/en-us/sysinternals/bb896649.aspx

I created a new C++ Console application in Visual Studio and added code to:
1. Get the Currently Logged on user token in the SessionID from where this program was launched under the SYSTEM account using WTSQueryUserToken
2. Impersonate this user using ImpersonateLoggedOnUser
3. Load the impersonated HKU registry in HKCU by using RegDisablePredefinedCache
4. Iterate through the IE cache using FindFirstUrlCacheEntry and FindNextUrlCacheEntry
5. I added code that would also show the cache if the WTSQueryUserToken failed. This will fail if you are running the program from the command line (no PsExec). This allows you to see the case running in the context of the current user.

I then added the WinInet.lib and Wtsapi32.lib to the linker input files and built the project.

I then tested the code with PsExec using: PsExec –i –s <<full path and program name>>. I opened a command prompt with Administrator permissions to run the tests. An interesting use of PsExec allows you to see the cache when running as an Low IL process: PsExec –l <<full path and program name>>. Note that this is a different set of values because the Low IL cache is in a different location then the Medium (normal) IL cache.
For details on protected mode cache see: https://blogs.msdn.com/ie/archive/2006/02/09/528963.aspx

Here is the code for your enjoyment. Of course you would need to add your code to test the success of calls, trap exceptions etc... But it should get you started:

C++ code listing for sample (Copy Code):

// ImpersonateWinInet.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <WinInet.h>
#pragma comment(lib, "WinInet.lib")
#include <Wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")
#include <iostream>
#include <conio.h> // for _getch
int showCache()
{
// another gotcha... The actual cache hit here depends on the IL that the program is running under.
// to see this run psexec with the -l option in a cmd prompt
// Local variables
DWORD cacheEntryInfoBufferSizeInitial = 0;
DWORD cacheEntryInfoBufferSize = 0;
int *cacheEntryInfoBuffer = 0;
INTERNET_CACHE_ENTRY_INFO *internetCacheEntry;
HANDLE enumHandle = NULL;
BOOL returnValue = false;
int aiNumEntries=0;
DWORD dwError;
// get the size of the buffer required
enumHandle = FindFirstUrlCacheEntry(NULL, 0, &cacheEntryInfoBufferSizeInitial);
if (enumHandle == NULL && ERROR_NO_MORE_ITEMS == GetLastError())
{
return aiNumEntries;
}
cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
internetCacheEntry = (INTERNET_CACHE_ENTRY_INFO *)malloc(cacheEntryInfoBufferSize);
enumHandle = FindFirstUrlCacheEntry(NULL, internetCacheEntry, &cacheEntryInfoBufferSizeInitial);
if (enumHandle == NULL)
{
std::cout << "could not get first URL entry. Error :" << GetLastError();
// ERROR_INVALID_PARAMETER is thrown because of incorrect registry info
return aiNumEntries;
}
returnValue=true;
while(1)
{
cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize;
if (returnValue)
{
if (internetCacheEntry != NULL)
{
std::wcout<<(LPWSTR)((INTERNET_CACHE_ENTRY_INFO *)internetCacheEntry->lpszSourceUrlName)<< _T("\r\n");
returnValue = FindNextUrlCacheEntry(enumHandle, internetCacheEntry, &cacheEntryInfoBufferSizeInitial);
aiNumEntries++;
}
else
{
// this should not happen!
break;
}
}

dwError = GetLastError();
if (!returnValue && ERROR_NO_MORE_ITEMS == dwError)
break; //now more items!

// if buffer not big enough, grow it
if (!returnValue && cacheEntryInfoBufferSizeInitial > cacheEntryInfoBufferSize)
{
cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
// note test for buffer here (OOM condition)
internetCacheEntry = (INTERNET_CACHE_ENTRY_INFO *)realloc(internetCacheEntry, cacheEntryInfoBufferSize);
returnValue = FindNextUrlCacheEntry(enumHandle, internetCacheEntry, &cacheEntryInfoBufferSizeInitial);
}
}
free(internetCacheEntry);
std::wcout<<_T("Number of entries processed: ")<<aiNumEntries<<_T("\r\n");
return aiNumEntries;
}

int _tmain(int argc, _TCHAR* argv[])
{
    // note, this below will get the session id that this program was run from.
    DWORD dwActiveSessionId = WTS_CURRENT_SESSION;
HANDLE hUserToken = INVALID_HANDLE_VALUE;
DWORD dwErr=0;

    //showCache();
    BOOL bSuccess = WTSQueryUserToken(dwActiveSessionId, &hUserToken);
    if (bSuccess)
{
std::cout << "Got User Token\r\n";
bSuccess = ::ImpersonateLoggedOnUser(hUserToken);
        if (bSuccess)
{
std::cout << "Impersonating\r\n";
            // This is necessary because we want to load the impersonated user registry into HKCU
            // comment this out whey running under the system acct and you will get ERROR_INVALID_PARAMETER
            if( ERROR_SUCCESS==RegDisablePredefinedCache())
{
showCache();
}
CloseHandle(hUserToken);
}
        else
        {
std::cout << "Failed Impersonation\r\n";
}
}
    else
    {
std::cout << "Did not Get User Token\r\n";
dwErr= ::GetLastError();
std::cout << "Error" << dwErr << "\r\n";
        if (ERROR_PRIVILEGE_NOT_HELD==dwErr)
{
std::cout << "To call WTSQueryUserToken successfully, the calling application must be running within the context of the LocalSystem account and have the SE_TCB_NAME privilege\r\n";
std::cout << "Trying in the context of the user running this program...\r\n";
std::cout << "Hit a key or enter to start";
_getch();
showCache();
}
}

std::cout<<

"Hit a key or enter to exit";
_getch();

    return 0;
}

 

I hope this sample helps you out. Please drop me a comment if you found this useful!