memshell-party-cli 0.8.0 → 0.8.1
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 +30 -1
- package/README.md +14 -0
- package/dist/cli.js +370 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +43 -2
- package/dist/index.js +201 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/memshell-party/SKILL.md +27 -1
package/README.en.md
CHANGED
|
@@ -115,6 +115,23 @@ multiple variants. Combining `-o` with `--json` writes the file and adds
|
|
|
115
115
|
`outputFile: {destination, size, decoded}` to the JSON document. v3 also supports `--obfuscate`,
|
|
116
116
|
`--obfuscation-options key=value,...`, and `--obfuscation-scope` (memshell only).
|
|
117
117
|
|
|
118
|
+
For built-in and external plugin fields not covered by a dedicated flag, repeat
|
|
119
|
+
`--tool-option name=value`, or pass `--tool-config <json|path>`. v3.1 `object` fields take JSON
|
|
120
|
+
objects directly. `--tool-config` can be inline JSON or a file path; `--tool-option` overrides
|
|
121
|
+
matching JSON-file keys and legacy convenience flags:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
memparty gen -s Tomcat -t PluginTool -y Filter -p DefaultBase64 \
|
|
125
|
+
--tool-option mode=fast \
|
|
126
|
+
--tool-option 'target={"id":"web-1","name":"Web"}'
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The wizard discovers plugin `string`, `enum`, and `object` parameters from the backend. For
|
|
130
|
+
v3.1 `remote-select` fields, it fetches the supplied HTTP(S) JSON endpoint and submits the
|
|
131
|
+
selected ID for a string field or the complete object for an object field. A sole remote field
|
|
132
|
+
selects its first option automatically. Set the environment variable named by `urlQueryParam`
|
|
133
|
+
to avoid entering the endpoint; generation never fetches it again on the backend.
|
|
134
|
+
|
|
118
135
|
### Discovering options
|
|
119
136
|
|
|
120
137
|
```bash
|
|
@@ -124,9 +141,16 @@ memparty config packers # packer parent/child tree
|
|
|
124
141
|
memparty config command # Command encryptors & implementation classes
|
|
125
142
|
memparty config tool-configs # v3 tool parameters and enum options
|
|
126
143
|
memparty config obfuscation # v3 obfuscation option reference
|
|
144
|
+
memparty config plugins # v3.1 plugin metadata, SPI services and load status
|
|
145
|
+
memparty config remote-options PluginTool target --url https://example.com/options --json
|
|
127
146
|
memparty config servers --json # machine-readable
|
|
128
147
|
```
|
|
129
148
|
|
|
149
|
+
`config tool-configs` describes remote metadata as `ui.kind=remote-select`; `config remote-options`
|
|
150
|
+
returns `{label, value}` choices ready to submit. If `--url` is omitted, it reads the environment
|
|
151
|
+
variable named by the field's `urlQueryParam`. Plugin `validate()` errors are returned unchanged
|
|
152
|
+
by the API; the CLI does not hide or retry them.
|
|
153
|
+
|
|
130
154
|
### Probe shells
|
|
131
155
|
|
|
132
156
|
```bash
|
|
@@ -403,7 +427,7 @@ Example Claude Code / Claude Desktop config:
|
|
|
403
427
|
```
|
|
404
428
|
|
|
405
429
|
Exposed tools: `list_servers`, `list_config`, `list_packers`, `list_command_configs`,
|
|
406
|
-
`list_shell_tool_configs`, `list_obfuscation_options`, `generate_memshell`, `generate_probe`, `bytecode_decode`, `bytecode_transform`, `parse_classname`, `server_version`, `connect_test`,
|
|
430
|
+
`list_shell_tool_configs`, `list_plugins`, `list_remote_options`, `list_obfuscation_options`, `generate_memshell`, `generate_probe`, `bytecode_decode`, `bytecode_transform`, `parse_classname`, `server_version`, `connect_test`,
|
|
407
431
|
`exec_command`, `download_file`, `upload_file`, `target_save`, `target_note`, `target_list`,
|
|
408
432
|
`target_remove`, `log_list`.
|
|
409
433
|
|
|
@@ -412,6 +436,11 @@ optional `outputFile` argument to also write it to a local file (base64 auto-dec
|
|
|
412
436
|
`.class`/`.jar`/`.zip`/`.ser`/`.bin`), and the response gains an `outputFile` field with the
|
|
413
437
|
write result.
|
|
414
438
|
|
|
439
|
+
`generate_memshell` also accepts `toolOptions`: a map of arbitrary shell-tool names to string
|
|
440
|
+
values or JSON objects, including v3.1 plugin parameters. `toolOptionJson` is the string-encoded
|
|
441
|
+
alternative for object fields. Use `list_shell_tool_configs` to discover fields and
|
|
442
|
+
`list_remote_options` to resolve remote dropdowns before generation.
|
|
443
|
+
|
|
415
444
|
## AI skill
|
|
416
445
|
|
|
417
446
|
The package ships `skills/memshell-party/SKILL.md` — a workflow guide for agents (server / tool /
|
package/README.md
CHANGED
|
@@ -179,6 +179,8 @@ memparty download web1 /etc/passwd -o loot/passwd
|
|
|
179
179
|
- `-o` 和 `--json` 可以同时用:文件照常写盘,JSON 里多一个 `outputFile` 字段(路径、大小、是否解码)。
|
|
180
180
|
- v3 中 `Base64` 等父项只是 packer 分类,要选具体变体(如 `DefaultBase64`);旧 v2 后端仍支持聚合 packer,但不能配 `-o`。
|
|
181
181
|
- v3 支持 `--obfuscate`、`--obfuscation-options key=value,...`、`--obfuscation-scope`(仅内存马)和 `--jdk java25`。
|
|
182
|
+
- v3.1 插件参数可用重复的 `--tool-option name=value` 或 `--tool-config <json|path>` 传入;`object` 类型直接写 JSON 对象。`tool-option` 覆盖同名的旧参数及 `tool-config` 字段。
|
|
183
|
+
- 插件的远程下拉字段可用 `memparty config remote-options <tool> <param> --url <endpoint>` 查询;交互向导会拉取选项,`string` 字段提交 ID,`object` 字段提交完整对象。
|
|
182
184
|
- 探测支持 `-m ResponseHeader`;可用 `--resp-header-name` 指定回显头,未知中间件时 `--server Unknown`。
|
|
183
185
|
- 走 MCP 时,`generate_memshell` / `generate_probe` 有个 `outputFile` 参数,效果同上。
|
|
184
186
|
- Agent 加载参数使用 `--agent-args persist=true` 或 `--agent-args restore=true`;它是 attach 参数,不会发送给生成 API。JSON 和终端会给出 `java -jar agent.jar <pid> persist=true`、`jattach <pid> load instrument false agent.jar=persist=true` 示例。
|
|
@@ -204,8 +206,20 @@ memparty config tools Tomcat # 这个中间件支持哪些工具和挂载类
|
|
|
204
206
|
memparty config packers # 打包器列表(按投放方式选)
|
|
205
207
|
memparty config tool-configs # v3 各工具的参数名和枚举值
|
|
206
208
|
memparty config obfuscation # v3 混淆参数参考
|
|
209
|
+
memparty config plugins # v3.1 外置插件、SPI 和加载状态
|
|
210
|
+
memparty config remote-options PluginTool target --url https://example.com/options --json
|
|
207
211
|
```
|
|
208
212
|
|
|
213
|
+
插件生成示例:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
memparty gen -s Tomcat -t PluginTool -y Filter -p DefaultBase64 \
|
|
217
|
+
--tool-option mode=fast \
|
|
218
|
+
--tool-option 'target={"id":"web-1","name":"Web"}'
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`config tool-configs` 的 `ui.kind=remote-select` 表示远程字段,`urlQueryParam` 是同名的环境变量名;没有 `--url` 时会读这个环境变量。后端参数校验错误会原样返回,不会被 CLI 自动重试。
|
|
222
|
+
|
|
209
223
|
每个命令都能加 `--help` 看详细参数和例子;完整参数参考见 [英文 README](README.en.md)。
|
|
210
224
|
|
|
211
225
|
## 后端:真实测试建议自建
|
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,14 @@ var MemPartyClient = class {
|
|
|
86
86
|
getVersion() {
|
|
87
87
|
return this.request("GET", "/api/version");
|
|
88
88
|
}
|
|
89
|
+
async getPlugins() {
|
|
90
|
+
try {
|
|
91
|
+
return await this.request("GET", "/api/plugins");
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err instanceof ApiError && err.status === 404) return [];
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
89
97
|
// ---------- generation ----------
|
|
90
98
|
async generateMemShell(req) {
|
|
91
99
|
const apiVersion = await this.getGenerationApiVersion();
|
|
@@ -251,6 +259,127 @@ function reportError(message, argv) {
|
|
|
251
259
|
}
|
|
252
260
|
}
|
|
253
261
|
|
|
262
|
+
// src/core/shell-tool-config.ts
|
|
263
|
+
function toolConfigForTool(configs, tool) {
|
|
264
|
+
return configs.find((config) => config.tool === tool);
|
|
265
|
+
}
|
|
266
|
+
function parseShellToolConfig(assignments, params) {
|
|
267
|
+
const result = {};
|
|
268
|
+
for (const assignment of assignments ?? []) {
|
|
269
|
+
const separator = assignment.indexOf("=");
|
|
270
|
+
if (separator <= 0) {
|
|
271
|
+
throw new Error(`Invalid --tool-option "${assignment}". Expected name=value.`);
|
|
272
|
+
}
|
|
273
|
+
const name = assignment.slice(0, separator).trim();
|
|
274
|
+
const rawValue = assignment.slice(separator + 1);
|
|
275
|
+
if (!name) throw new Error(`Invalid --tool-option "${assignment}". Expected name=value.`);
|
|
276
|
+
const param = params?.find((candidate) => candidate.name === name);
|
|
277
|
+
if (params !== void 0 && param === void 0) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Unknown shell tool option "${name}". Available options: ${params.map((item) => item.name).join(", ") || "(none)"}.`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (param?.type === "object") {
|
|
283
|
+
result[name] = parseJsonObject(rawValue, `--tool-option ${name}`);
|
|
284
|
+
} else if (rawValue.startsWith("{")) {
|
|
285
|
+
result[name] = parseJsonObject(rawValue, `--tool-option ${name}`);
|
|
286
|
+
} else {
|
|
287
|
+
result[name] = rawValue;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
function parseToolConfigFile(source, params) {
|
|
293
|
+
const parsed = parseJsonObject(source, "--tool-config");
|
|
294
|
+
const assignments = Object.entries(parsed).map(([name, value]) => {
|
|
295
|
+
if (typeof value === "string") return `${name}=${value}`;
|
|
296
|
+
return `${name}=${JSON.stringify(value)}`;
|
|
297
|
+
});
|
|
298
|
+
return parseShellToolConfig(assignments, params);
|
|
299
|
+
}
|
|
300
|
+
function validateRequiredRemoteOptions(config, params) {
|
|
301
|
+
const missing = (params ?? []).filter((param) => param.ui?.kind === "remote-select" && param.ui.required).filter((param) => {
|
|
302
|
+
const value = config[param.name];
|
|
303
|
+
return value === void 0 || typeof value === "string" && value.trim() === "";
|
|
304
|
+
}).map((param) => param.name);
|
|
305
|
+
if (missing.length > 0) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Missing required remote shell tool option(s): ${missing.join(", ")}. Provide them with --tool-option or --tool-config.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function parseJsonObject(source, label) {
|
|
312
|
+
let parsed;
|
|
313
|
+
try {
|
|
314
|
+
parsed = JSON.parse(source);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
throw new Error(`${label} must be valid JSON: ${err.message}`);
|
|
317
|
+
}
|
|
318
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
319
|
+
throw new Error(`${label} must be a JSON object.`);
|
|
320
|
+
}
|
|
321
|
+
return parsed;
|
|
322
|
+
}
|
|
323
|
+
function readRemotePath(value, path) {
|
|
324
|
+
if (!path) return value;
|
|
325
|
+
let current = value;
|
|
326
|
+
for (const segment of path.split(".")) {
|
|
327
|
+
if (!segment || typeof current !== "object" || current === null || Array.isArray(current) || !(segment in current)) {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
current = current[segment];
|
|
331
|
+
}
|
|
332
|
+
return current;
|
|
333
|
+
}
|
|
334
|
+
function normalizeRemoteOptions(payload, hint, objectMode) {
|
|
335
|
+
const items = readRemotePath(payload, hint.itemsPath);
|
|
336
|
+
if (!Array.isArray(items)) {
|
|
337
|
+
throw new Error("Remote options response does not contain an array at itemsPath.");
|
|
338
|
+
}
|
|
339
|
+
return items.map((item) => {
|
|
340
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
341
|
+
throw new Error("Remote option items must be objects.");
|
|
342
|
+
}
|
|
343
|
+
const rawLabel = readRemotePath(item, hint.labelPath);
|
|
344
|
+
if (typeof rawLabel !== "string" && typeof rawLabel !== "number") {
|
|
345
|
+
throw new Error("Remote option labels must be strings or numbers.");
|
|
346
|
+
}
|
|
347
|
+
if (objectMode) return { label: String(rawLabel), value: item };
|
|
348
|
+
if (!hint.valuePath) throw new Error("String remote options require valuePath.");
|
|
349
|
+
const rawValue = readRemotePath(item, hint.valuePath);
|
|
350
|
+
if (typeof rawValue !== "string" && typeof rawValue !== "number") {
|
|
351
|
+
throw new Error("Remote option values must be strings or numbers.");
|
|
352
|
+
}
|
|
353
|
+
return { label: String(rawLabel), value: String(rawValue) };
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function validateRemoteOptionsUrl(value) {
|
|
357
|
+
const url = new URL(value);
|
|
358
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
359
|
+
throw new Error("Remote options URL must use HTTP or HTTPS.");
|
|
360
|
+
}
|
|
361
|
+
return url.toString();
|
|
362
|
+
}
|
|
363
|
+
async function fetchRemoteOptions(sourceUrl, hint, objectMode, fetchImpl = globalThis.fetch, signal) {
|
|
364
|
+
const url = validateRemoteOptionsUrl(sourceUrl);
|
|
365
|
+
const response = await fetchImpl(url, {
|
|
366
|
+
method: "GET",
|
|
367
|
+
headers: { Accept: "application/json" },
|
|
368
|
+
redirect: "follow",
|
|
369
|
+
signal
|
|
370
|
+
});
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
throw new Error(`Remote options request failed (${response.status} ${response.statusText}).`);
|
|
373
|
+
}
|
|
374
|
+
let payload;
|
|
375
|
+
try {
|
|
376
|
+
payload = await response.json();
|
|
377
|
+
} catch {
|
|
378
|
+
throw new Error("Remote options response must be valid JSON.");
|
|
379
|
+
}
|
|
380
|
+
return normalizeRemoteOptions(payload, hint, objectMode);
|
|
381
|
+
}
|
|
382
|
+
|
|
254
383
|
// src/commands/config.ts
|
|
255
384
|
function printJsonOrLines(data, json, renderLines) {
|
|
256
385
|
if (json) {
|
|
@@ -272,6 +401,8 @@ Examples:
|
|
|
272
401
|
$ memparty config command # Command-tool encryptors and impl classes
|
|
273
402
|
$ memparty config tool-configs # v3+ tool parameter names and enum values
|
|
274
403
|
$ memparty config obfuscation # v3+ obfuscation option reference
|
|
404
|
+
$ memparty config plugins # v3.1+ external plugin metadata and load status
|
|
405
|
+
$ memparty config remote-options PluginTool target --url https://example.com/options
|
|
275
406
|
`
|
|
276
407
|
);
|
|
277
408
|
config.command("servers").description("List supported servers and their shell types").option("--json", "output raw JSON").action(async (opts, cmd) => {
|
|
@@ -316,11 +447,47 @@ Examples:
|
|
|
316
447
|
() => configs.length ? configs.flatMap((config2) => [
|
|
317
448
|
config2.tool,
|
|
318
449
|
...config2.params.map(
|
|
319
|
-
(param) => ` ${param.name} (${param.type})${param.options.length ? `: ${param.options.join(", ")}` : ""}`
|
|
450
|
+
(param) => ` ${param.name} (${param.type})${param.options.length ? `: ${param.options.join(", ")}` : ""}${param.ui?.kind === "remote-select" ? ` [remote: ${param.ui.urlQueryParam}${param.ui.required ? ", required" : ""}]` : ""}`
|
|
320
451
|
)
|
|
321
452
|
]) : ["No v3 shell-tool config endpoint on this backend."]
|
|
322
453
|
);
|
|
323
454
|
});
|
|
455
|
+
config.command("plugins").description("List external plugin metadata and load status (MemShellParty v3.1+)").option("--json", "output raw JSON").action(async (opts, cmd) => {
|
|
456
|
+
const client = createClient(cmd.optsWithGlobals());
|
|
457
|
+
const plugins = await client.getPlugins();
|
|
458
|
+
printJsonOrLines(
|
|
459
|
+
plugins,
|
|
460
|
+
opts.json,
|
|
461
|
+
() => plugins.length ? plugins.flatMap((plugin) => [
|
|
462
|
+
`${plugin.name}${plugin.version ? ` (${plugin.version})` : ""} [${plugin.loaded ? "loaded" : "not loaded"}]`,
|
|
463
|
+
` jar: ${plugin.jarFileName}`,
|
|
464
|
+
...plugin.author ? [` author: ${plugin.author}`] : [],
|
|
465
|
+
...plugin.description ? [` description: ${plugin.description}`] : [],
|
|
466
|
+
...plugin.homepage ? [` homepage: ${plugin.homepage}`] : [],
|
|
467
|
+
...plugin.license ? [` license: ${plugin.license}`] : [],
|
|
468
|
+
...plugin.source ? [` source: ${plugin.source}`] : [],
|
|
469
|
+
...plugin.spiServices.length ? [` SPI: ${plugin.spiServices.join(", ")}`] : [],
|
|
470
|
+
...plugin.loadError ? [` load error: ${plugin.loadError}`] : []
|
|
471
|
+
]) : ["No external plugins (or the backend predates v3.1)."]
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
config.command("remote-options").description("Resolve a v3.1 remote-select field into string IDs or complete objects").argument("<tool>", "shell tool name").argument("<param>", "remote-select parameter name").option("--url <url>", "remote JSON endpoint (or set the field's urlQueryParam as an env var)").option("--object", "return complete objects instead of using the parameter's declared type").option("--json", "output raw JSON").action(async (tool, paramName, opts, cmd) => {
|
|
475
|
+
const client = createClient(cmd.optsWithGlobals());
|
|
476
|
+
const descriptor = (await client.getShellToolConfigs()).find((config2) => config2.tool === tool);
|
|
477
|
+
const param = descriptor?.params.find((candidate) => candidate.name === paramName);
|
|
478
|
+
if (!param?.ui || param.ui.kind !== "remote-select") {
|
|
479
|
+
throw new Error(`${tool}.${paramName} is not a remote-select shell tool option.`);
|
|
480
|
+
}
|
|
481
|
+
const sourceUrl = opts.url ?? process.env[param.ui.urlQueryParam];
|
|
482
|
+
if (!sourceUrl) {
|
|
483
|
+
throw new Error(`Provide --url or set the ${param.ui.urlQueryParam} environment variable.`);
|
|
484
|
+
}
|
|
485
|
+
const options = await fetchRemoteOptions(sourceUrl, param.ui, opts.object ?? param.type === "object");
|
|
486
|
+
printJsonOrLines(options, opts.json, () => options.map((option, index) => {
|
|
487
|
+
const value = typeof option.value === "string" ? option.value : JSON.stringify(option.value);
|
|
488
|
+
return `${index}: ${option.label} => ${value}`;
|
|
489
|
+
}));
|
|
490
|
+
});
|
|
324
491
|
config.command("obfuscation").description("List obfuscation options (MemShellParty v3+)").option("--json", "output raw JSON").action(async (opts, cmd) => {
|
|
325
492
|
const client = createClient(cmd.optsWithGlobals());
|
|
326
493
|
const options = await client.getObfuscationOptions();
|
|
@@ -3987,6 +4154,7 @@ function generateSpringExpressionInjectorClassName() {
|
|
|
3987
4154
|
return `org.springframework.expression.${randomChar(UPPERCASE_LETTERS)}${randomName}Util`;
|
|
3988
4155
|
}
|
|
3989
4156
|
function buildMemShellRequest(opts) {
|
|
4157
|
+
validateRequiredRemoteOptions(opts.shellToolConfig ?? {}, opts.shellToolParams);
|
|
3990
4158
|
const injectorClassName = SPRING_GZIP_JDK17_RELATED_PACKERS.has(opts.packer) ? generateSpringExpressionInjectorClassName() : opts.injectorClassName;
|
|
3991
4159
|
return {
|
|
3992
4160
|
shellConfig: {
|
|
@@ -4013,7 +4181,8 @@ function buildMemShellRequest(opts) {
|
|
|
4013
4181
|
headerValue: opts.headerValue,
|
|
4014
4182
|
shellClassBase64: opts.shellClassBase64,
|
|
4015
4183
|
encryptor: opts.encryptor,
|
|
4016
|
-
implementationClass: opts.implementationClass
|
|
4184
|
+
implementationClass: opts.implementationClass,
|
|
4185
|
+
...opts.shellToolConfig
|
|
4017
4186
|
},
|
|
4018
4187
|
injectorConfig: {
|
|
4019
4188
|
injectorClassName,
|
|
@@ -4072,7 +4241,11 @@ var JDK_CHOICES = [
|
|
|
4072
4241
|
}))
|
|
4073
4242
|
];
|
|
4074
4243
|
async function runMemShellWizard(client) {
|
|
4075
|
-
const [config, packerTree] = await Promise.all([
|
|
4244
|
+
const [config, packerTree, toolConfigs] = await Promise.all([
|
|
4245
|
+
client.getConfig(),
|
|
4246
|
+
client.getPackerTree(),
|
|
4247
|
+
client.getShellToolConfigs()
|
|
4248
|
+
]);
|
|
4076
4249
|
const server = await select({
|
|
4077
4250
|
message: "Target server",
|
|
4078
4251
|
choices: Object.keys(config).map((s) => ({ name: s, value: s }))
|
|
@@ -4096,7 +4269,111 @@ async function runMemShellWizard(client) {
|
|
|
4096
4269
|
choices: selected.children.map((name) => ({ name, value: name }))
|
|
4097
4270
|
}) : category;
|
|
4098
4271
|
const opts = { server, shellTool, shellType, packer };
|
|
4099
|
-
|
|
4272
|
+
const toolConfig = toolConfigForTool(toolConfigs, shellTool);
|
|
4273
|
+
if (toolConfig) {
|
|
4274
|
+
opts.shellToolParams = toolConfig.params;
|
|
4275
|
+
opts.shellToolConfig = {};
|
|
4276
|
+
const visibleParams = toolConfig.params.filter((param) => param.ui?.kind === "remote-select" || !useRandomDefaults(toolConfig));
|
|
4277
|
+
const singleRemoteParam = visibleParams.length === 1 && visibleParams[0]?.ui?.kind === "remote-select" ? visibleParams[0] : void 0;
|
|
4278
|
+
for (const param of visibleParams) {
|
|
4279
|
+
const value = await promptToolParam(param, param.name === singleRemoteParam?.name);
|
|
4280
|
+
if (value !== void 0 && value !== "") opts.shellToolConfig[param.name] = value;
|
|
4281
|
+
}
|
|
4282
|
+
} else {
|
|
4283
|
+
await promptLegacyToolConfig(opts, client);
|
|
4284
|
+
}
|
|
4285
|
+
const shellClassName = await input({ message: "Shell class name (optional, random if empty)", default: "" });
|
|
4286
|
+
if (shellClassName) opts.shellClassName = shellClassName;
|
|
4287
|
+
opts.urlPattern = await input({ message: "URL pattern", default: "/*" });
|
|
4288
|
+
const jdk = await select({ message: "Target JDK", choices: JDK_CHOICES });
|
|
4289
|
+
if (jdk) opts.jdk = jdk;
|
|
4290
|
+
const toggles = await checkbox({
|
|
4291
|
+
message: "Options",
|
|
4292
|
+
choices: [
|
|
4293
|
+
{ name: "shrink bytecode", value: "shrink", checked: true },
|
|
4294
|
+
{ name: "static initialize", value: "staticInitialize", checked: true },
|
|
4295
|
+
{ name: "bypass Java module", value: "byPassJavaModule" },
|
|
4296
|
+
{ name: "lambda suffix", value: "lambdaSuffix" },
|
|
4297
|
+
{ name: "debug", value: "debug" },
|
|
4298
|
+
{ name: "class obfuscation (v3+)", value: "obfuscate" }
|
|
4299
|
+
]
|
|
4300
|
+
});
|
|
4301
|
+
opts.shrink = toggles.includes("shrink");
|
|
4302
|
+
opts.staticInitialize = toggles.includes("staticInitialize");
|
|
4303
|
+
opts.byPassJavaModule = toggles.includes("byPassJavaModule");
|
|
4304
|
+
opts.lambdaSuffix = toggles.includes("lambdaSuffix");
|
|
4305
|
+
opts.debug = toggles.includes("debug");
|
|
4306
|
+
opts.obfuscate = toggles.includes("obfuscate");
|
|
4307
|
+
const confirmed = await confirm({ message: `Generate ${shellTool} ${shellType} for ${server} (${packer})?` });
|
|
4308
|
+
if (!confirmed) {
|
|
4309
|
+
throw new Error("Cancelled by user.");
|
|
4310
|
+
}
|
|
4311
|
+
return opts;
|
|
4312
|
+
}
|
|
4313
|
+
async function promptToolParam(param, autoSelectFirst = false) {
|
|
4314
|
+
const label = param.label || param.name;
|
|
4315
|
+
if (param.ui?.kind === "remote-select") {
|
|
4316
|
+
const defaultSource = process.env[param.ui.urlQueryParam] ?? "";
|
|
4317
|
+
const sourceUrl = defaultSource || await input({
|
|
4318
|
+
message: `${label}: remote options URL (${param.ui.urlQueryParam})${param.ui.required ? "" : " (optional)"}`,
|
|
4319
|
+
default: defaultSource
|
|
4320
|
+
});
|
|
4321
|
+
if (!sourceUrl) return void 0;
|
|
4322
|
+
const controller = new AbortController();
|
|
4323
|
+
const timer = setTimeout(() => controller.abort(), 3e4);
|
|
4324
|
+
try {
|
|
4325
|
+
const options = await fetchRemoteOptions(
|
|
4326
|
+
sourceUrl,
|
|
4327
|
+
param.ui,
|
|
4328
|
+
param.type === "object",
|
|
4329
|
+
globalThis.fetch,
|
|
4330
|
+
controller.signal
|
|
4331
|
+
);
|
|
4332
|
+
if (options.length === 0) throw new Error(`No remote options returned for ${param.name}.`);
|
|
4333
|
+
if (autoSelectFirst) return options[0].value;
|
|
4334
|
+
const selected = await select({
|
|
4335
|
+
message: label,
|
|
4336
|
+
choices: options.map((option, index) => ({ name: option.label, value: String(index) }))
|
|
4337
|
+
});
|
|
4338
|
+
return options[Number(selected)].value;
|
|
4339
|
+
} finally {
|
|
4340
|
+
clearTimeout(timer);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
if (param.type === "object") {
|
|
4344
|
+
const raw = await input({ message: `${label} (JSON object, optional)`, default: "" });
|
|
4345
|
+
if (!raw) return void 0;
|
|
4346
|
+
let value;
|
|
4347
|
+
try {
|
|
4348
|
+
value = JSON.parse(raw);
|
|
4349
|
+
} catch (err) {
|
|
4350
|
+
throw new Error(`${param.name} must be valid JSON: ${err.message}`);
|
|
4351
|
+
}
|
|
4352
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4353
|
+
throw new Error(`${param.name} must be a JSON object.`);
|
|
4354
|
+
}
|
|
4355
|
+
return value;
|
|
4356
|
+
}
|
|
4357
|
+
if (param.type === "enum") {
|
|
4358
|
+
return select({
|
|
4359
|
+
message: label,
|
|
4360
|
+
choices: [
|
|
4361
|
+
{ name: `backend default${param.placeholder ? ` (${param.placeholder})` : ""}`, value: "" },
|
|
4362
|
+
...param.options.map((option) => ({ name: option, value: option }))
|
|
4363
|
+
]
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
if (param.name === "shellClassBase64") {
|
|
4367
|
+
const file = await input({ message: "Path to custom .class file" });
|
|
4368
|
+
return file ? readFileSync7(file).toString("base64") : void 0;
|
|
4369
|
+
}
|
|
4370
|
+
return input({ message: label, default: "", ...param.placeholder ? { placeholder: param.placeholder } : {} });
|
|
4371
|
+
}
|
|
4372
|
+
function useRandomDefaults(config) {
|
|
4373
|
+
return ["Godzilla", "GodzillaSession", "Behinder", "AntSword"].includes(config.tool);
|
|
4374
|
+
}
|
|
4375
|
+
async function promptLegacyToolConfig(opts, client) {
|
|
4376
|
+
switch (opts.shellTool) {
|
|
4100
4377
|
case "Godzilla":
|
|
4101
4378
|
case "GodzillaSession":
|
|
4102
4379
|
opts.godzillaPass = await input({ message: "Godzilla pass", default: "pass" });
|
|
@@ -4110,14 +4387,14 @@ async function runMemShellWizard(client) {
|
|
|
4110
4387
|
break;
|
|
4111
4388
|
case "Command": {
|
|
4112
4389
|
opts.commandParamName = await input({ message: "Command param name", default: "cmd" });
|
|
4113
|
-
const
|
|
4390
|
+
const commandConfig = await client.getCommandConfigs();
|
|
4114
4391
|
opts.encryptor = await select({
|
|
4115
4392
|
message: "Encryptor",
|
|
4116
|
-
choices:
|
|
4393
|
+
choices: commandConfig.encryptors.map((value) => ({ name: value, value }))
|
|
4117
4394
|
});
|
|
4118
4395
|
opts.implementationClass = await select({
|
|
4119
4396
|
message: "Implementation class",
|
|
4120
|
-
choices:
|
|
4397
|
+
choices: commandConfig.implementationClasses.map((value) => ({ name: value, value }))
|
|
4121
4398
|
});
|
|
4122
4399
|
const template = await input({ message: "Command template (optional, {command} placeholder)", default: "" });
|
|
4123
4400
|
if (template) opts.commandTemplate = template;
|
|
@@ -4128,48 +4405,30 @@ async function runMemShellWizard(client) {
|
|
|
4128
4405
|
opts.shellClassBase64 = readFileSync7(file).toString("base64");
|
|
4129
4406
|
break;
|
|
4130
4407
|
}
|
|
4131
|
-
default:
|
|
4132
|
-
break;
|
|
4133
4408
|
}
|
|
4134
4409
|
opts.headerName = await input({ message: "Header name", default: "User-Agent" });
|
|
4135
4410
|
const headerValue = await input({ message: "Header value (optional)", default: "" });
|
|
4136
4411
|
if (headerValue) opts.headerValue = headerValue;
|
|
4137
|
-
const shellClassName = await input({ message: "Shell class name (optional, random if empty)", default: "" });
|
|
4138
|
-
if (shellClassName) opts.shellClassName = shellClassName;
|
|
4139
|
-
opts.urlPattern = await input({ message: "URL pattern", default: "/*" });
|
|
4140
|
-
const jdk = await select({ message: "Target JDK", choices: JDK_CHOICES });
|
|
4141
|
-
if (jdk) opts.jdk = jdk;
|
|
4142
|
-
const toggles = await checkbox({
|
|
4143
|
-
message: "Options",
|
|
4144
|
-
choices: [
|
|
4145
|
-
{ name: "shrink bytecode", value: "shrink", checked: true },
|
|
4146
|
-
{ name: "static initialize", value: "staticInitialize", checked: true },
|
|
4147
|
-
{ name: "bypass Java module", value: "byPassJavaModule" },
|
|
4148
|
-
{ name: "lambda suffix", value: "lambdaSuffix" },
|
|
4149
|
-
{ name: "debug", value: "debug" },
|
|
4150
|
-
{ name: "class obfuscation (v3+)", value: "obfuscate" }
|
|
4151
|
-
]
|
|
4152
|
-
});
|
|
4153
|
-
opts.shrink = toggles.includes("shrink");
|
|
4154
|
-
opts.staticInitialize = toggles.includes("staticInitialize");
|
|
4155
|
-
opts.byPassJavaModule = toggles.includes("byPassJavaModule");
|
|
4156
|
-
opts.lambdaSuffix = toggles.includes("lambdaSuffix");
|
|
4157
|
-
opts.debug = toggles.includes("debug");
|
|
4158
|
-
opts.obfuscate = toggles.includes("obfuscate");
|
|
4159
|
-
const confirmed = await confirm({ message: `Generate ${shellTool} ${shellType} for ${server} (${packer})?` });
|
|
4160
|
-
if (!confirmed) {
|
|
4161
|
-
throw new Error("Cancelled by user.");
|
|
4162
|
-
}
|
|
4163
|
-
return opts;
|
|
4164
4412
|
}
|
|
4165
4413
|
|
|
4166
4414
|
// src/commands/gen.ts
|
|
4167
4415
|
var REQUIRED = ["server", "tool", "type", "packer"];
|
|
4168
|
-
function toOptions(o) {
|
|
4416
|
+
async function toOptions(o, client) {
|
|
4169
4417
|
let shellClassBase64 = o.shellClassBase64;
|
|
4170
4418
|
if (o.shellClassFile) {
|
|
4171
4419
|
shellClassBase64 = readFileSync8(o.shellClassFile).toString("base64");
|
|
4172
4420
|
}
|
|
4421
|
+
const hasPluginOptions = (o.toolOption?.length ?? 0) > 0 || o.toolConfig !== void 0;
|
|
4422
|
+
const toolConfigs = hasPluginOptions ? await client.getShellToolConfigs() : [];
|
|
4423
|
+
const shellToolParams = hasPluginOptions ? toolConfigs.find((config) => config.tool === o.tool)?.params : void 0;
|
|
4424
|
+
const fromFile = o.toolConfig === void 0 ? {} : parseToolConfigFile(
|
|
4425
|
+
o.toolConfig.trimStart().startsWith("{") ? o.toolConfig : readFileSync8(o.toolConfig, "utf8"),
|
|
4426
|
+
shellToolParams
|
|
4427
|
+
);
|
|
4428
|
+
const shellToolConfig = {
|
|
4429
|
+
...fromFile,
|
|
4430
|
+
...parseShellToolConfig(o.toolOption, shellToolParams)
|
|
4431
|
+
};
|
|
4173
4432
|
return {
|
|
4174
4433
|
server: o.server,
|
|
4175
4434
|
serverVersion: o.serverVersion,
|
|
@@ -4186,6 +4445,8 @@ function toOptions(o) {
|
|
|
4186
4445
|
obfuscationOptions: parseObfuscationOptions(o.obfuscationOptions),
|
|
4187
4446
|
obfuscationScope: o.obfuscationScope,
|
|
4188
4447
|
shellClassName: o.shellClassName,
|
|
4448
|
+
shellToolConfig: hasPluginOptions ? shellToolConfig : void 0,
|
|
4449
|
+
shellToolParams,
|
|
4189
4450
|
godzillaPass: o.godzillaPass,
|
|
4190
4451
|
godzillaKey: o.godzillaKey,
|
|
4191
4452
|
behinderPass: o.behinderPass,
|
|
@@ -4210,7 +4471,12 @@ function registerGenCommand(program) {
|
|
|
4210
4471
|
"INJECTOR",
|
|
4211
4472
|
"MEMSHELL"
|
|
4212
4473
|
])
|
|
4213
|
-
).option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option(
|
|
4474
|
+
).option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option(
|
|
4475
|
+
"--tool-option <name=value>",
|
|
4476
|
+
"shell-tool-specific option; repeat for multiple fields; object values use JSON (v3.1+)",
|
|
4477
|
+
(value, previous = []) => [...previous, value],
|
|
4478
|
+
[]
|
|
4479
|
+
).option("--tool-config <json|path>", "shell-tool options as a JSON object or a JSON file path").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(
|
|
4214
4480
|
"after",
|
|
4215
4481
|
`
|
|
4216
4482
|
Examples:
|
|
@@ -4223,6 +4489,8 @@ Examples:
|
|
|
4223
4489
|
--command-param-name cmd --encryptor RAW --implementation-class RuntimeExec
|
|
4224
4490
|
$ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 --json
|
|
4225
4491
|
$ memparty gen -s Tomcat -t Godzilla -y AgentFilterChain -p AgentJar --agent-args persist=true
|
|
4492
|
+
$ memparty gen -s Tomcat -t PluginTool -y Filter -p DefaultBase64 \\
|
|
4493
|
+
--tool-option profile='{"id":"web-1","name":"Web"}' --tool-option mode=fast
|
|
4226
4494
|
|
|
4227
4495
|
Note: run 'memparty config' to list the available servers, tools, shell
|
|
4228
4496
|
types and packers. On v3, parent packers are categories \u2014 pick a concrete
|
|
@@ -4230,6 +4498,9 @@ variant (e.g. DefaultBase64 instead of Base64). Legacy v2 backends still
|
|
|
4230
4498
|
support aggregate packers, but -o cannot write several variants to one file.
|
|
4231
4499
|
The --json output includes the gate header (shellToolConfig) you need for
|
|
4232
4500
|
'memparty connect' / 'memparty exec'.
|
|
4501
|
+
Use 'memparty config tool-configs' to discover plugin string, enum and object
|
|
4502
|
+
fields. --tool-config can be an inline JSON object or a path to a JSON file.
|
|
4503
|
+
Repeated --tool-option values override matching keys from --tool-config.
|
|
4233
4504
|
`
|
|
4234
4505
|
).action(async (opts, cmd) => {
|
|
4235
4506
|
const client = createClient(cmd.optsWithGlobals());
|
|
@@ -4245,7 +4516,7 @@ The --json output includes the gate header (shellToolConfig) you need for
|
|
|
4245
4516
|
`Missing required option(s): ${missing.map((m) => `--${m}`).join(", ")}. Run in a terminal for the wizard, or pass -i.`
|
|
4246
4517
|
);
|
|
4247
4518
|
}
|
|
4248
|
-
memOpts = toOptions(opts);
|
|
4519
|
+
memOpts = await toOptions(opts, client);
|
|
4249
4520
|
}
|
|
4250
4521
|
const request = buildMemShellRequest(memOpts);
|
|
4251
4522
|
const { agentArgs } = opts;
|
|
@@ -4484,6 +4755,10 @@ var memShellInput = {
|
|
|
4484
4755
|
obfuscationOptions: z.string().optional().describe("obfuscation overrides: key=value, comma-separated"),
|
|
4485
4756
|
obfuscationScope: z.enum(["ALL", "MEMSHELL_INJECTOR", "INJECTOR", "MEMSHELL"]).optional(),
|
|
4486
4757
|
shellClassName: z.string().optional(),
|
|
4758
|
+
toolOptions: z.record(z.union([z.string(), z.record(z.unknown())])).optional().describe(
|
|
4759
|
+
"Additional shell-tool fields from list_shell_tool_configs, including plugin fields. String/enum fields use strings; v3.1+ object fields use JSON objects. These override matching built-in convenience arguments."
|
|
4760
|
+
),
|
|
4761
|
+
toolOptionJson: z.record(z.string()).optional().describe("String-encoded shell-tool fields (name -> value); object fields contain JSON text."),
|
|
4487
4762
|
godzillaPass: z.string().optional(),
|
|
4488
4763
|
godzillaKey: z.string().optional(),
|
|
4489
4764
|
behinderPass: z.string().optional(),
|
|
@@ -4758,6 +5033,50 @@ function createMcpServer(client) {
|
|
|
4758
5033
|
}
|
|
4759
5034
|
}
|
|
4760
5035
|
);
|
|
5036
|
+
server.registerTool(
|
|
5037
|
+
"list_plugins",
|
|
5038
|
+
{
|
|
5039
|
+
title: "List external plugins",
|
|
5040
|
+
description: "List v3.1+ external plugin metadata, SPI services, load status and load errors. An empty list means no plugins or a backend older than v3.1."
|
|
5041
|
+
},
|
|
5042
|
+
async () => {
|
|
5043
|
+
try {
|
|
5044
|
+
return jsonContent(await client.getPlugins());
|
|
5045
|
+
} catch (err) {
|
|
5046
|
+
return errorContent(err);
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
);
|
|
5050
|
+
server.registerTool(
|
|
5051
|
+
"list_remote_options",
|
|
5052
|
+
{
|
|
5053
|
+
title: "Resolve remote shell tool options",
|
|
5054
|
+
description: "Fetch and normalize a v3.1 remote-select field. Returns labels and string IDs or complete objects ready to pass to generate_memshell toolOptions.",
|
|
5055
|
+
inputSchema: {
|
|
5056
|
+
tool: z.string(),
|
|
5057
|
+
param: z.string(),
|
|
5058
|
+
url: z.string().optional().describe("Remote HTTP(S) endpoint; falls back to the urlQueryParam environment variable."),
|
|
5059
|
+
object: z.boolean().optional().describe("Override object mode; normally inferred from the field type.")
|
|
5060
|
+
}
|
|
5061
|
+
},
|
|
5062
|
+
async (args) => {
|
|
5063
|
+
try {
|
|
5064
|
+
const descriptor = (await client.getShellToolConfigs()).find((config) => config.tool === args.tool);
|
|
5065
|
+
const param = descriptor?.params.find((candidate) => candidate.name === args.param);
|
|
5066
|
+
if (!param?.ui || param.ui.kind !== "remote-select") {
|
|
5067
|
+
throw new Error(`${args.tool}.${args.param} is not a remote-select shell tool option.`);
|
|
5068
|
+
}
|
|
5069
|
+
const sourceUrl = args.url ?? process.env[param.ui.urlQueryParam];
|
|
5070
|
+
if (!sourceUrl) {
|
|
5071
|
+
throw new Error(`Provide url or set the ${param.ui.urlQueryParam} environment variable.`);
|
|
5072
|
+
}
|
|
5073
|
+
const options = await fetchRemoteOptions(sourceUrl, param.ui, args.object ?? param.type === "object");
|
|
5074
|
+
return jsonContent({ tool: args.tool, param: args.param, sourceUrl, options });
|
|
5075
|
+
} catch (err) {
|
|
5076
|
+
return errorContent(err);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
);
|
|
4761
5080
|
server.registerTool(
|
|
4762
5081
|
"list_obfuscation_options",
|
|
4763
5082
|
{
|
|
@@ -4795,9 +5114,21 @@ function createMcpServer(client) {
|
|
|
4795
5114
|
"agentArgs requires an Agent* shell type and an AgentJar, AgentJarWithJDKAttacher, or AgentJarWithJREAttacher packer."
|
|
4796
5115
|
);
|
|
4797
5116
|
}
|
|
5117
|
+
const hasPluginOptions = args.toolOptions !== void 0 || args.toolOptionJson !== void 0;
|
|
5118
|
+
const toolConfigs = hasPluginOptions ? await client.getShellToolConfigs() : [];
|
|
5119
|
+
const shellToolParams = hasPluginOptions ? toolConfigs.find((config) => config.tool === args.shellTool)?.params : void 0;
|
|
5120
|
+
const shellToolConfig = {
|
|
5121
|
+
...args.toolOptions,
|
|
5122
|
+
...parseShellToolConfig(
|
|
5123
|
+
Object.entries(args.toolOptionJson ?? {}).map(([name, value]) => `${name}=${value}`),
|
|
5124
|
+
shellToolParams
|
|
5125
|
+
)
|
|
5126
|
+
};
|
|
4798
5127
|
const response = await client.generateMemShell(
|
|
4799
5128
|
buildMemShellRequest({
|
|
4800
5129
|
...args,
|
|
5130
|
+
shellToolConfig: hasPluginOptions ? shellToolConfig : void 0,
|
|
5131
|
+
shellToolParams,
|
|
4801
5132
|
obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
|
|
4802
5133
|
})
|
|
4803
5134
|
);
|