Files
wj32 f4ad8a85da Upgrade all projects to VS 2015 (almost all by dmex)
git-svn-id: svn://svn.code.sf.net/p/processhacker/code@6179 21ef857c-d57f-4fe0-8362-d861dc6d29cd
2015-08-16 06:10:56 +00:00

1171 lines
39 KiB
C

/*
* Process Hacker -
* run as dialog
*
* Copyright (C) 2010-2013 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* The run-as mechanism has three stages:
* 1. The user enters the information into the dialog box. Here it is decided
* whether the run-as service is needed. If it is not, PhCreateProcessAsUser
* is called directly. Otherwise, PhExecuteRunAsCommand2 is called for
* stage 2.
* 2. PhExecuteRunAsCommand2 creates a random service name and tries to create
* the service and execute it (using PhExecuteRunAsCommand). If the process
* has insufficient permissions, an elevated instance of phsvc is started
* and PhSvcCallExecuteRunAsCommand is called.
* 3. The service is started, and sets up an instance of phsvc with the same
* random service name as its port name. Either the original or elevated
* Process Hacker instance then calls PhSvcCallInvokeRunAsService to complete
* the operation.
*
* ProcessHacker.exe (user, limited privileges)
* * | ^
* | | | phsvc API (LPC)
* | | |
* | v |
* ProcessHacker.exe (user, full privileges)
* | ^ | ^
* | | SCM API (RPC) | |
* | | | |
* v | | | phsvc API (LPC)
* services.exe | |
* * | |
* | | |
* | | |
* | v |
* ProcessHacker.exe (NT AUTHORITY\SYSTEM)
* *
* |
* |
* |
* program.exe
*/
#include <phapp.h>
#include <phsvc.h>
#include <phsvccl.h>
#include <settings.h>
#include <emenu.h>
#include <shlwapi.h>
#include <winsta.h>
#include <windowsx.h>
typedef struct _RUNAS_DIALOG_CONTEXT
{
HANDLE ProcessId;
PPH_LIST DesktopList;
PPH_STRING CurrentWinStaName;
} RUNAS_DIALOG_CONTEXT, *PRUNAS_DIALOG_CONTEXT;
INT_PTR CALLBACK PhpRunAsDlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
VOID PhSetDesktopWinStaAccess(
VOID
);
VOID PhpSplitUserName(
_In_ PWSTR UserName,
_Out_ PPH_STRING *DomainPart,
_Out_ PPH_STRING *UserPart
);
#define SIP(String, Integer) { (String), (PVOID)(Integer) }
static PH_KEY_VALUE_PAIR PhpLogonTypePairs[] =
{
SIP(L"Batch", LOGON32_LOGON_BATCH),
SIP(L"Interactive", LOGON32_LOGON_INTERACTIVE),
SIP(L"Network", LOGON32_LOGON_NETWORK),
SIP(L"New credentials", LOGON32_LOGON_NEW_CREDENTIALS),
SIP(L"Service", LOGON32_LOGON_SERVICE)
};
static WCHAR RunAsOldServiceName[32] = L"";
static PH_QUEUED_LOCK RunAsOldServiceLock = PH_QUEUED_LOCK_INIT;
static PPH_STRING RunAsServiceName;
static SERVICE_STATUS_HANDLE RunAsServiceStatusHandle;
static PHSVC_STOP RunAsServiceStop;
VOID PhShowRunAsDialog(
_In_ HWND ParentWindowHandle,
_In_opt_ HANDLE ProcessId
)
{
RUNAS_DIALOG_CONTEXT context;
context.ProcessId = ProcessId;
context.DesktopList = NULL;
DialogBoxParam(
PhInstanceHandle,
MAKEINTRESOURCE(IDD_RUNAS),
ParentWindowHandle,
PhpRunAsDlgProc,
(LPARAM)&context
);
}
static VOID PhpAddAccountsToComboBox(
_In_ HWND ComboBoxHandle
)
{
LSA_HANDLE policyHandle;
LSA_ENUMERATION_HANDLE enumerationContext = 0;
PLSA_ENUMERATION_INFORMATION buffer;
ULONG count;
ULONG i;
PPH_STRING name;
SID_NAME_USE nameUse;
if (NT_SUCCESS(PhOpenLsaPolicy(&policyHandle, POLICY_VIEW_LOCAL_INFORMATION, NULL)))
{
while (NT_SUCCESS(LsaEnumerateAccounts(
policyHandle,
&enumerationContext,
&buffer,
0x100,
&count
)))
{
for (i = 0; i < count; i++)
{
name = PhGetSidFullName(buffer[i].Sid, TRUE, &nameUse);
if (name)
{
if (nameUse == SidTypeUser)
ComboBox_AddString(ComboBoxHandle, name->Buffer);
PhDereferenceObject(name);
}
}
LsaFreeMemory(buffer);
}
LsaClose(policyHandle);
}
}
static BOOLEAN IsServiceAccount(
_In_ PPH_STRING UserName
)
{
if (
PhEqualString2(UserName, L"NT AUTHORITY\\LOCAL SERVICE", TRUE) ||
PhEqualString2(UserName, L"NT AUTHORITY\\NETWORK SERVICE", TRUE) ||
PhEqualString2(UserName, L"NT AUTHORITY\\SYSTEM", TRUE)
)
{
return TRUE;
}
else
{
return FALSE;
}
}
static PPH_STRING GetCurrentWinStaName(
VOID
)
{
PPH_STRING string;
string = PhCreateStringEx(NULL, 0x200);
if (GetUserObjectInformation(
GetProcessWindowStation(),
UOI_NAME,
string->Buffer,
(ULONG)string->Length + 2,
NULL
))
{
PhTrimToNullTerminatorString(string);
return string;
}
else
{
PhDereferenceObject(string);
return PhCreateString(L"WinSta0"); // assume the current window station is WinSta0
}
}
static BOOL CALLBACK EnumDesktopsCallback(
_In_ PWSTR DesktopName,
_In_ LPARAM Context
)
{
PRUNAS_DIALOG_CONTEXT context = (PRUNAS_DIALOG_CONTEXT)Context;
PhAddItemList(context->DesktopList, PhConcatStrings(
3,
context->CurrentWinStaName->Buffer,
L"\\",
DesktopName
));
return TRUE;
}
INT_PTR CALLBACK PhpRunAsDlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
PRUNAS_DIALOG_CONTEXT context;
if (uMsg != WM_INITDIALOG)
{
context = (PRUNAS_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
}
else
{
context = (PRUNAS_DIALOG_CONTEXT)lParam;
SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context);
}
if (!context)
return FALSE;
switch (uMsg)
{
case WM_INITDIALOG:
{
HWND typeComboBoxHandle = GetDlgItem(hwndDlg, IDC_TYPE);
HWND userNameComboBoxHandle = GetDlgItem(hwndDlg, IDC_USERNAME);
ULONG sessionId;
PhCenterWindow(hwndDlg, GetParent(hwndDlg));
if (SHAutoComplete_I)
{
SHAutoComplete_I(
GetDlgItem(hwndDlg, IDC_PROGRAM),
SHACF_AUTOAPPEND_FORCE_ON | SHACF_AUTOSUGGEST_FORCE_ON | SHACF_FILESYS_ONLY
);
}
ComboBox_AddString(typeComboBoxHandle, L"Batch");
ComboBox_AddString(typeComboBoxHandle, L"Interactive");
ComboBox_AddString(typeComboBoxHandle, L"Network");
ComboBox_AddString(typeComboBoxHandle, L"New credentials");
ComboBox_AddString(typeComboBoxHandle, L"Service");
PhSelectComboBoxString(typeComboBoxHandle, L"Interactive", FALSE);
ComboBox_AddString(userNameComboBoxHandle, L"NT AUTHORITY\\SYSTEM");
ComboBox_AddString(userNameComboBoxHandle, L"NT AUTHORITY\\LOCAL SERVICE");
ComboBox_AddString(userNameComboBoxHandle, L"NT AUTHORITY\\NETWORK SERVICE");
PhpAddAccountsToComboBox(userNameComboBoxHandle);
if (NT_SUCCESS(PhGetProcessSessionId(NtCurrentProcess(), &sessionId)))
SetDlgItemInt(hwndDlg, IDC_SESSIONID, sessionId, FALSE);
SetDlgItemText(hwndDlg, IDC_DESKTOP, L"WinSta0\\Default");
SetDlgItemText(hwndDlg, IDC_PROGRAM,
((PPH_STRING)PhAutoDereferenceObject(PhGetStringSetting(L"RunAsProgram")))->Buffer);
if (!context->ProcessId)
{
SetDlgItemText(hwndDlg, IDC_USERNAME,
((PPH_STRING)PhAutoDereferenceObject(PhGetStringSetting(L"RunAsUserName")))->Buffer);
// Fire the user name changed event so we can fix the logon type.
SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_USERNAME, CBN_EDITCHANGE), 0);
}
else
{
HANDLE processHandle;
HANDLE tokenHandle;
PTOKEN_USER user;
PPH_STRING userName;
if (NT_SUCCESS(PhOpenProcess(
&processHandle,
ProcessQueryAccess,
context->ProcessId
)))
{
if (NT_SUCCESS(PhOpenProcessToken(
&tokenHandle,
TOKEN_QUERY,
processHandle
)))
{
if (NT_SUCCESS(PhGetTokenUser(tokenHandle, &user)))
{
if (userName = PhGetSidFullName(user->User.Sid, TRUE, NULL))
{
SetDlgItemText(hwndDlg, IDC_USERNAME, userName->Buffer);
PhDereferenceObject(userName);
}
PhFree(user);
}
NtClose(tokenHandle);
}
NtClose(processHandle);
}
EnableWindow(GetDlgItem(hwndDlg, IDC_USERNAME), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_PASSWORD), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_TYPE), FALSE);
}
SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_PROGRAM), TRUE);
Edit_SetSel(GetDlgItem(hwndDlg, IDC_PROGRAM), 0, -1);
//if (!PhElevated)
// SendMessage(GetDlgItem(hwndDlg, IDOK), BCM_SETSHIELD, 0, TRUE);
if (!WINDOWS_HAS_UAC)
ShowWindow(GetDlgItem(hwndDlg, IDC_TOGGLEELEVATION), SW_HIDE);
}
break;
case WM_DESTROY:
{
if (context->DesktopList)
PhDereferenceObject(context->DesktopList);
RemoveProp(hwndDlg, PhMakeContextAtom());
}
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDCANCEL:
EndDialog(hwndDlg, IDCANCEL);
break;
case IDOK:
{
NTSTATUS status;
PPH_STRING program;
PPH_STRING userName;
PPH_STRING password;
PPH_STRING logonTypeString;
ULONG logonType;
ULONG sessionId;
PPH_STRING desktopName;
BOOLEAN useLinkedToken;
program = PhaGetDlgItemText(hwndDlg, IDC_PROGRAM);
userName = PhaGetDlgItemText(hwndDlg, IDC_USERNAME);
logonTypeString = PhaGetDlgItemText(hwndDlg, IDC_TYPE);
// Fix up the user name if it doesn't have a domain.
if (PhFindCharInString(userName, 0, '\\') == -1)
{
PSID sid;
PPH_STRING newUserName;
if (NT_SUCCESS(PhLookupName(&userName->sr, &sid, NULL, NULL)))
{
newUserName = PhGetSidFullName(sid, TRUE, NULL);
if (newUserName)
{
PhAutoDereferenceObject(newUserName);
userName = newUserName;
}
PhFree(sid);
}
}
if (!IsServiceAccount(userName))
password = PhGetWindowText(GetDlgItem(hwndDlg, IDC_PASSWORD));
else
password = NULL;
sessionId = GetDlgItemInt(hwndDlg, IDC_SESSIONID, NULL, FALSE);
desktopName = PhaGetDlgItemText(hwndDlg, IDC_DESKTOP);
if (WINDOWS_HAS_UAC)
useLinkedToken = Button_GetCheck(GetDlgItem(hwndDlg, IDC_TOGGLEELEVATION)) == BST_CHECKED;
else
useLinkedToken = FALSE;
if (PhFindIntegerSiKeyValuePairs(
PhpLogonTypePairs,
sizeof(PhpLogonTypePairs),
logonTypeString->Buffer,
&logonType
))
{
if (
logonType == LOGON32_LOGON_INTERACTIVE &&
!context->ProcessId &&
sessionId == NtCurrentPeb()->SessionId &&
!useLinkedToken
)
{
// We are eligible to load the user profile.
// This must be done here, not in the service, because
// we need to be in the target session.
PH_CREATE_PROCESS_AS_USER_INFO createInfo;
PPH_STRING domainPart;
PPH_STRING userPart;
PhpSplitUserName(userName->Buffer, &domainPart, &userPart);
memset(&createInfo, 0, sizeof(PH_CREATE_PROCESS_AS_USER_INFO));
createInfo.CommandLine = program->Buffer;
createInfo.UserName = userPart->Buffer;
createInfo.DomainName = domainPart->Buffer;
createInfo.Password = PhGetStringOrEmpty(password);
// Whenever we can, try not to set the desktop name; it breaks a lot of things.
// Note that on XP we must set it, otherwise the program doesn't display correctly.
if (WindowsVersion < WINDOWS_VISTA || (desktopName->Length != 0 && !PhEqualString2(desktopName, L"WinSta0\\Default", TRUE)))
createInfo.DesktopName = desktopName->Buffer;
PhSetDesktopWinStaAccess();
status = PhCreateProcessAsUser(
&createInfo,
PH_CREATE_PROCESS_WITH_PROFILE,
NULL,
NULL,
NULL
);
if (domainPart) PhDereferenceObject(domainPart);
if (userPart) PhDereferenceObject(userPart);
}
else
{
status = PhExecuteRunAsCommand2(
hwndDlg,
program->Buffer,
userName->Buffer,
PhGetStringOrEmpty(password),
logonType,
context->ProcessId,
sessionId,
desktopName->Buffer,
useLinkedToken
);
}
}
else
{
status = STATUS_INVALID_PARAMETER;
}
if (password)
{
RtlSecureZeroMemory(password->Buffer, password->Length);
PhDereferenceObject(password);
}
if (!NT_SUCCESS(status))
{
if (status != STATUS_CANCELLED)
PhShowStatus(hwndDlg, L"Unable to start the program", status, 0);
}
else if (status != STATUS_TIMEOUT)
{
PhSetStringSetting2(L"RunAsProgram", &program->sr);
PhSetStringSetting2(L"RunAsUserName", &userName->sr);
EndDialog(hwndDlg, IDOK);
}
}
break;
case IDC_BROWSE:
{
static PH_FILETYPE_FILTER filters[] =
{
{ L"Programs (*.exe;*.pif;*.com;*.bat)", L"*.exe;*.pif;*.com;*.bat" },
{ L"All files (*.*)", L"*.*" }
};
PVOID fileDialog;
fileDialog = PhCreateOpenFileDialog();
PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER));
PhSetFileDialogFileName(fileDialog, PhaGetDlgItemText(hwndDlg, IDC_PROGRAM)->Buffer);
if (PhShowFileDialog(hwndDlg, fileDialog))
{
PPH_STRING fileName;
fileName = PhGetFileDialogFileName(fileDialog);
SetDlgItemText(hwndDlg, IDC_PROGRAM, fileName->Buffer);
PhDereferenceObject(fileName);
}
PhFreeFileDialog(fileDialog);
}
break;
case IDC_USERNAME:
{
PPH_STRING userName = NULL;
if (!context->ProcessId && HIWORD(wParam) == CBN_SELCHANGE)
{
userName = PhAutoDereferenceObject(PhGetComboBoxString(GetDlgItem(hwndDlg, IDC_USERNAME), -1));
}
else if (!context->ProcessId && (
HIWORD(wParam) == CBN_EDITCHANGE ||
HIWORD(wParam) == CBN_CLOSEUP
))
{
userName = PhaGetDlgItemText(hwndDlg, IDC_USERNAME);
}
if (userName)
{
if (IsServiceAccount(userName))
{
EnableWindow(GetDlgItem(hwndDlg, IDC_PASSWORD), FALSE);
// Hack for Windows XP
if (
PhEqualString2(userName, L"NT AUTHORITY\\SYSTEM", TRUE) &&
WindowsVersion <= WINDOWS_XP
)
{
PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"New credentials", FALSE);
}
else
{
PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"Service", FALSE);
}
}
else
{
EnableWindow(GetDlgItem(hwndDlg, IDC_PASSWORD), TRUE);
PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"Interactive", FALSE);
}
}
}
break;
case IDC_SESSIONS:
{
PPH_EMENU sessionsMenu;
PSESSIONIDW sessions;
ULONG numberOfSessions;
ULONG i;
RECT buttonRect;
PPH_EMENU_ITEM selectedItem;
sessionsMenu = PhCreateEMenu();
if (WinStationEnumerateW(NULL, &sessions, &numberOfSessions))
{
for (i = 0; i < numberOfSessions; i++)
{
PPH_STRING menuString;
WINSTATIONINFORMATION winStationInfo;
ULONG returnLength;
if (!WinStationQueryInformationW(
NULL,
sessions[i].SessionId,
WinStationInformation,
&winStationInfo,
sizeof(WINSTATIONINFORMATION),
&returnLength
))
{
winStationInfo.Domain[0] = 0;
winStationInfo.UserName[0] = 0;
}
if (
winStationInfo.UserName[0] != 0 &&
sessions[i].WinStationName[0] != 0
)
{
menuString = PhFormatString(
L"%u: %s (%s\\%s)",
sessions[i].SessionId,
sessions[i].WinStationName,
winStationInfo.Domain,
winStationInfo.UserName
);
}
else if (winStationInfo.UserName[0] != 0)
{
menuString = PhFormatString(
L"%u: %s\\%s",
sessions[i].SessionId,
winStationInfo.Domain,
winStationInfo.UserName
);
}
else if (sessions[i].WinStationName[0] != 0)
{
menuString = PhFormatString(
L"%u: %s",
sessions[i].SessionId,
sessions[i].WinStationName
);
}
else
{
menuString = PhFormatString(L"%u", sessions[i].SessionId);
}
PhInsertEMenuItem(sessionsMenu,
PhCreateEMenuItem(0, 0, menuString->Buffer, NULL, UlongToPtr(sessions[i].SessionId)), -1);
PhAutoDereferenceObject(menuString);
}
WinStationFreeMemory(sessions);
GetWindowRect(GetDlgItem(hwndDlg, IDC_SESSIONS), &buttonRect);
selectedItem = PhShowEMenu(
sessionsMenu,
hwndDlg,
PH_EMENU_SHOW_LEFTRIGHT,
PH_ALIGN_LEFT | PH_ALIGN_TOP,
buttonRect.right,
buttonRect.top
);
if (selectedItem)
{
SetDlgItemInt(
hwndDlg,
IDC_SESSIONID,
PtrToUlong(selectedItem->Context),
FALSE
);
}
PhDestroyEMenu(sessionsMenu);
}
}
break;
case IDC_DESKTOPS:
{
PPH_EMENU desktopsMenu;
ULONG i;
RECT buttonRect;
PPH_EMENU_ITEM selectedItem;
desktopsMenu = PhCreateEMenu();
if (!context->DesktopList)
context->DesktopList = PhCreateList(10);
context->CurrentWinStaName = GetCurrentWinStaName();
EnumDesktops(GetProcessWindowStation(), EnumDesktopsCallback, (LPARAM)context);
for (i = 0; i < context->DesktopList->Count; i++)
{
PhInsertEMenuItem(
desktopsMenu,
PhCreateEMenuItem(0, 0, ((PPH_STRING)context->DesktopList->Items[i])->Buffer, NULL, NULL),
-1
);
}
GetWindowRect(GetDlgItem(hwndDlg, IDC_DESKTOPS), &buttonRect);
selectedItem = PhShowEMenu(
desktopsMenu,
hwndDlg,
PH_EMENU_SHOW_LEFTRIGHT,
PH_ALIGN_LEFT | PH_ALIGN_TOP,
buttonRect.right,
buttonRect.top
);
if (selectedItem)
{
SetDlgItemText(
hwndDlg,
IDC_DESKTOP,
selectedItem->Text
);
}
for (i = 0; i < context->DesktopList->Count; i++)
PhDereferenceObject(context->DesktopList->Items[i]);
PhClearList(context->DesktopList);
PhDereferenceObject(context->CurrentWinStaName);
PhDestroyEMenu(desktopsMenu);
}
break;
}
}
break;
}
return FALSE;
}
/**
* Sets the access control lists of the current window station
* and desktop to allow all access.
*/
VOID PhSetDesktopWinStaAccess(
VOID
)
{
static SID_IDENTIFIER_AUTHORITY appPackageAuthority = SECURITY_APP_PACKAGE_AUTHORITY;
HWINSTA wsHandle;
HDESK desktopHandle;
ULONG allocationLength;
PSECURITY_DESCRIPTOR securityDescriptor;
PACL dacl;
CHAR allAppPackagesSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2];
PSID allAppPackagesSid;
// TODO: Set security on the correct window station and desktop.
allAppPackagesSid = (PISID)allAppPackagesSidBuffer;
RtlInitializeSid(allAppPackagesSid, &appPackageAuthority, SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT);
*RtlSubAuthoritySid(allAppPackagesSid, 0) = SECURITY_APP_PACKAGE_BASE_RID;
*RtlSubAuthoritySid(allAppPackagesSid, 1) = SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE;
// We create a DACL that allows everyone to access everything.
allocationLength = SECURITY_DESCRIPTOR_MIN_LENGTH +
(ULONG)sizeof(ACL) +
(ULONG)sizeof(ACCESS_ALLOWED_ACE) +
RtlLengthSid(&PhSeEveryoneSid) +
(ULONG)sizeof(ACCESS_ALLOWED_ACE) +
RtlLengthSid(allAppPackagesSid);
securityDescriptor = PhAllocate(allocationLength);
dacl = (PACL)((PCHAR)securityDescriptor + SECURITY_DESCRIPTOR_MIN_LENGTH);
RtlCreateSecurityDescriptor(securityDescriptor, SECURITY_DESCRIPTOR_REVISION);
RtlCreateAcl(dacl, allocationLength - SECURITY_DESCRIPTOR_MIN_LENGTH, ACL_REVISION);
RtlAddAccessAllowedAce(dacl, ACL_REVISION, GENERIC_ALL, &PhSeEveryoneSid);
if (WindowsVersion >= WINDOWS_8)
{
RtlAddAccessAllowedAce(dacl, ACL_REVISION, GENERIC_ALL, allAppPackagesSid);
}
RtlSetDaclSecurityDescriptor(securityDescriptor, TRUE, dacl, FALSE);
if (wsHandle = OpenWindowStation(
L"WinSta0",
FALSE,
WRITE_DAC
))
{
PhSetObjectSecurity(wsHandle, DACL_SECURITY_INFORMATION, securityDescriptor);
CloseWindowStation(wsHandle);
}
if (desktopHandle = OpenDesktop(
L"Default",
0,
FALSE,
WRITE_DAC | DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS
))
{
PhSetObjectSecurity(desktopHandle, DACL_SECURITY_INFORMATION, securityDescriptor);
CloseDesktop(desktopHandle);
}
PhFree(securityDescriptor);
}
/**
* Executes the run-as service.
*
* \param Parameters The run-as parameters.
*
* \remarks This function requires administrator-level access.
*/
NTSTATUS PhExecuteRunAsCommand(
_In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters
)
{
NTSTATUS status;
ULONG win32Result;
PPH_STRING commandLine;
SC_HANDLE scManagerHandle;
SC_HANDLE serviceHandle;
PPH_STRING portName;
UNICODE_STRING portNameUs;
ULONG attempts;
LARGE_INTEGER interval;
if (!(scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)))
return PhGetLastWin32ErrorAsNtStatus();
commandLine = PhFormatString(L"\"%s\" -ras \"%s\"", PhApplicationFileName->Buffer, Parameters->ServiceName);
serviceHandle = CreateService(
scManagerHandle,
Parameters->ServiceName,
Parameters->ServiceName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE,
commandLine->Buffer,
NULL,
NULL,
NULL,
L"LocalSystem",
L""
);
win32Result = GetLastError();
PhDereferenceObject(commandLine);
CloseServiceHandle(scManagerHandle);
if (!serviceHandle)
{
return NTSTATUS_FROM_WIN32(win32Result);
}
PhSetDesktopWinStaAccess();
StartService(serviceHandle, 0, NULL);
DeleteService(serviceHandle);
portName = PhConcatStrings2(L"\\BaseNamedObjects\\", Parameters->ServiceName);
PhStringRefToUnicodeString(&portName->sr, &portNameUs);
attempts = 10;
// Try to connect several times because the server may take
// a while to initialize.
do
{
status = PhSvcConnectToServer(&portNameUs, 0);
if (NT_SUCCESS(status))
break;
interval.QuadPart = -50 * PH_TIMEOUT_MS;
NtDelayExecution(FALSE, &interval);
} while (--attempts != 0);
PhDereferenceObject(portName);
if (NT_SUCCESS(status))
{
status = PhSvcCallInvokeRunAsService(Parameters);
PhSvcDisconnectFromServer();
}
if (serviceHandle)
CloseServiceHandle(serviceHandle);
return status;
}
/**
* Starts a program as another user.
*
* \param hWnd A handle to the parent window.
* \param Program The command line of the program to start.
* \param UserName The user to start the program as. The user
* name should be specified as: domain\\name. This parameter
* can be NULL if \a ProcessIdWithToken is specified.
* \param Password The password for the specified user. If there
* is no password, specify an empty string. This parameter
* can be NULL if \a ProcessIdWithToken is specified.
* \param LogonType The logon type for the specified user. This
* parameter can be 0 if \a ProcessIdWithToken is specified.
* \param ProcessIdWithToken The ID of a process from which
* to duplicate the token.
* \param SessionId The ID of the session to run the program
* under.
* \param DesktopName The window station and desktop to run the
* program under.
* \param UseLinkedToken Uses the linked token if possible.
*
* \retval STATUS_CANCELLED The user cancelled the operation.
*
* \remarks This function will cause another instance of
* Process Hacker to be executed if the current security context
* does not have sufficient system access. This is done
* through a UAC elevation prompt.
*/
NTSTATUS PhExecuteRunAsCommand2(
_In_ HWND hWnd,
_In_ PWSTR Program,
_In_opt_ PWSTR UserName,
_In_opt_ PWSTR Password,
_In_opt_ ULONG LogonType,
_In_opt_ HANDLE ProcessIdWithToken,
_In_ ULONG SessionId,
_In_ PWSTR DesktopName,
_In_ BOOLEAN UseLinkedToken
)
{
NTSTATUS status = STATUS_SUCCESS;
PH_RUNAS_SERVICE_PARAMETERS parameters;
WCHAR serviceName[32];
PPH_STRING portName;
UNICODE_STRING portNameUs;
memset(&parameters, 0, sizeof(PH_RUNAS_SERVICE_PARAMETERS));
parameters.ProcessId = HandleToUlong(ProcessIdWithToken);
parameters.UserName = UserName;
parameters.Password = Password;
parameters.LogonType = LogonType;
parameters.SessionId = SessionId;
parameters.CommandLine = Program;
parameters.DesktopName = DesktopName;
parameters.UseLinkedToken = UseLinkedToken;
// Try to use an existing instance of the service if possible.
if (RunAsOldServiceName[0] != 0)
{
PhAcquireQueuedLockExclusive(&RunAsOldServiceLock);
portName = PhConcatStrings2(L"\\BaseNamedObjects\\", RunAsOldServiceName);
PhStringRefToUnicodeString(&portName->sr, &portNameUs);
if (NT_SUCCESS(PhSvcConnectToServer(&portNameUs, 0)))
{
parameters.ServiceName = RunAsOldServiceName;
status = PhSvcCallInvokeRunAsService(&parameters);
PhSvcDisconnectFromServer();
PhDereferenceObject(portName);
PhReleaseQueuedLockExclusive(&RunAsOldServiceLock);
return status;
}
PhDereferenceObject(portName);
PhReleaseQueuedLockExclusive(&RunAsOldServiceLock);
}
// An existing instance was not available. Proceed normally.
memcpy(serviceName, L"ProcessHacker", 13 * sizeof(WCHAR));
PhGenerateRandomAlphaString(&serviceName[13], 16);
PhAcquireQueuedLockExclusive(&RunAsOldServiceLock);
memcpy(RunAsOldServiceName, serviceName, sizeof(serviceName));
PhReleaseQueuedLockExclusive(&RunAsOldServiceLock);
parameters.ServiceName = serviceName;
if (PhElevated)
{
status = PhExecuteRunAsCommand(&parameters);
}
else
{
if (PhUiConnectToPhSvc(hWnd, FALSE))
{
status = PhSvcCallExecuteRunAsCommand(&parameters);
PhUiDisconnectFromPhSvc();
}
else
{
status = STATUS_CANCELLED;
}
}
return status;
}
static VOID PhpSplitUserName(
_In_ PWSTR UserName,
_Out_ PPH_STRING *DomainPart,
_Out_ PPH_STRING *UserPart
)
{
PH_STRINGREF userName;
PH_STRINGREF domainPart;
PH_STRINGREF userPart;
PhInitializeStringRefLongHint(&userName, UserName);
if (PhSplitStringRefAtChar(&userName, '\\', &domainPart, &userPart))
{
*DomainPart = PhCreateString2(&domainPart);
*UserPart = PhCreateString2(&userPart);
}
else
{
*DomainPart = NULL;
*UserPart = PhCreateString2(&userName);
}
}
static VOID SetRunAsServiceStatus(
_In_ ULONG State
)
{
SERVICE_STATUS status;
memset(&status, 0, sizeof(SERVICE_STATUS));
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
status.dwCurrentState = State;
status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
SetServiceStatus(RunAsServiceStatusHandle, &status);
}
static DWORD WINAPI RunAsServiceHandlerEx(
_In_ DWORD dwControl,
_In_ DWORD dwEventType,
_In_ LPVOID lpEventData,
_In_ LPVOID lpContext
)
{
switch (dwControl)
{
case SERVICE_CONTROL_STOP:
PhSvcStop(&RunAsServiceStop);
return NO_ERROR;
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
static VOID WINAPI RunAsServiceMain(
_In_ DWORD dwArgc,
_In_ LPTSTR *lpszArgv
)
{
PPH_STRING portName;
UNICODE_STRING portNameUs;
LARGE_INTEGER timeout;
memset(&RunAsServiceStop, 0, sizeof(PHSVC_STOP));
RunAsServiceStatusHandle = RegisterServiceCtrlHandlerEx(RunAsServiceName->Buffer, RunAsServiceHandlerEx, NULL);
SetRunAsServiceStatus(SERVICE_RUNNING);
portName = PhConcatStrings2(L"\\BaseNamedObjects\\", RunAsServiceName->Buffer);
PhStringRefToUnicodeString(&portName->sr, &portNameUs);
// Use a shorter timeout value to reduce the time spent running as SYSTEM.
timeout.QuadPart = -5 * PH_TIMEOUT_SEC;
PhSvcMain(&portNameUs, &timeout, &RunAsServiceStop);
SetRunAsServiceStatus(SERVICE_STOPPED);
}
NTSTATUS PhRunAsServiceStart(
_In_ PPH_STRING ServiceName
)
{
HANDLE tokenHandle;
SERVICE_TABLE_ENTRY entry;
// Enable some required privileges.
if (NT_SUCCESS(PhOpenProcessToken(
&tokenHandle,
TOKEN_ADJUST_PRIVILEGES,
NtCurrentProcess()
)))
{
PhSetTokenPrivilege(tokenHandle, L"SeAssignPrimaryTokenPrivilege", NULL, SE_PRIVILEGE_ENABLED);
PhSetTokenPrivilege(tokenHandle, L"SeBackupPrivilege", NULL, SE_PRIVILEGE_ENABLED);
PhSetTokenPrivilege(tokenHandle, L"SeImpersonatePrivilege", NULL, SE_PRIVILEGE_ENABLED);
PhSetTokenPrivilege(tokenHandle, L"SeIncreaseQuotaPrivilege", NULL, SE_PRIVILEGE_ENABLED);
PhSetTokenPrivilege(tokenHandle, L"SeRestorePrivilege", NULL, SE_PRIVILEGE_ENABLED);
NtClose(tokenHandle);
}
RunAsServiceName = ServiceName;
entry.lpServiceName = ServiceName->Buffer;
entry.lpServiceProc = RunAsServiceMain;
StartServiceCtrlDispatcher(&entry);
return STATUS_SUCCESS;
}
NTSTATUS PhInvokeRunAsService(
_In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters
)
{
NTSTATUS status;
PPH_STRING domainName;
PPH_STRING userName;
PH_CREATE_PROCESS_AS_USER_INFO createInfo;
ULONG flags;
if (Parameters->UserName)
{
PhpSplitUserName(Parameters->UserName, &domainName, &userName);
}
else
{
domainName = NULL;
userName = NULL;
}
memset(&createInfo, 0, sizeof(PH_CREATE_PROCESS_AS_USER_INFO));
createInfo.ApplicationName = Parameters->FileName;
createInfo.CommandLine = Parameters->CommandLine;
createInfo.CurrentDirectory = Parameters->CurrentDirectory;
createInfo.DomainName = PhGetString(domainName);
createInfo.UserName = PhGetString(userName);
createInfo.Password = Parameters->Password;
createInfo.LogonType = Parameters->LogonType;
createInfo.SessionId = Parameters->SessionId;
createInfo.DesktopName = Parameters->DesktopName;
flags = PH_CREATE_PROCESS_SET_SESSION_ID;
if (Parameters->ProcessId)
{
createInfo.ProcessIdWithToken = UlongToHandle(Parameters->ProcessId);
flags |= PH_CREATE_PROCESS_USE_PROCESS_TOKEN;
}
if (Parameters->UseLinkedToken)
flags |= PH_CREATE_PROCESS_USE_LINKED_TOKEN;
status = PhCreateProcessAsUser(
&createInfo,
flags,
NULL,
NULL,
NULL
);
if (domainName) PhDereferenceObject(domainName);
if (userName) PhDereferenceObject(userName);
return status;
}