winutils/index.cc
2018-12-16 16:11:27 +03:00

279 lines
9.1 KiB
C++

#include <node.h>
#include <v8.h>
#include "Windows.h"
#include "Shlobj.h"
#include <string>
#include <sstream>
#include <iomanip>
#include "utils.h"
#include <tchar.h>
using namespace v8;
/*++
Routine Description: This routine returns TRUE if the caller's
process is a member of the Administrators local group. Caller is NOT
expected to be impersonating anyone and is expected to be able to
open its own process and process token.
Arguments: None.
Return Value:
TRUE - Caller has Administrators local group.
FALSE - Caller does not have Administrators local group. --
*/
BOOL _isUserAdmin(VOID) {
BOOL b;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
b = AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdministratorsGroup);
if (b) {
if (!CheckTokenMembership(NULL, AdministratorsGroup, &b)) {
b = FALSE;
}
FreeSid(AdministratorsGroup);
}
return b;
}
// Definition of the function this sample is all about.
// The szApp, szCmdLine, szCurrDir, si, and pi parameters are passed directly to CreateProcessWithTokenW.
// sErrorInfo returns text describing any error that occurs.
// Returns "true" on success, "false" on any error.
// It is up to the caller to close the HANDLEs returned in the PROCESS_INFORMATION structure.
bool RunAsDesktopUser(
__in const wchar_t * szApp,
__in wchar_t * szCmdLine,
__in const wchar_t * szCurrDir,
__in STARTUPINFOW & si,
__inout PROCESS_INFORMATION & pi,
__inout std::wstringstream & sErrorInfo)
{
HANDLE hShellProcess = NULL, hShellProcessToken = NULL, hPrimaryToken = NULL;
HWND hwnd = NULL;
DWORD dwPID = 0;
BOOL ret;
DWORD dwLastErr;
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.)
HANDLE hProcessToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken)) {
dwLastErr = GetLastError();
sErrorInfo << L"OpenProcessToken failed: " << SysErrorMessageWithCode(dwLastErr);
return false;
} else {
TOKEN_PRIVILEGES tkp;
tkp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_INCREASE_QUOTA_NAME, &tkp.Privileges[0].Luid);
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hProcessToken, FALSE, &tkp, 0, NULL, NULL);
dwLastErr = GetLastError();
CloseHandle(hProcessToken);
if (ERROR_SUCCESS != dwLastErr) {
sErrorInfo << L"AdjustTokenPrivileges failed: " << SysErrorMessageWithCode(dwLastErr);
return false;
}
}
// Get an HWND representing the desktop shell.
// CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and
// restarted elevated.
hwnd = GetShellWindow();
if (NULL == hwnd) {
sErrorInfo << L"No desktop shell is present";
return false;
}
// Get the PID of the desktop shell process.
GetWindowThreadProcessId(hwnd, &dwPID);
if (0 == dwPID) {
sErrorInfo << L"Unable to get PID of desktop shell.";
return false;
}
// Open the desktop shell process in order to query it (get the token)
hShellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID);
if (!hShellProcess) {
dwLastErr = GetLastError();
sErrorInfo << L"Can't open desktop shell process: " << SysErrorMessageWithCode(dwLastErr);
return false;
}
// From this point down, we have handles to close, so make sure to clean up.
bool retval = false;
// Get the process token of the desktop shell.
ret = OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken);
if (!ret) {
dwLastErr = GetLastError();
sErrorInfo << L"Can't get process token of desktop shell: " << SysErrorMessageWithCode(dwLastErr);
goto cleanup;
}
// Duplicate the shell's process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID;
ret = DuplicateTokenEx(hShellProcessToken, dwTokenRights, NULL, SecurityImpersonation, TokenPrimary, &hPrimaryToken);
if (!ret) {
dwLastErr = GetLastError();
sErrorInfo << L"Can't get primary token: " << SysErrorMessageWithCode(dwLastErr);
goto cleanup;
}
// Start the target process with the new token.
ret = CreateProcessWithTokenW(
hPrimaryToken,
0,
szApp,
szCmdLine,
0,
NULL,
szCurrDir,
&si,
&pi);
if (!ret) {
dwLastErr = GetLastError();
sErrorInfo << L"CreateProcessWithTokenW failed: " << SysErrorMessageWithCode(dwLastErr);
goto cleanup;
}
retval = true;
cleanup:
// Clean up resources
CloseHandle(hShellProcessToken);
CloseHandle(hPrimaryToken);
CloseHandle(hShellProcess);
return retval;
}
void deelevate(const v8::FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
String::Utf8Value exePathArg(args[0]);
std::string exePath(*exePathArg);
std::wstring w_exePath = s2ws(exePath);
String::Utf8Value cmdLineArg(args[1]);
std::string cmdLine(*cmdLineArg);
std::wstring w_cmdLine = s2ws(cmdLine);
// Build the sCmdLine argument and the other args needed for CreateProcessWithTokenW.
std::wstringstream sCmdLine, sErrorInfo;
sCmdLine << L"\"" << w_exePath << L"\" " << w_cmdLine;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
SecureZeroMemory(&si, sizeof(si));
SecureZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
// TODO: casting sCmdLine.str().c_str() to a non-const is a little sloppy. You can do better.
if (RunAsDesktopUser(
w_exePath.c_str(),
(LPWSTR)sCmdLine.str().c_str(),
NULL,
si,
pi,
sErrorInfo))
{
// Make sure to close HANDLEs return in the PROCESS_INFORMATION.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, ws2s(sErrorInfo.str()).c_str())));
return;
}
//args.GetReturnValue().Set(String::NewFromUtf8(isolate, "111"));
}
void elevate(const v8::FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
String::Utf8Value exePathArg(args[0]);
std::string exePath(*exePathArg);
std::wstring w_exePath = s2ws(exePath);
String::Utf8Value cmdLineArg(args[1]);
std::string cmdLine(*cmdLineArg);
std::wstring w_cmdLine = s2ws(cmdLine);
SHELLEXECUTEINFO shExInfo = {0};
shExInfo.cbSize = sizeof(shExInfo);
shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExInfo.hwnd = 0;
shExInfo.lpVerb = L"runas";
shExInfo.lpFile = w_exePath.c_str();
shExInfo.lpParameters = w_cmdLine.c_str();
shExInfo.lpDirectory = 0;
shExInfo.nShow = SW_SHOW;
shExInfo.hInstApp = 0;
if (ShellExecuteEx(&shExInfo)) {
CloseHandle(shExInfo.hProcess);
args.GetReturnValue().Set(Boolean::New(isolate, TRUE));
} else {
args.GetReturnValue().Set(Boolean::New(isolate, FALSE));
}
}
void GetSystem32Path(const v8::FunctionCallbackInfo<Value>& args) {
TCHAR szPath[MAX_PATH];
Isolate* isolate = args.GetIsolate();
if (FAILED(SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, szPath)))
{
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Failed to retrieve a path")));
return;
}
#ifdef UNICODE
std::vector<char> buffer;
int size = WideCharToMultiByte(CP_UTF8, 0, szPath, -1, NULL, 0, NULL, NULL);
if (size > 0) {
buffer.resize(size);
WideCharToMultiByte(CP_UTF8, 0, szPath, -1, &buffer[0], buffer.size(), NULL, NULL);
}
else {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Failed to convert string")));
return;
}
std::string string(&buffer[0]);
#else
std::string string(szPath);
#endif
args.GetReturnValue().Set(String::NewFromUtf8(isolate, string.c_str()));
}
void isUserAdmin(const v8::FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(Boolean::New(isolate, _isUserAdmin()));
}
void resetIconCache(const v8::FunctionCallbackInfo<Value>& args) {
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
}
void Init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "deelevate", deelevate);
NODE_SET_METHOD(exports, "elevate", elevate);
NODE_SET_METHOD(exports, "getSystem32Path", GetSystem32Path);
NODE_SET_METHOD(exports, "isUserAdmin", isUserAdmin);
NODE_SET_METHOD(exports, "resetIconCache", resetIconCache);
}
NODE_MODULE(winutils, Init)