/* * Copyright (C) 2010-2016 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.LocalDataArea; import com.jpexs.decompiler.flash.abc.avm2.exceptions.AVM2ExecutionException; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions; import com.jpexs.decompiler.flash.abc.avm2.instructions.DeobfuscatePopIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.IfTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.AddIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.AddIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.DecrementIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.DecrementIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.DivideIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.IncrementIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.IncrementIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.ModuloIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.MultiplyIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.MultiplyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.NegateIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.NegateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.NotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.SubtractIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.SubtractIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitAndIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitOrIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitXorIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.LShiftIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.RShiftIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.URShiftIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.EqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.GreaterEqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.GreaterThanIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.LessEqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.LessThanIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.StrictEqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewArrayIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewFunctionIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewObjectIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocalTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocalTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetPropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.DupIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushByteIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushDoubleIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushFalseIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushIntIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushNullIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushShortIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushStringIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushTrueIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushUndefinedIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.SwapIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceOrConvertTypeIns; import com.jpexs.decompiler.flash.abc.types.MethodBody; import com.jpexs.decompiler.flash.abc.types.traits.Trait; import com.jpexs.decompiler.flash.ecma.NotCompileTime; import com.jpexs.decompiler.flash.ecma.Undefined; import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter; import com.jpexs.decompiler.flash.helpers.collections.FixItemCounterStack; import com.jpexs.decompiler.graph.TranslateException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Stack; /** * * @author JPEXS */ public class AVM2DeobfuscatorSimple extends SWFDecompilerAdapter { private final int executionLimit = 30000; protected boolean removeObfuscationIfs(int classIndex, boolean isStatic, int scriptIndex, ABC abc, MethodBody body, AVM2Instruction inlineIns) throws InterruptedException { AVM2Code code = body.getCode(); if (code.code.isEmpty()) { return false; } Map<Integer, Object> staticRegs = new HashMap<>(); if (inlineIns != null && inlineIns.definition instanceof GetLocalTypeIns) { staticRegs.put(((GetLocalTypeIns) inlineIns.definition).getRegisterId(inlineIns), Undefined.INSTANCE); } if (code.code.isEmpty()) { return false; } FixItemCounterStack stack = new FixItemCounterStack(); LocalDataArea localData = new LocalDataArea(); localData.operandStack = stack; int localReservedCount = body.getLocalReservedCount(); for (int i = 0; i < code.code.size(); i++) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } localData.clear(); initLocalRegs(localData, localReservedCount, body.max_regs, i == 0); if (executeInstructions(staticRegs, body, abc, code, localData, i, code.code.size() - 1, null, inlineIns)) { code.removeDeadCode(body); i--; } } return false; } protected boolean removeZeroJumps(AVM2Code code, MethodBody body) throws InterruptedException { boolean result = false; for (int i = 0; i < code.code.size(); i++) { AVM2Instruction ins = code.code.get(i); if (ins.definition instanceof JumpIns) { if (ins.operands[0] == 0) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } code.removeInstruction(i, body); i--; result = true; } } } return result; } protected void initLocalRegs(LocalDataArea localData, int localReservedCount, int maxRegs, boolean executeFromFirst) { for (int i = 0; i < localReservedCount; i++) { localData.localRegisters.put(i, NotCompileTime.INSTANCE); } for (int i = localReservedCount; i < maxRegs; i++) { localData.localRegisters.put(i, executeFromFirst ? Undefined.INSTANCE : NotCompileTime.INSTANCE); } } private boolean executeInstructions(Map<Integer, Object> staticRegs, MethodBody body, ABC abc, AVM2Code code, LocalDataArea localData, int idx, int endIdx, ExecutionResult result, AVM2Instruction inlineIns) throws InterruptedException { int instructionsProcessed = 0; FixItemCounterStack stack = (FixItemCounterStack) localData.operandStack; Set<Long> refs = code.getImportantOffsets(body, false); boolean modified = false; while (true) { if (idx > endIdx) { break; } if (instructionsProcessed > executionLimit) { break; } AVM2Instruction ins = code.code.get(idx); if (instructionsProcessed > 0 && refs.contains(ins.getAddress())) { break; } modified = modified | code.inlineJumpExit(); InstructionDefinition def = ins.definition; if (inlineIns == ins) { if (def instanceof SetLocalTypeIns) { int regId = ((SetLocalTypeIns) def).getRegisterId(ins); staticRegs.put(regId, localData.localRegisters.get(regId)); code.replaceInstruction(idx, new AVM2Instruction(0, DeobfuscatePopIns.getInstance(), null), body); modified = true; } } if (def instanceof GetLocalTypeIns) { int regId = ((GetLocalTypeIns) def).getRegisterId(ins); if (staticRegs.containsKey(regId)) { AVM2Instruction pushins = abc.constants.makePush(staticRegs.get(regId)); if (pushins == null) { break; } code.replaceInstruction(idx, pushins, body); modified = true; ins = pushins; def = ins.definition; } } if (def instanceof NewFunctionIns && idx + 1 < code.code.size() && code.code.get(idx + 1).definition instanceof PopIns) { code.removeInstruction(idx + 1, body); code.removeInstruction(idx, body); modified = true; continue; } boolean ok = false; // todo: honfika: order by statistics if (def.isNotCompileTimeSupported() || def instanceof PushByteIns || def instanceof PushShortIns || def instanceof PushIntIns || def instanceof PushDoubleIns || def instanceof PushStringIns || def instanceof PushNullIns || def instanceof PushUndefinedIns || def instanceof PushFalseIns || def instanceof PushTrueIns || def instanceof DupIns || def instanceof SwapIns || def instanceof AddIns || def instanceof AddIIns || def instanceof SubtractIns || def instanceof SubtractIIns || def instanceof ModuloIns || def instanceof MultiplyIns || def instanceof MultiplyIIns// || def instanceof DivideIns// || def instanceof BitAndIns || def instanceof BitXorIns || def instanceof BitOrIns || def instanceof LShiftIns || def instanceof RShiftIns || def instanceof URShiftIns || def instanceof EqualsIns || def instanceof NotIns || def instanceof NegateIns// || def instanceof NegateIIns// || def instanceof IncrementIns// || def instanceof IncrementIIns// || def instanceof DecrementIns// || def instanceof DecrementIIns // || def instanceof IfTypeIns || def instanceof JumpIns || def instanceof EqualsIns || def instanceof LessEqualsIns || def instanceof GreaterEqualsIns || def instanceof GreaterThanIns || def instanceof LessThanIns || def instanceof StrictEqualsIns || def instanceof PopIns || def instanceof GetLocalTypeIns || def instanceof SetLocalTypeIns || def instanceof NewFunctionIns || def instanceof NewArrayIns || def instanceof NewObjectIns || def instanceof GetPropertyIns || def instanceof CoerceOrConvertTypeIns) { ok = true; } if (!ok) { break; } if (!(def instanceof NewFunctionIns)) { // do not throw EmptyStackException, much faster int requiredStackSize = def.getStackPopCount(ins, abc); if (stack.size() < requiredStackSize) { break; } if (requiredStackSize > 0 && !def.isNotCompileTimeSupported()) { boolean notCompileTime = false; for (int i = 0; i < requiredStackSize; i++) { if (stack.peek(i + 1) == NotCompileTime.INSTANCE) { notCompileTime = true; break; } } if (notCompileTime) { break; } } if (localData.scopeStack.size() < -def.getScopeStackDelta(ins, abc)) { break; } boolean supported; try { localData.jump = null; supported = def.execute(localData, abc.constants, ins); } catch (AVM2ExecutionException ex) { supported = false; } if (!supported) { break; } } boolean ifed = false; if (def instanceof IfTypeIns && !(def instanceof JumpIns)) { long address = ins.getTargetAddress(); int nidx = code.adr2pos(address); AVM2Instruction tarIns = code.code.get(nidx); //Some IfType instructions need more than 1 operand, we must pop out all of them int stackCount = -def.getStackDelta(ins, abc); if (localData.jump != null) { //System.err.println("replacing " + ins + " on " + idx + " with jump"); AVM2Instruction jumpIns = new AVM2Instruction(0, AVM2Instructions.Jump, new int[]{0}); //jumpIns.operands[0] = ins.operands[0] /*- ins.getBytes().length*/ + jumpIns.getBytes().length; code.replaceInstruction(idx, jumpIns, body); jumpIns.operands[0] = (int) (tarIns.getAddress() - jumpIns.getAddress() - jumpIns.getBytesLength()); for (int s = 0; s < stackCount; s++) { code.insertInstruction(idx, new AVM2Instruction(ins.getAddress(), DeobfuscatePopIns.getInstance(), null), true, body); } idx = code.adr2pos(jumpIns.getTargetAddress()); } else { //System.err.println("replacing " + ins + " on " + idx + " with pop"); code.replaceInstruction(idx, new AVM2Instruction(ins.getAddress(), DeobfuscatePopIns.getInstance(), null), body); for (int s = 1 /*first is replaced*/; s < stackCount; s++) { code.insertInstruction(idx, new AVM2Instruction(ins.getAddress(), DeobfuscatePopIns.getInstance(), null), true, body); } //ins.definition = DeobfuscatePopIns.getInstance(); idx++; } modified = true; ifed = true; } else { idx++; } instructionsProcessed++; if (result != null && stack.allItemsFixed()) { result.idx = idx == code.code.size() ? idx - 1 : idx; result.instructionsProcessed = instructionsProcessed; result.stack.clear(); result.stack.addAll(stack); } if (ifed) { break; } if (localData.jump != null) { idx = code.adr2pos(localData.jump); if (idx == -1) { throw new TranslateException("Jump target not found: " + localData.jump); } } } return modified; } @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); removeObfuscationIfs(classIndex, isStatic, scriptIndex, abc, body, null); removeZeroJumps(code, body); } class ExecutionResult { public int idx = -1; public int instructionsProcessed = -1; public Stack<Object> stack = new Stack<>(); } }