@ebowwa/mcp-nm 2.0.2 → 2.1.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/dist/index.js +1139 -1
- package/package.json +8 -3
- package/src/handlers/bin.ts +558 -0
- package/src/handlers/nm.ts +368 -0
- package/src/handlers/xxd.ts +304 -0
- package/src/index.ts +1452 -1
- package/src/types/index.ts +109 -0
- package/src/utils/convert.ts +166 -0
- package/src/utils/exec.ts +69 -0
- package/src/utils/file.ts +108 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/nm.ts +152 -0
- package/src/utils/xxd.ts +173 -0
package/dist/index.js
CHANGED
|
@@ -13640,10 +13640,13 @@ class StdioServerTransport {
|
|
|
13640
13640
|
// src/index.ts
|
|
13641
13641
|
import { exec } from "child_process";
|
|
13642
13642
|
import { promisify } from "util";
|
|
13643
|
+
import * as fs from "fs";
|
|
13644
|
+
import * as path from "path";
|
|
13645
|
+
import * as os from "os";
|
|
13643
13646
|
var execAsync = promisify(exec);
|
|
13644
13647
|
var server = new Server({
|
|
13645
13648
|
name: "@ebowwa/mcp-nm",
|
|
13646
|
-
version: "2.
|
|
13649
|
+
version: "2.1.0"
|
|
13647
13650
|
}, {
|
|
13648
13651
|
capabilities: {
|
|
13649
13652
|
tools: {}
|
|
@@ -14977,6 +14980,953 @@ async function handleHexEditor(args) {
|
|
|
14977
14980
|
throw new Error(`Hex editor failed: ${error2 instanceof Error ? error2.message : error2}`);
|
|
14978
14981
|
}
|
|
14979
14982
|
}
|
|
14983
|
+
function isMacOS() {
|
|
14984
|
+
return process.platform === "darwin";
|
|
14985
|
+
}
|
|
14986
|
+
async function handleCodesignInfo(args) {
|
|
14987
|
+
if (!isMacOS()) {
|
|
14988
|
+
return {
|
|
14989
|
+
content: [{
|
|
14990
|
+
type: "text",
|
|
14991
|
+
text: "Error: Code signing tools are only available on macOS. This tool requires the 'codesign' utility."
|
|
14992
|
+
}],
|
|
14993
|
+
isError: true
|
|
14994
|
+
};
|
|
14995
|
+
}
|
|
14996
|
+
try {
|
|
14997
|
+
const results = [];
|
|
14998
|
+
results.push(`Code Signature Info: ${args.filePath}`);
|
|
14999
|
+
results.push("");
|
|
15000
|
+
const { stdout: fileInfo } = await execAsync(`file "${args.filePath}"`);
|
|
15001
|
+
if (!fileInfo.toLowerCase().includes("mach-o")) {
|
|
15002
|
+
results.push("Warning: File does not appear to be a Mach-O binary");
|
|
15003
|
+
results.push(`File type: ${fileInfo.trim()}`);
|
|
15004
|
+
}
|
|
15005
|
+
try {
|
|
15006
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15007
|
+
results.push("Signature Details:");
|
|
15008
|
+
results.push(signInfo.split(`
|
|
15009
|
+
`).map((line) => ` ${line}`).join(`
|
|
15010
|
+
`));
|
|
15011
|
+
} catch (e) {
|
|
15012
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15013
|
+
if (output.includes("no signature") || output.includes("code object is not signed")) {
|
|
15014
|
+
results.push("Status: NOT SIGNED");
|
|
15015
|
+
results.push(" The binary has no code signature");
|
|
15016
|
+
} else {
|
|
15017
|
+
results.push(`Signature check: ${output}`);
|
|
15018
|
+
}
|
|
15019
|
+
}
|
|
15020
|
+
try {
|
|
15021
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15022
|
+
results.push("");
|
|
15023
|
+
results.push("Validity: VALID - Signature is intact");
|
|
15024
|
+
} catch (e) {
|
|
15025
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15026
|
+
if (output.includes("not signed")) {
|
|
15027
|
+
results.push("");
|
|
15028
|
+
results.push("Validity: NOT SIGNED");
|
|
15029
|
+
} else {
|
|
15030
|
+
results.push("");
|
|
15031
|
+
results.push(`Validity: INVALID - ${output}`);
|
|
15032
|
+
}
|
|
15033
|
+
}
|
|
15034
|
+
try {
|
|
15035
|
+
const { stdout: reqs } = await execAsync(`codesign -dr - "${args.filePath}" 2>&1`);
|
|
15036
|
+
if (reqs.trim() && !reqs.includes("no signature")) {
|
|
15037
|
+
results.push("");
|
|
15038
|
+
results.push("Designated Requirements:");
|
|
15039
|
+
results.push(reqs.split(`
|
|
15040
|
+
`).map((line) => ` ${line}`).join(`
|
|
15041
|
+
`));
|
|
15042
|
+
}
|
|
15043
|
+
} catch {}
|
|
15044
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15045
|
+
`) }] };
|
|
15046
|
+
} catch (error2) {
|
|
15047
|
+
return {
|
|
15048
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15049
|
+
isError: true
|
|
15050
|
+
};
|
|
15051
|
+
}
|
|
15052
|
+
}
|
|
15053
|
+
async function handleCodesignRemove(args) {
|
|
15054
|
+
if (!isMacOS()) {
|
|
15055
|
+
return {
|
|
15056
|
+
content: [{
|
|
15057
|
+
type: "text",
|
|
15058
|
+
text: "Error: Code signing tools are only available on macOS."
|
|
15059
|
+
}],
|
|
15060
|
+
isError: true
|
|
15061
|
+
};
|
|
15062
|
+
}
|
|
15063
|
+
const createBackup = args.createBackup !== false;
|
|
15064
|
+
try {
|
|
15065
|
+
const results = [];
|
|
15066
|
+
results.push(`Remove Code Signature: ${args.filePath}`);
|
|
15067
|
+
results.push("");
|
|
15068
|
+
if (createBackup) {
|
|
15069
|
+
const backupPath = `${args.filePath}.bak`;
|
|
15070
|
+
await execAsync(`cp "${args.filePath}" "${backupPath}"`);
|
|
15071
|
+
results.push(`Backup created: ${backupPath}`);
|
|
15072
|
+
}
|
|
15073
|
+
try {
|
|
15074
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15075
|
+
results.push(`Previous signature: ${signInfo.split(`
|
|
15076
|
+
`)[0] || "signed"}`);
|
|
15077
|
+
} catch {
|
|
15078
|
+
results.push("Previous signature: not signed or invalid");
|
|
15079
|
+
}
|
|
15080
|
+
try {
|
|
15081
|
+
await execAsync(`codesign --remove-signature "${args.filePath}"`);
|
|
15082
|
+
results.push("Status: SUCCESS - Code signature removed");
|
|
15083
|
+
} catch (e) {
|
|
15084
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15085
|
+
if (output.includes("no signature")) {
|
|
15086
|
+
results.push("Status: NO ACTION - Binary was not signed");
|
|
15087
|
+
} else {
|
|
15088
|
+
throw new Error(`Failed to remove signature: ${output}`);
|
|
15089
|
+
}
|
|
15090
|
+
}
|
|
15091
|
+
try {
|
|
15092
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15093
|
+
results.push("Warning: Signature still present after removal");
|
|
15094
|
+
} catch (e) {
|
|
15095
|
+
if (e instanceof Error && e.message.includes("not signed")) {
|
|
15096
|
+
results.push("Verification: Confirmed - Binary is now unsigned");
|
|
15097
|
+
}
|
|
15098
|
+
}
|
|
15099
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15100
|
+
`) }] };
|
|
15101
|
+
} catch (error2) {
|
|
15102
|
+
return {
|
|
15103
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15104
|
+
isError: true
|
|
15105
|
+
};
|
|
15106
|
+
}
|
|
15107
|
+
}
|
|
15108
|
+
async function handleCodesignSign(args) {
|
|
15109
|
+
if (!isMacOS()) {
|
|
15110
|
+
return {
|
|
15111
|
+
content: [{
|
|
15112
|
+
type: "text",
|
|
15113
|
+
text: "Error: Code signing tools are only available on macOS."
|
|
15114
|
+
}],
|
|
15115
|
+
isError: true
|
|
15116
|
+
};
|
|
15117
|
+
}
|
|
15118
|
+
const force = args.force !== false;
|
|
15119
|
+
const createBackup = args.createBackup === true;
|
|
15120
|
+
const deep = args.deep === true;
|
|
15121
|
+
const certificate = args.certificate || "-";
|
|
15122
|
+
try {
|
|
15123
|
+
const results = [];
|
|
15124
|
+
results.push(`Sign Binary: ${args.filePath}`);
|
|
15125
|
+
results.push("");
|
|
15126
|
+
if (createBackup) {
|
|
15127
|
+
const backupPath = `${args.filePath}.bak`;
|
|
15128
|
+
await execAsync(`cp "${args.filePath}" "${backupPath}"`);
|
|
15129
|
+
results.push(`Backup created: ${backupPath}`);
|
|
15130
|
+
}
|
|
15131
|
+
const forceFlag = force ? "--force" : "";
|
|
15132
|
+
const deepFlag = deep ? "--deep" : "";
|
|
15133
|
+
const optionsFlag = args.options ? `--options ${args.options}` : "";
|
|
15134
|
+
const cmd = `codesign -s ${certificate} ${forceFlag} ${deepFlag} ${optionsFlag} "${args.filePath}"`.replace(/\s+/g, " ").trim();
|
|
15135
|
+
results.push(`Command: ${cmd}`);
|
|
15136
|
+
results.push(`Signing with: ${certificate === "-" ? "ad-hoc signature" : certificate}`);
|
|
15137
|
+
await execAsync(cmd);
|
|
15138
|
+
results.push("Status: SUCCESS - Binary signed");
|
|
15139
|
+
try {
|
|
15140
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15141
|
+
results.push("Verification: VALID - Signature is intact");
|
|
15142
|
+
} catch (e) {
|
|
15143
|
+
results.push(`Verification: WARNING - ${e instanceof Error ? e.message : String(e)}`);
|
|
15144
|
+
}
|
|
15145
|
+
try {
|
|
15146
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15147
|
+
const infoLine = signInfo.split(`
|
|
15148
|
+
`).find((l) => l.includes("Identifier") || l.includes("Authority"));
|
|
15149
|
+
if (infoLine) {
|
|
15150
|
+
results.push(`New signature: ${infoLine.trim()}`);
|
|
15151
|
+
}
|
|
15152
|
+
} catch {}
|
|
15153
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15154
|
+
`) }] };
|
|
15155
|
+
} catch (error2) {
|
|
15156
|
+
return {
|
|
15157
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15158
|
+
isError: true
|
|
15159
|
+
};
|
|
15160
|
+
}
|
|
15161
|
+
}
|
|
15162
|
+
async function handleQuarantineCheck(args) {
|
|
15163
|
+
if (!isMacOS()) {
|
|
15164
|
+
return {
|
|
15165
|
+
content: [{
|
|
15166
|
+
type: "text",
|
|
15167
|
+
text: "Error: Quarantine tools are only available on macOS."
|
|
15168
|
+
}],
|
|
15169
|
+
isError: true
|
|
15170
|
+
};
|
|
15171
|
+
}
|
|
15172
|
+
try {
|
|
15173
|
+
const results = [];
|
|
15174
|
+
results.push(`Quarantine Check: ${args.filePath}`);
|
|
15175
|
+
results.push("");
|
|
15176
|
+
try {
|
|
15177
|
+
const { stdout: quarantine } = await execAsync(`xattr -p com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15178
|
+
if (quarantine.trim()) {
|
|
15179
|
+
results.push("Status: QUARANTINED");
|
|
15180
|
+
results.push("");
|
|
15181
|
+
results.push("Quarantine data:");
|
|
15182
|
+
results.push(` ${quarantine.trim()}`);
|
|
15183
|
+
try {
|
|
15184
|
+
const { stdout: details } = await execAsync(`xattr -l "${args.filePath}" 2>&1`);
|
|
15185
|
+
const quarantineSection = details.split(`
|
|
15186
|
+
`).filter((l) => l.includes("quarantine") || l.includes("provenance"));
|
|
15187
|
+
if (quarantineSection.length > 0) {
|
|
15188
|
+
results.push("");
|
|
15189
|
+
results.push("Extended attributes:");
|
|
15190
|
+
quarantineSection.forEach((l) => results.push(` ${l}`));
|
|
15191
|
+
}
|
|
15192
|
+
} catch {}
|
|
15193
|
+
results.push("");
|
|
15194
|
+
results.push("Note: Quarantine prevents execution of downloaded files.");
|
|
15195
|
+
results.push("Use bin_quarantine_remove to allow execution.");
|
|
15196
|
+
} else {
|
|
15197
|
+
results.push("Status: NOT QUARANTINED");
|
|
15198
|
+
results.push("The file has no quarantine attribute.");
|
|
15199
|
+
}
|
|
15200
|
+
} catch (e) {
|
|
15201
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15202
|
+
if (output.includes("No such xattr") || output.includes("not found")) {
|
|
15203
|
+
results.push("Status: NOT QUARANTINED");
|
|
15204
|
+
results.push("The file has no quarantine attribute.");
|
|
15205
|
+
} else {
|
|
15206
|
+
throw new Error(`Failed to check quarantine: ${output}`);
|
|
15207
|
+
}
|
|
15208
|
+
}
|
|
15209
|
+
try {
|
|
15210
|
+
const { stdout: provenance } = await execAsync(`xattr -p com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15211
|
+
if (provenance.trim()) {
|
|
15212
|
+
results.push("");
|
|
15213
|
+
results.push("Provenance: PRESENT");
|
|
15214
|
+
results.push(" File has provenance metadata (tracks download source)");
|
|
15215
|
+
}
|
|
15216
|
+
} catch {}
|
|
15217
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15218
|
+
`) }] };
|
|
15219
|
+
} catch (error2) {
|
|
15220
|
+
return {
|
|
15221
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15222
|
+
isError: true
|
|
15223
|
+
};
|
|
15224
|
+
}
|
|
15225
|
+
}
|
|
15226
|
+
async function handleQuarantineRemove(args) {
|
|
15227
|
+
if (!isMacOS()) {
|
|
15228
|
+
return {
|
|
15229
|
+
content: [{
|
|
15230
|
+
type: "text",
|
|
15231
|
+
text: "Error: Quarantine tools are only available on macOS."
|
|
15232
|
+
}],
|
|
15233
|
+
isError: true
|
|
15234
|
+
};
|
|
15235
|
+
}
|
|
15236
|
+
try {
|
|
15237
|
+
const results = [];
|
|
15238
|
+
results.push(`Remove Quarantine: ${args.filePath}`);
|
|
15239
|
+
results.push("");
|
|
15240
|
+
let removedAny = false;
|
|
15241
|
+
try {
|
|
15242
|
+
await execAsync(`xattr -d com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15243
|
+
results.push("Removed: com.apple.quarantine");
|
|
15244
|
+
removedAny = true;
|
|
15245
|
+
} catch (e) {
|
|
15246
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15247
|
+
if (output.includes("No such xattr") || output.includes("not found")) {
|
|
15248
|
+
results.push("Skipped: com.apple.quarantine (not present)");
|
|
15249
|
+
} else {
|
|
15250
|
+
results.push(`Warning: Could not remove quarantine - ${output}`);
|
|
15251
|
+
}
|
|
15252
|
+
}
|
|
15253
|
+
try {
|
|
15254
|
+
await execAsync(`xattr -d com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15255
|
+
results.push("Removed: com.apple.provenance");
|
|
15256
|
+
removedAny = true;
|
|
15257
|
+
} catch {}
|
|
15258
|
+
try {
|
|
15259
|
+
await execAsync(`xattr -d com.apple.metadata:kMDItemWhereFroms "${args.filePath}" 2>&1`);
|
|
15260
|
+
results.push("Removed: com.apple.metadata:kMDItemWhereFroms");
|
|
15261
|
+
removedAny = true;
|
|
15262
|
+
} catch {}
|
|
15263
|
+
if (removedAny) {
|
|
15264
|
+
results.push("");
|
|
15265
|
+
results.push("Status: SUCCESS - Quarantine attributes removed");
|
|
15266
|
+
results.push("The file should now be able to execute without quarantine warnings.");
|
|
15267
|
+
} else {
|
|
15268
|
+
results.push("");
|
|
15269
|
+
results.push("Status: NO ACTION - No quarantine attributes found");
|
|
15270
|
+
}
|
|
15271
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15272
|
+
`) }] };
|
|
15273
|
+
} catch (error2) {
|
|
15274
|
+
return {
|
|
15275
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15276
|
+
isError: true
|
|
15277
|
+
};
|
|
15278
|
+
}
|
|
15279
|
+
}
|
|
15280
|
+
async function handleSafePatch(args) {
|
|
15281
|
+
if (!isMacOS()) {
|
|
15282
|
+
return {
|
|
15283
|
+
content: [{
|
|
15284
|
+
type: "text",
|
|
15285
|
+
text: "Error: Safe patching tools are only available on macOS."
|
|
15286
|
+
}],
|
|
15287
|
+
isError: true
|
|
15288
|
+
};
|
|
15289
|
+
}
|
|
15290
|
+
const createBackup = args.createBackup !== false;
|
|
15291
|
+
try {
|
|
15292
|
+
const results = [];
|
|
15293
|
+
results.push(`Safe Patch Workflow: ${args.filePath}`);
|
|
15294
|
+
results.push(` Offset: 0x${args.offset.toString(16)} (${args.offset})`);
|
|
15295
|
+
results.push(` Data: ${args.hexData}`);
|
|
15296
|
+
results.push("");
|
|
15297
|
+
if (createBackup) {
|
|
15298
|
+
const backupPath = `${args.filePath}.bak`;
|
|
15299
|
+
await execAsync(`cp "${args.filePath}" "${backupPath}"`);
|
|
15300
|
+
results.push(`[1/4] Backup created: ${backupPath}`);
|
|
15301
|
+
} else {
|
|
15302
|
+
results.push("[1/4] Backup: SKIPPED (createBackup=false)");
|
|
15303
|
+
}
|
|
15304
|
+
results.push("[2/4] Applying binary patch...");
|
|
15305
|
+
const hexBytes = args.hexData.replace(/\s+/g, "");
|
|
15306
|
+
const byteCount = hexBytes.length / 2;
|
|
15307
|
+
const patchCmd = `printf '${hexBytes}' | dd of="${args.filePath}" bs=1 seek=${args.offset} count=${byteCount} conv=notrunc 2>/dev/null`;
|
|
15308
|
+
await execAsync(patchCmd);
|
|
15309
|
+
results.push(` Patched ${byteCount} bytes at offset 0x${args.offset.toString(16)}`);
|
|
15310
|
+
results.push("[3/4] Removing old code signature...");
|
|
15311
|
+
try {
|
|
15312
|
+
await execAsync(`codesign --remove-signature "${args.filePath}" 2>&1`);
|
|
15313
|
+
results.push(" Old signature removed");
|
|
15314
|
+
} catch (e) {
|
|
15315
|
+
if (e instanceof Error && e.message.includes("no signature")) {
|
|
15316
|
+
results.push(" No previous signature to remove");
|
|
15317
|
+
} else {
|
|
15318
|
+
results.push(` Warning: ${e instanceof Error ? e.message : String(e)}`);
|
|
15319
|
+
}
|
|
15320
|
+
}
|
|
15321
|
+
results.push("[4/4] Applying ad-hoc signature...");
|
|
15322
|
+
await execAsync(`codesign --force --sign - "${args.filePath}"`);
|
|
15323
|
+
results.push(" Ad-hoc signature applied");
|
|
15324
|
+
results.push("[5/5] Removing quarantine attributes...");
|
|
15325
|
+
try {
|
|
15326
|
+
await execAsync(`xattr -d com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15327
|
+
results.push(" Quarantine attribute removed");
|
|
15328
|
+
} catch {
|
|
15329
|
+
results.push(" No quarantine attribute present");
|
|
15330
|
+
}
|
|
15331
|
+
try {
|
|
15332
|
+
await execAsync(`xattr -d com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15333
|
+
results.push(" Provenance attribute removed");
|
|
15334
|
+
} catch {}
|
|
15335
|
+
results.push("");
|
|
15336
|
+
results.push("Verification:");
|
|
15337
|
+
try {
|
|
15338
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15339
|
+
results.push(" Code signature: VALID");
|
|
15340
|
+
} catch (e) {
|
|
15341
|
+
results.push(` Code signature: WARNING - ${e instanceof Error ? e.message : String(e)}`);
|
|
15342
|
+
}
|
|
15343
|
+
try {
|
|
15344
|
+
const { stdout: fileInfo } = await execAsync(`file "${args.filePath}"`);
|
|
15345
|
+
if (fileInfo.includes("executable")) {
|
|
15346
|
+
results.push(" File type: Executable");
|
|
15347
|
+
}
|
|
15348
|
+
} catch {}
|
|
15349
|
+
results.push("");
|
|
15350
|
+
results.push("Status: SUCCESS - Binary patched and ready to run");
|
|
15351
|
+
results.push("Note: The binary is now signed with an ad-hoc signature.");
|
|
15352
|
+
results.push(" Some apps may require proper code signing for full functionality.");
|
|
15353
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15354
|
+
`) }] };
|
|
15355
|
+
} catch (error2) {
|
|
15356
|
+
return {
|
|
15357
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15358
|
+
isError: true
|
|
15359
|
+
};
|
|
15360
|
+
}
|
|
15361
|
+
}
|
|
15362
|
+
async function handleBinVerify(args) {
|
|
15363
|
+
if (!isMacOS()) {
|
|
15364
|
+
return {
|
|
15365
|
+
content: [{
|
|
15366
|
+
type: "text",
|
|
15367
|
+
text: "Error: Binary verification tools are only available on macOS."
|
|
15368
|
+
}],
|
|
15369
|
+
isError: true
|
|
15370
|
+
};
|
|
15371
|
+
}
|
|
15372
|
+
try {
|
|
15373
|
+
const results = [];
|
|
15374
|
+
const issues = [];
|
|
15375
|
+
results.push(`Binary Verification: ${args.filePath}`);
|
|
15376
|
+
results.push("");
|
|
15377
|
+
let isMachO = false;
|
|
15378
|
+
try {
|
|
15379
|
+
const { stdout: fileInfo } = await execAsync(`file "${args.filePath}"`);
|
|
15380
|
+
results.push(`File type: ${fileInfo.trim()}`);
|
|
15381
|
+
isMachO = fileInfo.toLowerCase().includes("mach-o");
|
|
15382
|
+
} catch (e) {
|
|
15383
|
+
return {
|
|
15384
|
+
content: [{ type: "text", text: `Error: Cannot read file - ${e instanceof Error ? e.message : String(e)}` }],
|
|
15385
|
+
isError: true
|
|
15386
|
+
};
|
|
15387
|
+
}
|
|
15388
|
+
try {
|
|
15389
|
+
const { stdout: perms } = await execAsync(`ls -la "${args.filePath}"`);
|
|
15390
|
+
const permLine = perms.split(`
|
|
15391
|
+
`).find((l) => l.includes(args.filePath.split("/").pop() || ""));
|
|
15392
|
+
if (permLine) {
|
|
15393
|
+
results.push(`Permissions: ${permLine.split(/\s+/)[0]}`);
|
|
15394
|
+
if (!permLine.includes("x")) {
|
|
15395
|
+
issues.push("File is not executable (no execute permission)");
|
|
15396
|
+
}
|
|
15397
|
+
}
|
|
15398
|
+
} catch {
|
|
15399
|
+
results.push("Permissions: Unable to check");
|
|
15400
|
+
}
|
|
15401
|
+
results.push("");
|
|
15402
|
+
if (isMachO) {
|
|
15403
|
+
results.push("Code Signature:");
|
|
15404
|
+
try {
|
|
15405
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15406
|
+
const authority = signInfo.split(`
|
|
15407
|
+
`).find((l) => l.includes("Authority"));
|
|
15408
|
+
if (authority) {
|
|
15409
|
+
results.push(` Status: SIGNED (${authority.split(":")[1]?.trim() || "unknown authority"})`);
|
|
15410
|
+
} else {
|
|
15411
|
+
results.push(" Status: SIGNED (ad-hoc or unknown)");
|
|
15412
|
+
}
|
|
15413
|
+
} catch (e) {
|
|
15414
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15415
|
+
if (output.includes("not signed")) {
|
|
15416
|
+
results.push(" Status: UNSIGNED");
|
|
15417
|
+
issues.push("Binary is not code signed");
|
|
15418
|
+
} else {
|
|
15419
|
+
results.push(` Status: INVALID - ${output}`);
|
|
15420
|
+
issues.push("Code signature is invalid");
|
|
15421
|
+
}
|
|
15422
|
+
}
|
|
15423
|
+
try {
|
|
15424
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15425
|
+
results.push(" Validity: VALID");
|
|
15426
|
+
} catch (e) {
|
|
15427
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15428
|
+
if (!output.includes("not signed")) {
|
|
15429
|
+
results.push(` Validity: INVALID - ${output}`);
|
|
15430
|
+
issues.push("Code signature verification failed");
|
|
15431
|
+
}
|
|
15432
|
+
}
|
|
15433
|
+
}
|
|
15434
|
+
results.push("");
|
|
15435
|
+
results.push("Quarantine:");
|
|
15436
|
+
try {
|
|
15437
|
+
const { stdout: quar } = await execAsync(`xattr -p com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15438
|
+
if (quar.trim()) {
|
|
15439
|
+
results.push(" Status: QUARANTINED");
|
|
15440
|
+
issues.push("File is quarantined and may be blocked from running");
|
|
15441
|
+
} else {
|
|
15442
|
+
results.push(" Status: NOT QUARANTINED");
|
|
15443
|
+
}
|
|
15444
|
+
} catch {
|
|
15445
|
+
results.push(" Status: NOT QUARANTINED");
|
|
15446
|
+
}
|
|
15447
|
+
if (isMachO) {
|
|
15448
|
+
results.push("");
|
|
15449
|
+
results.push("Architecture:");
|
|
15450
|
+
try {
|
|
15451
|
+
const { stdout: arch } = await execAsync(`lipo -info "${args.filePath}" 2>&1`);
|
|
15452
|
+
results.push(` ${arch.trim()}`);
|
|
15453
|
+
try {
|
|
15454
|
+
const { stdout: currentArch } = await execAsync(`uname -m`);
|
|
15455
|
+
if (!arch.includes(currentArch.trim()) && !arch.includes("universal")) {
|
|
15456
|
+
issues.push(`Binary may not be compatible with current architecture (${currentArch.trim()})`);
|
|
15457
|
+
}
|
|
15458
|
+
} catch {}
|
|
15459
|
+
} catch (e) {
|
|
15460
|
+
results.push(` Unable to determine: ${e instanceof Error ? e.message : String(e)}`);
|
|
15461
|
+
}
|
|
15462
|
+
}
|
|
15463
|
+
results.push("");
|
|
15464
|
+
results.push("=== SUMMARY ===");
|
|
15465
|
+
if (issues.length === 0) {
|
|
15466
|
+
results.push("Status: READY TO RUN");
|
|
15467
|
+
results.push("The binary should execute without issues.");
|
|
15468
|
+
} else {
|
|
15469
|
+
results.push("Status: ISSUES DETECTED");
|
|
15470
|
+
results.push("");
|
|
15471
|
+
results.push("Issues:");
|
|
15472
|
+
issues.forEach((issue2, i) => results.push(` ${i + 1}. ${issue2}`));
|
|
15473
|
+
results.push("");
|
|
15474
|
+
results.push("Suggested fixes:");
|
|
15475
|
+
if (issues.some((i) => i.includes("quarantined"))) {
|
|
15476
|
+
results.push(" - Run: xattr -d com.apple.quarantine <file>");
|
|
15477
|
+
}
|
|
15478
|
+
if (issues.some((i) => i.includes("not code signed") || i.includes("signature"))) {
|
|
15479
|
+
results.push(" - Run: codesign --force --sign - <file>");
|
|
15480
|
+
}
|
|
15481
|
+
if (issues.some((i) => i.includes("not executable"))) {
|
|
15482
|
+
results.push(" - Run: chmod +x <file>");
|
|
15483
|
+
}
|
|
15484
|
+
}
|
|
15485
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15486
|
+
`) }] };
|
|
15487
|
+
} catch (error2) {
|
|
15488
|
+
return {
|
|
15489
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15490
|
+
isError: true
|
|
15491
|
+
};
|
|
15492
|
+
}
|
|
15493
|
+
}
|
|
15494
|
+
var PATCH_DIR = path.join(os.homedir(), ".claude", "patches");
|
|
15495
|
+
var MANIFEST_PATH = path.join(PATCH_DIR, "manifest.json");
|
|
15496
|
+
var BACKUPS_DIR = path.join(PATCH_DIR, "backups");
|
|
15497
|
+
var MANIFEST_VERSION = "1.0.0";
|
|
15498
|
+
function ensurePatchDir() {
|
|
15499
|
+
if (!fs.existsSync(PATCH_DIR)) {
|
|
15500
|
+
fs.mkdirSync(PATCH_DIR, { recursive: true });
|
|
15501
|
+
}
|
|
15502
|
+
if (!fs.existsSync(BACKUPS_DIR)) {
|
|
15503
|
+
fs.mkdirSync(BACKUPS_DIR, { recursive: true });
|
|
15504
|
+
}
|
|
15505
|
+
}
|
|
15506
|
+
function loadManifest() {
|
|
15507
|
+
ensurePatchDir();
|
|
15508
|
+
if (fs.existsSync(MANIFEST_PATH)) {
|
|
15509
|
+
try {
|
|
15510
|
+
const data = fs.readFileSync(MANIFEST_PATH, "utf-8");
|
|
15511
|
+
return JSON.parse(data);
|
|
15512
|
+
} catch {}
|
|
15513
|
+
}
|
|
15514
|
+
return {
|
|
15515
|
+
version: MANIFEST_VERSION,
|
|
15516
|
+
createdAt: new Date().toISOString(),
|
|
15517
|
+
updatedAt: new Date().toISOString(),
|
|
15518
|
+
binaries: {}
|
|
15519
|
+
};
|
|
15520
|
+
}
|
|
15521
|
+
function saveManifest(manifest) {
|
|
15522
|
+
ensurePatchDir();
|
|
15523
|
+
manifest.updatedAt = new Date().toISOString();
|
|
15524
|
+
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
15525
|
+
}
|
|
15526
|
+
async function getFileChecksum(filePath) {
|
|
15527
|
+
try {
|
|
15528
|
+
const { stdout } = await execAsync(`shasum -a 256 "${filePath}" | cut -d' ' -f1`);
|
|
15529
|
+
return stdout.trim();
|
|
15530
|
+
} catch {
|
|
15531
|
+
return "unknown";
|
|
15532
|
+
}
|
|
15533
|
+
}
|
|
15534
|
+
function readBytesAtOffset(filePath, offset, length) {
|
|
15535
|
+
const fd = fs.openSync(filePath, "r");
|
|
15536
|
+
const buffer = Buffer.alloc(length);
|
|
15537
|
+
fs.readSync(fd, buffer, 0, length, offset);
|
|
15538
|
+
fs.closeSync(fd);
|
|
15539
|
+
return buffer.toString("hex").match(/.{2}/g)?.join(" ").toUpperCase() || "";
|
|
15540
|
+
}
|
|
15541
|
+
function writeBytesAtOffset(filePath, offset, hexData) {
|
|
15542
|
+
const bytes = Buffer.from(hexData.replace(/\s+/g, ""), "hex");
|
|
15543
|
+
const fd = fs.openSync(filePath, "r+");
|
|
15544
|
+
fs.writeSync(fd, bytes, 0, bytes.length, offset);
|
|
15545
|
+
fs.closeSync(fd);
|
|
15546
|
+
}
|
|
15547
|
+
async function handlePatchRegister(args) {
|
|
15548
|
+
try {
|
|
15549
|
+
const results = [];
|
|
15550
|
+
results.push(`Register Patch: ${args.patchId}`);
|
|
15551
|
+
results.push(`Binary: ${args.filePath}`);
|
|
15552
|
+
results.push(`Offset: 0x${args.offset.toString(16)} (${args.offset})`);
|
|
15553
|
+
results.push("");
|
|
15554
|
+
if (!fs.existsSync(args.filePath)) {
|
|
15555
|
+
return {
|
|
15556
|
+
content: [{ type: "text", text: `Error: File not found: ${args.filePath}` }],
|
|
15557
|
+
isError: true
|
|
15558
|
+
};
|
|
15559
|
+
}
|
|
15560
|
+
const patchedBytes = args.patchedBytes.replace(/\s+/g, "").toUpperCase();
|
|
15561
|
+
const byteLength = patchedBytes.length / 2;
|
|
15562
|
+
let originalBytes = "";
|
|
15563
|
+
if (args.captureOriginal !== false) {
|
|
15564
|
+
try {
|
|
15565
|
+
originalBytes = readBytesAtOffset(args.filePath, args.offset, byteLength);
|
|
15566
|
+
results.push(`Original bytes: ${originalBytes}`);
|
|
15567
|
+
} catch (e) {
|
|
15568
|
+
return {
|
|
15569
|
+
content: [{ type: "text", text: `Error: Could not read original bytes - ${e instanceof Error ? e.message : String(e)}` }],
|
|
15570
|
+
isError: true
|
|
15571
|
+
};
|
|
15572
|
+
}
|
|
15573
|
+
}
|
|
15574
|
+
const backupFileName = `${path.basename(args.filePath)}.offset_${args.offset}.bin`;
|
|
15575
|
+
const backupPath = path.join(BACKUPS_DIR, backupFileName);
|
|
15576
|
+
if (originalBytes) {
|
|
15577
|
+
fs.writeFileSync(backupPath, Buffer.from(originalBytes.replace(/\s+/g, ""), "hex"));
|
|
15578
|
+
results.push(`Backup saved: ${backupPath}`);
|
|
15579
|
+
}
|
|
15580
|
+
const manifest = loadManifest();
|
|
15581
|
+
const checksum = await getFileChecksum(args.filePath);
|
|
15582
|
+
if (!manifest.binaries[args.filePath]) {
|
|
15583
|
+
manifest.binaries[args.filePath] = {
|
|
15584
|
+
binaryPath: args.filePath,
|
|
15585
|
+
binaryName: path.basename(args.filePath),
|
|
15586
|
+
lastPatchedChecksum: checksum,
|
|
15587
|
+
lastAppliedAt: new Date().toISOString(),
|
|
15588
|
+
patches: []
|
|
15589
|
+
};
|
|
15590
|
+
}
|
|
15591
|
+
const existingIndex = manifest.binaries[args.filePath].patches.findIndex((p) => p.id === args.patchId);
|
|
15592
|
+
const newPatch = {
|
|
15593
|
+
id: args.patchId,
|
|
15594
|
+
offset: args.offset,
|
|
15595
|
+
originalBytes: originalBytes || "UNKNOWN",
|
|
15596
|
+
patchedBytes: patchedBytes.match(/.{2}/g)?.join(" ") || patchedBytes,
|
|
15597
|
+
description: args.description,
|
|
15598
|
+
createdAt: new Date().toISOString()
|
|
15599
|
+
};
|
|
15600
|
+
if (existingIndex >= 0) {
|
|
15601
|
+
manifest.binaries[args.filePath].patches[existingIndex] = newPatch;
|
|
15602
|
+
results.push(`Updated existing patch: ${args.patchId}`);
|
|
15603
|
+
} else {
|
|
15604
|
+
manifest.binaries[args.filePath].patches.push(newPatch);
|
|
15605
|
+
results.push(`Registered new patch: ${args.patchId}`);
|
|
15606
|
+
}
|
|
15607
|
+
saveManifest(manifest);
|
|
15608
|
+
results.push("");
|
|
15609
|
+
results.push("Patch registered successfully!");
|
|
15610
|
+
results.push(`Manifest: ${MANIFEST_PATH}`);
|
|
15611
|
+
results.push("");
|
|
15612
|
+
results.push("To apply this patch, use: bin_patch_apply");
|
|
15613
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15614
|
+
`) }] };
|
|
15615
|
+
} catch (error2) {
|
|
15616
|
+
return {
|
|
15617
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15618
|
+
isError: true
|
|
15619
|
+
};
|
|
15620
|
+
}
|
|
15621
|
+
}
|
|
15622
|
+
async function handlePatchApply(args) {
|
|
15623
|
+
const isMac = isMacOS();
|
|
15624
|
+
const resign = args.resign !== false && isMac;
|
|
15625
|
+
const removeQuarantine = args.removeQuarantine !== false && isMac;
|
|
15626
|
+
try {
|
|
15627
|
+
const results = [];
|
|
15628
|
+
results.push(`Apply Patches: ${args.filePath}`);
|
|
15629
|
+
results.push("");
|
|
15630
|
+
const manifest = loadManifest();
|
|
15631
|
+
const record3 = manifest.binaries[args.filePath];
|
|
15632
|
+
if (!record3 || record3.patches.length === 0) {
|
|
15633
|
+
results.push("No registered patches found for this binary.");
|
|
15634
|
+
results.push("");
|
|
15635
|
+
results.push("To register a patch, use: bin_patch_register");
|
|
15636
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15637
|
+
`) }] };
|
|
15638
|
+
}
|
|
15639
|
+
const currentChecksum = await getFileChecksum(args.filePath);
|
|
15640
|
+
results.push(`Current checksum: ${currentChecksum.substring(0, 16)}...`);
|
|
15641
|
+
results.push(`Last patched checksum: ${record3.lastPatchedChecksum.substring(0, 16)}...`);
|
|
15642
|
+
if (currentChecksum === record3.lastPatchedChecksum) {
|
|
15643
|
+
results.push("Status: Binary appears to already have patches applied.");
|
|
15644
|
+
results.push("Use force=true to re-apply anyway.");
|
|
15645
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15646
|
+
`) }] };
|
|
15647
|
+
}
|
|
15648
|
+
results.push("Binary has been updated - applying patches...");
|
|
15649
|
+
results.push("");
|
|
15650
|
+
const backupPath = `${args.filePath}.prepatch`;
|
|
15651
|
+
fs.copyFileSync(args.filePath, backupPath);
|
|
15652
|
+
results.push(`Backup: ${backupPath}`);
|
|
15653
|
+
let appliedCount = 0;
|
|
15654
|
+
for (const patch of record3.patches) {
|
|
15655
|
+
try {
|
|
15656
|
+
results.push(`Applying: ${patch.id}`);
|
|
15657
|
+
results.push(` Offset: 0x${patch.offset.toString(16)}`);
|
|
15658
|
+
results.push(` Bytes: ${patch.patchedBytes}`);
|
|
15659
|
+
writeBytesAtOffset(args.filePath, patch.offset, patch.patchedBytes);
|
|
15660
|
+
appliedCount++;
|
|
15661
|
+
results.push(" Status: Applied");
|
|
15662
|
+
} catch (e) {
|
|
15663
|
+
results.push(` Status: FAILED - ${e instanceof Error ? e.message : String(e)}`);
|
|
15664
|
+
}
|
|
15665
|
+
}
|
|
15666
|
+
if (isMac && appliedCount > 0) {
|
|
15667
|
+
results.push("");
|
|
15668
|
+
if (resign) {
|
|
15669
|
+
results.push("Re-signing binary...");
|
|
15670
|
+
try {
|
|
15671
|
+
await execAsync(`codesign --remove-signature "${args.filePath}" 2>&1`);
|
|
15672
|
+
await execAsync(`codesign --force --sign - "${args.filePath}"`);
|
|
15673
|
+
results.push(" Status: Re-signed with ad-hoc signature");
|
|
15674
|
+
} catch (e) {
|
|
15675
|
+
results.push(` Warning: ${e instanceof Error ? e.message : String(e)}`);
|
|
15676
|
+
}
|
|
15677
|
+
}
|
|
15678
|
+
if (removeQuarantine) {
|
|
15679
|
+
results.push("Removing quarantine...");
|
|
15680
|
+
try {
|
|
15681
|
+
await execAsync(`xattr -d com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15682
|
+
await execAsync(`xattr -d com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15683
|
+
results.push(" Status: Quarantine attributes removed");
|
|
15684
|
+
} catch {
|
|
15685
|
+
results.push(" Status: No quarantine to remove");
|
|
15686
|
+
}
|
|
15687
|
+
}
|
|
15688
|
+
}
|
|
15689
|
+
const newChecksum = await getFileChecksum(args.filePath);
|
|
15690
|
+
record3.lastPatchedChecksum = newChecksum;
|
|
15691
|
+
record3.lastAppliedAt = new Date().toISOString();
|
|
15692
|
+
saveManifest(manifest);
|
|
15693
|
+
results.push("");
|
|
15694
|
+
results.push(`=== SUMMARY ===`);
|
|
15695
|
+
results.push(`Patches applied: ${appliedCount}/${record3.patches.length}`);
|
|
15696
|
+
results.push(`New checksum: ${newChecksum.substring(0, 16)}...`);
|
|
15697
|
+
results.push("Binary is ready to use.");
|
|
15698
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15699
|
+
`) }] };
|
|
15700
|
+
} catch (error2) {
|
|
15701
|
+
return {
|
|
15702
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15703
|
+
isError: true
|
|
15704
|
+
};
|
|
15705
|
+
}
|
|
15706
|
+
}
|
|
15707
|
+
async function handlePatchVerify(args) {
|
|
15708
|
+
try {
|
|
15709
|
+
const results = [];
|
|
15710
|
+
results.push(`Verify Patches: ${args.filePath}`);
|
|
15711
|
+
results.push("");
|
|
15712
|
+
const manifest = loadManifest();
|
|
15713
|
+
const record3 = manifest.binaries[args.filePath];
|
|
15714
|
+
if (!record3 || record3.patches.length === 0) {
|
|
15715
|
+
results.push("No registered patches found for this binary.");
|
|
15716
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15717
|
+
`) }] };
|
|
15718
|
+
}
|
|
15719
|
+
const currentChecksum = await getFileChecksum(args.filePath);
|
|
15720
|
+
results.push(`Current checksum: ${currentChecksum.substring(0, 16)}...`);
|
|
15721
|
+
results.push(`Expected checksum: ${record3.lastPatchedChecksum.substring(0, 16)}...`);
|
|
15722
|
+
results.push("");
|
|
15723
|
+
if (currentChecksum !== record3.lastPatchedChecksum) {
|
|
15724
|
+
results.push("⚠️ Binary has been UPDATED since last patch.");
|
|
15725
|
+
results.push(" Run bin_patch_apply to re-apply patches.");
|
|
15726
|
+
results.push("");
|
|
15727
|
+
} else {
|
|
15728
|
+
results.push("✓ Binary checksum matches patched version.");
|
|
15729
|
+
results.push("");
|
|
15730
|
+
}
|
|
15731
|
+
results.push("Patch Status:");
|
|
15732
|
+
let allApplied = true;
|
|
15733
|
+
for (const patch of record3.patches) {
|
|
15734
|
+
try {
|
|
15735
|
+
const byteLength = patch.patchedBytes.replace(/\s+/g, "").length / 2;
|
|
15736
|
+
const currentBytes = readBytesAtOffset(args.filePath, patch.offset, byteLength);
|
|
15737
|
+
const normalizedCurrent = currentBytes.replace(/\s+/g, "").toUpperCase();
|
|
15738
|
+
const normalizedPatched = patch.patchedBytes.replace(/\s+/g, "").toUpperCase();
|
|
15739
|
+
if (normalizedCurrent === normalizedPatched) {
|
|
15740
|
+
results.push(` ✓ ${patch.id}: APPLIED`);
|
|
15741
|
+
} else if (normalizedCurrent === patch.originalBytes.replace(/\s+/g, "").toUpperCase()) {
|
|
15742
|
+
results.push(` ✗ ${patch.id}: NOT APPLIED (original bytes present)`);
|
|
15743
|
+
allApplied = false;
|
|
15744
|
+
} else {
|
|
15745
|
+
results.push(` ? ${patch.id}: UNKNOWN (bytes don't match expected)`);
|
|
15746
|
+
results.push(` Current: ${currentBytes}`);
|
|
15747
|
+
results.push(` Expected: ${patch.patchedBytes}`);
|
|
15748
|
+
allApplied = false;
|
|
15749
|
+
}
|
|
15750
|
+
} catch (e) {
|
|
15751
|
+
results.push(` ✗ ${patch.id}: ERROR - ${e instanceof Error ? e.message : String(e)}`);
|
|
15752
|
+
allApplied = false;
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
15755
|
+
results.push("");
|
|
15756
|
+
if (allApplied && currentChecksum === record3.lastPatchedChecksum) {
|
|
15757
|
+
results.push("Status: All patches verified and applied.");
|
|
15758
|
+
} else if (!allApplied) {
|
|
15759
|
+
results.push("Status: Some patches need to be applied.");
|
|
15760
|
+
results.push("Action: Run bin_patch_apply");
|
|
15761
|
+
} else {
|
|
15762
|
+
results.push("Status: Binary may have been updated. Re-apply patches.");
|
|
15763
|
+
results.push("Action: Run bin_patch_apply");
|
|
15764
|
+
}
|
|
15765
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15766
|
+
`) }] };
|
|
15767
|
+
} catch (error2) {
|
|
15768
|
+
return {
|
|
15769
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15770
|
+
isError: true
|
|
15771
|
+
};
|
|
15772
|
+
}
|
|
15773
|
+
}
|
|
15774
|
+
async function handlePatchRestore(args) {
|
|
15775
|
+
const isMac = isMacOS();
|
|
15776
|
+
const resign = args.resign !== false && isMac;
|
|
15777
|
+
try {
|
|
15778
|
+
const results = [];
|
|
15779
|
+
results.push(`Restore Patches: ${args.filePath}`);
|
|
15780
|
+
results.push("");
|
|
15781
|
+
const manifest = loadManifest();
|
|
15782
|
+
const record3 = manifest.binaries[args.filePath];
|
|
15783
|
+
if (!record3 || record3.patches.length === 0) {
|
|
15784
|
+
results.push("No registered patches found for this binary.");
|
|
15785
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15786
|
+
`) }] };
|
|
15787
|
+
}
|
|
15788
|
+
const patchesToRestore = args.patchId ? record3.patches.filter((p) => p.id === args.patchId) : record3.patches;
|
|
15789
|
+
if (patchesToRestore.length === 0) {
|
|
15790
|
+
results.push(`No patch found with ID: ${args.patchId}`);
|
|
15791
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15792
|
+
`) }] };
|
|
15793
|
+
}
|
|
15794
|
+
const backupPath = `${args.filePath}.prerestore`;
|
|
15795
|
+
fs.copyFileSync(args.filePath, backupPath);
|
|
15796
|
+
results.push(`Backup: ${backupPath}`);
|
|
15797
|
+
results.push("");
|
|
15798
|
+
let restoredCount = 0;
|
|
15799
|
+
for (const patch of patchesToRestore) {
|
|
15800
|
+
if (patch.originalBytes === "UNKNOWN") {
|
|
15801
|
+
results.push(`${patch.id}: SKIPPED (no original bytes captured)`);
|
|
15802
|
+
continue;
|
|
15803
|
+
}
|
|
15804
|
+
try {
|
|
15805
|
+
results.push(`Restoring: ${patch.id}`);
|
|
15806
|
+
writeBytesAtOffset(args.filePath, patch.offset, patch.originalBytes);
|
|
15807
|
+
results.push(` Offset: 0x${patch.offset.toString(16)}`);
|
|
15808
|
+
results.push(` Bytes: ${patch.originalBytes}`);
|
|
15809
|
+
results.push(" Status: Restored");
|
|
15810
|
+
restoredCount++;
|
|
15811
|
+
} catch (e) {
|
|
15812
|
+
results.push(` Status: FAILED - ${e instanceof Error ? e.message : String(e)}`);
|
|
15813
|
+
}
|
|
15814
|
+
}
|
|
15815
|
+
if (isMac && restoredCount > 0 && resign) {
|
|
15816
|
+
results.push("");
|
|
15817
|
+
results.push("Re-signing binary...");
|
|
15818
|
+
try {
|
|
15819
|
+
await execAsync(`codesign --remove-signature "${args.filePath}" 2>&1`);
|
|
15820
|
+
await execAsync(`codesign --force --sign - "${args.filePath}"`);
|
|
15821
|
+
results.push(" Status: Re-signed with ad-hoc signature");
|
|
15822
|
+
} catch (e) {
|
|
15823
|
+
results.push(` Warning: ${e instanceof Error ? e.message : String(e)}`);
|
|
15824
|
+
}
|
|
15825
|
+
}
|
|
15826
|
+
const newChecksum = await getFileChecksum(args.filePath);
|
|
15827
|
+
record3.lastPatchedChecksum = newChecksum;
|
|
15828
|
+
saveManifest(manifest);
|
|
15829
|
+
results.push("");
|
|
15830
|
+
results.push(`Restored: ${restoredCount}/${patchesToRestore.length} patches`);
|
|
15831
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15832
|
+
`) }] };
|
|
15833
|
+
} catch (error2) {
|
|
15834
|
+
return {
|
|
15835
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15836
|
+
isError: true
|
|
15837
|
+
};
|
|
15838
|
+
}
|
|
15839
|
+
}
|
|
15840
|
+
async function handlePatchList(args) {
|
|
15841
|
+
try {
|
|
15842
|
+
const results = [];
|
|
15843
|
+
results.push("Patch Registry");
|
|
15844
|
+
results.push(`Manifest: ${MANIFEST_PATH}`);
|
|
15845
|
+
results.push("");
|
|
15846
|
+
const manifest = loadManifest();
|
|
15847
|
+
if (Object.keys(manifest.binaries).length === 0) {
|
|
15848
|
+
results.push("No patches registered.");
|
|
15849
|
+
results.push("");
|
|
15850
|
+
results.push("To register a patch, use: bin_patch_register");
|
|
15851
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15852
|
+
`) }] };
|
|
15853
|
+
}
|
|
15854
|
+
const binariesToShow = args.filePath ? { [args.filePath]: manifest.binaries[args.filePath] } : manifest.binaries;
|
|
15855
|
+
for (const [binaryPath, record3] of Object.entries(binariesToShow)) {
|
|
15856
|
+
if (!record3)
|
|
15857
|
+
continue;
|
|
15858
|
+
results.push(`═══════════════════════════════════════════════════════════`);
|
|
15859
|
+
results.push(`Binary: ${binaryPath}`);
|
|
15860
|
+
results.push(`Name: ${record3.binaryName}`);
|
|
15861
|
+
results.push(`Patches: ${record3.patches.length}`);
|
|
15862
|
+
results.push(`Last Applied: ${record3.lastAppliedAt}`);
|
|
15863
|
+
results.push(`Checksum: ${record3.lastPatchedChecksum.substring(0, 16)}...`);
|
|
15864
|
+
results.push("");
|
|
15865
|
+
if (record3.patches.length > 0) {
|
|
15866
|
+
results.push("Registered Patches:");
|
|
15867
|
+
for (const patch of record3.patches) {
|
|
15868
|
+
results.push(` ┌─ ${patch.id} ─────────────────────────────────`);
|
|
15869
|
+
results.push(` │ Offset: 0x${patch.offset.toString(16)} (${patch.offset})`);
|
|
15870
|
+
results.push(` │ Original: ${patch.originalBytes}`);
|
|
15871
|
+
results.push(` │ Patched: ${patch.patchedBytes}`);
|
|
15872
|
+
results.push(` │ Description: ${patch.description}`);
|
|
15873
|
+
results.push(` │ Created: ${patch.createdAt}`);
|
|
15874
|
+
results.push(` └────────────────────────────────────────────`);
|
|
15875
|
+
}
|
|
15876
|
+
}
|
|
15877
|
+
results.push("");
|
|
15878
|
+
}
|
|
15879
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15880
|
+
`) }] };
|
|
15881
|
+
} catch (error2) {
|
|
15882
|
+
return {
|
|
15883
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15884
|
+
isError: true
|
|
15885
|
+
};
|
|
15886
|
+
}
|
|
15887
|
+
}
|
|
15888
|
+
async function handlePatchRemove(args) {
|
|
15889
|
+
try {
|
|
15890
|
+
const results = [];
|
|
15891
|
+
results.push(`Remove Patch: ${args.patchId}`);
|
|
15892
|
+
results.push("");
|
|
15893
|
+
const manifest = loadManifest();
|
|
15894
|
+
const record3 = manifest.binaries[args.filePath];
|
|
15895
|
+
if (!record3) {
|
|
15896
|
+
results.push("No patches registered for this binary.");
|
|
15897
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15898
|
+
`) }] };
|
|
15899
|
+
}
|
|
15900
|
+
const index = record3.patches.findIndex((p) => p.id === args.patchId);
|
|
15901
|
+
if (index < 0) {
|
|
15902
|
+
results.push(`Patch not found: ${args.patchId}`);
|
|
15903
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15904
|
+
`) }] };
|
|
15905
|
+
}
|
|
15906
|
+
const removed = record3.patches.splice(index, 1)[0];
|
|
15907
|
+
results.push("Removed patch:");
|
|
15908
|
+
results.push(` ID: ${removed.id}`);
|
|
15909
|
+
results.push(` Offset: 0x${removed.offset.toString(16)}`);
|
|
15910
|
+
results.push(` Description: ${removed.description}`);
|
|
15911
|
+
if (record3.patches.length === 0) {
|
|
15912
|
+
delete manifest.binaries[args.filePath];
|
|
15913
|
+
results.push("");
|
|
15914
|
+
results.push("No more patches for this binary - removed from registry.");
|
|
15915
|
+
}
|
|
15916
|
+
saveManifest(manifest);
|
|
15917
|
+
results.push("");
|
|
15918
|
+
results.push("Patch removed from registry.");
|
|
15919
|
+
results.push("Note: This does NOT undo the patch in the binary.");
|
|
15920
|
+
results.push("Use bin_patch_restore to restore original bytes.");
|
|
15921
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15922
|
+
`) }] };
|
|
15923
|
+
} catch (error2) {
|
|
15924
|
+
return {
|
|
15925
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15926
|
+
isError: true
|
|
15927
|
+
};
|
|
15928
|
+
}
|
|
15929
|
+
}
|
|
14980
15930
|
var TOOLS = [
|
|
14981
15931
|
{
|
|
14982
15932
|
name: "nm_list_symbols",
|
|
@@ -15439,6 +16389,168 @@ var TOOLS = [
|
|
|
15439
16389
|
},
|
|
15440
16390
|
required: ["filePath", "offset", "length"]
|
|
15441
16391
|
}
|
|
16392
|
+
},
|
|
16393
|
+
{
|
|
16394
|
+
name: "bin_codesign_info",
|
|
16395
|
+
description: "Show code signature details for a macOS binary (macOS only)",
|
|
16396
|
+
inputSchema: {
|
|
16397
|
+
type: "object",
|
|
16398
|
+
properties: {
|
|
16399
|
+
filePath: { type: "string", description: "Path to the binary file" }
|
|
16400
|
+
},
|
|
16401
|
+
required: ["filePath"]
|
|
16402
|
+
}
|
|
16403
|
+
},
|
|
16404
|
+
{
|
|
16405
|
+
name: "bin_codesign_remove",
|
|
16406
|
+
description: "Remove code signature from a macOS binary (DESTRUCTIVE - macOS only)",
|
|
16407
|
+
inputSchema: {
|
|
16408
|
+
type: "object",
|
|
16409
|
+
properties: {
|
|
16410
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16411
|
+
createBackup: { type: "boolean", description: "Create .bak backup (default: true)" }
|
|
16412
|
+
},
|
|
16413
|
+
required: ["filePath"]
|
|
16414
|
+
}
|
|
16415
|
+
},
|
|
16416
|
+
{
|
|
16417
|
+
name: "bin_codesign_sign",
|
|
16418
|
+
description: "Apply ad-hoc or certificate signature to a macOS binary (macOS only)",
|
|
16419
|
+
inputSchema: {
|
|
16420
|
+
type: "object",
|
|
16421
|
+
properties: {
|
|
16422
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16423
|
+
certificate: { type: "string", description: "Certificate identity (default: '-' for ad-hoc signature)" },
|
|
16424
|
+
force: { type: "boolean", description: "Force replace existing signature (default: true)" },
|
|
16425
|
+
createBackup: { type: "boolean", description: "Create .bak backup before signing" },
|
|
16426
|
+
deep: { type: "boolean", description: "Sign all nested code" },
|
|
16427
|
+
options: { type: "string", description: "Additional signing options (e.g., 'runtime' for hardened runtime)" }
|
|
16428
|
+
},
|
|
16429
|
+
required: ["filePath"]
|
|
16430
|
+
}
|
|
16431
|
+
},
|
|
16432
|
+
{
|
|
16433
|
+
name: "bin_quarantine_check",
|
|
16434
|
+
description: "Check quarantine status of a file on macOS (macOS only)",
|
|
16435
|
+
inputSchema: {
|
|
16436
|
+
type: "object",
|
|
16437
|
+
properties: {
|
|
16438
|
+
filePath: { type: "string", description: "Path to the file" }
|
|
16439
|
+
},
|
|
16440
|
+
required: ["filePath"]
|
|
16441
|
+
}
|
|
16442
|
+
},
|
|
16443
|
+
{
|
|
16444
|
+
name: "bin_quarantine_remove",
|
|
16445
|
+
description: "Remove quarantine attributes from a file on macOS (DESTRUCTIVE - macOS only)",
|
|
16446
|
+
inputSchema: {
|
|
16447
|
+
type: "object",
|
|
16448
|
+
properties: {
|
|
16449
|
+
filePath: { type: "string", description: "Path to the file" }
|
|
16450
|
+
},
|
|
16451
|
+
required: ["filePath"]
|
|
16452
|
+
}
|
|
16453
|
+
},
|
|
16454
|
+
{
|
|
16455
|
+
name: "bin_safe_patch",
|
|
16456
|
+
description: "Safe binary patching workflow for macOS: patch, remove signature, re-sign, remove quarantine (DESTRUCTIVE - macOS only)",
|
|
16457
|
+
inputSchema: {
|
|
16458
|
+
type: "object",
|
|
16459
|
+
properties: {
|
|
16460
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16461
|
+
offset: { type: "number", description: "Byte offset to patch" },
|
|
16462
|
+
hexData: { type: "string", description: "Hex bytes to write (e.g., '90 90 90')" },
|
|
16463
|
+
createBackup: { type: "boolean", description: "Create .bak backup (default: true)" }
|
|
16464
|
+
},
|
|
16465
|
+
required: ["filePath", "offset", "hexData"]
|
|
16466
|
+
}
|
|
16467
|
+
},
|
|
16468
|
+
{
|
|
16469
|
+
name: "bin_verify",
|
|
16470
|
+
description: "Verify if a macOS binary will run: check signature, quarantine, architecture (macOS only)",
|
|
16471
|
+
inputSchema: {
|
|
16472
|
+
type: "object",
|
|
16473
|
+
properties: {
|
|
16474
|
+
filePath: { type: "string", description: "Path to the binary file" }
|
|
16475
|
+
},
|
|
16476
|
+
required: ["filePath"]
|
|
16477
|
+
}
|
|
16478
|
+
},
|
|
16479
|
+
{
|
|
16480
|
+
name: "bin_patch_register",
|
|
16481
|
+
description: "Register a binary patch for persistence across updates. Captures original bytes and stores in manifest.",
|
|
16482
|
+
inputSchema: {
|
|
16483
|
+
type: "object",
|
|
16484
|
+
properties: {
|
|
16485
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16486
|
+
patchId: { type: "string", description: "Unique identifier for this patch (e.g., 'skip-permissions')" },
|
|
16487
|
+
offset: { type: "number", description: "Byte offset to patch" },
|
|
16488
|
+
patchedBytes: { type: "string", description: "Hex bytes to write (e.g., '90 90 90')" },
|
|
16489
|
+
description: { type: "string", description: "Description of what this patch does" },
|
|
16490
|
+
captureOriginal: { type: "boolean", description: "Capture original bytes (default: true)" }
|
|
16491
|
+
},
|
|
16492
|
+
required: ["filePath", "patchId", "offset", "patchedBytes", "description"]
|
|
16493
|
+
}
|
|
16494
|
+
},
|
|
16495
|
+
{
|
|
16496
|
+
name: "bin_patch_apply",
|
|
16497
|
+
description: "Apply all registered patches to a binary. Handles macOS re-signing and quarantine. Detects binary updates.",
|
|
16498
|
+
inputSchema: {
|
|
16499
|
+
type: "object",
|
|
16500
|
+
properties: {
|
|
16501
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16502
|
+
resign: { type: "boolean", description: "Re-sign binary after patching (macOS, default: true)" },
|
|
16503
|
+
removeQuarantine: { type: "boolean", description: "Remove quarantine after patching (macOS, default: true)" }
|
|
16504
|
+
},
|
|
16505
|
+
required: ["filePath"]
|
|
16506
|
+
}
|
|
16507
|
+
},
|
|
16508
|
+
{
|
|
16509
|
+
name: "bin_patch_verify",
|
|
16510
|
+
description: "Verify if registered patches are currently applied to a binary. Detects if binary was updated.",
|
|
16511
|
+
inputSchema: {
|
|
16512
|
+
type: "object",
|
|
16513
|
+
properties: {
|
|
16514
|
+
filePath: { type: "string", description: "Path to the binary file" }
|
|
16515
|
+
},
|
|
16516
|
+
required: ["filePath"]
|
|
16517
|
+
}
|
|
16518
|
+
},
|
|
16519
|
+
{
|
|
16520
|
+
name: "bin_patch_restore",
|
|
16521
|
+
description: "Restore original bytes for registered patches. Undoes patches in the binary.",
|
|
16522
|
+
inputSchema: {
|
|
16523
|
+
type: "object",
|
|
16524
|
+
properties: {
|
|
16525
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16526
|
+
patchId: { type: "string", description: "Specific patch to restore (optional - restores all if omitted)" },
|
|
16527
|
+
resign: { type: "boolean", description: "Re-sign binary after restoring (macOS, default: true)" }
|
|
16528
|
+
},
|
|
16529
|
+
required: ["filePath"]
|
|
16530
|
+
}
|
|
16531
|
+
},
|
|
16532
|
+
{
|
|
16533
|
+
name: "bin_patch_list",
|
|
16534
|
+
description: "List all registered patches in the manifest, optionally filtered by binary.",
|
|
16535
|
+
inputSchema: {
|
|
16536
|
+
type: "object",
|
|
16537
|
+
properties: {
|
|
16538
|
+
filePath: { type: "string", description: "Filter by binary path (optional)" }
|
|
16539
|
+
},
|
|
16540
|
+
required: []
|
|
16541
|
+
}
|
|
16542
|
+
},
|
|
16543
|
+
{
|
|
16544
|
+
name: "bin_patch_remove",
|
|
16545
|
+
description: "Remove a patch from the registry. Does NOT undo the patch in the binary.",
|
|
16546
|
+
inputSchema: {
|
|
16547
|
+
type: "object",
|
|
16548
|
+
properties: {
|
|
16549
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16550
|
+
patchId: { type: "string", description: "ID of the patch to remove from registry" }
|
|
16551
|
+
},
|
|
16552
|
+
required: ["filePath", "patchId"]
|
|
16553
|
+
}
|
|
15442
16554
|
}
|
|
15443
16555
|
];
|
|
15444
16556
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -15516,6 +16628,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
15516
16628
|
return await handleHexEditor(args);
|
|
15517
16629
|
case "bin_convert":
|
|
15518
16630
|
return await handleConvertNumber(args);
|
|
16631
|
+
case "bin_codesign_info":
|
|
16632
|
+
return await handleCodesignInfo(args);
|
|
16633
|
+
case "bin_codesign_remove":
|
|
16634
|
+
return await handleCodesignRemove(args);
|
|
16635
|
+
case "bin_codesign_sign":
|
|
16636
|
+
return await handleCodesignSign(args);
|
|
16637
|
+
case "bin_quarantine_check":
|
|
16638
|
+
return await handleQuarantineCheck(args);
|
|
16639
|
+
case "bin_quarantine_remove":
|
|
16640
|
+
return await handleQuarantineRemove(args);
|
|
16641
|
+
case "bin_safe_patch":
|
|
16642
|
+
return await handleSafePatch(args);
|
|
16643
|
+
case "bin_verify":
|
|
16644
|
+
return await handleBinVerify(args);
|
|
16645
|
+
case "bin_patch_register":
|
|
16646
|
+
return await handlePatchRegister(args);
|
|
16647
|
+
case "bin_patch_apply":
|
|
16648
|
+
return await handlePatchApply(args);
|
|
16649
|
+
case "bin_patch_verify":
|
|
16650
|
+
return await handlePatchVerify(args);
|
|
16651
|
+
case "bin_patch_restore":
|
|
16652
|
+
return await handlePatchRestore(args);
|
|
16653
|
+
case "bin_patch_list":
|
|
16654
|
+
return await handlePatchList(args);
|
|
16655
|
+
case "bin_patch_remove":
|
|
16656
|
+
return await handlePatchRemove(args);
|
|
15519
16657
|
default:
|
|
15520
16658
|
throw new Error(`Unknown tool: ${name}`);
|
|
15521
16659
|
}
|