@ebowwa/mcp-nm 2.0.3 → 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 +528 -1
- package/package.json +5 -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 +695 -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.0
|
|
13649
|
+
version: "2.1.0"
|
|
13647
13650
|
}, {
|
|
13648
13651
|
capabilities: {
|
|
13649
13652
|
tools: {}
|
|
@@ -15488,6 +15491,442 @@ async function handleBinVerify(args) {
|
|
|
15488
15491
|
};
|
|
15489
15492
|
}
|
|
15490
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
|
+
}
|
|
15491
15930
|
var TOOLS = [
|
|
15492
15931
|
{
|
|
15493
15932
|
name: "nm_list_symbols",
|
|
@@ -16036,6 +16475,82 @@ var TOOLS = [
|
|
|
16036
16475
|
},
|
|
16037
16476
|
required: ["filePath"]
|
|
16038
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
|
+
}
|
|
16039
16554
|
}
|
|
16040
16555
|
];
|
|
16041
16556
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -16127,6 +16642,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
16127
16642
|
return await handleSafePatch(args);
|
|
16128
16643
|
case "bin_verify":
|
|
16129
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);
|
|
16130
16657
|
default:
|
|
16131
16658
|
throw new Error(`Unknown tool: ${name}`);
|
|
16132
16659
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/mcp-nm",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Comprehensive binary analysis MCP server - symbols (nm), hex dumps (xxd), strings, disassembly, security audit, entropy analysis, ELF/Mach-O inspection, binary patching,
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Comprehensive binary analysis MCP server - symbols (nm), hex dumps (xxd), strings, disassembly, security audit, entropy analysis, ELF/Mach-O inspection, binary patching, macOS code signing, and persistent patch management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"otool",
|
|
37
37
|
"codesign",
|
|
38
38
|
"quarantine",
|
|
39
|
-
"macos"
|
|
39
|
+
"macos",
|
|
40
|
+
"patch-management",
|
|
41
|
+
"binary-persistence"
|
|
40
42
|
],
|
|
41
43
|
"author": "ebowwa",
|
|
42
44
|
"license": "MIT",
|