@jshookmcp/jshook 0.2.8 → 0.3.0

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 (162) hide show
  1. package/README.md +36 -5
  2. package/README.zh.md +36 -5
  3. package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-CqGDXmfc.mjs} +160 -54
  4. package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
  5. package/dist/ConsoleMonitor-DykL3IAw.mjs +2269 -0
  6. package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-ETyy0xyo.mjs} +1 -1
  7. package/dist/DetailedDataManager-HT49OrvF.mjs +217 -0
  8. package/dist/EventBus-DFKvADm3.mjs +141 -0
  9. package/dist/EvidenceGraphBridge-318Oi0Lf.mjs +153 -0
  10. package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-BDMsY2Dz.mjs} +27 -13
  11. package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-BN4UQWnX.mjs} +1 -1
  12. package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
  13. package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-DruMgsgj.mjs} +21 -21
  14. package/dist/HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs +566 -0
  15. package/dist/InstrumentationSession-DLH0vd-z.mjs +244 -0
  16. package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CMtviNW_.mjs} +3 -3
  17. package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-ITgb_NMi.mjs} +81 -78
  18. package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-CiL7Z3ey.mjs} +50 -21
  19. package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +20 -56
  20. package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
  21. package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-DMQ44gen.mjs} +16 -16
  22. package/dist/PageController-BPJNqqBN.mjs +431 -0
  23. package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
  24. package/dist/PrerequisiteError-TuyZIs6n.mjs +20 -0
  25. package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
  26. package/dist/ResponseBuilder-CJXWmWNw.mjs +143 -0
  27. package/dist/ReverseEvidenceGraph-C02-gXOh.mjs +269 -0
  28. package/dist/ScriptManager-ZuWD-0Jg.mjs +3003 -0
  29. package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-D-z0umeT.mjs} +2 -2
  30. package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
  31. package/dist/ToolCatalog-5OJdMiF0.mjs +582 -0
  32. package/dist/ToolError-jh9whhMd.mjs +15 -0
  33. package/dist/ToolProbe-DbCFGyrg.mjs +45 -0
  34. package/dist/ToolRegistry-B9krbTtI.mjs +180 -0
  35. package/dist/ToolRouter.policy-BGDAGyeH.mjs +344 -0
  36. package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
  37. package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-C2kjj0ze.mjs} +19 -13
  38. package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-CKrGOTpo.mjs} +3 -3
  39. package/dist/WorkflowEngine-DJ6M4opp.mjs +569 -0
  40. package/dist/analysis-BHeJW2Nb.mjs +1234 -0
  41. package/dist/antidebug-BRKeyt27.mjs +1081 -0
  42. package/dist/artifactRetention-CPXkUJXp.mjs +598 -0
  43. package/dist/artifacts-DkfosXH3.mjs +59 -0
  44. package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
  45. package/dist/betterSqlite3-DLSBZodi.mjs +74 -0
  46. package/dist/binary-instrument--V3MAhJ4.mjs +971 -0
  47. package/dist/bind-helpers-ClV34xdn.mjs +42 -0
  48. package/dist/boringssl-inspector-Bo_LOLaS.mjs +180 -0
  49. package/dist/browser-Dx3_S2cG.mjs +4369 -0
  50. package/dist/capabilities-CcHlvWgK.mjs +33 -0
  51. package/dist/concurrency-Drev_Vz9.mjs +41 -0
  52. package/dist/{constants-CCvsN80K.mjs → constants-CDZLOoVv.mjs} +105 -48
  53. package/dist/coordination-DgItD9DL.mjs +259 -0
  54. package/dist/debugger-RS3RSAqs.mjs +1288 -0
  55. package/dist/definitions-BEoYofW5.mjs +47 -0
  56. package/dist/definitions-BRaefg3u.mjs +365 -0
  57. package/dist/definitions-BbkvZkiv.mjs +96 -0
  58. package/dist/definitions-BtWSHJ3o.mjs +17 -0
  59. package/dist/definitions-C1gCHO0i.mjs +43 -0
  60. package/dist/definitions-CDOg_b-l.mjs +138 -0
  61. package/dist/definitions-CVPD9hzZ.mjs +54 -0
  62. package/dist/definitions-Cea8Lgl7.mjs +94 -0
  63. package/dist/definitions-DAgIyjxM.mjs +10 -0
  64. package/dist/definitions-DJA27nsL.mjs +66 -0
  65. package/dist/definitions-DKPFU3LW.mjs +25 -0
  66. package/dist/definitions-DPRpZQ96.mjs +47 -0
  67. package/dist/definitions-DUE5gmdn.mjs +18 -0
  68. package/dist/definitions-DYVjOtxa.mjs +26 -0
  69. package/dist/definitions-DcYLVLCo.mjs +37 -0
  70. package/dist/definitions-Pp5LI2H4.mjs +27 -0
  71. package/dist/definitions-j9KdHVNR.mjs +14 -0
  72. package/dist/definitions-uzkjBwa7.mjs +258 -0
  73. package/dist/definitions-va-AnLuQ.mjs +28 -0
  74. package/dist/encoding-DJeqHmpd.mjs +1079 -0
  75. package/dist/evidence-graph-bridge-DcYizFk2.mjs +136 -0
  76. package/dist/{factory-CibqTNC8.mjs → factory-C90tBff6.mjs} +41 -56
  77. package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
  78. package/dist/graphql-CoHrhweh.mjs +1197 -0
  79. package/dist/handlers-4jmR0nMs.mjs +898 -0
  80. package/dist/handlers-BAHPxcch.mjs +789 -0
  81. package/dist/handlers-BOs9b907.mjs +2600 -0
  82. package/dist/handlers-BWXEy6ef.mjs +917 -0
  83. package/dist/handlers-Bndn6QvE.mjs +111 -0
  84. package/dist/handlers-BqC4bD4s.mjs +681 -0
  85. package/dist/handlers-BtYq60bM2.mjs +276 -0
  86. package/dist/handlers-BzgcB4iv.mjs +799 -0
  87. package/dist/handlers-CRyRWj2b.mjs +859 -0
  88. package/dist/handlers-CVv2H1uq.mjs +592 -0
  89. package/dist/handlers-Dl5a7JS4.mjs +572 -0
  90. package/dist/handlers-Dx2d7jt7.mjs +2537 -0
  91. package/dist/handlers-Dz9PYsCa.mjs +2805 -0
  92. package/dist/handlers-HujRKC3b.mjs +661 -0
  93. package/dist/handlers.impl-XWXkQfyi.mjs +807 -0
  94. package/dist/hooks-B1B8NRHL.mjs +898 -0
  95. package/dist/index.mjs +491 -259
  96. package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
  97. package/dist/maintenance-PRMkLVRW.mjs +835 -0
  98. package/dist/manifest-67Bok-Si.mjs +58 -0
  99. package/dist/manifest-6lNTMZAB2.mjs +87 -0
  100. package/dist/manifest-B2duEHiH.mjs +90 -0
  101. package/dist/manifest-B6EY9Vm8.mjs +57 -0
  102. package/dist/manifest-B6nKSbyY.mjs +95 -0
  103. package/dist/manifest-BL8AQNPF.mjs +106 -0
  104. package/dist/manifest-BSZvJJmV.mjs +47 -0
  105. package/dist/manifest-BU7qzUyX.mjs +418 -0
  106. package/dist/manifest-Bl62e8WK.mjs +49 -0
  107. package/dist/manifest-Bo5cXjdt.mjs +82 -0
  108. package/dist/manifest-BpS4gtUK.mjs +1347 -0
  109. package/dist/manifest-Bv65_e2W.mjs +101 -0
  110. package/dist/manifest-BytNIF4Z.mjs +117 -0
  111. package/dist/manifest-C-xtsjS3.mjs +81 -0
  112. package/dist/manifest-CDYl7OhA.mjs +66 -0
  113. package/dist/manifest-CRZ3xmkD.mjs +61 -0
  114. package/dist/manifest-CoW6u4Tp.mjs +132 -0
  115. package/dist/manifest-Cq5zN_8A.mjs +50 -0
  116. package/dist/manifest-D7YZM_2e.mjs +194 -0
  117. package/dist/manifest-DE_VrAeQ.mjs +314 -0
  118. package/dist/manifest-DGsXSCpT.mjs +39 -0
  119. package/dist/manifest-DJ2vfEuW.mjs +156 -0
  120. package/dist/manifest-DPXDYhEu.mjs +80 -0
  121. package/dist/manifest-Dd4fQb0a.mjs +322 -0
  122. package/dist/manifest-Deq6opGg.mjs +223 -0
  123. package/dist/manifest-DfJTafJK.mjs +37 -0
  124. package/dist/manifest-DgOdgN_j.mjs +50 -0
  125. package/dist/manifest-DlbMW4v4.mjs +47 -0
  126. package/dist/manifest-DmVfbH0w.mjs +374 -0
  127. package/dist/manifest-Dog6Ddjr.mjs +109 -0
  128. package/dist/manifest-DvgU5FWb.mjs +58 -0
  129. package/dist/manifest-HsfDBs7j.mjs +50 -0
  130. package/dist/manifest-I8oQHvCG.mjs +186 -0
  131. package/dist/manifest-NvH_a-av.mjs +786 -0
  132. package/dist/manifest-cEJU1v0Z.mjs +129 -0
  133. package/dist/manifest-wOl5XLB12.mjs +112 -0
  134. package/dist/modules-tZozf0LQ.mjs +10635 -0
  135. package/dist/mojo-ipc-DXNEXEqb.mjs +640 -0
  136. package/dist/network-CPVvwvFg.mjs +3852 -0
  137. package/dist/{artifacts-BbdOMET5.mjs → outputPaths-um7lCRY3.mjs} +219 -216
  138. package/dist/parse-args-B4cY5Vx5.mjs +39 -0
  139. package/dist/platform-CYeFoTWp.mjs +2161 -0
  140. package/dist/process-BTbgcVc6.mjs +1306 -0
  141. package/dist/proxy-r8YN6nP1.mjs +192 -0
  142. package/dist/registry-Bl8ZQW61.mjs +34 -0
  143. package/dist/response-CWhh2aLo.mjs +34 -0
  144. package/dist/server/plugin-api.mjs +2 -2
  145. package/dist/shared-state-board-BoZnSoj-.mjs +586 -0
  146. package/dist/sourcemap-BIDHUVXy.mjs +934 -0
  147. package/dist/ssrf-policy-Dsqd-DTX.mjs +166 -0
  148. package/dist/streaming-Dal6utPp.mjs +725 -0
  149. package/dist/tool-builder-BHJp32mV.mjs +186 -0
  150. package/dist/transform-DRVgGG90.mjs +1011 -0
  151. package/dist/types-Bx92KJfT.mjs +4 -0
  152. package/dist/wasm-BYx5UOeG.mjs +1044 -0
  153. package/dist/webcrack-Be0_FccV.mjs +747 -0
  154. package/dist/workflow-BpuKEtvn.mjs +725 -0
  155. package/package.json +82 -49
  156. package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
  157. package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
  158. package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
  159. package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
  160. package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
  161. package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-nnMvEohD.mjs} +0 -0
  162. package/dist/{types-BBjOqye-.mjs → types-DDBWs9UP.mjs} +1 -1
@@ -0,0 +1,971 @@
1
+ import { t as __exportAll } from "./chunk-CjcI7cDX.mjs";
2
+ import { t as logger } from "./logger-Dh_xb7_2.mjs";
3
+ import { Q as FRIDA_TIMEOUT_MS, et as GHIDRA_TIMEOUT_MS, xr as UNIDBG_TIMEOUT_MS } from "./constants-CDZLOoVv.mjs";
4
+ import { t as probeCommand } from "./ToolProbe-DbCFGyrg.mjs";
5
+ import { tmpdir } from "node:os";
6
+ import { randomUUID } from "node:crypto";
7
+ import { basename, dirname, join } from "node:path";
8
+ import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
9
+ import { execFile } from "node:child_process";
10
+ //#region src/modules/binary-instrument/FridaSession.ts
11
+ const FRIDA_MAX_BUFFER_BYTES = 5 * 1024 * 1024;
12
+ var FridaSession = class {
13
+ sessions = /* @__PURE__ */ new Map();
14
+ activeSessionId;
15
+ fridaProbe;
16
+ probePromise;
17
+ async attach(target) {
18
+ const availability = await this.getAvailability();
19
+ if (!availability.available) throw new Error(availability.reason ?? "Frida CLI is not available");
20
+ const probe = await this.runFridaCommand(target, "console.log(\"__frida_attach_ok__\");");
21
+ if (probe.error) throw new Error(probe.error);
22
+ const sessionId = randomUUID();
23
+ const record = {
24
+ id: sessionId,
25
+ target,
26
+ pid: this.resolvePid(target),
27
+ status: "attached",
28
+ attachedAt: (/* @__PURE__ */ new Date()).toISOString()
29
+ };
30
+ this.sessions.set(sessionId, record);
31
+ this.activeSessionId = sessionId;
32
+ return sessionId;
33
+ }
34
+ async detach() {
35
+ const active = this.getActiveSessionRecord();
36
+ if (!active) return;
37
+ active.status = "detached";
38
+ this.activeSessionId = void 0;
39
+ }
40
+ async executeScript(script) {
41
+ const session = this.requireActiveSession();
42
+ const result = await this.runFridaCommand(session.target, script);
43
+ if (result.error) {
44
+ session.status = "error";
45
+ session.lastError = result.error;
46
+ }
47
+ return result;
48
+ }
49
+ async enumerateModules() {
50
+ const session = this.requireActiveSession();
51
+ const result = await this.runFridaCommand(session.target, "console.log(JSON.stringify(Process.enumerateModules()));");
52
+ const parsed = this.parseModuleList(result.output);
53
+ if (parsed.length > 0) return parsed;
54
+ if (result.error) {
55
+ session.status = "error";
56
+ session.lastError = result.error;
57
+ }
58
+ return [];
59
+ }
60
+ async enumerateFunctions(moduleName) {
61
+ const session = this.requireActiveSession();
62
+ const safeModuleName = JSON.stringify(moduleName);
63
+ const result = await this.runFridaCommand(session.target, [
64
+ `const entries = Process.getModuleByName(${safeModuleName}).enumerateExports()`,
65
+ ".filter(function (entry) { return entry.type === \"function\"; })",
66
+ ".map(function (entry) {",
67
+ " return { name: entry.name, address: String(entry.address), size: 0 };",
68
+ "});",
69
+ "console.log(JSON.stringify(entries));"
70
+ ].join(""));
71
+ const parsed = this.parseFunctionList(result.output);
72
+ if (parsed.length > 0) return parsed;
73
+ if (result.error) {
74
+ session.status = "error";
75
+ session.lastError = result.error;
76
+ }
77
+ return [];
78
+ }
79
+ async findSymbols(pattern) {
80
+ const session = this.requireActiveSession();
81
+ const trimmedPattern = pattern.trim();
82
+ const resolvedPattern = trimmedPattern.includes(":") ? trimmedPattern : trimmedPattern.includes("!") ? `exports:${trimmedPattern}` : `exports:*!${trimmedPattern}*`;
83
+ const matchPattern = JSON.stringify(resolvedPattern);
84
+ const result = await this.runFridaCommand(session.target, [
85
+ "const resolver = new ApiResolver(\"module\");",
86
+ `const matches = resolver.enumerateMatches(${matchPattern});`,
87
+ "const mapped = matches.map(function (entry) {",
88
+ " const resolvedName = typeof entry.name === \"string\" ? entry.name : \"unknown\";",
89
+ " const resolvedAddress = entry.address ? String(entry.address) : \"0x0\";",
90
+ " return { name: resolvedName, address: resolvedAddress, demangled: resolvedName };",
91
+ "});",
92
+ "console.log(JSON.stringify(mapped));"
93
+ ].join(""));
94
+ const parsed = this.parseSymbolList(result.output);
95
+ if (parsed.length > 0) return parsed;
96
+ if (result.error) {
97
+ session.status = "error";
98
+ session.lastError = result.error;
99
+ }
100
+ return [];
101
+ }
102
+ listSessions() {
103
+ return Array.from(this.sessions.values()).map((session) => ({
104
+ id: session.id,
105
+ target: session.target,
106
+ pid: session.pid,
107
+ status: session.status
108
+ }));
109
+ }
110
+ async isAvailable() {
111
+ return (await this.getAvailability()).available;
112
+ }
113
+ async getAvailability() {
114
+ if (this.fridaProbe) return this.fridaProbe;
115
+ if (!this.probePromise) this.probePromise = probeCommand("frida");
116
+ const resolved = await this.probePromise;
117
+ this.fridaProbe = resolved;
118
+ this.probePromise = void 0;
119
+ return resolved;
120
+ }
121
+ useSession(sessionId) {
122
+ if (!this.sessions.has(sessionId)) return false;
123
+ this.activeSessionId = sessionId;
124
+ return true;
125
+ }
126
+ hasSession(sessionId) {
127
+ return this.sessions.has(sessionId);
128
+ }
129
+ getSessionDiagnostics(sessionId) {
130
+ const session = this.sessions.get(sessionId);
131
+ if (!session) return;
132
+ return {
133
+ status: session.status,
134
+ lastError: session.lastError
135
+ };
136
+ }
137
+ getActiveSessionRecord() {
138
+ if (!this.activeSessionId) return;
139
+ return this.sessions.get(this.activeSessionId);
140
+ }
141
+ requireActiveSession() {
142
+ const session = this.getActiveSessionRecord();
143
+ if (!session) throw new Error("No active Frida session. Call attach() first.");
144
+ return session;
145
+ }
146
+ resolvePid(target) {
147
+ if (!/^\d+$/.test(target)) return null;
148
+ const parsed = Number.parseInt(target, 10);
149
+ return Number.isNaN(parsed) ? null : parsed;
150
+ }
151
+ async runFridaCommand(target, script) {
152
+ const availability = await this.getAvailability();
153
+ if (!availability.available) return {
154
+ output: "",
155
+ error: availability.reason ?? "Frida CLI is not available"
156
+ };
157
+ const command = availability.path ?? "frida";
158
+ const args = [
159
+ ...this.buildTargetArgs(target),
160
+ "--runtime=v8",
161
+ "-q",
162
+ "-e",
163
+ script
164
+ ];
165
+ try {
166
+ const result = await this.execFileUtf8(command, args, FRIDA_TIMEOUT_MS);
167
+ const output = result.stdout.trim();
168
+ const error = result.stderr.trim();
169
+ return error ? {
170
+ output,
171
+ error
172
+ } : { output };
173
+ } catch (error) {
174
+ const message = error instanceof Error ? error.message : String(error);
175
+ logger.warn("[binary-instrument] Frida command failed", {
176
+ target,
177
+ message
178
+ });
179
+ return {
180
+ output: "",
181
+ error: message
182
+ };
183
+ }
184
+ }
185
+ buildTargetArgs(target) {
186
+ if (/^\d+$/.test(target)) return ["-p", target];
187
+ if (target.includes("/") || target.includes("\\")) return ["-f", target];
188
+ return ["-n", target];
189
+ }
190
+ parseModuleList(output) {
191
+ const payload = this.extractJsonPayload(output);
192
+ if (!Array.isArray(payload)) return [];
193
+ const modules = [];
194
+ for (const entry of payload) {
195
+ if (!this.isRecord(entry)) continue;
196
+ const name = this.readStringField(entry, "name");
197
+ const path = this.readStringField(entry, "path");
198
+ const base = this.normalizeHex(entry["base"]);
199
+ const size = this.readNumberField(entry, "size");
200
+ if (!name || !path || !base || size === void 0) continue;
201
+ modules.push({
202
+ name,
203
+ base,
204
+ size,
205
+ path
206
+ });
207
+ }
208
+ return modules;
209
+ }
210
+ parseFunctionList(output) {
211
+ const payload = this.extractJsonPayload(output);
212
+ if (!Array.isArray(payload)) return [];
213
+ const functions = [];
214
+ for (const entry of payload) {
215
+ if (!this.isRecord(entry)) continue;
216
+ const name = this.readStringField(entry, "name");
217
+ const address = this.normalizeHex(entry["address"]);
218
+ const size = this.readNumberField(entry, "size") ?? 0;
219
+ if (!name || !address) continue;
220
+ functions.push({
221
+ name,
222
+ address,
223
+ size
224
+ });
225
+ }
226
+ return functions;
227
+ }
228
+ parseSymbolList(output) {
229
+ const payload = this.extractJsonPayload(output);
230
+ if (!Array.isArray(payload)) return [];
231
+ const symbols = [];
232
+ for (const entry of payload) {
233
+ if (!this.isRecord(entry)) continue;
234
+ const name = this.readStringField(entry, "name");
235
+ const address = this.normalizeHex(entry["address"]);
236
+ const demangled = this.readStringField(entry, "demangled");
237
+ if (!name || !address) continue;
238
+ if (demangled) symbols.push({
239
+ name,
240
+ address,
241
+ demangled
242
+ });
243
+ else symbols.push({
244
+ name,
245
+ address
246
+ });
247
+ }
248
+ return symbols;
249
+ }
250
+ extractJsonPayload(output) {
251
+ const candidates = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("{") || line.startsWith("[")).toReversed();
252
+ for (const line of candidates) try {
253
+ return JSON.parse(line);
254
+ } catch {
255
+ continue;
256
+ }
257
+ }
258
+ readStringField(record, key) {
259
+ const value = record[key];
260
+ return typeof value === "string" && value.length > 0 ? value : void 0;
261
+ }
262
+ readNumberField(record, key) {
263
+ const value = record[key];
264
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
265
+ }
266
+ normalizeHex(value) {
267
+ if (typeof value === "number" && Number.isFinite(value)) return `0x${value.toString(16)}`;
268
+ if (typeof value === "string" && value.length > 0) return value.startsWith("0x") ? value : `0x${value}`;
269
+ }
270
+ isRecord(value) {
271
+ return typeof value === "object" && value !== null;
272
+ }
273
+ execFileUtf8(file, args, timeoutMs) {
274
+ return new Promise((resolve, reject) => {
275
+ execFile(file, args, {
276
+ timeout: timeoutMs,
277
+ windowsHide: true,
278
+ maxBuffer: FRIDA_MAX_BUFFER_BYTES,
279
+ encoding: "utf8"
280
+ }, (error, stdout, stderr) => {
281
+ if (error) {
282
+ reject(error);
283
+ return;
284
+ }
285
+ resolve({
286
+ stdout: typeof stdout === "string" ? stdout : "",
287
+ stderr: typeof stderr === "string" ? stderr : ""
288
+ });
289
+ });
290
+ });
291
+ }
292
+ };
293
+ //#endregion
294
+ //#region src/modules/binary-instrument/GhidraAnalyzer.ts
295
+ const GHIDRA_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
296
+ var GhidraAnalyzer = class {
297
+ ghidraProbe;
298
+ probePromise;
299
+ async analyze(binaryPath, options) {
300
+ await access(binaryPath);
301
+ const fileBuffer = await readFile(binaryPath);
302
+ const strings = this.extractPrintableStrings(fileBuffer);
303
+ const imports = this.deriveImports(strings);
304
+ const exports = this.deriveExports(strings);
305
+ if (!(await this.getAvailability()).available) return {
306
+ functions: [],
307
+ imports,
308
+ exports,
309
+ strings
310
+ };
311
+ const timeoutMs = typeof options?.timeout === "number" && Number.isFinite(options.timeout) ? options.timeout : GHIDRA_TIMEOUT_MS;
312
+ const scriptDirectory = await mkdtemp(join(tmpdir(), "jshook-ghidra-script-"));
313
+ const scriptPath = join(scriptDirectory, "BinaryInstrumentDump.py");
314
+ try {
315
+ await writeFile(scriptPath, this.buildDefaultScript(), "utf8");
316
+ const output = await this.headlessAnalyze(scriptPath, binaryPath, timeoutMs);
317
+ return {
318
+ functions: this.parseDecompiledOutput(output),
319
+ imports,
320
+ exports,
321
+ strings
322
+ };
323
+ } catch (error) {
324
+ const message = error instanceof Error ? error.message : String(error);
325
+ logger.warn("[binary-instrument] Ghidra analyze fallback", {
326
+ binaryPath,
327
+ message
328
+ });
329
+ return {
330
+ functions: [],
331
+ imports,
332
+ exports,
333
+ strings
334
+ };
335
+ } finally {
336
+ await rm(scriptDirectory, {
337
+ recursive: true,
338
+ force: true
339
+ });
340
+ }
341
+ }
342
+ async headlessAnalyze(scriptPath, binaryPath, timeoutMs = GHIDRA_TIMEOUT_MS) {
343
+ const availability = await this.getAvailability();
344
+ if (!availability.available) throw new Error(availability.reason ?? "Ghidra analyzeHeadless is not available");
345
+ await access(binaryPath);
346
+ await access(scriptPath);
347
+ const command = availability.path ?? "analyzeHeadless";
348
+ const projectDirectory = await mkdtemp(join(tmpdir(), "jshook-ghidra-project-"));
349
+ const projectName = "binary-instrument";
350
+ try {
351
+ const result = await this.execFileUtf8(command, [
352
+ projectDirectory,
353
+ projectName,
354
+ "-import",
355
+ binaryPath,
356
+ "-scriptPath",
357
+ dirname(scriptPath),
358
+ "-postScript",
359
+ basename(scriptPath)
360
+ ], timeoutMs);
361
+ return [result.stdout.trim(), result.stderr.trim()].filter((entry) => entry.length > 0).join("\n");
362
+ } finally {
363
+ await rm(projectDirectory, {
364
+ recursive: true,
365
+ force: true
366
+ });
367
+ }
368
+ }
369
+ parseDecompiledOutput(output) {
370
+ const functions = [];
371
+ const blockPattern = /FUNCTION_START\s*[\r\n]+NAME:(.+?)\s*[\r\n]+ADDRESS:(.+?)\s*[\r\n]+SIGNATURE:(.+?)\s*[\r\n]+DECOMPILED_START\s*[\r\n]+([\s\S]*?)\s*[\r\n]+DECOMPILED_END\s*[\r\n]+FUNCTION_END/g;
372
+ let match = blockPattern.exec(output);
373
+ while (match) {
374
+ const rawName = match[1] ?? "";
375
+ const rawAddress = match[2] ?? "";
376
+ const rawSignature = match[3] ?? "";
377
+ const rawBody = match[4] ?? "";
378
+ const name = rawName.trim();
379
+ const address = this.normalizeHex(rawAddress.trim());
380
+ const signature = rawSignature.trim();
381
+ const decompiled = rawBody.trim();
382
+ if (name.length > 0 && address.length > 0 && signature.length > 0) functions.push({
383
+ name,
384
+ address,
385
+ signature,
386
+ decompiled
387
+ });
388
+ match = blockPattern.exec(output);
389
+ }
390
+ return functions;
391
+ }
392
+ async isAvailable() {
393
+ return (await this.getAvailability()).available;
394
+ }
395
+ async getAvailability() {
396
+ if (this.ghidraProbe) return this.ghidraProbe;
397
+ if (!this.probePromise) this.probePromise = probeCommand("analyzeHeadless", ["-help"]);
398
+ const resolved = await this.probePromise;
399
+ this.ghidraProbe = resolved;
400
+ this.probePromise = void 0;
401
+ return resolved;
402
+ }
403
+ buildDefaultScript() {
404
+ return [
405
+ "# @category BinaryInstrument",
406
+ "from ghidra.app.decompiler import DecompInterface",
407
+ "",
408
+ "program = currentProgram",
409
+ "interface = DecompInterface()",
410
+ "interface.openProgram(program)",
411
+ "function_manager = program.getFunctionManager()",
412
+ "functions = function_manager.getFunctions(True)",
413
+ "",
414
+ "for function in functions:",
415
+ " print(\"FUNCTION_START\")",
416
+ " print(\"NAME:\" + str(function.getName()))",
417
+ " print(\"ADDRESS:\" + str(function.getEntryPoint()))",
418
+ " try:",
419
+ " signature = str(function.getSignature())",
420
+ " except:",
421
+ " signature = str(function.getName()) + \"()\"",
422
+ " print(\"SIGNATURE:\" + signature)",
423
+ " print(\"DECOMPILED_START\")",
424
+ " try:",
425
+ " decompiled = interface.decompileFunction(function, 30, monitor).getDecompiledFunction()",
426
+ " if decompiled:",
427
+ " print(str(decompiled.getC()))",
428
+ " else:",
429
+ " print(\"// no decompiled output\")",
430
+ " except:",
431
+ " print(\"// decompile failed\")",
432
+ " print(\"DECOMPILED_END\")",
433
+ " print(\"FUNCTION_END\")"
434
+ ].join("\n");
435
+ }
436
+ extractPrintableStrings(buffer) {
437
+ const results = [];
438
+ let current = "";
439
+ for (const byte of buffer.values()) {
440
+ if (byte >= 32 && byte <= 126) {
441
+ current += String.fromCharCode(byte);
442
+ continue;
443
+ }
444
+ if (current.length >= 4) results.push(current);
445
+ current = "";
446
+ }
447
+ if (current.length >= 4) results.push(current);
448
+ return Array.from(new Set(results)).slice(0, 1e3);
449
+ }
450
+ deriveImports(strings) {
451
+ return strings.filter((entry) => /(?:\.dll|\.so|\.dylib|kernel32|user32|libc|printf|malloc|LoadLibrary)/i.test(entry)).slice(0, 100);
452
+ }
453
+ deriveExports(strings) {
454
+ return strings.filter((entry) => /^[A-Za-z_][A-Za-z0-9_@?$]{2,}$/.test(entry)).slice(0, 100);
455
+ }
456
+ normalizeHex(value) {
457
+ return value.startsWith("0x") ? value : `0x${value}`;
458
+ }
459
+ execFileUtf8(file, args, timeoutMs) {
460
+ return new Promise((resolve, reject) => {
461
+ execFile(file, args, {
462
+ timeout: timeoutMs,
463
+ windowsHide: true,
464
+ maxBuffer: GHIDRA_MAX_BUFFER_BYTES,
465
+ encoding: "utf8"
466
+ }, (error, stdout, stderr) => {
467
+ if (error) {
468
+ reject(error);
469
+ return;
470
+ }
471
+ resolve({
472
+ stdout: typeof stdout === "string" ? stdout : "",
473
+ stderr: typeof stderr === "string" ? stderr : ""
474
+ });
475
+ });
476
+ });
477
+ }
478
+ };
479
+ //#endregion
480
+ //#region src/modules/binary-instrument/HookCodeGenerator.ts
481
+ var HookCodeGenerator = class {
482
+ generateHooks(input) {
483
+ const templates = [];
484
+ for (const fn of input.functions) {
485
+ const category = this.classifyFunction(fn, input);
486
+ if (category === "unknown") continue;
487
+ templates.push({
488
+ functionName: fn.name,
489
+ description: this.describeCategory(category, input),
490
+ hookCode: this.buildHookCode(fn, category),
491
+ parameters: this.buildParameters(fn)
492
+ });
493
+ }
494
+ return templates;
495
+ }
496
+ exportScript(templates, format) {
497
+ if (format !== "frida") throw new Error("Unsupported export format");
498
+ const lines = [
499
+ "// Frida hook script",
500
+ "// auto-generated by HookCodeGenerator",
501
+ `// Hook count: ${templates.length}`,
502
+ ""
503
+ ];
504
+ for (const template of templates) {
505
+ lines.push(`// ${template.functionName}: ${template.description}`);
506
+ lines.push(template.hookCode);
507
+ lines.push("");
508
+ }
509
+ return lines.join("\n");
510
+ }
511
+ classifyFunction(fn, input) {
512
+ const name = fn.name.toLowerCase();
513
+ const imports = input.imports.map((entry) => entry.toLowerCase());
514
+ const strings = input.strings.map((entry) => entry.toLowerCase());
515
+ if (fn.name.startsWith("Java_")) return "jni";
516
+ if (name.includes("aes")) return "aes";
517
+ if (name.includes("md5")) return "md5";
518
+ if (name.includes("sha")) return "sha";
519
+ if (name.includes("rsa")) return "rsa";
520
+ if (name.includes("base64")) return "base64";
521
+ if (name.includes("send") || name.includes("recv") || name.includes("socket") || name.includes("http")) return "network";
522
+ if (name.includes("open") || name.includes("read") || name.includes("write") || name.includes("fopen")) return "file-io";
523
+ if ((name.includes("decrypt") || name.includes("encrypt")) && strings.some((entry) => entry.includes("obfuscation") || entry.includes("encryption"))) return "obfuscation";
524
+ if (name.includes("memcpy") || name.includes("strcpy") || name.includes("memmove") || name.includes("string")) return "string";
525
+ if (imports.some((entry) => entry.includes("aes"))) return "aes";
526
+ return "unknown";
527
+ }
528
+ describeCategory(category, input) {
529
+ switch (category) {
530
+ case "jni": return "JNI bridge hook for Java/native boundary inspection";
531
+ case "aes": return "AES crypto hook for key/plaintext capture";
532
+ case "md5": return "MD5 crypto hook for digest inspection";
533
+ case "sha": return "SHA crypto hook for digest inspection";
534
+ case "rsa": return "RSA crypto hook for key operation tracing";
535
+ case "base64": return "Base64 transform hook for encoded payload tracing";
536
+ case "network": return "Network hook for request and payload tracing";
537
+ case "file-io": return "File I/O hook for filesystem access tracing";
538
+ case "string": return "String operation hook for buffer tracing";
539
+ case "obfuscation": return input.strings.some((entry) => entry.toLowerCase().includes("obfuscation")) ? "Potential obfuscation hook for runtime string decryption tracing" : "Potential obfuscation hook";
540
+ case "unknown": return "Unknown hook";
541
+ }
542
+ }
543
+ buildHookCode(fn, category) {
544
+ if (category === "jni") return [
545
+ "Java.perform(function () {",
546
+ ` const target = ptr("${fn.address}");`,
547
+ " Interceptor.attach(target, {",
548
+ " onEnter(args) {",
549
+ ` console.log("[jni] ${fn.name}");`,
550
+ " },",
551
+ " });",
552
+ "});"
553
+ ].join("\n");
554
+ const extraLine = category === "aes" || category === "md5" || category === "sha" || category === "rsa" || category === "base64" ? " console.log(hexdump(args[0]));" : " console.log(\"[trace] entering\");";
555
+ const targetName = fn.name.replace(/[^A-Za-z0-9_]/g, "_");
556
+ return [
557
+ `const target_${targetName} = ptr("${fn.address}");`,
558
+ `Interceptor.attach(target_${targetName}, {`,
559
+ " onEnter(args) {",
560
+ ` console.log("[hook] ${fn.name}");`,
561
+ extraLine,
562
+ " },",
563
+ "});"
564
+ ].join("\n");
565
+ }
566
+ buildParameters(fn) {
567
+ if (fn.parameters.length > 0) return fn.parameters.map((parameter, index) => ({
568
+ name: parameter.name || `arg${index}`,
569
+ type: parameter.type || "pointer",
570
+ description: `Captured parameter ${index}`
571
+ }));
572
+ return [{
573
+ name: "arg0",
574
+ type: "pointer",
575
+ description: "Captured parameter 0"
576
+ }];
577
+ }
578
+ };
579
+ //#endregion
580
+ //#region src/modules/binary-instrument/ExtensionBridge.ts
581
+ async function invokePlugin(ctx, config) {
582
+ const available = getAvailablePlugins(ctx);
583
+ const normalizedRequested = normalizePluginId(config.pluginId);
584
+ const actualPluginId = resolvePluginId(normalizedRequested, available);
585
+ if (!actualPluginId) return {
586
+ success: false,
587
+ tool: "binary-instrument",
588
+ action: config.toolName,
589
+ error: `Plugin ${normalizedRequested} is not installed`
590
+ };
591
+ const runtime = findRuntime(ctx, actualPluginId);
592
+ if (!runtime?.lifecycleContext) return {
593
+ success: false,
594
+ tool: "binary-instrument",
595
+ action: config.toolName,
596
+ error: `Plugin ${actualPluginId} is installed but has no runtime`
597
+ };
598
+ try {
599
+ const firstText = (await runtime.lifecycleContext.invokeTool(config.toolName, config.args)).content?.find((item) => item.type === "text")?.text;
600
+ if (!firstText) return {
601
+ success: false,
602
+ tool: "binary-instrument",
603
+ action: config.toolName,
604
+ error: "Plugin returned no text content"
605
+ };
606
+ try {
607
+ const parsed = JSON.parse(firstText);
608
+ if (isResultRecord(parsed)) return {
609
+ tool: "binary-instrument",
610
+ action: config.toolName,
611
+ success: readBoolean(parsed, "success") ?? true,
612
+ data: parsed["data"],
613
+ error: readString(parsed, "error")
614
+ };
615
+ } catch {
616
+ return {
617
+ success: true,
618
+ tool: "binary-instrument",
619
+ action: config.toolName,
620
+ data: firstText
621
+ };
622
+ }
623
+ return {
624
+ success: true,
625
+ tool: "binary-instrument",
626
+ action: config.toolName,
627
+ data: firstText
628
+ };
629
+ } catch (error) {
630
+ return {
631
+ success: false,
632
+ tool: "binary-instrument",
633
+ action: config.toolName,
634
+ error: error instanceof Error ? error.message : String(error)
635
+ };
636
+ }
637
+ }
638
+ function getAvailablePlugins(ctx) {
639
+ return Array.from(ctx.extensionPluginsById.keys()).map(normalizePluginId);
640
+ }
641
+ function normalizePluginId(pluginId) {
642
+ return pluginId.replaceAll("_", "-");
643
+ }
644
+ function resolvePluginId(requested, installed) {
645
+ const requestedWithoutPrefix = requested.replace(/^plugin-/, "");
646
+ for (const installedId of installed) {
647
+ const installedWithoutPrefix = installedId.replace(/^plugin-/, "");
648
+ if (installedId === requested || installedWithoutPrefix === requestedWithoutPrefix) return installedId;
649
+ }
650
+ }
651
+ function findRuntime(ctx, normalizedPluginId) {
652
+ for (const [pluginId, runtime] of ctx.extensionPluginRuntimeById.entries()) if (normalizePluginId(pluginId) === normalizedPluginId) return isPluginRuntime(runtime) ? runtime : void 0;
653
+ }
654
+ function isPluginRuntime(value) {
655
+ return typeof value === "object" && value !== null;
656
+ }
657
+ function isResultRecord(value) {
658
+ return typeof value === "object" && value !== null;
659
+ }
660
+ function readBoolean(record, key) {
661
+ const value = record[key];
662
+ return typeof value === "boolean" ? value : void 0;
663
+ }
664
+ function readString(record, key) {
665
+ const value = record[key];
666
+ return typeof value === "string" ? value : void 0;
667
+ }
668
+ //#endregion
669
+ //#region src/modules/binary-instrument/HookGenerator.ts
670
+ var HookGenerator = class {
671
+ generateFridaHookScript(symbols, options) {
672
+ const includeArgs = options?.includeArgs ?? true;
673
+ const includeRetAddr = options?.includeRetAddr ?? false;
674
+ const lines = [
675
+ "'use strict';",
676
+ "",
677
+ "function resolveTarget(name) {",
678
+ " try {",
679
+ " const exported = Module.findExportByName(null, name);",
680
+ " if (exported) {",
681
+ " return exported;",
682
+ " }",
683
+ " } catch (error) {}",
684
+ " try {",
685
+ " const symbol = DebugSymbol.fromName(name);",
686
+ " if (symbol && symbol.address) {",
687
+ " return symbol.address;",
688
+ " }",
689
+ " } catch (error) {}",
690
+ " return null;",
691
+ "}",
692
+ "",
693
+ "const installedHooks = [];"
694
+ ];
695
+ for (let index = 0; index < symbols.length; index += 1) {
696
+ const descriptor = this.toDescriptor(symbols[index]);
697
+ const varName = `target_${index}`;
698
+ const label = descriptor.demangled ?? descriptor.name;
699
+ const addressLine = descriptor.address ? `const ${varName} = ptr("${this.escapeForDoubleQuotes(descriptor.address)}");` : `const ${varName} = resolveTarget("${this.escapeForDoubleQuotes(descriptor.name)}");`;
700
+ lines.push(addressLine);
701
+ lines.push(`if (${varName}) {`);
702
+ lines.push(` Interceptor.attach(${varName}, {`);
703
+ lines.push(" onEnter(args) {");
704
+ if (includeRetAddr) lines.push(` console.log("[binary-instrument] enter ${this.escapeForDoubleQuotes(label)} ret=" + this.returnAddress);`);
705
+ else lines.push(` console.log("[binary-instrument] enter ${this.escapeForDoubleQuotes(label)}");`);
706
+ if (includeArgs) {
707
+ lines.push(" const renderedArgs = [];");
708
+ lines.push(" for (let i = 0; i < 6; i += 1) {");
709
+ lines.push(" try {");
710
+ lines.push(" renderedArgs.push(String(args[i]));");
711
+ lines.push(" } catch (error) {");
712
+ lines.push(" renderedArgs.push(\"<unreadable>\");");
713
+ lines.push(" }");
714
+ lines.push(" }");
715
+ lines.push(" console.log(\"[binary-instrument] args \" + JSON.stringify(renderedArgs));");
716
+ }
717
+ lines.push(" },");
718
+ lines.push(" onLeave(retval) {");
719
+ lines.push(` console.log("[binary-instrument] leave ${this.escapeForDoubleQuotes(label)} retval=" + retval);`);
720
+ lines.push(" },");
721
+ lines.push(" });");
722
+ lines.push(` installedHooks.push({ name: "${this.escapeForDoubleQuotes(label)}", address: String(${varName}) });`);
723
+ lines.push("} else {");
724
+ lines.push(` console.log("[binary-instrument] unresolved ${this.escapeForDoubleQuotes(label)}");`);
725
+ lines.push("}");
726
+ lines.push("");
727
+ }
728
+ lines.push("console.log(\"[binary-instrument] hooks=\" + JSON.stringify(installedHooks));");
729
+ return lines.join("\n");
730
+ }
731
+ generateInterceptorScript(targetFuncs) {
732
+ return this.generateFridaHookScript(targetFuncs, {
733
+ includeArgs: true,
734
+ includeRetAddr: false
735
+ });
736
+ }
737
+ toDescriptor(symbol) {
738
+ if (typeof symbol === "string") return { name: symbol };
739
+ return symbol;
740
+ }
741
+ escapeForDoubleQuotes(value) {
742
+ return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
743
+ }
744
+ };
745
+ //#endregion
746
+ //#region src/modules/binary-instrument/UnidbgRunner.ts
747
+ const UNIDBG_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
748
+ var UnidbgRunner = class {
749
+ sessions = /* @__PURE__ */ new Map();
750
+ close() {
751
+ for (const session of this.sessions.values()) if (session.childProcess) try {
752
+ process.kill(session.childProcess.pid, "SIGTERM");
753
+ } catch {}
754
+ this.sessions.clear();
755
+ }
756
+ /**
757
+ * Launch a .so library in the Unidbg emulator via JVM subprocess.
758
+ * Returns a sessionId for subsequent call/trace operations.
759
+ */
760
+ async launch(soPath, arch = "arm", jarPath) {
761
+ const resolvedJar = jarPath ?? process.env["UNIDBG_JAR"];
762
+ if (!resolvedJar) throw new Error("UNIDBG_JAR is not configured. Set the UNIDBG_JAR env var or pass jarPath.");
763
+ try {
764
+ await access(resolvedJar);
765
+ } catch {
766
+ throw new Error(`Unidbg JAR not found: ${resolvedJar}`);
767
+ }
768
+ try {
769
+ await access(soPath);
770
+ } catch {
771
+ throw new Error(`Shared library not found: ${soPath}`);
772
+ }
773
+ const sessionId = randomUUID();
774
+ const command = this.getJavaCommand();
775
+ const args = [
776
+ "-jar",
777
+ resolvedJar,
778
+ "--so",
779
+ soPath,
780
+ "--arch",
781
+ arch,
782
+ "--server"
783
+ ];
784
+ try {
785
+ const result = await this.execFileUtf8(command, args, UNIDBG_TIMEOUT_MS);
786
+ const sessionInfo = this.parseLaunchOutput(result.stdout, sessionId);
787
+ const session = {
788
+ id: sessionInfo.id,
789
+ soPath,
790
+ arch,
791
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
792
+ childProcess: sessionInfo.pid ? { pid: sessionInfo.pid } : void 0
793
+ };
794
+ this.sessions.set(sessionId, session);
795
+ return {
796
+ sessionId,
797
+ soPath,
798
+ arch
799
+ };
800
+ } catch (error) {
801
+ const message = error instanceof Error ? error.message : String(error);
802
+ logger.warn("[binary-instrument] Unidbg launch failed, registering stub session", {
803
+ soPath,
804
+ message
805
+ });
806
+ const session = {
807
+ id: sessionId,
808
+ soPath,
809
+ arch,
810
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
811
+ };
812
+ this.sessions.set(sessionId, session);
813
+ return {
814
+ sessionId,
815
+ soPath,
816
+ arch
817
+ };
818
+ }
819
+ }
820
+ async callFunction(sessionId, functionName, args = {}) {
821
+ if (!this.sessions.get(sessionId)) throw new Error(`No unidbg session found for ${sessionId}`);
822
+ const jarPath = process.env["UNIDBG_JAR"];
823
+ if (!jarPath) return {
824
+ sessionId,
825
+ functionName,
826
+ args,
827
+ returnValue: "0x0",
828
+ stdout: "",
829
+ stderr: "",
830
+ trace: ["mock-unidbg-unavailable"],
831
+ _note: "Unidbg emulation requires UNIDBG_JAR to be configured"
832
+ };
833
+ const command = this.getJavaCommand();
834
+ const callArgs = [
835
+ "-jar",
836
+ jarPath,
837
+ "--session",
838
+ sessionId,
839
+ "--call",
840
+ functionName,
841
+ "--args",
842
+ JSON.stringify(args)
843
+ ];
844
+ try {
845
+ const result = await this.execFileUtf8(command, callArgs, UNIDBG_TIMEOUT_MS);
846
+ return {
847
+ sessionId,
848
+ functionName,
849
+ args,
850
+ returnValue: this.extractReturnValue(result.stdout),
851
+ stdout: result.stdout.trim(),
852
+ stderr: result.stderr.trim(),
853
+ trace: []
854
+ };
855
+ } catch (error) {
856
+ return {
857
+ sessionId,
858
+ functionName,
859
+ args,
860
+ returnValue: "0x0",
861
+ stdout: "",
862
+ stderr: error instanceof Error ? error.message : String(error),
863
+ trace: ["error"]
864
+ };
865
+ }
866
+ }
867
+ async trace(sessionId) {
868
+ if (!this.sessions.get(sessionId)) throw new Error(`No unidbg session found for ${sessionId}`);
869
+ const jarPath = process.env["UNIDBG_JAR"];
870
+ if (!jarPath) return {
871
+ sessionId,
872
+ trace: ["mock-unidbg-unavailable"],
873
+ _note: "Unidbg tracing requires UNIDBG_JAR to be configured"
874
+ };
875
+ const command = this.getJavaCommand();
876
+ const traceArgs = [
877
+ "-jar",
878
+ jarPath,
879
+ "--session",
880
+ sessionId,
881
+ "--trace"
882
+ ];
883
+ try {
884
+ const result = await this.execFileUtf8(command, traceArgs, UNIDBG_TIMEOUT_MS);
885
+ return {
886
+ sessionId,
887
+ trace: this.parseTraceOutput(result.stdout),
888
+ instructionCount: this.countInstructions(result.stdout)
889
+ };
890
+ } catch (error) {
891
+ return {
892
+ sessionId,
893
+ trace: ["error"],
894
+ error: error instanceof Error ? error.message : String(error)
895
+ };
896
+ }
897
+ }
898
+ /**
899
+ * Get info about an active Unidbg session.
900
+ */
901
+ getSessionInfo(sessionId) {
902
+ return this.sessions.get(sessionId);
903
+ }
904
+ /**
905
+ * List all active Unidbg sessions.
906
+ */
907
+ listSessions() {
908
+ return Array.from(this.sessions.values()).map((s) => ({
909
+ id: s.id,
910
+ soPath: s.soPath,
911
+ arch: s.arch,
912
+ startedAt: s.startedAt
913
+ }));
914
+ }
915
+ getJavaCommand() {
916
+ return process.env["JAVA_HOME"] ? `${process.env["JAVA_HOME"]}/bin/java` : "java";
917
+ }
918
+ parseLaunchOutput(stdout, fallbackId) {
919
+ const lines = stdout.split(/\r?\n/).filter((l) => l.trim().length > 0);
920
+ for (const line of lines.toReversed()) try {
921
+ const parsed = JSON.parse(line);
922
+ if (typeof parsed["id"] === "string") return {
923
+ id: parsed["id"],
924
+ pid: typeof parsed["pid"] === "number" ? parsed["pid"] : null
925
+ };
926
+ } catch {}
927
+ return {
928
+ id: fallbackId,
929
+ pid: null
930
+ };
931
+ }
932
+ extractReturnValue(stdout) {
933
+ const match = /return[=:\s]+(0x[0-9a-fA-F]+|-?\d+)/.exec(stdout);
934
+ if (match?.[1]) return match[1];
935
+ return "0x0";
936
+ }
937
+ parseTraceOutput(stdout) {
938
+ return stdout.split(/\r?\n/).filter((line) => line.trim().length > 0 && !line.startsWith("{")).slice(0, 1e4);
939
+ }
940
+ countInstructions(stdout) {
941
+ return stdout.split(/\r?\n/).filter((line) => line.trim().length > 0 && !line.startsWith("{") && /\b(ldr|str|mov|bl|b|add|sub)\b/i.test(line)).length;
942
+ }
943
+ async execFileUtf8(file, args, timeoutMs) {
944
+ return new Promise((resolve, reject) => {
945
+ execFile(file, args, {
946
+ timeout: timeoutMs,
947
+ windowsHide: true,
948
+ maxBuffer: UNIDBG_MAX_BUFFER_BYTES,
949
+ encoding: "utf8"
950
+ }, (error, stdout, stderr) => {
951
+ if (error) {
952
+ reject(error);
953
+ return;
954
+ }
955
+ resolve({
956
+ stdout: typeof stdout === "string" ? stdout : "",
957
+ stderr: typeof stderr === "string" ? stderr : "",
958
+ exitCode: 0
959
+ });
960
+ });
961
+ });
962
+ }
963
+ };
964
+ //#endregion
965
+ //#region src/modules/binary-instrument/index.ts
966
+ var binary_instrument_exports = /* @__PURE__ */ __exportAll({
967
+ GhidraAnalyzer: () => GhidraAnalyzer,
968
+ HookGenerator: () => HookGenerator
969
+ });
970
+ //#endregion
971
+ export { invokePlugin as a, FridaSession as c, getAvailablePlugins as i, UnidbgRunner as n, HookCodeGenerator as o, HookGenerator as r, GhidraAnalyzer as s, binary_instrument_exports as t };