binary-ninja-mcp 1.0.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.
- package/README.md +191 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +111 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/tools.d.ts +7 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +752 -0
- package/dist/tools.js.map +1 -0
- package/package.json +45 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool definitions for Binary Ninja integration.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export function registerTools(server, client) {
|
|
6
|
+
// Helper function to get active filename
|
|
7
|
+
async function getActiveFilename() {
|
|
8
|
+
const data = await client.getJson("status");
|
|
9
|
+
if (data && typeof data === "object" && "filename" in data) {
|
|
10
|
+
return data.filename || "(none)";
|
|
11
|
+
}
|
|
12
|
+
return "(none)";
|
|
13
|
+
}
|
|
14
|
+
// ===== Function Analysis Tools =====
|
|
15
|
+
server.tool("list_methods", "List all function names in the program with pagination.", {
|
|
16
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
17
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
18
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
19
|
+
const filename = await getActiveFilename();
|
|
20
|
+
const lines = await client.getLines("methods", { offset, limit });
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: `File: ${filename}\n${lines.join("\n")}` }],
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
server.tool("get_entry_points", "List entry point(s) of the loaded binary.", {}, async () => {
|
|
26
|
+
const data = await client.getJson("entryPoints");
|
|
27
|
+
if (!data || "error" in data) {
|
|
28
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
29
|
+
}
|
|
30
|
+
const entryPoints = data.entry_points || [];
|
|
31
|
+
const lines = entryPoints.map((ep) => {
|
|
32
|
+
const addr = ep.address;
|
|
33
|
+
const name = ep.name || "(unknown)";
|
|
34
|
+
return `${addr}\t${name}`;
|
|
35
|
+
});
|
|
36
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
37
|
+
});
|
|
38
|
+
server.tool("search_functions_by_name", "Search for functions whose name contains the given substring.", {
|
|
39
|
+
query: z.string().describe("Search query string"),
|
|
40
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
41
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
42
|
+
}, async ({ query, offset = 0, limit = 100 }) => {
|
|
43
|
+
if (!query) {
|
|
44
|
+
return { content: [{ type: "text", text: "Error: query string is required" }] };
|
|
45
|
+
}
|
|
46
|
+
const lines = await client.getLines("searchFunctions", { query, offset, limit });
|
|
47
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
48
|
+
});
|
|
49
|
+
server.tool("decompile_function", "Decompile a specific function by name and return the decompiled C code.", {
|
|
50
|
+
name: z.string().describe("Function name or address"),
|
|
51
|
+
}, async ({ name }) => {
|
|
52
|
+
const filename = await getActiveFilename();
|
|
53
|
+
const data = await client.getJson("decompile", { name });
|
|
54
|
+
if (!data) {
|
|
55
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\nError: no response` }] };
|
|
56
|
+
}
|
|
57
|
+
if ("error" in data) {
|
|
58
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\nError: ${data.error}` }] };
|
|
59
|
+
}
|
|
60
|
+
const decompiled = data.decompiled;
|
|
61
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\n${decompiled || ""}` }] };
|
|
62
|
+
});
|
|
63
|
+
server.tool("get_il", "Get IL for a function in the selected view (hlil, mlil, llil).", {
|
|
64
|
+
name_or_address: z.string().describe("Function name or address (hex like 0x401000)"),
|
|
65
|
+
view: z.enum(["hlil", "mlil", "llil"]).default("hlil").describe("IL view: hlil, mlil, or llil"),
|
|
66
|
+
ssa: z.boolean().default(false).describe("Request SSA form (MLIL/LLIL only)"),
|
|
67
|
+
}, async ({ name_or_address, view = "hlil", ssa = false }) => {
|
|
68
|
+
const filename = await getActiveFilename();
|
|
69
|
+
const ident = name_or_address.trim();
|
|
70
|
+
const params = { view, ssa: ssa ? 1 : 0 };
|
|
71
|
+
if (ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)) {
|
|
72
|
+
params.address = ident;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
params.name = ident;
|
|
76
|
+
}
|
|
77
|
+
const data = await client.getJson("il", params);
|
|
78
|
+
if (!data) {
|
|
79
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\nError: no response` }] };
|
|
80
|
+
}
|
|
81
|
+
if ("error" in data) {
|
|
82
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\nError: ${JSON.stringify(data.error)}` }] };
|
|
83
|
+
}
|
|
84
|
+
const il = data.il;
|
|
85
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\n${il || ""}` }] };
|
|
86
|
+
});
|
|
87
|
+
server.tool("fetch_disassembly", "Retrieve the disassembled code of a function as assembly mnemonic instructions.", {
|
|
88
|
+
name: z.string().describe("Function name"),
|
|
89
|
+
}, async ({ name }) => {
|
|
90
|
+
const filename = await getActiveFilename();
|
|
91
|
+
const data = await client.getJson("assembly", { name });
|
|
92
|
+
if (!data) {
|
|
93
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\nError: no response` }] };
|
|
94
|
+
}
|
|
95
|
+
if ("error" in data) {
|
|
96
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\nError: ${data.error}` }] };
|
|
97
|
+
}
|
|
98
|
+
const assembly = data.assembly;
|
|
99
|
+
return { content: [{ type: "text", text: `File: ${filename}\n\n${assembly || ""}` }] };
|
|
100
|
+
});
|
|
101
|
+
// ===== Rename Tools =====
|
|
102
|
+
server.tool("rename_function", "Rename a function by its current name. The configured prefix will be automatically prepended if not present.", {
|
|
103
|
+
old_name: z.string().describe("Current function name"),
|
|
104
|
+
new_name: z.string().describe("New function name"),
|
|
105
|
+
}, async ({ old_name, new_name }) => {
|
|
106
|
+
const result = await client.post("renameFunction", { oldName: old_name, newName: new_name });
|
|
107
|
+
return { content: [{ type: "text", text: result }] };
|
|
108
|
+
});
|
|
109
|
+
server.tool("rename_single_variable", "Rename a variable in a function.", {
|
|
110
|
+
function_name: z.string().describe("Function name"),
|
|
111
|
+
variable_name: z.string().describe("Current variable name"),
|
|
112
|
+
new_name: z.string().describe("New variable name"),
|
|
113
|
+
}, async ({ function_name, variable_name, new_name }) => {
|
|
114
|
+
const data = await client.getJson("renameVariable", {
|
|
115
|
+
functionName: function_name,
|
|
116
|
+
variableName: variable_name,
|
|
117
|
+
newName: new_name,
|
|
118
|
+
});
|
|
119
|
+
if (!data) {
|
|
120
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
121
|
+
}
|
|
122
|
+
if ("status" in data) {
|
|
123
|
+
return { content: [{ type: "text", text: data.status }] };
|
|
124
|
+
}
|
|
125
|
+
if ("error" in data) {
|
|
126
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
127
|
+
}
|
|
128
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
129
|
+
});
|
|
130
|
+
server.tool("rename_multi_variables", "Rename multiple local variables in one call.", {
|
|
131
|
+
function_identifier: z.string().describe("Function name or address (hex)"),
|
|
132
|
+
mapping_json: z.string().optional().describe("JSON object old->new"),
|
|
133
|
+
pairs: z.string().optional().describe("Comma-separated old:new pairs"),
|
|
134
|
+
renames_json: z.string().optional().describe("JSON array of {old,new} objects"),
|
|
135
|
+
}, async ({ function_identifier, mapping_json, pairs, renames_json }) => {
|
|
136
|
+
const ident = function_identifier.trim();
|
|
137
|
+
const params = {};
|
|
138
|
+
if (ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)) {
|
|
139
|
+
params.address = ident;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
params.functionName = ident;
|
|
143
|
+
}
|
|
144
|
+
if (renames_json) {
|
|
145
|
+
try {
|
|
146
|
+
JSON.parse(renames_json);
|
|
147
|
+
params.renames = renames_json;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { content: [{ type: "text", text: "Error: renames_json is not valid JSON" }] };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (mapping_json) {
|
|
154
|
+
try {
|
|
155
|
+
JSON.parse(mapping_json);
|
|
156
|
+
params.mapping = mapping_json;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return { content: [{ type: "text", text: "Error: mapping_json is not valid JSON" }] };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else if (pairs) {
|
|
163
|
+
params.pairs = pairs;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
return { content: [{ type: "text", text: "Error: provide mapping_json, renames_json, or pairs" }] };
|
|
167
|
+
}
|
|
168
|
+
const data = await client.getJson("renameVariables", params);
|
|
169
|
+
if (!data) {
|
|
170
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
171
|
+
}
|
|
172
|
+
if ("error" in data) {
|
|
173
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
174
|
+
}
|
|
175
|
+
const total = data.total;
|
|
176
|
+
const renamed = data.renamed;
|
|
177
|
+
return { content: [{ type: "text", text: `Batch rename: ${renamed}/${total} applied` }] };
|
|
178
|
+
});
|
|
179
|
+
server.tool("rename_data", "Rename a data label at the specified address.", {
|
|
180
|
+
address: z.string().describe("Address (hex like 0x401000)"),
|
|
181
|
+
new_name: z.string().describe("New name for the data label"),
|
|
182
|
+
}, async ({ address, new_name }) => {
|
|
183
|
+
const result = await client.post("renameData", { address, newName: new_name });
|
|
184
|
+
return { content: [{ type: "text", text: result }] };
|
|
185
|
+
});
|
|
186
|
+
// ===== Comment Tools =====
|
|
187
|
+
server.tool("set_comment", "Set a comment at a specific address.", {
|
|
188
|
+
address: z.string().describe("Address (hex like 0x401000)"),
|
|
189
|
+
comment: z.string().describe("Comment text"),
|
|
190
|
+
}, async ({ address, comment }) => {
|
|
191
|
+
const result = await client.post("comment", { address, comment });
|
|
192
|
+
return { content: [{ type: "text", text: result }] };
|
|
193
|
+
});
|
|
194
|
+
server.tool("get_comment", "Get the comment at a specific address.", {
|
|
195
|
+
address: z.string().describe("Address (hex like 0x401000)"),
|
|
196
|
+
}, async ({ address }) => {
|
|
197
|
+
const lines = await client.getLines("comment", { address });
|
|
198
|
+
return { content: [{ type: "text", text: lines[0] ?? "" }] };
|
|
199
|
+
});
|
|
200
|
+
server.tool("delete_comment", "Delete the comment at a specific address.", {
|
|
201
|
+
address: z.string().describe("Address (hex like 0x401000)"),
|
|
202
|
+
}, async ({ address }) => {
|
|
203
|
+
const result = await client.post("comment", { address, _method: "DELETE" });
|
|
204
|
+
return { content: [{ type: "text", text: result }] };
|
|
205
|
+
});
|
|
206
|
+
server.tool("set_function_comment", "Set a comment for a function.", {
|
|
207
|
+
function_name: z.string().describe("Function name"),
|
|
208
|
+
comment: z.string().describe("Comment text"),
|
|
209
|
+
}, async ({ function_name, comment }) => {
|
|
210
|
+
const result = await client.post("comment/function", { name: function_name, comment });
|
|
211
|
+
return { content: [{ type: "text", text: result }] };
|
|
212
|
+
});
|
|
213
|
+
server.tool("get_function_comment", "Get the comment for a function.", {
|
|
214
|
+
function_name: z.string().describe("Function name"),
|
|
215
|
+
}, async ({ function_name }) => {
|
|
216
|
+
const lines = await client.getLines("comment/function", { name: function_name });
|
|
217
|
+
return { content: [{ type: "text", text: lines[0] || "" }] };
|
|
218
|
+
});
|
|
219
|
+
server.tool("delete_function_comment", "Delete the comment for a function.", {
|
|
220
|
+
function_name: z.string().describe("Function name"),
|
|
221
|
+
}, async ({ function_name }) => {
|
|
222
|
+
const result = await client.post("comment/function", { name: function_name, _method: "DELETE" });
|
|
223
|
+
return { content: [{ type: "text", text: result }] };
|
|
224
|
+
});
|
|
225
|
+
// ===== Type Tools =====
|
|
226
|
+
server.tool("define_types", "Define types from a C code string.", {
|
|
227
|
+
c_code: z.string().describe("C code containing type definitions"),
|
|
228
|
+
}, async ({ c_code }) => {
|
|
229
|
+
const data = await client.getJson("defineTypes", { cCode: c_code });
|
|
230
|
+
if (!data) {
|
|
231
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
232
|
+
}
|
|
233
|
+
if (Array.isArray(data)) {
|
|
234
|
+
return { content: [{ type: "text", text: `Defined types: ${data.join(", ")}` }] };
|
|
235
|
+
}
|
|
236
|
+
if ("error" in data) {
|
|
237
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
238
|
+
}
|
|
239
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
240
|
+
});
|
|
241
|
+
server.tool("list_local_types", "List all local types in the database (paginated).", {
|
|
242
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
243
|
+
count: z.number().default(200).describe("Number of results to return"),
|
|
244
|
+
include_libraries: z.boolean().default(false).describe("Include library types"),
|
|
245
|
+
}, async ({ offset = 0, count = 200, include_libraries = false }) => {
|
|
246
|
+
const lines = await client.getLines("localTypes", {
|
|
247
|
+
offset,
|
|
248
|
+
limit: count,
|
|
249
|
+
includeLibraries: include_libraries ? 1 : 0,
|
|
250
|
+
});
|
|
251
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
252
|
+
});
|
|
253
|
+
server.tool("search_types", "Search local types whose name or declaration contains the substring.", {
|
|
254
|
+
query: z.string().describe("Search query"),
|
|
255
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
256
|
+
count: z.number().default(200).describe("Number of results to return"),
|
|
257
|
+
include_libraries: z.boolean().default(false).describe("Include library types"),
|
|
258
|
+
}, async ({ query, offset = 0, count = 200, include_libraries = false }) => {
|
|
259
|
+
const lines = await client.getLines("searchTypes", {
|
|
260
|
+
query,
|
|
261
|
+
offset,
|
|
262
|
+
limit: count,
|
|
263
|
+
includeLibraries: include_libraries ? 1 : 0,
|
|
264
|
+
});
|
|
265
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
266
|
+
});
|
|
267
|
+
server.tool("get_user_defined_type", "Retrieve definition of a user defined type (struct, enumeration, typedef, union).", {
|
|
268
|
+
type_name: z.string().describe("Type name"),
|
|
269
|
+
}, async ({ type_name }) => {
|
|
270
|
+
const lines = await client.getLines("getUserDefinedType", { name: type_name });
|
|
271
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
272
|
+
});
|
|
273
|
+
server.tool("get_type_info", "Resolve a type name and return its declaration and details (kind, members, enum values).", {
|
|
274
|
+
type_name: z.string().describe("Type name"),
|
|
275
|
+
}, async ({ type_name }) => {
|
|
276
|
+
const data = await client.getJson("getTypeInfo", { name: type_name });
|
|
277
|
+
if (!data) {
|
|
278
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
279
|
+
}
|
|
280
|
+
if ("error" in data) {
|
|
281
|
+
return { content: [{ type: "text", text: `Error: ${String(data.error)}` }] };
|
|
282
|
+
}
|
|
283
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
284
|
+
});
|
|
285
|
+
server.tool("declare_c_type", "Create or update a local type from a C declaration.", {
|
|
286
|
+
c_declaration: z.string().describe("C type declaration"),
|
|
287
|
+
}, async ({ c_declaration }) => {
|
|
288
|
+
const data = await client.getJson("declareCType", { declaration: c_declaration });
|
|
289
|
+
if (!data) {
|
|
290
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
291
|
+
}
|
|
292
|
+
if ("error" in data) {
|
|
293
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
294
|
+
}
|
|
295
|
+
if (data.defined_types) {
|
|
296
|
+
const names = Object.keys(data.defined_types).join(", ");
|
|
297
|
+
const count = data.count || 0;
|
|
298
|
+
return { content: [{ type: "text", text: `Declared types (${count}): ${names}` }] };
|
|
299
|
+
}
|
|
300
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
301
|
+
});
|
|
302
|
+
server.tool("retype_variable", "Retype a variable in a function.", {
|
|
303
|
+
function_name: z.string().describe("Function name"),
|
|
304
|
+
variable_name: z.string().describe("Variable name"),
|
|
305
|
+
type_str: z.string().describe("New type for the variable"),
|
|
306
|
+
}, async ({ function_name, variable_name, type_str }) => {
|
|
307
|
+
const data = await client.getJson("retypeVariable", {
|
|
308
|
+
functionName: function_name,
|
|
309
|
+
variableName: variable_name,
|
|
310
|
+
type: type_str,
|
|
311
|
+
});
|
|
312
|
+
if (!data) {
|
|
313
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
314
|
+
}
|
|
315
|
+
if ("status" in data) {
|
|
316
|
+
return { content: [{ type: "text", text: data.status }] };
|
|
317
|
+
}
|
|
318
|
+
if ("error" in data) {
|
|
319
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
320
|
+
}
|
|
321
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
322
|
+
});
|
|
323
|
+
server.tool("set_local_variable_type", "Set a local variable's type.", {
|
|
324
|
+
function_address: z.string().describe("Function address or name"),
|
|
325
|
+
variable_name: z.string().describe("Variable name"),
|
|
326
|
+
new_type: z.string().describe("New type"),
|
|
327
|
+
}, async ({ function_address, variable_name, new_type }) => {
|
|
328
|
+
const data = await client.getJson("setLocalVariableType", {
|
|
329
|
+
functionAddress: function_address,
|
|
330
|
+
variableName: variable_name,
|
|
331
|
+
newType: new_type,
|
|
332
|
+
});
|
|
333
|
+
if (!data) {
|
|
334
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
335
|
+
}
|
|
336
|
+
if (data.status === "ok") {
|
|
337
|
+
const d = data;
|
|
338
|
+
return { content: [{ type: "text", text: `Retyped ${d.variable} in ${d.function} to ${d.applied_type}` }] };
|
|
339
|
+
}
|
|
340
|
+
if ("error" in data) {
|
|
341
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
342
|
+
}
|
|
343
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
344
|
+
});
|
|
345
|
+
// ===== Data Tools =====
|
|
346
|
+
server.tool("list_data_items", "List defined data labels and their values with pagination.", {
|
|
347
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
348
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
349
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
350
|
+
const lines = await client.getLines("data", { offset, limit });
|
|
351
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
352
|
+
});
|
|
353
|
+
server.tool("hexdump_address", "Hexdump data starting at an address.", {
|
|
354
|
+
address: z.string().describe("Address (hex like 0x401000)"),
|
|
355
|
+
length: z.number().default(-1).describe("Number of bytes (use -1 for defined size)"),
|
|
356
|
+
}, async ({ address, length = -1 }) => {
|
|
357
|
+
const params = { address };
|
|
358
|
+
if (length !== undefined && length !== -1) {
|
|
359
|
+
params.length = length;
|
|
360
|
+
}
|
|
361
|
+
const text = await client.getText("hexdump", params);
|
|
362
|
+
return { content: [{ type: "text", text }] };
|
|
363
|
+
});
|
|
364
|
+
server.tool("hexdump_data", "Hexdump a data symbol by name or address.", {
|
|
365
|
+
name_or_address: z.string().describe("Symbol name or address (hex)"),
|
|
366
|
+
length: z.number().default(-1).describe("Number of bytes (use -1 for defined size)"),
|
|
367
|
+
}, async ({ name_or_address, length = -1 }) => {
|
|
368
|
+
const ident = name_or_address.trim();
|
|
369
|
+
if (ident.startsWith("0x")) {
|
|
370
|
+
const params = { address: ident };
|
|
371
|
+
if (length !== undefined && length !== -1) {
|
|
372
|
+
params.length = length;
|
|
373
|
+
}
|
|
374
|
+
const text = await client.getText("hexdump", params);
|
|
375
|
+
return { content: [{ type: "text", text }] };
|
|
376
|
+
}
|
|
377
|
+
const params = { name: ident };
|
|
378
|
+
if (length !== undefined && length !== -1) {
|
|
379
|
+
params.length = length;
|
|
380
|
+
}
|
|
381
|
+
const text = await client.getText("hexdumpByName", params);
|
|
382
|
+
return { content: [{ type: "text", text }] };
|
|
383
|
+
});
|
|
384
|
+
server.tool("get_data_decl", "Return a declaration-like string and hexdump for a data symbol.", {
|
|
385
|
+
name_or_address: z.string().describe("Symbol name or address (hex)"),
|
|
386
|
+
length: z.number().default(-1).describe("Number of bytes"),
|
|
387
|
+
}, async ({ name_or_address, length = -1 }) => {
|
|
388
|
+
const ident = name_or_address.trim();
|
|
389
|
+
const params = ident.startsWith("0x")
|
|
390
|
+
? { address: ident }
|
|
391
|
+
: { name: ident };
|
|
392
|
+
if (length !== undefined && length !== -1) {
|
|
393
|
+
params.length = length;
|
|
394
|
+
}
|
|
395
|
+
const data = await client.getJson("getDataDecl", params);
|
|
396
|
+
if (!data) {
|
|
397
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
398
|
+
}
|
|
399
|
+
if ("error" in data) {
|
|
400
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
401
|
+
}
|
|
402
|
+
const decl = data.decl || "(no declaration)";
|
|
403
|
+
const hexdump = data.hexdump || "";
|
|
404
|
+
const addr = data.address || "";
|
|
405
|
+
const name = data.name || ident;
|
|
406
|
+
return {
|
|
407
|
+
content: [{ type: "text", text: `Declaration (${addr} ${name}):\n${decl}\n\nHexdump:\n${hexdump}` }],
|
|
408
|
+
};
|
|
409
|
+
});
|
|
410
|
+
// ===== Cross-Reference Tools =====
|
|
411
|
+
server.tool("get_xrefs_to", "Get all cross references (code and data) to the given address.", {
|
|
412
|
+
address: z.string().describe("Address (hex or decimal)"),
|
|
413
|
+
}, async ({ address }) => {
|
|
414
|
+
const lines = await client.getLines("getXrefsTo", { address });
|
|
415
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
416
|
+
});
|
|
417
|
+
server.tool("get_xrefs_to_field", "Get all cross references to a named struct field (member).", {
|
|
418
|
+
struct_name: z.string().describe("Struct name"),
|
|
419
|
+
field_name: z.string().describe("Field name"),
|
|
420
|
+
}, async ({ struct_name, field_name }) => {
|
|
421
|
+
const lines = await client.getLines("getXrefsToField", { struct: struct_name, field: field_name });
|
|
422
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
423
|
+
});
|
|
424
|
+
server.tool("get_xrefs_to_struct", "Get cross references/usages related to a struct name.", {
|
|
425
|
+
struct_name: z.string().describe("Struct name"),
|
|
426
|
+
}, async ({ struct_name }) => {
|
|
427
|
+
const lines = await client.getLines("getXrefsToStruct", { name: struct_name });
|
|
428
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
429
|
+
});
|
|
430
|
+
server.tool("get_xrefs_to_type", "Get xrefs/usages related to a struct or type name.", {
|
|
431
|
+
type_name: z.string().describe("Type name"),
|
|
432
|
+
}, async ({ type_name }) => {
|
|
433
|
+
const lines = await client.getLines("getXrefsToType", { name: type_name });
|
|
434
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
435
|
+
});
|
|
436
|
+
server.tool("get_xrefs_to_enum", "Get usages/xrefs of an enum by scanning for member values and matches.", {
|
|
437
|
+
enum_name: z.string().describe("Enum name"),
|
|
438
|
+
}, async ({ enum_name }) => {
|
|
439
|
+
const lines = await client.getLines("getXrefsToEnum", { name: enum_name });
|
|
440
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
441
|
+
});
|
|
442
|
+
server.tool("get_xrefs_to_union", "Get cross references/usages related to a union type by name.", {
|
|
443
|
+
union_name: z.string().describe("Union name"),
|
|
444
|
+
}, async ({ union_name }) => {
|
|
445
|
+
const lines = await client.getLines("getXrefsToUnion", { name: union_name });
|
|
446
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
447
|
+
});
|
|
448
|
+
// ===== Utility Tools =====
|
|
449
|
+
server.tool("function_at", "Retrieve the name of the function the address belongs to.", {
|
|
450
|
+
address: z.string().describe("Address (hex format 0x00001)"),
|
|
451
|
+
}, async ({ address }) => {
|
|
452
|
+
const lines = await client.getLines("functionAt", { address });
|
|
453
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
454
|
+
});
|
|
455
|
+
server.tool("get_stack_frame_vars", "Get stack frame variable information for a function (names, offsets, sizes, types).", {
|
|
456
|
+
function_identifier: z.string().describe("Function name or address (hex)"),
|
|
457
|
+
}, async ({ function_identifier }) => {
|
|
458
|
+
const ident = function_identifier.trim();
|
|
459
|
+
const params = ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)
|
|
460
|
+
? { address: ident }
|
|
461
|
+
: { name: ident };
|
|
462
|
+
const data = await client.getJson("getStackFrameVars", params);
|
|
463
|
+
if (!data) {
|
|
464
|
+
return { content: [{ type: "text", text: "" }] };
|
|
465
|
+
}
|
|
466
|
+
if ("error" in data) {
|
|
467
|
+
return { content: [{ type: "text", text: "" }] };
|
|
468
|
+
}
|
|
469
|
+
const vars = data.stack_frame_vars;
|
|
470
|
+
return { content: [{ type: "text", text: (vars || []).join("\n") }] };
|
|
471
|
+
});
|
|
472
|
+
server.tool("list_classes", "List all namespace/class names in the program with pagination.", {
|
|
473
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
474
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
475
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
476
|
+
const lines = await client.getLines("classes", { offset, limit });
|
|
477
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
478
|
+
});
|
|
479
|
+
server.tool("list_namespaces", "List all non-global namespaces in the program with pagination.", {
|
|
480
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
481
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
482
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
483
|
+
const lines = await client.getLines("namespaces", { offset, limit });
|
|
484
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
485
|
+
});
|
|
486
|
+
server.tool("list_segments", "List all memory segments in the program with pagination.", {
|
|
487
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
488
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
489
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
490
|
+
const lines = await client.getLines("segments", { offset, limit });
|
|
491
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
492
|
+
});
|
|
493
|
+
server.tool("list_sections", "List sections in the program with pagination.", {
|
|
494
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
495
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
496
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
497
|
+
const data = await client.getJson("sections", { offset, limit });
|
|
498
|
+
if (!data || "error" in data) {
|
|
499
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
500
|
+
}
|
|
501
|
+
const filename = await getActiveFilename();
|
|
502
|
+
const sections = data.sections || [];
|
|
503
|
+
const lines = [`File: ${filename}`];
|
|
504
|
+
for (const s of sections) {
|
|
505
|
+
const start = s.start || "";
|
|
506
|
+
const end = s.end || "";
|
|
507
|
+
const size = s.size;
|
|
508
|
+
const name = s.name || "(unnamed)";
|
|
509
|
+
const sem = (s.semantics || s.type) || "";
|
|
510
|
+
const tail = sem ? `\t${sem}` : "";
|
|
511
|
+
lines.push(`${start}-${end}\t${size}\t${name}${tail}`);
|
|
512
|
+
}
|
|
513
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
514
|
+
});
|
|
515
|
+
server.tool("list_imports", "List imported symbols in the program with pagination.", {
|
|
516
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
517
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
518
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
519
|
+
const lines = await client.getLines("imports", { offset, limit });
|
|
520
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
521
|
+
});
|
|
522
|
+
server.tool("list_exports", "List exported functions/symbols with pagination.", {
|
|
523
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
524
|
+
limit: z.number().default(100).describe("Number of results to return"),
|
|
525
|
+
}, async ({ offset = 0, limit = 100 }) => {
|
|
526
|
+
const lines = await client.getLines("exports", { offset, limit });
|
|
527
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
528
|
+
});
|
|
529
|
+
server.tool("list_strings", "List all strings in the database (paginated).", {
|
|
530
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
531
|
+
count: z.number().default(100).describe("Number of results to return"),
|
|
532
|
+
}, async ({ offset = 0, count = 100 }) => {
|
|
533
|
+
const lines = await client.getLines("strings", { offset, limit: count });
|
|
534
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
535
|
+
});
|
|
536
|
+
server.tool("list_strings_filter", "List matching strings in the database (paginated, filtered).", {
|
|
537
|
+
offset: z.number().default(0).describe("Offset for pagination"),
|
|
538
|
+
count: z.number().default(100).describe("Number of results to return"),
|
|
539
|
+
filter: z.string().optional().describe("Filter string"),
|
|
540
|
+
}, async ({ offset = 0, count = 100, filter = "" }) => {
|
|
541
|
+
const lines = await client.getLines("strings/filter", { offset, limit: count, filter });
|
|
542
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
543
|
+
});
|
|
544
|
+
server.tool("list_all_strings", "List all strings in the database (aggregated across pages).", {
|
|
545
|
+
batch_size: z.number().default(500).describe("Batch size for aggregation"),
|
|
546
|
+
}, async ({ batch_size = 500 }) => {
|
|
547
|
+
const results = [];
|
|
548
|
+
let offset = 0;
|
|
549
|
+
while (true) {
|
|
550
|
+
const data = await client.getJson("strings", { offset, limit: batch_size });
|
|
551
|
+
if (!data || !("strings" in data)) {
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
const stringsData = data.strings;
|
|
555
|
+
const items = Array.isArray(stringsData) ? stringsData : [];
|
|
556
|
+
if (!items.length) {
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
for (const s of items) {
|
|
560
|
+
const addr = s.address || "";
|
|
561
|
+
const length = s.length;
|
|
562
|
+
const stype = s.type || "";
|
|
563
|
+
const value = s.value || "";
|
|
564
|
+
results.push(`${addr}\t${length}\t${stype}\t${value}`);
|
|
565
|
+
}
|
|
566
|
+
if (items.length < batch_size) {
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
offset += batch_size;
|
|
570
|
+
}
|
|
571
|
+
return { content: [{ type: "text", text: results.join("\n") }] };
|
|
572
|
+
});
|
|
573
|
+
server.tool("get_binary_status", "Get the current status of the loaded binary.", {}, async () => {
|
|
574
|
+
const lines = await client.getLines("status");
|
|
575
|
+
return { content: [{ type: "text", text: lines[0] || "" }] };
|
|
576
|
+
});
|
|
577
|
+
server.tool("list_binaries", "List managed/open binaries known to the server with ids and active flag.", {}, async () => {
|
|
578
|
+
const data = await client.getJson("binaries");
|
|
579
|
+
if (!data) {
|
|
580
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
581
|
+
}
|
|
582
|
+
if ("error" in data) {
|
|
583
|
+
return { content: [{ type: "text", text: data.error }] };
|
|
584
|
+
}
|
|
585
|
+
const binaries = data.binaries || [];
|
|
586
|
+
const lines = binaries.map((it) => {
|
|
587
|
+
const vid = it.id;
|
|
588
|
+
const view_id = it.view_id;
|
|
589
|
+
const fn = it.filename;
|
|
590
|
+
const basename = it.basename || "";
|
|
591
|
+
const selectors = it.selectors || [];
|
|
592
|
+
const active = it.active;
|
|
593
|
+
const label = basename || fn || "(unknown)";
|
|
594
|
+
const full = fn || "(no filename)";
|
|
595
|
+
const selectorText = selectors.map(String).join(", ");
|
|
596
|
+
const mark = active ? " *active*" : "";
|
|
597
|
+
const viewPart = view_id ? ` view=${view_id}` : "";
|
|
598
|
+
return `${vid}. ${label}${viewPart}${mark}\n path: ${full}\n selectors: ${selectorText}`;
|
|
599
|
+
});
|
|
600
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
601
|
+
});
|
|
602
|
+
server.tool("select_binary", "Select which binary to analyze by ordinal, internal view id, full path, or basename.", {
|
|
603
|
+
view: z.string().describe("Ordinal, view id, full path, or basename"),
|
|
604
|
+
}, async ({ view }) => {
|
|
605
|
+
const data = await client.getJson("selectBinary", { view });
|
|
606
|
+
if (!data) {
|
|
607
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
608
|
+
}
|
|
609
|
+
if ("error" in data) {
|
|
610
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
611
|
+
}
|
|
612
|
+
const sel = data.selected;
|
|
613
|
+
if (sel) {
|
|
614
|
+
const ordinal = sel.id || "?";
|
|
615
|
+
const view_id = sel.view_id;
|
|
616
|
+
const fn = sel.filename || "";
|
|
617
|
+
const basename = sel.basename || "";
|
|
618
|
+
const selectors = sel.selectors || [];
|
|
619
|
+
const selectorText = selectors.map(String).join(", ");
|
|
620
|
+
const displayName = basename || fn || "(unknown)";
|
|
621
|
+
const viewPart = view_id ? ` (view ${view_id})` : "";
|
|
622
|
+
const pathPart = fn ? `\nFull path: ${fn}` : "";
|
|
623
|
+
return { content: [{ type: "text", text: `Selected ${ordinal}: ${displayName}${viewPart}${pathPart}\nSelectors: ${selectorText}` }] };
|
|
624
|
+
}
|
|
625
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
626
|
+
});
|
|
627
|
+
// ===== Binary Modification Tools =====
|
|
628
|
+
server.tool("set_function_prototype", "Set a function's prototype by name or address.", {
|
|
629
|
+
name_or_address: z.string().describe("Function name or address"),
|
|
630
|
+
prototype: z.string().describe("New function prototype"),
|
|
631
|
+
}, async ({ name_or_address, prototype }) => {
|
|
632
|
+
const ident = name_or_address.trim();
|
|
633
|
+
const params = { prototype };
|
|
634
|
+
if (ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)) {
|
|
635
|
+
params.address = ident;
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
params.name = ident;
|
|
639
|
+
}
|
|
640
|
+
const data = await client.getJson("setFunctionPrototype", params);
|
|
641
|
+
if (!data) {
|
|
642
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
643
|
+
}
|
|
644
|
+
if ("status" in data) {
|
|
645
|
+
return { content: [{ type: "text", text: `Applied prototype at ${data.address}: ${data.applied_type}` }] };
|
|
646
|
+
}
|
|
647
|
+
if ("error" in data) {
|
|
648
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }] };
|
|
649
|
+
}
|
|
650
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
651
|
+
});
|
|
652
|
+
server.tool("make_function_at", "Create a function at the given address.", {
|
|
653
|
+
address: z.string().describe("Address (hex like 0x401000 or decimal)"),
|
|
654
|
+
platform: z.string().optional().describe("Platform (e.g., linux-x86_64)"),
|
|
655
|
+
}, async ({ address, platform }) => {
|
|
656
|
+
const params = { address };
|
|
657
|
+
if (platform) {
|
|
658
|
+
params.platform = platform;
|
|
659
|
+
}
|
|
660
|
+
const data = await client.getJson("makeFunctionAt", params);
|
|
661
|
+
if (!data) {
|
|
662
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
663
|
+
}
|
|
664
|
+
if ("error" in data) {
|
|
665
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
666
|
+
}
|
|
667
|
+
if (data.status === "exists") {
|
|
668
|
+
return { content: [{ type: "text", text: `Function already exists at ${data.address}: ${data.name}` }] };
|
|
669
|
+
}
|
|
670
|
+
if (data.status === "ok") {
|
|
671
|
+
return { content: [{ type: "text", text: `Created function at ${data.address}: ${data.name}` }] };
|
|
672
|
+
}
|
|
673
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
674
|
+
});
|
|
675
|
+
server.tool("list_platforms", "List all available platform names from Binary Ninja.", {}, async () => {
|
|
676
|
+
const data = await client.getJson("platforms");
|
|
677
|
+
if (!data) {
|
|
678
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
679
|
+
}
|
|
680
|
+
if ("error" in data) {
|
|
681
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
682
|
+
}
|
|
683
|
+
const plats = data.platforms;
|
|
684
|
+
if (!plats) {
|
|
685
|
+
return { content: [{ type: "text", text: "(no platforms)" }] };
|
|
686
|
+
}
|
|
687
|
+
return { content: [{ type: "text", text: plats.join("\n") }] };
|
|
688
|
+
});
|
|
689
|
+
server.tool("patch_bytes", "Patch bytes at a given address in the binary.", {
|
|
690
|
+
address: z.string().describe("Address (hex like 0x401000 or decimal)"),
|
|
691
|
+
data: z.string().describe("Hex string of bytes to write (e.g., '90 90' or '9090')"),
|
|
692
|
+
save_to_file: z.boolean().default(true).describe("Save patched binary to disk"),
|
|
693
|
+
}, async ({ address, data, save_to_file = true }) => {
|
|
694
|
+
const result = await client.getJson("patch", { address, data, save_to_file: save_to_file ? 1 : 0 });
|
|
695
|
+
if (!result) {
|
|
696
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
697
|
+
}
|
|
698
|
+
if ("error" in result) {
|
|
699
|
+
return { content: [{ type: "text", text: `Error: ${result.error}` }] };
|
|
700
|
+
}
|
|
701
|
+
const status = result.status;
|
|
702
|
+
if (status === "ok" || status === "partial") {
|
|
703
|
+
const orig = result.original_bytes || "";
|
|
704
|
+
const patched = result.patched_bytes || "";
|
|
705
|
+
const written = result.bytes_written || 0;
|
|
706
|
+
const requested = result.bytes_requested || 0;
|
|
707
|
+
const saved = result.saved_to_file || false;
|
|
708
|
+
const savedPath = result.saved_path || "";
|
|
709
|
+
const warning = result.warning || "";
|
|
710
|
+
let msg = `Patched ${written}/${requested} bytes at ${address}`;
|
|
711
|
+
if (status === "partial") {
|
|
712
|
+
msg += " (PARTIAL WRITE)";
|
|
713
|
+
}
|
|
714
|
+
if (warning) {
|
|
715
|
+
msg += `\nWarning: ${warning}`;
|
|
716
|
+
}
|
|
717
|
+
if (orig) {
|
|
718
|
+
msg += `\nOriginal: ${orig}`;
|
|
719
|
+
}
|
|
720
|
+
if (patched) {
|
|
721
|
+
msg += `\nPatched: ${patched}`;
|
|
722
|
+
}
|
|
723
|
+
if (saved) {
|
|
724
|
+
msg += `\nSaved to file: ${savedPath}`;
|
|
725
|
+
}
|
|
726
|
+
return { content: [{ type: "text", text: msg }] };
|
|
727
|
+
}
|
|
728
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
729
|
+
});
|
|
730
|
+
server.tool("format_value", "Convert and annotate a value at an address in Binary Ninja.", {
|
|
731
|
+
address: z.string().describe("Address (hex like 0x401000)"),
|
|
732
|
+
text: z.string().describe("Text to convert"),
|
|
733
|
+
size: z.number().default(0).describe("Size in bytes"),
|
|
734
|
+
}, async ({ address, text, size = 0 }) => {
|
|
735
|
+
const lines = await client.getLines("formatValue", { address, text, size });
|
|
736
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
737
|
+
});
|
|
738
|
+
server.tool("convert_number", "Convert a number or string to multiple representations (hex/dec/bin, C literals).", {
|
|
739
|
+
text: z.string().describe("Number or string to convert (decimal, hex 0x7b, binary 0b1111011, char 'A', or string)"),
|
|
740
|
+
size: z.number().default(0).describe("Size in bytes (0 for auto)"),
|
|
741
|
+
}, async ({ text, size = 0 }) => {
|
|
742
|
+
const data = await client.getJson("convertNumber", { text, size });
|
|
743
|
+
if (!data) {
|
|
744
|
+
return { content: [{ type: "text", text: "Error: no response" }] };
|
|
745
|
+
}
|
|
746
|
+
if ("error" in data) {
|
|
747
|
+
return { content: [{ type: "text", text: `Error: ${String(data.error)}` }] };
|
|
748
|
+
}
|
|
749
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
//# sourceMappingURL=tools.js.map
|