From 5a422fd18f6420342e842f2cd61ea1b4a2dda9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=F8=EDk?= Date: Tue, 29 Jan 2013 20:41:49 +0100 Subject: [PATCH] AS3 automatic deobfuscation, better identifiers renaming --- trunk/src/com/jpexs/asdec/abc/ABC.java | 54 +++++---- .../com/jpexs/asdec/abc/avm2/AVM2Code.java | 111 ++++++++++++++---- .../avm2/instructions/AVM2Instruction.java | 6 +- .../instructions/InstructionDefinition.java | 3 +- .../asdec/abc/avm2/treemodel/TreeItem.java | 6 +- .../asdec/abc/gui/ASMSourceEditorPane.java | 3 +- .../asdec/abc/gui/DeobfuscationDialog.java | 54 +++------ .../com/jpexs/asdec/abc/types/MethodBody.java | 32 +++-- trunk/src/com/jpexs/asdec/gui/MainFrame.java | 17 ++- 9 files changed, 168 insertions(+), 118 deletions(-) diff --git a/trunk/src/com/jpexs/asdec/abc/ABC.java b/trunk/src/com/jpexs/asdec/abc/ABC.java index 6e1420000..3848ac603 100644 --- a/trunk/src/com/jpexs/asdec/abc/ABC.java +++ b/trunk/src/com/jpexs/asdec/abc/ABC.java @@ -71,48 +71,48 @@ public class ABC { } public int removeTraps() { - int rem=0; - for(MethodBody body:bodies){ - rem+=body.removeTraps(constants); + int rem = 0; + for (MethodBody body : bodies) { + rem += body.removeTraps(constants); } return rem; } - + public int removeDeadCode() { - int rem=0; - for(MethodBody body:bodies){ - rem+=body.removeDeadCode(constants); + int rem = 0; + for (MethodBody body : bodies) { + rem += body.removeDeadCode(constants); } return rem; } - + public void restoreControlFlow() { - for(MethodBody body:bodies){ + for (MethodBody body : bodies) { body.restoreControlFlow(constants); } } - - public int deobfuscateIdentifiers() { + + public int deobfuscateIdentifiers(HashMap namesMap) { int ret = 0; for (int i = 1; i < instance_info.length; i++) { if (instance_info[i].name_index != 0) { - if (deobfuscateName(constants.constant_multiname[instance_info[i].name_index].name_index, true)) { + if (deobfuscateName(namesMap, constants.constant_multiname[instance_info[i].name_index].name_index, true)) { ret++; } } if (instance_info[i].super_index != 0) { - if (deobfuscateName(constants.constant_multiname[instance_info[i].super_index].name_index, true)) { + if (deobfuscateName(namesMap, constants.constant_multiname[instance_info[i].super_index].name_index, true)) { ret++; } } } for (int i = 1; i < constants.constant_multiname.length; i++) { - if (deobfuscateName(constants.constant_multiname[i].name_index, false)) { + if (deobfuscateName(namesMap, constants.constant_multiname[i].name_index, false)) { ret++; } } for (int i = 1; i < constants.constant_namespace.length; i++) { - if (deobfuscateNameSpace(constants.constant_namespace[i].name_index)) { + if (deobfuscateNameSpace(namesMap, constants.constant_namespace[i].name_index)) { ret++; } } @@ -593,9 +593,9 @@ public class ABC { } public static final String[] reservedWords = { "as", "break", "case", "catch", "class", "const", "continue", "default", "delete", "do", "each", "else", - "extends", "false", "finally", "for", "function", "get","if", "implements", "import", "in", "instanceof", - "interface", "internal", "is", "native", "new", "null","override", "package", "private", "protected", "public", - "return", "set","super", "switch", "this", "throw", "true", "try", "typeof", "use", "var", /*"void",*/ "while", + "extends", "false", "finally", "for", "function", "get", "if", "implements", "import", "in", "instanceof", + "interface", "internal", "is", "native", "new", "null", "override", "package", "private", "protected", "public", + "return", "set", "super", "switch", "this", "throw", "true", "try", "typeof", "use", "var", /*"void",*/ "while", "with", "dynamic", "default", "final", "in"}; public static final String validFirstCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; public static final String validNextCharacters = validFirstCharacters + "0123456789"; @@ -657,7 +657,7 @@ public class ABC { return false; } - public boolean deobfuscateNameSpace(int strIndex) { + public boolean deobfuscateNameSpace(HashMap namesMap, int strIndex) { if (strIndex <= 0) { return false; } @@ -680,12 +680,17 @@ public class ABC { isValid = false; } if (!isValid) { - constants.constant_string[strIndex] = fooString(constants.constant_string[strIndex], false, DEFAULT_FOO_SIZE); + if (namesMap.containsKey(s)) { + constants.constant_string[strIndex] = namesMap.get(s); + } else { + constants.constant_string[strIndex] = fooString(constants.constant_string[strIndex], false, DEFAULT_FOO_SIZE); + namesMap.put(s, constants.constant_string[strIndex]); + } } return !isValid; } - public boolean deobfuscateName(int strIndex, boolean firstUppercase) { + public boolean deobfuscateName(HashMap namesMap, int strIndex, boolean firstUppercase) { if (strIndex <= 0) { return false; } @@ -710,7 +715,12 @@ public class ABC { } if (!isValid) { - constants.constant_string[strIndex] = fooString(constants.constant_string[strIndex], firstUppercase, DEFAULT_FOO_SIZE); + if (namesMap.containsKey(s)) { + constants.constant_string[strIndex] = namesMap.get(s); + } else { + constants.constant_string[strIndex] = fooString(constants.constant_string[strIndex], firstUppercase, DEFAULT_FOO_SIZE); + namesMap.put(s, constants.constant_string[strIndex]); + } } return !isValid; } diff --git a/trunk/src/com/jpexs/asdec/abc/avm2/AVM2Code.java b/trunk/src/com/jpexs/asdec/abc/avm2/AVM2Code.java index cbda1becc..0d1f2800f 100644 --- a/trunk/src/com/jpexs/asdec/abc/avm2/AVM2Code.java +++ b/trunk/src/com/jpexs/asdec/abc/avm2/AVM2Code.java @@ -61,7 +61,7 @@ import java.util.regex.Pattern; import javax.swing.text.Highlighter; import com.jpexs.asdec.abc.avm2.instructions.TagInstruction; -public class AVM2Code { +public class AVM2Code implements Serializable { private static final boolean DEBUG_MODE = false; public static int toSourceLimit = -1; @@ -708,6 +708,10 @@ public class AVM2Code { } public String toASMSource(ConstantPool constants, MethodBody body) { + return toASMSource(constants, body, new ArrayList()); + } + + public String toASMSource(ConstantPool constants, MethodBody body, List outputMap) { String ret = ""; for (int e = 0; e < body.exceptions.length; e++) { ret += "exception " + e + " m[" + body.exceptions[e].name_index + "]\"" + Helper.escapeString(body.exceptions[e].getVarName(constants, new ArrayList())) + "\" " @@ -718,6 +722,7 @@ public class AVM2Code { offsets.addAll(ins.getOffsets()); } long ofs = 0; + int ip = 0; for (AVM2Instruction ins : code) { if (ins.labelname != null) { ret += ins.labelname + ":"; @@ -736,28 +741,33 @@ public class AVM2Code { } } if (ins.replaceWith != null) { - for (AVM2Instruction ins2 : ins.replaceWith) { - if (ins2.isIgnored()) { - continue; - } - if (ins2.definition instanceof TagInstruction) { - if (ins2.definition.instructionName.equals("appendjump")) { - ret += "jump ofs" + Helper.formatAddress(pos2adr(ins2.operands[0])) + "\n"; + for (Object o : ins.replaceWith) { + if (o instanceof Integer) { + AVM2Instruction ins2 = code.get((Integer) o); + if (ins2.isIgnored()) { + continue; } - if (ins2.definition.instructionName.equals("mark")) { - ret += "ofs" + Helper.formatAddress(pos2adr(ins2.operands[0])) + ":"; - } - } else { ret += Highlighting.hilighOffset("", ofs) + ins2.toStringNoAddress(constants, new ArrayList()) + "\n"; + outputMap.add((Integer) o); + } else if (o instanceof ControlFlowTag) { + ControlFlowTag cft = (ControlFlowTag) o; + if (cft.name.equals("appendjump")) { + ret += "jump ofs" + Helper.formatAddress(pos2adr(cft.value)) + "\n"; + outputMap.add(-1); + } + if (cft.name.equals("mark")) { + ret += "ofs" + Helper.formatAddress(pos2adr(cft.value)) + ":"; + } } } } else { if (!ins.isIgnored()) { ret += Highlighting.hilighOffset("", ofs) + ins.toStringNoAddress(constants, new ArrayList()) + "\n"; + outputMap.add(ip); } } ofs += ins.getBytes().length; - + ip++; } return ret; @@ -2622,8 +2632,19 @@ public class AVM2Code { return ret; } - public void restoreControlFlow(int ip, HashMap> refs, int visited2[], HashMap> appended) throws ConvertException { - List buf = new ArrayList(); + private static class ControlFlowTag { + + public String name; + public int value; + + public ControlFlowTag(String name, int value) { + this.name = name; + this.value = value; + } + } + + public void restoreControlFlow(int ip, HashMap> refs, int visited2[], HashMap appended) throws ConvertException { + List buf = new ArrayList(); boolean cont = false; int continueip = 0; AVM2Instruction prev = null; @@ -2632,7 +2653,7 @@ public class AVM2Code { if ((refs.containsKey(ip) && refs.get(ip).size() > 1) || (visited2[ip] > 0)) { if (cont) { - buf.add(new AVM2Instruction(0, new TagInstruction("appendjump"), new int[]{ip}, new byte[0])); + buf.add(new ControlFlowTag("appendjump", ip)); } cont = false; if (visited2[ip] > 0) { @@ -2643,7 +2664,7 @@ public class AVM2Code { if (ins.definition instanceof LookupSwitchIns) { if (cont) { - buf.add(new AVM2Instruction(0, new JumpIns(), new int[]{ip}, new byte[0])); + buf.add(new ControlFlowTag("appendjump", ip)); } cont = false; restoreControlFlow(adr2pos(pos2adr(ip) + ins.operands[0]), refs, visited2, appended); @@ -2672,11 +2693,11 @@ public class AVM2Code { } } refs.get(newip).clear(); - buf.add(new AVM2Instruction(0, new TagInstruction("mark"), new int[]{newip}, new byte[0])); + buf.add(new ControlFlowTag("mark", newip)); cont = true; } else { if (cont) { - buf.add(new AVM2Instruction(0, new TagInstruction("appendjump"), new int[]{ip}, new byte[0])); + buf.add(new ControlFlowTag("appendjump", ip)); } cont = false; } @@ -2685,17 +2706,17 @@ public class AVM2Code { } else if (ins.definition instanceof IfTypeIns) { int newip = adr2pos(pos2adr(ip + 1) + ins.operands[0]); if (cont) { - buf.add(new AVM2Instruction(0, new TagInstruction("appendjump"), new int[]{ip}, new byte[0])); + buf.add(new ControlFlowTag("appendjump", ip)); } cont = false; restoreControlFlow(newip, refs, visited2, appended); } else if ((ins.definition instanceof ReturnVoidIns) || (ins.definition instanceof ReturnValueIns) || (ins.definition instanceof ThrowIns)) { if (cont) { - buf.add(ins); + buf.add(ip); } break; } else if (cont) { - buf.add(ins); + buf.add(ip); } prev = ins; } @@ -2707,7 +2728,7 @@ public class AVM2Code { HashMap> refs = new HashMap>(); int visited2[] = new int[code.size()]; visitCode(body, refs); - HashMap> appended = new HashMap>(); + HashMap appended = new HashMap(); if (secondpass) { restoreControlFlow(code.size() - 1, refs, visited2, appended); } else { @@ -2726,9 +2747,24 @@ public class AVM2Code { } } catch (ConvertException cex) { } + invalidateCache(); try { - String src = Highlighting.stripHilights(toASMSource(constants, body)); - AVM2Code acode = ASM3Parser.parse(new ByteArrayInputStream(src.getBytes()), constants, null, body); + List outputMap = new ArrayList(); + String src = Highlighting.stripHilights(toASMSource(constants, body, outputMap)); + AVM2Code acode = ASM3Parser.parse(new ByteArrayInputStream(src.getBytes()), constants, null, body); + for (int i = 0; i < acode.code.size(); i++) { + if (outputMap.size() > i) { + int tpos = outputMap.get(i); + if(tpos==-1){ + + }else if (code.get(tpos).mappedOffset >= 0) { + acode.code.get(i).mappedOffset = code.get(tpos).mappedOffset; + } else { + acode.code.get(i).mappedOffset = pos2adr(tpos); + } + + } + } this.code = acode.code; } catch (IOException ex) { Logger.getLogger(AVM2Code.class.getName()).log(Level.SEVERE, null, ex); @@ -2773,4 +2809,29 @@ public class AVM2Code { } return cnt; } + + public void markMappedOffsets(){ + int ofs=0; + for(int i=0;i replaceWith; + public List replaceWith; } diff --git a/trunk/src/com/jpexs/asdec/abc/avm2/instructions/InstructionDefinition.java b/trunk/src/com/jpexs/asdec/abc/avm2/instructions/InstructionDefinition.java index a93aa374f..8d43a3895 100644 --- a/trunk/src/com/jpexs/asdec/abc/avm2/instructions/InstructionDefinition.java +++ b/trunk/src/com/jpexs/asdec/abc/avm2/instructions/InstructionDefinition.java @@ -24,11 +24,12 @@ import com.jpexs.asdec.abc.avm2.treemodel.FullMultinameTreeItem; import com.jpexs.asdec.abc.avm2.treemodel.TreeItem; import com.jpexs.asdec.abc.types.MethodInfo; import com.jpexs.asdec.helpers.Highlighting; +import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Stack; -public class InstructionDefinition { +public class InstructionDefinition implements Serializable{ protected String hilighOffset(String text, long offset) { return Highlighting.hilighOffset(text, offset); diff --git a/trunk/src/com/jpexs/asdec/abc/avm2/treemodel/TreeItem.java b/trunk/src/com/jpexs/asdec/abc/avm2/treemodel/TreeItem.java index d7f2c8e8e..19d486264 100644 --- a/trunk/src/com/jpexs/asdec/abc/avm2/treemodel/TreeItem.java +++ b/trunk/src/com/jpexs/asdec/abc/avm2/treemodel/TreeItem.java @@ -68,7 +68,11 @@ public abstract class TreeItem { if (instruction == null) { return str; } - return Highlighting.hilighOffset(str, instruction.offset); + if(instruction.mappedOffset>=0){ + return Highlighting.hilighOffset(str, instruction.mappedOffset); + }else{ + return Highlighting.hilighOffset(str, instruction.offset); + } } public boolean isFalse() { diff --git a/trunk/src/com/jpexs/asdec/abc/gui/ASMSourceEditorPane.java b/trunk/src/com/jpexs/asdec/abc/gui/ASMSourceEditorPane.java index 69ab72ab9..db90376bd 100644 --- a/trunk/src/com/jpexs/asdec/abc/gui/ASMSourceEditorPane.java +++ b/trunk/src/com/jpexs/asdec/abc/gui/ASMSourceEditorPane.java @@ -102,7 +102,8 @@ public class ASMSourceEditorPane extends LineMarkedEditorPane implements CaretLi @Override public void setText(String t) { disassembledHilights = Highlighting.getInstrHighlights(t); - super.setText(Highlighting.stripHilights(t)); + t=Highlighting.stripHilights(t); + super.setText(t); } public void verify(ConstantPool constants, ABC abc) { diff --git a/trunk/src/com/jpexs/asdec/abc/gui/DeobfuscationDialog.java b/trunk/src/com/jpexs/asdec/abc/gui/DeobfuscationDialog.java index ed2d6bc8b..c3ad3ccbd 100644 --- a/trunk/src/com/jpexs/asdec/abc/gui/DeobfuscationDialog.java +++ b/trunk/src/com/jpexs/asdec/abc/gui/DeobfuscationDialog.java @@ -41,11 +41,9 @@ import javax.swing.JSlider; */ public class DeobfuscationDialog extends JDialog implements ActionListener { - public JCheckBox renameIdentifiersCheckbox = new JCheckBox("Rename identifiers"); public JCheckBox processAllCheckbox = new JCheckBox("Process all classes"); public JSlider codeProcessingLevel; public boolean ok = false; - public static final int LEVEL_NONE = 0; public static final int LEVEL_REMOVE_DEAD_CODE = 1; public static final int LEVEL_REMOVE_TRAPS = 2; public static final int LEVEL_RESTORE_CONTROL_FLOW = 3; @@ -53,19 +51,19 @@ public class DeobfuscationDialog extends JDialog implements ActionListener { public DeobfuscationDialog() { setDefaultCloseOperation(HIDE_ON_CLOSE); setSize(new Dimension(300, 270)); - setTitle("About"); -Container cp = getContentPane(); + setTitle("PCode deobfuscation"); + Container cp = getContentPane(); cp.setLayout(null); - codeProcessingLevel = new JSlider(JSlider.VERTICAL, 0, 3, 3); + codeProcessingLevel = new JSlider(JSlider.VERTICAL, 1, 3, 3); codeProcessingLevel.setMajorTickSpacing(1); codeProcessingLevel.setPaintTicks(true); codeProcessingLevel.setMinorTickSpacing(1); - codeProcessingLevel.setSnapToTicks(true); - JLabel lab1=new JLabel("Code deobfuscation level:"); - lab1.setBounds(30, 0, getWidth()-60, 25); + codeProcessingLevel.setSnapToTicks(true); + JLabel lab1 = new JLabel("Code deobfuscation level:"); + lab1.setBounds(30, 0, getWidth() - 60, 25); cp.add(lab1); Hashtable labelTable = new Hashtable(); - labelTable.put(new Integer(LEVEL_NONE), new JLabel("None")); + //labelTable.put(new Integer(LEVEL_NONE), new JLabel("None")); labelTable.put(new Integer(LEVEL_REMOVE_DEAD_CODE), new JLabel("Remove dead code")); labelTable.put(new Integer(LEVEL_REMOVE_TRAPS), new JLabel("Remove traps")); labelTable.put(new Integer(LEVEL_RESTORE_CONTROL_FLOW), new JLabel("Restore control flow")); @@ -75,42 +73,22 @@ Container cp = getContentPane(); codeProcessingLevel.setAlignmentX(Component.CENTER_ALIGNMENT); codeProcessingLevel.setSize(300, 200); - renameIdentifiersCheckbox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (renameIdentifiersCheckbox.isSelected()) { - processAllCheckbox.setSelected(true); - } - } - }); - - processAllCheckbox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if(!processAllCheckbox.isSelected()){ - renameIdentifiersCheckbox.setSelected(false); - } - } - }); - codeProcessingLevel.setBounds(30,25,getWidth()-60,125); - + codeProcessingLevel.setBounds(30, 25, getWidth() - 60, 125); + add(codeProcessingLevel); - processAllCheckbox.setBounds(50, 150, getWidth()-100, 25); + processAllCheckbox.setBounds(50, 150, getWidth() - 100, 25); add(processAllCheckbox); - renameIdentifiersCheckbox.setBounds(50, 175, getWidth()-100, 25); - add(renameIdentifiersCheckbox); - + processAllCheckbox.setSelected(true); - renameIdentifiersCheckbox.setSelected(true); - + JButton cancelButton = new JButton("Cancel"); cancelButton.addActionListener(this); cancelButton.setActionCommand("CANCEL"); JButton okButton = new JButton("OK"); okButton.addActionListener(this); - okButton.setActionCommand("OK"); + okButton.setActionCommand("OK"); okButton.setBounds(50, 200, 75, 25); cancelButton.setBounds(145, 200, 75, 25); cp.add(okButton); @@ -134,11 +112,9 @@ Container cp = getContentPane(); @Override public void setVisible(boolean b) { - if(b){ - ok=false; + if (b) { + ok = false; } super.setVisible(b); } - - } diff --git a/trunk/src/com/jpexs/asdec/abc/types/MethodBody.java b/trunk/src/com/jpexs/asdec/abc/types/MethodBody.java index 480b0ae0b..0a0af613b 100644 --- a/trunk/src/com/jpexs/asdec/abc/types/MethodBody.java +++ b/trunk/src/com/jpexs/asdec/abc/types/MethodBody.java @@ -27,6 +27,8 @@ import com.jpexs.asdec.helpers.Highlighting; import java.util.HashMap; import java.util.List; import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; public class MethodBody implements Cloneable { @@ -47,15 +49,16 @@ public class MethodBody implements Cloneable { s += "\r\nCode:\r\n" + code.toString(); return s; } - - public int removeDeadCode(ConstantPool constants){ - return code.removeDeadCode(constants,this); + + public int removeDeadCode(ConstantPool constants) { + return code.removeDeadCode(constants, this); } + public void restoreControlFlow(ConstantPool constants) { code.restoreControlFlow(constants, this); } - - public int removeTraps(ConstantPool constants){ + + public int removeTraps(ConstantPool constants) { return code.removeTraps(constants, this); } @@ -85,19 +88,18 @@ public class MethodBody implements Cloneable { return ret; } - /*public String toString(boolean pcode, boolean isStatic, int classIndex, ABC abc, ConstantPool constants, MethodInfo method_info[], Stack scopeStack, boolean isStaticInitializer) { - return toString(pcode, isStatic, classIndex, abc, constants, method_info, scopeStack, isStaticInitializer,false); - }*/ public String toString(boolean pcode, boolean isStatic, int classIndex, ABC abc, ConstantPool constants, MethodInfo method_info[], Stack scopeStack, boolean isStaticInitializer, boolean hilight, List fullyQualifiedNames, Traits initTraits) { String s = ""; - - //s+="method_info="+method_info+" max_stack="+max_stack+" max_regs="+max_regs+" scope_depth="+scope_depth+" max_scope="+max_scope; - //s+="\r\nCode:\r\n"+ if (pcode) { s += code.toASMSource(constants, this); } else { + AVM2Code deobfuscated = null; + deobfuscated = code.deepCopy(); + deobfuscated.markMappedOffsets(); + deobfuscated.removeTraps(constants, this); + deobfuscated.restoreControlFlow(constants, this); try { - s += code.toSource(isStatic, classIndex, abc, constants, method_info, this, hilight, getLocalRegNames(abc), scopeStack, isStaticInitializer, fullyQualifiedNames, initTraits); + s += deobfuscated.toSource(isStatic, classIndex, abc, constants, method_info, this, hilight, getLocalRegNames(abc), scopeStack, isStaticInitializer, fullyQualifiedNames, initTraits); s = s.trim(); if (hilight) { s = Highlighting.hilighMethod(s, this.method_info); @@ -106,12 +108,6 @@ public class MethodBody implements Cloneable { s = "//error:" + ex.toString(); } } - //s+="----------- ORIGINAL ------------\r\n"; - //s+=code.toString(constants); - /*s+="Exceptions:"; - for(int i=0;i namesMap=new HashMap(); for (DoABCTag tag : abcPanel.list) { - cnt += tag.abc.deobfuscateIdentifiers(); + cnt += tag.abc.deobfuscateIdentifiers(namesMap); } Main.stopWork(); JOptionPane.showMessageDialog(null, "Identifiers renamed: " + cnt); @@ -878,9 +880,6 @@ public class MainFrame extends JFrame implements ActionListener { protected Object doInBackground() throws Exception { if (deobfuscationDialog.processAllCheckbox.isSelected()) { for (DoABCTag tag : abcPanel.list) { - if (deobfuscationDialog.renameIdentifiersCheckbox.isSelected()) { - tag.abc.deobfuscateIdentifiers(); - } if (deobfuscationDialog.codeProcessingLevel.getValue() == DeobfuscationDialog.LEVEL_REMOVE_DEAD_CODE) { tag.abc.removeDeadCode(); } else if (deobfuscationDialog.codeProcessingLevel.getValue() == DeobfuscationDialog.LEVEL_REMOVE_TRAPS) {