Handling some cases of obfuscated exception ranges

master
upnotes 6 years ago
parent c3ff7141ab
commit 95cefbcfd2
  1. 4
      src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java
  2. 146
      src/org/jetbrains/java/decompiler/modules/decompiler/deobfuscator/ExceptionDeobfuscator.java
  3. 2
      test/org/jetbrains/java/decompiler/SingleClassesTest.java
  4. BIN
      testData/classes/pkg/TestNamedSuspendFun2Kt$foo2$1.class
  5. BIN
      testData/classes/pkg/TestNamedSuspendFun2Kt.class
  6. 301
      testData/results/TestNamedSuspendFun2Kt.dec
  7. 14
      testData/src/pkg/TestNamedSuspendFun2.kt

@ -107,6 +107,10 @@ public class MethodProcessorRunnable implements Runnable {
if (ExceptionDeobfuscator.hasObfuscatedExceptions(graph)) {
DecompilerContext.getLogger().writeMessage("Heavily obfuscated exception ranges found!", IFernflowerLogger.Severity.WARN);
if(!ExceptionDeobfuscator.handleMultipleEntryExceptionRanges(graph)) {
DecompilerContext.getLogger().writeMessage("Found multiple entry exception ranges which could not be splitted", IFernflowerLogger.Severity.WARN);
}
ExceptionDeobfuscator.insertDummyExceptionHandlerBlocks(graph, cl.getBytecodeVersion());
}
RootStatement root = DomHelper.parseGraph(graph);

@ -8,6 +8,8 @@ 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;
@ -233,7 +235,7 @@ public class ExceptionDeobfuscator {
if (rangeList.contains(handler)) { // TODO: better removing strategy
List<BasicBlock> lstRemBlocks = getReachableBlocksRestricted(range, engine);
List<BasicBlock> lstRemBlocks = getReachableBlocksRestricted(range.getHandler(), range, engine);
if (lstRemBlocks.size() < rangeList.size() || rangeList.size() == 1) {
for (BasicBlock block : lstRemBlocks) {
@ -249,22 +251,21 @@ public class ExceptionDeobfuscator {
}
}
private static List<BasicBlock> getReachableBlocksRestricted(ExceptionRangeCFG range, GenericDominatorEngine engine) {
private static List<BasicBlock> getReachableBlocksRestricted(BasicBlock start, ExceptionRangeCFG range, GenericDominatorEngine engine) {
List<BasicBlock> lstRes = new ArrayList<>();
LinkedList<BasicBlock> stack = new LinkedList<>();
Set<BasicBlock> setVisited = new HashSet<>();
BasicBlock handler = range.getHandler();
stack.addFirst(handler);
stack.addFirst(start);
while (!stack.isEmpty()) {
BasicBlock block = stack.removeFirst();
setVisited.add(block);
if (range.getProtectedRange().contains(block) && engine.isDominator(block, handler)) {
if (range.getProtectedRange().contains(block) && engine.isDominator(block, start)) {
lstRes.add(block);
List<BasicBlock> lstSuccs = new ArrayList<>(block.getSuccs());
@ -308,4 +309,139 @@ public class ExceptionDeobfuscator {
return false;
}
public static boolean handleMultipleEntryExceptionRanges(ControlFlowGraph graph) {
GenericDominatorEngine engine = new GenericDominatorEngine(new IGraph() {
public List<? extends IGraphNode> getReversePostOrderList() {
return graph.getReversePostOrder();
}
public Set<? extends IGraphNode> getRoots() {
return new HashSet<>(Collections.singletonList(graph.getFirst()));
}
});
engine.initialize();
boolean found = false;
while(true) {
found = false;
boolean splitted = false;
for(ExceptionRangeCFG range : graph.getExceptions()) {
Set<BasicBlock> 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<BasicBlock> getRangeEntries(ExceptionRangeCFG range) {
Set<BasicBlock> setEntries = new HashSet<>();
Set<BasicBlock> setRange= new HashSet<>(range.getProtectedRange());
for(BasicBlock block : range.getProtectedRange()) {
Set<BasicBlock> setPreds = new HashSet<>(block.getPreds());
setPreds.removeAll(setRange);
if (!setPreds.isEmpty()) {
setEntries.add(block);
}
}
return setEntries;
}
private static boolean splitExceptionRange(ExceptionRangeCFG range, Set<BasicBlock> setEntries, ControlFlowGraph graph, GenericDominatorEngine engine) {
for(BasicBlock entry : setEntries) {
List<BasicBlock> lstSubrangeBlocks = getReachableBlocksRestricted(entry, range, engine);
if(!lstSubrangeBlocks.isEmpty() && lstSubrangeBlocks.size() < range.getProtectedRange().size()) {
// add new range
ExceptionRangeCFG subRange = new ExceptionRangeCFG(lstSubrangeBlocks, range.getHandler(), 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<BasicBlock, Set<ExceptionRangeCFG>> mapRanges = new HashMap<>();
for (ExceptionRangeCFG range : graph.getExceptions()) {
mapRanges.computeIfAbsent(range.getHandler(), k -> new HashSet<>()).add(range);
}
for (Entry<BasicBlock, Set<ExceptionRangeCFG>> ent : mapRanges.entrySet()) {
BasicBlock handler = ent.getKey();
Set<ExceptionRangeCFG> 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<BasicBlock> 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<BasicBlock> 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);
}
}
}
}

@ -113,6 +113,7 @@ public class SingleClassesTest {
@Test public void testPrivateEmptyConstructor() { doTest("pkg/TestPrivateEmptyConstructor"); }
@Test public void testSynchronizedUnprotected() { doTest("pkg/TestSynchronizedUnprotected"); }
@Test public void testInterfaceSuper() { doTest("pkg/TestInterfaceSuper"); }
@Test public void testFieldSingleAccess() { doTest("pkg/TestFieldSingleAccess"); }
// TODO: fix all below
//@Test public void testPackageInfo() { doTest("pkg/package-info"); }
@ -125,6 +126,7 @@ public class SingleClassesTest {
@Test public void testGroovyTrait() { doTest("pkg/TestGroovyTrait"); }
@Test public void testPrivateClasses() { doTest("pkg/PrivateClasses"); }
@Test public void testSuspendLambda() { doTest("pkg/TestSuspendLambdaKt"); }
@Test public void testNamedSuspendFun2Kt() { doTest("pkg/TestNamedSuspendFun2Kt"); }
private void doTest(String testFile, String... companionFiles) {
ConsoleDecompiler decompiler = fixture.getDecompiler();

@ -0,0 +1,301 @@
import kotlin.Metadata;
import kotlin.SuccessOrFailure.Failure;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.coroutines.jvm.internal.ContinuationImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 2,
xi = 2,
d1 = {"\u0000\n\n\u0000\n\u0002\u0010\b\n\u0002\b\u0002\u001a\u0011\u0010\u0000\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u001a\u0011\u0010\u0003\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u0082\u0002\u0004\n\u0002\b\u0019"},
d2 = {"bar", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "foo2"}
)
public final class TestNamedSuspendFun2Kt {
@Nullable
public static final Object foo2(@NotNull Continuation<? super Integer> var0) {
@Metadata(
mv = {1, 1, 11},
bv = {1, 0, 2},
k = 3,
xi = 2,
d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\b\u0010\u0000\u001a\u0004\u0018\u00010\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003H\u0086@ø\u0001\u0000"},
d2 = {"foo2", "", "continuation", "Lkotlin/coroutines/Continuation;", ""}
)
final class NamelessClass_1 extends ContinuationImpl {
int label;
int I$0;
Object L$0;
@Nullable
public final Object invokeSuspend(@NotNull Object result) {
this.data = result;
this.label |= -2147483648;
return TestNamedSuspendFun2Kt.foo2(this);
}
NamelessClass_1(Continuation var1) {
super(var1);
}
}
NamelessClass_1 var3;
label463: {
if (var0 instanceof NamelessClass_1) {
var3 = (NamelessClass_1)var0;
if ((var3.getLabel() & -2147483648) != 0) {
var3.setLabel(var3.getLabel() - -2147483648);
break label463;
}
}
var3 = new NamelessClass_1(var0);
}
Object var4;
int x;
label491: {
Throwable var1;
Throwable var10000;
label472: {
Object var2 = var3.data;
var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();// 2
boolean var10001;
Object var22;
switch(var3.label) {
case 0:
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
break;
case 1:
try {
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
var22 = var2;
} catch (Throwable var19) {
var10000 = var19;
var10001 = false;
break label472;
}
try {
x = ((Number)var22).intValue();// 6
if (x == 0) {
break label491;
}
} catch (Throwable var17) {
var10000 = var17;
var10001 = false;
break label472;
}
var3.label = 3;
if (bar(var3) == var4) {
return var4;
}
break;
case 2:
x = var3.I$0;
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
return 1;// 11
case 3:
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
break;
case 4:
var1 = (Throwable)var3.L$0;
if (var2 instanceof Failure) {
throw ((Failure)var2).exception;
}
throw var1;// 9
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
do {
try {
var3.label = 1;// 5
var22 = bar(var3);
} catch (Throwable var18) {
var10000 = var18;
var10001 = false;
break label472;
}
if (var22 == var4) {
return var4;
}
try {
x = ((Number)var22).intValue();
if (x == 0) {
break label491;
}
} catch (Throwable var20) {
var10000 = var20;
var10001 = false;
break label472;
}
var3.label = 3;
} while(bar(var3) != var4);
return var4;
}
var1 = var10000;
var3.L$0 = var1;
var3.label = 4;
if (bar(var3) == var4) {// 8
return var4;
}
throw var1;
}
var3.I$0 = x;
var3.label = 2;
if (bar(var3) == var4) {
return var4;
} else {
return 1;
}
}
@Nullable
public static final Object bar(@NotNull Continuation<? super Integer> var0) {
return 0;// 14
}
}
class 'TestNamedSuspendFun2Kt$foo2$1' {
method 'invokeSuspend (Ljava/lang/Object;)Ljava/lang/Object;' {
2 34
a 35
d 35
11 36
14 36
}
method '<init> (Lkotlin/coroutines/Continuation;)V' {
2 40
5 41
}
}
class 'TestNamedSuspendFun2Kt' {
method 'foo2 (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;' {
1 46
4 46
8 47
b 47
d 48
10 48
12 48
13 48
18 49
1b 49
1d 49
1e 49
21 50
2c 54
2e 63
31 63
32 64
35 64
38 67
3b 67
5e 69
61 69
64 70
67 70
6a 70
6f 127
70 127
73 128
79 135
7e 136
81 75
84 75
87 76
8a 76
8d 76
90 87
93 87
96 87
98 88
9e 166
a2 167
a3 167
a6 168
ac 168
b1 169
b3 103
b6 103
b9 104
bc 104
bf 105
c2 105
c5 105
c9 108
ce 97
cf 97
d2 98
d8 98
dd 99
e0 110
e3 110
e6 111
e9 111
ec 111
f3 156
f7 157
fb 158
fc 158
ff 159
105 159
10a 160
10c 115
10f 115
112 115
115 116
118 116
11b 117
11e 117
121 117
126 120
12a 108
12b 108
133 122
138 122
}
method 'bar (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;' {
0 177
1 177
4 177
}
}
Lines mapping:
2 <-> 65
5 <-> 128
6 <-> 88
8 <-> 160
9 <-> 121
11 <-> 109
14 <-> 178
Not mapped:
3
4

@ -0,0 +1,14 @@
suspend fun foo2(): Int {
while (true) {
try {
val x = bar()
if (x == 0) break
} finally {
bar()
}
}
return 1
}
suspend fun bar(): Int = 0
Loading…
Cancel
Save