diff options
-rw-r--r-- | binding.gyp | 8 | ||||
-rw-r--r-- | index.cc | 271 | ||||
-rw-r--r-- | index.js | 2 | ||||
-rw-r--r-- | package.json | 7 | ||||
-rw-r--r-- | utils.h | 93 |
5 files changed, 381 insertions, 0 deletions
diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..e0c5e4c --- /dev/null +++ b/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "winutils", + "sources": [ "index.cc" ] + } + ] +} diff --git a/index.cc b/index.cc new file mode 100644 index 0000000..289c2b9 --- /dev/null +++ b/index.cc @@ -0,0 +1,271 @@ +#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); + + String::Utf8Value cmdLineArg(args[1]); + std::string cmdLine(*cmdLineArg); + + SHELLEXECUTEINFO shExInfo = {0}; + shExInfo.cbSize = sizeof(shExInfo); + shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + shExInfo.hwnd = 0; + shExInfo.lpVerb = "runas"; + shExInfo.lpFile = exePath.c_str(); + shExInfo.lpParameters = 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, static_cast<BYTE*>(&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 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_MODULE(winutils, Init) diff --git a/index.js b/index.js new file mode 100644 index 0000000..ba4ac3d --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +'use strict' +module.exports = require('./build/Release/winutils.node') diff --git a/package.json b/package.json new file mode 100644 index 0000000..ade919b --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "gypfile": true, + "main": "index.js", + "name": "winutils", + "version": "1.0.0", + "os": ["win32"] +} @@ -0,0 +1,93 @@ +#pragma once + +// ------------------------------------------------------------------------------------------ +// Structure and operators to insert a zero-filled hex-formatted number into a std::ostream. +struct HEX +{ + HEX(unsigned long num, unsigned long fieldwidth = 8, bool bUpcase = false) + : m_num(num), m_width(fieldwidth), m_upcase(bUpcase) + {} + + unsigned long m_num; + unsigned long m_width; + bool m_upcase; +}; + +inline std::ostream& operator << ( std::ostream& os, const HEX & h ) +{ + int fmt = os.flags(); + char fillchar = os.fill('0'); + os << "0x" << std::hex << (h.m_upcase ? std::uppercase : std::nouppercase) << std::setw(h.m_width) << h.m_num ; + os.fill(fillchar); + os.flags(fmt); + return os; +} + +inline std::wostream& operator << ( std::wostream& os, const HEX & h ) +{ + int fmt = os.flags(); + wchar_t fillchar = os.fill(L'0'); + os << L"0x" << std::hex << (h.m_upcase ? std::uppercase : std::nouppercase) << std::setw(h.m_width) << h.m_num ; + os.fill(fillchar); + os.flags(fmt); + return os; +} + + +// ------------------------------------------------------------------------------------------ + +// Convert an error code to corresponding text, returning it as a std::wstring. + +inline std::wstring SysErrorMessageWithCode(DWORD dwErrCode /*= GetLastError()*/) +{ + LPWSTR pszErrMsg = NULL; + std::wstringstream sRetval; + DWORD flags = + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM ; + + if ( FormatMessageW( + flags, + NULL, + dwErrCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPWSTR) &pszErrMsg, + 0, + NULL ) ) + { + sRetval << pszErrMsg << L" (Error # " << dwErrCode << L" = " << HEX(dwErrCode) << L")"; + LocalFree(pszErrMsg); + } + else + { + sRetval << L"Error # " << dwErrCode << L" (" << HEX(dwErrCode) << L")"; + } + return sRetval.str(); +} + +// Convert std::string to std::wstring +std::wstring s2ws(const std::string& s) +{ + int len; + int slength = (int)s.length() + 1; + len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); + wchar_t* buf = new wchar_t[len]; + MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); + std::wstring r(buf); + delete[] buf; + return r; +} + +// Convert std::wstring to std::string +std::string ws2s(const std::wstring& s) +{ + int len; + int slength = (int)s.length() + 1; + len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0); + char* buf = new char[len]; + WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, buf, len, 0, 0); + std::string r(buf); + delete[] buf; + return r; +} |