572 lines
18 KiB
C++
572 lines
18 KiB
C++
#include "stdafx.h"
|
|
#include "winsta.h"
|
|
#include "pchrexec.h"
|
|
#include "Userenv.h"
|
|
#include "resrv.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Globals
|
|
|
|
CRITICAL_SECTION g_csInit;
|
|
HANDLE g_hthES = NULL;
|
|
HANDLE g_hPipe = INVALID_HANDLE_VALUE;
|
|
HANDLE g_hevShutdown = NULL;
|
|
HANDLE g_hevOverlapped = NULL;
|
|
|
|
extern "C"
|
|
{
|
|
typedef enum
|
|
{
|
|
NameUnknown = 0,
|
|
NameFullyQualifiedDN = 1,
|
|
NameSamCompatible = 2,
|
|
NameDisplay = 3,
|
|
NameUniqueId = 6,
|
|
NameCanonical = 7,
|
|
NameUserPrincipal = 8,
|
|
NameCanonicalEx = 9,
|
|
NameServicePrincipal = 10
|
|
|
|
} EXTENDED_NAME_FORMAT, * PEXTENDED_NAME_FORMAT ;
|
|
|
|
BOOLEAN
|
|
WINAPI
|
|
GetUserNameExW(
|
|
EXTENDED_NAME_FORMAT NameFormat,
|
|
LPWSTR lpNameBuffer,
|
|
PULONG nSize
|
|
);
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// utility functions
|
|
|
|
// ***************************************************************************
|
|
inline BOOL IsInvalidPtr(LPVOID pv, LPVOID pvStart, ULONG_PTR cbRange)
|
|
{
|
|
return (pv < pvStart || pv > ((LPVOID)((ULONG_PTR)pvStart + cbRange)));
|
|
}
|
|
|
|
// ***************************************************************************
|
|
inline LPVOID NormalizePtrs(LPVOID pv, LPVOID pvBase)
|
|
{
|
|
return (LPVOID)((ULONG_PTR)pvBase + (ULONG_PTR)pv);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
inline DWORD RolloverSubtrace(DWORD dwA, DWORD dwB)
|
|
{
|
|
return (dwA >= dwB) ? (dwA - dwB) : ((0xffffffff - dwA) + dwB);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// remote execution functions
|
|
|
|
// ***************************************************************************
|
|
BOOL ProcessExecRequest(PBYTE pBuf, DWORD *pcbBuf)
|
|
{
|
|
WINSTATIONUSERTOKEN wsut;
|
|
SPCHExecServRequest *pesreq = (SPCHExecServRequest *)pBuf;
|
|
SPCHExecServReply esrep;
|
|
HANDLE hprocRemote = NULL;
|
|
HANDLE hTokenUser = NULL;
|
|
HANDLE hProcess = NULL;
|
|
HANDLE hThread = NULL;
|
|
LPVOID pvEnv = NULL;
|
|
DWORD cbWrote;
|
|
WCHAR wszSysDir[MAX_PATH];
|
|
BOOL fRet = FALSE;
|
|
BOOL fImpersonate = FALSE;
|
|
char sz[1024];
|
|
|
|
ZeroMemory(&esrep, sizeof(esrep));
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
esrep.cb = sizeof(esrep);
|
|
esrep.fRet = FALSE;
|
|
|
|
// validate parameters
|
|
if (*pcbBuf < sizeof(SPCHExecServRequest) ||
|
|
pesreq->cbESR != sizeof(SPCHExecServRequest))
|
|
goto done;
|
|
|
|
// normalize the pointers
|
|
if (pesreq->wszCmdLine != NULL)
|
|
{
|
|
pesreq->wszCmdLine = (LPWSTR)NormalizePtrs(pesreq->wszCmdLine, pesreq);
|
|
if (IsInvalidPtr(pesreq->wszCmdLine, pesreq, *pcbBuf))
|
|
goto done;
|
|
}
|
|
|
|
if(pesreq->si.lpDesktop != NULL)
|
|
{
|
|
pesreq->si.lpDesktop = (LPWSTR)NormalizePtrs(pesreq->si.lpDesktop, pesreq);
|
|
if (IsInvalidPtr(pesreq->si.lpDesktop, pesreq, *pcbBuf))
|
|
goto done;
|
|
}
|
|
|
|
if(pesreq->si.lpTitle != NULL)
|
|
{
|
|
pesreq->si.lpTitle = (LPWSTR)NormalizePtrs(pesreq->si.lpTitle, pesreq);
|
|
if (IsInvalidPtr(pesreq->si.lpTitle, pesreq, *pcbBuf))
|
|
goto done;
|
|
}
|
|
|
|
// We do not know what the reserved is, so make sure it is NULL
|
|
pesreq->si.lpReserved = NULL;
|
|
|
|
// Get the handle to remote process
|
|
hprocRemote = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
|
|
FALSE, pesreq->pidReqProcess);
|
|
if (hprocRemote == NULL)
|
|
goto done;
|
|
|
|
// fetch the token associated with the sessions user
|
|
ZeroMemory(&wsut, sizeof(wsut));
|
|
wsut.ProcessId = LongToHandle(GetCurrentProcessId());
|
|
wsut.ThreadId = LongToHandle(GetCurrentThreadId());
|
|
fRet = WinStationQueryInformationW(SERVERNAME_CURRENT, pesreq->ulSessionId,
|
|
WinStationUserToken, &wsut,
|
|
sizeof(wsut), &cbWrote);
|
|
if (fRet == NULL)
|
|
goto done;
|
|
|
|
// create the default environment for the user token- note that we
|
|
// have to set the CREATE_UNICODE_ENVIRONMENT flag...
|
|
fRet = CreateEnvironmentBlock(&pvEnv, wsut.UserToken, FALSE);
|
|
if (fRet == FALSE)
|
|
pvEnv = NULL;
|
|
|
|
pesreq->fdwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
|
|
|
|
// If we are to run the process under USER security, impersonate
|
|
// the user.
|
|
// This will also access check the users access to the exe image.
|
|
fRet = ImpersonateLoggedOnUser(wsut.UserToken);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
// note that we do not allow inheritance of handles cuz they would be
|
|
// inherited from this process and not the real parent, making it sorta
|
|
// pointless.
|
|
GetSystemDirectoryW(wszSysDir, sizeof(wszSysDir) / sizeof(WCHAR));
|
|
fRet = CreateProcessAsUserW(wsut.UserToken, NULL, pesreq->wszCmdLine,
|
|
NULL, NULL, FALSE, pesreq->fdwCreateFlags,
|
|
pvEnv, wszSysDir, &pesreq->si, &esrep.pi);
|
|
|
|
// since we successfully impersonated above (couldn't get here otherwise),
|
|
// revert back to our original security context
|
|
RevertToSelf();
|
|
|
|
if (pvEnv != NULL)
|
|
DestroyEnvironmentBlock(pvEnv);
|
|
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
// save a copy of the handles so we can close them later
|
|
hProcess = esrep.pi.hProcess;
|
|
hThread = esrep.pi.hThread;
|
|
|
|
// duplicate the process & thread handles back into the remote process
|
|
fRet = DuplicateHandle(GetCurrentProcess(), esrep.pi.hProcess,
|
|
hprocRemote, &esrep.pi.hProcess,
|
|
0, FALSE, DUPLICATE_SAME_ACCESS);
|
|
if (fRet == FALSE)
|
|
{
|
|
// do something useful here- can't return FALSE, cuz the process is
|
|
// running...
|
|
}
|
|
|
|
|
|
// If the program got launched into the shared WOW virtual machine,
|
|
// then the hThread will be NULL.
|
|
if(esrep.pi.hThread != NULL)
|
|
{
|
|
fRet = DuplicateHandle(GetCurrentProcess(), esrep.pi.hThread,
|
|
hprocRemote, &esrep.pi.hThread,
|
|
0, FALSE, DUPLICATE_SAME_ACCESS);
|
|
if (fRet == FALSE)
|
|
{
|
|
// do something useful here- can't return FALSE, cuz the process is
|
|
// running...
|
|
}
|
|
}
|
|
|
|
// get rid of any errors we might have encountered
|
|
SetLastError(0);
|
|
fRet = TRUE;
|
|
|
|
esrep.fRet = TRUE;
|
|
|
|
done:
|
|
esrep.dwErr = GetLastError();
|
|
|
|
// build the reply packet with the handle valid in the context
|
|
// of the requesting process
|
|
RtlMoveMemory(pBuf, &esrep, sizeof(esrep));
|
|
*pcbBuf = sizeof(esrep);
|
|
|
|
// close our versions of the handles. The requestors references
|
|
// are now the main ones
|
|
if (hProcess != NULL)
|
|
CloseHandle(hProcess);
|
|
if (hThread != NULL)
|
|
CloseHandle(hThread);
|
|
if (hprocRemote != NULL)
|
|
CloseHandle(hprocRemote);
|
|
if (hTokenUser != NULL)
|
|
CloseHandle(hTokenUser);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
BOOL IsClientLocalSystem(HANDLE hPipe)
|
|
{
|
|
WCHAR wszClient[MAX_PATH];
|
|
WCHAR wszBase[MAX_PATH];
|
|
DWORD cbInfo;
|
|
BOOL fRet = FALSE;
|
|
|
|
// we assumes that the thread is running under local system context now
|
|
cbInfo = sizeof(wszBase) / sizeof(WCHAR);
|
|
fRet = GetUserNameExW(NameUniqueId, wszBase, &cbInfo);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
OutputDebugStringW(L"base: ");
|
|
OutputDebugStringW(wszBase);
|
|
OutputDebugStringW(L"\n");
|
|
|
|
// impersonate the client & then fetch his user name.
|
|
fRet = ImpersonateNamedPipeClient(hPipe);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
cbInfo = sizeof(wszClient) / sizeof(WCHAR);
|
|
fRet = GetUserNameExW(NameUniqueId, wszClient, &cbInfo);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
OutputDebugStringW(L"client: ");
|
|
OutputDebugStringW(wszClient);
|
|
OutputDebugStringW(L"\n");
|
|
|
|
// make sure to return to our base security context
|
|
RevertToSelf();
|
|
|
|
// check if the two names are the same
|
|
fRet = (_wcsicmp(wszClient, wszBase) == 0);
|
|
|
|
done:
|
|
if (fRet == FALSE)
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
BOOL ExecServer(LPVOID pvParam)
|
|
{
|
|
OVERLAPPED ol;
|
|
HANDLE rghWait[2] = { NULL, NULL };
|
|
HANDLE hPipe = INVALID_HANDLE_VALUE;
|
|
DWORD cbBuf, cb, dwErr, dw;
|
|
WCHAR wszPipeName[MAX_PATH];
|
|
BOOL fRet, fContinue = TRUE;
|
|
BYTE Buf[HANGREP_EXECSVC_BUF_SIZE];
|
|
|
|
rghWait[0] = (HANDLE)pvParam;
|
|
|
|
// create the event we're gonna wait on- we'll pass this to the various
|
|
// IO functions via the OVERLAPPED structure
|
|
rghWait[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (rghWait[1] == NULL)
|
|
goto done;
|
|
|
|
EnterCriticalSection(&g_csInit);
|
|
g_hevOverlapped = rghWait[1];
|
|
LeaveCriticalSection(&g_csInit);
|
|
|
|
// setup the overlapped structure
|
|
ZeroMemory(&ol, sizeof(ol));
|
|
ol.hEvent = rghWait[1];
|
|
|
|
// create the pipe
|
|
wcscpy(wszPipeName, HANGREP_EXECSVC_PIPENAME);
|
|
hPipe = CreateNamedPipeW(wszPipeName,
|
|
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
|
PIPE_WAIT | PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
HANGREP_EXECSVC_BUF_SIZE,
|
|
HANGREP_EXECSVC_BUF_SIZE, 0, NULL);
|
|
if (hPipe == INVALID_HANDLE_VALUE)
|
|
goto done;
|
|
|
|
EnterCriticalSection(&g_csInit);
|
|
g_hPipe = hPipe;
|
|
LeaveCriticalSection(&g_csInit);
|
|
|
|
while(fContinue)
|
|
{
|
|
// read the pipe for a request (pipe is in message mode)- note that
|
|
// in overlapped mode, ConnectNamedPipe should always return FALSE.
|
|
// So the only non-failure codes GetLastError() can return are
|
|
// ERROR_IO_PENDING or ERROR_PIPE_CONNECTED.
|
|
fRet = ConnectNamedPipe(hPipe, &ol);
|
|
if (fRet == FALSE && GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
// if the pipe is already connected, just set the event so we
|
|
// don't have to add special case code below.
|
|
if (GetLastError() == ERROR_PIPE_CONNECTED)
|
|
SetEvent(ol.hEvent);
|
|
else
|
|
goto done;
|
|
}
|
|
|
|
// WAIT_OBJECT_0 is the shutdown event
|
|
// WAIT_OBJECT_0 + 1 is the overlapped event
|
|
dw = WaitForMultipleObjects(2, rghWait, FALSE, INFINITE);
|
|
if (dw == WAIT_OBJECT_0)
|
|
{
|
|
DisconnectNamedPipe(hPipe);
|
|
fContinue = FALSE;
|
|
break;
|
|
}
|
|
else if (dw != WAIT_OBJECT_0 + 1)
|
|
{
|
|
dwErr = (GetLastError() == ERROR_SUCCESS) ? E_FAIL : GetLastError();
|
|
if (WaitForSingleObject(ol.hEvent, 0) == WAIT_OBJECT_0)
|
|
DisconnectNamedPipe(hPipe);
|
|
SetLastError(dwErr);
|
|
goto done;
|
|
}
|
|
|
|
ResetEvent(ol.hEvent);
|
|
fRet = ReadFile(hPipe, Buf, sizeof(Buf), &cb, &ol);
|
|
if (fRet == FALSE && GetLastError() == ERROR_IO_PENDING)
|
|
{
|
|
// give the client 60s to write the data to us.
|
|
// WAIT_OBJECT_0 is the shutdown event
|
|
// WAIT_OBJECT_0 + 1 is the overlapped event
|
|
dw = WaitForMultipleObjects(2, rghWait, FALSE, 60000);
|
|
if (dw == WAIT_OBJECT_0)
|
|
{
|
|
fContinue = FALSE;
|
|
}
|
|
else if (dw != WAIT_OBJECT_0 + 1)
|
|
{
|
|
DisconnectNamedPipe(hPipe);
|
|
if (dw == WAIT_TIMEOUT)
|
|
continue;
|
|
else
|
|
goto done;
|
|
}
|
|
|
|
fRet = GetOverlappedResult(hPipe, &ol, &cbBuf, FALSE);
|
|
}
|
|
else
|
|
{
|
|
OutputDebugString("ReadFile on pipe failed\n");
|
|
}
|
|
|
|
// check and make sure that the calling process is running under the
|
|
// local system account- if this fails, we cannot proceed cuz we
|
|
// don't know who the caller is
|
|
if (fContinue)
|
|
fRet = IsClientLocalSystem(hPipe);
|
|
|
|
// if we got an error, the client might still be waiting for a
|
|
// reply, so construct a default one.
|
|
// ProcessExecRequest() will always construct a reply and store it
|
|
// in Buf, so no special handling is needed if it fails.
|
|
if (fContinue && fRet)
|
|
{
|
|
fRet = ProcessExecRequest(Buf, &cbBuf);
|
|
}
|
|
else
|
|
{
|
|
SPCHExecServReply esrep;
|
|
ZeroMemory(&esrep, sizeof(esrep));
|
|
esrep.cb = sizeof(esrep);
|
|
esrep.fRet = FALSE;
|
|
esrep.dwErr = GetLastError();
|
|
|
|
RtlMoveMemory(Buf, &esrep, sizeof(esrep));
|
|
cbBuf = sizeof(esrep);
|
|
}
|
|
|
|
// write the reply to the message
|
|
ResetEvent(ol.hEvent);
|
|
fRet = WriteFile(hPipe, Buf, cbBuf, &cb, &ol);
|
|
if (fRet == FALSE && GetLastError() == ERROR_IO_PENDING)
|
|
{
|
|
// give ourselves 60s to write the data to the pipe.
|
|
// WAIT_OBJECT_0 is the shutdown event
|
|
// WAIT_OBJECT_0 + 1 is the overlapped event
|
|
dw = WaitForMultipleObjects(2, rghWait, FALSE, 60000);
|
|
if (dw == WAIT_OBJECT_0)
|
|
{
|
|
fContinue = FALSE;
|
|
}
|
|
else if (dw != WAIT_OBJECT_0 + 1)
|
|
{
|
|
DisconnectNamedPipe(hPipe);
|
|
if (dw == WAIT_TIMEOUT)
|
|
continue;
|
|
else
|
|
goto done;
|
|
}
|
|
|
|
fRet = TRUE;
|
|
}
|
|
|
|
// wait for the client to read the buffer- note that we could use
|
|
// FlushFileBuffers() to do this, but that is blocking with no
|
|
// timeout, so we try to do a read on the pipe & wait to get an
|
|
// error indicating that the client closed it.
|
|
if (fContinue && fRet)
|
|
{
|
|
ResetEvent(ol.hEvent);
|
|
fRet = ReadFile(hPipe, Buf, sizeof(Buf), &cb, &ol);
|
|
if (fRet == FALSE && GetLastError() == ERROR_IO_PENDING)
|
|
{
|
|
// give ourselves 60s to read the data from the pipe.
|
|
// Except for the shutdown notification, don't really
|
|
// care what this routine returns cuz we're just using
|
|
// it to wait on
|
|
// WAIT_OBJECT_0 is the shutdown event
|
|
// WAIT_OBJECT_0 + 1 is the overlapped event
|
|
dw = WaitForMultipleObjects(2, rghWait, FALSE, 60000);
|
|
if (dw == WAIT_OBJECT_0)
|
|
fContinue = FALSE;
|
|
}
|
|
}
|
|
|
|
// disconnect the name pipe
|
|
DisconnectNamedPipe(hPipe);
|
|
}
|
|
|
|
SetLastError(0);
|
|
|
|
done:
|
|
EnterCriticalSection(&g_csInit);
|
|
if (hPipe != NULL)
|
|
CloseHandle(hPipe);
|
|
if (rghWait[1] != NULL)
|
|
CloseHandle(rghWait[1]);
|
|
g_hPipe = INVALID_HANDLE_VALUE;
|
|
g_hevOverlapped = NULL;
|
|
LeaveCriticalSection(&g_csInit);
|
|
|
|
return fContinue;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
DWORD threadExecServer(LPVOID pvParam)
|
|
{
|
|
DWORD cErr = 0, dwErr = ERROR_SUCCESS, dwStart = GetTickCount();
|
|
BOOL fShutdown = FALSE;
|
|
|
|
while(fShutdown == FALSE && cErr < 5)
|
|
{
|
|
__try
|
|
{
|
|
fShutdown = ExecServer(pvParam);
|
|
if (fShutdown == FALSE)
|
|
cErr++;
|
|
|
|
dwErr = GetLastError();
|
|
}
|
|
__except(dwErr = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
cErr++;
|
|
EnterCriticalSection(&g_csInit);
|
|
if (g_hPipe != NULL)
|
|
CloseHandle(g_hPipe);
|
|
if (g_hevOverlapped)
|
|
CloseHandle(g_hevOverlapped);
|
|
g_hPipe = INVALID_HANDLE_VALUE;
|
|
g_hevOverlapped = NULL;
|
|
LeaveCriticalSection(&g_csInit);
|
|
}
|
|
|
|
if (fShutdown || WaitForSingleObject(g_hevShutdown, 0) == WAIT_OBJECT_0)
|
|
{
|
|
dwErr = 0;
|
|
break;
|
|
}
|
|
|
|
// if this is the first error in the last 10s, restart the counter
|
|
if (RolloverSubtrace(GetTickCount(), dwStart) > 10000)
|
|
{
|
|
dwStart = GetTickCount();
|
|
cErr = 1;
|
|
}
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
BOOL StartExecServerThread(void)
|
|
{
|
|
DWORD dwThreadId, cRef;
|
|
WCHAR wszPipeName[MAX_PATH];
|
|
BOOL fRet = FALSE;
|
|
|
|
g_hevShutdown = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
if (g_hevShutdown == NULL)
|
|
goto done;
|
|
|
|
g_hthES = CreateThread(NULL, 0, threadExecServer, (LPVOID)g_hevShutdown,
|
|
0, &dwThreadId);
|
|
if (g_hthES == NULL)
|
|
{
|
|
CloseHandle(g_hevShutdown);
|
|
g_hevShutdown = NULL;
|
|
goto done;
|
|
}
|
|
|
|
fRet = TRUE;
|
|
|
|
done:
|
|
return fRet;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
BOOL StopExecServerThread(void)
|
|
{
|
|
DWORD dw;
|
|
|
|
if (g_hthES == NULL || g_hevShutdown == NULL)
|
|
return TRUE;
|
|
|
|
SetEvent(g_hevShutdown);
|
|
dw = WaitForSingleObject(g_hthES, 60000);
|
|
if (dw != WAIT_OBJECT_0)
|
|
{
|
|
// need to do the TerminateThread in a CS cuz I don't want to terminate
|
|
// the thread while it holds the CS.
|
|
EnterCriticalSection(&g_csInit);
|
|
TerminateThread(g_hthES, E_FAIL);
|
|
CloseHandle(g_hPipe);
|
|
CloseHandle(g_hevOverlapped);
|
|
g_hPipe = INVALID_HANDLE_VALUE;
|
|
g_hevOverlapped = NULL;
|
|
EnterCriticalSection(&g_csInit);
|
|
}
|
|
|
|
CloseHandle(g_hthES);
|
|
CloseHandle(g_hevShutdown);
|
|
|
|
g_hthES = NULL;
|
|
g_hevShutdown = NULL;
|
|
|
|
return TRUE;
|
|
}
|