diff --git a/package-lock.json b/package-lock.json index 519d1a6..d4667ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,22 +9,29 @@ "version": "3.5.0", "license": "MIT", "dependencies": { + "koffi": "^2.9.0", "ws": "^8.11.0" }, "bin": { "arrpc": "src/index.js" } }, + "node_modules/koffi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.9.0.tgz", + "integrity": "sha512-KCsuJ2gM58n6bNdR2Z7gqsh/3TchxxQFbVgax2/UvAjRTgwNSYAJDx9E3jrkBP4jEDHWRCfE47Y2OG+/fiSvEw==", + "hasInstallScript": true + }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 96fe4ff..110daa9 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,11 @@ }, "homepage": "https://github.com/OpenAsar/arrpc#readme", "dependencies": { + "koffi": "^2.9.0", "ws": "^8.11.0" }, "type": "module", "bin": { "arrpc": "src/index.js" } -} \ No newline at end of file +} diff --git a/src/process/index.js b/src/process/index.js index f445024..c88dba7 100644 --- a/src/process/index.js +++ b/src/process/index.js @@ -35,7 +35,7 @@ export default class ProcessServer { // log(`got processed in ${(performance.now() - startTime).toFixed(2)}ms`); for (const [ pid, _path, args ] of processes) { - const path = _path.toLowerCase().replaceAll('\\', '/'); + const path = _path.toLowerCase().replaceAll('\\', '/').replaceAll('\x00', ''); const toCompare = []; const splitPath = path.split('/'); for (let i = 1; i < splitPath.length; i++) { diff --git a/src/process/native/win32.js b/src/process/native/win32.js index 3e38578..0a6c549 100644 --- a/src/process/native/win32.js +++ b/src/process/native/win32.js @@ -1,8 +1,88 @@ -import { exec } from 'child_process'; - -export const getProcesses = () => new Promise(res => exec(`wmic process get ProcessID,ExecutablePath /format:csv`, (e, out) => { - res(out.toString().split('\r\n').slice(2).map(x => { - const parsed = x.trim().split(',').slice(1).reverse(); - return [ parseInt(parsed[0]) || parsed[0], parsed[1] ]; - }).filter(x => x[1])); -})); +import koffi from 'koffi'; + +// Load Windows API +const psapi = koffi.load('psapi.dll'); +const kernel32 = koffi.load('kernel32.dll'); +const ntdll = koffi.load('ntdll.dll'); + +// Define Alias +const DWORD = koffi.alias('DWORD', 'uint32_t'); +const BOOL = koffi.alias('BOOL', 'int32_t'); +const HANDLE = koffi.pointer('HANDLE', koffi.opaque()) + +const UNICODE_STRING = koffi.struct('UNICODE_STRING', { + Length: 'uint16', + MaximumLength: 'uint16', + Buffer: HANDLE +}); + +const SYSTEM_PROCESS_ID_INFORMATION = koffi.struct('SYSTEM_PROCESS_ID_INFORMATION', { + ProcessId: HANDLE, + ImageName: UNICODE_STRING +}); + +const EnumProcesses = psapi.func('BOOL __stdcall EnumProcesses(_Out_ DWORD *lpidProcess, DWORD cb, _Out_ DWORD *lpcbNeeded)') +const GetLastError = kernel32.func('DWORD GetLastError()') +const NtQuerySystemInformation = ntdll.func('NtQuerySystemInformation', 'int32', ['int32', 'SYSTEM_PROCESS_ID_INFORMATION*', 'uint32', HANDLE]); + +const SystemProcessIdInformation = 88; // SYSTEM_INFORMATION_CLASS enum value for SystemProcessIdInformation + +const STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; +const NT_SUCCESS = (status) => status >= 0; +const NT_ERROR = (status) => status < 0; + +// Using undocumented function NtQuerySystemInformation() +// to circumvent limited privilege of wmic or gwmi +// when querying executable path of a process +const getProcessImageName = (pid) => { + let bufferSize = 1024; + let buffer = Buffer.alloc(bufferSize); + + while (true) { + const info = { + ProcessId: pid, + ImageName: { + Length: 0, + MaximumLength: buffer.length, + Buffer: buffer + } + }; + + const result = NtQuerySystemInformation(SystemProcessIdInformation, info, 24, null); + + if (NT_ERROR(result) && result !== STATUS_INFO_LENGTH_MISMATCH) { + console.error(`NtQuerySystemInformation() failed with pid = ${pid}, error = ${result}`); + return null; + } + + if (NT_SUCCESS(result)) { + return buffer.subarray(0, buffer.length).toString('utf16le'); + } + + bufferSize *= 2; + if (bufferSize > 0xffff) bufferSize = 0xffff; + buffer = Buffer.alloc(bufferSize); + } +}; + +export const getProcesses = () => new Promise(res => { + const processIds = new Uint32Array(1024); + const bytesNeeded = new Uint32Array(1); + let out = [] + + const success = EnumProcesses(processIds, processIds.byteLength, bytesNeeded) + if (!success) { + console.log(GetLastError()) + } else { + const numProcesses = bytesNeeded[0] / 4; // Divide by 4 because DWORD is 4 bytes + for(let i = 0; i < numProcesses; ++i) { + if (processIds[i]) { + let imageName = getProcessImageName(processIds[i]) + if( imageName != null ){ + out.push([processIds, imageName]) + } + } + } + } + res(out) +});