Problem
I'm creating a Windows server program that is potentially quite vulnerable to attacks. I'd like to sandbox (jail?) it or at least run my process in very low integrity setting. I probably be happy with just starting the process with:
SAFER_LEVEL_HANDLE hSaferLevel = NULL;
HANDLE hToken = NULL;
SaferCreateLevel(SAFER_SCOPEID_USER,SAFER_LEVELID_UNTRUSTED,0,&hSaferLevel,NULL);
SaferComputeTokenFromLevel(hSaferLevel, NULL, &hToken, 0, NULL);
CreateProcessAsUser(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
It would probably still be vulnerable to some attacks, but it would be easier for me to sleep with this protection. Now here is the problem, my process uses a DLL that needs at least Normal level to initialize.
Attempted Impersonation
Once the initialization done, I can ImpersonateLoggedOnUser(hToken)
no problem, but it's easy for someone injecting code to just RevertToSelf()
. I was trying to find a way to block the reverting once I'd impersonated an anonymous user using privilege reduction...
HANDLE hProcessToken, hProcess = GetCurrentProcess();
char pNewStateArray[sizeof(TOKEN_PRIVILEGES) + 1 * sizeof(LUID_AND_ATTRIBUTES)] = {};
PTOKEN_PRIVILEGES pNewState = (PTOKEN_PRIVILEGES)pNewStateArray;
OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hProcessToken);
LookupPrivilegeValue(0, SE_IMPERSONATE_NAME, &pNewState->Privileges[0].Luid);
pNewState->Privileges[i].Attributes = SE_PRIVILEGE_REMOVED;
AdjustTokenPrivileges(hProcessToken, FALSE, pNewState, sizeof(pNewStateArray), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL);
But I've misread what SeImpersonatePrivilege does: it seems that you can still impersonate lower level users when the permission is disabled or removed (plus the call to AdjustTokenPrivileges()
for SeImpersonatePrivilege doesn't work if I start my process in user mode). I gave up trying to find a way to block RevertToSelf()
after a few days of forum browsing, trial and mostly error.
Attempted Memory Cloning
My second option was to bypass the DLL init some way or another.
First, I've tried to start two mirror processes of the same program - one in user mode, one in anonymous mode, trying to copy the memory from one process to the other once the initialization was done. It would look something like this on the starter:
HANDLE hToken = NULL;
SAFER_LEVEL_HANDLE hSaferLevel = NULL;
SaferCreateLevel(SAFER_SCOPEID_USER,SAFER_LEVELID_UNTRUSTED,0,&hSaferLevel,NULL);
SaferComputeTokenFromLevel(hSaferLevel, NULL, &hToken, 0, NULL);
CreateProcess(lpApplicationName, lpCommandLine, pProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformationInit));
CreateProcessAsUser(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformationSafe));
SYSTEM_INFO si;
GetSystemInfo(&si);
LPVOID lpMem = 0;
DWORD memoryPtr = 0;
char memory[256*1024];
while (lpMem < si.lpMaximumApplicationAddress) {
MEMORY_BASIC_INFORMATION info;
if(0 == VirtualQueryEx(GetCurrentProcess(), lpMem, &info, sizeof(info))) break;
if (info.State & MEM_COMMIT) {
ReadProcessMemory(GetCurrentProcess(), info.BaseAddress, &memory[memoryPtr], info.RegionSize, 0);
}
memoryPtr += info.RegionSize;
lpMem = (LPVOID)((DWORD)info.BaseAddress + (DWORD)info.RegionSize);
}
... and then figure out a way to WriteProcessMemory()
the exact copy of the memory to get the DLL initialization data set and then run in anonymous mode. But these attempts were not going well. I figured this would be at best a very buggy solution.
Attempted Reverse Engineering
My last attempt was at trying to figure out how the DLL initialization went - what memory was modified and then hardcoding it in the sandboxed version of my process. Unfortunately, since the DLL was allocating so many different blocks all around, I gave that project up as well. Plus I felt dirty just thinking about hardcoding this kind of data.
Misc
Here are the other venues I thought of but gave up before actually trying them:
Injecting my own allocator in the DLL to figure out how to mimic the initialization.
Find the location where the DLL breaks anonymous settings and reverse engineer the assembler to figure out how to fake access to that object.
I'm not sure how likely I am to get a solution, but if you found anything wrong in my statements or have a hint about how I could solve my problem, you'd make my day!