├── LICENSE ├── README.md ├── docs ├── GUIDE.md ├── ex.png ├── rvm11.png ├── s11.png └── sivm11.png ├── lib └── asm-mod.jar └── src └── me ├── lpk ├── CorrelationMapper.java ├── analysis │ ├── InsnAnalyzer.java │ ├── InsnValue.java │ ├── Sandbox.java │ ├── StackFrame.java │ ├── StackHelper.java │ └── StackUtil.java ├── log │ └── Logger.java ├── mapping │ ├── MappedClass.java │ ├── MappedMember.java │ ├── MappedObject.java │ ├── MappingClassWriter.java │ ├── MappingFactory.java │ ├── MappingProcessor.java │ ├── MappingRenamer.java │ ├── SkidRemapper.java │ ├── loaders │ │ ├── EnigmaLoader.java │ │ ├── MappingLoader.java │ │ ├── ProguardLoader.java │ │ └── SRGLoader.java │ └── remap │ │ ├── MappingMode.java │ │ └── impl │ │ ├── ModeNone.java │ │ ├── ModeRandom.java │ │ ├── ModeSimple.java │ │ └── ModeUnicodeEvil.java └── util │ ├── ASMUtils.java │ ├── AccessHelper.java │ ├── AntiSynthetic.java │ ├── Characters.java │ ├── Classpather.java │ ├── InjectedClassLoader.java │ ├── JarClassLoader.java │ ├── JarUtils.java │ ├── OpUtils.java │ ├── ParentUtils.java │ ├── Reference.java │ ├── ReferenceUtils.java │ ├── RegexUtils.java │ ├── Setup.java │ ├── StringUtils.java │ ├── SwingUtils.java │ └── Timer.java └── nov └── zelixkiller ├── JarArchive.java ├── ZelixKiller.java ├── transformer ├── Transformer.java ├── zkm │ └── ExceptionObfuscationTX.java └── zkm11 │ ├── ControlFlowT11.java │ ├── ReflectionObfuscationVMT11.java │ ├── StringObfuscationCipherT11.java │ ├── StringObfuscationCipherVMT11.java │ ├── StringObfuscationT11.java │ └── utils │ └── ClinitCutter.java └── utils ├── ClassUtils.java ├── InsnUtils.java ├── IssueUtils.java ├── MethodUtils.java ├── ReflectionUtils.java └── analysis └── ConstantTracker.java /README.md: -------------------------------------------------------------------------------- 1 | # ZelixKiller 11 2 | # Outdated! Use threadtear! 3 | Kill every protection by zelix.com with zelixkiller11! 4 | ## CLI 5 | | Argument | Short Version | Description | 6 | | --- | --- | --- | 7 | | --help | -? | Displays help | 8 | | --input | -i | Specify input file | 9 | | --output | -o | Specify output file | 10 | | --transformer | -t | Specify transformer | 11 | | --verbose | -v | Print more information | 12 | ## Transformer 13 | 14 | ### ZKM 11 15 | 16 | | Transformer | Short Version | Description | 17 | | --- | --- | --- | 18 | | String Obfuscation | s11 | Deobfuscates (enhanced) string obfuscation | 19 | | String Obfuscation (Cipher Version) | sivm11 / ~~si11~~ | Deobfuscates string obfuscation that uses DES Cipher and invokedynamic calls | 20 | | Reference Obfuscation | rvm11 | Deobfuscates reflection obfuscation | 21 | | Control Flow Obfuscation | cf11 | Deobfuscates flow obfuscation | 22 | 23 | ### ZKM 8 24 | 25 | | Transformer | Short Version | Description | 26 | | --- | --- | --- | 27 | | ~~String Obfuscation~~ | s8 (*) | Deobfuscates (enhanced) string obfuscation | 28 | | ~~Reference Obfuscation~~ | r8 | Deobfuscates reflection obfuscation | 29 | | ~~Control Flow Obfuscation~~ | cf8 (*) | Deobfuscates flow and exception obfuscation | 30 | ### ZKM General 31 | 32 | | Transformer | Short Version | Description | 33 | | --- | --- | --- | 34 | | Exception Obfuscation | ex | Removes redundant try catch blocks | 35 | 36 | 37 | 38 | Transformers marked with a star may also work using transformers intended for more recent versions. 39 | Crossed out means that the transformer is not implemented yet. 40 | ## Which transformer 41 | If you're not sure which transformer you should use please use this [guide](docs/GUIDE.md). 42 | 43 | ## Libraries needed 44 | commons-io 2.6, commons-cli 1.4, asm 6+ 45 | 46 | ## License 47 | zelixkiller is licensed under the GNU General Public License 3.0 48 | 49 | #### Notice 50 | Do not deobfuscate any file that doesn't belong to you. 51 | Please open an issue or send me an email if your file won't deobfuscate properly. If a "fault-proxy-dump" file is created please attach. 52 | Transformers that use vm emulation could possibly execute dangerous code, use with caution! 53 | Note that output files are most likely not runnable. If you still want to try to run them use "-noverify" as JVM argument! 54 | This tool is intended for Java 8 but it will probably run on higher versions too. 55 | -------------------------------------------------------------------------------- /docs/GUIDE.md: -------------------------------------------------------------------------------- 1 | # Obfuscation Identification 2 | 3 | | Transformer | Short Version | Identification | Sample | 4 | | --- | --- | --- | --- | 5 | | String Obfuscation | s11 | Class contains method with huge switch | ![Decompiled with Fernflower](s11.png) | 6 | | String Obfuscation (Cipher Version) | sivm11 / ~~si11~~ | Static initializer contains `Cipher.getInstance("DES/CBC/PKCS5Padding")` | ![Decompiled with Fernflower](sivm11.png) | 7 | | Reference Obfuscation | rvm11 | Class contains invokedynamic calls and this method | ![Decompiled with Fernflower](rvm11.png) | 8 | | Control Flow Obfuscation | cf11 | Conditional jumps that always jump the same way | / | 9 | | Exception Obfuscation | ex | Method contains redundant try catch blocks | ![Decompiled with Fernflower](ex.png) | 10 | -------------------------------------------------------------------------------- /docs/ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraxCode/zelixkiller/8f607e98b746c9b57592798ae88b731a9efbfa19/docs/ex.png -------------------------------------------------------------------------------- /docs/rvm11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraxCode/zelixkiller/8f607e98b746c9b57592798ae88b731a9efbfa19/docs/rvm11.png -------------------------------------------------------------------------------- /docs/s11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraxCode/zelixkiller/8f607e98b746c9b57592798ae88b731a9efbfa19/docs/s11.png -------------------------------------------------------------------------------- /docs/sivm11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraxCode/zelixkiller/8f607e98b746c9b57592798ae88b731a9efbfa19/docs/sivm11.png -------------------------------------------------------------------------------- /lib/asm-mod.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraxCode/zelixkiller/8f607e98b746c9b57592798ae88b731a9efbfa19/lib/asm-mod.jar -------------------------------------------------------------------------------- /src/me/lpk/analysis/InsnValue.java: -------------------------------------------------------------------------------- 1 | package me.lpk.analysis; 2 | 3 | import org.objectweb.asm.Type; 4 | import org.objectweb.asm.tree.AbstractInsnNode; 5 | import org.objectweb.asm.tree.analysis.Value; 6 | 7 | import org.objectweb.asm.Opcodes; 8 | 9 | import me.lpk.util.OpUtils; 10 | 11 | /** 12 | * A {@link Value} that is represented by types with a possibly null value. This 13 | * type system distinguishes the UNINITIALZED, INT, FLOAT, LONG, DOUBLE, CHAR, 14 | * SHORT REFERENCE and RETURNADDRESS types. Arrays for these types are also 15 | * included. 16 | * 17 | * @author Eric Bruneton 18 | * @editor Matt 19 | */ 20 | public class InsnValue implements Value { 21 | 22 | public static final InsnValue UNINITIALIZED_VALUE = new InsnValue(null); 23 | public static final InsnValue INT_VALUE = new InsnValue(Type.INT_TYPE); 24 | public static final InsnValue FLOAT_VALUE = new InsnValue(Type.FLOAT_TYPE); 25 | public static final InsnValue LONG_VALUE = new InsnValue(Type.LONG_TYPE); 26 | public static final InsnValue DOUBLE_VALUE = new InsnValue(Type.DOUBLE_TYPE); 27 | public static final InsnValue BYTE_VALUE = new InsnValue(Type.BYTE_TYPE); 28 | public static final InsnValue CHAR_VALUE = new InsnValue(Type.CHAR_TYPE); 29 | public static final InsnValue SHORT_VALUE = new InsnValue(Type.SHORT_TYPE); 30 | public static final InsnValue REFERENCE_VALUE = new InsnValue(Type.getObjectType("java/lang/Object")); 31 | public static final InsnValue NULL_REFERENCE_VALUE = new InsnValue(Type.getType("java/lang/Object")); 32 | 33 | // 34 | public static final InsnValue CHAR_ARR_VALUE = new InsnValue(Type.getObjectType("[C")); 35 | public static final InsnValue DOUBLE_ARR_VALUE = new InsnValue(Type.getObjectType("[D")); 36 | public static final InsnValue INT_ARR_VALUE = new InsnValue(Type.getObjectType("[I")); 37 | public static final InsnValue FLOAT_ARR_VALUE = new InsnValue(Type.getObjectType("[F")); 38 | public static final InsnValue BOOLEAN_ARR_VALUE = new InsnValue(Type.getObjectType("[Z")); 39 | public static final InsnValue LONG_ARR_VALUE = new InsnValue(Type.getObjectType("[J")); 40 | public static final InsnValue SHORT_ARR_VALUE = new InsnValue(Type.getObjectType("[S")); 41 | public static final InsnValue BYTE_ARR_VALUE = new InsnValue(Type.getObjectType("[B")); 42 | public static final InsnValue REFERENCE_ARR_VALUE = new InsnValue(Type.getObjectType("[java/lang/Object")); 43 | 44 | // 45 | public static final InsnValue RETURNADDRESS_VALUE = new InsnValue(Type.VOID_TYPE); 46 | 47 | private final Type type; 48 | private final Object value; 49 | 50 | public InsnValue(final Type type) { 51 | this(type, null); 52 | } 53 | 54 | public InsnValue(final Type type, final Object value) { 55 | this.type = type; 56 | this.value = value; 57 | } 58 | 59 | public Object getValue() { 60 | return value; 61 | } 62 | 63 | public Type getType() { 64 | return type; 65 | } 66 | 67 | public int getSize() { 68 | return type == Type.LONG_TYPE || type == Type.DOUBLE_TYPE ? 2 : 1; 69 | } 70 | 71 | public boolean isReference() { 72 | return type != null && (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY); 73 | } 74 | 75 | @Override 76 | public boolean equals(final Object value) { 77 | if (value == this) { 78 | return true; 79 | } else if (value instanceof InsnValue) { 80 | if (type == null) { 81 | return ((InsnValue) value).type == null; 82 | } else { 83 | return type.equals(((InsnValue) value).type); 84 | } 85 | } else { 86 | return false; 87 | } 88 | } 89 | 90 | @Override 91 | public int hashCode() { 92 | return type == null ? 0 : type.hashCode(); 93 | } 94 | 95 | @Override 96 | public String toString() { 97 | if (value != null) { 98 | return type.getDescriptor() + " " + (value.toString()).trim(); 99 | } 100 | if (type == null || this == UNINITIALIZED_VALUE) { 101 | return "Uninitialized Null"; 102 | } else if (this == RETURNADDRESS_VALUE) { 103 | return "Return Address"; 104 | } else if (this == REFERENCE_VALUE) { 105 | return "Misc. Ref Value"; 106 | 107 | } else if (this == NULL_REFERENCE_VALUE){ 108 | return "Null"; 109 | } 110 | else if (type != null) { 111 | return type.getDescriptor(); 112 | } else { 113 | return "ERROR"; 114 | } 115 | } 116 | 117 | public static InsnValue intValue(AbstractInsnNode opcode) { 118 | return new InsnValue(Type.INT_TYPE, OpUtils.getIntValue(opcode)); 119 | } 120 | 121 | public static InsnValue longValue(int opcode) { 122 | switch (opcode) { 123 | case Opcodes.LCONST_0: 124 | return new InsnValue(Type.LONG_TYPE, 0L); 125 | case Opcodes.LCONST_1: 126 | return new InsnValue(Type.LONG_TYPE, 1L); 127 | } 128 | return InsnValue.LONG_VALUE; 129 | } 130 | 131 | public static InsnValue doubleValue(int opcode) { 132 | switch (opcode) { 133 | case Opcodes.DCONST_0: 134 | return new InsnValue(Type.DOUBLE_TYPE, 0.0); 135 | case Opcodes.DCONST_1: 136 | return new InsnValue(Type.DOUBLE_TYPE, 1.0); 137 | } 138 | return InsnValue.DOUBLE_VALUE; 139 | } 140 | 141 | public static InsnValue floatValue(int opcode) { 142 | switch (opcode) { 143 | case Opcodes.FCONST_0: 144 | return new InsnValue(Type.FLOAT_TYPE, 0.0f); 145 | case Opcodes.FCONST_1: 146 | return new InsnValue(Type.FLOAT_TYPE, 1.0f); 147 | case Opcodes.FCONST_2: 148 | return new InsnValue(Type.FLOAT_TYPE, 2.0f); 149 | } 150 | return InsnValue.FLOAT_VALUE; 151 | } 152 | 153 | public static InsnValue intValue(Object cst) { 154 | return new InsnValue(Type.INT_TYPE, cst); 155 | } 156 | 157 | public static InsnValue charValue(Object cst) { 158 | return new InsnValue(Type.CHAR_TYPE, cst); 159 | } 160 | 161 | public static InsnValue byteValue(Object cst) { 162 | return new InsnValue(Type.BYTE_TYPE, cst); 163 | } 164 | 165 | public static InsnValue longValue(Object cst) { 166 | return new InsnValue(Type.LONG_TYPE, cst); 167 | } 168 | 169 | public static InsnValue doubleValue(Object cst) { 170 | return new InsnValue(Type.DOUBLE_TYPE, cst); 171 | } 172 | 173 | public static InsnValue floatValue(Object cst) { 174 | return new InsnValue(Type.FLOAT_TYPE, cst); 175 | } 176 | 177 | public static InsnValue shortValue(Object cst) { 178 | return new InsnValue(Type.SHORT_TYPE, cst); 179 | } 180 | 181 | public static InsnValue stringValue(Object cst) { 182 | return new InsnValue(Type.getType(String.class), cst); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/me/lpk/analysis/StackUtil.java: -------------------------------------------------------------------------------- 1 | package me.lpk.analysis; 2 | 3 | import org.objectweb.asm.tree.MethodNode; 4 | import org.objectweb.asm.tree.analysis.AnalyzerException; 5 | 6 | public class StackUtil { 7 | 8 | public static StackFrame[] getFrames(MethodNode mn) { 9 | InsnAnalyzer a = new InsnAnalyzer(new StackHelper()); 10 | StackFrame[] sfs = null; 11 | try { 12 | sfs = a.analyze(mn.owner, mn); 13 | } catch (AnalyzerException e) { 14 | //e.printStackTrace(); 15 | } 16 | return sfs; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/me/lpk/log/Logger.java: -------------------------------------------------------------------------------- 1 | package me.lpk.log; 2 | 3 | /** 4 | * Too lazy to setup Log4J. Made this instead. 5 | */ 6 | public class Logger { 7 | public static final int VERY_HIGH = 0, HIGH = 1, MEDIUM = 2, LOW = 3; 8 | private static int level = HIGH; 9 | 10 | public static void logVeryHigh(String s) { 11 | log(s, VERY_HIGH); 12 | } 13 | 14 | public static void logHigh(String s) { 15 | log(s, HIGH); 16 | } 17 | 18 | public static void logMedium(String s) { 19 | log(s, MEDIUM); 20 | } 21 | 22 | public static void logLow(String s) { 23 | log(s, LOW); 24 | } 25 | 26 | public static void errVeryHigh(String s) { 27 | err(s, VERY_HIGH); 28 | } 29 | 30 | public static void errHigh(String s) { 31 | err(s, HIGH); 32 | } 33 | 34 | public static void errMedium(String s) { 35 | err(s, MEDIUM); 36 | } 37 | 38 | public static void errLow(String s) { 39 | err(s, LOW); 40 | } 41 | 42 | private static void log(String s, int i) { 43 | if (i >= level) { 44 | System.out.println(s); 45 | } 46 | } 47 | 48 | private static void err(String s, int i) { 49 | if (i >= level) { 50 | System.err.println(s); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappedClass.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.objectweb.asm.tree.ClassNode; 9 | 10 | public class MappedClass extends MappedObject { 11 | /** 12 | * Original name : Child 13 | */ 14 | private final Map children = new HashMap(); 15 | /** 16 | * Original name : Inner Class 17 | */ 18 | private final Map inners = new HashMap(); 19 | /** 20 | * A list of interfaces implemented by the class. 21 | */ 22 | private final List interfaces = new ArrayList(); 23 | /** 24 | * A list of fields belonging to the class. 25 | */ 26 | private final List fields = new ArrayList(); 27 | /** 28 | * A list of methods belonging to the class. 29 | */ 30 | private final List methods = new ArrayList(); 31 | /** 32 | * The ClassNode associated with the current MappedClass. 33 | */ 34 | private final ClassNode node; 35 | /** 36 | * The super class. 37 | */ 38 | private MappedClass parent; 39 | /** 40 | * The outer class, if any. 41 | */ 42 | private MappedClass outer; 43 | 44 | public MappedClass(ClassNode node, String nameNew) { 45 | super("CLASS", node.name, nameNew); 46 | this.node = node; 47 | } 48 | 49 | /** 50 | * Finds a method given a name and description. 51 | * 52 | * @param name 53 | * @param useOriginalName 54 | * @return 55 | */ 56 | public MappedMember findMethodByNameAndDesc(String name, String desc, boolean useOriginalName) { 57 | for (MappedMember mm : getMethods()) { 58 | if (mm.getDesc().equals(desc) && useOriginalName ? mm.getOriginalName().equals(name) : mm.getNewName().equals(name)) { 59 | return mm; 60 | } 61 | } 62 | return null; 63 | } 64 | 65 | /** 66 | * Finds a field given a name and description. 67 | * 68 | * @param name 69 | * @param useOriginalName 70 | * @return 71 | */ 72 | public MappedMember findFieldByNameAndDesc(String name, String desc, boolean useOriginalName) { 73 | for (MappedMember mm : getFields()) { 74 | if (mm.getDesc().equals(desc) && useOriginalName ? mm.getOriginalName().equals(name) : mm.getNewName().equals(name)) { 75 | return mm; 76 | } 77 | } 78 | return null; 79 | } 80 | 81 | /** 82 | * Returns a list of fields matching a given name. 83 | * 84 | * @param text 85 | * @param useOriginalName 86 | * @return 87 | */ 88 | public List findFieldsByName(String text, boolean useOriginalName) { 89 | List list = new ArrayList(); 90 | for (MappedMember mm : getFields()) { 91 | if (useOriginalName ? mm.getOriginalName().equals(text) : mm.getNewName().equals(text)) { 92 | list.add(mm); 93 | } 94 | } 95 | return list; 96 | } 97 | 98 | /** 99 | * Returns a list of fields matching a given descriptor. 100 | * 101 | * @param text 102 | * @return 103 | */ 104 | public List findFieldsByDesc(String text) { 105 | List list = new ArrayList(); 106 | for (MappedMember mm : getFields()) { 107 | if (mm.getDesc().equals(text)) { 108 | list.add(mm); 109 | } 110 | } 111 | return list; 112 | } 113 | 114 | /** 115 | * Returns a list of methods matching a given name. 116 | * 117 | * @param text 118 | * @param useOriginalName 119 | * @return 120 | */ 121 | public List findMethodsByName(String text, boolean useOriginalName) { 122 | List list = new ArrayList(); 123 | for (MappedMember mm : getMethods()) { 124 | if (useOriginalName ? mm.getOriginalName().equals(text) : mm.getNewName().equals(text)) { 125 | list.add(mm); 126 | } 127 | } 128 | return list; 129 | } 130 | 131 | /** 132 | * Returns a list of methods matching a given descriptor. 133 | * 134 | * @param text 135 | * @return 136 | */ 137 | public List findMethodsByDesc(String text) { 138 | List list = new ArrayList(); 139 | for (MappedMember mm : getMethods()) { 140 | if (mm.getDesc().equals(text)) { 141 | list.add(mm); 142 | } 143 | } 144 | return list; 145 | } 146 | 147 | /** 148 | * Returns a map of field mappings. Keys are based on the index they appear 149 | * in the class. 150 | * 151 | * @return 152 | */ 153 | public List getFields() { 154 | return fields; 155 | } 156 | 157 | /** 158 | * Returns a collection of methods in the MappedClass. 159 | * 160 | * @return 161 | */ 162 | public List getMethods() { 163 | return methods; 164 | } 165 | 166 | /** 167 | * Finds a field given an index. 168 | * 169 | * @param key 170 | * @return 171 | */ 172 | public MappedMember getField(int key) { 173 | return fields.get(key); 174 | } 175 | 176 | /** 177 | * Finds a field given an index. 178 | * 179 | * @param key 180 | * @return 181 | */ 182 | public MappedMember getMethod(int key) { 183 | return methods.get(key); 184 | } 185 | 186 | /** 187 | * Adds a child instance to the class. 188 | * 189 | * @param child 190 | */ 191 | public void addChild(MappedClass child) { 192 | children.put(child.getOriginalName(), child); 193 | } 194 | 195 | 196 | /** 197 | * Adds an interface to the class. 198 | * 199 | * @param interfaze 200 | */ 201 | public void addInterface(MappedClass interfaze) { 202 | interfaces.add(interfaze); 203 | } 204 | 205 | /** 206 | * Adds an inner class to this class. 207 | * 208 | * @param child 209 | */ 210 | public void addInnerClass(MappedClass child) { 211 | inners.put(child.getOriginalName(), child); 212 | } 213 | 214 | /** 215 | * Add a field to the class. Returns the field's index. 216 | * 217 | * @param mm 218 | * @return 219 | */ 220 | public void addField(MappedMember mm) { 221 | fields.add(mm); 222 | } 223 | 224 | /** 225 | * Add a method to the class. Returns the method's index. 226 | * 227 | * @param mm 228 | * @return 229 | */ 230 | public void addMethod(MappedMember mm) { 231 | methods.add(mm); 232 | } 233 | 234 | /** 235 | * Returns true if the given name is a child of this class. 236 | * 237 | * @param childName 238 | * @return 239 | */ 240 | public boolean hasChild(String childName) { 241 | return children.containsKey(childName); 242 | } 243 | 244 | /** 245 | * Returns the map of child instances the class has. 246 | * 247 | * @return 248 | */ 249 | public Map getChildrenMap() { 250 | return children; 251 | } 252 | 253 | /** 254 | * Returns the map of interfaces the class has. 255 | * 256 | * @return 257 | */ 258 | public List getInterfaces() { 259 | return interfaces; 260 | } 261 | 262 | /** 263 | * Get's the ClassNode associated with the mapping. 264 | * 265 | * @return 266 | */ 267 | public ClassNode getNode() { 268 | return node; 269 | } 270 | 271 | /** 272 | * Get's the parent's mapped instance. 273 | * 274 | * @return 275 | */ 276 | public MappedClass getParent() { 277 | return parent; 278 | } 279 | 280 | /** 281 | * Set's the parent mapped instance. 282 | * 283 | * @param parent 284 | */ 285 | public void setParent(MappedClass parent) { 286 | this.parent = parent; 287 | } 288 | 289 | /** 290 | * Returns a map of inner classes. 291 | * 292 | * @return 293 | */ 294 | public Map getInnerClassMap() { 295 | return inners; 296 | } 297 | 298 | /** 299 | * Returns the outer class. 300 | * 301 | * @return 302 | */ 303 | public MappedClass getOuterClass() { 304 | return outer; 305 | } 306 | 307 | /** 308 | * Returns true if this is an inner class. 309 | * 310 | * @return 311 | */ 312 | public boolean isInnerClass() { 313 | return outer != null; 314 | } 315 | 316 | /** 317 | * Set's the class's outer class. 318 | * 319 | * @param outer 320 | */ 321 | public void setOuterClass(MappedClass outer) { 322 | this.outer = outer; 323 | } 324 | 325 | /** 326 | * Obvious. 327 | * 328 | * @return 329 | */ 330 | public boolean hasParent() { 331 | return parent != null; 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappedMember.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.objectweb.asm.tree.FieldNode; 7 | import org.objectweb.asm.tree.MethodNode; 8 | 9 | public class MappedMember extends MappedObject { 10 | /** 11 | * The member's owner. 12 | */ 13 | private final MappedClass owner; 14 | /** 15 | * The FieldNode or MethodNode of the current MappedMember. 16 | */ 17 | private final Object memberNode; 18 | /** 19 | * The index in the owner that the current member appears in. 20 | */ 21 | private final int index; 22 | /** 23 | * The list of MappedMembers that this member overrides. 24 | */ 25 | private List overrides = new ArrayList(); 26 | /** 27 | * The list of MappedMembers that override this member. 28 | * 29 | * TODO: Think of a better name 30 | */ 31 | private List overridesMe = new ArrayList(); 32 | 33 | public MappedMember(MappedClass owner, Object memberNode, int index, String desc, String nameOriginal) { 34 | super(desc, nameOriginal, nameOriginal); 35 | this.memberNode = memberNode; 36 | this.owner = owner; 37 | this.index = index; 38 | } 39 | 40 | /** 41 | * The class the member belongs to. 42 | * 43 | * @return 44 | */ 45 | public MappedClass getOwner() { 46 | return owner; 47 | } 48 | 49 | /** 50 | * The order in which the member was indexed (Ex: One field after another) 51 | * 52 | * @return 53 | */ 54 | public int getIndex() { 55 | return index; 56 | } 57 | 58 | /** 59 | * Returns the node of the member. Can be a Field/MethodNode. 60 | * 61 | * @return 62 | */ 63 | public Object getMemberNode() { 64 | return memberNode; 65 | } 66 | 67 | /** 68 | * Returns the node of the member as a FieldNode. 69 | * 70 | * @return 71 | */ 72 | public FieldNode getFieldNode() { 73 | return (FieldNode) memberNode; 74 | } 75 | 76 | /** 77 | * Returns the node of the member as a MethodNode. 78 | * 79 | * @return 80 | */ 81 | public MethodNode getMethodNode() { 82 | return (MethodNode) memberNode; 83 | } 84 | 85 | /** 86 | * Returns true if the memberNode is a FieldNode. 87 | * 88 | * @return 89 | */ 90 | public boolean isField() { 91 | return memberNode == null ? false : memberNode instanceof FieldNode; 92 | } 93 | 94 | /** 95 | * Returns true if the memberNode is a MethodNode. 96 | * 97 | * @return 98 | */ 99 | public boolean isMethod() { 100 | return memberNode == null ? false : memberNode instanceof MethodNode; 101 | } 102 | 103 | /** 104 | * Returns true if the member overrides another member. 105 | * 106 | * @return 107 | */ 108 | public boolean doesOverride() { 109 | return overrides.size() > 0; 110 | } 111 | 112 | /** 113 | * Returns true if this member is overriden by another. 114 | * 115 | * @return 116 | */ 117 | public boolean isOverriden() { 118 | return overridesMe.size() > 0; 119 | } 120 | 121 | /** 122 | * Gets the first MappedMember this member overrides. May be null. 123 | * 124 | * @return 125 | */ 126 | public MappedMember getFirstOverride() { 127 | return doesOverride() ? overrides.get(0) : null; 128 | } 129 | 130 | /** 131 | * Gets the list of MappedMembers that are overriden by this. 132 | * 133 | * @return 134 | */ 135 | public List getOverrides() { 136 | return overrides; 137 | } 138 | 139 | /** 140 | * Gets the list of MappedMembers that override this. 141 | * 142 | * TODO: think of a better name 143 | * 144 | * @return 145 | */ 146 | public List getMyOverrides() { 147 | return overridesMe; 148 | } 149 | 150 | /** 151 | * Sets the overridden (method) member object. 152 | * 153 | * @param override 154 | */ 155 | public void addOverride(MappedMember override) { 156 | overrides.add(override); 157 | } 158 | 159 | /** 160 | * Adds a member to the list that shows methods overriding this.
161 | * TODO: Better name for this method 162 | * 163 | * @param overrider 164 | */ 165 | public void addMemberThatOverridesMe(MappedMember overrider) { 166 | overridesMe.add(overrider); 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappedObject.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | class MappedObject { 4 | private final String nameOriginal; 5 | private String desc, nameNew; 6 | private boolean isRenamedOverride, isLibrary; 7 | 8 | public MappedObject(String desc, String nameOriginal, String nameNew) { 9 | this.desc = desc; 10 | this.nameOriginal = nameOriginal; 11 | this.nameNew = nameNew; 12 | } 13 | 14 | /** 15 | * Returns the bytecode description of the object. 16 | * 17 | * @return 18 | */ 19 | public String getDesc() { 20 | return desc; 21 | } 22 | 23 | /** 24 | * Returns the original name of the object. 25 | * 26 | * @return 27 | */ 28 | public String getOriginalName() { 29 | return nameOriginal; 30 | } 31 | 32 | /** 33 | * Returns the new name of the object. 34 | * 35 | * @return 36 | */ 37 | public String getNewName() { 38 | return nameNew; 39 | } 40 | 41 | /** 42 | * Updates the new name. 43 | * 44 | * @param nameNew 45 | */ 46 | public void setNewName(String nameNew) { 47 | if (isLibrary){ 48 | return; 49 | } 50 | this.nameNew = nameNew; 51 | } 52 | 53 | /** 54 | * Updates the new desc. 55 | */ 56 | public void setDesc(String desc) { 57 | this.desc = desc; 58 | } 59 | 60 | /** 61 | * Returns if the object has been renamed. 62 | * 63 | * @return 64 | */ 65 | public boolean isRenamed() { 66 | return isTruelyRenamed() || isRenamedOverride; 67 | } 68 | 69 | public boolean isTruelyRenamed() { 70 | return !nameOriginal.equals(nameNew) || isLibrary; 71 | } 72 | 73 | /** 74 | * Sets the override for isRenamed(). 75 | * 76 | * @param isRenamedOverride 77 | */ 78 | public void setRenamedOverride(boolean isRenamedOverride) { 79 | this.isRenamedOverride = isRenamedOverride; 80 | } 81 | 82 | /** 83 | * Returns true if the Mapped object is considered to be a library object. 84 | * Library objects are not to be modified. 85 | * 86 | * @return 87 | */ 88 | public boolean isLibrary() { 89 | return isLibrary; 90 | } 91 | 92 | /** 93 | * Sets isLibrary. See {@link #isLibrary()} for details. 94 | * 95 | * @param isLibrary 96 | */ 97 | public void setIsLibrary(boolean isLibrary) { 98 | this.isLibrary = isLibrary; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappingClassWriter.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.objectweb.asm.ClassWriter; 7 | 8 | import me.lpk.util.ParentUtils; 9 | 10 | /** 11 | * A ClassWriter that works off of MappedClasses. Does not require classes being 12 | * loaded into the JVM like a standard ClassWriter. 13 | */ 14 | public class MappingClassWriter extends ClassWriter { 15 | private final Map mappings; 16 | private final Map mappingsInv = new HashMap(); 17 | 18 | public MappingClassWriter(Map mappings, int i) { 19 | super(i); 20 | this.mappings = mappings; 21 | for (MappedClass mc : mappings.values()) { 22 | mappingsInv.put(mc.getNewName(), mc); 23 | } 24 | } 25 | 26 | @Override 27 | protected String getCommonSuperClass(final String type1, final String type2) { 28 | MappedClass mc1 = mappings.getOrDefault(type1, mappingsInv.get(type1)); 29 | MappedClass mc2 = mappings.getOrDefault(type2, mappingsInv.get(type2)); 30 | if (mc1 == null || mc2 == null) { 31 | return "java/lang/Object"; 32 | } 33 | MappedClass common = ParentUtils.findCommonParent(mc1, mc2); 34 | if (common == null) { 35 | return "java/lang/Object"; 36 | } 37 | return common.getOriginalName(); 38 | } 39 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappingFactory.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import org.objectweb.asm.tree.ClassNode; 13 | import org.objectweb.asm.tree.FieldNode; 14 | import org.objectweb.asm.tree.MethodNode; 15 | 16 | import me.lpk.mapping.loaders.EnigmaLoader; 17 | import me.lpk.mapping.loaders.MappingLoader; 18 | import me.lpk.mapping.loaders.ProguardLoader; 19 | import me.lpk.mapping.loaders.SRGLoader; 20 | import me.lpk.util.AccessHelper; 21 | import me.lpk.util.JarUtils; 22 | import me.lpk.util.ParentUtils; 23 | import me.lpk.util.RegexUtils; 24 | 25 | /** 26 | * Factory for generating Mappings from various sources such as: 27 | *
    28 | *
  • Mappings 29 | *
      30 | *
    • Proguard 31 | *
    • Enigma 32 | *
    • SRG 33 | *
    34 | *
  • Files 35 | *
      36 | *
    • Jar 37 | *
    38 | *
  • Java Objects 39 | *
      40 | *
    • Map<String, ClassNode> 41 | *
    42 | *
43 | */ 44 | public class MappingFactory { 45 | /** 46 | * Returns a map of class names to MappedClasses given an SRG mapping file. 47 | * 48 | * @param map 49 | * @param nodes 50 | * @return 51 | */ 52 | public static Map mappingsFromSRG(File file, Map nodes) { 53 | Map base = mappingsFromNodes(nodes); 54 | MappingLoader loader = new SRGLoader(nodes); 55 | try { 56 | Map newMappings = loader.read(new FileReader(file)); 57 | for (MappedClass mappedClass : newMappings.values()) { 58 | newMappings = linkMappings(mappedClass, newMappings); 59 | } 60 | base = fixFromMappingsText(base, newMappings); 61 | } catch (FileNotFoundException e) { 62 | e.printStackTrace(); 63 | } 64 | return base; 65 | } 66 | 67 | /** 68 | * Returns a map of class names to MappedClasses given an Engima mapping 69 | * file. 70 | * 71 | * @param file 72 | * @return 73 | */ 74 | public static Map mappingsFromEnigma(File file, Map nodes) { 75 | Map base = mappingsFromNodes(nodes); 76 | MappingLoader loader = new EnigmaLoader(nodes); 77 | try { 78 | Map newMappings = loader.read(new FileReader(file)); 79 | for (MappedClass mappedClass : newMappings.values()) { 80 | newMappings = linkMappings(mappedClass, newMappings); 81 | } 82 | base = fixFromMappingsText(base, newMappings); 83 | } catch (FileNotFoundException e) { 84 | e.printStackTrace(); 85 | } 86 | return base; 87 | } 88 | 89 | /** 90 | * Returns a map of class names to mapped classes given a Proguard mapping 91 | * file. 92 | * 93 | * @param file 94 | * @return 95 | */ 96 | public static Map mappingsFromProguard(File file, Map nodes) { 97 | Map base = mappingsFromNodes(nodes); 98 | MappingLoader loader = new ProguardLoader(nodes); 99 | try { 100 | Map newMappings = loader.read(new FileReader(file)); 101 | for (MappedClass mappedClass : newMappings.values()) { 102 | newMappings = linkMappings(mappedClass, newMappings); 103 | } 104 | base = fixFromMappingsText(base, newMappings); 105 | } catch (FileNotFoundException e) { 106 | e.printStackTrace(); 107 | } 108 | return base; 109 | } 110 | 111 | /** 112 | * Given two maps of mappings, applies the data from the new map to the base 113 | * map. 114 | * 115 | * @param base 116 | * @param newMappings 117 | * @return 118 | */ 119 | public static Map fixFromMappingsText(Map base, Map newMappings) { 120 | for (String className : newMappings.keySet()) { 121 | MappedClass baseClass = base.get(className); 122 | MappedClass newClass = newMappings.get(className); 123 | if (baseClass == null) { 124 | continue; 125 | } 126 | baseClass.setNewName(newClass.getNewName()); 127 | for (MappedMember newMember : newClass.getFields()) { 128 | MappedMember baseMember = ParentUtils.findField(baseClass, newMember.getOriginalName(), newMember.getDesc()); 129 | if (baseMember != null && ParentUtils.matches(baseMember, newMember.getOriginalName(), newMember.getDesc(), true)) { 130 | baseMember.setNewName(newMember.getNewName()); 131 | } 132 | } 133 | for (MappedMember newMember : newClass.getMethods()) { 134 | MappedMember baseMember = ParentUtils.findMethod(baseClass, newMember.getOriginalName(), newMember.getDesc(), true); 135 | if (baseMember != null && ParentUtils.matches(baseMember, newMember.getOriginalName(), newMember.getDesc(), true)) { 136 | baseMember.setNewName(newMember.getNewName()); 137 | } 138 | } 139 | base.put(className, baseClass); 140 | } 141 | return base; 142 | } 143 | 144 | /** 145 | * Returns a map of class names to mapped classes given a jar file. 146 | * 147 | * @param file 148 | * @return 149 | */ 150 | public static Map mappingsFromJar(File file) { 151 | Map nodes = null; 152 | try { 153 | nodes = JarUtils.loadClasses(file); 154 | } catch (IOException e) { 155 | e.printStackTrace(); 156 | } 157 | return mappingsFromNodes(nodes); 158 | } 159 | 160 | /** 161 | * Returns a map of class names to mapped classes given a map of class names 162 | * to ClassNodes. 163 | * 164 | * @param nodes 165 | * @return 166 | */ 167 | public static Map mappingsFromNodes(Map nodes) { 168 | Map mappings = new HashMap(); 169 | for (ClassNode node : nodes.values()) { 170 | mappings = generateClassMapping(node, nodes, mappings); 171 | } 172 | for (String name : mappings.keySet()) { 173 | mappings = linkMappings(mappings.get(name), mappings); 174 | } 175 | return mappings; 176 | } 177 | 178 | /** 179 | * Returns a map of class names to mapped classes given a map of class names 180 | * to ClassNodes. Does not link the classes once the mappings are generated. 181 | * 182 | * @param nodes 183 | * @return 184 | */ 185 | public static Map mappingsFromNodesNoLinking(Map nodes) { 186 | Map mappings = new HashMap(); 187 | for (ClassNode node : nodes.values()) { 188 | mappings = generateClassMapping(node, nodes, mappings); 189 | } 190 | return mappings; 191 | } 192 | 193 | /** 194 | * Generates mapping for the given node and it's parents / interfaces. 195 | * 196 | * @param node 197 | * @param nodes 198 | * @param mappings 199 | */ 200 | private static Map generateClassMapping(ClassNode node, Map nodes, Map mappings) { 201 | // If the node isn't Object, it has parents. 202 | boolean hasParents = !node.name.equals("java/lang/Object"); 203 | boolean hasInterfaces = node.interfaces.size() > 0; 204 | if (hasParents) { 205 | // Since we're in the generation phase, parents won't be created 206 | // yet. 207 | boolean parentRenamed = mappings.containsKey(node.superName); 208 | ClassNode parentNode = nodes.get(node.superName); 209 | if (parentNode != null && !parentRenamed) { 210 | boolean conflict = ParentUtils.isLoop(node, nodes, 0); 211 | if (conflict) { 212 | // Really ugly hack for when class super names loop. 213 | // Only happens on a few obfuscated samples 214 | parentNode.superName = "java/lang/Object"; 215 | } else { 216 | generateClassMapping(parentNode, nodes, mappings); 217 | } 218 | } 219 | } 220 | if (hasInterfaces) { 221 | // For each interface if it has a ClassNode, map it. 222 | for (String interfaze : node.interfaces) { 223 | boolean interfaceRenamed = mappings.containsKey(interfaze); 224 | ClassNode interfaceNode = nodes.get(interfaze); 225 | if (interfaceNode != null && !interfaceRenamed) { 226 | generateClassMapping(interfaceNode, nodes, mappings); 227 | } 228 | } 229 | } 230 | // Now map the ClassNode given by the parameter 'node' 231 | if (!mappings.containsKey(node.name)) { 232 | MappedClass mappedClass = new MappedClass(node, node.name); 233 | for (FieldNode fn : node.fields) { 234 | mappedClass.addField(new MappedMember(mappedClass, fn, mappedClass.getFields().size(), fn.desc, fn.name)); 235 | } 236 | for (MethodNode mn : node.methods) { 237 | mappedClass.addMethod(new MappedMember(mappedClass, mn, mappedClass.getMethods().size(), mn.desc, mn.name)); 238 | } 239 | mappings.put(node.name, mappedClass); 240 | } 241 | return mappings; 242 | } 243 | 244 | /** 245 | * Iterates through entries in the given map and matches together parent and 246 | * child classes. 247 | * 248 | * @param mappings 249 | * @return 250 | */ 251 | public static Map linkMappings(MappedClass mc, Map mappings) { 252 | // Setting up parent structure 253 | if (!mc.hasParent()) { 254 | // No parent, check to see if one can be found 255 | MappedClass parentMappedClass = mappings.get(mc.getNode().superName); 256 | if (parentMappedClass != null) { 257 | mappings = linkMappings(parentMappedClass, mappings); 258 | parentMappedClass.addChild(mc); 259 | mc.setParent(parentMappedClass); 260 | } 261 | } 262 | // Adding interfaces 263 | if (mc.getInterfaces().size() == 0) { 264 | for (String interfaze : mc.getNode().interfaces) { 265 | MappedClass mappedInterface = mappings.get(interfaze); 266 | if (mappedInterface != null) { 267 | mappings = linkMappings(mappedInterface, mappings); 268 | mc.addInterface(mappedInterface); 269 | mappedInterface.addChild(mc); 270 | } 271 | } 272 | } 273 | // Setting up outer/inner class structure 274 | if (mc.getOuterClass() == null) { 275 | boolean outerClassASM = mc.getNode().outerClass != null; 276 | boolean outerClassName = mc.getOriginalName().contains("$"); 277 | String outerClass = null; 278 | if (outerClassASM) { 279 | outerClass = mc.getNode().outerClass; 280 | } else if (outerClassName) { 281 | outerClass = mc.getOriginalName().substring(0, mc.getOriginalName().indexOf("$")); 282 | if (outerClass.endsWith("/")) { 283 | // TODO: Do this better, account for obfuscations that 284 | // purposefully put $'s in names 285 | // The name starts with the $ so probably not actually an 286 | // outer class. Just obfuscation. 287 | outerClass = null; 288 | } 289 | } else { 290 | int synths = 0, synthID = -1; 291 | for (int fieldKey = 0; fieldKey < mc.getFields().size(); fieldKey++) { 292 | // Check for synthetic fields 293 | FieldNode fn = mc.getFields().get(fieldKey).getFieldNode(); 294 | if (fn == null) { 295 | continue; 296 | } 297 | int access = fn.access; 298 | if (AccessHelper.isSynthetic(access) && AccessHelper.isFinal(access) && !AccessHelper.isPublic(access) && !AccessHelper.isPrivate(access) 299 | && !AccessHelper.isProtected(access)) { 300 | synths++; 301 | synthID = fieldKey; 302 | } 303 | } 304 | if (synths == 1) { 305 | // If there is a single synthetic field referencing a class, 306 | // it's probably an anonymous inner class. 307 | FieldNode fn = mc.getFields().get(synthID).getFieldNode(); 308 | if (fn != null && fn.desc.contains(";")) { 309 | List matches = RegexUtils.matchDescriptionClasses(fn.desc); 310 | if (matches.size() > 0) { 311 | outerClass = matches.get(0); 312 | } 313 | } 314 | } 315 | } 316 | // Adding inner classes 317 | if (outerClass != null) { 318 | MappedClass outer = mappings.get(outerClass); 319 | if (outer != null) { 320 | outer.addInnerClass(mc); 321 | mc.setOuterClass(outer); 322 | mappings = linkMappings(outer, mappings); 323 | } 324 | } 325 | } 326 | // Adding method overrides 327 | for (MappedMember method : mc.getMethods()) { 328 | // Already checked for overrides. Skip. 329 | 330 | addOverrides(method); 331 | } 332 | mappings.put(mc.getOriginalName(), mc); 333 | return mappings; 334 | } 335 | 336 | /** 337 | * Given a MappedMember searches for members in parent classes that match 338 | * the same name & desc. 339 | * 340 | * @param method 341 | */ 342 | private static void addOverrides(MappedMember method) { 343 | // This method has already been searched. Skip. 344 | if (method.getFirstOverride() != null) { 345 | return; 346 | } 347 | MappedClass mappedClass = method.getOwner(); 348 | // Skip if already searched for methods 349 | List methodOverridens = new ArrayList(); 350 | MappedClass parent = mappedClass.getParent(); 351 | // Search the parents (search is inclusive from parent all the way up 352 | // the class hierarchy) for matching methods. 353 | if (parent != null) { 354 | MappedMember parentMethod = ParentUtils.findMethodInParentInclusive(parent, method.getOriginalName(), method.getDesc(), true); 355 | if (parentMethod != null) { 356 | methodOverridens.add(parentMethod); 357 | } 358 | } 359 | // Search the interfaces (search is inclusive from parent all the way up 360 | // the class hierarchy) for matching methods. 361 | for (MappedClass interfacee : mappedClass.getInterfaces()) { 362 | MappedMember interfaceMethod = ParentUtils.findMethodInParentInclusive(interfacee, method.getOriginalName(), method.getDesc(), true); 363 | if (interfaceMethod != null) { 364 | methodOverridens.add(interfaceMethod); 365 | } 366 | } 367 | // Add each override. 368 | for (MappedMember override : methodOverridens) { 369 | // Check the overriden member for additional overrides. 370 | addOverrides(override); 371 | // Don't add duplicates. Set up override structure for the given 372 | // method and the override. 373 | if (!method.getOverrides().contains(override)) { 374 | method.addOverride(override); 375 | override.addMemberThatOverridesMe(method); 376 | method.setIsLibrary(override.isLibrary()); 377 | } 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappingProcessor.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.objectweb.asm.ClassVisitor; 7 | import org.objectweb.asm.ClassWriter; 8 | import org.objectweb.asm.commons.ClassRemapper; 9 | import org.objectweb.asm.tree.ClassNode; 10 | 11 | public class MappingProcessor { 12 | 13 | /** 14 | * Given a map of ClassNodes and mappings, returns a map of class names to 15 | * class bytes. 16 | * 17 | * @param nodes 18 | * @param mappings 19 | * @return 20 | */ 21 | public static Map process(Map nodes, Map mappings, boolean useMaxs) { 22 | Map out = new HashMap(); 23 | SkidRemapper mapper = new SkidRemapper(mappings); 24 | try { 25 | for (ClassNode cn : nodes.values()) { 26 | ClassWriter cw = new MappingClassWriter(mappings, useMaxs ? ClassWriter.COMPUTE_MAXS : ClassWriter.COMPUTE_FRAMES); 27 | ClassVisitor remapper = new ClassRemapper(cw, mapper); 28 | cn.accept(remapper); 29 | out.put(mappings.containsKey(cn.name) ? mappings.get(cn.name).getNewName() : cn.name, cw.toByteArray()); 30 | } 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | return out; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/MappingRenamer.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | import org.objectweb.asm.tree.MethodNode; 11 | 12 | import me.lpk.mapping.remap.MappingMode; 13 | import me.lpk.util.ParentUtils; 14 | 15 | public class MappingRenamer { 16 | private static final Set whitelist = new HashSet(); 17 | private final List remapped = new ArrayList(); 18 | 19 | /** 20 | * Gives each MappedClass in a map new names based on rules defined in a 21 | * given MappingMode. 22 | * 23 | * @param mappings 24 | * @param mode 25 | * @return 26 | */ 27 | public Map remapClasses(Map mappings, MappingMode mode) { 28 | for (MappedClass mc : mappings.values()) { 29 | if (!mc.isLibrary()) { 30 | remapClass(mc, mappings, mode); 31 | } 32 | } 33 | return mappings; 34 | } 35 | 36 | /** 37 | * Gives a MappedClass based on rules defined in a given MappingMode 38 | * 39 | * @param mc 40 | * @param mappings 41 | * @param mode 42 | * @return 43 | */ 44 | public Map remapClass(MappedClass mc, Map mappings, MappingMode mode) { 45 | // If already renamed or is a library, MappedClass should not be 46 | // renamed. Skip. 47 | if (mc.isLibrary() || remapped.contains(mc.getOriginalName())) { 48 | return mappings; 49 | } 50 | // Remap parents before remapping the target class. 51 | if (mc.hasParent()) { 52 | mappings = remapClass(mc.getParent(), mappings, mode); 53 | } 54 | // Remap interfaces before remapping the target class. 55 | for (MappedClass interfaze : mc.getInterfaces()) { 56 | mappings = remapClass(interfaze, mappings, mode); 57 | } 58 | // Remap outer classes before remapping the target class. 59 | if (mc.isInnerClass()) { 60 | mappings = remapClass(mc.getOuterClass(), mappings, mode); 61 | } 62 | if (!mc.isInnerClass()) { 63 | // Rename the class 64 | mc.setNewName(mode.getClassName(mc)); 65 | } else { 66 | // Handling naming of inner class names 67 | // Syntax is: OuterClassName + $ + NewClassName 68 | MappedClass outter = mc.getOuterClass(); 69 | String newName = mode.getClassName(mc); 70 | String post = newName.contains("/") ? newName.substring(newName.lastIndexOf("/") + 1, newName.length()) : newName; 71 | mc.setNewName(outter.getNewName() + "$" + post); 72 | } 73 | // Rename fields 74 | for (MappedMember mm : mc.getFields()) { 75 | mm.setNewName(mode.getFieldName(mm)); 76 | } 77 | // Rename methods 78 | for (MappedMember mm : mc.getMethods()) { 79 | // Skip methods that should not be renamed. 80 | // Library is checked since in the generation phase, members 81 | // overriding library methods are in turn marked as library methods 82 | // themselves. 83 | if (keepName(mm) || mm.isLibrary()) { 84 | continue; 85 | } 86 | // Check and see if there is an overriding method to pull a name 87 | // from. 88 | MappedMember override = ParentUtils.findMethodOverride(mm); 89 | if (override.equals(mm)) { 90 | // No parent found. Give method a name 91 | mm.setNewName(mode.getMethodName(mm)); 92 | } else { 93 | // Override found. Give method parent's name. 94 | mm.setNewName(override.getNewName()); 95 | // Make sure if override structure is convoluted it's all named 96 | // correctly regardless. 97 | // This is only needed when a class extends and implements 98 | // multiple classes with shared method names. 99 | if (mm.doesOverride() && !mm.isOverriden()) { 100 | fixOverrideNames(mm, override); 101 | } 102 | } 103 | MethodNode mn = mm.getMethodNode(); 104 | updateStrings(mn, mappings); 105 | } 106 | remapped.add(mc.getOriginalName()); 107 | return mappings; 108 | } 109 | 110 | /** 111 | * Ensures all methods in the override structure have the same name. This is 112 | * only needed for cases like: http://pastebin.com/CpeD6wgN
113 | * TODO: Determine if this step is even needed for each input and ignore it 114 | * if it's not needed. 115 | * 116 | * @param mm 117 | * @param override 118 | */ 119 | private static void fixOverrideNames(MappedMember mm, MappedMember override) { 120 | for (MappedMember mm2 : mm.getOverrides()) { 121 | fixOverrideNames(mm2, override); 122 | } 123 | mm.setNewName(override.getNewName()); 124 | } 125 | 126 | /** 127 | * Updates strings when they are used in situations such as Class.forName / 128 | * Reflection. 129 | * 130 | * @param mn 131 | * @param mappings 132 | */ 133 | private static void updateStrings(MethodNode mn, Map mappings) { 134 | // TODO: Check for Class.forName(String) 135 | } 136 | 137 | /** 138 | * Checks if a given MappedMember should not be renamed. 139 | * 140 | * @param mm 141 | * @return 142 | */ 143 | public static boolean keepName(MappedMember mm) { 144 | // Main class 145 | if (mm.getDesc().equals("([Ljava/lang/String;)V") && mm.getOriginalName().equals("main")) { 146 | return true; 147 | } 148 | // or 149 | if (mm.getOriginalName().contains("<")) { 150 | return true; 151 | } 152 | // A method name that shan't be renamed! 153 | if (isNameWhitelisted(mm.getOriginalName())) { 154 | return true; 155 | } 156 | return false; 157 | } 158 | 159 | public static boolean isNameWhitelisted(String name) { 160 | return whitelist.contains(name); 161 | } 162 | 163 | static { 164 | 165 | // Should let user add additional names to the list 166 | // I guess classes like Enum don't have this as parent methods per say, 167 | // so this will be necessary. 168 | Collections.addAll(whitelist, "contains", "toString", "equals", "clone", "run", "start"); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/SkidRemapper.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.commons.Remapper; 6 | 7 | import me.lpk.util.ParentUtils; 8 | import me.lpk.util.StringUtils; 9 | 10 | /** 11 | * An implementation of ASM's Remapper. Given a map of MappedClasses in the 12 | * constructor updates the modified values in descriptions and names. 13 | * 14 | *
    15 | *
  • {@link #map(String)} - map type
  • 16 | *
  • {@link #mapFieldName(String, String, String)} - map field name
  • 17 | *
  • {@link #mapMethodName(String, String, String)} - map method name
  • 18 | *
19 | */ 20 | public class SkidRemapper extends Remapper { 21 | private final Map mappings; 22 | 23 | public SkidRemapper(Map renamed) { 24 | this.mappings = renamed; 25 | } 26 | 27 | @Override 28 | public String mapDesc(String desc) { 29 | return super.mapDesc(StringUtils.fixDesc(desc, mappings)); 30 | } 31 | 32 | @Override 33 | public String mapType(String type) { 34 | // Do not map null types 35 | if (type == null) { 36 | return null; 37 | } 38 | return super.mapType(StringUtils.fixDesc(type, mappings)); 39 | } 40 | 41 | @Override 42 | public String[] mapTypes(String[] types) { 43 | for (int i = 0; i < types.length; i++) { 44 | types[i] = StringUtils.fixDesc(types[i], mappings); 45 | } 46 | return super.mapTypes(types); 47 | } 48 | 49 | @Override 50 | public String mapMethodDesc(String desc) { 51 | if ("()V".equals(desc)) { 52 | return desc; 53 | } 54 | if (!desc.startsWith("(")) { 55 | // In case something goes terribly wrong, make it obvious what the issue is. 56 | throw new RuntimeException(); 57 | } 58 | return super.mapMethodDesc(StringUtils.fixDesc(desc, mappings)); 59 | } 60 | 61 | @Override 62 | public Object mapValue(Object value) { 63 | return super.mapValue(value); 64 | } 65 | 66 | @Override 67 | public String mapSignature(String signature, boolean typeSignature) { 68 | // Do not map null signatures 69 | if (signature == null) { 70 | return null; 71 | } 72 | return super.mapSignature(StringUtils.fixDesc(signature, mappings), typeSignature); 73 | } 74 | 75 | @Override 76 | public String mapMethodName(String owner, String name, String desc) { 77 | MappedClass mc = mappings.get(owner); 78 | if (mc == null) { 79 | // Default behavior if there is no mapping for the given method. 80 | return super.mapMethodName(owner, name, desc); 81 | } else { 82 | MappedMember mm = ParentUtils.findMethodInParentInclusive(mc, name, desc, true); 83 | if (mm != null) { 84 | return super.mapMethodName(owner, mm.getNewName(), desc); 85 | } 86 | } 87 | return super.mapMethodName(owner, name, desc); 88 | } 89 | 90 | @Override 91 | public String mapInvokeDynamicMethodName(String name, String desc) { 92 | MappedClass mc = mappings.get(StringUtils.getMappedFromDesc(mappings, desc)); 93 | if (mc == null) { 94 | // Default behavior if there is no mapping for the given method. 95 | return super.mapInvokeDynamicMethodName(name, desc); 96 | } else { 97 | MappedMember mm = ParentUtils.findMethodInParentInclusive(mc, name, desc, true); 98 | if (mm != null) { 99 | return super.mapInvokeDynamicMethodName(ParentUtils.findMethodOverride(mm).getNewName(), desc); 100 | } 101 | } 102 | return super.mapInvokeDynamicMethodName(name, desc); 103 | } 104 | 105 | @Override 106 | public String mapFieldName(String owner, String name, String desc) { 107 | MappedClass mc = mappings.get(owner); 108 | if (mc != null) { 109 | MappedMember field = ParentUtils.findFieldInParentInclusive(mc, name, desc, true); 110 | if (field != null) { 111 | return super.mapFieldName(owner, field.getNewName(), desc); 112 | } 113 | } 114 | return super.mapFieldName(owner, name, desc); 115 | } 116 | 117 | @Override 118 | public String map(String typeName) { 119 | return super.map(StringUtils.fixDesc(typeName, mappings)); 120 | } 121 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/loaders/EnigmaLoader.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.loaders; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import org.objectweb.asm.tree.ClassNode; 13 | import org.objectweb.asm.tree.FieldNode; 14 | import org.objectweb.asm.tree.MethodNode; 15 | 16 | import me.lpk.mapping.MappedClass; 17 | import me.lpk.mapping.MappedMember; 18 | import me.lpk.mapping.MappingFactory; 19 | 20 | public class EnigmaLoader extends MappingLoader { 21 | /** 22 | * Instantiates the loader with a map of classnodes to be mapped. 23 | * 24 | * @param nodes 25 | */ 26 | public EnigmaLoader(Map nodes) { 27 | super(nodes); 28 | } 29 | 30 | /** 31 | * Instantiates the loader without a classnode map. 32 | */ 33 | public EnigmaLoader() { 34 | super(null); 35 | } 36 | 37 | /** 38 | * Returns a map of MappedClasses based on the nodes given in the 39 | * constructor and the mapping file read through the parameter. 40 | * 41 | * @param in 42 | * FileReader of the Enigma mappings file 43 | * @return 44 | */ 45 | @Override 46 | public Map read(FileReader in) { 47 | try { 48 | return read(new BufferedReader(in)); 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | } 52 | return null; 53 | } 54 | 55 | /** 56 | * Reads each line in a reader and parses mappings from the Enigma format. 57 | * 58 | * @param fileReader 59 | * @return 60 | * @throws Exception 61 | */ 62 | @Override 63 | public Map read(BufferedReader fileReader) throws Exception { 64 | Map remap = new HashMap(); 65 | int lineNumber = 0; 66 | String line = null; 67 | MappedClass curClass = null; 68 | while ((line = fileReader.readLine()) != null) { 69 | lineNumber++; 70 | int commentPos = line.indexOf('#'); 71 | if (commentPos >= 0) { 72 | line = line.substring(0, commentPos); 73 | } 74 | if (line.trim().length() <= 0) { 75 | continue; 76 | } 77 | int indent = 0; 78 | for (int i = 0; i < line.length(); i++) { 79 | if (line.charAt(i) != '\t') { 80 | break; 81 | } 82 | indent++; 83 | } 84 | String[] parts = line.trim().split("\\s"); 85 | try { 86 | // read the first token 87 | String token = parts[0]; 88 | if (token.equalsIgnoreCase("CLASS")) { 89 | if (indent <= 0) { 90 | // outer class 91 | curClass = readClass(parts); 92 | remap.put(curClass.getOriginalName(), curClass); 93 | } else { 94 | // inner class 95 | // TODO: If Engima is ever updated so that inner classes 96 | // aren't borked add this. 97 | } 98 | } else if (token.equalsIgnoreCase("FIELD")) { 99 | if (curClass == null) { 100 | throw new Exception("Unexpected FIELD entry (Line: " + lineNumber + " )"); 101 | } 102 | addField(curClass, parts); 103 | } else if (token.equalsIgnoreCase("METHOD")) { 104 | if (curClass == null) { 105 | throw new Exception("Unexpected METHOD entry (Line: " + lineNumber + " )"); 106 | } 107 | addMethod(curClass, parts); 108 | } else if (token.equalsIgnoreCase("ARG")) { 109 | // SkidGUI does not map method args yet. 110 | if (curClass == null) { 111 | throw new Exception("Unexpected ARG entry (Line: " + lineNumber + " )"); 112 | } 113 | } 114 | } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { 115 | throw new Exception("Malformed line:\n" + line); 116 | } 117 | } 118 | // Fixing the MappedClass's parent / child structure. 119 | for (String className : remap.keySet()) { 120 | MappedClass mappedClass = remap.get(className); 121 | remap = MappingFactory.linkMappings(mappedClass, remap); 122 | } 123 | return remap; 124 | } 125 | 126 | @Override 127 | public void save(Map mappings, File file) { 128 | if (!file.exists()) { 129 | try { 130 | file.createNewFile(); 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | } 135 | try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) { 136 | for (MappedClass mc : mappings.values()) { 137 | if (mc.isLibrary()){ 138 | continue; 139 | } 140 | String renamed = mc.isTruelyRenamed() ? " " + mc.getNewName() : ""; 141 | bw.write("CLASS " + mc.getOriginalName() + renamed + "\n"); 142 | for (MappedMember mm : mc.getFields()) { 143 | bw.write("\tFIELD " + mm.getOriginalName() + " " + mm.getNewName() + " " + mm.getDesc() + "\n"); 144 | } 145 | for (MappedMember mm : mc.getMethods()) { 146 | bw.write("\tMETHOD " + mm.getOriginalName() + " " + mm.getNewName() + " " + mm.getDesc() + "\n"); 147 | } 148 | } 149 | } catch (IOException e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | /** 155 | * Generating mapping for a class. 156 | * 157 | * @param parts 158 | * @return 159 | */ 160 | private MappedClass readClass(String[] parts) { 161 | String original = parts[1]; 162 | if (original.startsWith("none/")) { 163 | original = original.substring("none/".length()); 164 | } 165 | MappedClass mc = null; 166 | if (parts.length == 2) { 167 | mc = new MappedClass(nodes == null ? fakeNode(original) : nodes.get(original), original); 168 | } else if (parts.length == 3) { 169 | String newName = parts[2]; 170 | mc = new MappedClass(nodes == null ? fakeNode(original) : nodes.get(original), newName); 171 | } 172 | return mc; 173 | } 174 | 175 | /** 176 | * Add a field to the given class. 177 | * 178 | * @param clazz 179 | * @param parts 180 | */ 181 | private void addField(MappedClass clazz, String[] parts) { 182 | String original = ""; 183 | String newName = ""; 184 | String desc = ""; 185 | if (parts.length == 3) { 186 | original = parts[1]; 187 | newName = parts[1]; 188 | desc = parts[2]; 189 | } else if (parts.length == 4) { 190 | original = parts[1]; 191 | newName = parts[2]; 192 | desc = parts[3]; 193 | } else { 194 | return; 195 | } 196 | if (desc.contains("Lnone/")) { 197 | desc = desc.replace("Lnone/", "L"); 198 | } 199 | MappedMember mm = new MappedMember(clazz, findField(clazz.getNode(), original, desc), -1, desc, original); 200 | mm.setNewName(newName); 201 | clazz.addField(mm); 202 | } 203 | 204 | /** 205 | * Add a method to the given class. 206 | * 207 | * @param clazz 208 | * @param parts 209 | */ 210 | private void addMethod(MappedClass clazz, String[] parts) { 211 | String original = ""; 212 | String newName = ""; 213 | String desc = ""; 214 | if (parts.length == 3) { 215 | original = parts[1]; 216 | newName = parts[1]; 217 | desc = parts[2]; 218 | } else if (parts.length == 4) { 219 | original = parts[1]; 220 | newName = parts[2]; 221 | desc = parts[3]; 222 | } else { 223 | return; 224 | } 225 | if (desc.contains("Lnone/")) { 226 | desc = desc.replace("Lnone/", "L"); 227 | } 228 | MappedMember mm = new MappedMember(clazz, findMethod(clazz.getNode(), original, desc), -1, desc, original); 229 | mm.setNewName(newName); 230 | clazz.addMethod(mm); 231 | } 232 | 233 | /** 234 | * Finds a field of a given name and description in a ClassNode. 235 | * 236 | * @param cn 237 | * @param name 238 | * @param desc 239 | * @return 240 | */ 241 | private FieldNode findField(ClassNode cn, String name, String desc) { 242 | if (cn == null) { 243 | return null; 244 | } 245 | for (FieldNode fn : cn.fields) { 246 | if (fn.desc.equals(desc) && fn.name.equals(name)) { 247 | return fn; 248 | } 249 | } 250 | return null; 251 | } 252 | 253 | /** 254 | * Finds a method of a given name and description in a ClassNode. 255 | * 256 | * @param cn 257 | * @param name 258 | * @param desc 259 | * @return 260 | */ 261 | private MethodNode findMethod(ClassNode cn, String name, String desc) { 262 | if (cn == null) { 263 | return null; 264 | } 265 | for (MethodNode mn : cn.methods) { 266 | if (mn.desc.equals(desc) && mn.name.equals(name)) { 267 | return mn; 268 | } 269 | } 270 | return null; 271 | } 272 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/loaders/MappingLoader.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.loaders; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.util.Map; 7 | 8 | import org.objectweb.asm.tree.ClassNode; 9 | 10 | import me.lpk.mapping.MappedClass; 11 | 12 | public abstract class MappingLoader { 13 | protected final Map nodes; 14 | 15 | /** 16 | * Instantiates the loader with a map of classnodes to be mapped. 17 | * 18 | * @param nodes 19 | */ 20 | public MappingLoader(Map nodes) { 21 | this.nodes = nodes; 22 | } 23 | 24 | public abstract Map read(FileReader in); 25 | 26 | public abstract Map read(BufferedReader fileReader) throws Exception; 27 | 28 | public abstract void save(Map mappings, File file); 29 | 30 | /** 31 | * Creates a fake node containing only a name attribute. 32 | * 33 | * @param name 34 | * @return 35 | */ 36 | protected ClassNode fakeNode(String name) { 37 | ClassNode cn = new ClassNode(); 38 | cn.name = name; 39 | return cn; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/loaders/ProguardLoader.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.loaders; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Map.Entry; 13 | import java.util.Set; 14 | 15 | import org.objectweb.asm.Type; 16 | import org.objectweb.asm.tree.ClassNode; 17 | 18 | import me.lpk.mapping.MappedClass; 19 | import me.lpk.mapping.MappedMember; 20 | import me.lpk.mapping.MappingFactory; 21 | import me.lpk.util.StringUtils; 22 | 23 | public class ProguardLoader extends MappingLoader { 24 | private final static Map primitives; 25 | 26 | static { 27 | primitives = new HashMap() { 28 | private static final long serialVersionUID = 1L; 29 | 30 | { 31 | put("void", "V"); 32 | put("int", "I"); 33 | put("long", "J"); 34 | put("byte", "B"); 35 | put("double", "D"); 36 | put("float", "F"); 37 | put("boolean", "Z"); 38 | put("char", "C"); 39 | put("short", "S"); 40 | } 41 | }; 42 | } 43 | 44 | /** 45 | * Instantiates the loader with a map of classnodes to be mapped. 46 | * 47 | * @param nodes 48 | */ 49 | public ProguardLoader(Map nodes) { 50 | super(nodes); 51 | } 52 | 53 | /** 54 | * Instantiates the loader without a classnode map. 55 | */ 56 | public ProguardLoader() { 57 | super(null); 58 | } 59 | 60 | /** 61 | * Returns a map of MappedClasses based on the nodes given in the 62 | * constructor and the mapping file read through the parameter. 63 | * 64 | * @param in 65 | * FileReader of the Proguard mappings file 66 | * @return 67 | */ 68 | @Override 69 | public Map read(FileReader in) { 70 | try { 71 | return read(new BufferedReader(in)); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } 75 | return null; 76 | } 77 | 78 | /** 79 | * Reads each line in a reader and parses mappings from the Proguard format. 80 | * 81 | * @param fileReader 82 | * @return 83 | * @throws Exception 84 | */ 85 | @Override 86 | public Map read(BufferedReader fileReader) throws Exception { 87 | Map origNameMap = new HashMap(); 88 | Map newNameMap = new HashMap(); 89 | String line = null; 90 | MappedClass curClass = null; 91 | while ((line = fileReader.readLine()) != null) { 92 | if (!line.contains("->")) { 93 | continue; 94 | } 95 | String[] parts = line.trim().split(" "); 96 | try { 97 | if (line.trim().endsWith(":")) { 98 | // Class definition 99 | curClass = readClass(parts); 100 | if (curClass != null) { 101 | origNameMap.put(curClass.getOriginalName(), curClass); 102 | newNameMap.put(curClass.getNewName(), curClass); 103 | } 104 | } else if (curClass != null) { 105 | if (isMethod(line.trim())) { 106 | // Method definition 107 | addMethod(curClass, parts); 108 | } else { 109 | // Field definition 110 | addField(curClass, parts); 111 | } 112 | } 113 | } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { 114 | throw new Exception("Malformed line:\n" + line); 115 | } 116 | } 117 | // Fixing the MappedClass's parent / child structure. 118 | Set origMapSet = new HashSet(); 119 | origMapSet.addAll(origNameMap.keySet()); 120 | for (String className : origMapSet) { 121 | MappedClass mappedClass = origNameMap.get(className); 122 | origNameMap = MappingFactory.linkMappings(mappedClass, origNameMap); 123 | } 124 | for (String className : origMapSet) { 125 | MappedClass mappedClass = origNameMap.get(className); 126 | for (MappedMember field : mappedClass.getFields()) { 127 | field.setDesc(StringUtils.fixDescReverse(field.getDesc(), origNameMap, newNameMap)); 128 | } 129 | for (MappedMember method : mappedClass.getMethods()) { 130 | method.setDesc(StringUtils.fixDescReverse(method.getDesc(), origNameMap, newNameMap)); 131 | } 132 | origNameMap.put(className, mappedClass); 133 | } 134 | return origNameMap; 135 | } 136 | 137 | @Override 138 | public void save(Map mappings, File file) { 139 | if (!file.exists()) { 140 | try { 141 | file.createNewFile(); 142 | } catch (IOException e) { 143 | e.printStackTrace(); 144 | } 145 | } 146 | try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) { 147 | for (MappedClass mc : mappings.values()) { 148 | if (mc.isLibrary()){ 149 | continue; 150 | } 151 | bw.write(mc.getOriginalName().replace("/", ".") + " -> " + mc.getNewName().replace("/", ".") + ":\n"); 152 | for (MappedMember mm : mc.getFields()) { 153 | bw.write(" " + toProguardFieldDesc(mm.getDesc()) + mm.getOriginalName() + " -> " + mm.getNewName()); 154 | } 155 | for (MappedMember mm : mc.getMethods()) { 156 | bw.write(" " + toProguardReturn(mm.getDesc()) + " " + mm.getOriginalName() + toProguardArgDesc(mm.getDesc()) + " -> " + mm.getNewName()); 157 | } 158 | } 159 | } catch (IOException e) { 160 | e.printStackTrace(); 161 | } 162 | } 163 | 164 | private String toProguardFieldDesc(String desc) { 165 | desc = desc.replace("/", "."); 166 | for (Entry es : primitives.entrySet()) { 167 | desc.replace(es.getValue(), es.getKey()); 168 | } 169 | return desc; 170 | } 171 | 172 | private String toProguardReturn(String desc) { 173 | desc = desc.replace("/", "."); 174 | desc = desc.substring(desc.indexOf(")") + 1); 175 | if (desc.length() == 1) { 176 | for (Entry es : primitives.entrySet()) { 177 | desc.replace(es.getValue(), es.getKey()); 178 | } 179 | } 180 | return desc; 181 | } 182 | 183 | private String toProguardArgDesc(String desc) { 184 | Type type = Type.getType(desc); 185 | String newDesc = "("; 186 | for (Type arg : type.getArgumentTypes()) { 187 | newDesc += arg.getInternalName() + ","; 188 | } 189 | if (newDesc.endsWith(",")) { 190 | newDesc = newDesc.substring(0, newDesc.length() - 1); 191 | } 192 | return newDesc + ")"; 193 | } 194 | 195 | private boolean isMethod(String text) { 196 | return text.contains("("); 197 | } 198 | 199 | /** 200 | * Generating mapping for a class. 201 | * 202 | * @param parts 203 | * @return 204 | */ 205 | private MappedClass readClass(String[] parts) { 206 | String original = parts[0].replace(".", "/"); 207 | String obfuscated = parts[2].replace(".", "/").substring(0, parts[2].length() - 1); 208 | ClassNode node = nodes == null ? fakeNode(obfuscated) : nodes.get(obfuscated); 209 | MappedClass mc = new MappedClass(node, obfuscated); 210 | if (mc != null) { 211 | mc.setNewName(original); 212 | } 213 | return mc; 214 | } 215 | 216 | /** 217 | * Add a field to the given class. 218 | * 219 | * @param clazz 220 | * @param parts 221 | */ 222 | private void addField(MappedClass clazz, String[] parts) { 223 | String newName = parts[1]; 224 | String original = parts[3]; 225 | String desc = fixDesc(parts[0]); 226 | MappedMember mm = new MappedMember(clazz, null, -1, desc, original); 227 | mm.setNewName(newName); 228 | clazz.addField(mm); 229 | } 230 | 231 | /** 232 | * Add a method to the given class. 233 | * 234 | * @param clazz 235 | * @param parts 236 | */ 237 | private void addMethod(MappedClass clazz, String[] parts) { 238 | String newName = parts[1].substring(0, parts[1].indexOf("(")); 239 | String original = parts[3]; 240 | String desc = fixDesc(parts[0], parts[1].substring(parts[1].indexOf("("))); 241 | MappedMember mm = new MappedMember(clazz, null, -1, desc, original); 242 | mm.setNewName(newName); 243 | clazz.addMethod(mm); 244 | } 245 | 246 | /** 247 | * Painfully creates a proper ASM description (for a method) given a return 248 | * type and parameters. 249 | * 250 | * @param type 251 | * @param parameters 252 | * @return 253 | */ 254 | private String fixDesc(String type, String parameters) { 255 | // type : parameters 256 | // void : (java.lang.Iterable,java.lang.Iterable,java.util.Map) 257 | String strReturnDesc = null, strParamsDesc = "", typeNoArr = type.replace("[]", ""); 258 | // Apply primitive names 259 | for (String key : primitives.keySet()) { 260 | if (typeNoArr.equals(key)) { 261 | strReturnDesc = getArrStr(type) + primitives.get(key); 262 | } 263 | } 264 | if (parameters.contains(",")) { 265 | // Multiple parameters 266 | String[] params = parameters.substring(1, parameters.length() - 1).split(","); 267 | for (String param : params) { 268 | boolean done = false; 269 | for (String key : primitives.keySet()) { 270 | if (param.replace("[]", "").equals(key)) { 271 | strParamsDesc += getArrStr(param) + primitives.get(key); 272 | done = true; 273 | } 274 | } 275 | 276 | if (!done) { 277 | strParamsDesc += getArrStr(param) + "L" + param.replace(".", "/").replace("[]", "") + ";"; 278 | } 279 | } 280 | } else if (parameters.equals("()")) { 281 | // No parameters 282 | strParamsDesc = ""; 283 | } else { 284 | // One parameter 285 | String param = parameters.substring(1, parameters.length() - 1); 286 | boolean done = false; 287 | for (String key : primitives.keySet()) { 288 | if (param.replace("[]", "").equals(key)) { 289 | strParamsDesc += getArrStr(param) + primitives.get(key); 290 | done = true; 291 | } 292 | } 293 | if (!done) { 294 | strParamsDesc += getArrStr(param) + "L" + param.replace("[]", "").replace(".", "/") + ";"; 295 | } 296 | } 297 | strParamsDesc = "(" + strParamsDesc + ")"; 298 | // Type is not just a primitive 299 | if (strReturnDesc == null) { 300 | strReturnDesc = "L" + typeNoArr.replace(".", "/") + ";"; 301 | } 302 | return strParamsDesc + strReturnDesc; 303 | } 304 | 305 | /** 306 | * Creates a proper ASM description given a type. 307 | * 308 | * @param type 309 | * @return 310 | */ 311 | private String fixDesc(String type) { 312 | // net.minecraft.util.IntHashMap$Entry[] 313 | String returnDesc = null, typeNoArr = type.replace("[]", ""); 314 | // Apply primitive names 315 | for (String key : primitives.keySet()) { 316 | if (type.replace("[]", "").equals(key)) { 317 | returnDesc = getArrStr(type) + primitives.get(key); 318 | } 319 | } 320 | // Type is not just a primitive 321 | if (returnDesc == null) { 322 | returnDesc = "L" + typeNoArr.replace(".", "/") + ";"; 323 | } 324 | return getArrStr(type) + returnDesc; 325 | } 326 | 327 | /** 328 | * Returns a string that signifies array depth in the proper ASM format. 329 | * 330 | * @param param 331 | * @return 332 | */ 333 | private String getArrStr(String param) { 334 | String arrayPrefix = ""; 335 | if (param.contains("[]")) { 336 | int array = 0; 337 | String paramCopy = param + ""; 338 | while (paramCopy.contains("[]")) { 339 | array++; 340 | paramCopy = paramCopy.substring(paramCopy.indexOf("[]") + 2); 341 | for (int i = 0; i < array; i++) { 342 | arrayPrefix = "[" + arrayPrefix; 343 | } 344 | } 345 | } 346 | return arrayPrefix; 347 | } 348 | 349 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/loaders/SRGLoader.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.loaders; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.objectweb.asm.tree.ClassNode; 14 | import org.objectweb.asm.tree.FieldNode; 15 | import org.objectweb.asm.tree.MethodNode; 16 | 17 | import me.lpk.mapping.MappedClass; 18 | import me.lpk.mapping.MappedMember; 19 | import me.lpk.util.StringUtils; 20 | 21 | public class SRGLoader extends MappingLoader { 22 | private boolean useNodes; 23 | 24 | /** 25 | * Instantiates the loader with a map of classnodes to be mapped. 26 | * 27 | * @param nodes 28 | */ 29 | public SRGLoader(Map nodes) { 30 | super(nodes); 31 | useNodes = true; 32 | } 33 | 34 | /** 35 | * Instantiates the loader without a classnode map. 36 | */ 37 | public SRGLoader() { 38 | super(null); 39 | useNodes = false; 40 | } 41 | 42 | /** 43 | * Returns a map of MappedClasses based on the nodes given in the 44 | * constructor and the mapping file read through the parameter. 45 | * 46 | * @param in 47 | * FileReader of the SRG mappings file 48 | * @return 49 | */ 50 | @Override 51 | public Map read(FileReader in) { 52 | try { 53 | return read(new BufferedReader(in)); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | return null; 58 | } 59 | 60 | /** 61 | * Reads each line in a reader and parses mappings from the SRG format. 62 | * 63 | * @param fileReader 64 | * @return 65 | * @throws Exception 66 | */ 67 | @Override 68 | public Map read(BufferedReader fileReader) throws Exception { 69 | Map remap = new HashMap(); 70 | String line = null; 71 | while ((line = fileReader.readLine()) != null) { 72 | if (line.startsWith("CL: ")) { 73 | String className = line.split(" ")[1]; 74 | String newClassName = line.split(" ")[2]; 75 | if (useNodes && !nodes.containsKey(className)) { 76 | continue; 77 | } 78 | MappedClass mc = new MappedClass(nodes == null ? fakeNode(className) : nodes.get(className), newClassName); 79 | remap.put(className, mc); 80 | } else if (line.startsWith("FD: ")) { 81 | String s1 = line.split(" ")[1], s2 = line.split(" ")[2]; 82 | String className = s1.substring(0, s1.lastIndexOf('/')); 83 | if (!remap.containsKey(className)) { 84 | continue; 85 | } 86 | String fieldName = s1.substring(s1.lastIndexOf('/') + 1); 87 | String newFieldName = s2.substring(s2.lastIndexOf('/') + 1); 88 | String desc = "L" + className + ";"; 89 | MappedClass mc = remap.get(className); 90 | // 91 | Object node = getFieldNode(mc.getNode().fields, fieldName, desc); 92 | MappedMember field = new MappedMember(mc, node, 0, desc, fieldName); 93 | field.setNewName(newFieldName); 94 | mc.addField(field); 95 | } else if (line.startsWith("MD: ")) { 96 | String s1 = line.split(" ")[1], desc = line.split(" ")[2], s3 = line.split(" ")[3]; 97 | String className = s1.substring(0, s1.lastIndexOf('/')); 98 | if (!remap.containsKey(className)) { 99 | continue; 100 | } 101 | String methodName = s1.substring(s1.lastIndexOf('/') + 1); 102 | String newMethodName = s3.substring(s3.lastIndexOf('/') + 1); 103 | MappedClass mc = remap.get(className); 104 | // 105 | Object node = getMethodNode(mc.getNode().methods, methodName, desc); 106 | MappedMember method = new MappedMember(mc, node, 0, desc, methodName); 107 | method.setNewName(newMethodName); 108 | mc.addMethod(method); 109 | } 110 | } 111 | return remap; 112 | } 113 | 114 | @Override 115 | public void save(Map mappings, File file) { 116 | if (!file.exists()) { 117 | try { 118 | file.createNewFile(); 119 | } catch (IOException e) { 120 | e.printStackTrace(); 121 | } 122 | } 123 | try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) { 124 | for (MappedClass mc : mappings.values()) { 125 | if (mc.isLibrary()){ 126 | continue; 127 | } 128 | if (mc.isTruelyRenamed()) { 129 | bw.write("CL: " + mc.getOriginalName() + " " + mc.getNewName() + "\n"); 130 | } 131 | } 132 | for (MappedClass mc : mappings.values()) { 133 | if (mc.isLibrary()){ 134 | continue; 135 | } 136 | for (MappedMember mm : mc.getFields()) { 137 | bw.write("FD: " + mc.getOriginalName() + "/" + mm.getOriginalName() + " " + mc.getNewName() + "/" + mm.getNewName() + "\n"); 138 | } 139 | } 140 | for (MappedClass mc : mappings.values()) { 141 | if (mc.isLibrary()){ 142 | continue; 143 | } 144 | for (MappedMember mm : mc.getMethods()) { 145 | bw.write("MD: " + mc.getOriginalName() + "/" + mm.getOriginalName() + " " + mm.getDesc() + " " + mc.getNewName() + "/" + mm.getNewName() + " " 146 | + StringUtils.fixDesc(mm.getDesc(), mappings) + "\n"); 147 | } 148 | } 149 | } catch (IOException e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | private static FieldNode getFieldNode(List fields, String name, String desc) { 155 | for (FieldNode fn : fields) { 156 | if (fn.name.equals(name) && fn.desc.equals(desc)) { 157 | return fn; 158 | } 159 | } 160 | return null; 161 | } 162 | 163 | private static MethodNode getMethodNode(List methods, String name, String desc) { 164 | for (MethodNode mn : methods) { 165 | if (mn.name.equals(name) && mn.desc.equals(desc)) { 166 | return mn; 167 | } 168 | } 169 | return null; 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/me/lpk/mapping/remap/MappingMode.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.remap; 2 | 3 | import me.lpk.mapping.MappedClass; 4 | import me.lpk.mapping.MappedMember; 5 | 6 | public abstract class MappingMode { 7 | /** 8 | * Creates a new name for a given class 9 | * 10 | * @param cn 11 | * @return 12 | */ 13 | public abstract String getClassName(MappedClass cn); 14 | 15 | /** 16 | * Creates a new name for a given method 17 | * 18 | * @param mn 19 | * @return 20 | */ 21 | public abstract String getMethodName(MappedMember mn); 22 | 23 | /** 24 | * Creates a new name for a given field 25 | * 26 | * @param fn 27 | * @return 28 | */ 29 | public abstract String getFieldName(MappedMember fn); 30 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/remap/impl/ModeNone.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.remap.impl; 2 | 3 | import me.lpk.mapping.MappedClass; 4 | import me.lpk.mapping.MappedMember; 5 | import me.lpk.mapping.remap.MappingMode; 6 | 7 | public class ModeNone extends MappingMode { 8 | @Override 9 | public String getClassName(MappedClass cn) { 10 | return cn.getOriginalName(); 11 | } 12 | 13 | @Override 14 | public String getMethodName(MappedMember mn) { 15 | return mn.getOriginalName(); 16 | } 17 | 18 | @Override 19 | public String getFieldName(MappedMember fn) { 20 | return fn.getOriginalName(); 21 | } 22 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/remap/impl/ModeRandom.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.remap.impl; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | import me.lpk.mapping.MappedClass; 7 | import me.lpk.mapping.MappedMember; 8 | import me.lpk.mapping.remap.MappingMode; 9 | import me.lpk.util.Characters; 10 | 11 | public class ModeRandom extends MappingMode { 12 | private Set used = new HashSet(); 13 | private int len; 14 | 15 | public ModeRandom(int len) { 16 | this.len = len; 17 | } 18 | 19 | @Override 20 | public String getClassName(MappedClass cn) { 21 | return randName(); 22 | } 23 | 24 | @Override 25 | public String getMethodName(MappedMember mn) { 26 | return randName(); 27 | } 28 | 29 | @Override 30 | public String getFieldName(MappedMember fn) { 31 | return randName(); 32 | } 33 | 34 | private String randName() { 35 | StringBuilder sb = new StringBuilder(); 36 | while (len > sb.length() || used.contains(sb.toString())) { 37 | int randIndex = (int) (Math.random() * Characters.ALPHABET_BOTH.length); 38 | sb.append(Characters.ALPHABET_BOTH[randIndex]); 39 | } 40 | used.add(sb.toString()); 41 | return sb.toString(); 42 | } 43 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/remap/impl/ModeSimple.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.remap.impl; 2 | 3 | import org.objectweb.asm.tree.MethodNode; 4 | 5 | import me.lpk.mapping.MappedClass; 6 | import me.lpk.mapping.MappedMember; 7 | import me.lpk.mapping.remap.MappingMode; 8 | 9 | public class ModeSimple extends MappingMode { 10 | private int classIndex, methodIndex, fieldIndex; 11 | 12 | @Override 13 | public String getClassName(MappedClass cn) { 14 | for (MethodNode mn : cn.getNode().methods) { 15 | if (mn.name.equals("main") && mn.desc.equals("([Ljava/lang/String;)V")) { 16 | return cn.getOriginalName(); 17 | } 18 | } 19 | return "Class" + classIndex++; 20 | } 21 | 22 | @Override 23 | public String getMethodName(MappedMember mn) { 24 | switch (mn.getDesc()) { 25 | case "([Ljava/lang/String;)V": 26 | if (mn.getOriginalName().equals("main")) { 27 | return "main"; 28 | } else { 29 | break; 30 | } 31 | case "()I": 32 | return "getInt" + methodIndex++; 33 | case "()J": 34 | return "getLong" + methodIndex++; 35 | case "()Z": 36 | return "getBoolean" + methodIndex++; 37 | case "()Ljava/lang/String": 38 | return "getString" + methodIndex++; 39 | case "()Ljava.util.Set;": 40 | case "()Ljava.util.HashSet;": 41 | return "getSet" + methodIndex++; 42 | case "()Ljava.util.List;": 43 | case "()Ljava.util.ArrayList;": 44 | return "getList" + methodIndex++; 45 | case "()Ljava.util.Map;": 46 | case "()Ljava.util.HashMap;": 47 | return "getMap" + methodIndex++; 48 | case "Ljava/lang/Class;": 49 | return "getClass" + methodIndex++; 50 | case "()F": 51 | return "getFloat" + methodIndex++; 52 | case "()D": 53 | return "getDouble" + methodIndex++; 54 | } 55 | return "method" + methodIndex++; 56 | } 57 | 58 | @Override 59 | public String getFieldName(MappedMember fn) { 60 | switch (fn.getDesc()) { 61 | case "I": 62 | return "int" + fieldIndex++; 63 | case "C": 64 | return "char" + fieldIndex++; 65 | case "J": 66 | return "long" + fieldIndex++; 67 | case "F": 68 | return "float" + fieldIndex++; 69 | case "D": 70 | return "double" + fieldIndex++; 71 | case "Z": 72 | return "boolean" + fieldIndex++; 73 | case "Ljava/lang/String;": 74 | return "string" + fieldIndex++; 75 | case "Ljava/util/Collection;": 76 | return "collection" + fieldIndex++; 77 | case "Ljava/util/Set;": 78 | case "Ljava/util/HashSet;": 79 | case "Ljava/util/LinkedHashSet;": 80 | return "set" + fieldIndex++; 81 | case "Ljava/util/List;": 82 | case "Ljava/util/ArrayList;": 83 | case "Ljava/util/LinkedList;": 84 | return "list" + fieldIndex++; 85 | case "Ljava/util/Map;": 86 | case "Ljava/util/HashMap;": 87 | case "Ljava/util/LinkedHashMap;": 88 | return "map" + fieldIndex++; 89 | case "Ljava/lang/Class;": 90 | return "class" + fieldIndex++; 91 | } 92 | if (fn.getDesc().startsWith("[")) { 93 | return "array" + fieldIndex++; 94 | } 95 | return "field" + fieldIndex++; 96 | } 97 | } -------------------------------------------------------------------------------- /src/me/lpk/mapping/remap/impl/ModeUnicodeEvil.java: -------------------------------------------------------------------------------- 1 | package me.lpk.mapping.remap.impl; 2 | import java.util.HashSet; 3 | import java.util.Set; 4 | 5 | import me.lpk.mapping.MappedClass; 6 | import me.lpk.mapping.MappedMember; 7 | import me.lpk.mapping.remap.MappingMode; 8 | import me.lpk.util.Characters; 9 | 10 | public class ModeUnicodeEvil extends MappingMode { 11 | public static final int UNICODE_MAX_LENGTH = 166; 12 | private Set used = new HashSet(); 13 | 14 | @Override 15 | public String getClassName(MappedClass cn) { 16 | return randName(); 17 | } 18 | 19 | @Override 20 | public String getMethodName(MappedMember mn) { 21 | return randName(); 22 | } 23 | 24 | @Override 25 | public String getFieldName(MappedMember fn) { 26 | return randName(); 27 | } 28 | 29 | private String randName() { 30 | StringBuilder sb = new StringBuilder(); 31 | while (sb.length() < UNICODE_MAX_LENGTH || used.contains(sb.toString())) { 32 | int randIndex = (int) (Math.random() * Characters.UNICODE.length); 33 | sb.append(Characters.UNICODE[randIndex]); 34 | } 35 | used.add(sb.toString()); 36 | return sb.toString(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/me/lpk/util/ASMUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import static org.objectweb.asm.Opcodes.*; 4 | 5 | import org.objectweb.asm.ClassReader; 6 | import org.objectweb.asm.ClassWriter; 7 | import org.objectweb.asm.Type; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.objectweb.asm.tree.FieldInsnNode; 10 | import org.objectweb.asm.tree.InsnNode; 11 | import org.objectweb.asm.tree.MethodNode; 12 | import org.objectweb.asm.tree.VarInsnNode; 13 | 14 | public class ASMUtils { 15 | /** 16 | * Gets the bytes of a given ClassNode. 17 | * 18 | * @param cn 19 | * @param useMaxs 20 | * @return 21 | */ 22 | public static byte[] getNodeBytes(ClassNode cn, boolean useMaxs) { 23 | ClassWriter cw = new ClassWriter(useMaxs ? ClassWriter.COMPUTE_MAXS : ClassWriter.COMPUTE_FRAMES); 24 | cn.accept(cw); 25 | byte[] b = cw.toByteArray(); 26 | return b; 27 | } 28 | 29 | /** 30 | * Gets a ClassNode based on given bytes 31 | * 32 | * @param bytez 33 | * @return 34 | */ 35 | public static ClassNode getNode(final byte[] bytez) { 36 | ClassReader cr = new ClassReader(bytez); 37 | ClassNode cn = new ClassNode(); 38 | try { 39 | cr.accept(cn, ClassReader.EXPAND_FRAMES); 40 | } catch (Exception e) { 41 | try { 42 | cr.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); 43 | } catch (Exception e2) { 44 | // e2.printStackTrace(); 45 | } 46 | } 47 | cr = null; 48 | return cn; 49 | } 50 | 51 | /** 52 | * Generates a getter method for the specified field 53 | * 54 | * @author Xerces 55 | * @param fieldName 56 | * the name of the field 57 | * @param className 58 | * the internal class name 59 | * @param fieldDesc 60 | * the field descriptor 61 | * @param methodName 62 | * the name of the method to create 63 | * @return the method as a {@link org.objectweb.asm.tree.MethodNode} 64 | */ 65 | public static MethodNode generateGetter(String methodName, String fieldName, String className, String fieldDesc) { 66 | MethodNode methodNode = new MethodNode(ACC_PUBLIC, methodName, "()" + fieldDesc, null, null); 67 | methodNode.instructions.insert(new VarInsnNode(ALOAD, 0)); 68 | methodNode.instructions.insert(new FieldInsnNode(GETFIELD, className, fieldName, fieldDesc)); 69 | methodNode.instructions.insert(new InsnNode(Type.getType(fieldDesc).getOpcode(IRETURN))); 70 | return methodNode; 71 | } 72 | 73 | /** 74 | * Generates a setter method for the specified field 75 | * 76 | * @author Xerces 77 | * @param fieldName 78 | * the name of the field 79 | * @param className 80 | * the internal class name 81 | * @param fieldDesc 82 | * the field descriptor 83 | * @param methodName 84 | * the name of the method to create 85 | * @return the method as a {@link org.objectweb.asm.tree.MethodNode} 86 | */ 87 | public static MethodNode generateSetter(String methodName, String fieldName, String className, String fieldDesc) { 88 | MethodNode methodNode = new MethodNode(ACC_PUBLIC, methodName, "(" + fieldDesc + ")V", null, null); 89 | methodNode.instructions.insert(new VarInsnNode(ALOAD, 0)); 90 | methodNode.instructions.insert(new VarInsnNode(Type.getType(fieldDesc).getOpcode(ILOAD), 1)); 91 | methodNode.instructions.insert(new FieldInsnNode(PUTFIELD, className, fieldName, fieldDesc)); 92 | methodNode.instructions.insert(new InsnNode(RETURN)); 93 | return methodNode; 94 | } 95 | 96 | /** 97 | * Adds interfaces to a class 98 | * 99 | * @author Xerces 100 | * @param classNode 101 | * the {@link org.objectweb.asm.tree.ClassNode} to add the 102 | * interfaces too 103 | * @param interfaces 104 | * a {@link java.lang.Class} array of the interfaces to add 105 | */ 106 | public static void addInterfaces(ClassNode classNode, Class[] interfaces) { 107 | for (Class interfaceClass : interfaces) { 108 | if (interfaceClass.isInterface()) { 109 | classNode.interfaces.add(interfaceClass.getName().replaceAll(".", "/")); 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/me/lpk/util/AccessHelper.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import static org.objectweb.asm.Opcodes.*; 4 | 5 | /** 6 | * @author Audrey 7 | * @since 4/30/15 8 | */ 9 | public class AccessHelper { 10 | /** 11 | * Tells whether a given access modifier is public 12 | * 13 | * @param mod 14 | * The access modifier to check 15 | * @return True if the access modifier is public, false otherwise 16 | */ 17 | public static boolean isPublic(int mod) { 18 | return (mod & ACC_PUBLIC) != 0; 19 | } 20 | 21 | /** 22 | * Tells whether a given access modifier is protected 23 | * 24 | * @param mod 25 | * The access modifier to check 26 | * @return True if the access modifier is protected, false otherwise 27 | */ 28 | public static boolean isProtected(int mod) { 29 | return (mod & ACC_PROTECTED) != 0; 30 | } 31 | 32 | /** 33 | * Tells whether a given access modifier is private 34 | * 35 | * @param mod 36 | * The access modifier to check 37 | * @return True if the access modifier is private, false otherwise 38 | */ 39 | public static boolean isPrivate(int mod) { 40 | return (mod & ACC_PRIVATE) != 0; 41 | } 42 | 43 | /** 44 | * Tells whether a given access modifier is static 45 | * 46 | * @param mod 47 | * The access modifier to check 48 | * @return True if the access modifier is static, false otherwise 49 | */ 50 | public static boolean isStatic(int mod) { 51 | return (mod & ACC_STATIC) != 0; 52 | } 53 | 54 | /** 55 | * Tells whether a given access modifier is native 56 | * 57 | * @param mod 58 | * The access modifier to check 59 | * @return True if the access modifier is native, false otherwise 60 | */ 61 | public static boolean isNative(int mod) { 62 | return (mod & ACC_NATIVE) != 0; 63 | } 64 | 65 | /** 66 | * Tells whether a given access modifier is abstract 67 | * 68 | * @param mod 69 | * The access modifier to check 70 | * @return True if the access modifier is abstract, false otherwise 71 | */ 72 | public static boolean isAbstract(int mod) { 73 | return (mod & ACC_ABSTRACT) != 0; 74 | } 75 | 76 | /** 77 | * Tells whether a given access modifier is final 78 | * 79 | * @param mod 80 | * The access modifier to check 81 | * @return True if the access modifier is final, false otherwise 82 | */ 83 | public static boolean isFinal(int mod) { 84 | return (mod & ACC_FINAL) != 0; 85 | } 86 | 87 | /** 88 | * Tells whether a given access modifier is synthetic. A modifier is 89 | * synthetic if it is marked with the ACC_SYNTHETIC flag (0x1000), as 90 | * specified in JLS8, 4.6 Methods. 91 | * 92 | * @param mod 93 | * The access modifier to check 94 | * @return True if the access modifier is synthetic, false otherwise 95 | */ 96 | public static boolean isSynthetic(int mod) { 97 | return (mod & ACC_SYNTHETIC) != 0; 98 | } 99 | 100 | /** 101 | * Tells whether a given access modifier is volatile 102 | * 103 | * @param mod 104 | * The access modifier to check 105 | * @return True if the access modifier is volatile, false otherwise 106 | */ 107 | public static boolean isVolatile(int mod) { 108 | return (mod & ACC_VOLATILE) != 0; 109 | } 110 | 111 | /** 112 | * Tells whether a given access modifier is bridge. A modifier is bridge if 113 | * it is marked with the ACC_BRIDGE flag (0x0040), as specified in JLS8, 4.6 114 | * Methods. 115 | * 116 | * @param mod 117 | * The access modifier to check 118 | * @return True if the access modifier is bridge, false otherwise 119 | */ 120 | public static boolean isBridge(int mod) { 121 | return (mod & ACC_BRIDGE) != 0; 122 | } 123 | 124 | /** 125 | * Tells whether a given access modifier is synchronized. A modifier is 126 | * synchronized if it is marked with the ACC_SYNCHRONIZED flag (0x0020), as 127 | * specified in JLS8, 4.6 Methods and JLS8, 2.11.10 128 | * Synchronization 129 | * 130 | * @param mod 131 | * The access modifier to check 132 | * @return True if the access modifier is synchronized, false otherwise 133 | */ 134 | public static boolean isSynchronized(int mod) { 135 | return (mod & ACC_SYNCHRONIZED) != 0; 136 | } 137 | 138 | /** 139 | * Tells whether a given access modifier is interface 140 | * 141 | * @param mod 142 | * The access modifier to check 143 | * @return True if the access modifier is interface, false otherwise 144 | */ 145 | public static boolean isInterface(int mod) { 146 | return (mod & ACC_INTERFACE) != 0; 147 | } 148 | 149 | /** 150 | * Tells whether a given access modifier is enum 151 | * 152 | * @param mod 153 | * The access modifier to check 154 | * @return True if the access modifier is enum, false otherwise 155 | */ 156 | public static boolean isEnum(int mod) { 157 | return (mod & ACC_ENUM) != 0; 158 | } 159 | 160 | /** 161 | * Tells whether a given access modifier is annotation (@interface) 162 | * 163 | * @param mod 164 | * The access modifier to check 165 | * @return True if the access modifier is annotation, false otherwise 166 | */ 167 | public static boolean isAnnotation(int mod) { 168 | return (mod & ACC_ANNOTATION) != 0; 169 | } 170 | 171 | /** 172 | * Tells whether a given access modifier is deprecated 173 | * 174 | * @param mod 175 | * The access modifier to check 176 | * @return True if the access modifier is deprecated, false otherwise 177 | */ 178 | public static boolean isDeprecated(int mod) { 179 | return (mod & ACC_DEPRECATED) != 0; 180 | } 181 | 182 | /** 183 | * Tells whether a given type is a void 184 | * 185 | * @param desc 186 | * The type description to check 187 | * @return True if the type is a void, false otherwise 188 | */ 189 | public static boolean isVoid(String desc) { 190 | return desc.endsWith("V"); 191 | } 192 | 193 | /** 194 | * Tells whether a given type is a boolean 195 | * 196 | * @param desc 197 | * The type description to check 198 | * @return True if the type is a boolean, false otherwise 199 | */ 200 | public static boolean isBoolean(String desc) { 201 | return desc.endsWith("Z"); 202 | } 203 | 204 | /** 205 | * Tells whether a given type is a char 206 | * 207 | * @param desc 208 | * The type description to check 209 | * @return True if the type is a char, false otherwise 210 | */ 211 | public static boolean isChar(String desc) { 212 | return desc.endsWith("C"); 213 | } 214 | 215 | /** 216 | * Tells whether a given type is a byte 217 | * 218 | * @param desc 219 | * The type description to check 220 | * @return True if the type is a byte, false otherwise 221 | */ 222 | public static boolean isByte(String desc) { 223 | return desc.endsWith("B"); 224 | } 225 | 226 | /** 227 | * Tells whether a given type is a short 228 | * 229 | * @param desc 230 | * The type description to check 231 | * @return True if the type is a short, false otherwise 232 | */ 233 | public static boolean isShort(String desc) { 234 | return desc.endsWith("S"); 235 | } 236 | 237 | /** 238 | * Tells whether a given type is an int 239 | * 240 | * @param desc 241 | * The type description to check 242 | * @return True if the type is an int, false otherwise 243 | */ 244 | public static boolean isInt(String desc) { 245 | return desc.endsWith("I"); 246 | } 247 | 248 | /** 249 | * Tells whether a given type is a float 250 | * 251 | * @param desc 252 | * The type description to check 253 | * @return True if the type is a float, false otherwise 254 | */ 255 | public static boolean isFloat(String desc) { 256 | return desc.endsWith("F"); 257 | } 258 | 259 | /** 260 | * Tells whether a given type is a long 261 | * 262 | * @param desc 263 | * The type description to check 264 | * @return True if the type is a long, false otherwise 265 | */ 266 | public static boolean isLong(String desc) { 267 | return desc.endsWith("J"); 268 | } 269 | 270 | /** 271 | * Tells whether a given type is a double 272 | * 273 | * @param desc 274 | * The type description to check 275 | * @return True if the type is a double, false otherwise 276 | */ 277 | public static boolean isDouble(String desc) { 278 | return desc.endsWith("D"); 279 | } 280 | 281 | /** 282 | * Tells whether a given type is an array 283 | * 284 | * @param desc 285 | * The type description to check 286 | * @return True if the type is an array, false otherwise 287 | */ 288 | public static boolean isArray(String desc) { 289 | return desc.startsWith("["); 290 | } 291 | 292 | /** 293 | * Tells whether a given type is an Object 294 | * 295 | * @param desc 296 | * The type description to check 297 | * @return True if the type is an Object, false otherwise 298 | */ 299 | public static boolean isObject(String desc) { 300 | return desc.endsWith(";"); 301 | } 302 | 303 | /** 304 | * Tells whether the given method signature is generic. The method is 305 | * considered generic if its signature ends with something along the lines 306 | * of ")TV;" 307 | * 308 | * @param desc 309 | * The method signature to check 310 | * @return True if the method signature is generic, false otherwise 311 | */ 312 | public static boolean isMethodReturnTypeGeneric(String desc) { 313 | return desc.contains(")T"); 314 | } 315 | 316 | /** 317 | * Tells whether the given field description+signature is generic. 318 | * 319 | * @param desc 320 | * Description of the field 321 | * @param signature 322 | * Signature of the field 323 | * @return True if the field is generic, false otherwise 324 | */ 325 | public static boolean isFieldGeneric(String desc, String signature) { 326 | return signature != null && desc != null && signature.startsWith("T") && signature.endsWith(";") && Character.isUpperCase(signature.charAt(1)) 327 | && desc.contains("java/lang/Object"); 328 | 329 | } 330 | } -------------------------------------------------------------------------------- /src/me/lpk/util/AntiSynthetic.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | 4 | import java.lang.reflect.Field; 5 | 6 | import org.objectweb.asm.Opcodes; 7 | 8 | public class AntiSynthetic implements Opcodes { 9 | public static int inverseSynthetic(int access) throws Exception { 10 | int i = 0; 11 | for (Field f : Opcodes.class.getFields()) { 12 | if (f.getName().startsWith("ACC_") && !f.getName().equals("ACC_SYNTHETIC")) { 13 | int accval = (int) f.get(null); 14 | if(hasAccess(access, accval)) { 15 | i |= accval; 16 | } 17 | } 18 | } 19 | return i; 20 | } 21 | 22 | private static boolean hasAccess(int mod, int access) { 23 | return (mod & access) != 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/me/lpk/util/Characters.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | import java.util.ArrayList; 3 | import java.util.List; 4 | 5 | public class Characters { 6 | public static final String[] ALPHABET_BOTH = { "A", "a", "B", "b", "C", "c", "D", "d", "E", "e", "F", "f", "G", "g", "H", "h", "I", "i", "J", "j", "K", "k", "L", "l", "M", "m", "N", "n", "O", "o", "P", "p", "Q", "q", "R", "r", "S", "s", "T", "t", "U", "u", "V", "v", "W", "w", "X", "x", "Y", "y", "Z", "z" }; 7 | public static final String[] ALPHABET_UPPER = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", }; 8 | public static final String[] ALPHABET_LOWER = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", }; 9 | // Aside from normal english text, unicode begins at 160 10 | public static final String[] UNICODE = genUnicode(1000, 9000); 11 | 12 | private static String[] genUnicode(int min, int max) { 13 | List list = new ArrayList(); 14 | for (int i = min; i < max; i++) { 15 | char c = (char) i; 16 | list.add(String.valueOf(c)); 17 | } 18 | return list.toArray(new String[list.size()]); 19 | } 20 | } -------------------------------------------------------------------------------- /src/me/lpk/util/Classpather.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.reflect.Method; 6 | import java.net.URL; 7 | import java.net.URLClassLoader; 8 | 9 | /** 10 | * Adds jar files to the classpath. 11 | */ 12 | public class Classpather { 13 | private static final Class[] parameters = new Class[] { URL.class }; 14 | 15 | /** 16 | * Add a file to the classpath. 17 | * 18 | * @param s 19 | * @throws IOException 20 | */ 21 | public static void addFile(String s) throws IOException { 22 | File f = new File(s); 23 | addFile(f); 24 | } 25 | 26 | /** 27 | * Add a file to the classpath. 28 | * 29 | * @param s 30 | * @throws IOException 31 | */ 32 | @SuppressWarnings("deprecation") 33 | public static void addFile(File f) throws IOException { 34 | addURL(f.toURL()); 35 | } 36 | 37 | private static void addURL(URL u) throws IOException { 38 | URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); 39 | Class sysclass = URLClassLoader.class; 40 | try { 41 | Method method = sysclass.getDeclaredMethod("addURL", parameters); 42 | method.setAccessible(true); 43 | method.invoke(sysloader, new Object[] { u }); 44 | } catch (Throwable t) { 45 | t.printStackTrace(); 46 | throw new IOException("Error, could not add URL to system classloader"); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/me/lpk/util/InjectedClassLoader.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.net.URL; 4 | import java.net.URLClassLoader; 5 | import java.util.HashMap; 6 | 7 | import org.objectweb.asm.ClassWriter; 8 | import org.objectweb.asm.tree.ClassNode; 9 | 10 | import me.lpk.analysis.Sandbox; 11 | 12 | public class InjectedClassLoader extends URLClassLoader { 13 | public static final HashMap extraClassDefs = new HashMap(); 14 | public static final HashMap nodes = new HashMap(); 15 | 16 | public InjectedClassLoader(URL[] urls, ClassLoader parent) { 17 | super(urls, parent); 18 | } 19 | 20 | @Override 21 | protected Class findClass(String name) throws ClassNotFoundException { 22 | try { 23 | ClassNode cn = nodes.get(name); 24 | if (cn != null) { 25 | ClassWriter cw = new ClassWriter(0); 26 | cn.accept(new Sandbox.VisitorImpl(cw)); 27 | byte[] b = cw.toByteArray(); 28 | return defineClass(cn.name, b, 0, b.length); 29 | } 30 | } catch (Throwable e) { 31 | try { 32 | return super.findClass(name); 33 | } catch (ClassNotFoundException e1) { 34 | e1.printStackTrace(); 35 | } 36 | } 37 | return null; 38 | } 39 | 40 | @Override 41 | public Class loadClass(String s) { 42 | try { 43 | ClassNode cn = nodes.get(s); 44 | if (cn != null) { 45 | ClassWriter cw = new ClassWriter(0); 46 | cn.accept(new Sandbox.VisitorImpl(cw)); 47 | byte[] b = cw.toByteArray(); 48 | return defineClass(cn.name, b, 0, b.length); 49 | } 50 | } catch (Throwable e) { 51 | try { 52 | return super.loadClass(s); 53 | } catch (ClassNotFoundException e1) { 54 | e1.printStackTrace(); 55 | } 56 | } 57 | return null; 58 | } 59 | } -------------------------------------------------------------------------------- /src/me/lpk/util/JarClassLoader.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.io.File; 4 | import java.io.InputStream; 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | import java.net.URLClassLoader; 8 | 9 | public class JarClassLoader extends URLClassLoader { 10 | 11 | public JarClassLoader(String... jar) throws MalformedURLException { 12 | super(getURLArr(jar)); 13 | } 14 | 15 | @Override 16 | public InputStream getResourceAsStream(String name) { 17 | return super.getResourceAsStream(name); 18 | } 19 | private static URL[] getURLArr(String[] jar) throws MalformedURLException { 20 | URL[] arr = new URL[jar.length]; 21 | for (int i = 0; i < jar.length; i++) { 22 | arr[i] = new File(jar[i]).toURI().toURL(); 23 | } 24 | return arr; 25 | } 26 | } -------------------------------------------------------------------------------- /src/me/lpk/util/JarUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.jar.JarEntry; 12 | import java.util.jar.JarFile; 13 | import java.util.jar.JarOutputStream; 14 | import java.util.stream.Stream; 15 | import java.util.zip.ZipEntry; 16 | import java.util.zip.ZipInputStream; 17 | 18 | import org.apache.commons.io.IOUtils; 19 | import org.objectweb.asm.tree.ClassNode; 20 | import org.objectweb.asm.tree.MethodNode; 21 | 22 | public class JarUtils { 23 | /** 24 | * Creates a map of for a given jar file 25 | * 26 | * @param jarFile 27 | * @author Konloch (Bytecode Viewer) 28 | * @return 29 | * @throws IOException 30 | */ 31 | public static Map loadClasses(File jarFile) throws IOException { 32 | Map classes = new HashMap(); 33 | JarFile jar = new JarFile(jarFile); 34 | Stream str = jar.stream(); 35 | // For some reason streaming = entries in messy jars 36 | // enumeration = no entries 37 | // Or if the jar is really big, enumeration = infinite hang 38 | // ... 39 | // Whatever. It works now! 40 | str.forEach(z -> readJar(jar, z, classes, null)); 41 | jar.close(); 42 | return classes; 43 | } 44 | 45 | public static Map loadRT() throws IOException { 46 | Map classes = new HashMap(); 47 | JarFile jar = new JarFile(getRT()); 48 | Stream str = jar.stream(); 49 | // TODO: Make ignoring these packages optional 50 | str.forEach(z -> readJar(jar, z, classes, Arrays.asList("com/sun/", "com/oracle/", "jdk/", "sun/"))); 51 | jar.close(); 52 | return classes; 53 | } 54 | 55 | /** 56 | * This method is less fussy about the jar integrity. 57 | * 58 | * @param jar 59 | * @param en 60 | * @param classes 61 | * @return 62 | */ 63 | private static Map readJar(JarFile jar, JarEntry en, Map classes, List ignored) { 64 | String name = en.getName(); 65 | try (InputStream jis = jar.getInputStream(en)) { 66 | if (name.endsWith(".class")) { 67 | if (ignored != null) { 68 | for (String s : ignored) { 69 | if (name.startsWith(s)) { 70 | return classes; 71 | } 72 | } 73 | } 74 | byte[] bytes = IOUtils.toByteArray(jis); 75 | String cafebabe = String.format("%02X%02X%02X%02X", bytes[0], bytes[1], bytes[2], bytes[3]); 76 | if (cafebabe.toLowerCase().equals("cafebabe")) { 77 | try { 78 | final ClassNode cn = ASMUtils.getNode(bytes); 79 | if (cn != null && (cn.name.equals("java/lang/Object") ? true : cn.superName != null)) { 80 | for (MethodNode mn : cn.methods) { 81 | mn.owner = cn.name; 82 | } 83 | classes.put(cn.name, cn); 84 | } 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | return classes; 94 | } 95 | 96 | /** 97 | * Creates a map of for a given jar file 98 | * 99 | * 100 | * @param jarFile 101 | * @return 102 | * @throws IOException 103 | */ 104 | public static Map loadNonClassEntries(File jarFile) throws IOException { 105 | Map entries = new HashMap(); 106 | ZipInputStream jis = new ZipInputStream(new FileInputStream(jarFile)); 107 | ZipEntry entry; 108 | while ((entry = jis.getNextEntry()) != null) { 109 | try { 110 | final String name = entry.getName(); 111 | if (!name.endsWith(".class") && !entry.isDirectory()) { 112 | byte[] bytes = IOUtils.toByteArray(jis); 113 | entries.put(name, bytes); 114 | } 115 | } catch (Exception e) { 116 | e.printStackTrace(); 117 | } finally { 118 | jis.closeEntry(); 119 | } 120 | } 121 | jis.close(); 122 | return entries; 123 | } 124 | 125 | /** 126 | * Saves a map of bytes to a jar file 127 | * 128 | * @param outBytes 129 | * @param output 130 | */ 131 | public static void saveAsJar(Map outBytes, File output) { 132 | try { 133 | JarOutputStream out = new JarOutputStream(new java.io.FileOutputStream(output)); 134 | for (String entry : outBytes.keySet()) { 135 | String ext = entry.contains(".") ? "" : ".class"; 136 | out.putNextEntry(new ZipEntry(entry + ext)); 137 | out.write(outBytes.get(entry)); 138 | out.closeEntry(); 139 | } 140 | out.close(); 141 | } catch (IOException e) { 142 | e.printStackTrace(); 143 | } 144 | } 145 | 146 | @SuppressWarnings("resource") 147 | public static String getManifestMainClass(File jar) { 148 | try { 149 | return new JarFile(jar.getAbsolutePath()).getManifest().getMainAttributes().getValue("Main-class").replace(".", "/"); 150 | } catch (Exception e) {} 151 | return null; 152 | } 153 | 154 | public static File getRT() { 155 | return new File(System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar"); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/me/lpk/util/ParentUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.tree.AbstractInsnNode; 9 | import org.objectweb.asm.tree.ClassNode; 10 | import org.objectweb.asm.tree.MethodInsnNode; 11 | import org.objectweb.asm.tree.MethodNode; 12 | 13 | import me.lpk.mapping.MappedClass; 14 | import me.lpk.mapping.MappedMember; 15 | 16 | public class ParentUtils { 17 | /** 18 | * Returns true if the method has called it's super method. 19 | * 20 | * @param mn 21 | * @return 22 | */ 23 | public static boolean callsSuper(MethodNode mn) { 24 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 25 | if (ain.getOpcode() == Opcodes.INVOKESPECIAL) { 26 | MethodInsnNode min = (MethodInsnNode) ain; 27 | if (min.name.equals(mn.name)) { 28 | return true; 29 | } 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | /** 36 | * Returns the field in the given class with the name and description. If 37 | * it's not in the given class, parents are checked. Returns null if nothing 38 | * is found. 39 | * 40 | * @param owner 41 | * @param name 42 | * @param desc 43 | * @return 44 | */ 45 | public static MappedMember findMethod(MappedClass owner, String name, String desc, boolean originalNames) { 46 | // Check the class itself 47 | for (MappedMember mm : owner.getMethods()) { 48 | if (matches(mm, name, desc, originalNames)) { 49 | return mm; 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | /** 56 | * Returns the method in the given class's parent with the name and 57 | * description. If it's not in the given class, further parents are checked. 58 | * Returns null if nothing is found. 59 | * 60 | * @param owner 61 | * @param name 62 | * @param desc 63 | * @return 64 | */ 65 | public static List findMethodParent(MappedClass owner, String name, String desc, boolean originalNames) { 66 | List list = new ArrayList(); 67 | // Check for interfaces in the method's class. 68 | for (MappedClass interfaceClass : owner.getInterfaces()) { 69 | MappedMember mm = findMethodInParentInclusive(interfaceClass, name, desc,originalNames); 70 | if (mm != null) { 71 | list.add(mm); 72 | } 73 | } 74 | // Check the parents 75 | if (owner.getParent() != null) { 76 | MappedMember mm = findMethodInParentInclusive(owner.getParent(), name, desc,originalNames); 77 | if (mm != null) { 78 | list.add(mm); 79 | } 80 | } 81 | return list; 82 | } 83 | 84 | public static MappedMember findMethodInParentInclusive(MappedClass owner, String name, String desc, boolean originalNames) { 85 | for (MappedMember mm : owner.getMethods()) { 86 | if (matches(mm, name, desc, originalNames)) { 87 | return mm; 88 | } 89 | } 90 | // Check for interfaces in the method's class. 91 | for (MappedClass interfaceClass : owner.getInterfaces()) { 92 | MappedMember mm = findMethodInParentInclusive(interfaceClass, name, desc, originalNames); 93 | if (mm != null) { 94 | return mm; 95 | } 96 | } 97 | // Check the parents 98 | if (owner.getParent() != null) { 99 | MappedMember mm = findMethodInParentInclusive(owner.getParent(), name, desc, originalNames); 100 | if (mm != null) { 101 | return mm; 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | /** 108 | * Finds the parent-most overridden member. 109 | * 110 | * @param mm 111 | * @return 112 | */ 113 | public static MappedMember findMethodOverride(MappedMember mm) { 114 | if (mm.doesOverride()) { 115 | // Overridden method's parent == given method's parent. 116 | for (MappedMember mm2 : mm.getOverrides()) { 117 | return findMethodOverride(mm2); 118 | } 119 | } 120 | return mm; 121 | } 122 | 123 | /** 124 | * Returns the field in the given class's parent with the name and 125 | * description. If it's not in the given class, further parents are checked. 126 | * Returns null if nothing is found. 127 | * 128 | * @param owner 129 | * @param name 130 | * @param desc 131 | * @return 132 | */ 133 | public static MappedMember findFieldInParent(MappedClass owner, String name, String desc, boolean originalNames) { 134 | 135 | // Check for interfaces in the field's class. 136 | for (MappedClass interfaceClass : owner.getInterfaces()) { 137 | MappedMember mm = findFieldInParentInclusive(interfaceClass, name, desc, originalNames); 138 | if (mm != null) { 139 | return mm; 140 | } 141 | } 142 | // Check the parents 143 | if (owner.getParent() != null) { 144 | MappedMember mm = findFieldInParentInclusive(owner.getParent(), name, desc, originalNames); 145 | if (mm != null) { 146 | return mm; 147 | } 148 | } 149 | return null; 150 | } 151 | 152 | public static MappedMember findFieldInParentInclusive(MappedClass owner, String name, String desc, boolean originalNames) { 153 | // Check the class itself 154 | for (MappedMember mm : owner.getFields()) { 155 | if (matches(mm, name, desc, originalNames)) { 156 | return mm; 157 | } 158 | } 159 | // Check for interfaces in the field's class. 160 | for (MappedClass interfaceClass : owner.getInterfaces()) { 161 | MappedMember mm = findFieldInParentInclusive(interfaceClass, name, desc, originalNames); 162 | if (mm != null) { 163 | return mm; 164 | } 165 | } 166 | // Check the parents 167 | if (owner.getParent() != null) { 168 | MappedMember mm = findFieldInParentInclusive(owner.getParent(), name, desc, originalNames); 169 | if (mm != null) { 170 | return mm; 171 | } 172 | } 173 | return null; 174 | } 175 | 176 | /** 177 | * Returns the method in the given class with the name and description. If 178 | * it's not in the given class, parents are checked. Returns null if nothing 179 | * is found. 180 | * 181 | * @param owner 182 | * @param name 183 | * @param desc 184 | * @return 185 | */ 186 | public static MappedMember findField(MappedClass owner, String name, String desc) { 187 | // Check the class itself 188 | for (MappedMember mm : owner.getFields()) { 189 | if (matches(mm, name, desc, true)) { 190 | return mm; 191 | } 192 | } 193 | return null; 194 | } 195 | 196 | /** 197 | * For some reason MappedMember.findNameAndDescWhatever(name,desc) doesn't 198 | * exactly work. This is an external implemtation which does the same thing 199 | * but somehow works. 200 | * 201 | * @param mm 202 | * @param name 203 | * @param desc 204 | * @return 205 | */ 206 | public static boolean matches(MappedMember mm, String name, String desc, boolean old) { 207 | if (name.equals(old ? mm.getOriginalName() : mm.getNewName()) && mm.getDesc().equals(desc)) { 208 | return true; 209 | } 210 | return false; 211 | } 212 | 213 | /** 214 | * Checks if two mapped members are the same. 215 | * 216 | * @param mm 217 | * @param mm2 218 | * @param orig 219 | * Whether to check original names or the updated names. 220 | * @return 221 | */ 222 | public static boolean matches(MappedMember mm, MappedMember mm2, boolean orig) { 223 | return matches(mm, orig ? mm2.getOriginalName() : mm2.getNewName(), mm2.getDesc(), true); 224 | } 225 | 226 | public static boolean isLoop(ClassNode node, Map nodes, int i) { 227 | ClassNode parentNode = nodes.get(node.superName); 228 | if (parentNode == null) { 229 | return false; 230 | } 231 | if (node.name.equals(parentNode.superName)) { 232 | return true; 233 | } 234 | return false; 235 | } 236 | 237 | public static MappedClass findCommonParent(MappedClass mc1, MappedClass mc2) { 238 | // Are they the same? 239 | if (mc1.getNewName().equals(mc2.getNewName())) { 240 | return mc1; 241 | } 242 | // Does m1 extend or implement anything m2 does? 243 | for (String parentNames1 : getParents(mc1)) { 244 | for (String parentNames2 : getParents(mc2)) { 245 | if (parentNames1.equals(parentNames2)) { 246 | return getParent(parentNames1, mc1); 247 | } 248 | } 249 | } 250 | return null; 251 | } 252 | 253 | private static MappedClass getParent(String n, MappedClass m) { 254 | while (m != null) { 255 | if (m.getOriginalName().equals(n)) { 256 | return m; 257 | } 258 | for (MappedClass i : m.getInterfaces()) { 259 | MappedClass p = getParent(n, i); 260 | if (p != null) { 261 | return p; 262 | } 263 | } 264 | m = m.getParent(); 265 | } 266 | return null; 267 | } 268 | 269 | private static List getParents(MappedClass m) { 270 | List list = new ArrayList(); 271 | while (m != null) { 272 | list.add(m.getOriginalName()); 273 | for (MappedClass i : m.getInterfaces()) { 274 | list.addAll(getParents(i)); 275 | } 276 | m = m.getParent(); 277 | } 278 | return list; 279 | } 280 | 281 | public static boolean isChild(MappedClass parent, MappedClass mc) { 282 | return findCommonParent(mc, parent) == parent; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/me/lpk/util/Reference.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import org.objectweb.asm.tree.AbstractInsnNode; 4 | import org.objectweb.asm.tree.ClassNode; 5 | import org.objectweb.asm.tree.MethodNode; 6 | 7 | public class Reference { 8 | private final ClassNode node; 9 | private final MethodNode method; 10 | private final AbstractInsnNode ain; 11 | 12 | public Reference(ClassNode node, MethodNode method, AbstractInsnNode ain) { 13 | this.node = node; 14 | this.method = method; 15 | this.ain = ain; 16 | } 17 | 18 | public ClassNode getNode() { 19 | return node; 20 | } 21 | 22 | public MethodNode getMethod() { 23 | return method; 24 | } 25 | 26 | public AbstractInsnNode getAin() { 27 | return ain; 28 | } 29 | } -------------------------------------------------------------------------------- /src/me/lpk/util/ReferenceUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.ClassNode; 8 | import org.objectweb.asm.tree.FieldInsnNode; 9 | import org.objectweb.asm.tree.FieldNode; 10 | import org.objectweb.asm.tree.MethodInsnNode; 11 | import org.objectweb.asm.tree.MethodNode; 12 | import org.objectweb.asm.tree.TypeInsnNode; 13 | 14 | public class ReferenceUtils { 15 | 16 | /** 17 | * Finds references to the target class in the second class. 18 | * 19 | * @param target 20 | * @param inThisNode 21 | * @return 22 | */ 23 | public static List getReferences(ClassNode target, ClassNode inThisNode) { 24 | List references = new ArrayList(); 25 | for (MethodNode method : inThisNode.methods) { 26 | references.addAll(getReferences(target, inThisNode, method)); 27 | } 28 | return references; 29 | } 30 | 31 | /** 32 | * Finds references to the target class's field in the second class. 33 | * 34 | * @param target 35 | * @param targetField 36 | * @param inThisNode 37 | * @return 38 | */ 39 | public static List getReferences(ClassNode target, FieldNode targetField, ClassNode inThisNode) { 40 | List references = new ArrayList(); 41 | for (MethodNode method : inThisNode.methods) { 42 | references.addAll(getReferences(target, targetField, inThisNode, method)); 43 | } 44 | return references; 45 | } 46 | 47 | /** 48 | * Finds references to the target class's method in the second class. 49 | * 50 | * @param target 51 | * @param targetMethod 52 | * @param inThisNode 53 | * @return 54 | */ 55 | public static List getReferences(ClassNode target, MethodNode targetMethod, ClassNode inThisNode) { 56 | List references = new ArrayList(); 57 | for (MethodNode method : inThisNode.methods) { 58 | references.addAll(getReferences(target, targetMethod, inThisNode, method)); 59 | } 60 | return references; 61 | } 62 | 63 | /** 64 | * Finds references to the class in the given method. 65 | * 66 | * @param target 67 | * @param method 68 | * @return 69 | */ 70 | public static List getReferences(ClassNode target, ClassNode inThisNode, MethodNode method) { 71 | String targetDesc = target.name; 72 | List references = new ArrayList(); 73 | for (AbstractInsnNode ain : method.instructions.toArray()) { 74 | switch (ain.getType()) { 75 | case AbstractInsnNode.METHOD_INSN: 76 | MethodInsnNode min = (MethodInsnNode) ain; 77 | if (min.desc.contains(targetDesc) || min.owner.contains(targetDesc)) { 78 | references.add(new Reference(inThisNode, method, ain)); 79 | } 80 | break; 81 | case AbstractInsnNode.FIELD_INSN: 82 | FieldInsnNode fin = (FieldInsnNode) ain; 83 | if (fin.desc.contains(targetDesc) || fin.owner.contains(targetDesc)) { 84 | references.add(new Reference(inThisNode, method, ain)); 85 | } 86 | break; 87 | case AbstractInsnNode.TYPE_INSN: 88 | TypeInsnNode tin = (TypeInsnNode) ain; 89 | if (tin.desc.contains(targetDesc)) { 90 | references.add(new Reference(inThisNode, method, ain)); 91 | } 92 | break; 93 | } 94 | } 95 | return references; 96 | } 97 | 98 | /** 99 | * Finds references to the class's field in the given method. 100 | * 101 | * @param target 102 | * @param targetField 103 | * @param method 104 | * @return 105 | */ 106 | public static List getReferences(ClassNode target, FieldNode targetField, ClassNode inThisNode, MethodNode method) { 107 | List references = new ArrayList(); 108 | for (AbstractInsnNode ain : method.instructions.toArray()) { 109 | if (ain.getType() == AbstractInsnNode.FIELD_INSN) { 110 | FieldInsnNode fin = (FieldInsnNode) ain; 111 | if (fin.owner.contains(target.name) && fin.name.equals(targetField.name) && fin.desc.equals(targetField.desc)) { 112 | references.add(new Reference(inThisNode, method, ain)); 113 | } 114 | } 115 | } 116 | return references; 117 | } 118 | 119 | /** 120 | * Finds references to the class's method in the given method. 121 | * 122 | * @param target 123 | * @param targetMethod 124 | * @param method 125 | * @return 126 | */ 127 | public static List getReferences(ClassNode target, MethodNode targetMethod, ClassNode inThisNode, MethodNode method) { 128 | List references = new ArrayList(); 129 | for (AbstractInsnNode ain : method.instructions.toArray()) { 130 | if (ain.getType() == AbstractInsnNode.METHOD_INSN) { 131 | MethodInsnNode min = (MethodInsnNode) ain; 132 | if (min.owner.contains(target.name) && min.name.equals(targetMethod.name) && min.desc.equals(targetMethod.desc)) { 133 | references.add(new Reference(inThisNode, method, ain)); 134 | } 135 | } 136 | } 137 | return references; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/me/lpk/util/RegexUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | public class RegexUtils { 9 | /** 10 | * Returns a list of all class matches in a description. 11 | * 12 | * @param desc 13 | * @return 14 | */ 15 | public static List matchDescriptionClasses(String desc) { 16 | String pattern = "(?<=L).*?(?=[<;(])"; 17 | Pattern pat = Pattern.compile(pattern); 18 | Matcher m = pat.matcher(desc); 19 | List matches = new ArrayList(); 20 | while (m.find()) { 21 | matches.add(m.group()); 22 | } 23 | return matches; 24 | } 25 | 26 | /** 27 | * Returns a list of all numbers found in a given string. 28 | * 29 | * @param text 30 | * @return 31 | */ 32 | public static List matchNumbers(String text) { 33 | String pattern = "\\d+[0-9]+"; 34 | Pattern pat = Pattern.compile(pattern); 35 | Matcher m = pat.matcher(text); 36 | List matches = new ArrayList(); 37 | while (m.find()) { 38 | matches.add(m.group()); 39 | } 40 | return matches; 41 | } 42 | 43 | /** 44 | * Returns true if text matches a given pattern. 45 | * 46 | * @param pattern 47 | * @param text 48 | * @return 49 | */ 50 | public static boolean isMatch(String pattern, String text) { 51 | Pattern pat = Pattern.compile(pattern); 52 | Matcher m = pat.matcher(text); 53 | while (m.find()) { 54 | return true; 55 | } 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/me/lpk/util/Setup.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import org.apache.commons.io.FileUtils; 11 | import org.objectweb.asm.tree.ClassNode; 12 | 13 | import me.lpk.log.Logger; 14 | import me.lpk.mapping.MappedClass; 15 | import me.lpk.mapping.MappedMember; 16 | import me.lpk.mapping.MappingFactory; 17 | 18 | /** 19 | * A container for all things necessary for general usage in SkidSuite. 20 | * Contains: 21 | *
    22 | *
  • Map of ClassNodes 23 | *
  • Map of MappedClasses 24 | *
25 | */ 26 | public class Setup { 27 | private static volatile Map rtMappings; 28 | private static volatile boolean setup, bypassSetup; 29 | private final static String sc = Setup.class.getSimpleName(); 30 | private final String jarName; 31 | private final Map nodes; 32 | private final Map mappings; 33 | private final Map libNodes; 34 | private final Map libMappings; 35 | 36 | public Setup(String name, Map nodes, Map mappings) { 37 | this.jarName = name; 38 | this.nodes = nodes; 39 | this.mappings = mappings; 40 | this.libNodes = new HashMap(); 41 | this.libMappings = new HashMap(); 42 | } 43 | 44 | public Setup(String name, Map nodes, Map mappings, Map libNodes, Map libMappings) { 45 | this.jarName = name; 46 | this.nodes = nodes; 47 | this.mappings = mappings; 48 | this.libNodes = libNodes; 49 | this.libMappings = libMappings; 50 | } 51 | 52 | /** 53 | * Given a jar file (Optional: and libraries) generates everything 54 | * needed for general usage in SkidSuite. 55 | * 56 | * @param jarIn 57 | * Jar to read from 58 | * @param readFileLibs 59 | * If libraries from the 'libraries' folder should be read 60 | * @return 61 | * @throws ImpatientSetupException 62 | */ 63 | public static Setup get(String jarIn, boolean readFileLibs) throws ImpatientSetupException { 64 | return get(jarIn, readFileLibs, null); 65 | } 66 | 67 | /** 68 | * Given a jar file (Optional: and libraries) generates everything 69 | * needed for general usage in SkidSuite. 70 | * 71 | * @param jarIn 72 | * Jar to read from 73 | * @param readFileLibs 74 | * If libraries from the 'libraries' folder should be read 75 | * @param libs 76 | * Additional library jars 77 | * @return 78 | * @throws ImpatientSetupException 79 | */ 80 | public static Setup get(String jarIn, boolean readFileLibs, Collection libs) throws ImpatientSetupException { 81 | boolean ignoredSetup = false; 82 | if (!setup) { 83 | if (!bypassSetup) { 84 | throw new ImpatientSetupException(); 85 | } else { 86 | ignoredSetup = true; 87 | } 88 | } 89 | Logger.logLow("Loading: " + jarIn + " (Reading Libraries: " + readFileLibs + ")..."); 90 | File in = new File(jarIn); 91 | Map nodes = loadNodes(in); 92 | Map libNodes = new HashMap(); 93 | if (readFileLibs) { 94 | if (libs == null) { 95 | libs = new ArrayList(); 96 | } 97 | libs.addAll(getLibraries()); 98 | } 99 | if (libs != null && libs.size() > 0) { 100 | for (File lib : libs) { 101 | try { 102 | for (ClassNode cn : JarUtils.loadClasses(lib).values()) { 103 | libNodes.put(cn.name, cn); 104 | } 105 | } catch (IOException e) { 106 | e.printStackTrace(); 107 | } 108 | } 109 | } 110 | // 111 | // 112 | Logger.logLow("Generating mappings..."); 113 | Map mappings = MappingFactory.mappingsFromNodesNoLinking(nodes); 114 | Map libMappings = new HashMap(MappingFactory.mappingsFromNodesNoLinking(libNodes)); 115 | if (libMappings.size() > 0) { 116 | Logger.logLow("Marking library nodes as read-only..."); 117 | for (MappedClass mc : libMappings.values()) { 118 | mc.setIsLibrary(true); 119 | for (MappedMember mm : mc.getFields()) { 120 | mm.setIsLibrary(true); 121 | } 122 | for (MappedMember mm : mc.getMethods()) { 123 | mm.setIsLibrary(true); 124 | } 125 | } 126 | } 127 | // 128 | // 129 | Logger.logLow("Merging target and library mappings..."); 130 | if (!ignoredSetup) { 131 | mappings.putAll(rtMappings); 132 | } 133 | if (libNodes.size() > 0) { 134 | mappings.putAll(libMappings); 135 | } 136 | for (MappedClass mc : mappings.values()) { 137 | MappingFactory.linkMappings(mc, mappings); 138 | } 139 | Logger.logLow("Completed loading from: " + jarIn); 140 | return new Setup(jarIn, nodes, mappings, libNodes, libMappings); 141 | } 142 | 143 | /** 144 | * Gets the name of the jar file this Setup read from. 145 | * 146 | * @return 147 | */ 148 | public String getJarName() { 149 | return jarName; 150 | } 151 | 152 | /** 153 | * Gets the map of ClassNodes (name to node). 154 | * 155 | * @return 156 | */ 157 | public Map getNodes() { 158 | return nodes; 159 | } 160 | 161 | /** 162 | * Gets the map of MappedClasses(name to mapping). 163 | * 164 | * @return 165 | */ 166 | public Map getMappings() { 167 | return mappings; 168 | } 169 | 170 | /** 171 | * Gets the map of ClassNodes(name to mapping), but only for library 172 | * nodes. 173 | * 174 | * @return 175 | */ 176 | public Map getLibNodes() { 177 | return libNodes; 178 | } 179 | 180 | /** 181 | * Gets the map of MappedClasses(name to mapping), but only for 182 | * library nodes. 183 | * 184 | * @return 185 | */ 186 | public Map getLibMappings() { 187 | return libMappings; 188 | } 189 | 190 | /** 191 | * Reads ClasNodes from a file and returns them as a map. 192 | * 193 | * @param file 194 | * @return 195 | */ 196 | private static Map loadNodes(File file) { 197 | Map nodes = null; 198 | try { 199 | nodes = JarUtils.loadClasses(file); 200 | } catch (IOException e) { 201 | e.printStackTrace(); 202 | } 203 | if (nodes == null) { 204 | Logger.errLow("Failed reading classes from: " + file.getAbsolutePath()); 205 | return null; 206 | } 207 | return nodes; 208 | } 209 | 210 | /** 211 | * Returns a list of libraries on loaded from the fily system. May be empty. 212 | * 213 | * @return 214 | */ 215 | private static List getLibraries() { 216 | return getLibraries(false); 217 | } 218 | 219 | /** 220 | * Returns a list of libraries on loaded from the fily system. May be empty. 221 | * 222 | * @param makeDir 223 | * @return 224 | */ 225 | private static List getLibraries(boolean makeDir) { 226 | List files = new ArrayList(); 227 | // 228 | if (makeDir) { 229 | File libDir = new File("libraries"); 230 | libDir.mkdirs(); 231 | for (File lib : FileUtils.listFiles(libDir, new String[] { "jar" }, true)) { 232 | files.add(lib); 233 | } 234 | } 235 | return files; 236 | } 237 | 238 | /** 239 | * This bypasses the setup process. This will skip a 5 or so second wait 240 | * time but it may result in less accurate output. Do not do this is 241 | * classes or class members are being renamed. 242 | */ 243 | public static void setBypassSetup() { 244 | bypassSetup = true; 245 | } 246 | 247 | /** 248 | * Loads RT the first time around. When this is loaded and saved once, it 249 | * makes using LazySetupMaker much quicker than doing it every time. It'd be 250 | * even better to save this to a local file or something. 251 | */ 252 | public static void setup() { 253 | try { 254 | if (setup) { 255 | return; 256 | } 257 | Logger.logLow("Setting up " + sc + "..."); 258 | Map libNodes = new HashMap(); 259 | for (ClassNode cn : JarUtils.loadRT().values()) { 260 | libNodes.put(cn.name, cn); 261 | } 262 | Map libMappings = new HashMap(MappingFactory.mappingsFromNodesNoLinking(libNodes)); 263 | 264 | for (MappedClass mc : libMappings.values()) { 265 | mc.setIsLibrary(true); 266 | for (MappedMember mm : mc.getFields()) { 267 | mm.setIsLibrary(true); 268 | } 269 | for (MappedMember mm : mc.getMethods()) { 270 | mm.setIsLibrary(true); 271 | } 272 | } 273 | for (MappedClass mc : libMappings.values()) { 274 | MappingFactory.linkMappings(mc, libMappings); 275 | } 276 | rtMappings = libMappings; 277 | setup = true; 278 | } catch (IOException e) { 279 | e.printStackTrace(); 280 | rtMappings = new HashMap(); 281 | } 282 | Logger.logLow("Finished setting up "+sc+"!"); 283 | } 284 | 285 | static class ImpatientSetupException extends Exception { 286 | public ImpatientSetupException() { 287 | super(sc + " has not finished preparing!"); 288 | } 289 | 290 | private static final long serialVersionUID = 54L; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/me/lpk/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.regex.Pattern; 7 | 8 | import me.lpk.mapping.MappedClass; 9 | 10 | public class StringUtils { 11 | /** 12 | * Given an obfuscated description, finds the new values for class names and 13 | * updates the description. 14 | * 15 | * @param description 16 | * @param oldNamestoClasses 17 | * @return 18 | */ 19 | public static String fixDesc(String description, Map oldNamestoClasses) { 20 | if (description == null || description.length() == 0 || isPrimitive(description)) { 21 | return description; 22 | } 23 | if (description.contains("L") && description.contains(";")) { 24 | if (description.startsWith("(") || (description.startsWith("L") || description.startsWith("[")) && description.endsWith(";")) { 25 | String regex = "(?<=[L])[^;]*(?=;)"; 26 | Pattern p = Pattern.compile(regex); 27 | Matcher m = p.matcher(Pattern.quote(description)); 28 | for (int i = 0; i < m.groupCount(); i++) { 29 | String found = m.group(i); 30 | description = description.replace(found, fixDesc(found, oldNamestoClasses)); 31 | } 32 | return description; 33 | } 34 | } else { 35 | MappedClass mc = oldNamestoClasses.get(description); 36 | if (mc == null) { 37 | return description; 38 | } 39 | return mc.getNewName(); 40 | } 41 | return description; 42 | } 43 | 44 | /** 45 | * Unfixes descriptions. (Lknown/class/Name; --> Lobfu;) 46 | * 47 | * @param description 48 | * @param oldNamestoClasses 49 | * @param newNamesToClasses 50 | * @return 51 | */ 52 | public static String fixDescReverse(String description, Map oldNamestoClasses, Map newNamesToClasses) { 53 | if (description == null || description.length() == 0 || isPrimitive(description)) { 54 | return description; 55 | } 56 | if (description.contains("L") && description.contains(";")) { 57 | if (description.startsWith("(") || (description.startsWith("L") || description.startsWith("[")) && description.endsWith(";")) { 58 | List findClasses = RegexUtils.matchDescriptionClasses(description); 59 | for (String found : findClasses) { 60 | MappedClass mc = newNamesToClasses.get(found); 61 | if (mc != null) { 62 | description = description.replace(found, mc.getOriginalName()); 63 | } 64 | } 65 | return description; 66 | } 67 | } else { 68 | MappedClass mc = oldNamestoClasses.get(description); 69 | if (mc == null) { 70 | return description; 71 | } 72 | return mc.getNewName(); 73 | } 74 | return description; 75 | } 76 | 77 | /** 78 | * Replaces strings with old references with ones with updated references. 79 | * 80 | * @param orig 81 | * @param oldStr 82 | * @param newStr 83 | * @return 84 | */ 85 | public static String replace(String orig, String oldStr, String newStr) { 86 | StringBuffer sb = new StringBuffer(orig); 87 | while (contains(sb.toString(), oldStr)) { 88 | if (orig.contains("(") && orig.contains(";")) { 89 | // orig is most likely a method desc 90 | int start = sb.indexOf("L" + oldStr) + 1; 91 | int end = sb.indexOf(oldStr + ";") + oldStr.length(); 92 | if (start > -1 && end <= orig.length()) { 93 | sb.replace(start, end, newStr); 94 | } else { 95 | System.err.println("REPLACE FAIL: (" + oldStr + ") - " + orig); 96 | break; 97 | } 98 | } else if (orig.startsWith("L") && orig.endsWith(";")) { 99 | // orig is most likely a class desc 100 | if (orig.substring(1, orig.length() - 1).equals(oldStr)) { 101 | sb.replace(1, orig.length() - 1, newStr); 102 | } 103 | } else { 104 | // Dunno 105 | if (orig.equals(oldStr)) { 106 | sb.replace(0, sb.length(), newStr); 107 | } else { 108 | // This shouldn't happen. 109 | System.err.println("FUCK: (" + sb.toString() + ") - " + oldStr + ":" + newStr); 110 | break; 111 | } 112 | } 113 | } 114 | return sb.toString(); 115 | } 116 | 117 | public static boolean contains(String orig, String check) { 118 | if (orig.contains(check)) { 119 | String regex = "([L]" + check.replace("/", "\\/") + "{1}[;])"; 120 | if (orig.contains("(") && orig.contains(";")) { 121 | return orig.matches(regex); 122 | } else if (orig.startsWith("L") && orig.endsWith(";") && orig.substring(1, orig.length() - 1).equals(check)) { 123 | return true; 124 | } else if (orig.equals(check)) { 125 | return true; 126 | } 127 | } 128 | return false; 129 | } 130 | 131 | /** 132 | * Gets a MappedClass in renamemap from a class's description 133 | * 134 | * @param renamemap 135 | * @param desc 136 | * @return 137 | */ 138 | public static MappedClass getMappedFromDesc(Map renamemap, String desc) { 139 | if (desc.length() <= 3) { 140 | return null; 141 | } 142 | int beginIndex = desc.indexOf("L"); 143 | int endIndex = desc.indexOf(";"); 144 | if (beginIndex == -1 || endIndex == -1) { 145 | return null; 146 | } 147 | String owner = desc.substring(beginIndex + 1, endIndex); 148 | return renamemap.get(owner); 149 | } 150 | 151 | /** 152 | * Checks if a given string is a link. 153 | * 154 | * TODO: Make this not shitty. 155 | * 156 | * @param input 157 | * @return 158 | */ 159 | public static boolean isLink(String input) { 160 | String regex = "[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&\\/=]*)"; 161 | if (input.contains("/") && input.contains(".") && input.matches(regex)) { 162 | return true; 163 | } 164 | // TODO: This is old, swap this section out for regex. The above does 165 | // not catch every URL. 166 | String[] lookFor = new String[] { "http://", "https://", "www.", ".", "ftp:", ".net", ".gov", ".com", ".org", ".php", ".tk", "www", ".io", ".xyz", ".cf", 167 | "upload" }; 168 | int i = 0; 169 | for (String lf : lookFor) { 170 | if (input.toLowerCase().contains(lf.toLowerCase())) { 171 | i++; 172 | if (i > 2) { 173 | return true; 174 | } 175 | } 176 | } 177 | return false; 178 | } 179 | 180 | /** 181 | * Checks if a description is just a primitive. 182 | * 183 | * @param description 184 | * @return 185 | */ 186 | private static boolean isPrimitive(String description) { 187 | String x = asmTrim(description); 188 | if (x.length() == 0) { 189 | return true; 190 | } else if (x.equals("Z") || x.equals("J") || x.equals("I") || x.equals("F") || x.equals("D") || x.equals("C") || x.equals("T") || x.equals("G")) { 191 | return true; 192 | } 193 | return false; 194 | } 195 | 196 | /** 197 | * Checks if a given string is an IP address. 198 | * 199 | * @param input 200 | * @return 201 | */ 202 | public static boolean isIP(String input) { 203 | String regex = "(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"; 204 | Pattern p = Pattern.compile(regex); 205 | Matcher m = p.matcher(input); 206 | String ss = input.replace(".", ""); 207 | if (m.find() && isNumeric(ss) && (input.length() - ss.length() > 2)) { 208 | return true; 209 | } 210 | return false; 211 | } 212 | 213 | /** 214 | * Checks if a string is a number 215 | * 216 | * @param s 217 | * @return 218 | */ 219 | public static boolean isNumeric(String s) { 220 | try { 221 | Double.parseDouble(s); 222 | return true; 223 | } catch (Exception e) { 224 | return false; 225 | } 226 | } 227 | 228 | public static String asmTrim(String s) { 229 | // TODO: should this remove primitives? 230 | // (?=([L;()\/\[IDFJBZV])) 231 | return s.replaceAll("(?=([L;()\\/\\[IDFJBZV]))", ""); 232 | } 233 | } -------------------------------------------------------------------------------- /src/me/lpk/util/SwingUtils.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import javax.swing.tree.DefaultMutableTreeNode; 8 | 9 | public class SwingUtils { 10 | /** 11 | * Method by Adrian: [ 12 | * StackOverflow ] 13 | * & Mike: [ 15 | * StackOverflow ] 16 | * 17 | * @param node 18 | * @return 19 | */ 20 | @SuppressWarnings("unchecked") 21 | public static DefaultMutableTreeNode sort(DefaultMutableTreeNode node) { 22 | List children = Collections.list(node.children()); 23 | List orgCnames = new ArrayList(); 24 | List cNames = new ArrayList(); 25 | DefaultMutableTreeNode temParent = new DefaultMutableTreeNode(); 26 | for (DefaultMutableTreeNode child : children) { 27 | DefaultMutableTreeNode ch = (DefaultMutableTreeNode) child; 28 | temParent.insert(ch, 0); 29 | String uppser = ch.toString().toUpperCase(); 30 | // Not dependent on package name, so if duplicates are found 31 | // they will later on be confused. Adding this is of 32 | // very little consequence and fixes the issue. 33 | if (cNames.contains(uppser)){ 34 | uppser += "$COPY"; 35 | } 36 | cNames.add(uppser); 37 | orgCnames.add(uppser); 38 | if (!child.isLeaf()) { 39 | sort(child); 40 | } 41 | } 42 | Collections.sort(cNames); 43 | for (String name : cNames) { 44 | int indx = orgCnames.indexOf(name); 45 | int insertIndex = node.getChildCount(); 46 | node.insert(children.get(indx), insertIndex); 47 | } 48 | // Fixing folder placement 49 | for (int i = 0; i < node.getChildCount() - 1; i++) { 50 | DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); 51 | for (int j = i + 1; j <= node.getChildCount() - 1; j++) { 52 | DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); 53 | if (!prevNode.isLeaf() && child.isLeaf()) { 54 | node.insert(child, j); 55 | node.insert(prevNode, i); 56 | } 57 | } 58 | } 59 | return node; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/me/lpk/util/Timer.java: -------------------------------------------------------------------------------- 1 | package me.lpk.util; 2 | 3 | import me.lpk.log.Logger; 4 | 5 | public class Timer { 6 | final long init; 7 | long then, now; 8 | 9 | public Timer() { 10 | init = System.currentTimeMillis(); 11 | then = System.currentTimeMillis(); 12 | } 13 | 14 | public void log(String s) { 15 | now = System.currentTimeMillis(); 16 | Logger.logLow(s + (now - then)); 17 | then = now; 18 | } 19 | 20 | public void logTotal(String s) { 21 | now = System.currentTimeMillis(); 22 | Logger.logLow(s + (now - init)); 23 | then = now; 24 | } 25 | } -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/JarArchive.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller; 2 | 3 | import java.io.File; 4 | import java.util.Map; 5 | 6 | import org.objectweb.asm.tree.ClassNode; 7 | 8 | public class JarArchive { 9 | 10 | private Map classes; 11 | private Map out; 12 | private File input; 13 | 14 | public JarArchive(File input, Map classes, Map out) { 15 | this.input = input; 16 | this.classes = classes; 17 | this.out = out; 18 | } 19 | 20 | public Map getClasses() { 21 | return classes; 22 | } 23 | 24 | public Map getOut() { 25 | return out; 26 | } 27 | 28 | public File getInput() { 29 | return input; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/ZelixKiller.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.logging.ConsoleHandler; 9 | import java.util.logging.Handler; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | 13 | import org.apache.commons.cli.CommandLine; 14 | import org.apache.commons.cli.CommandLineParser; 15 | import org.apache.commons.cli.DefaultParser; 16 | import org.apache.commons.cli.HelpFormatter; 17 | import org.apache.commons.cli.Options; 18 | import org.objectweb.asm.ClassWriter; 19 | import org.objectweb.asm.tree.ClassNode; 20 | 21 | import me.lpk.util.JarUtils; 22 | import me.nov.zelixkiller.transformer.Transformer; 23 | import me.nov.zelixkiller.transformer.zkm.ExceptionObfuscationTX; 24 | import me.nov.zelixkiller.transformer.zkm11.ControlFlowT11; 25 | import me.nov.zelixkiller.transformer.zkm11.ReflectionObfuscationVMT11; 26 | import me.nov.zelixkiller.transformer.zkm11.StringObfuscationCipherT11; 27 | import me.nov.zelixkiller.transformer.zkm11.StringObfuscationCipherVMT11; 28 | import me.nov.zelixkiller.transformer.zkm11.StringObfuscationT11; 29 | 30 | public class ZelixKiller { 31 | public final static Logger logger = Logger.getLogger(ZelixKiller.class.getName()); 32 | private final static HashMap> transformers = new HashMap<>(); 33 | 34 | static { 35 | System.setProperty("java.util.logging.SimpleFormatter.format", "[%1$tT] [%4$-7s] %5$s %6$s%n"); 36 | transformers.put("s11", StringObfuscationT11.class); 37 | transformers.put("si11", StringObfuscationCipherT11.class); 38 | transformers.put("sivm11", StringObfuscationCipherVMT11.class); 39 | transformers.put("rvm11", ReflectionObfuscationVMT11.class); 40 | transformers.put("cf11", ControlFlowT11.class); 41 | transformers.put("ex", ExceptionObfuscationTX.class); 42 | } 43 | 44 | public static void main(String[] args) throws Exception { 45 | Options options = new Options(); 46 | options.addOption("i", "input", true, "The obfuscated input file to use"); 47 | options.addOption("o", "output", true, "The output file"); 48 | options.addOption("t", "transformer", true, "The transformer to use"); 49 | options.addOption("v", "verbose", false, "Turn on verbose mode"); 50 | options.addOption("?", "help", false, "Prints this help"); 51 | 52 | CommandLineParser parser = new DefaultParser(); 53 | CommandLine line; 54 | try { 55 | line = parser.parse(options, args); 56 | } catch (org.apache.commons.cli.ParseException e) { 57 | e.printStackTrace(); 58 | throw new RuntimeException("An error occurred while parsing the commandline!"); 59 | } 60 | if (line.hasOption("help") || !line.hasOption("i") || !line.hasOption("o") || !line.hasOption("t")) { 61 | HelpFormatter formatter = new HelpFormatter(); 62 | formatter.printHelp("zelixkiller 11", options); 63 | return; 64 | } 65 | if (line.hasOption("v")) { 66 | Handler handler = new ConsoleHandler(); 67 | handler.setLevel(Level.ALL); 68 | logger.addHandler(handler); 69 | logger.setLevel(Level.ALL); 70 | logger.setUseParentHandlers(false); 71 | Logger.getLogger("").setLevel(Level.OFF); 72 | } 73 | File input = new File(line.getOptionValue("i")); 74 | File output = new File(line.getOptionValue("o")); 75 | if (!input.exists()) { 76 | throw new FileNotFoundException(input.getAbsolutePath()); 77 | } 78 | if (output.exists()) { 79 | logger.log(Level.INFO, "Output already exists, renaming existing file"); 80 | File existing = new File(line.getOptionValue("o")); 81 | File newName = new File(line.getOptionValue("o") + "-BAK"); 82 | if (newName.exists()) { 83 | newName.delete(); 84 | } 85 | existing.renameTo(newName); 86 | } 87 | String transf = line.getOptionValue("t"); 88 | if (!transformers.containsKey(transf)) { 89 | HelpFormatter formatter = new HelpFormatter(); 90 | formatter.printHelp("zelixkiller 11", options); 91 | return; 92 | } 93 | Class tClass = transformers.get(transf); 94 | Transformer t = tClass.newInstance(); 95 | if(tClass.getAnnotations().length > 0) { 96 | if(tClass.getAnnotations()[0].annotationType().getName().equals("java.lang.Deprecated")) { 97 | logger.log(Level.WARNING, "Transformer " + t.getClass().getSimpleName() + " is deprecated"); 98 | } 99 | } 100 | Map classes = JarUtils.loadClasses(input); 101 | Map out = JarUtils.loadNonClassEntries(input); 102 | JarArchive ja = new JarArchive(input, classes, out); 103 | logger.log(Level.INFO, "Starting with deobfuscation using transformer " + t.getClass().getSimpleName()); 104 | t.preTransform(ja); 105 | for (ClassNode cn : new ArrayList<>(classes.values())) { 106 | if (t.isAffected(cn)) { 107 | t.transform(ja, cn); 108 | } 109 | } 110 | t.postTransform(); 111 | for (ClassNode cn : classes.values()) { 112 | ClassWriter cw = new ClassWriter(0); 113 | cn.accept(cw); 114 | out.put(cn.name, cw.toByteArray()); 115 | } 116 | JarUtils.saveAsJar(out, output); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/Transformer.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer; 2 | 3 | import org.objectweb.asm.Opcodes; 4 | import org.objectweb.asm.tree.ClassNode; 5 | 6 | import me.nov.zelixkiller.JarArchive; 7 | 8 | public abstract class Transformer implements Opcodes { 9 | public abstract boolean isAffected(ClassNode cn); 10 | public abstract void transform(JarArchive ja, ClassNode cn); 11 | public abstract void preTransform(JarArchive ja); 12 | public abstract void postTransform(); 13 | } 14 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/zkm/ExceptionObfuscationTX.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer.zkm; 2 | 3 | import java.util.ArrayList; 4 | import java.util.logging.Level; 5 | 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.ClassNode; 8 | import org.objectweb.asm.tree.LabelNode; 9 | import org.objectweb.asm.tree.MethodInsnNode; 10 | import org.objectweb.asm.tree.MethodNode; 11 | import org.objectweb.asm.tree.TryCatchBlockNode; 12 | import org.objectweb.asm.tree.VarInsnNode; 13 | 14 | import me.nov.zelixkiller.JarArchive; 15 | import me.nov.zelixkiller.ZelixKiller; 16 | import me.nov.zelixkiller.transformer.Transformer; 17 | import me.nov.zelixkiller.utils.ClassUtils; 18 | 19 | public class ExceptionObfuscationTX extends Transformer { 20 | 21 | private int removed; 22 | 23 | @Override 24 | public boolean isAffected(ClassNode cn) { 25 | return cn.methods.stream().anyMatch(mn -> !mn.tryCatchBlocks.isEmpty()); 26 | } 27 | 28 | @Override 29 | public void transform(JarArchive ja, ClassNode cn) { 30 | cn.methods.forEach(mn -> new ArrayList<>(mn.tryCatchBlocks).forEach(tcb -> check(cn, mn, tcb, tcb.handler))); 31 | } 32 | 33 | private void check(ClassNode cn, MethodNode mn, TryCatchBlockNode tcb, LabelNode handler) { 34 | AbstractInsnNode ain = handler; 35 | while (ain.getOpcode() == -1) { // skip labels and frames 36 | ain = ain.getNext(); 37 | } 38 | if (ain.getOpcode() == ATHROW) { 39 | removeTCB(mn, tcb); 40 | } else if (ain instanceof MethodInsnNode && ain.getNext().getOpcode() == ATHROW) { 41 | MethodInsnNode min = (MethodInsnNode) ain; 42 | if (min.owner.equals(cn.name)) { 43 | MethodNode getter = ClassUtils.getMethod(cn, min.name, min.desc); 44 | AbstractInsnNode getterFirst = getter.instructions.getFirst(); 45 | while (getterFirst.getOpcode() == -1) { 46 | getterFirst = ain.getNext(); 47 | } 48 | if (getterFirst instanceof VarInsnNode && getterFirst.getNext().getOpcode() == ARETURN) { 49 | if (((VarInsnNode) getterFirst).var == 0) { 50 | removeTCB(mn, tcb); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | private void removeTCB(MethodNode mn, TryCatchBlockNode tcb) { 58 | removed++; 59 | mn.tryCatchBlocks.remove(tcb); 60 | } 61 | 62 | @Override 63 | public void preTransform(JarArchive ja) { 64 | } 65 | 66 | @Override 67 | public void postTransform() { 68 | ZelixKiller.logger.log(Level.INFO, "Removed " + removed + " TryCatchBlocks successfully"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/zkm11/ControlFlowT11.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer.zkm11; 2 | 3 | import java.util.logging.Level; 4 | 5 | import org.objectweb.asm.tree.AbstractInsnNode; 6 | import org.objectweb.asm.tree.ClassNode; 7 | import org.objectweb.asm.tree.InsnNode; 8 | import org.objectweb.asm.tree.JumpInsnNode; 9 | import org.objectweb.asm.tree.MethodInsnNode; 10 | import org.objectweb.asm.tree.VarInsnNode; 11 | import org.objectweb.asm.tree.analysis.Analyzer; 12 | import org.objectweb.asm.tree.analysis.Frame; 13 | 14 | import me.nov.zelixkiller.JarArchive; 15 | import me.nov.zelixkiller.ZelixKiller; 16 | import me.nov.zelixkiller.transformer.Transformer; 17 | import me.nov.zelixkiller.utils.analysis.ConstantTracker; 18 | import me.nov.zelixkiller.utils.analysis.ConstantTracker.ConstantValue; 19 | 20 | public class ControlFlowT11 extends Transformer { 21 | 22 | public int success = 0; 23 | public int failures = 0; 24 | private int jumps = 0; 25 | 26 | @Override 27 | public boolean isAffected(ClassNode cn) { 28 | return !cn.methods.isEmpty(); 29 | } 30 | 31 | @Override 32 | public void transform(JarArchive ja, ClassNode cn) { 33 | cn.methods.forEach(mn -> { 34 | if (mn.instructions.size() > 3) { 35 | AbstractInsnNode first = mn.instructions.getFirst(); 36 | if (first.getOpcode() == INVOKESTATIC && first.getNext().getOpcode() == ISTORE) { 37 | MethodInsnNode min = (MethodInsnNode) first; 38 | if (min.desc.equals("()I")) { 39 | AbstractInsnNode ain = getNumberPush(min, ((VarInsnNode) first.getNext()).var); 40 | if (ain != null) { 41 | mn.instructions.set(first, ain); 42 | } 43 | } 44 | } 45 | } 46 | }); 47 | cn.methods.forEach(mn -> { 48 | Analyzer a = new Analyzer<>(new ConstantTracker()); 49 | try { 50 | a.analyze(cn.name, mn); 51 | } catch (Exception e) { 52 | failures++; 53 | return; 54 | } 55 | Frame[] frames = a.getFrames(); 56 | int i = 0; 57 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 58 | Frame frame = frames[i++]; 59 | int op = ain.getOpcode(); 60 | if (op == IFEQ || op == IFNE) { 61 | ConstantValue v = frame.getStack(frame.getStackSize() - 1); 62 | if (v.getValue() != null) { 63 | boolean zero = (int) v.getValue() == 0; 64 | if (op == IFEQ) { 65 | if (!zero) { 66 | mn.instructions.set(ain, new InsnNode(POP)); 67 | } else { 68 | mn.instructions.insertBefore(ain, new InsnNode(POP)); 69 | mn.instructions.set(ain, new JumpInsnNode(GOTO, ((JumpInsnNode) ain).label)); 70 | } 71 | } else { 72 | if (zero) { 73 | mn.instructions.set(ain, new InsnNode(POP)); 74 | } else { 75 | mn.instructions.insertBefore(ain, new InsnNode(POP)); 76 | mn.instructions.set(ain, new JumpInsnNode(GOTO, ((JumpInsnNode) ain).label)); 77 | } 78 | } 79 | jumps++; 80 | } 81 | } 82 | } 83 | success++; 84 | }); 85 | } 86 | 87 | private AbstractInsnNode getNumberPush(MethodInsnNode min, int var) { 88 | // TODO find out by invoking if it can't be identified by patterns in future versions 89 | AbstractInsnNode ain = min; 90 | while (ain != null) { 91 | if (ain instanceof VarInsnNode) { 92 | if (((VarInsnNode) ain).var == var) { 93 | int nextOp = ain.getNext().getOpcode(); 94 | // jump should never happen 95 | if (nextOp == IFEQ) { 96 | return new InsnNode(ICONST_1); 97 | } else if (nextOp == IFNE) { 98 | return new InsnNode(ICONST_0); 99 | } 100 | } 101 | } 102 | ain = ain.getNext(); 103 | } 104 | return null; 105 | } 106 | 107 | @Override 108 | public void preTransform(JarArchive ja) { 109 | } 110 | 111 | @Override 112 | public void postTransform() { 113 | ZelixKiller.logger.log(Level.INFO, "Succeeded in " + success + " classes, failed in " + failures); 114 | ZelixKiller.logger.log(Level.INFO, "Removed " + jumps + " redundant jumps, please clean code afterwards"); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/zkm11/ReflectionObfuscationVMT11.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer.zkm11; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.lang.invoke.MethodHandleInfo; 5 | import java.lang.invoke.MethodHandles; 6 | import java.lang.invoke.MethodHandles.Lookup; 7 | import java.lang.invoke.MethodType; 8 | import java.lang.invoke.MutableCallSite; 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Method; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.Map.Entry; 15 | import java.util.logging.Level; 16 | 17 | import org.objectweb.asm.ClassWriter; 18 | import org.objectweb.asm.tree.AbstractInsnNode; 19 | import org.objectweb.asm.tree.ClassNode; 20 | import org.objectweb.asm.tree.FieldInsnNode; 21 | import org.objectweb.asm.tree.InsnNode; 22 | import org.objectweb.asm.tree.InvokeDynamicInsnNode; 23 | import org.objectweb.asm.tree.LdcInsnNode; 24 | import org.objectweb.asm.tree.MethodInsnNode; 25 | import org.objectweb.asm.tree.MethodNode; 26 | import org.objectweb.asm.tree.analysis.Analyzer; 27 | import org.objectweb.asm.tree.analysis.Frame; 28 | 29 | import me.lpk.analysis.Sandbox.ClassDefiner; 30 | import me.lpk.util.AccessHelper; 31 | import me.nov.zelixkiller.JarArchive; 32 | import me.nov.zelixkiller.ZelixKiller; 33 | import me.nov.zelixkiller.transformer.Transformer; 34 | import me.nov.zelixkiller.utils.ClassUtils; 35 | import me.nov.zelixkiller.utils.ReflectionUtils; 36 | import me.nov.zelixkiller.utils.analysis.ConstantTracker; 37 | import me.nov.zelixkiller.utils.analysis.ConstantTracker.ConstantValue; 38 | 39 | public class ReflectionObfuscationVMT11 extends Transformer { 40 | 41 | private ClassDefiner vm; 42 | private int reversed = 0; 43 | private boolean twoLongType; 44 | private int references; 45 | 46 | @Override 47 | public boolean isAffected(ClassNode cn) { 48 | return !cn.methods.isEmpty(); 49 | } 50 | 51 | @Override 52 | public void transform(JarArchive ja, ClassNode node) { 53 | if (twoLongType) { 54 | // init surroundings before decryption 55 | Outer: for (ClassNode cn : ja.getClasses().values()) { 56 | for (MethodNode mn : cn.methods) { 57 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 58 | if (ain.getOpcode() == INVOKESPECIAL) { 59 | MethodInsnNode min = (MethodInsnNode) ain; 60 | if (min.owner.equals(node.name) && min.name.equals("")) { 61 | try { 62 | Class.forName(cn.name.replace("/", "."), true, vm); 63 | } catch (ClassNotFoundException e) { 64 | } 65 | continue Outer; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | node.methods.forEach(mn -> removeDynamicCalls(node, mn)); 73 | } 74 | 75 | private void removeDynamicCalls(ClassNode cn, MethodNode mn) { 76 | if (twoLongType) { 77 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 78 | if (ain.getOpcode() == GETSTATIC) { 79 | FieldInsnNode fin = (FieldInsnNode) ain; 80 | if (fin.owner.equals(cn.name) && fin.desc.equals("J")) { 81 | // inline needed fields 82 | try { 83 | Field f = Class.forName(cn.name.replace('/', '.'), true, vm).getDeclaredField(fin.name); 84 | if (f != null && f.getType() == long.class) { 85 | f.setAccessible(true); 86 | long l = (long) f.get(null); 87 | if (l != 0) { 88 | mn.instructions.set(fin, new LdcInsnNode(l)); 89 | } 90 | } 91 | } catch (Exception e) { 92 | ZelixKiller.logger.log(Level.SEVERE, "Exception at inlining field", e); 93 | continue; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | try { 100 | int i = -1; 101 | Analyzer a = new Analyzer<>(new ConstantTracker()); 102 | a.analyze(cn.name, mn); 103 | Frame[] frames = a.getFrames(); 104 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 105 | i++; 106 | if (ain.getOpcode() == INVOKEDYNAMIC) { 107 | InvokeDynamicInsnNode idin = (InvokeDynamicInsnNode) ain; 108 | if (idin.bsm != null 109 | && idin.bsm.getDesc().equals( 110 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;") 111 | && idin.name.length() == 1) { 112 | references++; 113 | Class decryptionClass = Class.forName(idin.bsm.getOwner().replace('/', '.'), true, vm); 114 | for (Method m : decryptionClass.getDeclaredMethods()) { 115 | if (m.getName().equals(idin.bsm.getName())) { 116 | if (m.getReturnType() == MethodHandle.class) { 117 | Frame frame = frames[i]; 118 | MethodType mt = MethodType.fromMethodDescriptorString(idin.desc, vm); 119 | MutableCallSite cs = new MutableCallSite(mt); 120 | if (twoLongType) { 121 | long longValue = 0; 122 | long secondLongValue = 0; 123 | try { 124 | longValue = (long) frame.getStack(frame.getStackSize() - 2).getValue(); 125 | secondLongValue = (long) frame.getStack(frame.getStackSize() - 1).getValue(); 126 | } catch (NullPointerException e) { 127 | ZelixKiller.logger.log(Level.FINE, 128 | "Couldn't resolve both long values in class " + cn.name + ", skipping!"); 129 | return; 130 | } 131 | Lookup lookup = getLookup(Class.forName(cn.name.replace('/', '.'), true, vm)); 132 | ReflectionUtils.setFinal(lookup.getClass().getDeclaredField("allowedModes"), lookup, -1); // trust lookup 133 | // emulate invokedynamic to retrieve callsite 134 | MethodHandle mh = (MethodHandle) m.invoke(null, lookup, cs, idin.name, mt, longValue, 135 | secondLongValue); 136 | AbstractInsnNode original = getOriginalNode(mh, lookup); 137 | if (idin.getPrevious().getPrevious().getOpcode() == LDC 138 | && idin.getPrevious().getOpcode() == LLOAD) { 139 | mn.instructions.remove(idin.getPrevious().getPrevious()); 140 | mn.instructions.remove(idin.getPrevious()); 141 | } else { 142 | mn.instructions.insertBefore(idin, new InsnNode(POP2)); 143 | mn.instructions.insertBefore(idin, new InsnNode(POP2)); 144 | } 145 | mn.instructions.set(idin, original); 146 | reversed++; 147 | } else { 148 | long longValue = (long) frame.getStack(frame.getStackSize() - 1).getValue(); 149 | Lookup lookup = getLookup(Class.forName(cn.name.replace('/', '.'), true, vm)); 150 | ReflectionUtils.setFinal(lookup.getClass().getDeclaredField("allowedModes"), lookup, -1); // trust lookup 151 | // emulate invokedynamic to retrieve callsite 152 | MethodHandle mh = (MethodHandle) m.invoke(null, lookup, cs, idin.name, mt, longValue); 153 | AbstractInsnNode original = getOriginalNode(mh, lookup); 154 | if (idin.getPrevious().getOpcode() == LDC) { 155 | mn.instructions.remove(idin.getPrevious()); 156 | } else { 157 | mn.instructions.insertBefore(idin, new InsnNode(POP2)); 158 | } 159 | mn.instructions.set(idin, original); 160 | reversed++; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | } 167 | } 168 | } catch (Exception e) { 169 | e.printStackTrace(); 170 | } 171 | } 172 | 173 | /** 174 | * Converts BoundMethodHandle$Species_L to AbstractInsnNode reference 175 | * 176 | * @param cn 177 | */ 178 | private AbstractInsnNode getOriginalNode(MethodHandle mh, Lookup lookup) throws Exception { 179 | Field original = mh.getClass().getDeclaredField("argL0"); 180 | original.setAccessible(true); 181 | MethodHandle originalHandle = (MethodHandle) original.get(mh); 182 | 183 | MethodHandleInfo direct = lookup.revealDirect(originalHandle); 184 | 185 | int refKind = direct.getReferenceKind(); 186 | Class declaringClass = direct.getDeclaringClass(); 187 | String name = direct.getName(); 188 | MethodType methodType = direct.getMethodType(); 189 | int op = -1; 190 | if (refKind <= 4) { 191 | switch (refKind) { 192 | case 1: 193 | op = GETFIELD; 194 | break; 195 | case 2: 196 | op = GETSTATIC; 197 | break; 198 | case 3: 199 | op = PUTFIELD; 200 | break; 201 | case 4: 202 | op = PUTSTATIC; 203 | break; 204 | } 205 | String desc; 206 | if (refKind <= 2) { 207 | desc = methodType.toMethodDescriptorString().substring(2); 208 | } else { 209 | // method handle treats field setting as a method (returning void) 210 | String mds = methodType.toMethodDescriptorString(); 211 | desc = mds.substring(1, mds.lastIndexOf(')')); 212 | } 213 | return new FieldInsnNode(op, declaringClass.getName().replace('.', '/'), name, desc); 214 | } 215 | switch (refKind) { 216 | case 5: 217 | op = INVOKEVIRTUAL; 218 | break; 219 | case 6: 220 | op = INVOKESTATIC; 221 | break; 222 | case 7: 223 | case 8: 224 | op = INVOKESPECIAL; 225 | break; 226 | case 9: 227 | op = INVOKEINTERFACE; 228 | break; 229 | } 230 | return new MethodInsnNode(op, declaringClass.getName().replace('.', '/'), name, 231 | methodType.toMethodDescriptorString()); 232 | } 233 | 234 | /** 235 | * Creates lookup as if it was by invokedynamic 236 | */ 237 | private Lookup getLookup(Class clazz) throws Exception { 238 | Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); 239 | constructor.setAccessible(true); 240 | return constructor.newInstance(clazz); 241 | } 242 | 243 | @Override 244 | public void postTransform() { 245 | ZelixKiller.logger.log(Level.INFO, 246 | "Removed " + reversed + " invokedynamic references of " + references + ", please clean code afterwards"); 247 | } 248 | 249 | @Override 250 | public void preTransform(JarArchive ja) { 251 | ClassNode referenceHolder = null; 252 | Outer: for (ClassNode cn : ja.getClasses().values()) { 253 | for (MethodNode mn : cn.methods) { 254 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 255 | if (ain.getOpcode() == INVOKEDYNAMIC) { 256 | InvokeDynamicInsnNode idin = (InvokeDynamicInsnNode) ain; 257 | if (idin.bsm != null 258 | && idin.bsm.getDesc().equals( 259 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;") 260 | && idin.name.length() == 1) { 261 | referenceHolder = ja.getClasses().get(idin.bsm.getOwner()); 262 | ZelixKiller.logger.log(Level.FINE, "Found class with bootstrap method (" + referenceHolder.name + ")"); 263 | if (idin.desc.contains("JJ)")) { 264 | ZelixKiller.logger.log(Level.FINE, "Bootstrap uses two long values instead of one"); 265 | twoLongType = true; 266 | } 267 | break Outer; 268 | } 269 | } 270 | } 271 | } 272 | } 273 | if (referenceHolder == null) { 274 | throw new RuntimeException("Class not obfuscated with zkm 11"); 275 | } 276 | ArrayList associatedDecryptionClasses = StringObfuscationCipherVMT11 277 | .findDecryptionClasses(ja.getClasses()); 278 | ZelixKiller.logger.log(Level.FINE, 279 | "Found " + associatedDecryptionClasses.size() + " associated decryption classes"); 280 | // prepare jar copy for vm 281 | HashMap jarCopy = new HashMap<>(); 282 | for (ClassNode node : ja.getClasses().values()) { 283 | ClassNode cn = ClassUtils.clone(node); 284 | if (!AccessHelper.isPublic(cn.access)) { 285 | if (AccessHelper.isPrivate(cn.access)) { 286 | cn.access -= ACC_PRIVATE; 287 | } 288 | if (AccessHelper.isProtected(cn.access)) { 289 | cn.access -= ACC_PROTECTED; 290 | } 291 | cn.access += ACC_PUBLIC; 292 | } 293 | 294 | // remove clinit from non decryption classes 295 | if (!node.equals(referenceHolder) && !associatedDecryptionClasses.contains(node)) { 296 | for (MethodNode mn : cn.methods) { 297 | if (mn.name.equals("")) { 298 | mn.tryCatchBlocks.clear(); 299 | mn.localVariables.clear(); 300 | if (mn.instructions.size() > 8) { 301 | AbstractInsnNode third = mn.instructions.getFirst().getNext().getNext(); 302 | if (third instanceof MethodInsnNode) { 303 | MethodInsnNode min = (MethodInsnNode) third; 304 | if (min.owner.equals("java/lang/invoke/MethodHandles") && min.name.equals("lookup")) { 305 | AbstractInsnNode ain = third; 306 | while (ain != null && ain.getOpcode() != INVOKEINTERFACE) { 307 | ain = ain.getNext(); 308 | } 309 | if (ain.getNext().getOpcode() != PUTSTATIC) { 310 | // TODO fix long only used in clinit, causing "Couldn't resolve both long values" 311 | mn.instructions.insert(ain, new InsnNode(POP2)); 312 | } 313 | ain = ain.getNext(); 314 | while (ain.getNext() != null) { 315 | mn.instructions.remove(ain.getNext()); 316 | } 317 | mn.instructions.add(new InsnNode(RETURN)); 318 | } 319 | } 320 | } else { 321 | mn.instructions.clear(); 322 | mn.instructions.add(new InsnNode(RETURN)); 323 | } 324 | } 325 | } 326 | } 327 | ClassWriter cw = new ClassWriter(0); 328 | cn.accept(cw); 329 | jarCopy.put(cn, cw.toByteArray()); 330 | } 331 | vm = new ClassDefiner(ClassLoader.getSystemClassLoader()); 332 | for (Entry e : jarCopy.entrySet()) { 333 | vm.predefine(e.getKey().name.replace("/", "."), e.getValue()); 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/zkm11/StringObfuscationCipherT11.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer.zkm11; 2 | 3 | import java.io.File; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.Map.Entry; 9 | import java.util.logging.Level; 10 | 11 | import org.objectweb.asm.ClassWriter; 12 | import org.objectweb.asm.tree.AbstractInsnNode; 13 | import org.objectweb.asm.tree.ClassNode; 14 | import org.objectweb.asm.tree.FieldInsnNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | import org.objectweb.asm.tree.InsnList; 17 | import org.objectweb.asm.tree.InsnNode; 18 | import org.objectweb.asm.tree.InvokeDynamicInsnNode; 19 | import org.objectweb.asm.tree.LdcInsnNode; 20 | import org.objectweb.asm.tree.MethodInsnNode; 21 | import org.objectweb.asm.tree.MethodNode; 22 | import org.objectweb.asm.tree.analysis.Analyzer; 23 | import org.objectweb.asm.tree.analysis.AnalyzerException; 24 | import org.objectweb.asm.tree.analysis.Frame; 25 | 26 | import me.lpk.analysis.Sandbox.ClassDefiner; 27 | import me.nov.zelixkiller.JarArchive; 28 | import me.nov.zelixkiller.ZelixKiller; 29 | import me.nov.zelixkiller.transformer.Transformer; 30 | import me.nov.zelixkiller.transformer.zkm11.utils.ClinitCutter; 31 | import me.nov.zelixkiller.utils.ClassUtils; 32 | import me.nov.zelixkiller.utils.IssueUtils; 33 | import me.nov.zelixkiller.utils.MethodUtils; 34 | import me.nov.zelixkiller.utils.analysis.ConstantTracker; 35 | import me.nov.zelixkiller.utils.analysis.ConstantTracker.ConstantValue; 36 | 37 | /** 38 | * Decrypts ZKM String Obfuscation technique that uses DES 39 | * doesn't always work 40 | */ 41 | @Deprecated 42 | public class StringObfuscationCipherT11 extends Transformer { 43 | 44 | public int success = 0; 45 | public int failures = 0; 46 | 47 | @Override 48 | public boolean isAffected(ClassNode cn) { 49 | if (cn.methods.isEmpty()) { 50 | return false; 51 | } 52 | MethodNode staticInitializer = cn.methods.stream().filter(mn -> mn.name.equals("")).findFirst() 53 | .orElse(null); 54 | return staticInitializer != null && StringObfuscationT11.containsEncryptedLDC(staticInitializer) 55 | && containsDESPadLDC(staticInitializer); 56 | } 57 | 58 | public static boolean containsDESPadLDC(MethodNode clinit) { 59 | for (AbstractInsnNode ain : clinit.instructions.toArray()) { 60 | if (ain.getOpcode() == LDC) { 61 | String cst = String.valueOf(((LdcInsnNode) ain).cst); 62 | if (cst.equals("DES/CBC/PKCS5Padding")) { 63 | return true; 64 | } 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | @Override 71 | public void transform(JarArchive ja, ClassNode cn) { 72 | MethodNode clinit = cn.methods.stream().filter(mn -> mn.name.equals("")).findFirst().get(); 73 | MethodNode mathMethod = findMathMethod(cn); 74 | Class proxy = null; 75 | try { 76 | proxy = createProxy(ja, cn, clinit, mathMethod); 77 | } catch (Throwable t) { 78 | ZelixKiller.logger.log(Level.SEVERE, "Proxy exception at " + cn.name, t); 79 | failures++; 80 | // TODO fix Given final block not properly padded (= wrong key) (every class throwing that error has a super / itf class) 81 | return; 82 | } 83 | if (mathMethod != null) { 84 | replaceInvokedynamicCalls(proxy, cn, mathMethod); 85 | } else { 86 | // TODO replace fields and arrayloads (if exist) 87 | } 88 | // TODO remove 89 | success++; 90 | } 91 | 92 | private void replaceInvokedynamicCalls(Class proxy, ClassNode cn, MethodNode mathMethod) { 93 | for (MethodNode mn : cn.methods) { 94 | try { 95 | HashMap decryptedStringMap = new HashMap<>(); 96 | int nIdx = 0; 97 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 98 | if (ain.getOpcode() == GETSTATIC) { 99 | FieldInsnNode fin = (FieldInsnNode) ain; 100 | if (fin.owner.equals(cn.name) && fin.desc.equals("J")) { 101 | // inline needed fields 102 | try { 103 | Field f = proxy.getDeclaredField(fin.name); 104 | if (f != null && f.getType() == long.class) { 105 | mn.instructions.set(fin, new LdcInsnNode((long) f.get(null))); 106 | } 107 | } catch (Exception e) { 108 | ZelixKiller.logger.log(Level.SEVERE, "Exception at inlining field", e); 109 | continue; 110 | } 111 | } 112 | } else if (ain.getOpcode() == INVOKEDYNAMIC) { 113 | // invokedynamic just invokes (String, long, int) method 114 | InvokeDynamicInsnNode idyn = (InvokeDynamicInsnNode) ain; 115 | if (idyn.desc.equals("(IJ)Ljava/lang/String;") && idyn.bsm != null && idyn.bsm.getOwner().equals(cn.name) 116 | && idyn.bsm.getDesc().equals( 117 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;")) { 118 | Analyzer a = new Analyzer<>(new ConstantTracker()); 119 | a.analyze(cn.name, mn); 120 | Frame[] frames = a.getFrames(); 121 | Frame frame = frames[nIdx]; 122 | int j = 0; 123 | Object[] args = new Object[2]; 124 | for (int i = frame.getStackSize() - 1; i > frame.getStackSize() - 3; i--) { 125 | ConstantValue v = frame.getStack(i); 126 | if (v != null) 127 | args[j++] = v.getValue(); 128 | } 129 | for (Method m : proxy.getDeclaredMethods()) { 130 | if (m.getName().equals(mathMethod.name) && m.getReturnType() == String.class 131 | && m.getParameterTypes()[0] == int.class && m.getParameterTypes()[1] == long.class) { 132 | try { 133 | m.setAccessible(true); 134 | String decrypted = (String) m.invoke(null, args[1], args[0]); 135 | decryptedStringMap.put(idyn, decrypted); 136 | } catch (Exception e) { 137 | throw new RuntimeException("math method threw exception", e); 138 | } 139 | break; 140 | } 141 | } 142 | } 143 | } 144 | nIdx++; 145 | } 146 | for (Entry entry : decryptedStringMap.entrySet()) { 147 | mn.instructions.insertBefore(entry.getKey(), new InsnNode(POP2)); 148 | mn.instructions.insertBefore(entry.getKey(), new InsnNode(POP)); 149 | mn.instructions.set(entry.getKey(), new LdcInsnNode(entry.getValue())); 150 | } 151 | } catch (AnalyzerException e) { 152 | e.printStackTrace(); 153 | continue; 154 | } 155 | } 156 | } 157 | 158 | private MethodNode findMathMethod(ClassNode cn) { 159 | return cn.methods.stream() 160 | .filter(mn -> mn.desc.equals("(IJ)Ljava/lang/String;") && !mn.name.startsWith("<") && containsDESPadLDC(mn)) 161 | .findFirst().orElse(null); 162 | } 163 | 164 | private Class createProxy(JarArchive ja, ClassNode cn, MethodNode clinit, MethodNode mathMethod) { 165 | // cut off rest of static initializer 166 | InsnList decryption = ClinitCutter.cutClinit(clinit); 167 | MethodNode emulationNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "static_init", "()V", null, null); 168 | emulationNode.instructions.add(decryption); 169 | emulationNode.maxStack = 10; 170 | emulationNode.maxLocals = 20; 171 | 172 | ClassNode proxy = new ClassNode(); 173 | proxy.access = ACC_PUBLIC; 174 | proxy.version = 52; 175 | proxy.name = "proxy"; // does this need the actual class name? 176 | proxy.superName = "java/lang/Object"; 177 | ArrayList addedFields = new ArrayList<>(); 178 | ArrayList decryptionClasses = new ArrayList<>(); 179 | // add fields and fix owner 180 | for (AbstractInsnNode ain : emulationNode.instructions.toArray()) { 181 | if (ain instanceof FieldInsnNode) { 182 | FieldInsnNode fin = (FieldInsnNode) ain; 183 | String id = fin.name + fin.desc; 184 | if (fin.owner.equals(cn.name)) { 185 | fin.owner = proxy.name; 186 | if (!addedFields.contains(id)) { 187 | proxy.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, fin.name, fin.desc, null, null)); 188 | addedFields.add(id); 189 | } 190 | } 191 | } 192 | if (ain instanceof MethodInsnNode) { 193 | MethodInsnNode min = (MethodInsnNode) ain; 194 | if (min.owner.equals(cn.name)) { 195 | min.owner = proxy.name; 196 | // we do not need to check this method 197 | if (ClassUtils.getMethod(proxy, min.name, min.desc) == null && !min.name.startsWith("<")) { 198 | proxy.methods.add(MethodUtils.cloneInstructions(ClassUtils.getMethod(cn, min.name, min.desc), null)); 199 | } 200 | } 201 | } 202 | } 203 | findBelongingClasses(new ArrayList<>(), decryptionClasses, ja, cn, proxy, emulationNode); 204 | proxy.methods.add(emulationNode); 205 | if (mathMethod != null) { 206 | MethodNode mathMethodClone = MethodUtils.cloneInstructions(mathMethod, null); 207 | for (AbstractInsnNode ain : mathMethodClone.instructions.toArray()) { 208 | if (ain instanceof FieldInsnNode) { 209 | FieldInsnNode fin = (FieldInsnNode) ain; 210 | String id = fin.name + fin.desc; 211 | if (fin.owner.equals(cn.name)) { 212 | fin.owner = proxy.name; 213 | if (!addedFields.contains(id)) { 214 | proxy.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, fin.name, fin.desc, null, null)); 215 | addedFields.add(id); 216 | } 217 | } 218 | } 219 | if (ain instanceof MethodInsnNode) { 220 | MethodInsnNode min = (MethodInsnNode) ain; 221 | if (min.owner.equals(cn.name)) { 222 | min.owner = proxy.name; 223 | // we do not need to check this method 224 | if (ClassUtils.getMethod(proxy, min.name, min.desc) == null && !min.name.startsWith("<")) { 225 | proxy.methods.add(MethodUtils.cloneInstructions(ClassUtils.getMethod(cn, min.name, min.desc), null)); 226 | } 227 | } 228 | } 229 | } 230 | proxy.methods.add(mathMethodClone); 231 | } 232 | // regenerate frames if original file throws verify errors 233 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES) { 234 | protected String getCommonSuperClass(final String type1, final String type2) { 235 | try { 236 | return super.getCommonSuperClass(type1, type2); 237 | } catch (Throwable t) { 238 | return "java/lang/Object"; 239 | } 240 | } 241 | }; 242 | proxy.accept(cw); 243 | ClassDefiner vm = new ClassDefiner(ClassLoader.getSystemClassLoader()); 244 | Class clazz = vm.get(proxy.name.replace("/", "."), cw.toByteArray()); 245 | 246 | for (ClassNode decryptionClazz : decryptionClasses) { 247 | ClassWriter cw2 = new ClassWriter(0); 248 | decryptionClazz.accept(cw2); 249 | vm.predefine(decryptionClazz.name.replace("/", "."), cw2.toByteArray()); 250 | } 251 | try { 252 | clazz.getDeclaredMethod("static_init").invoke(null, (Object[]) null); 253 | } catch (Throwable t) { 254 | IssueUtils.dump(new File("fault-proxy-dump" + ((t instanceof VerifyError) ? "-verify" : "") 255 | + (System.currentTimeMillis() % 100) + ".jar"), proxy); 256 | throw new RuntimeException("clinit DES decryption unsuccessful (invocation) at class " + clinit.owner, t); 257 | } 258 | 259 | for (Field f : clazz.getDeclaredFields()) { 260 | try { 261 | f.setAccessible(true); 262 | if (f.get(null) == null) { 263 | IssueUtils.dump(new File("fault-proxy-dump" + (System.currentTimeMillis() % 100) + ".jar"), proxy); 264 | throw new RuntimeException("clinit decryption unsuccessful"); 265 | } 266 | } catch (Exception e) { 267 | throw new RuntimeException("field error", e); 268 | } 269 | } 270 | return clazz; 271 | } 272 | 273 | private void findBelongingClasses(ArrayList scanned, ArrayList decryptionClasses, 274 | JarArchive ja, ClassNode cn, ClassNode proxy, MethodNode node) { 275 | if (scanned.contains(node)) { 276 | return; 277 | } 278 | scanned.add(node); 279 | for (AbstractInsnNode ain : node.instructions.toArray()) { 280 | if (ain instanceof MethodInsnNode) { 281 | MethodInsnNode min = (MethodInsnNode) ain; 282 | if (!min.owner.startsWith("java/") && !min.owner.startsWith("javax/")) { 283 | ClassNode decryptionClass = ja.getClasses().get(min.owner); 284 | if (decryptionClass != null && !decryptionClasses.contains(decryptionClass)) { 285 | decryptionClasses.add(decryptionClass); 286 | for (MethodNode mn : decryptionClass.methods) { 287 | findBelongingClasses(scanned, decryptionClasses, ja, decryptionClass, proxy, mn); 288 | } 289 | } 290 | } 291 | } 292 | } 293 | } 294 | 295 | @Override 296 | public void preTransform(JarArchive ja) { 297 | ZelixKiller.logger.log(Level.WARNING, "You're using a transformer that only supports invokedynamic decryption and doesn't support nested classes"); 298 | } 299 | 300 | @Override 301 | public void postTransform() { 302 | ZelixKiller.logger.log(Level.INFO, "Succeeded in " + success + " classes, failed in " + failures); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/zkm11/StringObfuscationT11.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer.zkm11; 2 | 3 | import java.io.File; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.logging.Level; 8 | 9 | import org.objectweb.asm.ClassWriter; 10 | import org.objectweb.asm.Type; 11 | import org.objectweb.asm.tree.AbstractInsnNode; 12 | import org.objectweb.asm.tree.ClassNode; 13 | import org.objectweb.asm.tree.FieldInsnNode; 14 | import org.objectweb.asm.tree.FieldNode; 15 | import org.objectweb.asm.tree.InsnList; 16 | import org.objectweb.asm.tree.InsnNode; 17 | import org.objectweb.asm.tree.LdcInsnNode; 18 | import org.objectweb.asm.tree.MethodInsnNode; 19 | import org.objectweb.asm.tree.MethodNode; 20 | import org.objectweb.asm.tree.analysis.Analyzer; 21 | import org.objectweb.asm.tree.analysis.AnalyzerException; 22 | import org.objectweb.asm.tree.analysis.Frame; 23 | 24 | import me.lpk.analysis.Sandbox; 25 | import me.lpk.util.ASMUtils; 26 | import me.nov.zelixkiller.JarArchive; 27 | import me.nov.zelixkiller.ZelixKiller; 28 | import me.nov.zelixkiller.transformer.Transformer; 29 | import me.nov.zelixkiller.transformer.zkm11.utils.ClinitCutter; 30 | import me.nov.zelixkiller.utils.InsnUtils; 31 | import me.nov.zelixkiller.utils.IssueUtils; 32 | import me.nov.zelixkiller.utils.MethodUtils; 33 | import me.nov.zelixkiller.utils.analysis.ConstantTracker; 34 | import me.nov.zelixkiller.utils.analysis.ConstantTracker.ConstantValue; 35 | 36 | /** 37 | * Old ZKM String Obfuscation technique that is still used in some cases 38 | */ 39 | public class StringObfuscationT11 extends Transformer { 40 | 41 | private boolean invokedynamicWarn; 42 | 43 | @Override 44 | public boolean isAffected(ClassNode cn) { 45 | if (cn.methods.isEmpty()) { 46 | return false; 47 | } 48 | MethodNode staticInitializer = cn.methods.stream().filter(mn -> mn.name.equals("")).findFirst() 49 | .orElse(null); 50 | return staticInitializer != null && containsEncryptedLDC(staticInitializer); 51 | } 52 | 53 | /** 54 | * Ensure it has zkm parts in it 55 | */ 56 | public static boolean containsEncryptedLDC(MethodNode clinit) { 57 | for (AbstractInsnNode ain : clinit.instructions.toArray()) { 58 | if (ain.getOpcode() == LDC) { 59 | String cst = String.valueOf(((LdcInsnNode) ain).cst); 60 | if (cst.length() < 5) 61 | continue; 62 | // calculate standard deviation 63 | double sum = 0; 64 | char[] ccst = cst.toCharArray(); 65 | for (char c : ccst) 66 | sum += c; 67 | double mean = sum / (double) cst.length(); 68 | double sdev = 0.0; 69 | for (int i = 1; i < ccst.length; i++) 70 | sdev += (ccst[i] - mean) * (ccst[i] - mean); 71 | sdev = Math.sqrt(sdev / (ccst.length - 1.0)); 72 | if (sdev > 30) { 73 | return true; 74 | } 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | /** 81 | * Check for second decryption method (two / three params (int, int, int)) 82 | */ 83 | private boolean hasMathMethod(ClassNode cn) { 84 | for (MethodNode mn : cn.methods) { 85 | if (InsnUtils.matches(mn.instructions, new int[] { ILOAD, ILOAD, IXOR, SIPUSH, IXOR, LDC })) { 86 | return true; 87 | } 88 | } 89 | return false; 90 | } 91 | 92 | @Override 93 | public void transform(JarArchive ja, ClassNode cn) { 94 | MethodNode clinit = cn.methods.stream().filter(mn -> mn.name.equals("")).findFirst().get(); 95 | if (InsnUtils.containsOpcode(clinit.instructions, INVOKEDYNAMIC)) { 96 | ZelixKiller.logger.log(Level.WARNING, 97 | "ZKM Static Initializer contains invokedynamic calls, decrypt dynamic calls first (Class: " + cn.name + ")"); 98 | return; 99 | } 100 | boolean mathMethod = hasMathMethod(cn); // if false, strings are only used in static initializer itself or as single field 101 | if (mathMethod) { 102 | MethodNode mm = null; 103 | // fix second method 104 | for (MethodNode mn : cn.methods) { 105 | if (InsnUtils.matches(mn.instructions, new int[] { ILOAD, ILOAD, IXOR, SIPUSH, IXOR, LDC })) { 106 | if (InsnUtils.containsOpcode(mn.instructions, INVOKEDYNAMIC)) { 107 | if (!invokedynamicWarn) { 108 | ZelixKiller.logger.log(Level.WARNING, 109 | "ZKM Math Method contains invokedynamic calls, decrypt dynamic calls first (Class: " + cn.name + ")"); 110 | invokedynamicWarn = true; 111 | } 112 | continue; 113 | } 114 | mm = mn; 115 | break; 116 | } 117 | } 118 | Class proxy = createProxy(mm, clinit); 119 | fixMathMethod(mm, clinit, cn, proxy); 120 | } else { 121 | // TODO handle arrayloads (aggressive type) 122 | Class proxy = createProxy(null, clinit); 123 | for (Field f : proxy.getDeclaredFields()) { 124 | try { 125 | f.setAccessible(true); 126 | String s = (String) f.get(null); 127 | for (MethodNode mn : cn.methods) { 128 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 129 | if (ain.getOpcode() == GETSTATIC) { 130 | FieldInsnNode fin = (FieldInsnNode) ain; 131 | if (fin.owner.equals(clinit.owner) && fin.name.equals(f.getName()) 132 | && fin.desc.equals(Type.getDescriptor(f.getType()))) { 133 | mn.instructions.set(fin, new LdcInsnNode(s)); 134 | } 135 | } 136 | } 137 | 138 | } 139 | for (FieldNode fn : new ArrayList<>(cn.fields)) { 140 | if (fn.name.equals(f.getName()) && fn.desc.equals(Type.getDescriptor(f.getType()))) { 141 | cn.fields.remove(fn); 142 | } 143 | } 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | } 148 | } 149 | // to finish everything, clean up clinit 150 | InsnList originalClinit = MethodUtils.copy(clinit.instructions, ClinitCutter.findEndLabel(clinit.instructions), 151 | null); 152 | clinit.instructions.clear(); 153 | clinit.instructions.add(originalClinit); 154 | if (originalClinit.size() <= 3) { 155 | cn.methods.remove(clinit); 156 | } 157 | } 158 | 159 | @Override 160 | public void preTransform(JarArchive ja) { 161 | } 162 | 163 | @Override 164 | public void postTransform() { 165 | ZelixKiller.logger.log(Level.INFO, "ZKM String decryption finished, please clean code afterwards"); 166 | } 167 | 168 | /** 169 | * Creates a proxy of a class with only decryption methods in it 170 | */ 171 | private Class createProxy(MethodNode mathMethod, MethodNode clinit) { 172 | // cut off rest of static initializer 173 | InsnList decryption = ClinitCutter.cutClinit(clinit); 174 | MethodNode emulationNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "static_init", "()V", null, null); 175 | emulationNode.instructions.add(decryption); 176 | emulationNode.maxStack = 10; 177 | emulationNode.maxLocals = 20; 178 | 179 | ClassNode proxy = new ClassNode(); 180 | proxy.access = ACC_PUBLIC; 181 | proxy.version = 52; 182 | proxy.name = "proxy"; 183 | proxy.superName = "java/lang/Object"; 184 | ArrayList addedFields = new ArrayList<>(); 185 | // add fields and fix owner 186 | for (AbstractInsnNode ain : emulationNode.instructions.toArray()) { 187 | if (ain instanceof FieldInsnNode) { 188 | FieldInsnNode fin = (FieldInsnNode) ain; 189 | String id = fin.name + fin.desc; 190 | if (fin.owner.equals(clinit.owner) && !addedFields.contains(id)) { 191 | proxy.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, fin.name, fin.desc, null, null)); 192 | fin.owner = proxy.name; 193 | addedFields.add(id); 194 | } 195 | } 196 | } 197 | 198 | if (mathMethod != null) { 199 | InsnList mmdecr = MethodUtils.copy(mathMethod.instructions, null, null); 200 | MethodNode mathMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "math_node", mathMethod.desc, null, null); 201 | mathMethodNode.instructions.add(mmdecr); 202 | mathMethodNode.maxStack = mathMethod.maxStack; 203 | mathMethodNode.maxLocals = mathMethod.maxLocals; 204 | 205 | // fix field owner 206 | for (AbstractInsnNode ain : mathMethodNode.instructions.toArray()) { 207 | if (ain instanceof FieldInsnNode) { 208 | FieldInsnNode fin = (FieldInsnNode) ain; 209 | if (fin.owner.equals(clinit.owner)) { 210 | fin.owner = proxy.name; 211 | } 212 | } 213 | } 214 | proxy.methods.add(mathMethodNode); 215 | } 216 | proxy.methods.add(emulationNode); 217 | // regenerate frames if original file throws verify errors 218 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 219 | proxy.accept(cw); 220 | Class clazz = Sandbox.load(ASMUtils.getNode(cw.toByteArray())); 221 | try { 222 | clazz.getDeclaredMethod("static_init").invoke(null, (Object[]) null); 223 | } catch (Exception e) { 224 | e.printStackTrace(); 225 | IssueUtils.dump(new File("fault-proxy-dump" + (System.currentTimeMillis() % 100) + ".jar"), proxy); 226 | throw new RuntimeException("clinit decryption unsuccessful (invocation) at class " + clinit.owner); 227 | } 228 | 229 | for (Field f : clazz.getDeclaredFields()) { 230 | try { 231 | f.setAccessible(true); 232 | if (f.get(null) == null) { 233 | IssueUtils.dump(new File("fault-proxy-dump" + (System.currentTimeMillis() % 100) + ".jar"), proxy); 234 | throw new RuntimeException("clinit decryption unsuccessful"); 235 | } 236 | } catch (Exception e) { 237 | throw new RuntimeException("field error"); 238 | } 239 | } 240 | return clazz; 241 | } 242 | 243 | /** 244 | * Fixes all strings in code 245 | */ 246 | private void fixMathMethod(MethodNode mathMethod, MethodNode clinit, ClassNode cn, Class proxy) { 247 | Analyzer a = new Analyzer<>(new ConstantTracker()); 248 | for (MethodNode mn : cn.methods) { 249 | try { 250 | a.analyze(cn.name, mn); 251 | Frame[] frames = a.getFrames(); 252 | int nIdx = 0; 253 | for (AbstractInsnNode ain : mn.instructions.toArray()) { 254 | if (ain.getOpcode() == INVOKESTATIC) { 255 | MethodInsnNode min = (MethodInsnNode) ain; 256 | if (min.owner.equals(cn.name) && min.name.equals(mathMethod.name) && min.desc.equals(mathMethod.desc)) { 257 | Frame frame = frames[nIdx]; 258 | int j = 0; 259 | int[] args2 = new int[3]; 260 | for (int i = frame.getStackSize() - 1; i > frame.getStackSize() - 4; i--) { 261 | ConstantValue v = frame.getStack(i); 262 | args2[j++] = (int) v.getValue(); 263 | } 264 | try { 265 | Method mathMethodProxy = proxy.getDeclaredMethod("math_node", int.class, int.class, int.class); 266 | String decoded = (String) mathMethodProxy.invoke(null, args2[2], args2[1], args2[0]); 267 | for (int i = 0; i < 3; i++) { 268 | mn.instructions.insertBefore(min, new InsnNode(POP)); 269 | } 270 | mn.instructions.set(min, new LdcInsnNode(decoded)); 271 | } catch (Exception e) { 272 | e.printStackTrace(); 273 | } 274 | 275 | } 276 | } 277 | nIdx++; 278 | } 279 | } catch (AnalyzerException e) { 280 | e.printStackTrace(); 281 | } 282 | } 283 | // to finish everything, clean up class 284 | ArrayList decryptionFields = new ArrayList<>(); 285 | for (Field f : proxy.getDeclaredFields()) { 286 | decryptionFields.add(f.getName() + "." + Type.getDescriptor(f.getType())); 287 | } 288 | for (FieldNode fn : new ArrayList<>(cn.fields)) { 289 | if (decryptionFields.contains(fn.name + "." + fn.desc)) { 290 | cn.fields.remove(fn); 291 | } 292 | } 293 | cn.methods.remove(mathMethod); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/transformer/zkm11/utils/ClinitCutter.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.transformer.zkm11.utils; 2 | 3 | import java.util.ArrayList; 4 | 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.FieldInsnNode; 8 | import org.objectweb.asm.tree.InsnList; 9 | import org.objectweb.asm.tree.InsnNode; 10 | import org.objectweb.asm.tree.JumpInsnNode; 11 | import org.objectweb.asm.tree.LabelNode; 12 | import org.objectweb.asm.tree.LookupSwitchInsnNode; 13 | import org.objectweb.asm.tree.MethodNode; 14 | import org.objectweb.asm.tree.TableSwitchInsnNode; 15 | import org.objectweb.asm.tree.TryCatchBlockNode; 16 | 17 | import me.nov.zelixkiller.utils.MethodUtils; 18 | 19 | public class ClinitCutter implements Opcodes { 20 | public static InsnList cutClinit(MethodNode mn) { 21 | InsnList insns = MethodUtils.copy(mn.instructions, null, null); 22 | ArrayList handlers = new ArrayList<>(); 23 | for (TryCatchBlockNode tcbn : mn.tryCatchBlocks) { 24 | handlers.add((LabelNode) insns.get(mn.instructions.indexOf(tcbn.handler))); 25 | } 26 | for(LabelNode ln : handlers) { 27 | findSubroutinesAndDelete(insns, ln); 28 | } 29 | AbstractInsnNode endLabel = findEndLabel(insns); 30 | while (endLabel.getOpcode() == -1) { 31 | endLabel = endLabel.getNext(); 32 | } 33 | AbstractInsnNode end = endLabel.getPrevious(); 34 | if (endLabel.getOpcode() != RETURN) { 35 | findSubroutinesAndDelete(insns, endLabel); 36 | insns.insert(end, new InsnNode(RETURN)); 37 | } 38 | return insns; 39 | } 40 | 41 | private static void findSubroutinesAndDelete(InsnList insns, AbstractInsnNode ain) { 42 | if (!insns.contains(ain)) { 43 | return; 44 | } 45 | while (ain != null && !(ain instanceof JumpInsnNode) && !(ain instanceof TableSwitchInsnNode) 46 | && !(ain instanceof LookupSwitchInsnNode)) { 47 | AbstractInsnNode next = ain.getNext(); 48 | insns.remove(ain); 49 | ain = next; 50 | } 51 | if (ain == null) { 52 | return; 53 | } 54 | if (ain instanceof JumpInsnNode) { 55 | AbstractInsnNode jumpTo = ((JumpInsnNode) ain).label; 56 | if (ain.getOpcode() != GOTO) { 57 | // ifs can also be false 58 | findSubroutinesAndDelete(insns, ain.getNext()); 59 | } 60 | insns.remove(ain); 61 | findSubroutinesAndDelete(insns, jumpTo); 62 | } else if (ain instanceof TableSwitchInsnNode) { 63 | // untested! 64 | TableSwitchInsnNode tsin = (TableSwitchInsnNode) ain; 65 | insns.remove(ain); 66 | for (LabelNode ln : tsin.labels) { 67 | findSubroutinesAndDelete(insns, ln); 68 | } 69 | findSubroutinesAndDelete(insns, tsin.dflt); 70 | } else if (ain instanceof LookupSwitchInsnNode) { 71 | // untested! 72 | LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) ain; 73 | insns.remove(ain); 74 | for (LabelNode ln : lsin.labels) { 75 | findSubroutinesAndDelete(insns, ln); 76 | } 77 | findSubroutinesAndDelete(insns, lsin.dflt); 78 | } 79 | } 80 | 81 | public static AbstractInsnNode findEndLabel(InsnList insns) { 82 | AbstractInsnNode ain = insns.getFirst(); 83 | while (ain != null) { 84 | if (ain.getOpcode() == GOTO && ain.getPrevious() != null 85 | && (blockContainsSetter(ain.getPrevious()) || ain.getPrevious().getOpcode() == ASTORE)) { 86 | return ((JumpInsnNode) ain).label; 87 | } 88 | ain = ain.getNext(); 89 | } 90 | ain = insns.getLast(); 91 | while (ain != null) { 92 | if (ain.getOpcode() == IF_ICMPGE && ain.getPrevious() != null && (ain.getPrevious().getOpcode() == ILOAD)) { 93 | return ((JumpInsnNode) ain).label; 94 | } 95 | ain = ain.getPrevious(); 96 | } 97 | throw new RuntimeException(); 98 | } 99 | 100 | private static boolean blockContainsSetter(AbstractInsnNode ain) { 101 | if (ain.getOpcode() == PUTSTATIC && ((FieldInsnNode) ain).desc.endsWith("Ljava/lang/String;")) { 102 | return true; 103 | } 104 | AbstractInsnNode ain2 = ain; 105 | while (ain2 != null && !(ain2 instanceof LabelNode)) { 106 | if (ain2.getOpcode() == PUTSTATIC && ain2.getPrevious().getOpcode() == ANEWARRAY) { 107 | FieldInsnNode fin = (FieldInsnNode) ain2; 108 | if (fin.desc.endsWith("[Ljava/lang/String;")) 109 | return true; 110 | } 111 | ain2 = ain2.getPrevious(); 112 | } 113 | return false; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/utils/ClassUtils.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.utils; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.tree.ClassNode; 5 | import org.objectweb.asm.tree.MethodNode; 6 | 7 | import me.lpk.util.ASMUtils; 8 | 9 | public class ClassUtils { 10 | public static MethodNode getMethod(ClassNode cn, String name) { 11 | for (MethodNode mn : cn.methods) { 12 | if (mn.name.equals(name)) { 13 | return mn; 14 | } 15 | } 16 | return null; 17 | } 18 | 19 | public static MethodNode getMethod(ClassNode cn, String name, String desc) { 20 | for (MethodNode mn : cn.methods) { 21 | if (mn.name.equals(name) && mn.desc.equals(desc)) { 22 | return mn; 23 | } 24 | } 25 | return null; 26 | } 27 | 28 | public static ClassNode clone(ClassNode cn) { 29 | ClassWriter cw = new ClassWriter(0); 30 | cn.accept(cw); 31 | return ASMUtils.getNode(cw.toByteArray()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/utils/InsnUtils.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.utils; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.FieldInsnNode; 8 | import org.objectweb.asm.tree.InsnList; 9 | import org.objectweb.asm.tree.InsnNode; 10 | import org.objectweb.asm.tree.IntInsnNode; 11 | import org.objectweb.asm.tree.LdcInsnNode; 12 | import org.objectweb.asm.tree.MethodInsnNode; 13 | 14 | public class InsnUtils implements Opcodes { 15 | public static boolean matches(InsnList il, int[] pattern) { 16 | AbstractInsnNode ain = il.getFirst(); 17 | for (int i = 0; i < pattern.length; i++) { 18 | if (ain == null) { 19 | return false; 20 | } 21 | if (ain.getOpcode() != pattern[i]) { 22 | return false; 23 | } 24 | ain = ain.getNext(); 25 | } 26 | return true; 27 | } 28 | 29 | public static boolean callsMethod(InsnList il) { 30 | AbstractInsnNode ain = il.getFirst(); 31 | while (ain != null) { 32 | if (ain instanceof MethodInsnNode) { 33 | return true; 34 | } 35 | ain = ain.getNext(); 36 | } 37 | return false; 38 | } 39 | 40 | public static boolean callsField(InsnList il) { 41 | AbstractInsnNode ain = il.getFirst(); 42 | while (ain != null) { 43 | if (ain instanceof MethodInsnNode) { 44 | return true; 45 | } 46 | ain = ain.getNext(); 47 | } 48 | return false; 49 | } 50 | 51 | public static boolean callsRef(InsnList il) { 52 | AbstractInsnNode ain = il.getFirst(); 53 | while (ain != null) { 54 | if (ain instanceof MethodInsnNode || ain instanceof FieldInsnNode) { 55 | return true; 56 | } 57 | ain = ain.getNext(); 58 | } 59 | return false; 60 | } 61 | 62 | public static boolean containsOpcode(InsnList il, int... opcodes) { 63 | AbstractInsnNode ain = il.getFirst(); 64 | while (ain != null) { 65 | final int op = ain.getOpcode(); 66 | if (Arrays.stream(opcodes).anyMatch(i -> i == op)) { 67 | return true; 68 | } 69 | ain = ain.getNext(); 70 | } 71 | return false; 72 | } 73 | 74 | public static AbstractInsnNode findFirst(InsnList il, int op) { 75 | AbstractInsnNode ain = il.getFirst(); 76 | while (ain != null) { 77 | if (ain.getOpcode() == op) { 78 | return ain; 79 | } 80 | ain = ain.getNext(); 81 | } 82 | return null; 83 | } 84 | 85 | public static AbstractInsnNode findLast(InsnList il, int op) { 86 | AbstractInsnNode ain = il.getLast(); 87 | while (ain != null) { 88 | if (ain.getOpcode() == op) { 89 | return ain; 90 | } 91 | ain = ain.getPrevious(); 92 | } 93 | return null; 94 | } 95 | 96 | public static boolean endsRoutine(AbstractInsnNode ain) { 97 | int op = ain.getOpcode(); 98 | return (op >= IFEQ && op <= RETURN) || op == ATHROW || op >= IFNULL && op <= IFNONNULL; 99 | } 100 | 101 | public static boolean isNumber(AbstractInsnNode ain) { 102 | if (ain.getOpcode() == BIPUSH || ain.getOpcode() == SIPUSH) { 103 | return true; 104 | } 105 | if (ain.getOpcode() >= ICONST_M1 && ain.getOpcode() <= ICONST_5) { 106 | return true; 107 | } 108 | if (ain instanceof LdcInsnNode) { 109 | LdcInsnNode ldc = (LdcInsnNode) ain; 110 | if (ldc.cst instanceof Number) { 111 | return true; 112 | } 113 | } 114 | return false; 115 | } 116 | 117 | public static AbstractInsnNode generateIntPush(int i) { 118 | if (i <= 5 && i >= -1) { 119 | return new InsnNode(i + 3); //iconst_i 120 | } 121 | if (i >= -128 && i <= 127) { 122 | return new IntInsnNode(BIPUSH, i); 123 | } 124 | 125 | if (i >= -32768 && i <= 32767) { 126 | return new IntInsnNode(SIPUSH, i); 127 | } 128 | return new LdcInsnNode(i); 129 | } 130 | 131 | public static int getIntValue(AbstractInsnNode node) { 132 | if (node.getOpcode() >= ICONST_M1 && node.getOpcode() <= ICONST_5) { 133 | return node.getOpcode() - 3; 134 | } 135 | if (node.getOpcode() == SIPUSH || node.getOpcode() == BIPUSH) { 136 | return ((IntInsnNode) node).operand; 137 | } 138 | if(node instanceof LdcInsnNode) { 139 | LdcInsnNode ldc = (LdcInsnNode) node; 140 | return Integer.parseInt(ldc.cst.toString()); 141 | } 142 | return 0; 143 | } 144 | 145 | public static String getStringValue(AbstractInsnNode node) { 146 | if (node.getType() == AbstractInsnNode.LDC_INSN) { 147 | LdcInsnNode ldc = (LdcInsnNode) node; 148 | return ldc.cst.toString(); 149 | } 150 | return ""; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/utils/IssueUtils.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.utils; 2 | 3 | import java.io.File; 4 | import java.util.HashMap; 5 | 6 | import org.objectweb.asm.ClassWriter; 7 | import org.objectweb.asm.tree.ClassNode; 8 | 9 | import me.lpk.util.JarUtils; 10 | 11 | public class IssueUtils { 12 | public static void dump(File output, ClassNode... cns) { 13 | HashMap file = new HashMap<>(); 14 | for (ClassNode cn : cns) { 15 | ClassWriter cw = new ClassWriter(0); 16 | cn.accept(cw); 17 | file.put(cn.name, cw.toByteArray()); 18 | } 19 | JarUtils.saveAsJar(file, output); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/utils/MethodUtils.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.InsnList; 8 | import org.objectweb.asm.tree.LabelNode; 9 | import org.objectweb.asm.tree.MethodNode; 10 | 11 | public class MethodUtils { 12 | public static Map getLabelMap(InsnList list) { 13 | Map map = new HashMap<>(); 14 | for (AbstractInsnNode ain : list.toArray()) { 15 | if (ain instanceof LabelNode) { 16 | map.put((LabelNode) ain, new LabelNode()); 17 | } 18 | } 19 | return map; 20 | } 21 | 22 | /** 23 | * copy InsnList start included, end excluded 24 | * 25 | * @param list 26 | * @param start 27 | * @param end 28 | * @return 29 | */ 30 | public static InsnList copy(InsnList list, AbstractInsnNode start, AbstractInsnNode end) { 31 | InsnList newList = new InsnList(); 32 | Map labelMap = getLabelMap(list); 33 | AbstractInsnNode ain = start == null ? list.getFirst() : start; 34 | while (ain != null && ain != end) { 35 | newList.add(ain.clone(labelMap)); 36 | ain = ain.getNext(); 37 | } 38 | return newList; 39 | } 40 | 41 | public static MethodNode cloneInstructions(MethodNode method, String newName) { 42 | MethodNode mn = new MethodNode(method.access, newName != null ? newName : method.name, method.desc, 43 | method.signature, method.exceptions.toArray(new String[0])); 44 | mn.instructions.add(copy(method.instructions, null, null)); 45 | mn.maxLocals = method.maxLocals; 46 | mn.maxStack = method.maxStack; 47 | return mn; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/utils/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.utils; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | 6 | public class ReflectionUtils { 7 | public static void setFinalStatic(Field field, Object newValue) throws Exception { 8 | field.setAccessible(true); 9 | 10 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 11 | modifiersField.setAccessible(true); 12 | modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 13 | 14 | field.set(null, newValue); 15 | } 16 | 17 | public static void setFinal(Field field, Object o, Object newValue) throws Exception { 18 | field.setAccessible(true); 19 | 20 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 21 | modifiersField.setAccessible(true); 22 | modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 23 | 24 | field.set(o, newValue); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/me/nov/zelixkiller/utils/analysis/ConstantTracker.java: -------------------------------------------------------------------------------- 1 | package me.nov.zelixkiller.utils.analysis; 2 | 3 | import static org.objectweb.asm.Opcodes.*; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import org.objectweb.asm.Type; 9 | import org.objectweb.asm.tree.*; 10 | import org.objectweb.asm.tree.analysis.*; 11 | 12 | /** 13 | * @author Holger https://stackoverflow.com/users/2711488/holger (Modified version) 14 | */ 15 | public class ConstantTracker extends Interpreter { 16 | static final ConstantValue NULL = new ConstantValue(BasicValue.REFERENCE_VALUE, null); 17 | 18 | public static final class ConstantValue implements Value { 19 | Object value; // null if unknown or NULL 20 | final BasicValue type; 21 | 22 | ConstantValue(BasicValue type, Object value) { 23 | this.value = value; 24 | this.type = Objects.requireNonNull(type); 25 | } 26 | 27 | @Override 28 | public int getSize() { 29 | return type.getSize(); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | Type t = type.getType(); 35 | if (t == null) 36 | return "uninitialized"; 37 | String typeName = type == BasicValue.REFERENCE_VALUE ? "a reference type" : t.getClassName(); 38 | return this == NULL ? "null" : value == null ? "unknown value of " + typeName : value + " (" + typeName + ")"; 39 | } 40 | 41 | public Object getValue() { 42 | return value; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object obj) { 47 | if (this == obj) 48 | return true; 49 | if (this == NULL || obj == NULL || !(obj instanceof ConstantValue)) 50 | return false; 51 | ConstantValue that = (ConstantValue) obj; 52 | return Objects.equals(this.value, that.value) && Objects.equals(this.type, that.type); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | if (this == NULL) 58 | return ~0; 59 | return (value == null ? 7 : value.hashCode()) + type.hashCode() * 31; 60 | } 61 | 62 | public void setValue(Object value) { 63 | this.value = value; 64 | } 65 | } 66 | 67 | BasicInterpreter basic = new BasicInterpreter() { 68 | @Override 69 | public BasicValue newValue(Type type) { 70 | return type != null && (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) ? new BasicValue(type) 71 | : super.newValue(type); 72 | } 73 | 74 | @Override 75 | public BasicValue merge(BasicValue a, BasicValue b) { 76 | if (a.equals(b)) 77 | return a; 78 | if (a.isReference() && b.isReference()) 79 | // this is the place to consider the actual type hierarchy if you want 80 | return BasicValue.REFERENCE_VALUE; 81 | return BasicValue.UNINITIALIZED_VALUE; 82 | } 83 | }; 84 | 85 | private Object[] args; 86 | private Type[] desc; 87 | 88 | public ConstantTracker() { 89 | super(ASM5); 90 | } 91 | 92 | /** 93 | * Inline method arguments 94 | */ 95 | public ConstantTracker(boolean isStatic, String descr, Object[] args) { 96 | super(ASM5); 97 | this.desc = Type.getArgumentTypes(descr); 98 | ArrayList reformatted = new ArrayList<>(); 99 | int local = isStatic ? 0 : 1; 100 | if (isStatic) { 101 | reformatted.set(0, null); // this reference 102 | } 103 | for (int i = 0; i < desc.length; ++i) { 104 | reformatted.set(local++, i >= args.length ? null : args[i]); 105 | if (desc[i].getSize() == 2) { 106 | reformatted.set(local++, null); 107 | } 108 | } 109 | this.args = reformatted.toArray(new Object[0]); 110 | } 111 | 112 | @Override 113 | public ConstantValue newOperation(AbstractInsnNode insn) throws AnalyzerException { 114 | switch (insn.getOpcode()) { 115 | case ACONST_NULL: 116 | return NULL; 117 | case ICONST_M1: 118 | case ICONST_0: 119 | case ICONST_1: 120 | case ICONST_2: 121 | case ICONST_3: 122 | case ICONST_4: 123 | case ICONST_5: 124 | return new ConstantValue(BasicValue.INT_VALUE, insn.getOpcode() - ICONST_0); 125 | case LCONST_0: 126 | case LCONST_1: 127 | return new ConstantValue(BasicValue.LONG_VALUE, (long) (insn.getOpcode() - LCONST_0)); 128 | case FCONST_0: 129 | case FCONST_1: 130 | case FCONST_2: 131 | return new ConstantValue(BasicValue.FLOAT_VALUE, (float) (insn.getOpcode() - FCONST_0)); 132 | case DCONST_0: 133 | case DCONST_1: 134 | return new ConstantValue(BasicValue.DOUBLE_VALUE, (double) (insn.getOpcode() - DCONST_0)); 135 | case BIPUSH: 136 | case SIPUSH: 137 | return new ConstantValue(BasicValue.INT_VALUE, ((IntInsnNode) insn).operand); 138 | case LDC: 139 | return new ConstantValue(basic.newOperation(insn), ((LdcInsnNode) insn).cst); 140 | default: 141 | BasicValue v = basic.newOperation(insn); 142 | return v == null ? null : new ConstantValue(v, null); 143 | } 144 | } 145 | 146 | @Override 147 | public ConstantValue copyOperation(AbstractInsnNode insn, ConstantValue value) { 148 | if (args != null && value.getValue() == null) { 149 | //unloaded arguments 150 | switch(insn.getOpcode()) { 151 | case ALOAD: 152 | case ILOAD: 153 | case LLOAD: 154 | case DLOAD: 155 | case FLOAD: 156 | Object val = args[((VarInsnNode)insn).var]; 157 | value.setValue(val); 158 | break; 159 | default: 160 | break; 161 | } 162 | } 163 | return value; 164 | } 165 | 166 | @Override 167 | public ConstantValue newValue(Type type) { 168 | BasicValue v = basic.newValue(type); 169 | return v == null ? null : new ConstantValue(v, null); 170 | } 171 | 172 | @Override 173 | public ConstantValue unaryOperation(AbstractInsnNode insn, ConstantValue value) throws AnalyzerException { 174 | BasicValue v = basic.unaryOperation(insn, value.type); 175 | return v == null ? null : new ConstantValue(v, getUnaryValue(insn.getOpcode(), value.value)); 176 | } 177 | 178 | private Object getUnaryValue(int opcode, Object value) { 179 | Number numVal = null; 180 | if (value instanceof Number) { 181 | numVal = (Number) value; 182 | } 183 | try { 184 | switch (opcode) { 185 | case INEG: 186 | return -((int) value); 187 | case FNEG: 188 | return -((float) value); 189 | case LNEG: 190 | return -((long) value); 191 | case DNEG: 192 | return -((double) value); 193 | case L2I: 194 | case F2I: 195 | case D2I: 196 | return numVal.intValue(); 197 | case I2B: 198 | return numVal.byteValue(); 199 | case I2C: 200 | return numVal.intValue() & 0x0000FFFF; 201 | case I2S: 202 | return numVal.shortValue(); 203 | case I2F: 204 | case L2F: 205 | case D2F: 206 | return numVal.floatValue(); 207 | case I2L: 208 | case F2L: 209 | case D2L: 210 | return numVal.longValue(); 211 | case I2D: 212 | case L2D: 213 | case F2D: 214 | return numVal.doubleValue(); 215 | case CHECKCAST: 216 | return value; 217 | default: 218 | return null; 219 | } 220 | } catch (NullPointerException e) { 221 | return null; 222 | } 223 | } 224 | 225 | @Override 226 | public ConstantValue binaryOperation(AbstractInsnNode insn, ConstantValue a, ConstantValue b) 227 | throws AnalyzerException { 228 | BasicValue v = basic.binaryOperation(insn, a.type, b.type); 229 | return v == null ? null : new ConstantValue(v, getBinaryValue(insn.getOpcode(), a.value, b.value)); 230 | } 231 | 232 | private Object getBinaryValue(int opcode, Object a, Object b) { 233 | if (a == null || b == null) { 234 | return null; 235 | } 236 | if (!(a instanceof Number) || !(b instanceof Number)) { 237 | return null; 238 | } 239 | // array load instructions not handled 240 | Number num1 = (Number) a; 241 | Number num2 = (Number) b; 242 | switch (opcode) { 243 | case IADD: 244 | return num1.intValue() + num2.intValue(); 245 | case ISUB: 246 | return num1.intValue() - num2.intValue(); 247 | case IMUL: 248 | return num1.intValue() * num2.intValue(); 249 | case IDIV: 250 | return num1.intValue() / num2.intValue(); 251 | case IREM: 252 | return num1.intValue() % num2.intValue(); 253 | case ISHL: 254 | return num1.intValue() << num2.intValue(); 255 | case ISHR: 256 | return num1.intValue() >> num2.intValue(); 257 | case IUSHR: 258 | return num1.intValue() >>> num2.intValue(); 259 | case IAND: 260 | return num1.intValue() & num2.intValue(); 261 | case IOR: 262 | return num1.intValue() | num2.intValue(); 263 | case IXOR: 264 | return num1.intValue() ^ num2.intValue(); 265 | case FADD: 266 | return num1.floatValue() + num2.floatValue(); 267 | case FSUB: 268 | return num1.floatValue() - num2.floatValue(); 269 | case FMUL: 270 | return num1.floatValue() * num2.floatValue(); 271 | case FDIV: 272 | return num1.floatValue() / num2.floatValue(); 273 | case FREM: 274 | return num1.floatValue() % num2.floatValue(); 275 | case LADD: 276 | return num1.longValue() + num2.longValue(); 277 | case LSUB: 278 | return num1.longValue() - num2.longValue(); 279 | case LMUL: 280 | return num1.longValue() * num2.longValue(); 281 | case LDIV: 282 | return num1.longValue() / num2.longValue(); 283 | case LREM: 284 | return num1.longValue() % num2.longValue(); 285 | case LSHL: 286 | return num1.longValue() << num2.longValue(); 287 | case LSHR: 288 | return num1.longValue() >> num2.longValue(); 289 | case LUSHR: 290 | return num1.longValue() >>> num2.longValue(); 291 | case LAND: 292 | return num1.longValue() & num2.longValue(); 293 | case LOR: 294 | return num1.longValue() | num2.longValue(); 295 | case LXOR: 296 | return num1.longValue() ^ num2.longValue(); 297 | case DADD: 298 | return num1.doubleValue() + num2.doubleValue(); 299 | case DSUB: 300 | return num1.doubleValue() - num2.doubleValue(); 301 | case DMUL: 302 | return num1.doubleValue() * num2.doubleValue(); 303 | case DDIV: 304 | return num1.doubleValue() / num2.doubleValue(); 305 | case DREM: 306 | return num1.doubleValue() % num2.doubleValue(); 307 | 308 | // compare instructions not tested, could return wrong result 309 | case LCMP: 310 | return Long.compare(num1.longValue(), num2.longValue()); 311 | case FCMPL: 312 | case FCMPG: 313 | // no NaN handling, could affect results 314 | return Float.compare(num1.longValue(), num2.longValue()); 315 | case DCMPL: 316 | case DCMPG: 317 | // no NaN handling, could affect results 318 | return Double.compare(num1.longValue(), num2.longValue()); 319 | default: 320 | return null; 321 | } 322 | } 323 | 324 | @Override 325 | public ConstantValue ternaryOperation(AbstractInsnNode insn, ConstantValue a, ConstantValue b, ConstantValue c) { 326 | return null; 327 | } 328 | 329 | @Override 330 | public ConstantValue naryOperation(AbstractInsnNode insn, List values) 331 | throws AnalyzerException { 332 | List unusedByBasicInterpreter = null; 333 | BasicValue v = basic.naryOperation(insn, unusedByBasicInterpreter); 334 | return v == null ? null : new ConstantValue(v, null); 335 | } 336 | 337 | @Override 338 | public void returnOperation(AbstractInsnNode insn, ConstantValue value, ConstantValue expected) { 339 | } 340 | 341 | @Override 342 | public ConstantValue merge(ConstantValue a, ConstantValue b) { 343 | if (a.equals(b)) 344 | return a; 345 | BasicValue t = basic.merge(a.type, b.type); 346 | return t.equals(a.type) && (a.value == null && a != NULL || a.value != null && a.value.equals(b.value)) ? a 347 | : t.equals(b.type) && b.value == null && b != NULL ? b : new ConstantValue(t, null); 348 | } 349 | } --------------------------------------------------------------------------------