@jshookmcp/jshook 0.1.5 → 0.1.6

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.
Files changed (81) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +72 -40
  3. package/README.zh.md +77 -40
  4. package/dist/constants.d.ts +1 -0
  5. package/dist/constants.js +13 -1
  6. package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
  7. package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
  8. package/dist/modules/browser/BrowserModeManager.js +96 -10
  9. package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
  10. package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
  11. package/dist/modules/browser/TabRegistry.js +3 -2
  12. package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
  13. package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
  14. package/dist/modules/captcha/AICaptchaDetector.js +185 -185
  15. package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
  16. package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
  17. package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
  18. package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
  19. package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
  20. package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
  21. package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
  22. package/dist/modules/monitor/PerformanceMonitor.js +64 -32
  23. package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
  24. package/dist/modules/process/LinuxProcessManager.js +7 -3
  25. package/dist/modules/process/MacProcessManager.d.ts +3 -1
  26. package/dist/modules/process/MacProcessManager.js +32 -28
  27. package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
  28. package/dist/modules/process/ProcessManager.impl.js +54 -13
  29. package/dist/modules/process/index.d.ts +3 -1
  30. package/dist/modules/process/index.js +2 -2
  31. package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
  32. package/dist/modules/process/memory/AuditTrail.js +44 -0
  33. package/dist/modules/process/memory/availability.js +49 -49
  34. package/dist/modules/process/memory/injector.js +185 -185
  35. package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
  36. package/dist/modules/process/memory/linux/mapsParser.js +28 -0
  37. package/dist/modules/process/memory/reader.js +50 -50
  38. package/dist/modules/process/memory/regions.enumerate.js +45 -1
  39. package/dist/modules/process/memory/regions.protection.js +48 -2
  40. package/dist/modules/process/memory/scanner.d.ts +4 -1
  41. package/dist/modules/process/memory/scanner.js +383 -182
  42. package/dist/modules/process/memory/writer.js +54 -54
  43. package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
  44. package/dist/native/NativeMemoryManager.impl.js +72 -24
  45. package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
  46. package/dist/native/NativeMemoryManager.utils.js +44 -1
  47. package/dist/native/scripts/linux/enum-windows.sh +12 -12
  48. package/dist/native/scripts/macos/enum-windows.applescript +22 -22
  49. package/dist/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
  50. package/dist/native/scripts/windows/enum-windows.ps1 +44 -44
  51. package/dist/native/scripts/windows/inject-dll.ps1 +21 -21
  52. package/dist/server/MCPServer.search.d.ts +3 -0
  53. package/dist/server/MCPServer.search.js +21 -2
  54. package/dist/server/ToolCallContextGuard.d.ts +2 -0
  55. package/dist/server/ToolCallContextGuard.js +29 -14
  56. package/dist/server/ToolSearch.js +11 -5
  57. package/dist/server/domains/browser/definitions.tools.page-core.js +53 -53
  58. package/dist/server/domains/browser/definitions.tools.runtime.js +40 -40
  59. package/dist/server/domains/browser/definitions.tools.security.js +76 -76
  60. package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
  61. package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
  62. package/dist/server/domains/process/definitions.js +20 -7
  63. package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
  64. package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
  65. package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
  66. package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
  67. package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
  68. package/dist/server/domains/process/manifest.js +1 -0
  69. package/dist/server/domains/transform/handlers.impl.transform-base.js +102 -102
  70. package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
  71. package/dist/server/domains/workflow/handlers.impl.workflow-base.js +51 -51
  72. package/dist/server/registry/discovery.js +17 -12
  73. package/dist/server/registry/index.js +10 -2
  74. package/dist/utils/TokenBudgetManager.d.ts +1 -0
  75. package/dist/utils/TokenBudgetManager.js +22 -0
  76. package/package.json +5 -1
  77. package/src/native/scripts/linux/enum-windows.sh +12 -12
  78. package/src/native/scripts/macos/enum-windows.applescript +22 -22
  79. package/src/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
  80. package/src/native/scripts/windows/enum-windows.ps1 +44 -44
  81. package/src/native/scripts/windows/inject-dll.ps1 +21 -21
@@ -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 bytes = [];
79
- for (const part of pattern.trim().split(/\s+/)) {
80
- if (part === '??' || part === '?' || part === '**')
81
- continue;
82
- const b = parseInt(part, 16);
83
- if (isNaN(b))
84
- throw new Error(`Invalid hex byte: ${part}`);
85
- bytes.push(b);
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 or all wildcards');
89
- return bytes;
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
- return Array.from(buf);
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
- return Array.from(buf);
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
- return Array.from(buf);
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,146 +136,176 @@ 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
- return Array.from(buf);
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);
128
157
  const patternArray = patternBytes.join(',');
129
158
  const maskArray = mask.join(',');
130
- return `
131
- Add-Type @"
132
- using System;
133
- using System.Runtime.InteropServices;
134
- using System.Collections.Generic;
135
- using System.ComponentModel;
136
-
137
- public class MemoryScanner {
138
- [DllImport("kernel32.dll", SetLastError = true)]
139
- public static extern IntPtr OpenProcess(int access, bool inherit, int pid);
140
-
141
- [DllImport("kernel32.dll", SetLastError = true)]
142
- public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr addr, byte[] buffer, int size, out int read);
143
-
144
- [DllImport("kernel32.dll", SetLastError = true)]
145
- public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr addr, out MEMORY_BASIC_INFORMATION info, int size);
146
-
147
- [DllImport("kernel32.dll", SetLastError = true)]
148
- public static extern bool CloseHandle(IntPtr handle);
149
-
150
- const int PROCESS_VM_READ = 0x0010;
151
- const int PROCESS_QUERY_INFORMATION = 0x0400;
152
-
153
- [StructLayout(LayoutKind.Sequential)]
154
- public struct MEMORY_BASIC_INFORMATION {
155
- public IntPtr BaseAddress;
156
- public IntPtr AllocationBase;
157
- public uint AllocationProtect;
158
- public IntPtr RegionSize;
159
- public uint State;
160
- public uint Protect;
161
- public uint Type;
162
- }
163
-
164
- const uint MEM_COMMIT = 0x1000;
165
- const uint PAGE_READONLY = 0x02;
166
- const uint PAGE_READWRITE = 0x04;
167
- const uint PAGE_WRITECOPY = 0x08;
168
- const uint PAGE_EXECUTE_READ = 0x20;
169
- const uint PAGE_EXECUTE_READWRITE = 0x40;
170
-
171
- public static List<string> ScanMemory(int pid, byte[] pattern, byte[] mask, int maxResults = 10000) {
172
- var results = new List<string>();
173
- IntPtr hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, pid);
174
- if (hProcess == IntPtr.Zero) {
175
- int error = Marshal.GetLastWin32Error();
176
- throw new Win32Exception(error, "Failed to open process. Run as Administrator.");
177
- }
178
-
179
- try {
180
- IntPtr addr = IntPtr.Zero;
181
- MEMORY_BASIC_INFORMATION info;
182
- int infoSize = Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION));
183
- int scannedRegions = 0;
184
-
185
- while (VirtualQueryEx(hProcess, addr, out info, infoSize) == infoSize) {
186
- scannedRegions++;
187
- bool isReadable = (info.State == MEM_COMMIT) &&
188
- ((info.Protect & PAGE_READONLY) != 0 ||
189
- (info.Protect & PAGE_READWRITE) != 0 ||
190
- (info.Protect & PAGE_WRITECOPY) != 0 ||
191
- (info.Protect & PAGE_EXECUTE_READ) != 0 ||
192
- (info.Protect & PAGE_EXECUTE_READWRITE) != 0);
193
-
194
- if (isReadable && info.RegionSize.ToInt64() > 0 && info.RegionSize.ToInt64() < 1073741824) {
195
- long regionSize = info.RegionSize.ToInt64();
196
- if (regionSize > 16777216) regionSize = 16777216; // bound scan window per region (16MB)
197
- byte[] buffer = new byte[(int)regionSize];
198
- int bytesRead;
199
-
200
- if (ReadProcessMemory(hProcess, info.BaseAddress, buffer, buffer.Length, out bytesRead)) {
201
- for (int i = 0; i <= bytesRead - pattern.Length; i++) {
202
- if (PatternMatch(buffer, i, pattern, mask)) {
203
- long foundAddr = info.BaseAddress.ToInt64() + i;
204
- results.Add("0x" + foundAddr.ToString("X"));
205
- if (results.Count >= maxResults) break;
206
- }
207
- }
208
- }
209
- }
210
-
211
- if (results.Count >= maxResults) break;
212
- if (scannedRegions >= 50000) break;
213
- long baseAddr = info.BaseAddress.ToInt64();
214
- long regionSizeRaw = info.RegionSize.ToInt64();
215
- if (regionSizeRaw <= 0) break;
216
- long nextAddr = baseAddr + regionSizeRaw;
217
- if (nextAddr <= baseAddr) break;
218
- addr = new IntPtr(nextAddr);
219
- if (addr.ToInt64() >= 0x7FFFFFFF0000) break;
220
- }
221
-
222
- return results;
223
- } finally {
224
- CloseHandle(hProcess);
225
- }
226
- }
227
-
228
- private static bool PatternMatch(byte[] buffer, int offset, byte[] pattern, byte[] mask) {
229
- for (int i = 0; i < pattern.Length; i++) {
230
- if (mask[i] == 1 && buffer[offset + i] != pattern[i]) {
231
- return false;
232
- }
233
- }
234
- return true;
235
- }
236
- }
237
- "@
238
-
239
- try {
240
- $patternBytes = @(${patternArray})
241
- $maskBytes = @(${maskArray})
242
- $results = [MemoryScanner]::ScanMemory(${pid}, $patternBytes, $maskBytes, 1000)
243
- @{
244
- success = $true;
245
- addresses = $results;
246
- stats = @{
247
- patternLength = $patternBytes.Length;
248
- resultsFound = $results.Count
249
- }
250
- } | ConvertTo-Json -Compress
251
- } catch {
252
- @{ success = $false; error = $_.Exception.Message } | ConvertTo-Json -Compress
253
- }
159
+ return `
160
+ Add-Type @"
161
+ using System;
162
+ using System.Runtime.InteropServices;
163
+ using System.Collections.Generic;
164
+ using System.ComponentModel;
165
+
166
+ public class MemoryScanner {
167
+ [DllImport("kernel32.dll", SetLastError = true)]
168
+ public static extern IntPtr OpenProcess(int access, bool inherit, int pid);
169
+
170
+ [DllImport("kernel32.dll", SetLastError = true)]
171
+ public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr addr, byte[] buffer, int size, out int read);
172
+
173
+ [DllImport("kernel32.dll", SetLastError = true)]
174
+ public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr addr, out MEMORY_BASIC_INFORMATION info, int size);
175
+
176
+ [DllImport("kernel32.dll", SetLastError = true)]
177
+ public static extern bool CloseHandle(IntPtr handle);
178
+
179
+ const int PROCESS_VM_READ = 0x0010;
180
+ const int PROCESS_QUERY_INFORMATION = 0x0400;
181
+
182
+ [StructLayout(LayoutKind.Sequential)]
183
+ public struct MEMORY_BASIC_INFORMATION {
184
+ public IntPtr BaseAddress;
185
+ public IntPtr AllocationBase;
186
+ public uint AllocationProtect;
187
+ public IntPtr RegionSize;
188
+ public uint State;
189
+ public uint Protect;
190
+ public uint Type;
191
+ }
192
+
193
+ const uint MEM_COMMIT = 0x1000;
194
+ const uint PAGE_READONLY = 0x02;
195
+ const uint PAGE_READWRITE = 0x04;
196
+ const uint PAGE_WRITECOPY = 0x08;
197
+ const uint PAGE_EXECUTE_READ = 0x20;
198
+ const uint PAGE_EXECUTE_READWRITE = 0x40;
199
+
200
+ public static List<string> ScanMemory(int pid, byte[] pattern, byte[] mask, int maxResults = 10000) {
201
+ var results = new List<string>();
202
+ IntPtr hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, pid);
203
+ if (hProcess == IntPtr.Zero) {
204
+ int error = Marshal.GetLastWin32Error();
205
+ throw new Win32Exception(error, "Failed to open process. Run as Administrator.");
206
+ }
207
+
208
+ try {
209
+ IntPtr addr = IntPtr.Zero;
210
+ MEMORY_BASIC_INFORMATION info;
211
+ int infoSize = Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION));
212
+ int scannedRegions = 0;
213
+
214
+ while (VirtualQueryEx(hProcess, addr, out info, infoSize) == infoSize) {
215
+ scannedRegions++;
216
+ bool isReadable = (info.State == MEM_COMMIT) &&
217
+ ((info.Protect & PAGE_READONLY) != 0 ||
218
+ (info.Protect & PAGE_READWRITE) != 0 ||
219
+ (info.Protect & PAGE_WRITECOPY) != 0 ||
220
+ (info.Protect & PAGE_EXECUTE_READ) != 0 ||
221
+ (info.Protect & PAGE_EXECUTE_READWRITE) != 0);
222
+
223
+ if (isReadable && info.RegionSize.ToInt64() > 0 && info.RegionSize.ToInt64() < 1073741824) {
224
+ long regionSize = info.RegionSize.ToInt64();
225
+ if (regionSize > 16777216) regionSize = 16777216; // bound scan window per region (16MB)
226
+ byte[] buffer = new byte[(int)regionSize];
227
+ int bytesRead;
228
+
229
+ if (ReadProcessMemory(hProcess, info.BaseAddress, buffer, buffer.Length, out bytesRead)) {
230
+ for (int i = 0; i <= bytesRead - pattern.Length; i++) {
231
+ if (PatternMatch(buffer, i, pattern, mask)) {
232
+ long foundAddr = info.BaseAddress.ToInt64() + i;
233
+ results.Add("0x" + foundAddr.ToString("X"));
234
+ if (results.Count >= maxResults) break;
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ if (results.Count >= maxResults) break;
241
+ if (scannedRegions >= 50000) break;
242
+ long baseAddr = info.BaseAddress.ToInt64();
243
+ long regionSizeRaw = info.RegionSize.ToInt64();
244
+ if (regionSizeRaw <= 0) break;
245
+ long nextAddr = baseAddr + regionSizeRaw;
246
+ if (nextAddr <= baseAddr) break;
247
+ addr = new IntPtr(nextAddr);
248
+ if (addr.ToInt64() >= 0x7FFFFFFF0000) break;
249
+ }
250
+
251
+ return results;
252
+ } finally {
253
+ CloseHandle(hProcess);
254
+ }
255
+ }
256
+
257
+ private static bool PatternMatch(byte[] buffer, int offset, byte[] pattern, byte[] mask) {
258
+ for (int i = 0; i < pattern.Length; i++) {
259
+ if (mask[i] == 1 && buffer[offset + i] != pattern[i]) {
260
+ return false;
261
+ }
262
+ }
263
+ return true;
264
+ }
265
+ }
266
+ "@
267
+
268
+ try {
269
+ $patternBytes = @(${patternArray})
270
+ $maskBytes = @(${maskArray})
271
+ $results = [MemoryScanner]::ScanMemory(${pid}, $patternBytes, $maskBytes, 1000)
272
+ @{
273
+ success = $true;
274
+ addresses = $results;
275
+ stats = @{
276
+ patternLength = $patternBytes.Length;
277
+ resultsFound = $results.Count
278
+ }
279
+ } | ConvertTo-Json -Compress
280
+ } catch {
281
+ @{ success = $false; error = $_.Exception.Message } | ConvertTo-Json -Compress
282
+ }
254
283
  `.trim();
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,60 +334,210 @@ async function scanMemoryWindows(pid, pattern, patternType) {
283
334
  };
284
335
  }
285
336
  }
286
- async function scanMemoryLinux(_pid, _pattern, _patternType) {
287
- return {
288
- success: false,
289
- addresses: [],
290
- error: 'Memory scanning on Linux requires scanning /proc/pid/maps and iterating regions. Use scanmem or GameConqueror for now.',
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
- patternBytes = patternToBytesMac(pattern, patternType);
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`;
305
- const pyScript = `
306
- import lldb, json, sys
307
-
308
- def __lldb_init_module(debugger, internal_dict):
309
- proc = debugger.GetSelectedTarget().GetProcess()
310
- pat = bytes([${byteList}])
311
- results = []
312
- rl = proc.GetMemoryRegions()
313
- for i in range(rl.GetSize()):
314
- info = lldb.SBMemoryRegionInfo()
315
- rl.GetMemoryRegionAtIndex(i, info)
316
- if not info.IsReadable():
317
- continue
318
- s = info.GetRegionBase()
319
- sz = info.GetRegionEnd() - s
320
- if sz > 32 * 1024 * 1024:
321
- continue
322
- err = lldb.SBError()
323
- data = proc.ReadMemory(s, sz, err)
324
- if not err.Success():
325
- continue
326
- n = len(pat)
327
- for j in range(len(data) - n + 1):
328
- if data[j:j+n] == pat:
329
- results.append(hex(s + j))
330
- if len(results) >= 1000:
331
- break
332
- if len(results) >= 1000:
333
- break
334
- sys.stdout.write('SCAN_RESULT:' + json.dumps({
335
- 'success': True,
336
- 'addresses': results,
337
- 'stats': {'patternLength': len(pat), 'resultsFound': len(results)}
338
- }) + '\\n')
339
- sys.stdout.flush()
500
+ const pyScript = `
501
+ import lldb, json, sys
502
+
503
+ def __lldb_init_module(debugger, internal_dict):
504
+ proc = debugger.GetSelectedTarget().GetProcess()
505
+ pat = bytes([${byteList}])
506
+ mask = [${maskList}]
507
+ results = []
508
+ rl = proc.GetMemoryRegions()
509
+ for i in range(rl.GetSize()):
510
+ info = lldb.SBMemoryRegionInfo()
511
+ rl.GetMemoryRegionAtIndex(i, info)
512
+ if not info.IsReadable():
513
+ continue
514
+ s = info.GetRegionBase()
515
+ sz = info.GetRegionEnd() - s
516
+ if sz > 32 * 1024 * 1024:
517
+ continue
518
+ err = lldb.SBError()
519
+ data = proc.ReadMemory(s, sz, err)
520
+ if not err.Success():
521
+ continue
522
+ n = len(pat)
523
+ for j in range(len(data) - n + 1):
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:
530
+ results.append(hex(s + j))
531
+ if len(results) >= 1000:
532
+ break
533
+ if len(results) >= 1000:
534
+ break
535
+ sys.stdout.write('SCAN_RESULT:' + json.dumps({
536
+ 'success': True,
537
+ 'addresses': results,
538
+ 'stats': {'patternLength': len(pat), 'resultsFound': len(results)}
539
+ }) + '\\n')
540
+ sys.stdout.flush()
340
541
  `;
341
542
  await fs.writeFile(pyFile, pyScript, 'utf8');
342
543
  await fs.writeFile(cmdFile, `command script import ${pyFile}\nprocess detach\n`, 'utf8');