diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java index 3218b5438..ce83b41ea 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java @@ -553,7 +553,7 @@ public class ABC { bodies = new ArrayList<>(bodies_count); for (int i = 0; i < bodies_count; i++) { ais.newDumpLevel("method_body", "method_body_info"); - MethodBody mb = new MethodBody(null, null, null); // do not create Traits in constructor + MethodBody mb = new MethodBody(this, null, null, null); // do not create Traits in constructor mb.method_info = ais.readU30("method_info"); mb.max_stack = ais.readU30("max_stack"); mb.max_regs = ais.readU30("max_regs"); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java index 5d996a9c4..e154022f5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java @@ -194,7 +194,9 @@ public class ABCInputStream implements AutoCloseable { } private int readU30Internal() throws IOException { - return (int) readU32Internal(); + long u32 = readU32Internal(); + //no bits above bit 30 + return (int) (u32 & 0x3FFFFFFF); } public int readU30(String name) throws IOException { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java index b83a69350..516769410 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java @@ -745,17 +745,59 @@ public class AVM2Code implements Cloneable { return true; } + /** + * Removes nonexistent indices to constants from instruction operands. + * + * @param constants + */ + public void removeWrongIndices(AVM2ConstantPool constants) { + for (AVM2Instruction ins : code) { + for (int i = 0; i < ins.definition.operands.length; i++) { + if (ins.definition.operands[i] == DAT_MULTINAME_INDEX && ins.operands[i] >= constants.constant_multiname.size()) { + ins.operands[i] = 0; + } + if (ins.definition.operands[i] == DAT_DOUBLE_INDEX && ins.operands[i] >= constants.constant_double.size()) { + ins.operands[i] = 0; + } + if (ins.definition.operands[i] == DAT_INT_INDEX && ins.operands[i] >= constants.constant_int.size()) { + ins.operands[i] = 0; + } + if (ins.definition.operands[i] == DAT_UINT_INDEX && ins.operands[i] >= constants.constant_uint.size()) { + ins.operands[i] = 0; + } + if (ins.definition.operands[i] == DAT_STRING_INDEX && ins.operands[i] >= constants.constant_string.size()) { + ins.operands[i] = 0; + } + } + } + } + public AVM2Code(ABCInputStream ais) throws IOException { Map codeMap = new TreeMap<>(); DumpInfo diParent = ais.dumpInfo; List addresses = new ArrayList<>(); + //Handle lookupswitches at the end - they can be invalid. Handle other instruction first so we can decide lookupswitch to be invalid based on other instructions inside it + //Flashplayer does not check casecount in lookupswitch instruction so the instruction can "be" long and over other instructions + List switchAddresses = new ArrayList<>(); + int availableBytes = ais.available(); + for (int i = 0; i < availableBytes; i++) { + codeMap.put((long) i, new AVM2Instruction(i, new NopIns(), new int[]{})); + } + long startPos = ais.getPosition(); addresses.add(startPos); - while (!addresses.isEmpty()) { - long address = addresses.remove(0); - boolean afterExit = false; - if (codeMap.containsKey(address)) { + loopaddr: + while (!addresses.isEmpty() || !switchAddresses.isEmpty()) { + long address; + boolean isSwitch = false; + if (!addresses.isEmpty()) { + address = addresses.remove(0); + } else { + address = switchAddresses.remove(0); + isSwitch = true; + } + if (codeMap.containsKey(address) && !(codeMap.get(address).definition instanceof NopIns)) { continue; } if (address < startPos) // no jump outside block @@ -769,19 +811,51 @@ public class AVM2Code implements Cloneable { long startOffset = ais.getPosition(); int instructionCode = ais.read("instructionCode"); InstructionDefinition instr = instructionSet[instructionCode]; + if (instr instanceof LookupSwitchIns) { + if (!isSwitch) { + switchAddresses.add(startOffset); + continue loopaddr; + } else { + isSwitch = false; + } + } if (di != null) { di.name = instr.instructionName; } if (instr != null) { int[] actualOperands = null; + long beforeSwitchPos = ais.getPosition(); + if (instructionCode == 0x1b) { // switch int firstOperand = ais.readS24("default_offset"); int case_count = ais.readU30("case_count"); - actualOperands = new int[case_count + 3]; - actualOperands[0] = firstOperand; - actualOperands[1] = case_count; - for (int c = 0; c < case_count + 1; c++) { - actualOperands[2 + c] = ais.readS24("actualOperand"); + long afterCasePos = ais.getPosition() + 3 * (case_count + 1); + + boolean invalidSwitch = false; + //If there are already some instructions in the lookupswitch bytes, the lookupswitch is invalid (obfuscation) + for (long a = beforeSwitchPos; a < afterCasePos; a++) { + if (codeMap.containsKey(a) && (!(codeMap.get(a).definition instanceof NopIns))) { + invalidSwitch = true; + break; + } + } + + long totalBytes = ais.getPosition() + ais.available(); + + //If the lookupswitch case_count are larger than available bytes, the lookupswitch is invalid (obfuscation) + if (afterCasePos > totalBytes) { + invalidSwitch = true; + } + + if (invalidSwitch) { + continue loopaddr; + } else { + actualOperands = new int[case_count + 3]; + actualOperands[0] = firstOperand; + actualOperands[1] = case_count; + for (int c = 0; c < case_count + 1; c++) { + actualOperands[2 + c] = ais.readS24("actualOperand"); + } } } else { if (instr.operands.length > 0) { @@ -805,15 +879,37 @@ public class AVM2Code implements Cloneable { } } - if (!afterExit && (instr instanceof IfTypeIns)) { + AVM2Instruction ai = new AVM2Instruction(startOffset, instr, actualOperands); + long endOffset = ais.getPosition(); + + for (long p = startOffset; p < endOffset; p++) { + codeMap.put(p, ai); + } + + ais.endDumpLevel(instr.instructionCode); + + if ((instr instanceof IfTypeIns)) { long target = ais.getPosition() + actualOperands[0]; addresses.add(target); + //addresses.add(ais.getPosition()); } - codeMap.put(startOffset, new AVM2Instruction(startOffset, instr, actualOperands)); - ais.endDumpLevel(instr.instructionCode); - if (instr.isExitInstruction()) { //do not process jumps if there is return/throw instruction - afterExit = true; - } + /*if (instr instanceof JumpIns) { + long target = ais.getPosition() + actualOperands[0]; + addresses.add(target); + continue loopaddr; + } + + if (instr.isExitInstruction()) { //do not process jumps if there is return/throw instruction + continue loopaddr; + } + if (instr instanceof LookupSwitchIns) { + addresses.add(beforeSwitchPos + actualOperands[0]); + + for (int c = 2; c < actualOperands.length; c++) { + addresses.add(beforeSwitchPos + actualOperands[c]); + } + continue loopaddr; + }*/ } else { ais.endDumpLevel(); break; // Unknown instructions are ignored (Some of the obfuscators add unknown instructions) @@ -825,7 +921,13 @@ public class AVM2Code implements Cloneable { ais.endDumpLevelUntil(diParent); } } - code.addAll(codeMap.values()); + AVM2Instruction prev = null; + for (AVM2Instruction ins : codeMap.values()) { + if (prev != ins) { + code.add(ins); + } + prev = ins; + } } public void compact() { @@ -1819,6 +1921,9 @@ public class AVM2Code implements Cloneable { public void fixJumps(MethodBody body) { buildCache(); + if (code.isEmpty()) { + return; + } AVM2Instruction lastInstuction = code.get(code.size() - 1); final long endOffset = lastInstuction.offset + lastInstuction.getBytesLength(); updateOffsets(new OffsetUpdater() { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java index c78c7561e..69c318d9f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java @@ -230,7 +230,7 @@ public class AVM2DeobfuscatorRegisters extends AVM2DeobfuscatorSimple { break; } } - } catch (EmptyStackException | TranslateException | InterruptedException ex) { + } catch (Exception ex) { //ignore } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorSimple.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorSimple.java index 4cd6dfab6..653efad41 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorSimple.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorSimple.java @@ -373,9 +373,7 @@ public class AVM2DeobfuscatorSimple implements SWFDecompilerListener { break; } } - } catch (EmptyStackException | TranslateException | InterruptedException ex) { - //result.idx = -1; - //result.isIf = false; + } catch (Exception ex) { //ignore } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/InstructionDefinition.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/InstructionDefinition.java index 74909950a..a4b3b91a2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/InstructionDefinition.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/InstructionDefinition.java @@ -93,11 +93,14 @@ public class InstructionDefinition implements Serializable { protected FullMultinameAVM2Item resolveMultiname(TranslateStack stack, AVM2ConstantPool constants, int multinameIndex, AVM2Instruction ins) { GraphTargetItem ns = null; GraphTargetItem name = null; - if (constants.getMultiname(multinameIndex).needsName()) { - name = stack.pop(); - } - if (constants.getMultiname(multinameIndex).needsNs()) { - ns = stack.pop(); + if (multinameIndex < constants.constant_multiname.size()) { + if (constants.getMultiname(multinameIndex).needsName()) { + name = stack.pop(); + } + if (constants.getMultiname(multinameIndex).needsNs()) { + ns = stack.pop(); + } + } return new FullMultinameAVM2Item(ins, multinameIndex, name, ns); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java index 3ac417bcb..6619dbf0d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java @@ -1665,7 +1665,7 @@ public class AVM2SourceGenerator implements SourceGenerator { int mindex; if (!isInterface) { - MethodBody mbody = new MethodBody(); + MethodBody mbody = new MethodBody(abc); if (needsActivation) { int slotId = 1; @@ -2146,7 +2146,7 @@ public class AVM2SourceGenerator implements SourceGenerator { Trait[] traitArr = generateTraitsPhase1(null, null, true, localData, commands, si.traits, class_index); generateTraitsPhase2(new ArrayList(), null/*FIXME*/, commands, traitArr, new ArrayList(), localData); MethodInfo mi = new MethodInfo(new int[0], 0, 0, 0, new ValueKind[0], new int[0]); - MethodBody mb = new MethodBody(); + MethodBody mb = new MethodBody(abc); mb.method_info = abc.addMethodInfo(mi); mb.setCode(new AVM2Code()); List mbCode = mb.getCode().code; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java index 0de407f74..e58f49029 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java @@ -86,16 +86,21 @@ public final class MethodBody implements Cloneable { @Internal public transient Throwable convertException; - public MethodBody() { + @Internal + private ABC abc; + + public MethodBody(ABC abc) { this.traits = new Traits(); this.codeBytes = new byte[0]; this.exceptions = new ABCException[0]; + this.abc = abc; } - public MethodBody(Traits traits, byte[] codeBytes, ABCException[] exceptions) { + public MethodBody(ABC abc, Traits traits, byte[] codeBytes, ABCException[] exceptions) { this.traits = traits; this.codeBytes = codeBytes; this.exceptions = exceptions; + this.abc = abc; } public synchronized void setCodeBytes(byte codeBytes[]) { @@ -117,6 +122,7 @@ public final class MethodBody implements Cloneable { try { ABCInputStream ais = new ABCInputStream(new MemoryInputStream(codeBytes)); avm2Code = new AVM2Code(ais); + avm2Code.removeWrongIndices(abc.constants); } catch (UnknownInstructionCode | IOException ex) { avm2Code = new AVM2Code(); logger.log(Level.SEVERE, null, ex); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/traits/TraitClass.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/traits/TraitClass.java index be97762cf..50196a425 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/traits/TraitClass.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/traits/TraitClass.java @@ -289,14 +289,18 @@ public class TraitClass extends Trait implements TraitWithSlot { || (ins.definition instanceof AsTypeIns)) { int m = ins.operands[0]; if (m != 0) { - parseImportsUsagesFromMultiname(abc, imports, uses, abc.constants.getMultiname(m), ignorePackage, fullyQualifiedNames); + if (m < abc.constants.constant_multiname.size()) { + parseImportsUsagesFromMultiname(abc, imports, uses, abc.constants.getMultiname(m), ignorePackage, fullyQualifiedNames); + } } } else { for (int k = 0; k < ins.definition.operands.length; k++) { if (ins.definition.operands[k] == AVM2Code.DAT_MULTINAME_INDEX) { int multinameIndex = ins.operands[k]; - parseUsagesFromMultiname(abc, imports, uses, abc.constants.getMultiname(multinameIndex), ignorePackage, fullyQualifiedNames); + if (multinameIndex < abc.constants.constant_multiname.size()) { + parseUsagesFromMultiname(abc, imports, uses, abc.constants.getMultiname(multinameIndex), ignorePackage, fullyQualifiedNames); + } } } } diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3AssemblerTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3AssemblerTest.java index 4d3082e7b..307713d45 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3AssemblerTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3AssemblerTest.java @@ -80,7 +80,7 @@ public class ActionScript3AssemblerTest extends ActionScriptTestBase { + str + "returnvoid\r\n"; - MethodBody b = new MethodBody(); + MethodBody b = new MethodBody(getABC()); AVM2Code code = ASM3Parser.parse(new StringReader(str), getABC().constants, null, b, new MethodInfo()); b.setCode(code); return b; 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 3494c457b..e12f9589b 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java @@ -80,12 +80,12 @@ public class ActionScript3DeobfuscatorTest extends ActionStript2TestBase { return 0; } }); - MethodBody b = new MethodBody(); + MethodBody b = new MethodBody(abc); AVM2Code code = ASM3Parser.parse(new StringReader(str), abc.constants, null, b, new MethodInfo()); b.setCode(code); new AVM2DeobfuscatorJumps().deobfuscate("test", 0, true, 0, abc, abc.constants, null, new MethodInfo(), b); HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); - code.toASMSource(abc.constants, null, new MethodInfo(), new MethodBody(), ScriptExportMode.PCODE, writer); + code.toASMSource(abc.constants, null, new MethodInfo(), new MethodBody(abc), ScriptExportMode.PCODE, writer); String ret = writer.toString(); return ret.substring(ret.lastIndexOf("code\r\n") + 6); }