This commit is contained in:
SuspiciousActivity committed 2019-10-09 17:15:27 +02:00
1 parent d1d71dd7f2
commit 46cd6de355
6 files changed
+781

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
# Unmapper
This simple program allows you to input an obfuscated jar file together with the corresponding mapping.txt to deobfuscate the jar file.
It uses ObjectWeb ASM (http://asm.ow2.org/)
ProGuard (https://www.guardsquare.com/en/products/proguard)
+394
View File
@@ -0,0 +1,394 @@
package me.Unmapper;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
public class ClassNodeRemapper {
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("(L[^;<]+[;<])");
private final HashMap<String, String> classMappings;
private final HashMap<MemberData, String> memberMappings;
public ClassNodeRemapper(HashMap<String, String> classMappings, HashMap<MemberData, String> memberMappings) {
this.classMappings = classMappings;
this.memberMappings = memberMappings;
}
public void remap(ClassNode cn) {
remapMethods(cn);
remapFields(cn);
cn.name = getOldClassName(cn.name);
cn.superName = getOldClassName(cn.superName);
cn.signature = getOldSignature(cn.signature);
if (!cn.interfaces.isEmpty()) {
List<String> newInterfaces = new ArrayList<String>();
for (String s : cn.interfaces) {
newInterfaces.add(getOldClassName(s));
}
cn.interfaces = newInterfaces;
}
if (cn.innerClasses != null && !cn.innerClasses.isEmpty()) {
for (InnerClassNode icn : cn.innerClasses) {
String full = getOldClassName(icn.name);
if (icn.outerName != null) {
String[] split = full.split("\\$");
icn.outerName = split[0];
if (icn.innerName != null) {
icn.innerName = split[1];
}
}
icn.name = full;
}
}
}
private static final int ACC_STATIC = 0x0008;
private void remapMethods(ClassNode cn) {
if (cn.methods == null)
return;
String clazz = cn.name;
for (MethodNode mn : cn.methods) {
MemberData data = new MemberData(cn.name, mn.desc, mn.name);
mn.name = getOldMemberName(data);
mn.desc = getOldMemberMethodDesc(mn.desc);
mn.signature = getOldSignature(mn.signature);
if (!mn.exceptions.isEmpty()) {
List<String> newExceptions = new ArrayList<String>();
for (String s : mn.exceptions) {
newExceptions.add(getOldClassName(s));
}
mn.exceptions = newExceptions;
}
if (mn.localVariables != null && !mn.localVariables.isEmpty()) {
Iterator<LocalVariableNode> lvns = mn.localVariables.iterator();
while (lvns.hasNext()) {
LocalVariableNode lvn = lvns.next();
if (Main.removeLocals) {
if ((mn.access & ACC_STATIC) == 0 && lvn.index == 0) { // "this" for non-static methods
lvn.name = "this"; // should always be "this"
} else if (!isAscii(lvn.name)) {
lvns.remove();
continue;
}
}
lvn.desc = getOldMemberFieldDesc(lvn.desc);
lvn.signature = getOldSignature(lvn.signature);
}
}
if (mn.tryCatchBlocks != null && !mn.tryCatchBlocks.isEmpty()) {
for (TryCatchBlockNode tcbn : mn.tryCatchBlocks) {
tcbn.type = getOldClassName(tcbn.type);
}
}
int len = mn.instructions.size();
for (int i = 0; i < len; i++) {
remapInstruction(clazz, mn.instructions.get(i));
}
}
}
private void remapFields(ClassNode cn) {
if (cn.fields == null)
return;
for (FieldNode fn : cn.fields) {
MemberData data = new MemberData(cn.name, fn.desc, fn.name);
fn.name = getOldMemberName(data);
fn.desc = getOldMemberFieldDesc(fn.desc);
fn.signature = getOldSignature(fn.signature);
}
}
private boolean isAscii(String s) {
for (char c : s.toCharArray()) {
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
return false;
}
return true;
}
private String getOldClassName(String name) {
if (name == null)
return null;
String old = classMappings.get(name);
if (old != null)
return old;
return name;
}
private String getOldMemberName(MemberData data) {
if (data == null)
return null;
String old = memberMappings.get(data);
if (old != null)
return old;
return data.getName();
}
private String getOldMemberMethodDesc(String desc) {
if (desc == null)
return null;
int argsEndIdx = desc.indexOf(')');
String retType = getOldMemberFieldDesc(desc.substring(argsEndIdx + 1));
String args = desc.substring(1, argsEndIdx);
int idx = 0;
char[] arr = args.toCharArray();
StringBuilder outerSb = new StringBuilder("(");
while (idx < arr.length) {
int arrayCount = 0;
for (; idx < arr.length && arr[idx] == '['; idx++)
arrayCount++;
if (idx >= arr.length) // weird input?
return desc;
StringBuilder sb = new StringBuilder();
switch (arr[idx]) {
case 'B':
case 'Z':
case 'I':
case 'J':
case 'C':
case 'D':
case 'F':
case 'S':
case 'V':
sb.append(arr[idx]);
idx++;
break;
case 'L': {
idx++;
int startIdx = idx;
for (; idx < arr.length && arr[idx] != ';'; idx++)
;
sb.append('L');
sb.append(getOldClassName(args.substring(startIdx, idx)));
sb.append(';');
idx++;
break;
}
default:
break;
}
for (int i = 0; i < arrayCount; i++)
sb.insert(0, '[');
outerSb.append(sb);
}
outerSb.append(')');
outerSb.append(retType);
return outerSb.toString();
}
private String getOldMemberFieldDesc(String desc) {
if (desc == null)
return null;
int idx = 0;
char[] arr = desc.toCharArray();
int arrayCount = 0;
for (; idx < arr.length && arr[idx] == '['; idx++)
arrayCount++;
if (idx >= arr.length) // weird input?
return desc;
StringBuilder sb = new StringBuilder();
switch (arr[idx]) {
case 'B':
case 'Z':
case 'I':
case 'J':
case 'C':
case 'D':
case 'F':
case 'S':
case 'V':
sb.append(arr[idx]);
break;
case 'L': {
idx++;
int startIdx = idx;
for (; idx < arr.length && arr[idx] != ';'; idx++)
;
sb.append('L');
sb.append(getOldClassName(desc.substring(startIdx, idx)));
sb.append(';');
break;
}
default:
break;
}
for (int i = 0; i < arrayCount; i++)
sb.insert(0, '[');
return sb.toString();
}
private String getOldSignature(String s) {
if (s == null || s.isEmpty())
return s;
if (s.startsWith("(")) { // Methods
int startIdx = s.indexOf('(');
int endIdx = s.indexOf(')');
if (endIdx < 0) // Weird?
return s;
String typeType = s.substring(0, startIdx);
String args = s.substring(startIdx + 1, endIdx);
String retType = s.substring(endIdx + 1);
return getOldSignature(typeType) + "(" + getOldSignature(args) + ")" + getOldSignature(retType);
} else {
StringBuffer sb = new StringBuffer();
Matcher m = CLASS_NAME_PATTERN.matcher(s);
while (m.find()) {
m.appendReplacement(sb, Matcher.quoteReplacement(getOldSignatureFromMatcher(m.group())));
}
m.appendTail(sb);
return sb.toString();
}
}
private String getOldSignatureFromMatcher(String s) {
boolean endsWithBracket = s.endsWith("<");
s = s.substring(1, s.length() - 1);
return "L" + getOldClassName(s) + (endsWithBracket ? "<" : ";");
}
private void remapInstruction(String clazz, AbstractInsnNode n) {
if (FieldInsnNode.class.isInstance(n)) {
FieldInsnNode node = (FieldInsnNode) n;
node.name = getOldMemberName(new MemberData(clazz, node.desc, node.name));
node.desc = getOldMemberFieldDesc(node.desc);
node.owner = getOldClassName(node.owner);
} else if (FrameNode.class.isInstance(n)) {
FrameNode node = (FrameNode) n;
if (node.local != null) {
int len = node.local.size();
for (int i = 0; i < len; i++) {
Object o = node.local.get(i);
if (o == null)
continue;
if (o instanceof String) {
String s = (String) o;
if (s.startsWith("["))
s = getOldMemberFieldDesc(s);
else
s = getOldClassName(s);
node.local.set(i, s);
}
}
}
if (node.stack != null) {
int len = node.stack.size();
for (int i = 0; i < len; i++) {
Object o = node.stack.get(i);
if (o == null)
continue;
if (o instanceof String) {
String s = (String) o;
if (s.startsWith("["))
s = getOldMemberFieldDesc(s);
else
s = getOldClassName(s);
node.stack.set(i, s);
}
}
}
} else if (InvokeDynamicInsnNode.class.isInstance(n)) {
InvokeDynamicInsnNode node = (InvokeDynamicInsnNode) n;
node.name = getOldMemberName(new MemberData(clazz, node.desc, node.name));
node.desc = getOldMemberMethodDesc(node.desc);
node.bsm = new Handle(node.bsm.getTag(), getOldClassName(node.bsm.getOwner()),
getOldMemberName(new MemberData(node.bsm.getOwner(), node.bsm.getDesc(), node.bsm.getName())),
getOldMemberMethodDesc(node.bsm.getDesc()), node.bsm.isInterface());
int len = node.bsmArgs.length;
for (int i = 0; i < len; i++) {
Object o = node.bsmArgs[i];
if (o instanceof Type) {
Type type = (Type) o;
try {
Field f_off = Type.class.getDeclaredField("off");
f_off.setAccessible(true);
Field f_len = Type.class.getDeclaredField("len");
f_len.setAccessible(true);
Field f_buf = Type.class.getDeclaredField("buf");
f_buf.setAccessible(true);
String buf = new String((char[]) f_buf.get(type));
String oldBuf = getOldClassName(buf);
f_buf.set(type, oldBuf.toCharArray());
f_len.set(type, oldBuf.length());
} catch (Exception e) {
e.printStackTrace();
}
} else if (o instanceof Handle) {
Handle h = (Handle) o;
node.bsmArgs[i] = new Handle(h.getTag(), getOldClassName(h.getOwner()),
getOldMemberName(new MemberData(h.getOwner(), h.getDesc(), h.getName())),
getOldMemberMethodDesc(h.getDesc()), h.isInterface());
}
}
} else if (MethodInsnNode.class.isInstance(n)) {
MethodInsnNode node = (MethodInsnNode) n;
node.name = getOldMemberName(new MemberData(clazz, node.desc, node.name));
node.desc = getOldMemberMethodDesc(node.desc);
node.owner = getOldClassName(node.owner);
} else if (TypeInsnNode.class.isInstance(n)) {
TypeInsnNode node = (TypeInsnNode) n;
node.desc = getOldClassName(node.desc);
} else if (MultiANewArrayInsnNode.class.isInstance(n)) {
MultiANewArrayInsnNode node = (MultiANewArrayInsnNode) n;
node.desc = getOldClassName(node.desc);
} else if (LdcInsnNode.class.isInstance(n)) {
LdcInsnNode node = (LdcInsnNode) n;
if (node.cst instanceof Type) {
Type type = (Type) node.cst;
try {
Field f_off = Type.class.getDeclaredField("off");
f_off.setAccessible(true);
Field f_len = Type.class.getDeclaredField("len");
f_len.setAccessible(true);
Field f_buf = Type.class.getDeclaredField("buf");
f_buf.setAccessible(true);
String buf = new String((char[]) f_buf.get(type));
String oldBuf = getOldClassName(buf);
f_buf.set(type, oldBuf.toCharArray());
f_len.set(type, oldBuf.length());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
package me.Unmapper;
import java.util.HashMap;
public class ClassUtil {
public static String getSimpleDesc(String type) {
String desc = "";
for (int i = 0; i < countArraySize(type); i++) {
desc += "[";
}
type = type.replace("[]", "");
if (VARS.containsKey(type)) {
desc += VARS.get(type);
} else {
desc += "L" + type + ";";
}
return desc;
}
private static int countArraySize(String type) {
int size = 0;
for (byte b : type.getBytes()) {
if (b == ']') {
size++;
}
}
return size;
}
public static final HashMap<String, String> VARS = new HashMap<>();
static {
VARS.put("byte", "B");
VARS.put("char", "C");
VARS.put("double", "D");
VARS.put("float", "F");
VARS.put("int", "I");
VARS.put("long", "J");
VARS.put("short", "S");
VARS.put("boolean", "Z");
VARS.put("void", "V");
}
}
+133
View File
@@ -0,0 +1,133 @@
package me.Unmapper;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import org.apache.commons.io.IOUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
public class Main implements Opcodes {
private static HashMap<JarEntry, byte[]> OTHER_FILES = new HashMap<>();
private static ArrayList<ClassNode> classNodes = new ArrayList<>();
private static MappingReader mapping;
public static boolean removeLocals = false;
public static void main(String[] args) throws Exception {
if (args.length < 3) {
System.out.println("Syntax: input.jar mapping.txt output.jar (-l)");
System.out.println("\t-l\tRemoves local variables which are not ASCII");
System.out.println("\t\t\t(let the decompilers generate better names)");
System.exit(1);
}
if (args.length > 3) {
removeLocals = args[3].equalsIgnoreCase("-l");
System.out.println("Enabled local variable removal.");
}
load(new File(args[0]));
map(new File(args[1]));
save(new File(args[2]));
}
public static void map(File mappingFile) {
System.out.println("Reading mapping...");
mapping = new MappingReader(mappingFile);
try {
mapping.load();
} catch (Exception ex) {
new Exception("Error loading mapping", ex).printStackTrace();
System.exit(1);
}
System.out.println("Read " + mapping.getClassMappings().size() + " class mappings and "
+ mapping.getMemberMappings().size() + " member mappings.");
System.out.println("Remapping...");
ClassNodeRemapper remapper = new ClassNodeRemapper(mapping.getClassMappings(), mapping.getMemberMappings());
for (ClassNode cn : classNodes) {
remapper.remap(cn);
}
}
public static void save(File jar) {
System.out.println("Saving...");
try {
try (final JarOutputStream output = new JarOutputStream(new FileOutputStream(jar))) {
for (Entry<JarEntry, byte[]> entry : OTHER_FILES.entrySet()) {
output.putNextEntry(entry.getKey());
output.write(entry.getValue());
output.closeEntry();
}
for (ClassNode element : classNodes) {
ClassWriter writer = new ClassWriter(0);
element.accept(writer);
output.putNextEntry(new JarEntry(element.name + ".class"));
output.write(writer.toByteArray());
output.closeEntry();
}
}
} catch (Exception ex) {
new Exception("Error saving jar file", ex).printStackTrace();
}
System.out.println("Saved!");
}
public static void load(File file) {
System.out.println("Loading...");
try {
JarFile jar = new JarFile(file);
ArrayList<JarEntry> entrysLeft = new ArrayList<>();
Enumeration<JarEntry> enumeration = jar.entries();
while (enumeration.hasMoreElements()) {
JarEntry next = enumeration.nextElement();
byte[] data = IOUtils.toByteArray(jar.getInputStream(next));
if (next.getName().endsWith(".class")) {
ClassReader reader = new ClassReader(data);
ClassNode node = new ClassNode();
reader.accept(node, 0);
try {
classNodes.add(node);
} catch (NoClassDefFoundError e) {
entrysLeft.add(next);
}
} else {
OTHER_FILES.put(new JarEntry(next.getName()), data);
}
}
while (!entrysLeft.isEmpty()) {
for (JarEntry next : (ArrayList<JarEntry>) entrysLeft.clone()) {
byte[] data = IOUtils.toByteArray(jar.getInputStream(next));
if (next.getName().endsWith(".class")) {
ClassReader reader = new ClassReader(data);
ClassNode node = new ClassNode();
reader.accept(node, 0);
try {
classNodes.add(node);
entrysLeft.remove(next);
} catch (NoClassDefFoundError e) {
}
} else {
OTHER_FILES.put(new JarEntry(next.getName()), data);
}
}
}
jar.close();
} catch (Exception ex) {
new Exception("Error loading jar file", ex).printStackTrace();
}
System.out.println("Loaded!");
}
}
+135
View File
@@ -0,0 +1,135 @@
package me.Unmapper;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
public class MappingReader {
private final File mappingFile;
private final HashMap<String, String> internalReversedClassMappings = new HashMap<>();
private final HashMap<String, String> classMappings = new HashMap<>();
private final HashMap<MemberData, String> memberMappings = new HashMap<>();
public MappingReader(File mappingFile) {
this.mappingFile = mappingFile;
}
public void load() throws IOException {
try (BufferedReader read = new BufferedReader(new FileReader(mappingFile))) {
String className = null;
String s;
while ((s = read.readLine()) != null) {
s = s.trim();
if (s.startsWith("#"))
continue;
if (s.endsWith(":")) {
className = parseClassMapping(s);
} else if (className != null) {
parseClassMemberMapping(className, s);
}
}
}
}
private String parseClassMapping(String s) {
int splitIdx = s.indexOf("->");
if (splitIdx < 0) {
return null;
}
int colonIndex = s.indexOf(':', splitIdx + 2);
if (colonIndex < 0) {
return null;
}
String originalName = s.substring(0, splitIdx).trim().replace('.', '/');
String obfuscatedName = s.substring(splitIdx + 2, colonIndex).trim().replace('.', '/');
classMappings.put(obfuscatedName, originalName);
internalReversedClassMappings.put(originalName, obfuscatedName);
return originalName;
}
private void parseClassMemberMapping(String className, String s) {
int colonIdx1 = s.indexOf(':');
int colonIdx2 = colonIdx1 < 0 ? -1 : s.indexOf(':', colonIdx1 + 1);
int spaceIdx = s.indexOf(' ', colonIdx2 + 2);
int argIdx1 = s.indexOf('(', spaceIdx + 1);
int argIdx2 = argIdx1 < 0 ? -1 : s.indexOf(')', argIdx1 + 1);
int colonIdx3 = argIdx2 < 0 ? -1 : s.indexOf(':', argIdx2 + 1);
int colonIdx4 = colonIdx3 < 0 ? -1 : s.indexOf(':', colonIdx3 + 1);
int arrowIdx = s.indexOf("->",
(colonIdx4 >= 0 ? colonIdx4 : colonIdx3 >= 0 ? colonIdx3 : argIdx2 >= 0 ? argIdx2 : spaceIdx) + 1);
if (spaceIdx < 0 || arrowIdx < 0) {
return;
}
String type = s.substring(colonIdx2 + 1, spaceIdx).trim();
String originalName = s.substring(spaceIdx + 1, argIdx1 >= 0 ? argIdx1 : arrowIdx).trim();
String obfuscatedName = s.substring(arrowIdx + 2).trim();
String newClassName = className;
int dotIndex = originalName.lastIndexOf('.');
if (dotIndex >= 0) {
className = originalName.substring(0, dotIndex);
originalName = originalName.substring(dotIndex + 1);
// How does this work?
System.out.println("Don't know how to handle mapping " + className + " to " + newClassName);
}
if (type.length() > 0 && originalName.length() > 0 && obfuscatedName.length() > 0) {
if (argIdx2 < 0) {
memberMappings.put(
new MemberData(getOldClassName(className), convertTypeToByteCode(type), obfuscatedName),
originalName);
} else {
String args = s.substring(argIdx1 + 1, argIdx2).trim();
memberMappings.put(new MemberData(getOldClassName(className),
"(" + convertMultipleTypesToByteCode(args) + ")" + convertTypeToByteCode(type), obfuscatedName),
originalName);
}
}
}
public HashMap<String, String> getClassMappings() {
return classMappings;
}
public HashMap<MemberData, String> getMemberMappings() {
return memberMappings;
}
private String getOldClassName(String name) {
name = name.replace('.', '/');
String old = internalReversedClassMappings.get(name);
if (old != null)
return old;
return name;
}
private String convertTypeToByteCode(String type) {
if (type.isEmpty())
return "";
return ClassUtil.getSimpleDesc(getOldClassName(type));
}
private String convertMultipleTypesToByteCode(String types) {
if (types.isEmpty())
return "";
String ret = "";
for (String s : types.split(",")) {
ret += convertTypeToByteCode(s);
}
return ret;
}
}
+69
View File
@@ -0,0 +1,69 @@
package me.Unmapper;
public class MemberData {
private final String clazz;
private final String desc;
private final String name;
public MemberData(String clazz, String desc, String name) {
this.clazz = clazz;
this.desc = desc;
this.name = name;
}
public String getClazz() {
return clazz;
}
public String getDesc() {
return desc;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((clazz == null) ? 0 : clazz.hashCode());
result = prime * result + ((desc == null) ? 0 : desc.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MemberData other = (MemberData) obj;
if (clazz == null) {
if (other.clazz != null)
return false;
} else if (!clazz.equals(other.clazz))
return false;
if (desc == null) {
if (other.desc != null)
return false;
} else if (!desc.equals(other.desc))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "MemberData [clazz=" + clazz + ", desc=" + desc + ", name=" + name + "]";
}
}