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