Advertisement
Earthcomputer

StackTraceDeobfuscator.java

Sep 20th, 2018
217
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 13.78 KB | None | 0 0
  1. package carpet.helpers;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.DataInputStream;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.InputStreamReader;
  8. import java.io.PrintStream;
  9. import java.net.MalformedURLException;
  10. import java.net.URL;
  11. import java.util.Arrays;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. import java.util.stream.Collectors;
  15. import java.util.zip.ZipEntry;
  16. import java.util.zip.ZipInputStream;
  17.  
  18. /**
  19.  * @author Earthcomputer
  20.  *
  21.  * Example: StackTraceDeobfuscator.create()
  22.  *              .withMinecraftVersion("1.12")
  23.  *              .withSnapshotMcpNames("20180713-1.12")
  24.  *              .withCurrentStackTrace()
  25.  *              .printDeobf();
  26.  */
  27. public class StackTraceDeobfuscator {
  28.  
  29.     private String srgUrl;
  30.     private String namesUrl;
  31.     private StackTraceElement[] stackTrace;
  32.     private ClassLoader classLoader = StackTraceDeobfuscator.class.getClassLoader();
  33.    
  34.     private Map<String, String> classMappings, methodMappings, methodDescCache, methodNames;
  35.    
  36.     private static final Map<String, Map<String, String>> classMappingsCache = new HashMap<>(),
  37.             methodMappingsCache = new HashMap<>(),
  38.             methodDescCaches = new HashMap<>(),
  39.             methodNamesCache = new HashMap<>();
  40.    
  41.     private StackTraceDeobfuscator() {}
  42.    
  43.     // BUILDER
  44.    
  45.     public static StackTraceDeobfuscator create() {
  46.         return new StackTraceDeobfuscator();
  47.     }
  48.    
  49.     public StackTraceDeobfuscator withSrgUrl(String srgUrl) {
  50.         this.srgUrl = srgUrl;
  51.         return this;
  52.     }
  53.    
  54.     public StackTraceDeobfuscator withMinecraftVersion(String minecraftVersion) {
  55.         return withSrgUrl(String.format("http://mcpbot.bspk.rs/mcp/%1$s/mcp-%1$s-srg.zip", minecraftVersion));
  56.     }
  57.    
  58.     public StackTraceDeobfuscator withNamesUrl(String namesUrl) {
  59.         this.namesUrl = namesUrl;
  60.         return this;
  61.     }
  62.    
  63.     // e.g. 39-1.12
  64.     public StackTraceDeobfuscator withStableMcpNames(String mcpVersion) {
  65.         return withNamesUrl(String.format("http://files.minecraftforge.net/maven/de/oceanlabs/mcp/mcp_stable/%1$s/mcp_stable-%1$s.zip", mcpVersion));
  66.     }
  67.    
  68.     // e.g. 20180204-1.12
  69.     public StackTraceDeobfuscator withSnapshotMcpNames(String mcpVersion) {
  70.         return withNamesUrl(String.format("http://files.minecraftforge.net/maven/de/oceanlabs/mcp/mcp_snapshot/%1$s/mcp_snapshot-%1$s.zip", mcpVersion));
  71.     }
  72.    
  73.     public StackTraceDeobfuscator withStackTrace(StackTraceElement[] stackTrace) {
  74.         this.stackTrace = stackTrace;
  75.         return this;
  76.     }
  77.    
  78.     public StackTraceDeobfuscator withCurrentStackTrace() {
  79.         StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
  80.         final String thisClass = getClass().getName();
  81.         boolean foundThisClass = false;
  82.         int firstIndex;
  83.         for (firstIndex = 0; firstIndex < stackTrace.length; firstIndex++) {
  84.             if (stackTrace[firstIndex].getClassName().equals(thisClass))
  85.                 foundThisClass = true;
  86.             else if (foundThisClass)
  87.                 break;
  88.         }
  89.         return withStackTrace(Arrays.copyOfRange(stackTrace, firstIndex, stackTrace.length));
  90.     }
  91.    
  92.     public StackTraceDeobfuscator withClassLoader(ClassLoader classLoader) {
  93.         this.classLoader = classLoader;
  94.         return this;
  95.     }
  96.    
  97.     // IMPLEMENTATION
  98.    
  99.     private void loadMappings() throws IOException {
  100.         if (methodMappingsCache.containsKey(srgUrl)) {
  101.             classMappings = classMappingsCache.get(srgUrl);
  102.             methodMappings = methodMappingsCache.get(srgUrl);
  103.             methodDescCache = methodDescCaches.get(srgUrl);
  104.         } else {
  105.             URL url;
  106.             try {
  107.                 url = new URL(srgUrl);
  108.             } catch (MalformedURLException e) {
  109.                 throw new RuntimeException(e);
  110.             }
  111.             ZipInputStream zipIn = new ZipInputStream(url.openConnection().getInputStream());
  112.             ZipEntry entry;
  113.             while ((entry = zipIn.getNextEntry()) != null) {
  114.                 if (entry.getName().equals("joined.srg")) {
  115.                     loadSrg(new BufferedReader(new InputStreamReader(zipIn)));
  116.                     break;
  117.                 } else {
  118.                     zipIn.closeEntry();
  119.                 }
  120.             }
  121.             zipIn.close();
  122.         }
  123.        
  124.         if (namesUrl != null) {
  125.             if (methodNamesCache.containsKey(namesUrl)) {
  126.                 methodNames = methodNamesCache.get(namesUrl);
  127.             } else {
  128.                 URL url;
  129.                 try {
  130.                     url = new URL(namesUrl);
  131.                 } catch (MalformedURLException e) {
  132.                     throw new RuntimeException(e);
  133.                 }
  134.                 ZipInputStream zipIn = new ZipInputStream(url.openConnection().getInputStream());
  135.                 ZipEntry entry;
  136.                 while ((entry = zipIn.getNextEntry()) != null) {
  137.                     if (entry.getName().equals("methods.csv")) {
  138.                         loadNames(new BufferedReader(new InputStreamReader(zipIn)));
  139.                         break;
  140.                     } else {
  141.                         zipIn.closeEntry();
  142.                     }
  143.                 }
  144.                 zipIn.close();
  145.             }
  146.         }
  147.     }
  148.    
  149.     private void loadSrg(BufferedReader in) {
  150.         classMappings = new HashMap<>();
  151.         methodMappings = new HashMap<>();
  152.         methodDescCache = new HashMap<>();
  153.         in.lines().map(line -> line.split(" ")).forEach(tokens -> {
  154.             if (tokens[0].equals("CL:")) {
  155.                 classMappings.put(tokens[1], tokens[2]);
  156.             } else if (tokens[0].equals("MD:")) {
  157.                 methodMappings.put(tokens[1] + tokens[2], tokens[3].substring(tokens[3].lastIndexOf('/') + 1));
  158.             }
  159.         });
  160.         classMappingsCache.put(srgUrl, classMappings);
  161.         methodMappingsCache.put(srgUrl, methodMappings);
  162.         methodDescCaches.put(srgUrl, methodDescCache);
  163.     }
  164.    
  165.     private void loadNames(BufferedReader in) {
  166.         methodNames = new HashMap<>();
  167.         in.lines().skip(1).map(line -> line.split(",")).forEach(tokens -> {
  168.             methodNames.put(tokens[0], tokens[1]);
  169.         });
  170.         methodNamesCache.put(namesUrl, methodNames);
  171.     }
  172.    
  173.     public void printDeobf() {
  174.         printDeobf(System.err);
  175.     }
  176.    
  177.     public void printDeobf(PrintStream out) {
  178.         out.println(deobfAsString());
  179.     }
  180.    
  181.     public String deobfAsString() {
  182.         StackTraceElement[] elems = deobfuscate();
  183.         return Arrays.stream(elems).map(StackTraceElement::toString).collect(Collectors.joining("\n"));
  184.     }
  185.    
  186.     public StackTraceElement[] deobfuscate() {
  187.         if (srgUrl == null) {
  188.             throw new IllegalStateException("No mappings url has been set");
  189.         }
  190.         if (stackTrace == null) {
  191.             throw new IllegalStateException("No stack trace has been set");
  192.         }
  193.        
  194.         try {
  195.             loadMappings();
  196.         } catch (IOException e) {
  197.             System.err.println("Unable to load mappings");
  198.         }
  199.        
  200.         StackTraceElement[] deobfStackTrace = new StackTraceElement[stackTrace.length];
  201.         for (int i = 0; i < stackTrace.length; i++) {
  202.             deobfStackTrace[i] = deobfuscate(stackTrace[i]);
  203.         }
  204.         return deobfStackTrace;
  205.     }
  206.    
  207.     private StackTraceElement deobfuscate(StackTraceElement elem) {
  208.         String className = elem.getClassName().replace('.', '/');
  209.        
  210.         if (!classMappings.containsKey(className))
  211.             return elem;
  212.        
  213.         String methodName = elem.getMethodName();
  214.         String methodDesc;
  215.         try {
  216.             methodDesc = getMethodDesc(elem);
  217.         } catch (IOException e) {
  218.             methodDesc = null;
  219.         }
  220.         if (methodDesc == null) {
  221.             System.err.println("Failed to get method desc for " + className + "/" + methodName + "@" + elem.getLineNumber());
  222.             return elem;
  223.         }
  224.        
  225.         String key = className + "/" + methodName + methodDesc;
  226.         if (methodMappings.containsKey(key)) {
  227.             methodName = methodMappings.get(key);
  228.             if (methodNames != null && methodNames.containsKey(methodName))
  229.                 methodName = methodNames.get(methodName);
  230.         }
  231.         className = classMappings.get(className);
  232.         className = className.replace('/', '.');
  233.        
  234.         return new StackTraceElement(className, methodName, createFileName(className, elem.getFileName()), elem.getLineNumber());
  235.     }
  236.    
  237.     private String getMethodDesc(StackTraceElement elem) throws IOException {
  238.         String className = elem.getClassName().replace('.', '/');
  239.         String methodName = elem.getMethodName();
  240.         int lineNumber = elem.getLineNumber();
  241.         if (methodDescCache.containsKey(className + "/" + methodName + "@" + lineNumber)) {
  242.             return methodDescCache.get(className + "/" + methodName + "@" + lineNumber);
  243.         }
  244.        
  245.         // Java Class File Format:
  246.         // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
  247.        
  248.         InputStream is = classLoader.getResourceAsStream(className + ".class");
  249.         if (is == null)
  250.             return null;
  251.         DataInputStream dataIn = new DataInputStream(is);
  252.         skip(dataIn, 8); // header
  253.        
  254.         // constant pool
  255.         Map<Integer, String> stringConstants = new HashMap<>();
  256.         int cpCount = dataIn.readUnsignedShort();
  257.         int[] constSizes = {-1, -1, -1, 4, 4, 8, 8, 2, 2, 4, 4, 4, 4, -1, -1, 3, 2, -1, 4};
  258.         for (int cpIndex = 1; cpIndex < cpCount; cpIndex++) {
  259.             int tag = dataIn.readUnsignedByte();
  260.             if (tag == 1) { // CONSTANT_Utf8
  261.                 stringConstants.put(cpIndex, dataIn.readUTF());
  262.                 //System.out.println(cpIndex + " -> " + stringConstants.get(cpIndex));
  263.             } else {
  264.                 if (tag == 5 || tag == 6) { // CONSTANT_Long or CONSTANT_Double
  265.                     cpIndex++;
  266.                 }
  267.                 skip(dataIn, constSizes[tag]);
  268.             }
  269.         }
  270.        
  271.         skip(dataIn, 6); // more boring information
  272.        
  273.         // Need to know interface count to know how much to skip over
  274.         int interfaceCount = dataIn.readUnsignedShort();
  275.         skip(dataIn, interfaceCount * 2);
  276.        
  277.         // Skip over the fields
  278.         int fieldCount = dataIn.readUnsignedShort();
  279.         for (int i = 0; i < fieldCount; i++) {
  280.             skip(dataIn, 6);
  281.             int attrCount = dataIn.readUnsignedShort();
  282.             for (int j = 0; j < attrCount; j++) {
  283.                 skip(dataIn, 2);
  284.                 long length = Integer.toUnsignedLong(dataIn.readInt());
  285.                 skip(dataIn, length);
  286.             }
  287.         }
  288.        
  289.         // Methods, now we're talking
  290.         int methodCount = dataIn.readUnsignedShort();
  291.         for (int i = 0; i < methodCount; i++) {
  292.             skip(dataIn, 2); // access
  293.             String name = stringConstants.get(dataIn.readUnsignedShort());
  294.             String desc = stringConstants.get(dataIn.readUnsignedShort());
  295.             int attrCount = dataIn.readUnsignedShort();
  296.             for (int j = 0; j < attrCount; j++) {
  297.                 String attrName = stringConstants.get(dataIn.readUnsignedShort());
  298.                 long length = Integer.toUnsignedLong(dataIn.readInt());
  299.                 if (name.equals(methodName) && attrName.equals("Code")) {
  300.                     skip(dataIn, 4); // max stack + locals
  301.                     long codeLength = Integer.toUnsignedLong(dataIn.readInt());
  302.                     skip(dataIn, codeLength);
  303.                     int exceptionTableLength = dataIn.readUnsignedShort();
  304.                     skip(dataIn, exceptionTableLength * 8);
  305.                     int codeAttrCount = dataIn.readUnsignedShort();
  306.                     for (int k = 0; k < codeAttrCount; k++) {
  307.                         String codeAttrName = stringConstants.get(dataIn.readUnsignedShort());
  308.                         long codeAttrLength = Integer.toUnsignedLong(dataIn.readInt());
  309.                         if (codeAttrName.equals("LineNumberTable")) {
  310.                             int lineNumberTableLength = dataIn.readUnsignedShort();
  311.                             for (int l = 0; l < lineNumberTableLength; l++) {
  312.                                 skip(dataIn, 2); // start_pc
  313.                                 int lineNo = dataIn.readUnsignedShort();
  314.                                 if (lineNo == lineNumber) {
  315.                                     methodDescCache.put(className + "/" + methodName + "@" + lineNumber, desc);
  316.                                     return desc;
  317.                                 }
  318.                             }
  319.                         } else {
  320.                             skip(dataIn, codeAttrLength);
  321.                         }
  322.                     }
  323.                 } else {
  324.                     skip(dataIn, length);
  325.                 }
  326.             }
  327.         }
  328.        
  329.         return null;
  330.     }
  331.    
  332.     private static void skip(DataInputStream dataIn, long n) throws IOException {
  333.         long actual = 0;
  334.         while (actual < n) {
  335.             actual += dataIn.skip(n - actual);
  336.         }
  337.     }
  338.    
  339.     private static String createFileName(String className, String oldFileName) {
  340.         if (oldFileName == null || "SourceFile".equals(oldFileName)) {
  341.             if (className.contains("."))
  342.                 className = className.substring(className.lastIndexOf('.') + 1);
  343.             if (className.contains("$"))
  344.                 className = className.substring(0, className.indexOf('$'));
  345.             return className + ".java";
  346.         } else {
  347.             return oldFileName;
  348.         }
  349.     }
  350.    
  351. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement