Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
2 changes: 1 addition & 1 deletion src/process/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down
96 changes: 88 additions & 8 deletions src/process/native/win32.js
Original file line number Diff line number Diff line change
@@ -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
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process_id.htm

It's officially undocumented but you could put this here, it has some good information

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');
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (bufferSize >= 0xffff) {
console.error(`NtQuerySystemInformation() failed with pid = ${pid}, result could not fit in buffer of size 0xffff`)
return null;
}

Just to prevent infinite loops

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)
});