diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorGroupParts.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorGroupParts.java new file mode 100644 index 000000000..6677f04b1 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorGroupParts.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2010-2021 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.avm2.deobfuscation; + +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import com.jpexs.decompiler.flash.abc.avm2.instructions.IfTypeIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.LookupSwitchIns; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Stub for deobfuscator merging jump parts + * + * @author JPEXS + */ +public class AVM2DeobfuscatorGroupParts extends SWFDecompilerAdapter { + + /* + blk_1 + jump A + B: + blk_2 + jump C +A: blk_3 + jump B +C: blk_4 + +=> + jump A +A: jump B +B: jump C + blk_1 + blk_3 + blk_2 + blk_4 + +---------------------- + + blk_1 + jump A + B: + blk_2 + jump C +A: blk_3 + if B + blk_5 +C: blk_4 + +=> + jump A +B: + blk_2 + jump C +A: blk_1 + blk_3 + if B + blk_5 +C: + blk_4 + +---------------------- + blk_1 + jump A + B: + blk_2 + if C +A: blk_3 + jump B +C: blk_4 + +=> + + blk_1 + jump A + B: + blk_3 + blk_2 + if C +A: jump B +C: blk_4 + + */ + @Override + public void avm2CodeRemoveTraps(String path, int classIndex, boolean isStatic, int scriptIndex, ABC abc, Trait trait, int methodInfo, MethodBody body) throws InterruptedException { + AVM2Code code = body.getCode(); + code.removeDeadCode(body); + + Map> refs = body.getCode().visitCode(body); + for (int i = 0; i < code.code.size(); i++) { + AVM2Instruction ins = code.code.get(i); + if (ins.definition instanceof JumpIns) { + long targetAddr = ins.getTargetAddress(); + int targetIp = code.adr2pos(targetAddr); + + if (realRefs(refs, targetIp) == 1) { + int startIp = 0; + //find preceeding ip which is start of the "part" + for (int j = i; j >= 0; j--) { + AVM2Instruction startIns = code.code.get(j); + if (j < i && ((startIns.definition instanceof IfTypeIns) || (startIns.definition instanceof LookupSwitchIns))) { + startIp = j + 1; + break; + } else { + long srcAddr = code.pos2adr(j); + boolean exceptionMismatch = false; + for (int e = 0; e < body.exceptions.length; e++) { + boolean sourceMatch = srcAddr >= body.exceptions[e].start && srcAddr < body.exceptions[e].end; + boolean targetMatch = targetAddr >= body.exceptions[e].start && targetAddr < body.exceptions[e].end; + if (sourceMatch != targetMatch) { + exceptionMismatch = true; + break; + } + } + if (exceptionMismatch) { + startIp = j + 1; + break; + } else if (realRefs(refs, j) > 1) { + startIp = j; + break; + } + } + } + if (startIp < i) { //only when there is something to move + List movedInstructions = new ArrayList<>(); + for (int k = startIp; k < i; k++) { + movedInstructions.add(code.code.get(startIp)); + code.removeInstruction(startIp, body); + } + + int newTargetIp = targetIp; + if (targetIp > i) { // forward jump + newTargetIp -= movedInstructions.size(); + } + for (int m = 0; m < movedInstructions.size(); m++) { + code.insertInstruction(newTargetIp + m, movedInstructions.get(m), body); + } + i = -1; + refs = body.getCode().visitCode(body); + } + } + } + } + + new AVM2DeobfuscatorJumps().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); + } + + private int realRefs(Map> refs, int ip) { + int refCount = 0; + for (int r : refs.get(ip)) { + if (r >= 0) { + refCount++; + } + } + return refCount; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorJumps.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorJumps.java index 12ea068d7..cb38e49e0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorJumps.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorJumps.java @@ -69,7 +69,7 @@ public class AVM2DeobfuscatorJumps extends SWFDecompilerAdapter { break; } } - if (!exceptionMismatch) { + if (exceptionMismatch) { continue; } @@ -80,8 +80,22 @@ public class AVM2DeobfuscatorJumps extends SWFDecompilerAdapter { for (int r : refs.get(i)) { if (r >= 0) { //Not Exception start/end AVM2Instruction srcIns = code.code.get(r); + srcAddr = srcIns.getAddress(); if ((srcIns.definition instanceof JumpIns) || ((srcIns.definition instanceof IfTypeIns) && (r != i - 1))) { + + exceptionMismatch = false; + for (int e = 0; e < body.exceptions.length; e++) { + boolean sourceMatch = srcAddr >= body.exceptions[e].start && srcAddr < body.exceptions[e].end; + boolean targetMatch = targetAddr >= body.exceptions[e].start && targetAddr < body.exceptions[e].end; + if (sourceMatch != targetMatch) { + exceptionMismatch = true; + break; + } + } + if (exceptionMismatch) { + continue; + } int oldop = srcIns.operands[0]; srcIns.operands[0] = (int) (targetAddr - (srcIns.getAddress() + srcIns.getBytesLength())); if (srcIns.operands[0] != oldop) { diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java index 40d9b9809..0d63f9333 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorGroupParts; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorJumps; import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.ASM3Parser; @@ -31,6 +32,7 @@ import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.helpers.CodeFormatting; import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.graph.CompilationException; import java.io.BufferedInputStream; @@ -64,7 +66,7 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { swf = new SWF(new BufferedInputStream(new FileInputStream("testdata/as3/as3.swf")), false); } - private String recompilePCode(String str) throws IOException, AVM2ParseException, InterruptedException { + private String recompilePCode(String str, SWFDecompilerAdapter deobfuscator) throws IOException, AVM2ParseException, InterruptedException { str = "code\r\n" + "getlocal0\r\n" + "pushscope\r\n" @@ -89,7 +91,7 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { MethodBody b = new MethodBody(abc, new Traits(), new byte[0], new ABCException[0]); AVM2Code code = ASM3Parser.parse(abc, new StringReader(str), null, b, new MethodInfo()); b.setCode(code); - new AVM2DeobfuscatorJumps().avm2CodeRemoveTraps("test", 0, true, 0, abc, null, 0, b); + deobfuscator.avm2CodeRemoveTraps("test", 0, true, 0, abc, null, 0, b); HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); code.toASMSource(abc, abc.constants, new MethodInfo(), new MethodBody(abc, new Traits(), new byte[0], new ABCException[0]), ScriptExportMode.PCODE, writer); String ret = writer.toString(); @@ -239,7 +241,7 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { + "jump b\r\n" //should not change + "a:jump c\r\n" + "c:pushbyte 4\r\n" - + "b:pushbyte 3\r\n"); + + "b:pushbyte 3\r\n", new AVM2DeobfuscatorJumps()); Assert.assertEquals(res, "getlocal0\r\n" + "pushscope\r\n" + "pushbyte 3\r\n" @@ -253,6 +255,52 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { + "returnvoid\r\n"); } + @Test + public void testGroupParts() throws Exception { + String res = recompilePCode( + "pushbyte 1\r\npop\r\n" + + "jump A\r\n" + + "B:pushbyte 3\r\npop\r\n" + + "jump C\r\n" + + "A:pushbyte 2\r\npop\r\n" + + "jump B\r\n" + + "C:pushbyte 4\r\npop\r\n", new AVM2DeobfuscatorGroupParts()); + Assert.assertEquals(res, "getlocal0\r\n" + + "pushscope\r\n" + + "pushbyte 1\r\n" + + "pop\r\n" + + "pushbyte 2\r\n" + + "pop\r\n" + + "pushbyte 3\r\n" + + "pop\r\n" + + "pushbyte 4\r\n" + + "pop\r\n" + + "returnvoid\r\n"); + } + + @Test + public void testGroupParts2() throws Exception { + String res = recompilePCode( + "pushbyte 1\r\npop\r\n" + + "jump A\r\n" + + "B:pushbyte 3\r\npop\r\n" + + "jump C\r\n" + + "A:pushbyte 2\r\npop\r\n" + + "jump B\r\n" + + "C:pushbyte 4\r\npop\r\n", new AVM2DeobfuscatorGroupParts()); + Assert.assertEquals(res, "getlocal0\r\n" + + "pushscope\r\n" + + "pushbyte 1\r\n" + + "pop\r\n" + + "pushbyte 2\r\n" + + "pop\r\n" + + "pushbyte 3\r\n" + + "pop\r\n" + + "pushbyte 4\r\n" + + "pop\r\n" + + "returnvoid\r\n"); + } + // TODO: JPEXS @Test public void testNotRemoveParams() throws Exception { String res = recompile("function tst(p1,p2){"