memshell-party-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,983 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command as Command3 } from "commander";
5
+
6
+ // src/api/client.ts
7
+ var ApiError = class extends Error {
8
+ constructor(message, status, url) {
9
+ super(message);
10
+ this.status = status;
11
+ this.url = url;
12
+ this.name = "ApiError";
13
+ }
14
+ status;
15
+ url;
16
+ };
17
+ var MemPartyClient = class {
18
+ baseUrl;
19
+ fetchImpl;
20
+ timeoutMs;
21
+ constructor(options) {
22
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
23
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
24
+ this.timeoutMs = options.timeoutMs ?? 3e4;
25
+ if (typeof this.fetchImpl !== "function") {
26
+ throw new Error(
27
+ "No fetch implementation available. Use Node.js >= 18 or pass a custom fetch."
28
+ );
29
+ }
30
+ }
31
+ // ---------- config ----------
32
+ getServers() {
33
+ return this.request("GET", "/api/config/servers");
34
+ }
35
+ getConfig() {
36
+ return this.request("GET", "/api/config");
37
+ }
38
+ getPackerTree() {
39
+ return this.request("GET", "/api/config/packers/tree");
40
+ }
41
+ getPackers() {
42
+ return this.request("GET", "/api/config/packers");
43
+ }
44
+ getCommandConfigs() {
45
+ return this.request("GET", "/api/config/command/configs");
46
+ }
47
+ getVersion() {
48
+ return this.request("GET", "/api/version");
49
+ }
50
+ // ---------- generation ----------
51
+ generateMemShell(req) {
52
+ return this.request("POST", "/api/memshell/generate", {
53
+ json: req
54
+ });
55
+ }
56
+ generateProbe(req) {
57
+ return this.request("POST", "/api/probe/generate", {
58
+ json: req
59
+ });
60
+ }
61
+ /** Parse a fully-qualified class name from a base64-encoded .class file. */
62
+ parseClassName(classBase64) {
63
+ return this.request("POST", "/api/className", {
64
+ text: classBase64,
65
+ raw: true
66
+ });
67
+ }
68
+ // ---------- transport ----------
69
+ async request(method, path, opts = {}) {
70
+ const url = `${this.baseUrl}${path}`;
71
+ const headers = {};
72
+ let body;
73
+ if (opts.json !== void 0) {
74
+ headers["Content-Type"] = "application/json";
75
+ body = JSON.stringify(opts.json);
76
+ } else if (opts.text !== void 0) {
77
+ headers["Content-Type"] = "text/plain";
78
+ body = opts.text;
79
+ }
80
+ const controller = new AbortController();
81
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
82
+ let response;
83
+ try {
84
+ response = await this.fetchImpl(url, {
85
+ method,
86
+ headers,
87
+ body,
88
+ signal: controller.signal
89
+ });
90
+ } catch (err) {
91
+ if (err instanceof Error && err.name === "AbortError") {
92
+ throw new ApiError(`Request timed out after ${this.timeoutMs}ms`, 0, url);
93
+ }
94
+ throw new ApiError(
95
+ `Failed to reach ${url}: ${err.message}`,
96
+ 0,
97
+ url
98
+ );
99
+ } finally {
100
+ clearTimeout(timer);
101
+ }
102
+ const rawText = await response.text();
103
+ if (!response.ok) {
104
+ let message = rawText || response.statusText;
105
+ try {
106
+ const parsed = JSON.parse(rawText);
107
+ if (parsed?.error) message = parsed.error;
108
+ } catch {
109
+ }
110
+ throw new ApiError(message, response.status, url);
111
+ }
112
+ if (opts.raw) {
113
+ return rawText;
114
+ }
115
+ if (rawText === "") {
116
+ return void 0;
117
+ }
118
+ return JSON.parse(rawText);
119
+ }
120
+ };
121
+
122
+ // src/core/config.ts
123
+ import { readFileSync } from "fs";
124
+ import { homedir } from "os";
125
+ import { join } from "path";
126
+ var DEFAULT_API_URL = "https://party.mem.mk";
127
+ var ENV_VAR = "MEMPARTY_API_URL";
128
+ function readRcFile(home) {
129
+ const path = join(home, ".mempartyrc");
130
+ try {
131
+ const raw = readFileSync(path, "utf8");
132
+ return JSON.parse(raw);
133
+ } catch {
134
+ return void 0;
135
+ }
136
+ }
137
+ function resolveApiUrl(input3 = {}) {
138
+ const env = input3.env ?? process.env;
139
+ const home = input3.home ?? homedir();
140
+ const fromFlag = input3.flag?.trim();
141
+ if (fromFlag) return fromFlag;
142
+ const fromEnv = env[ENV_VAR]?.trim();
143
+ if (fromEnv) return fromEnv;
144
+ const fromFile = readRcFile(home)?.apiUrl?.trim();
145
+ if (fromFile) return fromFile;
146
+ return DEFAULT_API_URL;
147
+ }
148
+
149
+ // src/cli-context.ts
150
+ function createClient(opts) {
151
+ const baseUrl = resolveApiUrl({ flag: opts.api });
152
+ const timeoutMs = opts.timeout ? Number(opts.timeout) : void 0;
153
+ return new MemPartyClient({ baseUrl, timeoutMs });
154
+ }
155
+ function logInfo(msg) {
156
+ process.stderr.write(`${msg}
157
+ `);
158
+ }
159
+
160
+ // src/commands/config.ts
161
+ function printJsonOrLines(data, json, renderLines) {
162
+ if (json) {
163
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
164
+ `);
165
+ } else {
166
+ process.stdout.write(`${renderLines().join("\n")}
167
+ `);
168
+ }
169
+ }
170
+ function registerConfigCommand(program) {
171
+ const config = program.command("config").description("Query supported servers, shell tools, shell types, and packers");
172
+ config.command("servers").description("List supported servers and their shell types").option("--json", "output raw JSON").action(async (opts, cmd) => {
173
+ const client = createClient(cmd.optsWithGlobals());
174
+ const servers = await client.getServers();
175
+ printJsonOrLines(
176
+ servers,
177
+ opts.json,
178
+ () => Object.entries(servers).map(([name, types]) => `${name}
179
+ ${types.join(", ")}`)
180
+ );
181
+ });
182
+ config.command("tools").description("List shell tools (and their shell types) per server").argument("[server]", "filter by a single server name").option("--json", "output raw JSON").action(async (server, opts, cmd) => {
183
+ const client = createClient(cmd.optsWithGlobals());
184
+ const config2 = await client.getConfig();
185
+ const filtered = server ? { [server]: config2[server] ?? {} } : config2;
186
+ printJsonOrLines(
187
+ filtered,
188
+ opts.json,
189
+ () => Object.entries(filtered).flatMap(([name, tools]) => [
190
+ name,
191
+ ...Object.entries(tools).map(([tool, types]) => ` ${tool}: ${types.join(", ")}`)
192
+ ])
193
+ );
194
+ });
195
+ config.command("packers").description("List packers (parent / child tree)").option("--json", "output raw JSON").action(async (opts, cmd) => {
196
+ const client = createClient(cmd.optsWithGlobals());
197
+ const tree = await client.getPackerTree();
198
+ printJsonOrLines(
199
+ tree,
200
+ opts.json,
201
+ () => tree.map((p) => p.children.length ? `${p.name}
202
+ ${p.children.join(", ")}` : p.name)
203
+ );
204
+ });
205
+ config.command("command").description("List Command-tool encryptors and implementation classes").option("--json", "output raw JSON").action(async (opts, cmd) => {
206
+ const client = createClient(cmd.optsWithGlobals());
207
+ const cc = await client.getCommandConfigs();
208
+ printJsonOrLines(cc, opts.json, () => [
209
+ `encryptors: ${cc.encryptors.join(", ")}`,
210
+ `implementationClasses: ${cc.implementationClasses.join(", ")}`
211
+ ]);
212
+ });
213
+ }
214
+
215
+ // src/commands/gen.ts
216
+ import { readFileSync as readFileSync3 } from "fs";
217
+ import { Option } from "commander";
218
+
219
+ // src/core/output.ts
220
+ import { writeFileSync } from "fs";
221
+ var BINARY_EXTENSIONS = [".class", ".jar", ".zip", ".ser", ".bin"];
222
+ function hasBinaryExtension(path) {
223
+ const lower = path.toLowerCase();
224
+ return BINARY_EXTENSIONS.some((ext) => lower.endsWith(ext));
225
+ }
226
+ function shouldDecode(opts) {
227
+ if (opts.decode !== void 0) return opts.decode;
228
+ if (opts.outFile) return hasBinaryExtension(opts.outFile);
229
+ return false;
230
+ }
231
+ function emitPayload(payload, opts, sinks = {}) {
232
+ const writeFile = sinks.writeFile ?? ((p, d) => writeFileSync(p, d));
233
+ const writeStdout = sinks.writeStdout ?? ((d) => process.stdout.write(d));
234
+ const decoded = shouldDecode(opts);
235
+ if (opts.outFile) {
236
+ if (decoded) {
237
+ const buf = Buffer.from(payload, "base64");
238
+ writeFile(opts.outFile, buf);
239
+ return { destination: opts.outFile, size: buf.length, decoded: true };
240
+ }
241
+ writeFile(opts.outFile, payload);
242
+ return { destination: opts.outFile, size: Buffer.byteLength(payload), decoded: false };
243
+ }
244
+ if (decoded) {
245
+ const buf = Buffer.from(payload, "base64");
246
+ writeStdout(buf.toString("binary"));
247
+ return { destination: "stdout", size: buf.length, decoded: true };
248
+ }
249
+ writeStdout(payload.endsWith("\n") ? payload : `${payload}
250
+ `);
251
+ return { destination: "stdout", size: payload.length, decoded: false };
252
+ }
253
+
254
+ // src/core/request-builder.ts
255
+ import { randomInt } from "crypto";
256
+
257
+ // src/core/jdk.ts
258
+ var JDK_VERSIONS = {
259
+ java6: 50,
260
+ java8: 52,
261
+ java9: 53,
262
+ java11: 55,
263
+ java17: 61,
264
+ java21: 65
265
+ };
266
+ function resolveJreVersion(input3) {
267
+ if (input3 === void 0 || input3 === "") return void 0;
268
+ if (typeof input3 === "number") return input3;
269
+ const normalized = input3.trim().toLowerCase();
270
+ if (normalized in JDK_VERSIONS) return JDK_VERSIONS[normalized];
271
+ const withPrefix = `java${normalized}`;
272
+ if (withPrefix in JDK_VERSIONS) return JDK_VERSIONS[withPrefix];
273
+ const num = Number(normalized);
274
+ if (Number.isInteger(num) && num > 0) return num;
275
+ throw new Error(
276
+ `Invalid JDK version "${input3}". Use one of: ${Object.keys(JDK_VERSIONS).join(", ")}, or a class-file major version like 52.`
277
+ );
278
+ }
279
+
280
+ // src/core/request-builder.ts
281
+ var SPRING_GZIP_JDK17_RELATED_PACKERS = /* @__PURE__ */ new Set([
282
+ "SpEL",
283
+ "SpELSpringGzipJDK17",
284
+ "OGNL",
285
+ "OGNLSpringGzipJDK17",
286
+ "JXPath",
287
+ "JXPathSpringGzipJDK17"
288
+ ]);
289
+ var UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
290
+ var CLASS_NAME_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
291
+ function randomChar(chars) {
292
+ return chars[randomInt(chars.length)];
293
+ }
294
+ function generateSpringExpressionInjectorClassName() {
295
+ const randomName = Array.from({ length: 5 }, () => randomChar(CLASS_NAME_LETTERS)).join("");
296
+ return `org.springframework.expression.${randomChar(UPPERCASE_LETTERS)}${randomName}Util`;
297
+ }
298
+ function buildMemShellRequest(opts) {
299
+ const injectorClassName = SPRING_GZIP_JDK17_RELATED_PACKERS.has(opts.packer) ? generateSpringExpressionInjectorClassName() : opts.injectorClassName;
300
+ return {
301
+ shellConfig: {
302
+ server: opts.server,
303
+ serverVersion: opts.serverVersion,
304
+ shellTool: opts.shellTool,
305
+ shellType: opts.shellType,
306
+ targetJreVersion: resolveJreVersion(opts.jdk),
307
+ debug: opts.debug,
308
+ byPassJavaModule: opts.byPassJavaModule,
309
+ shrink: opts.shrink,
310
+ lambdaSuffix: opts.lambdaSuffix,
311
+ probe: opts.probe
312
+ },
313
+ shellToolConfig: {
314
+ shellClassName: opts.shellClassName,
315
+ godzillaPass: opts.godzillaPass,
316
+ godzillaKey: opts.godzillaKey,
317
+ commandParamName: opts.commandParamName,
318
+ commandTemplate: opts.commandTemplate,
319
+ behinderPass: opts.behinderPass,
320
+ antSwordPass: opts.antSwordPass,
321
+ headerName: opts.headerName,
322
+ headerValue: opts.headerValue,
323
+ shellClassBase64: opts.shellClassBase64,
324
+ encryptor: opts.encryptor,
325
+ implementationClass: opts.implementationClass
326
+ },
327
+ injectorConfig: {
328
+ injectorClassName,
329
+ urlPattern: opts.urlPattern,
330
+ staticInitialize: opts.staticInitialize
331
+ },
332
+ packer: opts.packer
333
+ };
334
+ }
335
+ function buildProbeRequest(opts) {
336
+ return {
337
+ probeConfig: {
338
+ probeMethod: opts.probeMethod,
339
+ probeContent: opts.probeContent,
340
+ shellClassName: opts.shellClassName,
341
+ targetJreVersion: resolveJreVersion(opts.jdk),
342
+ debug: opts.debug,
343
+ byPassJavaModule: opts.byPassJavaModule,
344
+ shrink: opts.shrink,
345
+ staticInitialize: opts.staticInitialize,
346
+ lambdaSuffix: opts.lambdaSuffix
347
+ },
348
+ probeContentConfig: {
349
+ host: opts.host,
350
+ seconds: opts.seconds,
351
+ sleepServer: opts.sleepServer,
352
+ server: opts.server,
353
+ reqParamName: opts.reqParamName,
354
+ commandTemplate: opts.commandTemplate
355
+ },
356
+ packer: opts.packer
357
+ };
358
+ }
359
+
360
+ // src/wizard/memshell-wizard.ts
361
+ import { readFileSync as readFileSync2 } from "fs";
362
+ import { checkbox, confirm, input, select, Separator } from "@inquirer/prompts";
363
+ var JDK_CHOICES = [
364
+ { name: "server default", value: "" },
365
+ ...Object.entries(JDK_VERSIONS).map(([name, major]) => ({
366
+ name: `${name} (${major})`,
367
+ value: name
368
+ }))
369
+ ];
370
+ function packerChoices(tree) {
371
+ const choices = [];
372
+ for (const parent of tree) {
373
+ choices.push(new Separator(`\u2500\u2500 ${parent.name} \u2500\u2500`));
374
+ choices.push({ name: parent.name, value: parent.name });
375
+ for (const child of parent.children) {
376
+ choices.push({ name: ` ${child}`, value: child });
377
+ }
378
+ }
379
+ return choices;
380
+ }
381
+ async function runMemShellWizard(client) {
382
+ const [config, packerTree] = await Promise.all([client.getConfig(), client.getPackerTree()]);
383
+ const server = await select({
384
+ message: "Target server",
385
+ choices: Object.keys(config).map((s) => ({ name: s, value: s }))
386
+ });
387
+ const tools = Object.keys(config[server]);
388
+ const shellTool = await select({
389
+ message: "Shell tool",
390
+ choices: tools.map((t) => ({ name: t, value: t }))
391
+ });
392
+ const shellType = await select({
393
+ message: "Shell type",
394
+ choices: config[server][shellTool].map((t) => ({ name: t, value: t }))
395
+ });
396
+ const packer = await select({
397
+ message: "Packer",
398
+ choices: packerChoices(packerTree)
399
+ });
400
+ const opts = { server, shellTool, shellType, packer };
401
+ switch (shellTool) {
402
+ case "Godzilla":
403
+ opts.godzillaPass = await input({ message: "Godzilla pass", default: "pass" });
404
+ opts.godzillaKey = await input({ message: "Godzilla key", default: "key" });
405
+ break;
406
+ case "Behinder":
407
+ opts.behinderPass = await input({ message: "Behinder pass", default: "rebeyond" });
408
+ break;
409
+ case "AntSword":
410
+ opts.antSwordPass = await input({ message: "AntSword pass", default: "ant" });
411
+ break;
412
+ case "Command": {
413
+ opts.commandParamName = await input({ message: "Command param name", default: "cmd" });
414
+ const cc = await client.getCommandConfigs();
415
+ opts.encryptor = await select({
416
+ message: "Encryptor",
417
+ choices: cc.encryptors.map((e) => ({ name: e, value: e }))
418
+ });
419
+ opts.implementationClass = await select({
420
+ message: "Implementation class",
421
+ choices: cc.implementationClasses.map((e) => ({ name: e, value: e }))
422
+ });
423
+ const template = await input({ message: "Command template (optional, {command} placeholder)", default: "" });
424
+ if (template) opts.commandTemplate = template;
425
+ break;
426
+ }
427
+ case "Custom": {
428
+ const file = await input({ message: "Path to custom .class file" });
429
+ opts.shellClassBase64 = readFileSync2(file).toString("base64");
430
+ break;
431
+ }
432
+ default:
433
+ break;
434
+ }
435
+ opts.headerName = await input({ message: "Header name", default: "User-Agent" });
436
+ const headerValue = await input({ message: "Header value (optional)", default: "" });
437
+ if (headerValue) opts.headerValue = headerValue;
438
+ const shellClassName = await input({ message: "Shell class name (optional, random if empty)", default: "" });
439
+ if (shellClassName) opts.shellClassName = shellClassName;
440
+ opts.urlPattern = await input({ message: "URL pattern", default: "/*" });
441
+ const jdk = await select({ message: "Target JDK", choices: JDK_CHOICES });
442
+ if (jdk) opts.jdk = jdk;
443
+ const toggles = await checkbox({
444
+ message: "Options",
445
+ choices: [
446
+ { name: "shrink bytecode", value: "shrink", checked: true },
447
+ { name: "static initialize", value: "staticInitialize", checked: true },
448
+ { name: "bypass Java module", value: "byPassJavaModule" },
449
+ { name: "lambda suffix", value: "lambdaSuffix" },
450
+ { name: "debug", value: "debug" }
451
+ ]
452
+ });
453
+ opts.shrink = toggles.includes("shrink");
454
+ opts.staticInitialize = toggles.includes("staticInitialize");
455
+ opts.byPassJavaModule = toggles.includes("byPassJavaModule");
456
+ opts.lambdaSuffix = toggles.includes("lambdaSuffix");
457
+ opts.debug = toggles.includes("debug");
458
+ const confirmed = await confirm({ message: `Generate ${shellTool} ${shellType} for ${server} (${packer})?` });
459
+ if (!confirmed) {
460
+ throw new Error("Cancelled by user.");
461
+ }
462
+ return opts;
463
+ }
464
+
465
+ // src/commands/gen.ts
466
+ var REQUIRED = ["server", "tool", "type", "packer"];
467
+ function toOptions(o) {
468
+ let shellClassBase64 = o.shellClassBase64;
469
+ if (o.shellClassFile) {
470
+ shellClassBase64 = readFileSync3(o.shellClassFile).toString("base64");
471
+ }
472
+ return {
473
+ server: o.server,
474
+ serverVersion: o.serverVersion,
475
+ shellTool: o.tool,
476
+ shellType: o.type,
477
+ packer: o.packer,
478
+ jdk: o.jdk,
479
+ debug: o.debug,
480
+ byPassJavaModule: o.bypassModule,
481
+ shrink: o.shrink,
482
+ lambdaSuffix: o.lambdaSuffix,
483
+ probe: o.probe,
484
+ shellClassName: o.shellClassName,
485
+ godzillaPass: o.godzillaPass,
486
+ godzillaKey: o.godzillaKey,
487
+ behinderPass: o.behinderPass,
488
+ antSwordPass: o.antswordPass,
489
+ commandParamName: o.commandParamName,
490
+ commandTemplate: o.commandTemplate,
491
+ encryptor: o.encryptor,
492
+ implementationClass: o.implementationClass,
493
+ headerName: o.headerName,
494
+ headerValue: o.headerValue,
495
+ shellClassBase64,
496
+ urlPattern: o.urlPattern,
497
+ injectorClassName: o.injectorClassName,
498
+ staticInitialize: o.staticInitialize
499
+ };
500
+ }
501
+ function registerGenCommand(program) {
502
+ program.command("gen").alias("generate").description("Generate a memory shell. Runs an interactive wizard when required flags are missing.").option("-s, --server <server>", "target server, e.g. Tomcat").option("--server-version <v>", "target server version").option("-t, --tool <tool>", "shell tool, e.g. Godzilla, Behinder, Command").option("-y, --type <type>", "shell type, e.g. Listener, Filter, Servlet").option("-p, --packer <packer>", "packer, e.g. Base64, Jar, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output in the payload").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--probe", "use echo/probe mode").option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option("--godzilla-pass <pass>", "Godzilla password").option("--godzilla-key <key>", "Godzilla key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option("--decode", "base64-decode the payload before writing")).addOption(new Option("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").action(async (opts, cmd) => {
503
+ const client = createClient(cmd.optsWithGlobals());
504
+ const missing = REQUIRED.filter((k) => !opts[k]);
505
+ const stdinIsTty = Boolean(process.stdin.isTTY);
506
+ const wantWizard = opts.interactive === true || opts.interactive !== false && missing.length > 0 && stdinIsTty;
507
+ let memOpts;
508
+ if (wantWizard) {
509
+ memOpts = await runMemShellWizard(client);
510
+ } else {
511
+ if (missing.length > 0) {
512
+ throw new Error(
513
+ `Missing required option(s): ${missing.map((m) => `--${m}`).join(", ")}. Run in a terminal for the wizard, or pass -i.`
514
+ );
515
+ }
516
+ memOpts = toOptions(opts);
517
+ }
518
+ const request = buildMemShellRequest(memOpts);
519
+ const response = await client.generateMemShell(request);
520
+ if (opts.json) {
521
+ process.stdout.write(`${JSON.stringify(response, null, 2)}
522
+ `);
523
+ return;
524
+ }
525
+ const r = response.memShellResult;
526
+ logInfo(`shell class: ${r.shellClassName} (${r.shellSize} bytes)`);
527
+ logInfo(`injector class: ${r.injectorClassName} (${r.injectorSize} bytes)`);
528
+ if (response.allPackResults && Object.keys(response.allPackResults).length > 0) {
529
+ for (const [name, value] of Object.entries(response.allPackResults)) {
530
+ logInfo(`
531
+ === ${name} ===`);
532
+ process.stdout.write(`${value}
533
+ `);
534
+ }
535
+ return;
536
+ }
537
+ if (response.packResult === void 0) {
538
+ throw new Error("Server returned no packResult.");
539
+ }
540
+ const result = emitPayload(response.packResult, {
541
+ outFile: opts.output,
542
+ decode: opts.decode
543
+ });
544
+ if (result.destination !== "stdout") {
545
+ logInfo(`wrote ${result.size} bytes to ${result.destination}${result.decoded ? " (base64-decoded)" : ""}`);
546
+ }
547
+ });
548
+ }
549
+
550
+ // src/mcp/server.ts
551
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
552
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
553
+ import { z } from "zod";
554
+
555
+ // src/version.ts
556
+ import { createRequire } from "module";
557
+ var require2 = createRequire(import.meta.url);
558
+ var version = "0.0.0";
559
+ try {
560
+ version = require2("../package.json").version;
561
+ } catch {
562
+ try {
563
+ version = require2("./package.json").version;
564
+ } catch {
565
+ }
566
+ }
567
+ var CLI_VERSION = version;
568
+
569
+ // src/mcp/server.ts
570
+ function jsonContent(data) {
571
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
572
+ }
573
+ function errorContent(err) {
574
+ return {
575
+ isError: true,
576
+ content: [{ type: "text", text: `Error: ${err.message}` }]
577
+ };
578
+ }
579
+ var memShellInput = {
580
+ server: z.string().describe("Target server, e.g. Tomcat"),
581
+ serverVersion: z.string().optional(),
582
+ shellTool: z.string().describe("Shell tool, e.g. Godzilla, Behinder, Command"),
583
+ shellType: z.string().describe("Shell type, e.g. Listener, Filter, Servlet"),
584
+ packer: z.string().describe("Packer, e.g. Base64, Jar, JSP"),
585
+ jdk: z.string().optional().describe("java6/8/9/11/17/21 or class-file major version"),
586
+ debug: z.boolean().optional(),
587
+ byPassJavaModule: z.boolean().optional(),
588
+ shrink: z.boolean().optional(),
589
+ lambdaSuffix: z.boolean().optional(),
590
+ probe: z.boolean().optional(),
591
+ shellClassName: z.string().optional(),
592
+ godzillaPass: z.string().optional(),
593
+ godzillaKey: z.string().optional(),
594
+ behinderPass: z.string().optional(),
595
+ antSwordPass: z.string().optional(),
596
+ commandParamName: z.string().optional(),
597
+ commandTemplate: z.string().optional(),
598
+ encryptor: z.string().optional(),
599
+ implementationClass: z.string().optional(),
600
+ headerName: z.string().optional(),
601
+ headerValue: z.string().optional(),
602
+ shellClassBase64: z.string().optional(),
603
+ urlPattern: z.string().optional(),
604
+ injectorClassName: z.string().optional(),
605
+ staticInitialize: z.boolean().optional()
606
+ };
607
+ var probeInput = {
608
+ probeMethod: z.enum(["ResponseBody", "DNSLog", "Sleep"]),
609
+ probeContent: z.string().describe("BasicInfo, Server, OS, JDK, Bytecode, Command"),
610
+ packer: z.string(),
611
+ jdk: z.string().optional(),
612
+ debug: z.boolean().optional(),
613
+ byPassJavaModule: z.boolean().optional(),
614
+ shrink: z.boolean().optional(),
615
+ lambdaSuffix: z.boolean().optional(),
616
+ staticInitialize: z.boolean().optional(),
617
+ shellClassName: z.string().optional(),
618
+ host: z.string().optional(),
619
+ seconds: z.number().optional(),
620
+ sleepServer: z.string().optional(),
621
+ server: z.string().optional(),
622
+ reqParamName: z.string().optional(),
623
+ commandTemplate: z.string().optional()
624
+ };
625
+ function createMcpServer(client) {
626
+ const server = new McpServer({ name: "memshell-party", version: CLI_VERSION });
627
+ server.registerTool(
628
+ "list_servers",
629
+ { title: "List servers", description: "List supported servers and their shell types" },
630
+ async () => {
631
+ try {
632
+ return jsonContent(await client.getServers());
633
+ } catch (err) {
634
+ return errorContent(err);
635
+ }
636
+ }
637
+ );
638
+ server.registerTool(
639
+ "list_config",
640
+ {
641
+ title: "List full config",
642
+ description: "List every server's shell tools and their supported shell types"
643
+ },
644
+ async () => {
645
+ try {
646
+ return jsonContent(await client.getConfig());
647
+ } catch (err) {
648
+ return errorContent(err);
649
+ }
650
+ }
651
+ );
652
+ server.registerTool(
653
+ "list_packers",
654
+ { title: "List packers", description: "List packers as a parent/child tree" },
655
+ async () => {
656
+ try {
657
+ return jsonContent(await client.getPackerTree());
658
+ } catch (err) {
659
+ return errorContent(err);
660
+ }
661
+ }
662
+ );
663
+ server.registerTool(
664
+ "list_command_configs",
665
+ {
666
+ title: "List command configs",
667
+ description: "List Command-tool encryptors and implementation classes"
668
+ },
669
+ async () => {
670
+ try {
671
+ return jsonContent(await client.getCommandConfigs());
672
+ } catch (err) {
673
+ return errorContent(err);
674
+ }
675
+ }
676
+ );
677
+ server.registerTool(
678
+ "generate_memshell",
679
+ { title: "Generate memory shell", description: "Generate a Java memory shell payload", inputSchema: memShellInput },
680
+ async (args) => {
681
+ try {
682
+ const response = await client.generateMemShell(buildMemShellRequest(args));
683
+ return jsonContent(response);
684
+ } catch (err) {
685
+ return errorContent(err);
686
+ }
687
+ }
688
+ );
689
+ server.registerTool(
690
+ "generate_probe",
691
+ { title: "Generate probe shell", description: "Generate a probe/detection shell payload", inputSchema: probeInput },
692
+ async (args) => {
693
+ try {
694
+ const response = await client.generateProbe(buildProbeRequest(args));
695
+ return jsonContent(response);
696
+ } catch (err) {
697
+ return errorContent(err);
698
+ }
699
+ }
700
+ );
701
+ server.registerTool(
702
+ "parse_classname",
703
+ {
704
+ title: "Parse class name",
705
+ description: "Parse the fully-qualified class name from base64-encoded .class bytes",
706
+ inputSchema: { classBase64: z.string().describe("base64-encoded .class file bytes") }
707
+ },
708
+ async ({ classBase64 }) => {
709
+ try {
710
+ return jsonContent({ className: await client.parseClassName(classBase64) });
711
+ } catch (err) {
712
+ return errorContent(err);
713
+ }
714
+ }
715
+ );
716
+ server.registerTool(
717
+ "server_version",
718
+ { title: "Server version", description: "Get the backend server version info" },
719
+ async () => {
720
+ try {
721
+ return jsonContent(await client.getVersion());
722
+ } catch (err) {
723
+ return errorContent(err);
724
+ }
725
+ }
726
+ );
727
+ return server;
728
+ }
729
+ async function startMcpStdio(client) {
730
+ const server = createMcpServer(client);
731
+ const transport = new StdioServerTransport();
732
+ await server.connect(transport);
733
+ }
734
+
735
+ // src/commands/mcp.ts
736
+ function registerMcpCommand(program) {
737
+ program.command("mcp").description("Run as an MCP server over stdio (exposes generation + config tools)").action(async (_opts, cmd) => {
738
+ const globals = cmd.optsWithGlobals();
739
+ const client = createClient(globals);
740
+ logInfo(`memshell-party MCP server starting (api: ${resolveApiUrl({ flag: globals.api })})`);
741
+ await startMcpStdio(client);
742
+ });
743
+ }
744
+
745
+ // src/commands/parse-classname.ts
746
+ import { readFileSync as readFileSync4 } from "fs";
747
+ function registerParseClassNameCommand(program) {
748
+ 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").action(async (base64, opts, cmd) => {
749
+ let classBase64;
750
+ if (opts.file) {
751
+ classBase64 = readFileSync4(opts.file).toString("base64");
752
+ } else if (base64) {
753
+ classBase64 = base64.trim();
754
+ } else {
755
+ throw new Error("Provide base64 bytes as an argument or use --file <path>.");
756
+ }
757
+ const client = createClient(cmd.optsWithGlobals());
758
+ const name = await client.parseClassName(classBase64);
759
+ process.stdout.write(`${name}
760
+ `);
761
+ });
762
+ }
763
+
764
+ // src/commands/probe.ts
765
+ import { Option as Option2 } from "commander";
766
+
767
+ // src/wizard/probe-wizard.ts
768
+ import { checkbox as checkbox2, confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
769
+ var JDK_CHOICES2 = [
770
+ { name: "server default", value: "" },
771
+ ...Object.entries(JDK_VERSIONS).map(([name, major]) => ({ name: `${name} (${major})`, value: name }))
772
+ ];
773
+ var PROBE_METHODS = ["ResponseBody", "DNSLog", "Sleep"];
774
+ var PROBE_CONTENTS = ["BasicInfo", "Server", "OS", "JDK", "Bytecode", "Command"];
775
+ async function runProbeWizard(client) {
776
+ const [config, packerTree] = await Promise.all([client.getConfig(), client.getPackerTree()]);
777
+ const servers = Object.keys(config);
778
+ const probeMethod = await select2({
779
+ message: "Probe method",
780
+ choices: PROBE_METHODS.map((m) => ({ name: m, value: m }))
781
+ });
782
+ const probeContent = await select2({
783
+ message: "Probe content",
784
+ choices: PROBE_CONTENTS.map((c) => ({ name: c, value: c }))
785
+ });
786
+ const packer = await select2({
787
+ message: "Packer",
788
+ choices: packerTree.flatMap((p) => [
789
+ { name: p.name, value: p.name },
790
+ ...p.children.map((c) => ({ name: ` ${c}`, value: c }))
791
+ ])
792
+ });
793
+ const opts = { probeMethod, probeContent, packer };
794
+ switch (probeMethod) {
795
+ case "DNSLog":
796
+ opts.host = await input2({ message: "DNSLog host" });
797
+ break;
798
+ case "Sleep":
799
+ opts.sleepServer = await select2({
800
+ message: "Sleep server",
801
+ choices: servers.map((s) => ({ name: s, value: s }))
802
+ });
803
+ opts.seconds = Number(await input2({ message: "Seconds", default: "5" }));
804
+ break;
805
+ case "ResponseBody":
806
+ opts.server = await select2({
807
+ message: "Server",
808
+ choices: servers.map((s) => ({ name: s, value: s }))
809
+ });
810
+ opts.reqParamName = await input2({ message: "Request param name", default: "cmd" });
811
+ opts.commandTemplate = await input2({
812
+ message: "Command template (optional, {command} placeholder)",
813
+ default: ""
814
+ });
815
+ break;
816
+ default:
817
+ break;
818
+ }
819
+ const shellClassName = await input2({ message: "Shell class name (optional)", default: "" });
820
+ if (shellClassName) opts.shellClassName = shellClassName;
821
+ const jdk = await select2({ message: "Target JDK", choices: JDK_CHOICES2 });
822
+ if (jdk) opts.jdk = jdk;
823
+ const toggles = await checkbox2({
824
+ message: "Options",
825
+ choices: [
826
+ { name: "shrink bytecode", value: "shrink", checked: true },
827
+ { name: "static initialize", value: "staticInitialize", checked: true },
828
+ { name: "bypass Java module", value: "byPassJavaModule" },
829
+ { name: "lambda suffix", value: "lambdaSuffix" },
830
+ { name: "debug", value: "debug" }
831
+ ]
832
+ });
833
+ opts.shrink = toggles.includes("shrink");
834
+ opts.staticInitialize = toggles.includes("staticInitialize");
835
+ opts.byPassJavaModule = toggles.includes("byPassJavaModule");
836
+ opts.lambdaSuffix = toggles.includes("lambdaSuffix");
837
+ opts.debug = toggles.includes("debug");
838
+ const confirmed = await confirm2({ message: `Generate ${probeMethod} probe (${packer})?` });
839
+ if (!confirmed) throw new Error("Cancelled by user.");
840
+ return opts;
841
+ }
842
+
843
+ // src/commands/probe.ts
844
+ var REQUIRED2 = ["method", "content", "packer"];
845
+ function toOptions2(o) {
846
+ return {
847
+ probeMethod: o.method,
848
+ probeContent: o.content,
849
+ packer: o.packer,
850
+ jdk: o.jdk,
851
+ debug: o.debug,
852
+ byPassJavaModule: o.bypassModule,
853
+ shrink: o.shrink,
854
+ lambdaSuffix: o.lambdaSuffix,
855
+ staticInitialize: o.staticInitialize,
856
+ shellClassName: o.shellClassName,
857
+ host: o.host,
858
+ seconds: o.seconds !== void 0 ? Number(o.seconds) : void 0,
859
+ sleepServer: o.sleepServer,
860
+ server: o.server,
861
+ reqParamName: o.reqParamName,
862
+ commandTemplate: o.commandTemplate
863
+ };
864
+ }
865
+ function registerProbeCommand(program) {
866
+ program.command("probe").description("Generate a probe/detection shell. Runs an interactive wizard when required flags are missing.").option("-m, --method <method>", "probe method: ResponseBody, DNSLog, Sleep").option("-c, --content <content>", "probe content: BasicInfo, Server, OS, JDK, Bytecode, Command").option("-p, --packer <packer>", "packer, e.g. Base64, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--no-static-initialize", "disable static initialization (enabled by default)").option("--shell-class-name <name>", "explicit shell class name").option("--host <host>", "DNSLog host (DNSLog method)").option("--seconds <n>", "sleep seconds (Sleep method)").option("--sleep-server <server>", "server to fingerprint (Sleep method)").option("--server <server>", "server (ResponseBody method)").option("--req-param-name <name>", "request param name (ResponseBody method)").option("--command-template <tpl>", "command template ({command} placeholder)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option2("--decode", "base64-decode the payload before writing")).addOption(new Option2("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").action(async (opts, cmd) => {
867
+ const client = createClient(cmd.optsWithGlobals());
868
+ const missing = REQUIRED2.filter((k) => !opts[k]);
869
+ const stdinIsTty = Boolean(process.stdin.isTTY);
870
+ const wantWizard = opts.interactive === true || opts.interactive !== false && missing.length > 0 && stdinIsTty;
871
+ let probeOpts;
872
+ if (wantWizard) {
873
+ probeOpts = await runProbeWizard(client);
874
+ } else {
875
+ if (missing.length > 0) {
876
+ throw new Error(
877
+ `Missing required option(s): ${missing.map((m) => `--${m}`).join(", ")}. Run in a terminal for the wizard, or pass -i.`
878
+ );
879
+ }
880
+ probeOpts = toOptions2(opts);
881
+ }
882
+ const request = buildProbeRequest(probeOpts);
883
+ const response = await client.generateProbe(request);
884
+ if (opts.json) {
885
+ process.stdout.write(`${JSON.stringify(response, null, 2)}
886
+ `);
887
+ return;
888
+ }
889
+ const r = response.probeShellResult;
890
+ logInfo(`shell class: ${r.shellClassName} (${r.shellSize} bytes)`);
891
+ if (response.allPackResults && Object.keys(response.allPackResults).length > 0) {
892
+ for (const [name, value] of Object.entries(response.allPackResults)) {
893
+ logInfo(`
894
+ === ${name} ===`);
895
+ process.stdout.write(`${value}
896
+ `);
897
+ }
898
+ return;
899
+ }
900
+ if (response.packResult === void 0) {
901
+ throw new Error("Server returned no packResult.");
902
+ }
903
+ const result = emitPayload(response.packResult, {
904
+ outFile: opts.output,
905
+ decode: opts.decode
906
+ });
907
+ if (result.destination !== "stdout") {
908
+ logInfo(`wrote ${result.size} bytes to ${result.destination}${result.decoded ? " (base64-decoded)" : ""}`);
909
+ }
910
+ });
911
+ }
912
+
913
+ // src/commands/version.ts
914
+ function registerVersionCommand(program) {
915
+ program.command("version").description("Show CLI version and the backend server version").option("--json", "output raw JSON").action(async (opts, cmd) => {
916
+ const client = createClient(cmd.optsWithGlobals());
917
+ let server;
918
+ try {
919
+ server = await client.getVersion();
920
+ } catch (err) {
921
+ server = { error: err.message };
922
+ }
923
+ if (opts.json) {
924
+ process.stdout.write(`${JSON.stringify({ cli: CLI_VERSION, server }, null, 2)}
925
+ `);
926
+ return;
927
+ }
928
+ const lines = [`CLI: ${CLI_VERSION}`];
929
+ if ("error" in server) {
930
+ lines.push(`Server: (unavailable) ${server.error}`);
931
+ } else {
932
+ lines.push(`Server: ${server.currentVersion}`);
933
+ if (server.hasUpdate) {
934
+ lines.push(`Update: ${server.latestVersion} available`);
935
+ }
936
+ }
937
+ process.stdout.write(`${lines.join("\n")}
938
+ `);
939
+ });
940
+ }
941
+
942
+ // src/cli.ts
943
+ function buildProgram() {
944
+ const program = new Command3();
945
+ program.name("memparty").description("CLI + MCP client for MemShellParty (party.mem.mk)").version(CLI_VERSION, "-v, --version", "output the CLI version").option(
946
+ "--api <url>",
947
+ `MemShellParty backend URL (env ${ENV_VAR}, default ${DEFAULT_API_URL})`
948
+ ).option("--timeout <ms>", "request timeout in milliseconds", "30000");
949
+ registerGenCommand(program);
950
+ registerProbeCommand(program);
951
+ registerConfigCommand(program);
952
+ registerParseClassNameCommand(program);
953
+ registerVersionCommand(program);
954
+ registerMcpCommand(program);
955
+ return program;
956
+ }
957
+ async function main() {
958
+ const program = buildProgram();
959
+ try {
960
+ await program.parseAsync(process.argv);
961
+ } catch (err) {
962
+ if (err instanceof ApiError) {
963
+ process.stderr.write(`API error (${err.status || "network"}): ${err.message}
964
+ `);
965
+ process.exitCode = 1;
966
+ } else if (err instanceof Error) {
967
+ if (err.name === "ExitPromptError") {
968
+ process.stderr.write("\nAborted.\n");
969
+ process.exitCode = 130;
970
+ } else {
971
+ process.stderr.write(`Error: ${err.message}
972
+ `);
973
+ process.exitCode = 1;
974
+ }
975
+ } else {
976
+ process.stderr.write(`Error: ${String(err)}
977
+ `);
978
+ process.exitCode = 1;
979
+ }
980
+ }
981
+ }
982
+ void main();
983
+ //# sourceMappingURL=cli.js.map