summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--binding.gyp8
-rw-r--r--index.cc271
-rw-r--r--index.js2
-rw-r--r--package.json7
-rw-r--r--utils.h93
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"]
+}
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..aa9d43f
--- /dev/null
+++ b/utils.h
@@ -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;
+}