2025-04-27 07:49:33 -04:00

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