initial
This commit is contained in:
commit
58f2e01a90
8
binding.gyp
Normal file
8
binding.gyp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "winutils",
|
||||||
|
"sources": [ "index.cc" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
271
index.cc
Normal file
271
index.cc
Normal file
@ -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)
|
2
index.js
Normal file
2
index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
'use strict'
|
||||||
|
module.exports = require('./build/Release/winutils.node')
|
7
package.json
Normal file
7
package.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"gypfile": true,
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "winutils",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"os": ["win32"]
|
||||||
|
}
|
93
utils.h
Normal file
93
utils.h
Normal file
@ -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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user