diff --git a/README.md b/README.md index 4c356eb..25d0e1e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,36 @@ Tags also support arguments. The find tag allows you to find a string by regex a # Changelog +## Version v2.2.33 (2025-11-27) + +- Added category checkboxes and a message when all variants are copied, +- Made the filter using the tag name. Changed dimensions. +- Added filter to output preview +- Added the first layer tag as the name of the Repeater tab +- Prevented dangerous categories from being shown in the MultiEncoderWindow. Added checkboxes to enable them. Made the window persist state in the project file when closed. + +## Version v2.2.26 (2025-11-26) + +- Fixed dialog problems +- Added limits for multiencoder + +## Version v2.2.24 (2025-11-26) + +- Fixed deflate, base32 detection +- Improved auto decoder (smart decoding) + +## Version v2.2.24 (2025-11-25) + +- Updated TagAutomator rules to allow multiple tools per rule +- Added multi encoder window +- Added websockets setting and websocket handler +- Added copy to clipboard button, clear button and select all checkbox to the MultiEncoderWindow. +- Fixed send to intruder +- Added layers to MultiEncoderWindow +- Added MultiEncoderWindow to the HackvertorExtension panel and added sendToHackvertor button +- Fixed the layers to work correctly. The layers now apply the nesting +- Added limits to MultiEncoderWindow + ## Version v2.2.16 (2025-11-20) - Changed HTTP handler to allow interception when there are Tag Automation rules diff --git a/src/main/java/burp/hv/Convertors.java b/src/main/java/burp/hv/Convertors.java index dc2ebf6..5e45463 100644 --- a/src/main/java/burp/hv/Convertors.java +++ b/src/main/java/burp/hv/Convertors.java @@ -321,9 +321,9 @@ private static void initializeTagRegistry() { TAG_REGISTRY.put("bzip2_compress", (output, args, vars, custom, hv) -> bzip2_compress(output)); TAG_REGISTRY.put("bzip2_decompress", (output, args, vars, custom, hv) -> bzip2_decompress(output)); TAG_REGISTRY.put("deflate_compress", (output, args, vars, custom, hv) -> - deflate_compress(output, getBoolean(args, 0))); + deflate_compress(output, getString(args, 0))); TAG_REGISTRY.put("deflate_decompress", (output, args, vars, custom, hv) -> - deflate_decompress(output, getBoolean(args, 0))); + deflate_decompress(output)); // SAML operations TAG_REGISTRY.put("saml", (output, args, vars, custom, hv) -> saml(output)); @@ -1219,60 +1219,58 @@ static String bzip2_decompress(String input) { } } - static String deflate_compress(String input, Boolean includeHeader) { - // Use Montoya API if available - if (HackvertorExtension.montoyaApi != null) { - try { - byte[] inputBytes = input.getBytes(); - ByteArray compressed = HackvertorExtension.montoyaApi.utilities().compressionUtils() - .compress(ByteArray.byteArray(inputBytes), CompressionType.DEFLATE); - return helpers.bytesToString(compressed.getBytes()); - } catch (Exception e) { - return "Error compressing DEFLATE: " + e.toString(); - } - } - // Fallback to legacy implementation for tests - ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length()); - CompressorOutputStream cos; - DeflateParameters params = new DeflateParameters(); - params.setWithZlibHeader(includeHeader); - cos = new DeflateCompressorOutputStream(bos, params); + static String deflate_compress(String input, String compressionType) { try { - cos.write(input.getBytes()); - cos.close(); - byte[] compressed = bos.toByteArray(); + byte[] inputBytes = helpers.stringToBytes(input); + java.util.zip.Deflater deflater; + + if ("store".equalsIgnoreCase(compressionType)) { + deflater = new java.util.zip.Deflater(java.util.zip.Deflater.NO_COMPRESSION); + } else if ("fixed".equalsIgnoreCase(compressionType)) { + deflater = new java.util.zip.Deflater(java.util.zip.Deflater.BEST_SPEED); + deflater.setStrategy(java.util.zip.Deflater.HUFFMAN_ONLY); + } else { + deflater = new java.util.zip.Deflater(java.util.zip.Deflater.BEST_COMPRESSION); + } + + deflater.setInput(inputBytes); + deflater.finish(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(inputBytes.length); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); + bos.write(buffer, 0, count); + } + deflater.end(); bos.close(); - return helpers.bytesToString(compressed); - } catch (IOException e) { + + return helpers.bytesToString(bos.toByteArray()); + } catch (Exception e) { return "Error:" + e; } } - static String deflate_decompress(String input, Boolean includeHeader) { - // Use Montoya API if available - if (HackvertorExtension.montoyaApi != null) { - try { - byte[] inputBytes = helpers.stringToBytes(input); - ByteArray decompressed = HackvertorExtension.montoyaApi.utilities().compressionUtils() - .decompress(ByteArray.byteArray(inputBytes), CompressionType.DEFLATE); - return new String(decompressed.getBytes()); - } catch (Exception e) { - return "Error decompressing DEFLATE: " + e.toString(); - } - } - // Fallback to legacy implementation for tests - ByteArrayInputStream bis = new ByteArrayInputStream(helpers.stringToBytes(input)); - DeflateCompressorInputStream cis; - byte[] bytes; + static String deflate_decompress(String input) { try { - DeflateParameters params = new DeflateParameters(); - params.setWithZlibHeader(includeHeader); - cis = new DeflateCompressorInputStream(bis, params); - bytes = IOUtils.toByteArray(cis); - cis.close(); - bis.close(); - return new String(bytes); - } catch (IOException e) { + byte[] inputBytes = helpers.stringToBytes(input); + java.util.zip.Inflater inflater = new java.util.zip.Inflater(); + inflater.setInput(inputBytes); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(inputBytes.length); + byte[] buffer = new byte[1024]; + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + if (count == 0 && inflater.needsInput()) { + break; + } + bos.write(buffer, 0, count); + } + inflater.end(); + bos.close(); + + return helpers.bytesToString(bos.toByteArray()); + } catch (Exception e) { return "Error:" + e; } } @@ -1337,14 +1335,61 @@ static String base64urlEncode(String str) { } static String saml(String input) { - return urlencode(base64Encode(deflate_compress(input, false))); + return urlencode(base64Encode(deflate_compress_raw(input))); } + static String d_saml(String input) { String decodedUrl = decode_url(input); if(isBase64(decodedUrl, true)) { - return deflate_decompress(decode_base64(decodedUrl), false); + return deflate_decompress_raw(decode_base64(decodedUrl)); } else { - return deflate_decompress(decode_base64(input), false); + return deflate_decompress_raw(decode_base64(input)); + } + } + + static String deflate_compress_raw(String input) { + try { + byte[] inputBytes = helpers.stringToBytes(input); + java.util.zip.Deflater deflater = new java.util.zip.Deflater(java.util.zip.Deflater.DEFAULT_COMPRESSION, true); + deflater.setInput(inputBytes); + deflater.finish(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(inputBytes.length); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); + bos.write(buffer, 0, count); + } + deflater.end(); + bos.close(); + + return helpers.bytesToString(bos.toByteArray()); + } catch (Exception e) { + return "Error:" + e; + } + } + + static String deflate_decompress_raw(String input) { + try { + byte[] inputBytes = helpers.stringToBytes(input); + java.util.zip.Inflater inflater = new java.util.zip.Inflater(true); + inflater.setInput(inputBytes); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(inputBytes.length); + byte[] buffer = new byte[1024]; + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + if (count == 0 && inflater.needsInput()) { + break; + } + bos.write(buffer, 0, count); + } + inflater.end(); + bos.close(); + + return helpers.bytesToString(bos.toByteArray()); + } catch (Exception e) { + return "Error:" + e; } } @@ -3312,6 +3357,54 @@ static > Map sortByValuesAsc(final Map ma static Boolean isBase64(String str, Boolean checkStart) { return Pattern.compile((checkStart ? "^" : "") + "[a-zA-Z0-9+/]{4,}=*$", Pattern.CASE_INSENSITIVE).matcher(str).find() && str.length() % 4 == 0; } + + private static final Pattern ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7f]+$"); + private static final Pattern PRINTABLE_ASCII_PATTERN = Pattern.compile("^[\\x09-\\x7f]+$"); + private static final Pattern GZIP_PATTERN = Pattern.compile("^\\x1f\\x8b\\x08"); + private static final Pattern DEFLATE_PATTERN = Pattern.compile("^\\x78[\\x01\\x5e\\x9c\\xda]"); + private static final Pattern BINARY_PATTERN = Pattern.compile("[01]{4,}\\s+[01]{4,}"); + private static final Pattern HEX_SPACED_PATTERN = Pattern.compile("(?:[0-9a-fA-F]{2}[\\s,\\-]?){3,}"); + private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9a-fA-F]+$"); + private static final Pattern CHARCODE_PATTERN = Pattern.compile("\\d+[,\\s]+"); + private static final Pattern NON_CHARCODE_PATTERN = Pattern.compile("[^\\d,\\s]"); + private static final Pattern CSS_ESCAPE_PATTERN = Pattern.compile("(?:\\\\0{0,4}[0-9a-fA-F]{2}[\\s,\\-]?){3,}"); + private static final Pattern HEX_ESCAPE_PATTERN = Pattern.compile("\\\\x[0-9a-f]{2}", Pattern.CASE_INSENSITIVE); + private static final Pattern OCTAL_ESCAPE_PATTERN = Pattern.compile("\\\\[0-9]{1,3}"); + private static final Pattern UNICODE_ESCAPE_PATTERN = Pattern.compile("\\\\u[0-9a-f]{4}", Pattern.CASE_INSENSITIVE); + private static final Pattern HTML_ENTITY_PATTERN = Pattern.compile("&[a-zA-Z]+;", Pattern.CASE_INSENSITIVE); + private static final Pattern HEX_ENTITY_PATTERN = Pattern.compile("&#x?[0-9a-f]+;?", Pattern.CASE_INSENSITIVE); + private static final Pattern URL_ENCODE_PATTERN = Pattern.compile("%[0-9a-f]{2}", Pattern.CASE_INSENSITIVE); + private static final Pattern JWT_PATTERN = Pattern.compile("^[a-zA-Z0-9\\-_.]+$", Pattern.CASE_INSENSITIVE); + private static final Pattern BASE32_PATTERN = Pattern.compile("^[A-Z2-7]+=*$"); + private static final Pattern WORDS_PATTERN = Pattern.compile("(?:[a-zA-Z]+[\\s,-]){2,}"); + private static final Pattern LOWERCASE_WORDS_PATTERN = Pattern.compile("(?:[a-z]+[\\s,-]){2,}"); + private static final Pattern LOWERCASE_ONLY_PATTERN = Pattern.compile("^[a-z]{10,}$"); + private static final Pattern BYTE_PATTERN = Pattern.compile("^[\\x00-\\xff]+$", Pattern.CASE_INSENSITIVE); + private static final Pattern BASE58_PATTERN = Pattern.compile("^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$"); + private static final Pattern BASE64URL_PATTERN = Pattern.compile("^[A-Za-z0-9_-]+$"); + private static final Pattern QUOTED_PRINTABLE_PATTERN = Pattern.compile("=[0-9A-Fa-f]{2}"); + private static final Pattern UTF7_PATTERN = Pattern.compile("\\+[A-Za-z0-9+/]*-"); + + private static boolean isAscii(String str) { + return ASCII_PATTERN.matcher(str).find(); + } + + private static boolean isPrintableAscii(String str) { + return PRINTABLE_ASCII_PATTERN.matcher(str).find(); + } + + private static boolean isGzip(String str) { + return GZIP_PATTERN.matcher(str).find(); + } + + private static boolean isDeflate(String str) { + return DEFLATE_PATTERN.matcher(str).find(); + } + + private static boolean isAsciiOrCompressed(String str) { + return isAscii(str) || isGzip(str) || isDeflate(str); + } + static String auto_decode(String str) { return auto_decode_decrypt(str, true); } @@ -3325,133 +3418,152 @@ static String auto_decode_decrypt(String str, Boolean decrypt) { int repeat = 0; boolean matched; String test; - StringBuilder encodingOpeningTags = new StringBuilder(); - StringBuilder encodingClosingTags = new StringBuilder(); + StringBuilder openTags = new StringBuilder(); + StringBuilder closeTags = new StringBuilder(); do { String startStr = str; matched = false; - int tagNo = new Random().nextInt(10000); - if (Pattern.compile("^\\x1f\\x8b\\x08").matcher(str).find()) { + if (isGzip(str)) { str = gzip_decompress(str); matched = true; - encodingOpeningTags.append("<@gzip_compress>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "gzip_compress"); + } + if (isDeflate(str)) { + test = deflate_decompress(str); + if (!test.startsWith("Error:") && isAscii(test)) { + str = test; + matched = true; + appendTags(openTags, closeTags, "deflate_compress"); + } } - if (Pattern.compile("[01]{4,}\\s+[01]{4,}").matcher(str).find()) { + if (BINARY_PATTERN.matcher(str).find()) { str = bin2ascii(str); matched = true; - encodingOpeningTags.append("<@ascii2bin>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "ascii2bin"); } - if (Pattern.compile("(?:[0-9a-fA-F]{2}[\\s,\\-]?){3,}").matcher(str).find()) { + if (HEX_SPACED_PATTERN.matcher(str).find()) { test = hex2ascii(str); - if (Pattern.compile("^[\\x09-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (isAsciiOrCompressed(test)) { str = test; - encodingOpeningTags.append("<@ascii2hex(\" \")>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "ascii2hex(\" \")", "ascii2hex"); repeat++; continue; } } - if (Pattern.compile("^[0-9a-fA-F]+$").matcher(str).find() && str.length() % 2 == 0) { + if (HEX_PATTERN.matcher(str).find() && str.length() % 2 == 0) { str = hex2ascii(str); matched = true; - encodingOpeningTags.append("<@ascii2hex(\"\")>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "ascii2hex(\"\")", "ascii2hex"); } - if (!Pattern.compile("[^\\d,\\s]").matcher(str).find() && Pattern.compile("\\d+[,\\s]+").matcher(str).find()) { + if (!NON_CHARCODE_PATTERN.matcher(str).find() && CHARCODE_PATTERN.matcher(str).find()) { str = from_charcode(str); matched = true; - encodingOpeningTags.append("<@to_charcode>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "to_charcode"); } - if (Pattern.compile("(?:\\\\0{0,4}[0-9a-fA-F]{2}[\\s,\\-]?){3,}").matcher(str).find()) { + if (CSS_ESCAPE_PATTERN.matcher(str).find()) { test = decode_css_escapes(str); - if (Pattern.compile("^[\\x09-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (isPrintableAscii(test)) { str = test; matched = true; - encodingOpeningTags.append("<@css_escapes>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "css_escapes"); } } - if (Pattern.compile("\\\\x[0-9a-f]{2}", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (HEX_ESCAPE_PATTERN.matcher(str).find()) { test = decode_js_string(str); - if (Pattern.compile("^[\\x09-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (isPrintableAscii(test)) { str = test; matched = true; - encodingOpeningTags.append("<@hex_escapes>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "hex_escapes"); } } - if (Pattern.compile("\\\\[0-9]{1,3}").matcher(str).find()) { + if (OCTAL_ESCAPE_PATTERN.matcher(str).find()) { test = decode_js_string(str); - if (Pattern.compile("^[\\x09-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (isPrintableAscii(test)) { str = test; matched = true; - encodingOpeningTags.append("<@octal_escapes>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "octal_escapes"); } } - if (Pattern.compile("\\\\u[0-9a-f]{4}", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (UNICODE_ESCAPE_PATTERN.matcher(str).find()) { test = decode_js_string(str); - if (Pattern.compile("^[\\x09-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (isPrintableAscii(test)) { str = test; matched = true; - encodingOpeningTags.append("<@unicode_escapes>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "unicode_escapes"); } } - if (Pattern.compile("&[a-zA-Z]+;", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (HTML_ENTITY_PATTERN.matcher(str).find()) { str = decode_html5_entities(str); matched = true; - encodingOpeningTags.append("<@html_entities>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "html_entities"); } - if (Pattern.compile("&#x?[0-9a-f]+;?", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (HEX_ENTITY_PATTERN.matcher(str).find()) { str = decode_html5_entities(str); matched = true; - encodingOpeningTags.append("<@hex_entities>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "hex_entities"); } - if (Pattern.compile("%[0-9a-f]{2}", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (URL_ENCODE_PATTERN.matcher(str).find()) { boolean plus = str.contains("+"); str = decode_url(str); matched = true; - if (plus) { - encodingOpeningTags.append("<@urlencode>"); - encodingClosingTags.insert(0, ""); - } else { - encodingOpeningTags.append("<@urlencode_not_plus>"); - encodingClosingTags.insert(0, ""); - } + appendTags(openTags, closeTags, plus ? "urlencode" : "urlencode_not_plus"); } - if (Pattern.compile("^[a-zA-Z0-9\\-_.]+$", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (JWT_PATTERN.matcher(str).find()) { String[] parts = str.split("\\."); if (parts.length == 3 && !d_jwt_get_header(str).equals("Invalid token")) { return d_jwt_get_header(str) + "\n" + d_jwt_get_payload(str) + "\n" + decode_base64url(parts[2]); } } - if (isBase64(str, true)) { + if (BASE32_PATTERN.matcher(str).find() && str.length() % 8 == 0) { + test = decode_base32(str); + if (isAsciiOrCompressed(test)) { + str = test; + matched = true; + appendTags(openTags, closeTags, "base32"); + } + } + if (isBase64(str, true) && !matched) { test = decode_base64(str); - if (Pattern.compile("^[\\x00-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (isAsciiOrCompressed(test)) { str = test; matched = true; - encodingOpeningTags.append("<@base64>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "base64"); } } - - if (Pattern.compile("[A-Z0-9+/]{4,}=*$", Pattern.CASE_INSENSITIVE).matcher(str).find() && str.length() % 4 == 0 && !matched) { - test = decode_base32(str); - if (Pattern.compile("^[\\x00-\\x7f]+$", Pattern.CASE_INSENSITIVE).matcher(test).find()) { + if (BASE64URL_PATTERN.matcher(str).find() && str.length() >= 4 && !matched) { + test = decode_base64url(str); + if (isAsciiOrCompressed(test)) { str = test; matched = true; - encodingOpeningTags.append("<@base32>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "base64url"); + } + } + if (BASE58_PATTERN.matcher(str).find() && str.length() >= 4 && !matched) { + test = decode_base58(str); + if (isPrintableAscii(test)) { + str = test; + matched = true; + appendTags(openTags, closeTags, "base58"); + } + } + if (QUOTED_PRINTABLE_PATTERN.matcher(str).find() && !matched) { + test = d_quoted_printable(str); + if (!test.startsWith("Error") && isPrintableAscii(test)) { + str = test; + matched = true; + appendTags(openTags, closeTags, "quoted_printable"); + } + } + if (UTF7_PATTERN.matcher(str).find() && !matched) { + test = utf7Decode(str); + if (isPrintableAscii(test) && !test.equals(str)) { + str = test; + matched = true; + appendTags(openTags, closeTags, "utf7", "utf7"); } } if (decrypt) { - if (Pattern.compile("(?:[a-zA-Z]+[\\s,-]){2,}").matcher(str).find()) { + if (WORDS_PATTERN.matcher(str).find()) { double total = 0; double bestScore = -9999999; int n = 0; @@ -3464,7 +3576,7 @@ static String auto_decode_decrypt(String str, Boolean decrypt) { n = i; } } - double average = (total / 25); + double average = total / 25; if ((((average - bestScore) / average) * 100) > 20) { String originalString = str; str = rotN(str, n); @@ -3475,11 +3587,11 @@ static String auto_decode_decrypt(String str, Boolean decrypt) { break; } } - encodingOpeningTags.append("<@rotN(").append(n).append(")>"); - encodingClosingTags.insert(0, ""); + openTags.append("<@rotN(").append(n).append(")>"); + closeTags.insert(0, ""); } } - if (Pattern.compile("(?:[a-z]+[\\s,-]){2,}").matcher(str).find()) { + if (LOWERCASE_WORDS_PATTERN.matcher(str).find()) { double total = 0; double bestScore = -9999999; int key1 = 0; @@ -3498,25 +3610,23 @@ static String auto_decode_decrypt(String str, Boolean decrypt) { } } } - double average = (total / 25); + double average = total / 25; if ((((average - bestScore) / average) * 100) > 60 && (key1 != 1 && key2 != 0)) { str = affine_decrypt(str, key1, key2); matched = true; - encodingOpeningTags.append("<@affine_encrypt(").append(key1).append(",").append(key2).append(")>"); - encodingClosingTags.insert(0, ""); + openTags.append("<@affine_encrypt(").append(key1).append(",").append(key2).append(")>"); + closeTags.insert(0, ""); } } - - if (Pattern.compile("(?:[a-z]+[\\s,-]){2,}").matcher(str).find()) { + if (LOWERCASE_WORDS_PATTERN.matcher(str).find()) { String plaintext = atbash_decrypt(str); if (is_like_english(plaintext) - is_like_english(str) >= 200) { str = plaintext; matched = true; - encodingOpeningTags.append("<@atbash_encrypt>"); - encodingClosingTags.insert(0, ""); + appendTags(openTags, closeTags, "atbash_encrypt"); } } - if (Pattern.compile("^[a-z]{10,}$").matcher(str).find()) { + if (LOWERCASE_ONLY_PATTERN.matcher(str).find()) { double total = 0; double bestScore = -9999999; int n = 0; @@ -3530,27 +3640,25 @@ static String auto_decode_decrypt(String str, Boolean decrypt) { n = i; } } - double average = (total / max - 1); + double average = total / (max - 1); if ((((average - bestScore) / average) * 100) > 20) { str = rail_fence_decrypt(str, n); matched = true; - encodingOpeningTags.append("<@rail_fence_encrypt(").append(n).append(")>"); - encodingClosingTags.insert(0, ""); + openTags.append("<@rail_fence_encrypt(").append(n).append(")>"); + closeTags.insert(0, ""); } } - - if (Pattern.compile("^[\\x00-\\xff]+$", Pattern.CASE_INSENSITIVE).matcher(str).find()) { + if (BYTE_PATTERN.matcher(str).find()) { int lenGuess = guess_key_length(str); test = xor_decrypt(str, lenGuess, false); int alphaCount = test.replaceAll("[^a-zA-Z0-9]+", "").length(); - int strLen = str.length(); - float percent = (((float) alphaCount / strLen) * 100); + float percent = ((float) alphaCount / str.length()) * 100; if (is_like_english(test) < is_like_english(str) && percent > 20) { String key = xor_decrypt(str, lenGuess, true).replaceAll("\"", "\\\""); str = test; matched = true; - encodingOpeningTags.append("<@xor(\"").append(key).append("\")>"); - encodingClosingTags.insert(0, ""); + openTags.append("<@xor(\"").append(key).append("\")>"); + closeTags.insert(0, ""); } } } @@ -3559,7 +3667,16 @@ static String auto_decode_decrypt(String str, Boolean decrypt) { } repeat++; } while (repeat < repeats); - return encodingOpeningTags + str + encodingClosingTags; + return openTags + str + closeTags; + } + + private static void appendTags(StringBuilder openTags, StringBuilder closeTags, String tagName) { + appendTags(openTags, closeTags, tagName, tagName); + } + + private static void appendTags(StringBuilder openTags, StringBuilder closeTags, String openTagName, String closeTagName) { + openTags.append("<@").append(openTagName).append(">"); + closeTags.insert(0, ""); } static String range(String str, int from, int to, int step) { diff --git a/src/main/java/burp/hv/Hackvertor.java b/src/main/java/burp/hv/Hackvertor.java index bc8a5b3..7da0cd8 100644 --- a/src/main/java/burp/hv/Hackvertor.java +++ b/src/main/java/burp/hv/Hackvertor.java @@ -351,11 +351,10 @@ private void initCompressionTags() { addTag(Tag.Category.Compression, "bzip2_compress", true, "bzip2_compress(String str)"); addTag(Tag.Category.Compression, "bzip2_decompress", true, "bzip2_decompress(String str)"); addTag(Tag.Category.Compression, "deflate_compress", true, - "deflate_compress(String str, Boolean includeHeader)", - "boolean", "true"); + "deflate_compress(String str, String compressionType)//fixed, store, dynamic", + "string", "fixed"); addTag(Tag.Category.Compression, "deflate_decompress", true, - "deflate_decompress(String str, Boolean includeHeader)", - "boolean", "true"); + "deflate_decompress(String str)"); } private void initDateTags() { diff --git a/src/main/java/burp/hv/HackvertorExtension.java b/src/main/java/burp/hv/HackvertorExtension.java index 09d14e7..7dde64a 100644 --- a/src/main/java/burp/hv/HackvertorExtension.java +++ b/src/main/java/burp/hv/HackvertorExtension.java @@ -10,7 +10,6 @@ import burp.api.montoya.ui.hotkey.HotKey; import burp.api.montoya.ui.hotkey.HotKeyContext; import burp.api.montoya.ui.hotkey.HotKeyHandler; -import burp.api.montoya.utilities.CompressionType; import burp.hv.settings.Settings; import burp.hv.tags.CustomTags; import burp.hv.tags.Tag; @@ -36,7 +35,7 @@ public class HackvertorExtension implements BurpExtension, IBurpExtender, ITab, IExtensionStateListener, IMessageEditorTabFactory { //TODO Unset on unload public static String extensionName = "Hackvertor"; - public static String version = "v2.2.16"; + public static String version = "v2.2.33"; public static JFrame HackvertorFrame = null; public static IBurpExtenderCallbacks callbacks; public static IExtensionHelpers helpers; @@ -186,6 +185,7 @@ public void initialize(MontoyaApi montoyaApi) { montoyaApi.userInterface().menuBar().registerMenu(Utils.generateHackvertorMenuBar()); Burp burp = new Burp(montoyaApi.burpSuite().version()); montoyaApi.http().registerHttpHandler(new HackvertorHttpHandler()); + montoyaApi.websockets().registerWebSocketCreatedHandler(new HackvertorWebSocketHandler()); montoyaApi.userInterface().registerContextMenuItemsProvider(new HackvertorContextMenu()); if(burp.hasCapability(Burp.Capability.REGISTER_HOTKEY)) { @@ -228,7 +228,8 @@ private void registerAllHotkeys(MontoyaApi montoyaApi, Burp burp) { new HotkeyDefinition("Tag Automator", "Ctrl+Alt+A", event -> TagAutomator.showRulesDialog()), new HotkeyDefinition("Settings", "Ctrl+Alt+S", event -> Settings.showSettingsWindow()), new HotkeyDefinition("Smart decode", "Ctrl+Alt+D", createAutoDecodeHandler()), - new HotkeyDefinition("Show tag store", "Ctrl+Alt+T", event -> TagStore.showTagStore()) + new HotkeyDefinition("Show tag store", "Ctrl+Alt+T", event -> TagStore.showTagStore()), + new HotkeyDefinition("Multi Encoder", "Ctrl+Alt+M", createMultiEncoderHandler(montoyaApi)) ); for (HotkeyDefinition hotkey : hotkeys) { @@ -291,4 +292,34 @@ private HotKeyHandler createAutoDecodeHandler() { } }; } + + private HotKeyHandler createMultiEncoderHandler(MontoyaApi montoyaApi) { + return event -> { + if (event.messageEditorRequestResponse().isEmpty()) { + return; + } + MessageEditorHttpRequestResponse requestResponse = event.messageEditorRequestResponse().get(); + if(requestResponse.selectionOffsets().isPresent() && + requestResponse.selectionContext().toString().equalsIgnoreCase("request")) { + String request = requestResponse.requestResponse().request().toString(); + int start = requestResponse.selectionOffsets().get().startIndexInclusive(); + int end = requestResponse.selectionOffsets().get().endIndexExclusive(); + + if (start != end) { + String selectedText = request.substring(start, end); + ArrayList tags = HackvertorExtension.hackvertor.getTags(); + + // Show the Multi Encoder window + MultiEncoderWindow multiEncoderWindow = new MultiEncoderWindow( + montoyaApi, + selectedText, + tags, + requestResponse, + requestResponse.requestResponse() + ); + multiEncoderWindow.show(); + } + } + }; + } } diff --git a/src/main/java/burp/hv/HackvertorMessageHandler.java b/src/main/java/burp/hv/HackvertorMessageHandler.java new file mode 100644 index 0000000..ea09770 --- /dev/null +++ b/src/main/java/burp/hv/HackvertorMessageHandler.java @@ -0,0 +1,31 @@ +package burp.hv; + +import burp.api.montoya.websocket.*; +import burp.hv.settings.InvalidTypeSettingException; +import burp.hv.settings.UnregisteredSettingException; + +public class HackvertorMessageHandler implements MessageHandler { + + @Override + public TextMessageAction handleTextMessage(TextMessage textMessage) { + boolean tagsInWebSockets; + try { + tagsInWebSockets = HackvertorExtension.generalSettings.getBoolean("tagsInWebSockets"); + } catch (UnregisteredSettingException | InvalidTypeSettingException e) { + HackvertorExtension.callbacks.printError("Error loading settings:" + e); + throw new RuntimeException(e); + } + if(tagsInWebSockets) { + if (textMessage.payload().contains("<@")) { + String converted = HackvertorExtension.hackvertor.convert(textMessage.payload(), HackvertorExtension.hackvertor); + return TextMessageAction.continueWith(converted); + } + } + return TextMessageAction.continueWith(textMessage); + } + + @Override + public BinaryMessageAction handleBinaryMessage(BinaryMessage binaryMessage) { + return BinaryMessageAction.continueWith(binaryMessage); + } +} diff --git a/src/main/java/burp/hv/HackvertorWebSocketHandler.java b/src/main/java/burp/hv/HackvertorWebSocketHandler.java new file mode 100644 index 0000000..999de59 --- /dev/null +++ b/src/main/java/burp/hv/HackvertorWebSocketHandler.java @@ -0,0 +1,12 @@ +package burp.hv; + +import burp.api.montoya.websocket.WebSocketCreated; +import burp.api.montoya.websocket.WebSocketCreatedHandler; + +public class HackvertorWebSocketHandler implements WebSocketCreatedHandler { + + @Override + public void handleWebSocketCreated(WebSocketCreated webSocketCreated) { + webSocketCreated.webSocket().registerMessageHandler(new HackvertorMessageHandler()); + } +} diff --git a/src/main/java/burp/hv/tags/TagAutomator.java b/src/main/java/burp/hv/tags/TagAutomator.java index 70148de..9743497 100644 --- a/src/main/java/burp/hv/tags/TagAutomator.java +++ b/src/main/java/burp/hv/tags/TagAutomator.java @@ -24,6 +24,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.List; import static burp.hv.HackvertorExtension.callbacks; @@ -328,10 +329,18 @@ private static void showCreateEditRuleDialog(boolean isEdit, JSONObject existing JCheckBox requestCheckbox = new JCheckBox("Request"); JCheckBox responseCheckbox = new JCheckBox("Response"); - JLabel toolLabel = new JLabel("Tool:"); + JLabel toolLabel = new JLabel("Tools:"); String[] tools = {"Proxy", "Intruder", "Repeater", "Scanner", "Extensions"}; - JComboBox toolComboBox = new JComboBox<>(tools); - toolComboBox.setSelectedItem("Repeater"); + JPanel toolPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0)); + JCheckBox[] toolCheckBoxes = new JCheckBox[tools.length]; + + for (int i = 0; i < tools.length; i++) { + toolCheckBoxes[i] = new JCheckBox(tools[i]); + if (tools[i].equals("Repeater")) { + toolCheckBoxes[i].setSelected(true); + } + toolPanel.add(toolCheckBoxes[i]); + } if (isEdit && existingRule != null) { JSONArray contexts = existingRule.getJSONArray("contexts"); @@ -343,8 +352,34 @@ private static void showCreateEditRuleDialog(boolean isEdit, JSONObject existing responseCheckbox.setSelected(true); } } - String tool = existingRule.optString("tool", "Repeater"); - toolComboBox.setSelectedItem(tool); + // Handle both single tool (string) and multiple tools (array) for backwards compatibility + ArrayList selectedTools = new ArrayList<>(); + if (existingRule.has("tool")) { + Object toolValue = existingRule.get("tool"); + if (toolValue instanceof String) { + selectedTools.add((String) toolValue); + } else if (toolValue instanceof JSONArray) { + JSONArray toolsArray = (JSONArray) toolValue; + for (int i = 0; i < toolsArray.length(); i++) { + selectedTools.add(toolsArray.getString(i)); + } + } + } else { + selectedTools.add("Repeater"); + } + // Clear all checkboxes first + for (JCheckBox checkBox : toolCheckBoxes) { + checkBox.setSelected(false); + } + // Set selected tools + for (String selectedTool : selectedTools) { + for (int i = 0; i < tools.length; i++) { + if (tools[i].equals(selectedTool)) { + toolCheckBoxes[i].setSelected(true); + break; + } + } + } } else { requestCheckbox.setSelected(true); } @@ -377,8 +412,34 @@ public void actionPerformed(ActionEvent e) { } } - String tool = example.optString("tool", "Repeater"); - toolComboBox.setSelectedItem(tool); + // Handle both single tool (string) and multiple tools (array) for backwards compatibility + ArrayList selectedTools = new ArrayList<>(); + if (example.has("tool")) { + Object toolValue = example.get("tool"); + if (toolValue instanceof String) { + selectedTools.add((String) toolValue); + } else if (toolValue instanceof JSONArray) { + JSONArray toolsArray = (JSONArray) toolValue; + for (int i = 0; i < toolsArray.length(); i++) { + selectedTools.add(toolsArray.getString(i)); + } + } + } else { + selectedTools.add("Repeater"); + } + // Clear all checkboxes first + for (JCheckBox checkBox : toolCheckBoxes) { + checkBox.setSelected(false); + } + // Set selected tools + for (String selectedTool : selectedTools) { + for (int i = 0; i < tools.length; i++) { + if (tools[i].equals(selectedTool)) { + toolCheckBoxes[i].setSelected(true); + break; + } + } + } } else { typeComboBox.setSelectedItem("Context Menu"); nameField.setText(""); @@ -462,7 +523,7 @@ public void actionPerformed(ActionEvent e) { mainPanel.add(contextPanel, GridbagUtils.createConstraints(1, y++, 1, GridBagConstraints.BOTH, 1, 0, 5, 5, GridBagConstraints.CENTER)); mainPanel.add(toolLabel, GridbagUtils.createConstraints(0, y, 1, GridBagConstraints.BOTH, 0, 0, 5, 5, GridBagConstraints.WEST)); - mainPanel.add(toolComboBox, GridbagUtils.createConstraints(1, y++, 1, GridBagConstraints.BOTH, 1, 0, 5, 5, GridBagConstraints.WEST)); + mainPanel.add(toolPanel, GridbagUtils.createConstraints(1, y++, 1, GridBagConstraints.BOTH, 1, 0, 5, 5, GridBagConstraints.WEST)); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); JButton saveButton = new JButton(isEdit ? "Update" : "Create"); @@ -498,9 +559,31 @@ public void actionPerformed(ActionEvent e) { "Validation Error", JOptionPane.ERROR_MESSAGE); return; } - String tool = toolComboBox.getSelectedItem().toString(); - if(!tool.equals("Repeater")) { - int confirm = JOptionPane.showConfirmDialog(null, "Running Python on every "+tool+" request or response can affect Burp's performance, are you sure?"); + List selectedTools = new ArrayList<>(); + for (int i = 0; i < toolCheckBoxes.length; i++) { + if (toolCheckBoxes[i].isSelected()) { + selectedTools.add(tools[i]); + } + } + if(selectedTools.isEmpty()) { + JOptionPane.showMessageDialog(dialog, "At least one tool must be selected", + "Validation Error", JOptionPane.ERROR_MESSAGE); + return; + } + + boolean hasNonRepeaterTool = false; + for(String tool : selectedTools) { + if(!tool.equals("Repeater")) { + hasNonRepeaterTool = true; + break; + } + } + + if(hasNonRepeaterTool) { + String toolsMessage = String.join(", ", selectedTools); + int confirm = JOptionPane.showConfirmDialog(null, + "Running Python on every request or response in " + toolsMessage + + " can affect Burp's performance, are you sure?"); if(confirm != 0) { return; } @@ -521,7 +604,12 @@ public void actionPerformed(ActionEvent e) { rule.put("modification", modification); rule.put("contexts", contexts); rule.put("enabled", enabledCheckbox.isSelected()); - rule.put("tool", (String) toolComboBox.getSelectedItem()); + // Save tools as array + JSONArray toolsArray = new JSONArray(); + for(String tool : selectedTools) { + toolsArray.put(tool); + } + rule.put("tool", toolsArray); if (isEdit) { for (int i = 0; i < rules.length(); i++) { @@ -593,7 +681,26 @@ public static boolean shouldApplyRules(String context, String tool, String type) } if(!rule.getString("type").equals(type)) continue; JSONArray contexts = rule.getJSONArray("contexts"); - String ruleTool = rule.optString("tool", "Repeater"); + + // Handle both single tool (string) and multiple tools (array) for backwards compatibility + boolean toolMatches = false; + if (rule.has("tool")) { + Object toolValue = rule.get("tool"); + if (toolValue instanceof String) { + toolMatches = ((String) toolValue).equalsIgnoreCase(tool); + } else if (toolValue instanceof JSONArray) { + JSONArray toolsArray = (JSONArray) toolValue; + for (int k = 0; k < toolsArray.length(); k++) { + if (toolsArray.getString(k).equalsIgnoreCase(tool)) { + toolMatches = true; + break; + } + } + } + } else { + // Default to Repeater for backwards compatibility + toolMatches = "Repeater".equalsIgnoreCase(tool); + } boolean appliesTo = false; for (int j = 0; j < contexts.length(); j++) { @@ -603,8 +710,6 @@ public static boolean shouldApplyRules(String context, String tool, String type) } } - boolean toolMatches = ruleTool.equalsIgnoreCase(tool); - if (appliesTo && toolMatches) { return true; } @@ -633,7 +738,26 @@ public static String applyRules(String content, String context, String tool, Str } JSONArray contexts = rule.getJSONArray("contexts"); - String ruleTool = rule.optString("tool", "Repeater"); + + // Handle both single tool (string) and multiple tools (array) for backwards compatibility + boolean toolMatches = false; + if (rule.has("tool")) { + Object toolValue = rule.get("tool"); + if (toolValue instanceof String) { + toolMatches = ((String) toolValue).equalsIgnoreCase(tool); + } else if (toolValue instanceof JSONArray) { + JSONArray toolsArray = (JSONArray) toolValue; + for (int k = 0; k < toolsArray.length(); k++) { + if (toolsArray.getString(k).equalsIgnoreCase(tool)) { + toolMatches = true; + break; + } + } + } + } else { + // Default to Repeater for backwards compatibility + toolMatches = "Repeater".equalsIgnoreCase(tool); + } boolean appliesTo = false; for (int j = 0; j < contexts.length(); j++) { @@ -642,9 +766,7 @@ public static String applyRules(String content, String context, String tool, Str break; } } - - boolean toolMatches = ruleTool.equalsIgnoreCase(tool); - + if (appliesTo && toolMatches) { String analysis = rule.getString("analysis"); String modification = rule.getString("modification"); diff --git a/src/main/java/burp/hv/ui/ExtensionPanel.java b/src/main/java/burp/hv/ui/ExtensionPanel.java index 6653cd8..9f3815a 100644 --- a/src/main/java/burp/hv/ui/ExtensionPanel.java +++ b/src/main/java/burp/hv/ui/ExtensionPanel.java @@ -151,6 +151,9 @@ public void saveState() { } public void restoreState() { + if(montoyaApi == null) { + return; + } try { String savedState = montoyaApi.persistence().extensionData().getString("extensionPanelState"); if (savedState == null || savedState.isEmpty()) { diff --git a/src/main/java/burp/hv/ui/HackvertorContextMenu.java b/src/main/java/burp/hv/ui/HackvertorContextMenu.java index 95dc6dc..7561cb0 100644 --- a/src/main/java/burp/hv/ui/HackvertorContextMenu.java +++ b/src/main/java/burp/hv/ui/HackvertorContextMenu.java @@ -243,6 +243,34 @@ public List provideMenuItems(ContextMenuEvent event) { } }); menu.add(autodecodeConvert); + + // Multi Encoder feature + JMenuItem multiEncoder = new JMenuItem("Multi Encoder (Ctrl+Alt+M)"); + multiEncoder.setEnabled(start != end); + multiEncoder.addActionListener(e -> { + if (event.invocationType() == InvocationType.MESSAGE_EDITOR_REQUEST || event.invocationType() == InvocationType.MESSAGE_VIEWER_REQUEST) { + if(event.messageEditorRequestResponse().isPresent()) { + HttpRequest request = event.messageEditorRequestResponse().get().requestResponse().request(); + String requestStr = request.toString(); + String selectedText = requestStr.substring(start, end); + + // Get all available tags + ArrayList tags = HackvertorExtension.hackvertor.getTags(); + + // Show the Multi Encoder window + MultiEncoderWindow multiEncoderWindow = new MultiEncoderWindow( + montoyaApi, + selectedText, + tags, + event.messageEditorRequestResponse().get(), + event.messageEditorRequestResponse().get().requestResponse() + ); + multiEncoderWindow.show(); + } + } + }); + menu.add(multiEncoder); + menu.addSeparator(); CustomTags.loadCustomTags(); if(allowTagCount) { diff --git a/src/main/java/burp/hv/ui/HackvertorPanel.java b/src/main/java/burp/hv/ui/HackvertorPanel.java index dea6ac4..252d04b 100644 --- a/src/main/java/burp/hv/ui/HackvertorPanel.java +++ b/src/main/java/burp/hv/ui/HackvertorPanel.java @@ -446,6 +446,37 @@ public void actionPerformed(ActionEvent evt) { finderWindow.show(); } }); + + inputArea.getInputMap().put(KeyStroke.getKeyStroke("control alt M"), "multiEncoder"); + inputArea.getActionMap().put("multiEncoder", new AbstractAction("multiEncoder") { + public void actionPerformed(ActionEvent evt) { + String selectedText = inputArea.getSelectedText(); + boolean hasSelection = selectedText != null && !selectedText.isEmpty(); + if (!hasSelection) { + selectedText = inputArea.getText(); + } + if (selectedText == null || selectedText.isEmpty()) { + return; + } + ArrayList tags = hackvertor.getTags(); + String textToEncode = selectedText; + boolean replaceSelection = hasSelection; + MultiEncoderWindow encoderWindow = new MultiEncoderWindow( + HackvertorExtension.montoyaApi, + textToEncode, + tags, + result -> { + if (replaceSelection) { + inputArea.replaceSelection(result); + } else { + inputArea.setText(result); + } + } + ); + encoderWindow.show(); + } + }); + decode.addActionListener(smartDecodeAction); JButton rehydrateTagExecutionKey = new JButton("Rehydrate Tags"); rehydrateTagExecutionKey.setToolTipText("Replace tag execution keys in selected text with your current key"); diff --git a/src/main/java/burp/hv/ui/MultiEncoderWindow.java b/src/main/java/burp/hv/ui/MultiEncoderWindow.java new file mode 100644 index 0000000..1567dff --- /dev/null +++ b/src/main/java/burp/hv/ui/MultiEncoderWindow.java @@ -0,0 +1,1099 @@ +package burp.hv.ui; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import burp.api.montoya.core.Range; +import burp.api.montoya.intruder.HttpRequestTemplate; +import burp.api.montoya.ui.contextmenu.MessageEditorHttpRequestResponse; +import burp.hv.Convertors; +import burp.hv.HackvertorExtension; +import burp.hv.tags.Tag; +import org.json.JSONArray; +import org.json.JSONObject; + +import javax.swing.*; +import javax.swing.text.*; +import java.util.EnumSet; +import java.util.Set; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.geom.RoundRectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.*; +import java.util.function.Consumer; +import java.util.function.Function; + +public class MultiEncoderWindow { + private static final int DEFAULT_WIDTH = 1000; + private static final int DEFAULT_HEIGHT = 750; + private static final int CORNER_RADIUS = 20; + private static final int COLUMN_COUNT = 4; + private static final int MAX_VARIANTS_DISPLAY = 100; + private static final int MAX_VARIANTS_TOTAL = 10000; + private static final int MAX_TAGS_PER_LAYER = 50; + private static final int CONVERT_TIMEOUT_SECONDS = 20; + private static final Set DANGEROUS_CATEGORIES = EnumSet.of( + Tag.Category.Custom, + Tag.Category.System, + Tag.Category.Languages + ); + private static final String PERSISTENCE_KEY = "multiEncoderState"; + + private final MontoyaApi montoyaApi; + private final String selectedText; + private final ArrayList tags; + private final MessageEditorHttpRequestResponse messageEditor; + private final HttpRequestResponse baseRequestResponse; + private final ArrayList layers; + private final Consumer hackvertorCallback; + private JTextPane previewArea; + private JTextField previewSearchField; + private String lastPreviewContent = ""; + private JWindow window; + private JComboBox modeComboBox; + private JTabbedPane layerTabbedPane; + private int layerCounter = 1; + private JLabel statusLabel; + private Timer statusClearTimer; + private final Set enabledDangerousCategories = EnumSet.noneOf(Tag.Category.class); + private final Set enabledCategories = EnumSet.noneOf(Tag.Category.class); + private final Map dangerousCategoryCheckboxes = new HashMap<>(); + private final Map categoryCheckboxes = new HashMap<>(); + + private class Layer { + final Map tagCheckboxes; + final ArrayList selectedTags; + final JPanel tagsPanel; + final JTextField searchField; + final JCheckBox selectAllCheckbox; + Runnable updateTags; + + Layer(JPanel tagsPanel, JTextField searchField, JCheckBox selectAllCheckbox) { + this.tagCheckboxes = new HashMap<>(); + this.selectedTags = new ArrayList<>(); + this.tagsPanel = tagsPanel; + this.searchField = searchField; + this.selectAllCheckbox = selectAllCheckbox; + } + } + + public MultiEncoderWindow(MontoyaApi montoyaApi, String selectedText, ArrayList tags, + MessageEditorHttpRequestResponse messageEditor, HttpRequestResponse baseRequestResponse) { + this(montoyaApi, selectedText, tags, messageEditor, baseRequestResponse, null); + } + + public MultiEncoderWindow(MontoyaApi montoyaApi, String selectedText, ArrayList tags, + Consumer hackvertorCallback) { + this(montoyaApi, selectedText, tags, null, null, hackvertorCallback); + } + + private MultiEncoderWindow(MontoyaApi montoyaApi, String selectedText, ArrayList tags, + MessageEditorHttpRequestResponse messageEditor, HttpRequestResponse baseRequestResponse, + Consumer hackvertorCallback) { + this.montoyaApi = montoyaApi; + this.selectedText = selectedText; + this.tags = tags; + this.messageEditor = messageEditor; + this.baseRequestResponse = baseRequestResponse; + this.hackvertorCallback = hackvertorCallback; + this.layers = new ArrayList<>(); + } + + public void show() { + SwingUtilities.invokeLater(() -> { + window = new JWindow(montoyaApi.userInterface().swingUtils().suiteFrame()); + window.setLayout(new BorderLayout()); + window.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + + Runnable applyRoundedCorners = () -> { + try { + window.setBackground(new Color(0, 0, 0, 0)); + SwingUtilities.invokeLater(() -> { + Shape shape = new RoundRectangle2D.Float(0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, + CORNER_RADIUS, CORNER_RADIUS); + window.setShape(shape); + }); + } catch (UnsupportedOperationException ignored) {} + }; + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.setBorder(new EmptyBorder(14, 14, 14, 14)); + montoyaApi.userInterface().applyThemeToComponent(mainPanel); + + JPanel titlePanel = new JPanel(new BorderLayout()); + JLabel titleLabel = new JLabel("Multi Encoder"); + titleLabel.setFont(new Font("Inter", Font.BOLD, 16)); + titlePanel.add(titleLabel, BorderLayout.WEST); + titlePanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + montoyaApi.userInterface().applyThemeToComponent(titlePanel); + montoyaApi.userInterface().applyThemeToComponent(titleLabel); + + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 12, 0)); + montoyaApi.userInterface().applyThemeToComponent(topPanel); + + layerTabbedPane = new JTabbedPane(); + layerTabbedPane.setPreferredSize(new Dimension(DEFAULT_WIDTH - 50, 220)); + montoyaApi.userInterface().applyThemeToComponent(layerTabbedPane); + + JPanel layerButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + JButton addLayerButton = new JButton("+ Add Layer"); + addLayerButton.addActionListener(e -> addLayer()); + montoyaApi.userInterface().applyThemeToComponent(addLayerButton); + + JButton removeLayerButton = new JButton("- Remove Layer"); + removeLayerButton.addActionListener(e -> removeCurrentLayer()); + montoyaApi.userInterface().applyThemeToComponent(removeLayerButton); + + layerButtonPanel.add(addLayerButton); + layerButtonPanel.add(removeLayerButton); + montoyaApi.userInterface().applyThemeToComponent(layerButtonPanel); + + loadState(); + + JPanel dangerousCategoriesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + JLabel dangerousLabel = new JLabel("Dangerous:"); + dangerousLabel.setFont(new Font("Inter", Font.PLAIN, 12)); + montoyaApi.userInterface().applyThemeToComponent(dangerousLabel); + dangerousCategoriesPanel.add(dangerousLabel); + + for (Tag.Category category : DANGEROUS_CATEGORIES) { + JCheckBox categoryCheckbox = new JCheckBox(category.name()); + categoryCheckbox.setFont(new Font("Inter", Font.PLAIN, 12)); + categoryCheckbox.setCursor(new Cursor(Cursor.HAND_CURSOR)); + categoryCheckbox.setToolTipText("Enable " + category.name() + " tags (may execute code)"); + categoryCheckbox.setSelected(enabledDangerousCategories.contains(category)); + categoryCheckbox.addActionListener(e -> { + if (categoryCheckbox.isSelected()) { + enabledDangerousCategories.add(category); + } else { + enabledDangerousCategories.remove(category); + } + refreshAllLayers(); + }); + montoyaApi.userInterface().applyThemeToComponent(categoryCheckbox); + dangerousCategoriesPanel.add(categoryCheckbox); + dangerousCategoryCheckboxes.put(category, categoryCheckbox); + } + montoyaApi.userInterface().applyThemeToComponent(dangerousCategoriesPanel); + + JPanel categoriesInnerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); + for (Tag.Category category : Tag.Category.values()) { + if (DANGEROUS_CATEGORIES.contains(category)) { + continue; + } + enabledCategories.add(category); + JCheckBox categoryCheckbox = new JCheckBox(category.name()); + categoryCheckbox.setFont(new Font("Inter", Font.PLAIN, 12)); + categoryCheckbox.setCursor(new Cursor(Cursor.HAND_CURSOR)); + categoryCheckbox.setToolTipText("Show " + category.name() + " tags"); + categoryCheckbox.setSelected(true); + categoryCheckbox.addActionListener(e -> { + if (categoryCheckbox.isSelected()) { + enabledCategories.add(category); + } else { + enabledCategories.remove(category); + } + refreshAllLayers(); + }); + montoyaApi.userInterface().applyThemeToComponent(categoryCheckbox); + categoriesInnerPanel.add(categoryCheckbox); + categoryCheckboxes.put(category, categoryCheckbox); + } + montoyaApi.userInterface().applyThemeToComponent(categoriesInnerPanel); + + JScrollPane categoriesScrollPane = new JScrollPane(categoriesInnerPanel); + categoriesScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + categoriesScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); + categoriesScrollPane.setBorder(BorderFactory.createTitledBorder("Categories")); + categoriesScrollPane.setPreferredSize(new Dimension(DEFAULT_WIDTH - 50, 60)); + categoriesScrollPane.getHorizontalScrollBar().setUnitIncrement(16); + montoyaApi.userInterface().applyThemeToComponent(categoriesScrollPane); + + JPanel allCategoriesPanel = new JPanel(new BorderLayout()); + allCategoriesPanel.add(categoriesScrollPane, BorderLayout.CENTER); + allCategoriesPanel.add(dangerousCategoriesPanel, BorderLayout.SOUTH); + montoyaApi.userInterface().applyThemeToComponent(allCategoriesPanel); + + JPanel layerControlPanel = new JPanel(new BorderLayout()); + layerControlPanel.add(layerButtonPanel, BorderLayout.WEST); + layerControlPanel.add(allCategoriesPanel, BorderLayout.CENTER); + montoyaApi.userInterface().applyThemeToComponent(layerControlPanel); + + topPanel.add(layerControlPanel, BorderLayout.NORTH); + topPanel.add(layerTabbedPane, BorderLayout.CENTER); + + JPanel previewPanel = new JPanel(new BorderLayout()); + previewPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + + JPanel previewSearchPanel = new JPanel(new BorderLayout(5, 0)); + previewSearchPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); + JLabel previewSearchLabel = new JLabel("Filter:"); + previewSearchLabel.setFont(new Font("Inter", Font.PLAIN, 12)); + montoyaApi.userInterface().applyThemeToComponent(previewSearchLabel); + previewSearchField = new JTextField(); + previewSearchField.setFont(new Font("Monospaced", Font.PLAIN, 12)); + previewSearchField.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + applyPreviewFilter(); + } + }); + montoyaApi.userInterface().applyThemeToComponent(previewSearchField); + previewSearchPanel.add(previewSearchLabel, BorderLayout.WEST); + previewSearchPanel.add(previewSearchField, BorderLayout.CENTER); + montoyaApi.userInterface().applyThemeToComponent(previewSearchPanel); + + previewArea = new JTextPane(); + previewArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); + previewArea.setEditable(false); + montoyaApi.userInterface().applyThemeToComponent(previewArea); + + JScrollPane previewScrollPane = new JScrollPane(previewArea); + previewScrollPane.setPreferredSize(new Dimension(DEFAULT_WIDTH - 50, 180)); + previewScrollPane.setBorder(BorderFactory.createTitledBorder("Preview")); + montoyaApi.userInterface().applyThemeToComponent(previewScrollPane); + + previewPanel.add(previewSearchPanel, BorderLayout.NORTH); + previewPanel.add(previewScrollPane, BorderLayout.CENTER); + + statusLabel = new JLabel(" "); + statusLabel.setFont(new Font("Inter", Font.BOLD, 12)); + statusLabel.setHorizontalAlignment(SwingConstants.CENTER); + statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); + montoyaApi.userInterface().applyThemeToComponent(statusLabel); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + montoyaApi.userInterface().applyThemeToComponent(buttonPanel); + + JLabel modeLabel = new JLabel("Mode:"); + modeLabel.setFont(new Font("Inter", Font.PLAIN, 12)); + montoyaApi.userInterface().applyThemeToComponent(modeLabel); + + String[] modes = {"Convert", "Add tags"}; + modeComboBox = new JComboBox<>(modes); + modeComboBox.setSelectedItem("Convert"); + modeComboBox.addActionListener(e -> updatePreview()); + montoyaApi.userInterface().applyThemeToComponent(modeComboBox); + + JButton previewButton = new JButton("Update Preview"); + previewButton.addActionListener(e -> updatePreview()); + montoyaApi.userInterface().applyThemeToComponent(previewButton); + + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(e -> window.dispose()); + montoyaApi.userInterface().applyThemeToComponent(cancelButton); + + JButton clearButton = new JButton("Clear"); + clearButton.addActionListener(e -> { + for (Layer layer : layers) { + for (JCheckBox checkbox : layer.tagCheckboxes.values()) { + checkbox.setSelected(false); + } + layer.selectedTags.clear(); + layer.selectAllCheckbox.setSelected(false); + } + previewArea.setText(""); + lastPreviewContent = ""; + previewSearchField.setText(""); + }); + montoyaApi.userInterface().applyThemeToComponent(clearButton); + + JButton copyButton = new JButton("Copy"); + copyButton.addActionListener(e -> copyToClipboard()); + montoyaApi.userInterface().applyThemeToComponent(copyButton); + + buttonPanel.add(clearButton); + buttonPanel.add(copyButton); + buttonPanel.add(new JSeparator(SwingConstants.VERTICAL)); + buttonPanel.add(modeLabel); + buttonPanel.add(modeComboBox); + buttonPanel.add(new JSeparator(SwingConstants.VERTICAL)); + buttonPanel.add(previewButton); + buttonPanel.add(new JSeparator(SwingConstants.VERTICAL)); + + if (hackvertorCallback != null) { + JButton sendToHackvertorButton = new JButton("Send to Hackvertor"); + sendToHackvertorButton.addActionListener(e -> sendToHackvertor()); + montoyaApi.userInterface().applyThemeToComponent(sendToHackvertorButton); + buttonPanel.add(sendToHackvertorButton); + } else { + JButton sendToRepeaterButton = new JButton("Send to Repeater"); + sendToRepeaterButton.addActionListener(e -> sendToRepeater()); + montoyaApi.userInterface().applyThemeToComponent(sendToRepeaterButton); + + JButton sendToIntruderButton = new JButton("Send to Intruder"); + sendToIntruderButton.addActionListener(e -> sendToIntruder()); + montoyaApi.userInterface().applyThemeToComponent(sendToIntruderButton); + + buttonPanel.add(sendToRepeaterButton); + buttonPanel.add(sendToIntruderButton); + } + + buttonPanel.add(new JSeparator(SwingConstants.VERTICAL)); + buttonPanel.add(cancelButton); + + window.addWindowFocusListener(new java.awt.event.WindowAdapter() { + @Override + public void windowLostFocus(java.awt.event.WindowEvent e) { + saveState(); + window.dispose(); + } + }); + + JPanel contentPanel = new JPanel(new BorderLayout()); + contentPanel.add(titlePanel, BorderLayout.NORTH); + contentPanel.add(topPanel, BorderLayout.CENTER); + montoyaApi.userInterface().applyThemeToComponent(contentPanel); + + JPanel southPanel = new JPanel(new BorderLayout()); + southPanel.add(statusLabel, BorderLayout.NORTH); + southPanel.add(buttonPanel, BorderLayout.CENTER); + montoyaApi.userInterface().applyThemeToComponent(southPanel); + + mainPanel.add(contentPanel, BorderLayout.NORTH); + mainPanel.add(previewPanel, BorderLayout.CENTER); + mainPanel.add(southPanel, BorderLayout.SOUTH); + + applyRoundedCorners.run(); + window.add(mainPanel); + + montoyaApi.userInterface().applyThemeToComponent(window.getContentPane()); + window.setLocationRelativeTo(montoyaApi.userInterface().swingUtils().suiteFrame()); + + int savedLayerCount = getSavedLayerCount(); + int layersToCreate = Math.max(1, savedLayerCount); + for (int i = 0; i < layersToCreate; i++) { + addLayer(); + if (i < savedLayerCount) { + loadLayerState(layers.get(i), i); + } + } + updatePreview(); + + window.setVisible(true); + if (!layers.isEmpty()) { + layers.get(0).searchField.requestFocusInWindow(); + } + }); + } + + private void addLayer() { + JPanel layerPanel = new JPanel(new BorderLayout()); + layerPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + montoyaApi.userInterface().applyThemeToComponent(layerPanel); + + JTextField searchField = new JTextField(); + searchField.setFont(new Font("Monospaced", Font.PLAIN, 14)); + searchField.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8)); + montoyaApi.userInterface().applyThemeToComponent(searchField); + + JLabel searchLabel = new JLabel("Search tags: "); + searchLabel.setFont(new Font("Inter", Font.PLAIN, 13)); + montoyaApi.userInterface().applyThemeToComponent(searchLabel); + + JPanel searchPanel = new JPanel(new BorderLayout()); + searchPanel.add(searchLabel, BorderLayout.WEST); + searchPanel.add(searchField, BorderLayout.CENTER); + montoyaApi.userInterface().applyThemeToComponent(searchPanel); + + JCheckBox selectAllCheckbox = new JCheckBox("Select all"); + selectAllCheckbox.setFont(new Font("Inter", Font.PLAIN, 12)); + selectAllCheckbox.setCursor(new Cursor(Cursor.HAND_CURSOR)); + selectAllCheckbox.setBorder(BorderFactory.createEmptyBorder(8, 0, 8, 0)); + montoyaApi.userInterface().applyThemeToComponent(selectAllCheckbox); + + JPanel searchAndSelectAllPanel = new JPanel(new BorderLayout()); + searchAndSelectAllPanel.add(searchPanel, BorderLayout.CENTER); + searchAndSelectAllPanel.add(selectAllCheckbox, BorderLayout.SOUTH); + montoyaApi.userInterface().applyThemeToComponent(searchAndSelectAllPanel); + + JPanel tagsPanel = new JPanel(new GridBagLayout()); + montoyaApi.userInterface().applyThemeToComponent(tagsPanel); + + JScrollPane tagsScrollPane = new JScrollPane(tagsPanel); + tagsScrollPane.setBorder(BorderFactory.createTitledBorder("Select Tags")); + tagsScrollPane.getVerticalScrollBar().setUnitIncrement(16); + montoyaApi.userInterface().applyThemeToComponent(tagsScrollPane); + + layerPanel.add(searchAndSelectAllPanel, BorderLayout.NORTH); + layerPanel.add(tagsScrollPane, BorderLayout.CENTER); + + Layer layer = new Layer(tagsPanel, searchField, selectAllCheckbox); + + Function> filterTags = searchText -> { + ArrayList filtered = new ArrayList<>(); + String lowerSearch = searchText.toLowerCase(); + for (Tag tag : tags) { + if (DANGEROUS_CATEGORIES.contains(tag.category)) { + if (!enabledDangerousCategories.contains(tag.category)) { + continue; + } + } else { + if (!enabledCategories.contains(tag.category)) { + continue; + } + } + if (lowerSearch.isEmpty() || + tag.name.toLowerCase().contains(lowerSearch) || + tag.category.toString().toLowerCase().contains(lowerSearch) || + (tag.tooltip != null && tag.tooltip.toLowerCase().contains(lowerSearch))) { + filtered.add(tag); + } + } + filtered.sort((a, b) -> a.name.compareToIgnoreCase(b.name)); + return filtered; + }; + + Function createTagCheckbox = tag -> { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); + JCheckBox checkbox = new JCheckBox(tag.name); + checkbox.setToolTipText(tag.tooltip != null ? tag.tooltip : tag.category.toString()); + checkbox.setFont(new Font("Inter", Font.PLAIN, 12)); + checkbox.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + montoyaApi.userInterface().applyThemeToComponent(checkbox); + montoyaApi.userInterface().applyThemeToComponent(panel); + + layer.tagCheckboxes.put(tag.name, checkbox); + + checkbox.addActionListener(e -> { + if (checkbox.isSelected()) { + if (!layer.selectedTags.contains(tag)) { + layer.selectedTags.add(tag); + } + } else { + layer.selectedTags.remove(tag); + } + updatePreview(); + }); + + panel.add(checkbox); + return panel; + }; + + Runnable updateTags = () -> { + tagsPanel.removeAll(); + layer.tagCheckboxes.clear(); + + ArrayList filteredTags = filterTags.apply(searchField.getText()); + + if (!filteredTags.isEmpty()) { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(3, 3, 3, 3); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridy = 0; + + int currentColumn = 0; + for (Tag tag : filteredTags) { + JPanel tagPanel = createTagCheckbox.apply(tag); + gbc.gridx = currentColumn; + gbc.weightx = 1.0 / COLUMN_COUNT; + tagsPanel.add(tagPanel, gbc); + + if (++currentColumn >= COLUMN_COUNT) { + currentColumn = 0; + gbc.gridy++; + } + } + } else { + JLabel noResultsLabel = new JLabel("No tags found matching: " + searchField.getText()); + noResultsLabel.setFont(new Font("Inter", Font.PLAIN, 13)); + montoyaApi.userInterface().applyThemeToComponent(noResultsLabel); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = COLUMN_COUNT; + tagsPanel.add(noResultsLabel, gbc); + } + + tagsPanel.revalidate(); + tagsPanel.repaint(); + }; + + layer.updateTags = updateTags; + + java.awt.event.ActionListener selectAllListener = new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent e) { + ArrayList filteredTags = filterTags.apply(searchField.getText()); + boolean selectAll = selectAllCheckbox.isSelected(); + + if (selectAll && filteredTags.size() > MAX_TAGS_PER_LAYER) { + showWarningMessage("Too many tags (" + filteredTags.size() + "). Max is " + MAX_TAGS_PER_LAYER + ". Use search to filter."); + } + + int count = 0; + for (Tag tag : filteredTags) { + if (selectAll && count >= MAX_TAGS_PER_LAYER) { + break; + } + JCheckBox checkbox = layer.tagCheckboxes.get(tag.name); + if (checkbox != null && checkbox.isSelected() != selectAll) { + checkbox.setSelected(selectAll); + if (selectAll) { + if (!layer.selectedTags.contains(tag)) { + layer.selectedTags.add(tag); + } + } else { + layer.selectedTags.remove(tag); + } + } + count++; + } + updatePreview(); + } + }; + selectAllCheckbox.addActionListener(selectAllListener); + + searchField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() { + public void insertUpdate(javax.swing.event.DocumentEvent e) { + selectAllCheckbox.setSelected(false); + updateTags.run(); + } + public void removeUpdate(javax.swing.event.DocumentEvent e) { + selectAllCheckbox.setSelected(false); + updateTags.run(); + } + public void changedUpdate(javax.swing.event.DocumentEvent e) { + selectAllCheckbox.setSelected(false); + updateTags.run(); + } + }); + + searchField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + window.dispose(); + } + } + }); + + layers.add(layer); + String tabTitle = "Layer " + layerCounter++; + layerTabbedPane.addTab(tabTitle, layerPanel); + layerTabbedPane.setSelectedIndex(layerTabbedPane.getTabCount() - 1); + + updateTags.run(); + } + + private void removeCurrentLayer() { + if (layers.size() <= 1) { + return; + } + int selectedIndex = layerTabbedPane.getSelectedIndex(); + if (selectedIndex >= 0 && selectedIndex < layers.size()) { + layers.remove(selectedIndex); + layerTabbedPane.removeTabAt(selectedIndex); + updatePreview(); + } + } + + private void refreshAllLayers() { + for (Layer layer : layers) { + layer.selectedTags.removeIf(tag -> { + if (DANGEROUS_CATEGORIES.contains(tag.category)) { + return !enabledDangerousCategories.contains(tag.category); + } else { + return !enabledCategories.contains(tag.category); + } + }); + layer.updateTags.run(); + } + updatePreview(); + } + + private ArrayList> getAllLayerTags() { + ArrayList> allLayerTags = new ArrayList<>(); + for (Layer layer : layers) { + if (!layer.selectedTags.isEmpty()) { + allLayerTags.add(new ArrayList<>(layer.selectedTags)); + } + } + return allLayerTags; + } + + private void showStatusMessage(String message, Color color) { + if (statusClearTimer != null && statusClearTimer.isRunning()) { + statusClearTimer.stop(); + } + statusLabel.setText(message); + statusLabel.setForeground(color); + statusClearTimer = new Timer(5000, e -> { + statusLabel.setText(" "); + statusClearTimer.stop(); + }); + statusClearTimer.setRepeats(false); + statusClearTimer.start(); + } + + private void showWarningMessage(String message) { + showStatusMessage("⚠ " + message, new Color(255, 165, 0)); + } + + private void showInfoMessage(String message) { + showStatusMessage("✓ " + message, new Color(0, 128, 0)); + } + + private void showErrorMessage(String message) { + showStatusMessage("✗ " + message, new Color(200, 0, 0)); + } + + private String convertWithTimeout(String taggedText) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(() -> + HackvertorExtension.hackvertor.convert(taggedText, HackvertorExtension.hackvertor) + ); + + try { + return future.get(CONVERT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (TimeoutException e) { + future.cancel(true); + return "Error: Conversion timed out after " + CONVERT_TIMEOUT_SECONDS + " seconds"; + } catch (Exception e) { + return "Error: " + e.getMessage(); + } finally { + executor.shutdownNow(); + } + } + + private int calculateTotalVariants() { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + return 1; + } + int total = 1; + for (ArrayList layerTags : allLayerTags) { + int layerSize = Math.min(layerTags.size(), MAX_TAGS_PER_LAYER); + if (total > MAX_VARIANTS_TOTAL / layerSize) { + return MAX_VARIANTS_TOTAL + 1; + } + total *= layerSize; + } + return total; + } + + private ArrayList generateAllVariants(String input, boolean shouldConvert) { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + ArrayList result = new ArrayList<>(); + result.add(input); + return result; + } + + int estimatedTotal = calculateTotalVariants(); + if (estimatedTotal > MAX_VARIANTS_TOTAL) { + ArrayList result = new ArrayList<>(); + result.add("Error: Too many variants (" + estimatedTotal + "+). Maximum allowed is " + MAX_VARIANTS_TOTAL + ". Please reduce tag selection."); + return result; + } + + ArrayList currentVariants = new ArrayList<>(); + currentVariants.add(input); + + for (ArrayList layerTags : allLayerTags) { + ArrayList newVariants = new ArrayList<>(); + int tagCount = 0; + for (String variant : currentVariants) { + for (Tag tag : layerTags) { + if (tagCount >= MAX_TAGS_PER_LAYER) { + break; + } + if (newVariants.size() >= MAX_VARIANTS_TOTAL) { + break; + } + String[] tagStartEnd = Convertors.generateTagStartEnd(tag); + String taggedText = tagStartEnd[0] + variant + tagStartEnd[1]; + + if (shouldConvert) { + String converted = convertWithTimeout(taggedText); + newVariants.add(converted); + } else { + newVariants.add(taggedText); + } + tagCount++; + } + if (newVariants.size() >= MAX_VARIANTS_TOTAL) { + break; + } + tagCount = 0; + } + if (newVariants.size() >= MAX_VARIANTS_TOTAL) { + currentVariants = newVariants; + break; + } + currentVariants = newVariants; + } + + return currentVariants; + } + + private String getLayersSummary() { + StringBuilder summary = new StringBuilder(); + ArrayList> allLayerTags = getAllLayerTags(); + for (int i = 0; i < allLayerTags.size(); i++) { + ArrayList layerTags = allLayerTags.get(i); + summary.append("Layer ").append(i + 1).append(": "); + for (int j = 0; j < layerTags.size(); j++) { + if (j > 0) summary.append(", "); + summary.append(layerTags.get(j).name); + } + summary.append("\n"); + } + return summary.toString(); + } + + private void copyToClipboard() { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + return; + } + boolean shouldConvert = "Convert".equals(modeComboBox.getSelectedItem()); + ArrayList taggedVariants = generateAllVariants(selectedText, false); + ArrayList resultVariants = generateAllVariants(selectedText, shouldConvert); + String filterText = previewSearchField.getText().toLowerCase(); + + ArrayList variantsToCopy = new ArrayList<>(); + if (filterText.isEmpty()) { + variantsToCopy.addAll(resultVariants); + } else { + for (int i = 0; i < taggedVariants.size(); i++) { + if (taggedVariants.get(i).toLowerCase().contains(filterText)) { + variantsToCopy.add(resultVariants.get(i)); + } + } + } + + if (variantsToCopy.isEmpty()) { + showWarningMessage("No variants match the filter."); + return; + } + + StringBuilder output = new StringBuilder(); + for (int i = 0; i < variantsToCopy.size(); i++) { + if (i > 0) output.append("\n"); + output.append(variantsToCopy.get(i)); + } + StringSelection selection = new StringSelection(output.toString()); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(selection, null); + + if (!filterText.isEmpty()) { + showInfoMessage("Copied " + variantsToCopy.size() + " filtered variant(s)."); + } else { + showInfoMessage("Copied " + variantsToCopy.size() + " variant(s)."); + } + } + + private void updatePreview() { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + lastPreviewContent = "No tags selected. Please select at least one tag in any layer."; + previewArea.setText(lastPreviewContent); + return; + } + + StringBuilder preview = new StringBuilder(); + preview.append("Selected text: ").append(selectedText).append("\n"); + preview.append("Mode: ").append(modeComboBox.getSelectedItem()).append("\n"); + preview.append("=====================================\n\n"); + preview.append(getLayersSummary()); + + boolean shouldConvert = "Convert".equals(modeComboBox.getSelectedItem()); + ArrayList taggedVariants = generateAllVariants(selectedText, false); + ArrayList resultVariants = generateAllVariants(selectedText, shouldConvert); + + preview.append("Total variants: ").append(resultVariants.size()).append("\n"); + if (resultVariants.size() > MAX_VARIANTS_DISPLAY) { + preview.append("Showing first ").append(MAX_VARIANTS_DISPLAY).append(" variants\n"); + } + preview.append("=====================================\n\n"); + + int displayLimit = Math.min(resultVariants.size(), MAX_VARIANTS_DISPLAY); + for (int i = 0; i < displayLimit; i++) { + preview.append("Variant ").append(i + 1).append(":\n"); + if (!shouldConvert) { + preview.append("Tagged: ").append(resultVariants.get(i)).append("\n"); + } else { + preview.append("Input: ").append(taggedVariants.get(i)).append("\n"); + preview.append("Result: ").append(resultVariants.get(i)).append("\n"); + } + preview.append("-------------------------------------\n"); + } + + if (resultVariants.size() > MAX_VARIANTS_DISPLAY) { + preview.append("\n... ").append(resultVariants.size() - MAX_VARIANTS_DISPLAY) + .append(" more variants not shown ...\n"); + } + + lastPreviewContent = preview.toString(); + applyPreviewFilter(); + } + + private void applyPreviewFilter() { + String filterText = previewSearchField.getText().toLowerCase(); + StyledDocument doc = previewArea.getStyledDocument(); + + Style defaultStyle = previewArea.addStyle("default", null); + StyleConstants.setBackground(defaultStyle, previewArea.getBackground()); + + Style highlightStyle = previewArea.addStyle("highlight", null); + StyleConstants.setBackground(highlightStyle, new Color(255, 255, 0)); + StyleConstants.setForeground(highlightStyle, Color.BLACK); + + if (filterText.isEmpty()) { + previewArea.setText(lastPreviewContent); + previewArea.setCaretPosition(0); + return; + } + + String[] lines = lastPreviewContent.split("\n"); + StringBuilder filteredContent = new StringBuilder(); + ArrayList highlights = new ArrayList<>(); + + for (String line : lines) { + if (line.toLowerCase().contains(filterText)) { + int lineStart = filteredContent.length(); + filteredContent.append(line).append("\n"); + + String lowerLine = line.toLowerCase(); + int searchStart = 0; + int idx; + while ((idx = lowerLine.indexOf(filterText, searchStart)) != -1) { + highlights.add(new int[]{lineStart + idx, filterText.length()}); + searchStart = idx + 1; + } + } + } + + if (filteredContent.isEmpty()) { + previewArea.setText("No matches found for: " + previewSearchField.getText()); + return; + } + + previewArea.setText(filteredContent.toString()); + + for (int[] highlight : highlights) { + doc.setCharacterAttributes(highlight[0], highlight[1], highlightStyle, false); + } + + previewArea.setCaretPosition(0); + } + + private void sendToHackvertor() { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + showWarningMessage("Please select at least one tag before sending to Hackvertor."); + return; + } + + boolean shouldConvert = "Convert".equals(modeComboBox.getSelectedItem()); + ArrayList variants = generateAllVariants(selectedText, shouldConvert); + + if (hackvertorCallback != null) { + StringBuilder output = new StringBuilder(); + for (int i = 0; i < variants.size(); i++) { + if (i > 0) output.append("\n"); + output.append(variants.get(i)); + } + hackvertorCallback.accept(output.toString()); + } + + window.dispose(); + } + + private void sendToRepeater() { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + showWarningMessage("Please select at least one tag before sending to Repeater."); + return; + } + + if (messageEditor == null || baseRequestResponse == null) { + showErrorMessage("Unable to access the original request."); + return; + } + + HttpRequest baseRequest = baseRequestResponse.request(); + String requestStr = baseRequest.toString(); + boolean shouldConvert = "Convert".equals(modeComboBox.getSelectedItem()); + ArrayList variants = generateAllVariants(selectedText, shouldConvert); + + String modePrefix = shouldConvert ? "HV-" : "HVT-"; + ArrayList layer1Tags = allLayerTags.get(0); + int variantsPerLayer1Tag = variants.size() / layer1Tags.size(); + for (int i = 0; i < variants.size(); i++) { + String variant = variants.get(i); + String modifiedRequestStr = requestStr.replace(selectedText, variant); + HttpRequest modifiedRequest = HttpRequest.httpRequest(modifiedRequestStr); + int layer1TagIndex = i / variantsPerLayer1Tag; + String layer1TagName = layer1Tags.get(layer1TagIndex).name; + String tabName = modePrefix + layer1TagName + "-" + selectedText.substring(0, Math.min(selectedText.length(), 10)); + montoyaApi.repeater().sendToRepeater(modifiedRequest, tabName); + } + + showInfoMessage("Sent " + variants.size() + " variant(s) to Repeater."); + window.dispose(); + } + + private void sendToIntruder() { + ArrayList> allLayerTags = getAllLayerTags(); + if (allLayerTags.isEmpty()) { + showWarningMessage("Please select at least one tag before sending to Intruder."); + return; + } + + if (messageEditor == null || baseRequestResponse == null) { + showErrorMessage("Unable to access the original request."); + return; + } + + HttpRequest baseRequest = baseRequestResponse.request(); + String requestStr = baseRequest.toString(); + + int startPos = requestStr.indexOf(selectedText); + int endPos = startPos + selectedText.length(); + + if (startPos == -1) { + showErrorMessage("Could not find the selected text in the request."); + return; + } + + Range insertionPoint = Range.range(startPos, endPos); + HttpRequestTemplate intruderTemplate = HttpRequestTemplate.httpRequestTemplate(baseRequest, Collections.singletonList(insertionPoint)); + String tabName = "HV-Layers-" + selectedText.substring(0, Math.min(selectedText.length(), 10)); + montoyaApi.intruder().sendToIntruder(baseRequestResponse.request().httpService(), intruderTemplate, tabName); + window.dispose(); + } + + private void saveState() { + try { + JSONObject state = new JSONObject(); + + JSONArray enabledCategories = new JSONArray(); + for (Tag.Category category : enabledDangerousCategories) { + enabledCategories.put(category.name()); + } + state.put("enabledDangerousCategories", enabledCategories); + + JSONArray layersArray = new JSONArray(); + for (Layer layer : layers) { + JSONObject layerObj = new JSONObject(); + layerObj.put("searchText", layer.searchField.getText()); + + JSONArray selectedTagNames = new JSONArray(); + for (Tag tag : layer.selectedTags) { + selectedTagNames.put(tag.name); + } + layerObj.put("selectedTags", selectedTagNames); + + layersArray.put(layerObj); + } + state.put("layers", layersArray); + + montoyaApi.persistence().extensionData().setString(PERSISTENCE_KEY, state.toString()); + } catch (Exception e) { + System.err.println("Failed to save MultiEncoder state: " + e.getMessage()); + } + } + + private void loadState() { + try { + String content = montoyaApi.persistence().extensionData().getString(PERSISTENCE_KEY); + if (content == null || content.isEmpty()) { + return; + } + + JSONObject state = new JSONObject(content); + + if (state.has("enabledDangerousCategories")) { + JSONArray enabledCategories = state.getJSONArray("enabledDangerousCategories"); + for (int i = 0; i < enabledCategories.length(); i++) { + String categoryName = enabledCategories.getString(i); + try { + Tag.Category category = Tag.Category.valueOf(categoryName); + if (DANGEROUS_CATEGORIES.contains(category)) { + enabledDangerousCategories.add(category); + } + } catch (IllegalArgumentException ignored) { + } + } + } + } catch (Exception e) { + System.err.println("Failed to load MultiEncoder state: " + e.getMessage()); + } + } + + private void loadLayerState(Layer layer, int layerIndex) { + try { + String content = montoyaApi.persistence().extensionData().getString(PERSISTENCE_KEY); + if (content == null || content.isEmpty()) { + return; + } + + JSONObject state = new JSONObject(content); + if (!state.has("layers")) { + return; + } + + JSONArray layersArray = state.getJSONArray("layers"); + if (layerIndex >= layersArray.length()) { + return; + } + + JSONObject layerObj = layersArray.getJSONObject(layerIndex); + + if (layerObj.has("searchText")) { + layer.searchField.setText(layerObj.getString("searchText")); + } + + if (layerObj.has("selectedTags")) { + JSONArray selectedTagNames = layerObj.getJSONArray("selectedTags"); + for (int i = 0; i < selectedTagNames.length(); i++) { + String tagName = selectedTagNames.getString(i); + JCheckBox checkbox = layer.tagCheckboxes.get(tagName); + if (checkbox != null) { + checkbox.setSelected(true); + for (Tag tag : tags) { + if (tag.name.equals(tagName)) { + if (!layer.selectedTags.contains(tag)) { + layer.selectedTags.add(tag); + } + break; + } + } + } + } + } + } catch (Exception e) { + System.err.println("Failed to load layer state: " + e.getMessage()); + } + } + + private int getSavedLayerCount() { + try { + String content = montoyaApi.persistence().extensionData().getString(PERSISTENCE_KEY); + if (content == null || content.isEmpty()) { + return 0; + } + + JSONObject state = new JSONObject(content); + if (!state.has("layers")) { + return 0; + } + + return state.getJSONArray("layers").length(); + } catch (Exception e) { + return 0; + } + } +} diff --git a/src/main/java/burp/hv/utils/Utils.java b/src/main/java/burp/hv/utils/Utils.java index ece2910..fef13ed 100644 --- a/src/main/java/burp/hv/utils/Utils.java +++ b/src/main/java/burp/hv/utils/Utils.java @@ -74,6 +74,7 @@ public static void registerGeneralSettings(Settings settings) { settings.registerBooleanSetting("tagsInScanner", true, "Allow tags in Scanner", "Tag permissions", null); settings.registerBooleanSetting("tagsInExtensions", true, "Allow tags in Extensions", "Tag permissions", null); settings.registerBooleanSetting("tagsInResponse", false, "Allow tags in HTTP response", "Tag permissions", null); + settings.registerBooleanSetting("tagsInWebSockets", false, "Allow tags in WebSockets", "Tag permissions", null); settings.registerBooleanSetting("codeExecutionTagsEnabled", false, "Allow code execution tags", "Tag permissions", "Using code execution tags on untrusted requests can compromise your system, are you sure?"); settings.registerBooleanSetting("autoUpdateContentLength", true, "Auto update content length", "Requests", null); settings.registerIntegerSetting("maxBodyLength", 3 * 1024 * 1024, "Maximum body length", "Requests"); diff --git a/src/test/java/burp/AutoDecodeNestedTests.java b/src/test/java/burp/AutoDecodeNestedTests.java new file mode 100644 index 0000000..7c94269 --- /dev/null +++ b/src/test/java/burp/AutoDecodeNestedTests.java @@ -0,0 +1,931 @@ +package burp; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class AutoDecodeNestedTests extends BaseHackvertorTest { + + @Test + void testAsciiHexSpaceToCssEscapesToBase64_3Levels() { + String input = "5c 35 41 5c 36 44 5c 33 39 5c 37 36 5c 35 39 5c 36 44 5c 34 36 5c 37 39"; + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + input + "", hackvertor); + assertEquals("<@ascii2hex(\" \")><@css_escapes><@base64>foobar", decoded); + } + + @Test + void testUnicodeEscapesToBase32_2Levels() { + String input = "\\u004D\\u005A\\u0058\\u0057\\u0036\\u0059\\u0054\\u0042\\u004F\\u0049\\u003D\\u003D\\u003D\\u003D\\u003D\\u003D"; + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + input + "", hackvertor); + assertEquals("<@unicode_escapes><@base32>foobar", decoded); + } + + @Test + void testGzipToBase64_2Levels() { + String encoded = hackvertor.convert("<@gzip_compress><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64>foobar", decoded); + } + + @Test + void testDeflateToBase64_2Levels() { + String encoded = hackvertor.convert("<@deflate_compress><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base64>foobar", decoded); + } + + @Test + void testBase32ToBase64_2Levels() { + String encoded = hackvertor.convert("<@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@base64>foobar", decoded); + } + + @Test + void testUrlencodeToBase64_2Levels() { + String encoded = hackvertor.convert("<@urlencode_all><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64>foobar", decoded); + } + + @Test + void testHexEscapesToBase64_2Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64>foobar", decoded); + } + + @Test + void testOctalEscapesToBase64_2Levels() { + String encoded = hackvertor.convert("<@octal_escapes><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@octal_escapes><@base64>foobar", decoded); + } + + @Test + void testHexEntitiesToBase64_2Levels() { + String encoded = hackvertor.convert("<@hex_entities><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@base64>foobar", decoded); + } + + @Test + void testAsciiHexWithSeparatorToBase64_2Levels() { + String encoded = hackvertor.convert("<@ascii2hex(' ')><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@ascii2hex(\" \")><@base64>foobar", decoded); + } + + @Test + void testCharcodeToBase64_2Levels() { + String encoded = hackvertor.convert("<@to_charcode><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@to_charcode><@base64>foobar", decoded); + } + + @Test + void testCssEscapesToBase64_2Levels() { + String encoded = hackvertor.convert("<@css_escapes><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@css_escapes><@base64>foobar", decoded); + } + + @Test + void testUrlencodeToHexEscapesToBase64_3Levels() { + String encoded = hackvertor.convert("<@urlencode_all><@hex_escapes><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@hex_escapes><@base64>foobar", decoded); + } + + @Test + void testBase64ToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase32ToBase32ToBase32_3Levels() { + String encoded = hackvertor.convert("<@base32><@base32><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@base32><@base32>foobar", decoded); + } + + @Test + void testUnicodeEscapesToHexEscapesToBase64_3Levels() { + String encoded = hackvertor.convert("<@unicode_escapes><@hex_escapes><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@unicode_escapes><@hex_escapes><@base64>foobar", decoded); + } + + @Test + void testHexEntitiesToUrlencodeToBase64_3Levels() { + String encoded = hackvertor.convert("<@hex_entities><@urlencode_all><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@urlencode_not_plus><@base64>foobar", decoded); + } + + @Test + void testGzipToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@gzip_compress><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64><@base64>foobar", decoded); + } + + @Test + void testDeflateToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@deflate_compress><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64ToBase64ToBase64ToBase64_4Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64x5_5Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64x6_6Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64x7_7Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64x8_8Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64x9_9Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64x10_10Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testAsciiHexSpaceToBase64_2Levels() { + String encoded = hackvertor.convert("<@ascii2hex(' ')><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@ascii2hex(\" \")><@base64>foobar", decoded); + } + + @Test + void testAsciiHexSpaceToGzip_2Levels() { + String encoded = hackvertor.convert("<@ascii2hex(' ')><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@ascii2hex(\" \")><@gzip_compress>foobar", decoded); + } + + @Test + void testAsciiHexSpaceToDeflate_2Levels() { + String encoded = hackvertor.convert("<@ascii2hex(' ')><@deflate_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@ascii2hex(\" \")><@deflate_compress>foobar", decoded); + } + + @Test + void testBase32ToGzip_2Levels() { + String encoded = hackvertor.convert("<@base32><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@gzip_compress>foobar", decoded); + } + + @Test + void testBase32ToDeflate_2Levels() { + String encoded = hackvertor.convert("<@base32><@deflate_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@deflate_compress>foobar", decoded); + } + + @Test + void testCssEscapesToBase32_2Levels() { + String encoded = hackvertor.convert("<@css_escapes><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@css_escapes><@base32>foobar", decoded); + } + + @Test + void testHexEscapesToBase32_2Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base32>foobar", decoded); + } + + @Test + void testOctalEscapesToBase32_2Levels() { + String encoded = hackvertor.convert("<@octal_escapes><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@octal_escapes><@base32>foobar", decoded); + } + + @Test + void testHexEntitiesToBase32_2Levels() { + String encoded = hackvertor.convert("<@hex_entities><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@base32>foobar", decoded); + } + + @Test + void testUrlencodeToBase32_2Levels() { + String encoded = hackvertor.convert("<@urlencode_all><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base32>foobar", decoded); + } + + @Test + void testOctalEscapesToHexEscapesToBase64_3Levels() { + String encoded = hackvertor.convert("<@octal_escapes><@hex_escapes><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@octal_escapes><@hex_escapes><@base64>foobar", decoded); + } + + @Test + void testHexEntitiesToHexEntitiesToBase64_3Levels() { + String encoded = hackvertor.convert("<@hex_entities><@hex_entities><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@hex_entities><@base64>foobar", decoded); + } + + @Test + void testBase32ToDeflateToBase64_3Levels() { + String encoded = hackvertor.convert("<@base32><@deflate_compress><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@deflate_compress><@base64>foobar", decoded); + } + + @Test + void testAsciiHexSpaceToBase32ToGzip_3Levels() { + String encoded = hackvertor.convert("<@ascii2hex(' ')><@base32><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@ascii2hex(\" \")><@base32><@gzip_compress>foobar", decoded); + } + + @Test + void testHexEscapesToBase64ToBase32ToGzip_4Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base64><@base32><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64><@base32><@gzip_compress>foobar", decoded); + } + + @Test + void testGzipToBase32_2Levels() { + String encoded = hackvertor.convert("<@gzip_compress><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base32>foobar", decoded); + } + + @Test + void testDeflateToBase32_2Levels() { + String encoded = hackvertor.convert("<@deflate_compress><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base32>foobar", decoded); + } + + @Test + void testCharcodeToBase32_2Levels() { + String encoded = hackvertor.convert("<@to_charcode><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@to_charcode><@base32>foobar", decoded); + } + + @Test + void testBase32ToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@base32><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@base64><@base64>foobar", decoded); + } + + @Test + void testBase32ToGzipToBase64_3Levels() { + String encoded = hackvertor.convert("<@base32><@gzip_compress><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@gzip_compress><@base64>foobar", decoded); + } + + @Test + void testUrlencodeToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@urlencode_all><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64><@base64>foobar", decoded); + } + + @Test + void testHexEntitiesToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@hex_entities><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@base64><@base64>foobar", decoded); + } + + @Test + void testHexEscapesToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64><@base64>foobar", decoded); + } + + @Test + void testOctalEscapesToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@octal_escapes><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@octal_escapes><@base64><@base64>foobar", decoded); + } + + @Test + void testUnicodeEscapesToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@unicode_escapes><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@unicode_escapes><@base64><@base64>foobar", decoded); + } + + @Test + void testCssEscapesToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@css_escapes><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@css_escapes><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64ToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@base64><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base32><@base64>foobar", decoded); + } + + @Test + void testBase32ToBase64ToBase32_3Levels() { + String encoded = hackvertor.convert("<@base32><@base64><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@base64><@base32>foobar", decoded); + } + + @Test + void testBase64ToBase64ToBase32_3Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base32>foobar", decoded); + } + + @Test + void testBase32ToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@base32><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@base32><@base64>foobar", decoded); + } + + @Test + void testHexEntitiesToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@hex_entities><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@base32><@base64>foobar", decoded); + } + + @Test + void testUrlencodeToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@urlencode_all><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base32><@base64>foobar", decoded); + } + + @Test + void testHexEscapesToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base32><@base64>foobar", decoded); + } + + @Test + void testGzipToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@gzip_compress><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base32><@base64>foobar", decoded); + } + + @Test + void testDeflateToBase32ToBase64_3Levels() { + String encoded = hackvertor.convert("<@deflate_compress><@base32><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base32><@base64>foobar", decoded); + } + + @Test + void testBase64urlToBase64_2Levels() { + String encoded = hackvertor.convert("<@base64url><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64url><@base64>foobar", decoded); + } + + @Test + void testBase64urlToBase32_2Levels() { + String encoded = hackvertor.convert("<@base64url><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64url><@base32>foobar", decoded); + } + + @Test + void testBase64urlToGzip_2Levels() { + String encoded = hackvertor.convert("<@base64url><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64url><@gzip_compress>foobar", decoded); + } + + @Test + void testBase64urlToDeflate_2Levels() { + String encoded = hackvertor.convert("<@base64url><@deflate_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64url><@deflate_compress>foobar", decoded); + } + + @Test + void testHexEscapesToBase64urlWithGzip_2Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base64url><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64url><@gzip_compress>foobar", decoded); + } + + @Test + void testBase64urlToGzipToBase64_3Levels() { + String encoded = hackvertor.convert("<@base64url><@gzip_compress><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64url><@gzip_compress><@base64>foobar", decoded); + } + + @Test + void testBase58Single() { + String encoded = hackvertor.convert("<@base58>hello world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base58>hello world", decoded); + } + + @Test + void testBase58ToBase64_2Levels() { + String encoded = hackvertor.convert("<@base58><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base58><@base64>foobar", decoded); + } + + @Test + void testHexEscapesToBase58_2Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@base58>hello world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base58>hello world", decoded); + } + + @Test + void testBase58ToBase32_2Levels() { + String encoded = hackvertor.convert("<@base58><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base58><@base32>foobar", decoded); + } + + @Test + void testQuotedPrintableSingle() { + String encoded = hackvertor.convert("<@quoted_printable>hello=world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@quoted_printable>hello=world", decoded); + } + + @Test + void testQuotedPrintableToBase64_2Levels() { + String encoded = hackvertor.convert("<@quoted_printable><@base64>test", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@quoted_printable><@base64>test", decoded); + } + + @Test + void testHexEscapesToQuotedPrintable_2Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@quoted_printable>hello=world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@quoted_printable>hello=world", decoded); + } + + @Test + void testQuotedPrintableToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@quoted_printable><@base64><@base64>test", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@quoted_printable><@base64><@base64>test", decoded); + } + + @Test + void testUtf7Single() { + String encoded = hackvertor.convert("<@utf7>hello + world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@utf7>hello + world", decoded); + } + + @Test + void testUtf7ToBase64_2Levels() { + String encoded = hackvertor.convert("<@utf7><@base64>hello + world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@utf7><@base64>hello + world", decoded); + } + + @Test + void testHexEscapesToUtf7_2Levels() { + String encoded = hackvertor.convert("<@hex_escapes><@utf7>hello + world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@utf7>hello + world", decoded); + } + + @Test + void testUtf7ToBase64ToBase64_3Levels() { + String encoded = hackvertor.convert("<@utf7><@base64><@base64>hello + world", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@utf7><@base64><@base64>hello + world", decoded); + } + + @Test + void testBase64ToBase64ToBase64ToBase32_4Levels() { + String encoded = hackvertor.convert("<@base64><@base64><@base64><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64><@base64><@base32>foobar", decoded); + } + + @Test + void testBase32ToBase64ToBase64ToBase64_4Levels() { + String encoded = hackvertor.convert("<@base32><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testGzipToBase64ToBase64ToBase64_4Levels() { + String encoded = hackvertor.convert("<@gzip_compress><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testDeflateToBase64ToBase64ToBase64_4Levels() { + String encoded = hackvertor.convert("<@deflate_compress><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testHexEntitiesToBase64ToBase64ToBase64_4Levels() { + String encoded = hackvertor.convert("<@hex_entities><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testUrlencodeToBase64ToBase64ToBase64_4Levels() { + String encoded = hackvertor.convert("<@urlencode_all><@base64><@base64><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64><@base64><@base64>foobar", decoded); + } + + @Test + void testBase64XssPayload() { + String xss = ""; + String encoded = hackvertor.convert("<@base64>" + xss + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + xss + "", decoded); + } + + @Test + void testUrlencodeToBase64XssPayload() { + String xss = ""; + String encoded = hackvertor.convert("<@urlencode_all><@base64>" + xss + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64>" + xss + "", decoded); + } + + @Test + void testBase64SqlInjection() { + String sqli = "' OR 1=1--"; + String encoded = hackvertor.convert("<@base64>" + sqli + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + sqli + "", decoded); + } + + @Test + void testHexEscapesToBase64SqlInjection() { + String sqli = "'; DROP TABLE users;--"; + String encoded = hackvertor.convert("<@hex_escapes><@base64>" + sqli + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64>" + sqli + "", decoded); + } + + @Test + void testBase64JsonPayload() { + String json = "{\"user\":\"admin\",\"role\":\"superuser\",\"token\":\"abc123\"}"; + String encoded = hackvertor.convert("<@base64>" + json + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + json + "", decoded); + } + + @Test + void testGzipToBase64JsonPayload() { + String json = "{\"username\":\"admin\",\"password\":\"secret123\",\"remember\":true}"; + String encoded = hackvertor.convert("<@gzip_compress><@base64>" + json + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64>" + json + "", decoded); + } + + @Test + void testBase64UrlPath() { + String url = "https://example.com/api/users?id=1&action=delete"; + String encoded = hackvertor.convert("<@base64>" + url + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + url + "", decoded); + } + + @Test + void testUrlencodeToBase64UrlPath() { + String url = "https://target.com/admin/config.php?debug=true"; + String encoded = hackvertor.convert("<@urlencode_all><@base64>" + url + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64>" + url + "", decoded); + } + + @Test + void testBase64CommandInjection() { + String cmd = "; cat /etc/passwd"; + String encoded = hackvertor.convert("<@base64>" + cmd + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + cmd + "", decoded); + } + + @Test + void testUnicodeEscapesToBase64CommandInjection() { + String cmd = "| ls -la /var/www/html"; + String encoded = hackvertor.convert("<@unicode_escapes><@base64>" + cmd + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@unicode_escapes><@base64>" + cmd + "", decoded); + } + + @Test + void testBase64LdapInjection() { + String ldap = "*)(&(objectClass=*)(uid=admin))"; + String encoded = hackvertor.convert("<@base64>" + ldap + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + ldap + "", decoded); + } + + @Test + void testBase64XpathInjection() { + String xpath = "' or '1'='1"; + String encoded = hackvertor.convert("<@base64>" + xpath + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + xpath + "", decoded); + } + + @Test + void testBase64SstiPayload() { + String ssti = "{{constructor.constructor('return this')().process.mainModule.require('child_process').execSync('id')}}"; + String encoded = hackvertor.convert("<@base64>" + ssti + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + ssti + "", decoded); + } + + @Test + void testHexEscapesToBase64SstiPayload() { + String ssti = "${7*7}"; + String encoded = hackvertor.convert("<@hex_escapes><@base64>" + ssti + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64>" + ssti + "", decoded); + } + + @Test + void testBase64XxePayload() { + String xxe = "]>&xxe;"; + String encoded = hackvertor.convert("<@base64>" + xxe + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@html_entities>" + xxe + "", decoded); + } + + @Test + void testDeflateToBase64XxePayload() { + String xxe = "%remote;]>"; + String encoded = hackvertor.convert("<@deflate_compress><@base64>" + xxe + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base64>" + xxe + "", decoded); + } + + @Test + void testBase64HttpHeader() { + String header = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + String encoded = hackvertor.convert("<@base64>" + header + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + header + "", decoded); + } + + @Test + void testUrlencodeToBase64Cookie() { + String cookie = "session=abc123; admin=true; Path=/; HttpOnly"; + String encoded = hackvertor.convert("<@urlencode_all><@base64>" + cookie + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64>" + cookie + "", decoded); + } + + @Test + void testBase64CsrfToken() { + String csrf = "csrf_token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"; + String encoded = hackvertor.convert("<@base64>" + csrf + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + csrf + "", decoded); + } + + @Test + void testBase64HtmlInjection() { + String html = ""; + String encoded = hackvertor.convert("<@base64>" + html + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + html + "", decoded); + } + + @Test + void testHexEntitiesToBase64HtmlInjection() { + String html = ""; + String encoded = hackvertor.convert("<@hex_entities><@base64>" + html + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_entities><@base64>" + html + "", decoded); + } + + @Test + void testBase64OpenRedirect() { + String redirect = "//evil.com/phish?target=https://bank.com"; + String encoded = hackvertor.convert("<@base64>" + redirect + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + redirect + "", decoded); + } + + @Test + void testUrlencodeToBase64Ssrf() { + String ssrf = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"; + String encoded = hackvertor.convert("<@urlencode_all><@base64>" + ssrf + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64>" + ssrf + "", decoded); + } + + @Test + void testBase64PathTraversal() { + String path = "../../../etc/passwd"; + String encoded = hackvertor.convert("<@base64>" + path + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + path + "", decoded); + } + + @Test + void testOctalEscapesToBase64PathTraversal() { + String path = "....//....//....//etc/shadow"; + String encoded = hackvertor.convert("<@octal_escapes><@base64>" + path + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@octal_escapes><@base64>" + path + "", decoded); + } + + @Test + void testBase64NoSqlInjection() { + String nosql = "{\"$gt\":\"\"}"; + String encoded = hackvertor.convert("<@base64>" + nosql + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + nosql + "", decoded); + } + + @Test + void testGzipToBase64NoSqlInjection() { + String nosql = "{\"username\":{\"$ne\":null},\"password\":{\"$ne\":null}}"; + String encoded = hackvertor.convert("<@gzip_compress><@base64>" + nosql + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64>" + nosql + "", decoded); + } + + @Test + void testBase64GraphqlInjection() { + String graphql = "{__schema{types{name,fields{name}}}}"; + String encoded = hackvertor.convert("<@base64>" + graphql + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + graphql + "", decoded); + } + + @Test + void testBase64JwtPayload() { + String jwtPayload = "{\"sub\":\"admin\",\"iat\":1516239022,\"exp\":9999999999,\"role\":\"admin\"}"; + String encoded = hackvertor.convert("<@base64>" + jwtPayload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + jwtPayload + "", decoded); + } + + @Test + void testBase64ToBase64XssInJson() { + String payload = "{\"name\":\"\"}"; + String encoded = hackvertor.convert("<@base64><@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64><@base64>" + payload + "", decoded); + } + + @Test + void testUrlencodeToBase64ToBase64DeepNested() { + String payload = "{\"redirect\":\"javascript:alert(origin)\"}"; + String encoded = hackvertor.convert("<@urlencode_all><@base64><@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@urlencode_not_plus><@base64><@base64>" + payload + "", decoded); + } + + @Test + void testHexEscapesToBase64ToBase64SqlUnion() { + String sqli = "' UNION SELECT username,password FROM users--"; + String encoded = hackvertor.convert("<@hex_escapes><@base64><@base64>" + sqli + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@hex_escapes><@base64><@base64>" + sqli + "", decoded); + } + + @Test + void testBase64AwsCredentials() { + String creds = "aws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; + String encoded = hackvertor.convert("<@base64>" + creds + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + creds + "", decoded); + } + + @Test + void testGzipToBase64LargePayload() { + String payload = "SELECT * FROM users WHERE id=1; SELECT * FROM admin_users; SELECT * FROM secrets; SELECT * FROM api_keys; SELECT * FROM sessions WHERE active=1;"; + String encoded = hackvertor.convert("<@gzip_compress><@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64>" + payload + "", decoded); + } + + @Test + void testBase64PrototypePollution() { + String payload = "{\"__proto__\":{\"admin\":true}}"; + String encoded = hackvertor.convert("<@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + payload + "", decoded); + } + + @Test + void testCssEscapesToBase64PrototypePollution() { + String payload = "{\"constructor\":{\"prototype\":{\"isAdmin\":true}}}"; + String encoded = hackvertor.convert("<@css_escapes><@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@css_escapes><@base64>" + payload + "", decoded); + } + + @Test + void testBase64DeserializationPayload() { + String payload = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcA=="; + String encoded = hackvertor.convert("<@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + payload + "", decoded); + } + + @Test + void testBase64RegexDos() { + String payload = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"; + String encoded = hackvertor.convert("<@base64>" + payload + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + payload + "", decoded); + } + + @Test + void testBase32ToGzipXmlPayload() { + String xml = "adminpassword123"; + String encoded = hackvertor.convert("<@base32><@gzip_compress>" + xml + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base32><@gzip_compress>" + xml + "", decoded); + } + + @Test + void testBase64HostHeaderInjection() { + String header = "Host: evil.com\r\nX-Forwarded-Host: attacker.com"; + String encoded = hackvertor.convert("<@base64>" + header + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64>" + header + "", decoded); + } + + @Test + void testUnicodeEscapesToBase64CrlfInjection() { + String crlf = "header\r\nSet-Cookie: session=hijacked"; + String encoded = hackvertor.convert("<@unicode_escapes><@base64>" + crlf + "", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@unicode_escapes><@base64>" + crlf + "", decoded); + } +} diff --git a/src/test/java/burp/ConvertorTests.java b/src/test/java/burp/ConvertorTests.java index 7c0982d..1954061 100644 --- a/src/test/java/burp/ConvertorTests.java +++ b/src/test/java/burp/ConvertorTests.java @@ -504,4 +504,95 @@ void testMultipleSameTags() { String converted = hackvertor.convert(input, hackvertor); assertEquals("SGVsbG8= V29ybGQ=", converted); } + + @Test + void testAutoDecodeGzipBase64() { + String encoded = hackvertor.convert("<@gzip_compress><@base64>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@gzip_compress><@base64>foobar", decoded); + } + + @Test + void testAutoDecodeDeflateBase32() { + String encoded = hackvertor.convert("<@deflate_compress><@base32>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@deflate_compress><@base32>foobar", decoded); + } + + @Test + void testAutoDecodeBase32ThenDeflate() { + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>PCOALAFBBEAAAAGCN3KWAHPYP4MIHZQCBCVQE6Q=", hackvertor); + assertEquals("<@base32><@deflate_compress>foobar", decoded); + } + + @Test + void testAutoDecodeBase32NotMisidentifiedAsBase64() { + String base32Encoded = hackvertor.convert("<@base32>test", hackvertor); + assertEquals("ORSXG5A=", base32Encoded); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + base32Encoded + "", hackvertor); + assertEquals("<@base32>test", decoded); + } + + @Test + void testAutoDecodeBase64() { + String base64Encoded = hackvertor.convert("<@base64>foobar", hackvertor); + assertEquals("Zm9vYmFy", base64Encoded); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + base64Encoded + "", hackvertor); + assertEquals("<@base64>foobar", decoded); + } + + @Test + void testDeflateCompressDecompressDynamic() { + String input = "foobar"; + String compressed = hackvertor.convert("<@deflate_compress('dynamic')>" + input + "", hackvertor); + String decompressed = hackvertor.convert("<@deflate_decompress>" + compressed + "", hackvertor); + assertEquals(input, decompressed); + } + + @Test + void testDeflateCompressDecompressFixed() { + String input = "foobar"; + String compressed = hackvertor.convert("<@deflate_compress('fixed')>" + input + "", hackvertor); + String decompressed = hackvertor.convert("<@deflate_decompress>" + compressed + "", hackvertor); + assertEquals(input, decompressed); + } + + @Test + void testDeflateCompressDecompressStore() { + String input = "foobar"; + String compressed = hackvertor.convert("<@deflate_compress('store')>" + input + "", hackvertor); + String decompressed = hackvertor.convert("<@deflate_decompress>" + compressed + "", hackvertor); + assertEquals(input, decompressed); + } + + @Test + void testDeflateCompressDynamicBase32Output() { + String encoded = hackvertor.convert("<@base32><@deflate_compress('dynamic')>foobar", hackvertor); + assertEquals("PDNEXS6PJ5FCYAQABCVQE6Q=", encoded); + } + + @Test + void testDeflateCompressFixedBase32Output() { + String encoded = hackvertor.convert("<@base32><@deflate_compress('fixed')>foobar", hackvertor); + assertEquals("PAAUXS6PJ5FCYAQABCVQE6Q=", encoded); + } + + @Test + void testDeflateCompressStoreBase32Output() { + String encoded = hackvertor.convert("<@base32><@deflate_compress('store')>foobar", hackvertor); + assertEquals("PAAQCBQA7H7WM33PMJQXECFLAJ5A====", encoded); + } + + @Test + void testBase64urlGzipOutput() { + String encoded = hackvertor.convert("<@base64url><@gzip_compress>foobar", hackvertor); + assertTrue(encoded.matches("^[A-Za-z0-9_-]+$"), "base64url output should only contain base64url characters, got: " + encoded); + } + + @Test + void testAutoDecodeBase64urlGzip() { + String encoded = hackvertor.convert("<@base64url><@gzip_compress>foobar", hackvertor); + String decoded = hackvertor.convert("<@auto_decode_no_decrypt>" + encoded + "", hackvertor); + assertEquals("<@base64url><@gzip_compress>foobar", decoded); + } } \ No newline at end of file diff --git a/src/test/java/burp/stubs/StubCallbacks.java b/src/test/java/burp/stubs/StubCallbacks.java index b96d420..b6d20a0 100644 --- a/src/test/java/burp/stubs/StubCallbacks.java +++ b/src/test/java/burp/stubs/StubCallbacks.java @@ -99,7 +99,7 @@ public byte[] base64Decode(byte[] bytes) { @Override public String base64Encode(String s) { - return Base64.getEncoder().encodeToString(s.getBytes()); + return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.ISO_8859_1)); } @Override diff --git a/src/test/java/burp/stubs/StubExtensionHelpers.java b/src/test/java/burp/stubs/StubExtensionHelpers.java index 536ebf8..cf5488f 100644 --- a/src/test/java/burp/stubs/StubExtensionHelpers.java +++ b/src/test/java/burp/stubs/StubExtensionHelpers.java @@ -64,7 +64,7 @@ public byte[] base64Decode(byte[] data) { @Override public String base64Encode(String data) { - return Base64.getEncoder().encodeToString(data.getBytes()); + return Base64.getEncoder().encodeToString(data.getBytes(StandardCharsets.ISO_8859_1)); } @Override diff --git a/src/test/java/burp/ui/HackvertorUiTest.java b/src/test/java/burp/ui/HackvertorUiTest.java index f3b342b..4b22049 100644 --- a/src/test/java/burp/ui/HackvertorUiTest.java +++ b/src/test/java/burp/ui/HackvertorUiTest.java @@ -1474,6 +1474,108 @@ void testUrlencodeAllTag() throws Exception { Assertions.assertEquals("%74%65%73%74", outputText, "Output should contain all characters URL encoded"); } + @Test + void testAutoDecodeGzipBase64Ui() throws Exception { + window.robot().waitForIdle(); + + Component[] allTextAreas = window.robot().finder() + .findAll(window.target(), component -> component instanceof JTextArea) + .toArray(new Component[0]); + + JTextArea inputArea = null; + JTextArea outputArea = null; + int hackvertorInputCount = 0; + + for (Component component : allTextAreas) { + if (component.getClass().getName().equals("burp.hv.ui.HackvertorInput")) { + if (hackvertorInputCount == 0) { + inputArea = (JTextArea) component; + } else if (hackvertorInputCount == 1) { + outputArea = (JTextArea) component; + } + hackvertorInputCount++; + } + } + + Assertions.assertNotNull(inputArea, "Input area should be found"); + Assertions.assertNotNull(outputArea, "Output area should be found"); + + window.robot().click(inputArea); + window.robot().waitForIdle(); + + final JTextArea finalInputArea = inputArea; + final JTextArea finalOutputArea = outputArea; + GuiActionRunner.execute(() -> finalInputArea.setText("<@gzip_compress><@base64>foobar")); + window.robot().waitForIdle(); + + Thread.sleep(300); + window.robot().waitForIdle(); + + String encodedOutput = outputArea.getText(); + Assertions.assertFalse(encodedOutput.isEmpty(), "Output should contain gzip+base64 encoded data"); + + GuiActionRunner.execute(() -> finalInputArea.setText("<@auto_decode_no_decrypt>" + finalOutputArea.getText() + "")); + window.robot().waitForIdle(); + + Thread.sleep(300); + window.robot().waitForIdle(); + + String decodedOutput = outputArea.getText(); + Assertions.assertEquals("<@gzip_compress><@base64>foobar", decodedOutput, + "auto_decode_no_decrypt should produce correct gzip+base64 encoding tags"); + } + + @Test + void testAutoDecodeDeflateBase32Ui() throws Exception { + window.robot().waitForIdle(); + + Component[] allTextAreas = window.robot().finder() + .findAll(window.target(), component -> component instanceof JTextArea) + .toArray(new Component[0]); + + JTextArea inputArea = null; + JTextArea outputArea = null; + int hackvertorInputCount = 0; + + for (Component component : allTextAreas) { + if (component.getClass().getName().equals("burp.hv.ui.HackvertorInput")) { + if (hackvertorInputCount == 0) { + inputArea = (JTextArea) component; + } else if (hackvertorInputCount == 1) { + outputArea = (JTextArea) component; + } + hackvertorInputCount++; + } + } + + Assertions.assertNotNull(inputArea, "Input area should be found"); + Assertions.assertNotNull(outputArea, "Output area should be found"); + + window.robot().click(inputArea); + window.robot().waitForIdle(); + + final JTextArea finalInputArea = inputArea; + final JTextArea finalOutputArea = outputArea; + GuiActionRunner.execute(() -> finalInputArea.setText("<@deflate_compress><@base32>foobar")); + window.robot().waitForIdle(); + + Thread.sleep(500); + window.robot().waitForIdle(); + + String encodedOutput = outputArea.getText(); + Assertions.assertFalse(encodedOutput.isEmpty(), "Output should contain deflate+base32 encoded data"); + + GuiActionRunner.execute(() -> finalInputArea.setText("<@auto_decode_no_decrypt>" + finalOutputArea.getText() + "")); + window.robot().waitForIdle(); + + Thread.sleep(500); + window.robot().waitForIdle(); + + String decodedOutput = outputArea.getText(); + Assertions.assertEquals("<@deflate_compress><@base32>foobar", decodedOutput, + "auto_decode_no_decrypt should produce correct deflate+base32 encoding tags"); + } + @AfterEach void checkForUncaughtExceptions() { // Check for uncaught exceptions and fail the test if any occurred diff --git a/tag-store/tag-store.json b/tag-store/tag-store.json index 66b9645..97b4535 100644 --- a/tag-store/tag-store.json +++ b/tag-store/tag-store.json @@ -46,6 +46,19 @@ "numberOfArgs": 0, "language": "Python" }, + { + "tagName": "zipfile", + "description": "Create a zip file on the fly with one file in it, file name as argument 1, file content as input (raw by default, optionally in base64 - set second argument to 1)", + "author": "pentagridsec", + "numberOfArgs": 2, + "argument1": "filename", + "argument1Type": "String", + "argument1Default": "test.txt", + "argument2": "isBase64", + "argument2Type": "Number", + "argument2Default": "0", + "language": "Python" + }, { "tagName": "ip", "description": "Generates a random IP", diff --git a/tag-store/zipfile/zipfile.py b/tag-store/zipfile/zipfile.py new file mode 100644 index 0000000..732672f --- /dev/null +++ b/tag-store/zipfile/zipfile.py @@ -0,0 +1,125 @@ +# Pentagrid AG, pentagridsec + +# Work around the problem that zipfile is the name of the standard library but also this file +# Attention: Cursed Python ahead. +import sys +local = sys.path.pop(0) +import zipfile as zipfileLibrary +sys.path.insert(0, local) + + +import time + +# argument 1, filename, default "test.png" +try: + filename_from_argument = filename +except NameError: + filename_from_argument = "test.txt" +# argument 2, isBase64, default 0 +try: + is_base64_encoded = isBase64 +except NameError: + is_base64_encoded = 0 +# input (between tags), default empty string +file_content = input +# input is actually a built-in function of Python, so it's always defined... +if callable(file_content): + file_content = "This is the test file content" + +file_content = file_content.encode("utf-8") + +if is_base64_encoded: + file_content = file_content.decode("base64") + + +# Create zip file, using in-memory memory_buffer +# memory_buffer = StringIO.StringIO() +# As the above leads to unicode errors, try something else +JYTHON=False +PYTHON3=False +PYTHON2=False +try: + # Jython: use Java byte array output stream + from java.io import ByteArrayOutputStream + class MemoryBuffer(object): + def __init__(self): + self._buf = ByteArrayOutputStream() + self._pos = 0 + + def write(self, b): + if isinstance(b, unicode): + b = b.encode('utf-8') + if isinstance(b, bytearray): + b = bytes(b) + self._buf.write(b) # always calls write(byte[]) + self._pos = len(self._buf.toByteArray()) + + def tell(self): + return self._pos + + def seek(self, offset, whence=0): + # We don't support random-access writes, but ZipFile only uses seek(0) + if whence == 0 and offset == 0: + self._pos = 0 + else: + # Not required for writing ZIPs + pass + + def getvalue(self): + return self._buf.toByteArray() + + # ZipFile checks for "read" in some cases; safe to implement + def read(self, n=-1): + data = self._buf.toByteArray() + if n == -1: + return data + return data[:n] + + def flush(self): + pass + + memory_buffer = MemoryBuffer() + JYTHON=True +except ImportError: + # CPython / Python 3 + try: + # Python 3 + import io + memory_buffer = io.BytesIO() + PYTHON3=True + except ImportError: + # Python 2.7 + import cStringIO + memory_buffer = cStringIO.StringIO + PYTHON2=True + + +zf = zipfileLibrary.ZipFile(memory_buffer, mode='w', compression=zipfileLibrary.ZIP_DEFLATED) + +# Create empty directories if the file should be in directories +if '/' in filename_from_argument: + splitted = filename_from_argument.split("/") + directories = splitted[:-1] + parents = "" + for directory in directories: + dir_info = zipfileLibrary.ZipInfo(parents + directory + "/") + parents += directory + "/" + dir_info.date_time = time.localtime(time.time())[:6] + dir_info.external_attr = 0o755 << 16 + zf.writestr(dir_info, '') + +# Put file in zip file +info = zipfileLibrary.ZipInfo(filename_from_argument) +info.date_time = time.localtime(time.time())[:6] +info.external_attr = 0o777 << 16 +zf.writestr(info, file_content) + +zf.close() + + +if JYTHON: + output = ''.join(map(lambda x: chr(x % 256), memory_buffer.getvalue())) +else: + output = memory_buffer.getvalue() + +print(repr(output)) \ No newline at end of file