// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.modules.decompiler.deobfuscator; import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.code.Instruction; import org.jetbrains.java.decompiler.code.InstructionSequence; import org.jetbrains.java.decompiler.code.SimpleInstructionSequence; import org.jetbrains.java.decompiler.code.cfg.BasicBlock; import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; import org.jetbrains.java.decompiler.code.cfg.ExceptionRangeCFG; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.modules.decompiler.decompose.GenericDominatorEngine; import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraph; import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraphNode; import org.jetbrains.java.decompiler.util.InterpreterUtil; import java.util.*; import java.util.Map.Entry; public final class ExceptionDeobfuscator { private static final class Range { private final BasicBlock handler; private final String uniqueStr; private final Set protectedRange; private final ExceptionRangeCFG rangeCFG; private Range(BasicBlock handler, String uniqueStr, Set protectedRange, ExceptionRangeCFG rangeCFG) { this.handler = handler; this.uniqueStr = uniqueStr; this.protectedRange = protectedRange; this.rangeCFG = rangeCFG; } } public static void restorePopRanges(ControlFlowGraph graph) { List lstRanges = new ArrayList<>(); // aggregate ranges for (ExceptionRangeCFG range : graph.getExceptions()) { boolean found = false; for (Range arr : lstRanges) { if (arr.handler == range.getHandler() && InterpreterUtil.equalObjects(range.getUniqueExceptionsString(), arr.uniqueStr)) { arr.protectedRange.addAll(range.getProtectedRange()); found = true; break; } } if (!found) { // doesn't matter, which range chosen lstRanges.add(new Range(range.getHandler(), range.getUniqueExceptionsString(), new HashSet<>(range.getProtectedRange()), range)); } } // process aggregated ranges for (Range range : lstRanges) { if (range.uniqueStr != null) { BasicBlock handler = range.handler; InstructionSequence seq = handler.getSeq(); Instruction firstinstr; if (seq.length() > 0) { firstinstr = seq.getInstr(0); if (firstinstr.opcode == CodeConstants.opc_pop || firstinstr.opcode == CodeConstants.opc_astore) { Set setrange = new HashSet<>(range.protectedRange); for (Range range_super : lstRanges) { // finally or strict superset if (range != range_super) { Set setrange_super = new HashSet<>(range_super.protectedRange); if (!setrange.contains(range_super.handler) && !setrange_super.contains(handler) && (range_super.uniqueStr == null || setrange_super.containsAll(setrange))) { if (range_super.uniqueStr == null) { setrange_super.retainAll(setrange); } else { setrange_super.removeAll(setrange); } if (!setrange_super.isEmpty()) { BasicBlock newblock = handler; // split the handler if (seq.length() > 1) { newblock = new BasicBlock(++graph.last_id); InstructionSequence newseq = new SimpleInstructionSequence(); newseq.addInstruction(firstinstr.clone(), -1); newblock.setSeq(newseq); graph.getBlocks().addWithKey(newblock, newblock.id); List lstTemp = new ArrayList<>(); lstTemp.addAll(handler.getPreds()); lstTemp.addAll(handler.getPredExceptions()); // replace predecessors for (BasicBlock pred : lstTemp) { pred.replaceSuccessor(handler, newblock); } // replace handler for (ExceptionRangeCFG range_ext : graph.getExceptions()) { if (range_ext.getHandler() == handler) { range_ext.setHandler(newblock); } else if (range_ext.getProtectedRange().contains(handler)) { newblock.addSuccessorException(range_ext.getHandler()); range_ext.getProtectedRange().add(newblock); } } newblock.addSuccessor(handler); if (graph.getFirst() == handler) { graph.setFirst(newblock); } // remove the first pop in the handler seq.removeInstruction(0); } newblock.addSuccessorException(range_super.handler); range_super.rangeCFG.getProtectedRange().add(newblock); handler = range.rangeCFG.getHandler(); seq = handler.getSeq(); } } } } } } } } } public static void insertEmptyExceptionHandlerBlocks(ControlFlowGraph graph) { Set setVisited = new HashSet<>(); for (ExceptionRangeCFG range : graph.getExceptions()) { BasicBlock handler = range.getHandler(); if (setVisited.contains(handler)) { continue; } setVisited.add(handler); BasicBlock emptyblock = new BasicBlock(++graph.last_id); graph.getBlocks().addWithKey(emptyblock, emptyblock.id); // only exception predecessors considered List lstTemp = new ArrayList<>(handler.getPredExceptions()); // replace predecessors for (BasicBlock pred : lstTemp) { pred.replaceSuccessor(handler, emptyblock); } // replace handler for (ExceptionRangeCFG range_ext : graph.getExceptions()) { if (range_ext.getHandler() == handler) { range_ext.setHandler(emptyblock); } else if (range_ext.getProtectedRange().contains(handler)) { emptyblock.addSuccessorException(range_ext.getHandler()); range_ext.getProtectedRange().add(emptyblock); } } emptyblock.addSuccessor(handler); if (graph.getFirst() == handler) { graph.setFirst(emptyblock); } } } public static void removeEmptyRanges(ControlFlowGraph graph) { List lstRanges = graph.getExceptions(); for (int i = lstRanges.size() - 1; i >= 0; i--) { ExceptionRangeCFG range = lstRanges.get(i); boolean isEmpty = true; for (BasicBlock block : range.getProtectedRange()) { if (!block.getSeq().isEmpty()) { isEmpty = false; break; } } if (isEmpty) { for (BasicBlock block : range.getProtectedRange()) { block.removeSuccessorException(range.getHandler()); } lstRanges.remove(i); } } } public static void removeCircularRanges(final ControlFlowGraph graph) { GenericDominatorEngine engine = new GenericDominatorEngine(new IGraph() { @Override public List getReversePostOrderList() { return graph.getReversePostOrder(); } @Override public Set getRoots() { return new HashSet<>(Collections.singletonList(graph.getFirst())); } }); engine.initialize(); List lstRanges = graph.getExceptions(); for (int i = lstRanges.size() - 1; i >= 0; i--) { ExceptionRangeCFG range = lstRanges.get(i); BasicBlock handler = range.getHandler(); List rangeList = range.getProtectedRange(); if (rangeList.contains(handler)) { // TODO: better removing strategy List lstRemBlocks = getReachableBlocksRestricted(range.getHandler(), range, engine); if (lstRemBlocks.size() < rangeList.size() || rangeList.size() == 1) { for (BasicBlock block : lstRemBlocks) { block.removeSuccessorException(handler); rangeList.remove(block); } } if (rangeList.isEmpty()) { lstRanges.remove(i); } } } } private static List getReachableBlocksRestricted(BasicBlock start, ExceptionRangeCFG range, GenericDominatorEngine engine) { List lstRes = new ArrayList<>(); LinkedList stack = new LinkedList<>(); Set setVisited = new HashSet<>(); stack.addFirst(start); while (!stack.isEmpty()) { BasicBlock block = stack.removeFirst(); setVisited.add(block); if (range.getProtectedRange().contains(block) && engine.isDominator(block, start)) { lstRes.add(block); List lstSuccs = new ArrayList<>(block.getSuccs()); lstSuccs.addAll(block.getSuccExceptions()); for (BasicBlock succ : lstSuccs) { if (!setVisited.contains(succ)) { stack.add(succ); } } } } return lstRes; } public static boolean hasObfuscatedExceptions(ControlFlowGraph graph) { Map> mapRanges = new HashMap<>(); for (ExceptionRangeCFG range : graph.getExceptions()) { mapRanges.computeIfAbsent(range.getHandler(), k -> new HashSet<>()).addAll(range.getProtectedRange()); } for (Entry> ent : mapRanges.entrySet()) { Set setEntries = new HashSet<>(); for (BasicBlock block : ent.getValue()) { Set setTemp = new HashSet<>(block.getPreds()); setTemp.removeAll(ent.getValue()); if (!setTemp.isEmpty()) { setEntries.add(block); } } if (!setEntries.isEmpty()) { if (setEntries.size() > 1 /*|| ent.getValue().contains(first)*/) { return true; } } } return false; } public static boolean handleMultipleEntryExceptionRanges(ControlFlowGraph graph) { GenericDominatorEngine engine = new GenericDominatorEngine(new IGraph() { @Override public List getReversePostOrderList() { return graph.getReversePostOrder(); } @Override public Set getRoots() { return new HashSet<>(Collections.singletonList(graph.getFirst())); } }); engine.initialize(); boolean found; while (true) { found = false; boolean splitted = false; for (ExceptionRangeCFG range : graph.getExceptions()) { Set setEntries = getRangeEntries(range); if (setEntries.size() > 1) { // multiple-entry protected range found = true; if (splitExceptionRange(range, setEntries, graph, engine)) { splitted = true; break; } } } if (!splitted) { break; } } return !found; } private static Set getRangeEntries(ExceptionRangeCFG range) { Set setEntries = new HashSet<>(); Set setRange = new HashSet<>(range.getProtectedRange()); for (BasicBlock block : range.getProtectedRange()) { Set setPreds = new HashSet<>(block.getPreds()); setPreds.removeAll(setRange); if (!setPreds.isEmpty()) { setEntries.add(block); } } return setEntries; } private static boolean splitExceptionRange(ExceptionRangeCFG range, Set setEntries, ControlFlowGraph graph, GenericDominatorEngine engine) { for (BasicBlock entry : setEntries) { List lstSubrangeBlocks = getReachableBlocksRestricted(entry, range, engine); if (!lstSubrangeBlocks.isEmpty() && lstSubrangeBlocks.size() < range.getProtectedRange().size()) { // add new range ExceptionRangeCFG subRange = new ExceptionRangeCFG(lstSubrangeBlocks, range.getHandler(), range.getHandlerBytecodeOffset(), range.getExceptionTypes()); graph.getExceptions().add(subRange); // shrink the original range range.getProtectedRange().removeAll(lstSubrangeBlocks); return true; } else { // should not happen DecompilerContext.getLogger().writeMessage("Inconsistency found while splitting protected range", IFernflowerLogger.Severity.WARN); } } return false; } public static void insertDummyExceptionHandlerBlocks(ControlFlowGraph graph, int bytecode_version) { Map> mapRanges = new HashMap<>(); for (ExceptionRangeCFG range : graph.getExceptions()) { mapRanges.computeIfAbsent(range.getHandler(), k -> new HashSet<>()).add(range); } for (Entry> ent : mapRanges.entrySet()) { BasicBlock handler = ent.getKey(); Set ranges = ent.getValue(); if (ranges.size() == 1) { continue; } for (ExceptionRangeCFG range : ranges) { // add some dummy instructions to prevent optimizing away the empty block SimpleInstructionSequence seq = new SimpleInstructionSequence(); seq.addInstruction(Instruction.create(CodeConstants.opc_bipush, false, CodeConstants.GROUP_GENERAL, bytecode_version, new int[]{0}), -1); seq.addInstruction(Instruction.create(CodeConstants.opc_pop, false, CodeConstants.GROUP_GENERAL, bytecode_version, null), -1); BasicBlock dummyBlock = new BasicBlock(++graph.last_id); dummyBlock.setSeq(seq); graph.getBlocks().addWithKey(dummyBlock, dummyBlock.id); // only exception predecessors from this range considered List lstPredExceptions = new ArrayList<>(handler.getPredExceptions()); lstPredExceptions.retainAll(range.getProtectedRange()); // replace predecessors for (BasicBlock pred : lstPredExceptions) { pred.replaceSuccessor(handler, dummyBlock); } // replace handler range.setHandler(dummyBlock); // add common exception edges Set commonHandlers = new HashSet<>(handler.getSuccExceptions()); for (BasicBlock pred : lstPredExceptions) { commonHandlers.retainAll(pred.getSuccExceptions()); } // TODO: more sanity checks? for (BasicBlock commonHandler : commonHandlers) { ExceptionRangeCFG commonRange = graph.getExceptionRange(commonHandler, handler); dummyBlock.addSuccessorException(commonHandler); commonRange.getProtectedRange().add(dummyBlock); } dummyBlock.addSuccessor(handler); } } } }