首頁/ 汽車/ 正文

一步一步學習Allatori解密 (四)如何還原jar包中的加密字串

前面已經可以批次解密加密字串,但是有一個問題,那就是解密完字串之後,不知道是在什麼位置的,可以把加密的字串替換成解密之後的嗎?要是這樣就可以很方便的閱讀程式碼了,為了實現這個目標,讓我們接著來更新我們的程式吧。

try { ClassNode targetClassNode = lookupClass(jar, targetClass + “。class”); MethodNode decrypterNode = targetClassNode。methods。stream() 。filter(mn -> mn。name。equals(m。name) && mn。desc。equals(m。desc)) 。findFirst()。orElse(null); if (decrypterNode == null || decrypterNode。instructions。getFirst() == null) { continue; } if (isAllatoriMethod(decrypterNode)) { //反射方法解密字串,需要把jar加到classpath裡面 System。out。println(“找到加密字串 類:” + targetClassNode。name + “方法:” + decrypterNode。name + “加密字串:” + ldc。cst); Class clazz = Class。forName(targetClassNode。name。replaceAll(“/”, “。”)); Object obj = clazz。newInstance(); Method decrypterMethod = clazz。getDeclaredMethod(decrypterNode。name, String。class); Object invoke = decrypterMethod。invoke(obj, ldc。cst); System。out。println(“解密字串:” + invoke); }} catch (Exception e) { e。printStackTrace();}

上一講,我們用反射去解密的加密字串,這裡會有一個問題,就是要把我們要解密的jar包,要加到我們專案的classpath裡面,不然沒辦法呼叫反射方法。這次我們要用asm去執行解密方法,獲取解密字串。

一步一步學習Allatori解密 (四)如何還原jar包中的加密字串

被混淆的類

一步一步學習Allatori解密 (四)如何還原jar包中的加密字串

未混淆的類

我們先分析一下加密之後class的位元組碼,每個加密方法都是由LDC和INVOKESTATIC兩部分組成的,原理是我們只要把LDC “(1\u000C8\u000Ft7;\u00128\u0004u”字串替換成LDC “Hello World!”,然後把INVOKESTATIC 呼叫解密方法移除就可以了

ldc。cst = MethodExecutor。execute(targetClassNode, decrypterNode, Collections。singletonList(JavaValue。valueOf(ldc。cst)), null, context);modifier。remove(m);

我們把執行方法封裝到一個工具類裡面MethodExecutor。execute呼叫這個方法,可以獲取執行結果,然後修改ldc。cst的值,這樣就可以替換成解密後的字元了,替換完還要把呼叫解密方法移除。這個操作完畢之後,我們要把我們修改的ClassNode寫回jar包中,這樣我就實現了在原始位置解密了,是不是很簡單呢?

一步一步學習Allatori解密 (四)如何還原jar包中的加密字串

混淆的反編譯程式碼

一步一步學習Allatori解密 (四)如何還原jar包中的加密字串

解密後反編譯程式碼

廢話不多說,上程式碼:

package cn。myj2c;import cn。myj2c。utils。InstructionModifier;import org。apache。commons。io。IOUtils;import cn。myj2c。asm。ConstantPool;import cn。myj2c。executor。Context;import cn。myj2c。executor。MethodExecutor;import cn。myj2c。executor。defined。JVMComparisonProvider;import cn。myj2c。executor。defined。JVMMethodProvider;import cn。myj2c。executor。defined。MappedMethodProvider;import cn。myj2c。executor。providers。ComparisonProvider;import cn。myj2c。executor。providers。DelegatingProvider;import cn。myj2c。executor。values。JavaValue;import org。objectweb。asm。ClassReader;import org。objectweb。asm。ClassWriter;import org。objectweb。asm。Opcodes;import org。objectweb。asm。Type;import org。objectweb。asm。commons。JSRInlinerAdapter;import org。objectweb。asm。tree。*;import org。objectweb。asm。tree。analysis。*;import org。objectweb。asm。util。CheckClassAdapter;import java。io。File;import java。io。FileOutputStream;import java。io。IOException;import java。util。*;import java。util。concurrent。atomic。AtomicInteger;import java。util。zip。ZipEntry;import java。util。zip。ZipFile;import java。util。zip。ZipOutputStream;import static org。objectweb。asm。Opcodes。*;public class AllatoriStringDecrypt { private static Map classes = new HashMap<>(); private final static Map constantPools = new HashMap<>(); public static void main(String[] args) throws IOException { String file = “D:\\work\\allatori-deobfuscator\\obfuscator\\allatoriDeobfuscator。jar”; //預載入專案中的class loadInput(new File(file)); DelegatingProvider provider = new DelegatingProvider(); provider。register(new JVMMethodProvider()); provider。register(new JVMComparisonProvider()); provider。register(new MappedMethodProvider(classes)); provider。register(new ComparisonProvider() { @Override public boolean instanceOf(JavaValue target, Type type, Context context) { return false; } @Override public boolean checkcast(JavaValue target, Type type, Context context) { return true; } @Override public boolean checkEquality(JavaValue first, JavaValue second, Context context) { return false; } @Override public boolean canCheckInstanceOf(JavaValue target, Type type, Context context) { return false; } @Override public boolean canCheckcast(JavaValue target, Type type, Context context) { return true; } @Override public boolean canCheckEquality(JavaValue first, JavaValue second, Context context) { return false; } }); Set decryptor = new HashSet<>(); //解析混淆字串 for (ClassNode classNode : classes。values()) { for (MethodNode method : classNode。methods) { InstructionModifier modifier = new InstructionModifier(); Frame[] frames; try { frames = new Analyzer<>(new SourceInterpreter())。analyze(classNode。name, method); } catch (AnalyzerException e) { continue; } ListIterator iterator = method。instructions。iterator(); while (iterator。hasNext()) { AbstractInsnNode node = iterator。next(); if (!(node instanceof LineNumberNode) && !(node instanceof FrameNode) && !(node instanceof LabelNode)) { if (node。getOpcode() != Opcodes。INVOKESTATIC) { //如果不是靜態方法 continue; } MethodInsnNode m = (MethodInsnNode) node; if (!m。desc。equals(“(Ljava/lang/Object;)Ljava/lang/String;”) && !m。desc。equals(“(Ljava/lang/String;)Ljava/lang/String;”)) { continue; } String targetClass = m。owner; Frame f = frames[method。instructions。indexOf(m)]; if (f。getStack(f。getStackSize() - 1)。insns。size() != 1) { continue; } AbstractInsnNode insn = f。getStack(f。getStackSize() - 1)。insns。iterator()。next(); if (insn。getOpcode() != Opcodes。LDC) { continue; } LdcInsnNode ldc = (LdcInsnNode) insn; if (!(ldc。cst instanceof String)) { continue; } /*try { ClassNode targetClassNode = lookupClass(jar, targetClass + “。class”); MethodNode decrypterNode = targetClassNode。methods。stream() 。filter(mn -> mn。name。equals(m。name) && mn。desc。equals(m。desc)) 。findFirst()。orElse(null); if (decrypterNode == null || decrypterNode。instructions。getFirst() == null) { continue; } if (isAllatoriMethod(decrypterNode)) { //反射方法解密字串,需要把jar加到classpath裡面 System。out。println(“找到加密字串 類:” + targetClassNode。name + “方法:” + decrypterNode。name + “加密字串:” + ldc。cst); Class clazz = Class。forName(targetClassNode。name。replaceAll(“/”, “。”)); Object obj = clazz。newInstance(); Method decrypterMethod = clazz。getDeclaredMethod(decrypterNode。name, String。class); Object invoke = decrypterMethod。invoke(obj, ldc。cst); System。out。println(“解密字串:” + invoke); } } catch (Exception e) { e。printStackTrace(); }*/ Context context = new Context(provider); context。push(classNode。name, method。name, getConstantPool(classNode)。getSize()); ClassNode targetClassNode = classes。get(targetClass); if (targetClassNode == null) { continue; } MethodNode decrypterNode = targetClassNode。methods。stream() 。filter(mn -> mn。name。equals(m。name) && mn。desc。equals(m。desc)) 。findFirst()。orElse(null); if (decrypterNode == null || decrypterNode。instructions。getFirst() == null) { continue; } if (decryptor。contains(decrypterNode) || isAllatoriMethod(decrypterNode)) { System。out。println(“找到加密字串 類:” + targetClassNode。name + “方法:” + decrypterNode。name + “加密字串:” + ldc。cst); patchMethod(decrypterNode); try { ldc。cst = MethodExecutor。execute(targetClassNode, decrypterNode, Collections。singletonList(JavaValue。valueOf(ldc。cst)), null, context); modifier。remove(m); decryptor。add(decrypterNode); } catch (Throwable t) { System。out。println(“Error while decrypting Allatori string。”); System。out。println(“Are you sure you‘re deobfuscating something obfuscated by Allatori?”); System。out。println(classNode。name + “ ” + method。name + method。desc + “ ” + m。owner + “ ” + m。name + m。desc); t。printStackTrace(System。out); } } } } modifier。apply(method); } } cleanup(decryptor); ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(file。replace(“。jar”, “。out。jar”))); classes。values()。forEach(classNode -> { try { byte[] b = toByteArray(classNode); if (b != null) { zipOut。putNextEntry(new ZipEntry(classNode。name + “。class”)); zipOut。write(b); zipOut。closeEntry(); } } catch (IOException e) { System。out。println(“Error writing entry ” + classNode。name + e。getMessage()); } }); zipOut。close(); } private static byte[] toByteArray(ClassNode node) { if (node。innerClasses != null) { node。innerClasses。stream()。filter(in -> in。innerName != null)。forEach(in -> { if (in。innerName。indexOf(’/‘) != -1) { in。innerName = in。innerName。substring(in。innerName。lastIndexOf(’/‘) + 1); //Stringer } }); } ClassWriter writer = new ClassWriter(ClassWriter。COMPUTE_FRAMES); try { node。accept(writer); } catch (Throwable e) { System。out。println(“Error while writing ” + node。name); e。printStackTrace(System。out); } byte[] classBytes = writer。toByteArray(); ClassReader cr = new ClassReader(classBytes); try { cr。accept(new CheckClassAdapter(new ClassWriter(0)), 0); } catch (Throwable t) { System。out。println(“Error: ” + node。name + “ failed verification”); t。printStackTrace(System。out); } return classBytes; } private static void patchMethod(MethodNode method) { boolean getStackTrace = false; boolean getClassName = false; for (AbstractInsnNode i = method。instructions。getFirst(); i != null; i = i。getNext()) { if (!(i instanceof MethodInsnNode)) { continue; } String name = ((MethodInsnNode) i)。name; if (!getStackTrace && name。equals(“getStackTrace”)) { getStackTrace = true; if (getClassName) { break; } } else if (!getClassName && name。equals(“getClassName”)) { getClassName = true; if (getStackTrace) { break; } } } if (!getClassName || !getStackTrace) { return; } for (AbstractInsnNode insn = method。instructions。getFirst(); insn != null; insn = insn。getNext()) { if (insn。getOpcode() == Opcodes。NEW) { TypeInsnNode typeInsn = (TypeInsnNode) insn; if (typeInsn。desc。endsWith(“Exception”) || typeInsn。desc。endsWith(“Error”)) { typeInsn。desc = “java/lang/RuntimeException”; } } else if (insn instanceof MethodInsnNode) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn。owner。endsWith(“Exception”) || methodInsn。owner。endsWith(“Error”)) { methodInsn。owner = “java/lang/RuntimeException”; } } } } private static void loadInput(File file) throws IOException { try (ZipFile zipIn = new ZipFile(file)) { Enumeration<? extends ZipEntry> e = zipIn。entries(); while (e。hasMoreElements()) { ZipEntry next = e。nextElement(); if (next。isDirectory() || next。getName()。endsWith(“。class/”)) { continue; } byte[] data = IOUtils。toByteArray(zipIn。getInputStream(next)); loadInput(next。getName(), data); } } } public static void loadInput(String name, byte[] data) { if (name。endsWith(“。class”) || name。endsWith(“。class/”)) { if (data。length <= 30) { return; } try { ClassReader reader = new ClassReader(data); ClassNode node = new ClassNode(); reader。accept(node, ClassReader。SKIP_FRAMES); for (int i = 0; i < node。methods。size(); i++) { MethodNode methodNode = node。methods。get(i); JSRInlinerAdapter adapter = new JSRInlinerAdapter( methodNode, methodNode。access, methodNode。name, methodNode。desc, methodNode。signature, methodNode。exceptions。toArray(new String[0])); methodNode。accept(adapter); node。methods。set(i, adapter); } classes。put(node。name, node); setConstantPool(node, new ConstantPool(reader)); } catch (IllegalArgumentException | IndexOutOfBoundsException x) { x。printStackTrace(); } } } private static boolean isAllatoriMethod(MethodNode decryptorNode) { boolean isAllatori = true; isAllatori = isAllatori && containsInvokeVirtual(decryptorNode, “java/lang/String”, “charAt”, “(I)C”); isAllatori = isAllatori && containsInvokeVirtual(decryptorNode, “java/lang/String”, “length”, “()I”); isAllatori = isAllatori && containsInvokeSpecial(decryptorNode, “java/lang/String”, “”, null); isAllatori = isAllatori && countOccurencesOf(decryptorNode, IXOR) > 2; isAllatori = isAllatori && countOccurencesOf(decryptorNode, NEWARRAY) > 0; return isAllatori; } public static boolean containsInvokeVirtual(MethodNode methodNode, String owner, String name, String desc) { for (AbstractInsnNode insn : methodNode。instructions) { if (isInvokeVirtual(insn, owner, name, desc)) { return true; } } return false; } public static boolean isInvokeVirtual(AbstractInsnNode insn, String owner, String name, String desc) { if (insn == null) { return false; } if (insn。getOpcode() != INVOKEVIRTUAL) { return false; } MethodInsnNode methodInsnNode = (MethodInsnNode) insn; return (owner == null || methodInsnNode。owner。equals(owner)) && (name == null || methodInsnNode。name。equals(name)) && (desc == null || methodInsnNode。desc。equals(desc)); } public static boolean containsInvokeSpecial(MethodNode methodNode, String owner, String name, String desc) { for (AbstractInsnNode insn : methodNode。instructions) { if (isInvokeSpecial(insn, owner, name, desc)) { return true; } } return false; } public static boolean isInvokeSpecial(AbstractInsnNode insn, String owner, String name, String desc) { if (insn == null) { return false; } if (insn。getOpcode() != INVOKESPECIAL) { return false; } MethodInsnNode methodInsnNode = (MethodInsnNode) insn; return (owner == null || methodInsnNode。owner。equals(owner)) && (name == null || methodInsnNode。name。equals(name)) && (desc == null || methodInsnNode。desc。equals(desc)); } public static int countOccurencesOf(MethodNode methodNode, int opcode) { int i = 0; for (AbstractInsnNode insnNode : methodNode。instructions) { if (insnNode。getOpcode() == opcode) { i++; } } return i; } public static ConstantPool getConstantPool(ClassNode classNode) { return constantPools。get(classNode); } public static void setConstantPool(ClassNode owner, ConstantPool pool) { constantPools。put(owner, pool); } public static Collection classNodes() { return classes。values(); } private static int cleanup(Set toRemove) { classNodes()。forEach(node -> node。methods。forEach(methodNode -> { for (AbstractInsnNode insn : methodNode。instructions) { if (insn。getOpcode() != INVOKESTATIC) { continue; } MethodInsnNode m = (MethodInsnNode) insn; ClassNode owner = classes。get(m。owner); if (owner == null) { continue; } MethodNode mNode = owner。methods。stream()。filter(mn -> mn。name。equals(m。name) && mn。desc。equals(m。desc))。findFirst()。orElse(null); toRemove。remove(mNode); } })); AtomicInteger count = new AtomicInteger(0); classNodes()。forEach(classNode -> { for (Iterator it = classNode。methods。iterator(); it。hasNext(); ) { MethodNode methodNode = it。next(); if (toRemove。remove(methodNode)) { it。remove(); count。getAndIncrement(); } } }); return count。get(); }}

由於用到了MethodExecutor等相關的工具類,這部分程式碼比較多,我把完整程式碼放到

https://gitee。com/myj2c/allatori-deobfuscator

請大家自行復制下載

相關文章

頂部