@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.
- package/LICENSE +661 -661
- 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/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/captcha/AICaptchaDetector.js +185 -185
- 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 +32 -28
- 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/availability.js +49 -49
- package/dist/modules/process/memory/injector.js +185 -185
- 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/reader.js +50 -50
- 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 +383 -182
- package/dist/modules/process/memory/writer.js +54 -54
- 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/native/scripts/linux/enum-windows.sh +12 -12
- package/dist/native/scripts/macos/enum-windows.applescript +22 -22
- package/dist/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
- package/dist/native/scripts/windows/enum-windows.ps1 +44 -44
- package/dist/native/scripts/windows/inject-dll.ps1 +21 -21
- 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/definitions.tools.page-core.js +53 -53
- package/dist/server/domains/browser/definitions.tools.runtime.js +40 -40
- package/dist/server/domains/browser/definitions.tools.security.js +76 -76
- 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/transform/handlers.impl.transform-base.js +102 -102
- package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
- package/dist/server/domains/workflow/handlers.impl.workflow-base.js +51 -51
- 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 +5 -1
- package/src/native/scripts/linux/enum-windows.sh +12 -12
- package/src/native/scripts/macos/enum-windows.applescript +22 -22
- package/src/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
- package/src/native/scripts/windows/enum-windows.ps1 +44 -44
- 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
|
|
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,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
|
-
|
|
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
|
-
|
|
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`;
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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');
|