@jshookmcp/jshook 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -40
- package/README.zh.md +77 -40
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +13 -1
- package/dist/index.js +0 -0
- package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
- package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
- package/dist/modules/browser/BrowserModeManager.js +96 -10
- package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
- package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
- package/dist/modules/browser/TabRegistry.js +3 -2
- package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
- package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
- package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
- package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
- package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
- package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
- package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
- package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
- package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
- package/dist/modules/monitor/PerformanceMonitor.js +64 -32
- package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
- package/dist/modules/process/LinuxProcessManager.js +7 -3
- package/dist/modules/process/MacProcessManager.d.ts +3 -1
- package/dist/modules/process/MacProcessManager.js +7 -3
- package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
- package/dist/modules/process/ProcessManager.impl.js +54 -13
- package/dist/modules/process/index.d.ts +3 -1
- package/dist/modules/process/index.js +2 -2
- package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
- package/dist/modules/process/memory/AuditTrail.js +44 -0
- package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
- package/dist/modules/process/memory/linux/mapsParser.js +28 -0
- package/dist/modules/process/memory/regions.enumerate.js +45 -1
- package/dist/modules/process/memory/regions.protection.js +48 -2
- package/dist/modules/process/memory/scanner.d.ts +4 -1
- package/dist/modules/process/memory/scanner.js +225 -24
- package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
- package/dist/native/NativeMemoryManager.impl.js +72 -24
- package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
- package/dist/native/NativeMemoryManager.utils.js +44 -1
- package/dist/server/MCPServer.search.d.ts +3 -0
- package/dist/server/MCPServer.search.js +21 -2
- package/dist/server/ToolCallContextGuard.d.ts +2 -0
- package/dist/server/ToolCallContextGuard.js +29 -14
- package/dist/server/ToolSearch.js +11 -5
- package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
- package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
- package/dist/server/domains/process/definitions.js +20 -7
- package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
- package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
- package/dist/server/domains/process/manifest.js +1 -0
- package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
- package/dist/server/registry/discovery.js +17 -12
- package/dist/server/registry/index.js +10 -2
- package/dist/utils/TokenBudgetManager.d.ts +1 -0
- package/dist/utils/TokenBudgetManager.js +22 -0
- package/package.json +28 -41
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
1
2
|
import { logger } from '../../../utils/logger.js';
|
|
2
3
|
import { execAsync, executePowerShellScript } from '../../process/memory/types.js';
|
|
4
|
+
import { parseProcMaps, formatLinuxProtection } from './linux/mapsParser.js';
|
|
5
|
+
import { nativeMemoryManager } from '../../../native/NativeMemoryManager.js';
|
|
6
|
+
import { isKoffiAvailable } from '../../../native/NativeMemoryManager.utils.js';
|
|
3
7
|
function buildProtectionCheckScript(pid, address) {
|
|
4
8
|
return `
|
|
5
9
|
Add-Type @"
|
|
@@ -110,8 +114,28 @@ try {
|
|
|
110
114
|
`.trim();
|
|
111
115
|
}
|
|
112
116
|
export async function checkMemoryProtection(platform, pid, address) {
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
const addrNum = BigInt(address.startsWith('0x') ? address : `0x${address}`);
|
|
118
|
+
if (platform === 'linux') {
|
|
119
|
+
try {
|
|
120
|
+
const mapsContent = readFileSync(`/proc/${pid}/maps`, 'utf-8');
|
|
121
|
+
const regions = parseProcMaps(mapsContent);
|
|
122
|
+
const region = regions.find(r => addrNum >= r.start && addrNum < r.end);
|
|
123
|
+
if (!region) {
|
|
124
|
+
return { success: false, error: `Address ${address} not found in any memory region` };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
protection: formatLinuxProtection(region.permissions),
|
|
129
|
+
isReadable: region.permissions.read,
|
|
130
|
+
isWritable: region.permissions.write,
|
|
131
|
+
isExecutable: region.permissions.exec,
|
|
132
|
+
regionStart: `0x${region.start.toString(16)}`,
|
|
133
|
+
regionSize: Number(region.end - region.start),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
138
|
+
}
|
|
115
139
|
}
|
|
116
140
|
if (platform === 'darwin') {
|
|
117
141
|
try {
|
|
@@ -145,6 +169,28 @@ export async function checkMemoryProtection(platform, pid, address) {
|
|
|
145
169
|
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
146
170
|
}
|
|
147
171
|
}
|
|
172
|
+
if (isKoffiAvailable()) {
|
|
173
|
+
try {
|
|
174
|
+
const nativeResult = await nativeMemoryManager.checkMemoryProtection(pid, address);
|
|
175
|
+
if (nativeResult.success) {
|
|
176
|
+
return nativeResult;
|
|
177
|
+
}
|
|
178
|
+
logger.warn('Native Windows memory protection check failed, falling back to PowerShell', {
|
|
179
|
+
pid,
|
|
180
|
+
address,
|
|
181
|
+
error: nativeResult.error,
|
|
182
|
+
nativeAvailable: isKoffiAvailable(),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
logger.warn('Native Windows memory protection check threw, falling back to PowerShell', {
|
|
187
|
+
pid,
|
|
188
|
+
address,
|
|
189
|
+
error: error instanceof Error ? error.message : String(error),
|
|
190
|
+
nativeAvailable: isKoffiAvailable(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
148
194
|
try {
|
|
149
195
|
const addrNum = parseInt(address, 16);
|
|
150
196
|
if (isNaN(addrNum)) {
|
|
@@ -3,7 +3,10 @@ export declare function buildPatternBytesAndMask(pattern: string, patternType: s
|
|
|
3
3
|
patternBytes: number[];
|
|
4
4
|
mask: number[];
|
|
5
5
|
};
|
|
6
|
-
export declare function patternToBytesMac(pattern: string, patternType: string):
|
|
6
|
+
export declare function patternToBytesMac(pattern: string, patternType: string): {
|
|
7
|
+
bytes: number[];
|
|
8
|
+
mask: number[];
|
|
9
|
+
};
|
|
7
10
|
export declare function scanMemory(platform: Platform, pid: number, pattern: string, patternType?: PatternType): Promise<MemoryScanResult>;
|
|
8
11
|
export declare function scanMemoryFiltered(pid: number, pattern: string, addresses: string[], patternType: PatternType | undefined, _readMemoryFn: (pid: number, address: string, size: number) => Promise<{
|
|
9
12
|
success: boolean;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { readFileSync, openSync, readSync, closeSync } from 'node:fs';
|
|
1
2
|
import { promises as fs } from 'node:fs';
|
|
2
3
|
import { logger } from '../../../utils/logger.js';
|
|
3
4
|
import { execAsync, executePowerShellScript, } from '../../process/memory/types.js';
|
|
5
|
+
import { nativeMemoryManager } from '../../../native/NativeMemoryManager.js';
|
|
6
|
+
import { isKoffiAvailable } from '../../../native/NativeMemoryManager.utils.js';
|
|
7
|
+
import { parseProcMaps } from './linux/mapsParser.js';
|
|
8
|
+
import { findPatternInBuffer } from '../../../native/NativeMemoryManager.utils.js';
|
|
4
9
|
export function buildPatternBytesAndMask(pattern, patternType) {
|
|
5
10
|
let patternBytes = [];
|
|
6
11
|
let mask = [];
|
|
@@ -73,20 +78,27 @@ export function buildPatternBytesAndMask(pattern, patternType) {
|
|
|
73
78
|
return { patternBytes, mask };
|
|
74
79
|
}
|
|
75
80
|
export function patternToBytesMac(pattern, patternType) {
|
|
81
|
+
const bytes = [];
|
|
82
|
+
const mask = [];
|
|
76
83
|
switch (patternType) {
|
|
77
84
|
case 'hex': {
|
|
78
|
-
const
|
|
79
|
-
for (const part of
|
|
80
|
-
if (part === '??' || part === '?' || part === '**')
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const parts = pattern.trim().split(/\s+/);
|
|
86
|
+
for (const part of parts) {
|
|
87
|
+
if (part === '??' || part === '?' || part === '**') {
|
|
88
|
+
bytes.push(0);
|
|
89
|
+
mask.push(0);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const b = parseInt(part, 16);
|
|
93
|
+
if (isNaN(b))
|
|
94
|
+
throw new Error(`Invalid hex byte: ${part}`);
|
|
95
|
+
bytes.push(b);
|
|
96
|
+
mask.push(1);
|
|
97
|
+
}
|
|
86
98
|
}
|
|
87
99
|
if (!bytes.length)
|
|
88
|
-
throw new Error('Pattern is empty
|
|
89
|
-
|
|
100
|
+
throw new Error('Pattern is empty');
|
|
101
|
+
break;
|
|
90
102
|
}
|
|
91
103
|
case 'int32': {
|
|
92
104
|
const v = parseInt(pattern);
|
|
@@ -94,12 +106,18 @@ export function patternToBytesMac(pattern, patternType) {
|
|
|
94
106
|
throw new Error('Invalid int32 value');
|
|
95
107
|
const buf = Buffer.allocUnsafe(4);
|
|
96
108
|
buf.writeInt32LE(v, 0);
|
|
97
|
-
|
|
109
|
+
const arr = Array.from(buf);
|
|
110
|
+
bytes.push(...arr);
|
|
111
|
+
mask.push(...arr.map(() => 1));
|
|
112
|
+
break;
|
|
98
113
|
}
|
|
99
114
|
case 'int64': {
|
|
100
115
|
const buf = Buffer.allocUnsafe(8);
|
|
101
116
|
buf.writeBigInt64LE(BigInt.asIntN(64, BigInt(pattern)), 0);
|
|
102
|
-
|
|
117
|
+
const arr = Array.from(buf);
|
|
118
|
+
bytes.push(...arr);
|
|
119
|
+
mask.push(...arr.map(() => 1));
|
|
120
|
+
break;
|
|
103
121
|
}
|
|
104
122
|
case 'float': {
|
|
105
123
|
const v = parseFloat(pattern);
|
|
@@ -107,7 +125,10 @@ export function patternToBytesMac(pattern, patternType) {
|
|
|
107
125
|
throw new Error('Invalid float value');
|
|
108
126
|
const buf = Buffer.allocUnsafe(4);
|
|
109
127
|
buf.writeFloatLE(v, 0);
|
|
110
|
-
|
|
128
|
+
const arr = Array.from(buf);
|
|
129
|
+
bytes.push(...arr);
|
|
130
|
+
mask.push(...arr.map(() => 1));
|
|
131
|
+
break;
|
|
111
132
|
}
|
|
112
133
|
case 'double': {
|
|
113
134
|
const v = parseFloat(pattern);
|
|
@@ -115,13 +136,21 @@ export function patternToBytesMac(pattern, patternType) {
|
|
|
115
136
|
throw new Error('Invalid double value');
|
|
116
137
|
const buf = Buffer.allocUnsafe(8);
|
|
117
138
|
buf.writeDoubleLE(v, 0);
|
|
118
|
-
|
|
139
|
+
const arr = Array.from(buf);
|
|
140
|
+
bytes.push(...arr);
|
|
141
|
+
mask.push(...arr.map(() => 1));
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'string': {
|
|
145
|
+
const arr = Array.from(Buffer.from(pattern, 'utf8'));
|
|
146
|
+
bytes.push(...arr);
|
|
147
|
+
mask.push(...arr.map(() => 1));
|
|
148
|
+
break;
|
|
119
149
|
}
|
|
120
|
-
case 'string':
|
|
121
|
-
return Array.from(Buffer.from(pattern, 'utf8'));
|
|
122
150
|
default:
|
|
123
151
|
throw new Error(`Unsupported pattern type: ${patternType}`);
|
|
124
152
|
}
|
|
153
|
+
return { bytes, mask };
|
|
125
154
|
}
|
|
126
155
|
function buildMemoryScanScript(pid, pattern, patternType) {
|
|
127
156
|
const { patternBytes, mask } = buildPatternBytesAndMask(pattern, patternType);
|
|
@@ -255,6 +284,28 @@ try {
|
|
|
255
284
|
}
|
|
256
285
|
async function scanMemoryWindows(pid, pattern, patternType) {
|
|
257
286
|
try {
|
|
287
|
+
if (isKoffiAvailable()) {
|
|
288
|
+
try {
|
|
289
|
+
const nativeResult = await nativeMemoryManager.scanMemory(pid, pattern, patternType);
|
|
290
|
+
if (nativeResult.success) {
|
|
291
|
+
return nativeResult;
|
|
292
|
+
}
|
|
293
|
+
logger.warn('Native Windows memory scan failed, falling back to PowerShell', {
|
|
294
|
+
pid,
|
|
295
|
+
patternType,
|
|
296
|
+
error: nativeResult.error,
|
|
297
|
+
nativeAvailable: isKoffiAvailable(),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
logger.warn('Native Windows memory scan threw, falling back to PowerShell', {
|
|
302
|
+
pid,
|
|
303
|
+
patternType,
|
|
304
|
+
error: error instanceof Error ? error.message : String(error),
|
|
305
|
+
nativeAvailable: isKoffiAvailable(),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
258
309
|
const psScript = buildMemoryScanScript(pid, pattern, patternType);
|
|
259
310
|
const { stdout, stderr } = await executePowerShellScript(psScript, {
|
|
260
311
|
maxBuffer: 1024 * 1024 * 50,
|
|
@@ -283,22 +334,166 @@ async function scanMemoryWindows(pid, pattern, patternType) {
|
|
|
283
334
|
};
|
|
284
335
|
}
|
|
285
336
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
337
|
+
function formatLinuxProcAccessError(pid, procFile, error) {
|
|
338
|
+
const err = error;
|
|
339
|
+
switch (err?.code) {
|
|
340
|
+
case 'ENOENT':
|
|
341
|
+
case 'ESRCH':
|
|
342
|
+
return `Process ${pid} no longer exists or /proc/${pid}/${procFile} is unavailable.`;
|
|
343
|
+
case 'EACCES':
|
|
344
|
+
case 'EPERM':
|
|
345
|
+
return `Cannot access /proc/${pid}/${procFile}. Requires root privileges or ptrace access.`;
|
|
346
|
+
default:
|
|
347
|
+
return err instanceof Error ? err.message : String(error);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function scanMemoryLinux(pid, pattern, patternType) {
|
|
351
|
+
let patternBytes;
|
|
352
|
+
let mask;
|
|
353
|
+
try {
|
|
354
|
+
const result = buildPatternBytesAndMask(pattern, patternType);
|
|
355
|
+
patternBytes = result.patternBytes;
|
|
356
|
+
mask = result.mask;
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
return {
|
|
360
|
+
success: false,
|
|
361
|
+
addresses: [],
|
|
362
|
+
error: error instanceof Error ? error.message : 'Invalid pattern',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
let mapsContent;
|
|
367
|
+
try {
|
|
368
|
+
mapsContent = readFileSync(`/proc/${pid}/maps`, 'utf-8');
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
addresses: [],
|
|
374
|
+
error: formatLinuxProcAccessError(pid, 'maps', error),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const linuxRegions = parseProcMaps(mapsContent).filter(r => r.permissions.read);
|
|
378
|
+
let fd;
|
|
379
|
+
try {
|
|
380
|
+
fd = openSync(`/proc/${pid}/mem`, 'r');
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
return {
|
|
384
|
+
success: false,
|
|
385
|
+
addresses: [],
|
|
386
|
+
error: formatLinuxProcAccessError(pid, 'mem', error),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const foundAddresses = new Set();
|
|
390
|
+
const chunkSize = 16 * 1024 * 1024;
|
|
391
|
+
const maxResults = 10000;
|
|
392
|
+
const overlap = Math.max(patternBytes.length - 1, 0);
|
|
393
|
+
try {
|
|
394
|
+
for (const region of linuxRegions) {
|
|
395
|
+
if (foundAddresses.size >= maxResults)
|
|
396
|
+
break;
|
|
397
|
+
if (region.end <= region.start)
|
|
398
|
+
continue;
|
|
399
|
+
let chunkOffset = 0n;
|
|
400
|
+
let carryOver = Buffer.alloc(0);
|
|
401
|
+
const regionSize = region.end - region.start;
|
|
402
|
+
while (chunkOffset < regionSize && foundAddresses.size < maxResults) {
|
|
403
|
+
const remaining = regionSize - chunkOffset;
|
|
404
|
+
const readSize = Number(remaining > BigInt(chunkSize) ? BigInt(chunkSize) : remaining);
|
|
405
|
+
const chunkBuffer = Buffer.allocUnsafe(readSize);
|
|
406
|
+
let bytesRead;
|
|
407
|
+
try {
|
|
408
|
+
bytesRead = readSync(fd, chunkBuffer, 0, readSize, region.start + chunkOffset);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
const err = error;
|
|
412
|
+
if (err?.code === 'EIO' || err?.code === 'EFAULT' || err?.code === 'EACCES' || err?.code === 'EPERM') {
|
|
413
|
+
logger.debug('Skipping unreadable Linux memory region chunk', {
|
|
414
|
+
pid,
|
|
415
|
+
start: `0x${region.start.toString(16)}`,
|
|
416
|
+
offset: chunkOffset.toString(),
|
|
417
|
+
code: err.code,
|
|
418
|
+
});
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
if (bytesRead <= 0) {
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
const chunk = bytesRead === readSize ? chunkBuffer : chunkBuffer.subarray(0, bytesRead);
|
|
427
|
+
const scanBuffer = carryOver.length > 0 ? Buffer.concat([carryOver, chunk]) : chunk;
|
|
428
|
+
const scanBase = region.start + chunkOffset - BigInt(carryOver.length);
|
|
429
|
+
const chunkAdvance = BigInt(bytesRead);
|
|
430
|
+
const isLastChunk = chunkOffset + chunkAdvance >= regionSize || bytesRead < readSize;
|
|
431
|
+
const deferredTail = isLastChunk ? 0 : Math.min(overlap, scanBuffer.length);
|
|
432
|
+
const reportableLimit = scanBuffer.length - deferredTail;
|
|
433
|
+
const matches = findPatternInBuffer(scanBuffer, patternBytes, mask);
|
|
434
|
+
for (const matchOffset of matches) {
|
|
435
|
+
if (!isLastChunk && matchOffset >= reportableLimit) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const absoluteAddress = scanBase + BigInt(matchOffset);
|
|
439
|
+
if (absoluteAddress < region.start || absoluteAddress >= region.end) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
foundAddresses.add(`0x${absoluteAddress.toString(16)}`);
|
|
443
|
+
if (foundAddresses.size >= maxResults) {
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (deferredTail > 0) {
|
|
448
|
+
carryOver = scanBuffer.subarray(scanBuffer.length - deferredTail);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
carryOver = Buffer.alloc(0);
|
|
452
|
+
}
|
|
453
|
+
chunkOffset += chunkAdvance;
|
|
454
|
+
if (bytesRead < readSize) {
|
|
455
|
+
logger.debug('Linux memory scan stopped after short read', {
|
|
456
|
+
pid,
|
|
457
|
+
start: `0x${region.start.toString(16)}`,
|
|
458
|
+
requested: readSize,
|
|
459
|
+
bytesRead,
|
|
460
|
+
});
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
finally {
|
|
467
|
+
closeSync(fd);
|
|
468
|
+
}
|
|
469
|
+
const addresses = Array.from(foundAddresses);
|
|
470
|
+
return {
|
|
471
|
+
success: true,
|
|
472
|
+
addresses,
|
|
473
|
+
stats: { patternLength: patternBytes.length, resultsFound: addresses.length },
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
return {
|
|
478
|
+
success: false,
|
|
479
|
+
addresses: [],
|
|
480
|
+
error: error instanceof Error ? error.message : String(error),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
292
483
|
}
|
|
293
484
|
async function scanMemoryMac(pid, pattern, patternType) {
|
|
294
485
|
let patternBytes;
|
|
486
|
+
let patternMask;
|
|
295
487
|
try {
|
|
296
|
-
|
|
488
|
+
const result = patternToBytesMac(pattern, patternType);
|
|
489
|
+
patternBytes = result.bytes;
|
|
490
|
+
patternMask = result.mask;
|
|
297
491
|
}
|
|
298
492
|
catch (e) {
|
|
299
493
|
return { success: false, addresses: [], error: e instanceof Error ? e.message : 'Invalid pattern' };
|
|
300
494
|
}
|
|
301
495
|
const byteList = patternBytes.map(b => `0x${b.toString(16)}`).join(',');
|
|
496
|
+
const maskList = patternMask.join(',');
|
|
302
497
|
const tag = `${pid}_${Date.now()}`;
|
|
303
498
|
const pyFile = `/tmp/lldb_scan_${tag}.py`;
|
|
304
499
|
const cmdFile = `/tmp/lldb_scan_${tag}.txt`;
|
|
@@ -308,6 +503,7 @@ import lldb, json, sys
|
|
|
308
503
|
def __lldb_init_module(debugger, internal_dict):
|
|
309
504
|
proc = debugger.GetSelectedTarget().GetProcess()
|
|
310
505
|
pat = bytes([${byteList}])
|
|
506
|
+
mask = [${maskList}]
|
|
311
507
|
results = []
|
|
312
508
|
rl = proc.GetMemoryRegions()
|
|
313
509
|
for i in range(rl.GetSize()):
|
|
@@ -325,7 +521,12 @@ def __lldb_init_module(debugger, internal_dict):
|
|
|
325
521
|
continue
|
|
326
522
|
n = len(pat)
|
|
327
523
|
for j in range(len(data) - n + 1):
|
|
328
|
-
|
|
524
|
+
match = True
|
|
525
|
+
for k in range(n):
|
|
526
|
+
if mask[k] == 1 and data[j+k] != pat[k]:
|
|
527
|
+
match = False
|
|
528
|
+
break
|
|
529
|
+
if match:
|
|
329
530
|
results.append(hex(s + j))
|
|
330
531
|
if len(results) >= 1000:
|
|
331
532
|
break
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { MemoryRegion, ModuleInfo, NativeMemoryReadResult, NativeMemoryScanResult, NativeMemoryWriteResult } from './NativeMemoryManager.types.js';
|
|
2
2
|
export type { MemoryRegion, ModuleInfo, NativeMemoryReadResult, NativeMemoryScanResult, NativeMemoryWriteResult, } from './NativeMemoryManager.types.js';
|
|
3
|
+
export declare function scanRegionInChunks(region: {
|
|
4
|
+
baseAddress: bigint;
|
|
5
|
+
regionSize: number;
|
|
6
|
+
}, patternBytes: number[], mask: number[], readChunk: (address: bigint, size: number) => Buffer<ArrayBufferLike>, chunkSize?: number): bigint[];
|
|
3
7
|
export declare class NativeMemoryManager {
|
|
4
8
|
checkAvailability(): Promise<{
|
|
5
9
|
available: boolean;
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
2
|
import { exec } from 'node:child_process';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
|
+
import { cpuLimit } from '../utils/concurrency.js';
|
|
4
5
|
import { PAGE, MEM, openProcessForMemory, CloseHandle, ReadProcessMemory, WriteProcessMemory, VirtualQueryEx, VirtualProtectEx, VirtualAllocEx, CreateRemoteThread, GetModuleHandle, GetProcAddress, NtQueryInformationProcess, EnumProcessModules, GetModuleBaseName, GetModuleInformation, } from './Win32API.js';
|
|
5
6
|
import { findPatternInBuffer, getProtectionString, getStateString, getTypeString, isExecutable, isReadable, isWritable, parsePattern, } from './NativeMemoryManager.utils.js';
|
|
6
7
|
import { checkNativeMemoryAvailability } from './NativeMemoryManager.availability.js';
|
|
7
8
|
const execAsync = promisify(exec);
|
|
9
|
+
const SCAN_CHUNK_SIZE = 16 * 1024 * 1024;
|
|
10
|
+
export function scanRegionInChunks(region, patternBytes, mask, readChunk, chunkSize = SCAN_CHUNK_SIZE) {
|
|
11
|
+
if (patternBytes.length === 0 || region.regionSize < patternBytes.length || chunkSize <= 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const overlap = Math.max(patternBytes.length - 1, 0);
|
|
15
|
+
let carryOver = Buffer.alloc(0);
|
|
16
|
+
const matches = [];
|
|
17
|
+
for (let chunkOffset = 0; chunkOffset < region.regionSize; chunkOffset += chunkSize) {
|
|
18
|
+
const readSize = Math.min(chunkSize, region.regionSize - chunkOffset);
|
|
19
|
+
const chunkAddress = region.baseAddress + BigInt(chunkOffset);
|
|
20
|
+
const chunk = readChunk(chunkAddress, readSize);
|
|
21
|
+
const scanBuffer = carryOver.length > 0 ? Buffer.concat([carryOver, chunk]) : chunk;
|
|
22
|
+
const chunkMatches = findPatternInBuffer(scanBuffer, patternBytes, mask);
|
|
23
|
+
for (const matchOffset of chunkMatches) {
|
|
24
|
+
const regionOffset = chunkOffset + matchOffset - carryOver.length;
|
|
25
|
+
matches.push(region.baseAddress + BigInt(regionOffset));
|
|
26
|
+
}
|
|
27
|
+
if (overlap === 0 || chunkOffset + readSize >= region.regionSize) {
|
|
28
|
+
carryOver = Buffer.alloc(0);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const carrySize = Math.min(overlap, scanBuffer.length);
|
|
32
|
+
carryOver = scanBuffer.subarray(scanBuffer.length - carrySize);
|
|
33
|
+
}
|
|
34
|
+
return matches;
|
|
35
|
+
}
|
|
8
36
|
export class NativeMemoryManager {
|
|
9
37
|
async checkAvailability() {
|
|
10
38
|
return checkNativeMemoryAvailability(execAsync);
|
|
@@ -156,45 +184,65 @@ export class NativeMemoryManager {
|
|
|
156
184
|
if (patternBytes.length === 0) {
|
|
157
185
|
return { success: false, addresses: [], error: 'Invalid pattern' };
|
|
158
186
|
}
|
|
159
|
-
const handle = openProcessForMemory(pid, false);
|
|
160
|
-
const addresses = [];
|
|
161
187
|
const maxResults = 10000;
|
|
188
|
+
const readableRegions = [];
|
|
189
|
+
const handle = openProcessForMemory(pid, false);
|
|
162
190
|
try {
|
|
163
191
|
let address = 0n;
|
|
164
192
|
const maxAddress = BigInt('0x7FFFFFFF0000');
|
|
165
|
-
while (address < maxAddress
|
|
193
|
+
while (address < maxAddress) {
|
|
166
194
|
const { success, info } = VirtualQueryEx(handle, address);
|
|
167
195
|
if (!success || info.RegionSize === 0n) {
|
|
168
196
|
break;
|
|
169
197
|
}
|
|
170
|
-
if (isReadable(info) &&
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (addresses.length >= maxResults)
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
}
|
|
198
|
+
if (isReadable(info) &&
|
|
199
|
+
info.RegionSize > 0n &&
|
|
200
|
+
info.RegionSize <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
201
|
+
readableRegions.push({
|
|
202
|
+
baseAddress: info.BaseAddress,
|
|
203
|
+
regionSize: Number(info.RegionSize),
|
|
204
|
+
});
|
|
183
205
|
}
|
|
184
206
|
address = info.BaseAddress + info.RegionSize;
|
|
185
207
|
}
|
|
186
|
-
return {
|
|
187
|
-
success: true,
|
|
188
|
-
addresses,
|
|
189
|
-
stats: {
|
|
190
|
-
patternLength: patternBytes.length,
|
|
191
|
-
resultsFound: addresses.length,
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
208
|
}
|
|
195
209
|
finally {
|
|
196
210
|
CloseHandle(handle);
|
|
197
211
|
}
|
|
212
|
+
const regionMatches = await Promise.all(readableRegions.map(region => cpuLimit(async () => {
|
|
213
|
+
const scanHandle = openProcessForMemory(pid, false);
|
|
214
|
+
try {
|
|
215
|
+
try {
|
|
216
|
+
return scanRegionInChunks(region, patternBytes, mask, (address, size) => ReadProcessMemory(scanHandle, address, size));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
CloseHandle(scanHandle);
|
|
224
|
+
}
|
|
225
|
+
})));
|
|
226
|
+
const addresses = [];
|
|
227
|
+
for (const matches of regionMatches) {
|
|
228
|
+
for (const foundAddr of matches) {
|
|
229
|
+
addresses.push(`0x${foundAddr.toString(16).toUpperCase()}`);
|
|
230
|
+
if (addresses.length >= maxResults) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (addresses.length >= maxResults) {
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
success: true,
|
|
240
|
+
addresses,
|
|
241
|
+
stats: {
|
|
242
|
+
patternLength: patternBytes.length,
|
|
243
|
+
resultsFound: addresses.length,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
198
246
|
}
|
|
199
247
|
catch (error) {
|
|
200
248
|
logger.error('Native memory scan failed', {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MemoryBasicInfo } from './Win32API.js';
|
|
2
2
|
import type { NativePatternType } from './NativeMemoryManager.types.js';
|
|
3
|
+
export declare function isKoffiAvailable(): boolean;
|
|
3
4
|
export declare function parsePattern(pattern: string, patternType: NativePatternType): {
|
|
4
5
|
patternBytes: number[];
|
|
5
6
|
mask: number[];
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { PAGE, MEM, MEM_TYPE } from './Win32API.js';
|
|
1
|
+
import { PAGE, MEM, MEM_TYPE, isKoffiAvailable as isWin32KoffiAvailable, } from './Win32API.js';
|
|
2
|
+
export function isKoffiAvailable() {
|
|
3
|
+
return isWin32KoffiAvailable();
|
|
4
|
+
}
|
|
2
5
|
export function parsePattern(pattern, patternType) {
|
|
3
6
|
const patternBytes = [];
|
|
4
7
|
const mask = [];
|
|
@@ -67,7 +70,47 @@ export function parsePattern(pattern, patternType) {
|
|
|
67
70
|
}
|
|
68
71
|
return { patternBytes, mask };
|
|
69
72
|
}
|
|
73
|
+
function findExactPatternBMH(buffer, pattern) {
|
|
74
|
+
const matches = [];
|
|
75
|
+
const patternLength = pattern.length;
|
|
76
|
+
if (patternLength === 0 || buffer.length < patternLength) {
|
|
77
|
+
return matches;
|
|
78
|
+
}
|
|
79
|
+
const lastIndex = patternLength - 1;
|
|
80
|
+
const skipTable = new Uint32Array(256);
|
|
81
|
+
skipTable.fill(patternLength);
|
|
82
|
+
for (let i = 0; i < lastIndex; i++) {
|
|
83
|
+
const patternByte = pattern[i];
|
|
84
|
+
if (patternByte !== undefined) {
|
|
85
|
+
skipTable[patternByte] = lastIndex - i;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let offset = 0;
|
|
89
|
+
while (offset <= buffer.length - patternLength) {
|
|
90
|
+
let patternIndex = lastIndex;
|
|
91
|
+
while (patternIndex >= 0 && buffer[offset + patternIndex] === pattern[patternIndex]) {
|
|
92
|
+
patternIndex--;
|
|
93
|
+
}
|
|
94
|
+
if (patternIndex < 0) {
|
|
95
|
+
matches.push(offset);
|
|
96
|
+
offset += 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const skipByte = buffer[offset + lastIndex];
|
|
100
|
+
if (skipByte === undefined) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
offset += skipTable[skipByte] ?? patternLength;
|
|
104
|
+
}
|
|
105
|
+
return matches;
|
|
106
|
+
}
|
|
70
107
|
export function findPatternInBuffer(buffer, pattern, mask) {
|
|
108
|
+
if (pattern.length === 0) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
if (mask.every(value => value === 1)) {
|
|
112
|
+
return findExactPatternBMH(buffer, pattern);
|
|
113
|
+
}
|
|
71
114
|
const matches = [];
|
|
72
115
|
for (let i = 0; i <= buffer.length - pattern.length; i++) {
|
|
73
116
|
let found = true;
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import type { MCPServerContext } from './MCPServer.context.js';
|
|
2
|
+
import { ToolSearchEngine } from './ToolSearch.js';
|
|
3
|
+
export declare function buildSearchSignature(ctx: MCPServerContext): string;
|
|
4
|
+
export declare function getSearchEngine(ctx: MCPServerContext): ToolSearchEngine;
|
|
2
5
|
export declare function registerSearchMetaTools(ctx: MCPServerContext): void;
|
|
@@ -28,7 +28,24 @@ function getCombinedTools(ctx) {
|
|
|
28
28
|
}
|
|
29
29
|
return [...tools.values()];
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
const searchEngineCache = new WeakMap();
|
|
32
|
+
export function buildSearchSignature(ctx) {
|
|
33
|
+
const extParts = [];
|
|
34
|
+
for (const [name, record] of ctx.extensionToolsByName) {
|
|
35
|
+
extParts.push(`${name}:${record.domain}`);
|
|
36
|
+
}
|
|
37
|
+
extParts.sort();
|
|
38
|
+
return [
|
|
39
|
+
ctx.currentTier,
|
|
40
|
+
ctx.extensionWorkflowRuntimeById.size,
|
|
41
|
+
extParts.join('|'),
|
|
42
|
+
].join('::');
|
|
43
|
+
}
|
|
44
|
+
export function getSearchEngine(ctx) {
|
|
45
|
+
const signature = buildSearchSignature(ctx);
|
|
46
|
+
const cached = searchEngineCache.get(ctx);
|
|
47
|
+
if (cached && cached.signature === signature)
|
|
48
|
+
return cached.engine;
|
|
32
49
|
const tools = getCombinedTools(ctx);
|
|
33
50
|
const extensionDomains = getExtensionDomainMap(ctx);
|
|
34
51
|
const domainScoreMultipliers = new Map();
|
|
@@ -43,7 +60,9 @@ function getSearchEngine(ctx) {
|
|
|
43
60
|
toolScoreMultipliers.set('run_extension_workflow', 1.35);
|
|
44
61
|
toolScoreMultipliers.set('list_extension_workflows', 1.25);
|
|
45
62
|
}
|
|
46
|
-
|
|
63
|
+
const engine = new ToolSearchEngine(tools, extensionDomains, domainScoreMultipliers, toolScoreMultipliers);
|
|
64
|
+
searchEngineCache.set(ctx, { signature, engine });
|
|
65
|
+
return engine;
|
|
47
66
|
}
|
|
48
67
|
function getToolByName(ctx) {
|
|
49
68
|
return new Map(getCombinedTools(ctx).map((tool) => [tool.name, tool]));
|
|
@@ -8,11 +8,13 @@ interface TabContextProvider {
|
|
|
8
8
|
}
|
|
9
9
|
export declare class ToolCallContextGuard {
|
|
10
10
|
private getProvider;
|
|
11
|
+
private readonly contextSensitiveCache;
|
|
11
12
|
constructor(getProvider: () => TabContextProvider | null);
|
|
12
13
|
isContextSensitive(toolName: string): boolean;
|
|
13
14
|
enrichResponse<T extends {
|
|
14
15
|
content?: unknown[];
|
|
15
16
|
isError?: boolean;
|
|
16
17
|
}>(toolName: string, response: T): T;
|
|
18
|
+
private spliceTabContext;
|
|
17
19
|
}
|
|
18
20
|
export {};
|