Handling some cases of obfuscated exception ranges

This commit is contained in:
upnotes
2018-10-03 17:21:01 +02:00
parent c3ff7141ab
commit 95cefbcfd2
7 changed files with 462 additions and 5 deletions

View File

@@ -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);

View File

@@ -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);
}
}
}
}

View File

@@ -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();

Binary file not shown.

View File

@@ -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

View File

@@ -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