diff --git a/CHANGELOG.md b/CHANGELOG.md index 4acc22565..0d5a79176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- [#2090] Support for Mochicrypt packed binarydata tags - loading SWF as subtree ## [19.0.0] - 2023-10-01 ### Added @@ -3118,6 +3120,7 @@ Major version of SWF to XML export changed to 2. [alpha 9]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha8...alpha9 [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 +[#2090]: https://www.free-decompiler.com/flash/issues/2090 [#1449]: https://www.free-decompiler.com/flash/issues/1449 [#2070]: https://www.free-decompiler.com/flash/issues/2070 [#2073]: https://www.free-decompiler.com/flash/issues/2073 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java new file mode 100644 index 000000000..d3ff980a6 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2010-2023 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.packers; + +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.helpers.Helper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +/** + * + * @author JPEXS + */ +public class MochiCryptPacker implements Packer { + + @Override + public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag) { + if (dataTag.getClassNames().contains("mochicrypt.Payload")) { + return true; + } + return null; + } + + @Override + public boolean decrypt(InputStream is, OutputStream os) throws IOException { + byte payload[] = Helper.readStream(is); + if (!handleXor(payload)) { + return false; + } + Helper.copyStream(new InflaterInputStream(new ByteArrayInputStream(payload)), os); + return true; + } + + private boolean handleXor(byte payload[]) { + if (payload.length < 32) { + return false; + } + int[] S = new int[256]; + int i = 0; + int j; + int k; + int n; + int u; + int v; + + n = payload.length - 32; + while (i < 256) { + S[i] = i; + i++; + } + j = 0; + i = 0; + while (i < 256) { + j = (j + S[i] + (payload[n + (i & 31)] & 0xff) ) & 255; + u = S[i]; + S[i] = S[j]; + S[j] = u; + i++; + } + + if (n > 0x20000) { + n = 0x20000; + } + j = 0; + i = 0; + k = 0; + while (k < n) { + i = (i + 1) & 255; + u = S[i]; + j = (j + u) & 255; + v = S[j]; + S[i] = v; + S[j] = u; + payload[k] = (byte) ((payload[k] & 0xff) ^ S[u + v & 255]); + k++; + } + return true; + } + + @Override + public boolean encrypt(InputStream is, OutputStream os) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeflaterOutputStream def = new DeflaterOutputStream(baos); + Helper.copyStream(is, def); + def.finish(); + byte payload[] = baos.toByteArray(); + + if (!handleXor(payload)) { + return false; + } + + os.write(payload); + + return true; + } + + @Override + public String getName() { + return "MochiCrypt"; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java new file mode 100644 index 000000000..9119b84a3 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010-2023 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.packers; + +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Packer interface. + * @author JPEXS + */ +public interface Packer { + + /** + * Is this DefineBinaryData packed with this packer? + * + * @param dataTag + * @return true = it definitely is encrypted with this, false = it definitely is not encrypted with this, null = it is unknown that it will work + */ + public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag); + + /** + * Unpack the data + * @param is + * @param os + * @return True if it was unpacked correctly, False if it is not suitable for unpacking or an error happened. + * @throws java.io.IOException + */ + public boolean decrypt(InputStream is, OutputStream os) throws IOException; + + /** + * Pack the data + * @param is + * @param os + * @return True if packed successfully, False if error happened. + * @throws java.io.IOException + */ + public boolean encrypt(InputStream is, OutputStream os) throws IOException; + + /** + * Human readable name of this packer + * @return + */ + public String getName(); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java index 366a106a2..ef429d917 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java @@ -22,6 +22,8 @@ import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys; import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; +import com.jpexs.decompiler.flash.packers.MochiCryptPacker; +import com.jpexs.decompiler.flash.packers.Packer; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.Internal; @@ -58,6 +60,13 @@ public class DefineBinaryDataTag extends CharacterTag { @Internal public SWF innerSwf; + @Internal + public Packer usedPacker; + + private final Packer[] PACKERS = { + new MochiCryptPacker() + }; + /** * Constructor * @@ -81,10 +90,10 @@ public class DefineBinaryDataTag extends CharacterTag { binaryData = sis.readByteRangeEx(sis.available(), "binaryData"); if (Configuration.autoLoadEmbeddedSwfs.get()) { - String path = getSwf().getShortPathTitle()+"/DefineBinaryData (" + getCharacterId() + ")"; + String path = getSwf().getShortPathTitle() + "/DefineBinaryData (" + getCharacterId() + ")"; SwfSpecificCustomConfiguration conf = Configuration.getSwfSpecificCustomConfiguration(path); String charset = conf == null ? Charset.defaultCharset().name() : conf.getCustomData(CustomConfigurationKeys.KEY_CHARSET, Charset.defaultCharset().name()); - + try { InputStream is = new ByteArrayInputStream(binaryData.getArray(), binaryData.getPos(), binaryData.getLength()); SWF bswf = new SWF(is, null, "(SWF Data)", Configuration.parallelSpeedUp.get(), charset); @@ -119,6 +128,15 @@ public class DefineBinaryDataTag extends CharacterTag { this.tag = characterId; } + public void detectPacker() { + for (Packer packer : PACKERS) { + if (packer.suitableForBinaryData(this) == Boolean.TRUE) { + usedPacker = packer; + break; + } + } + } + public boolean isSwfData() { try { if (binaryData.getLength() > 8) { @@ -129,8 +147,10 @@ public class DefineBinaryDataTag extends CharacterTag { } } catch (Exception ex) { } - - return false; + + detectPacker(); + + return usedPacker != null; } @Override diff --git a/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java b/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java index 5c34ec738..aa57eff5a 100644 --- a/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java @@ -17,6 +17,8 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.gui.hexview.HexView; +import com.jpexs.decompiler.flash.packers.MochiCryptPacker; +import com.jpexs.decompiler.flash.packers.Packer; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import java.awt.BorderLayout; import java.awt.Color; @@ -44,6 +46,8 @@ public final class BinaryPanel extends JPanel { private DefineBinaryDataTag binaryDataTag = null; private final MainPanel mainPanel; + + private final JLabel swfInsideLabel; public BinaryPanel(final MainPanel mainPanel) { super(new BorderLayout()); @@ -64,10 +68,13 @@ public final class BinaryPanel extends JPanel { setBinaryData(binaryDataTag); } });*/ + + swfInsideLabel = new JLabel(AppStrings.translate("binarydata.swfInside")); + swfInsidePanel = new JPanel(); swfInsidePanel.setBackground(new Color(253, 205, 137)); swfInsidePanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - swfInsidePanel.add(new JLabel(AppStrings.translate("binarydata.swfInside"))); + swfInsidePanel.add(swfInsideLabel); swfInsidePanel.setFocusable(true); swfInsidePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); swfInsidePanel.addMouseListener(new MouseAdapter() { @@ -88,7 +95,15 @@ public final class BinaryPanel extends JPanel { data = binaryDataTag == null ? null : binaryDataTag.binaryData.getRangeData(); if (data != null) { hexEditor.setData(data, null, null); - swfInsidePanel.setVisible(binaryDataTag.innerSwf == null && binaryDataTag.isSwfData()); + boolean isSwfData = binaryDataTag.isSwfData(); + if (isSwfData) { + if (binaryDataTag.usedPacker != null) { + swfInsideLabel.setText(AppStrings.translate("binarydata.swfInside.packer").replace("%packer%", binaryDataTag.usedPacker.getName())); + } else { + swfInsideLabel.setText(AppStrings.translate("binarydata.swfInside")); + } + } + swfInsidePanel.setVisible(binaryDataTag.innerSwf == null && isSwfData); } else { hexEditor.setData(new byte[0], null, null); swfInsidePanel.setVisible(false); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 49f42fd84..480f43439 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -162,7 +162,13 @@ public abstract class MainFrameMenu implements MenuBuilder { try { openable.saveTo(baos); SWF swf = (SWF) openable; - swf.binaryData.binaryData = new ByteArrayRange(baos.toByteArray()); + byte data[] = baos.toByteArray(); + if (swf.binaryData.usedPacker != null) { + ByteArrayOutputStream encBaos = new ByteArrayOutputStream(); + swf.binaryData.usedPacker.encrypt(new ByteArrayInputStream(data), encBaos); + data = encBaos.toByteArray(); + } + swf.binaryData.binaryData = new ByteArrayRange(data); swf.binaryData.setModified(true); saved = saveOpenable(swf.binaryData.getSwf()); //save parent swf } catch (IOException ex) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 65c10be11..d08f4aeb5 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -4978,8 +4978,16 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se try { SwfSpecificCustomConfiguration conf = Configuration.getSwfSpecificCustomConfiguration(path); String charset = conf == null ? Charset.defaultCharset().name() : conf.getCustomData(CustomConfigurationKeys.KEY_CHARSET, Charset.defaultCharset().name()); - InputStream is = new ByteArrayInputStream(binaryDataTag.binaryData.getRangeData()); - SWF bswf = new SWF(is, null, "(SWF Data)", new ProgressListener() { + byte data[] = binaryDataTag.binaryData.getRangeData(); + String packerAdd = ""; + if (binaryDataTag.usedPacker != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + binaryDataTag.usedPacker.decrypt(new ByteArrayInputStream(data), baos); + data = baos.toByteArray(); + packerAdd = " - " + binaryDataTag.usedPacker.getName(); + } + InputStream is = new ByteArrayInputStream(data); + SWF bswf = new SWF(is, null, "(SWF Data" + packerAdd +")", new ProgressListener() { @Override public void progress(int p) { Main.loadingDialog.setPercent(p); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 2d60baea5..de5bd83dd 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1157,4 +1157,7 @@ menu.tools.abcexplorer = ABC Explorer contextmenu.abcexplorer = Explore ABC -button.abcexploretrait = Show trait in ABC Explorer \ No newline at end of file +button.abcexploretrait = Show trait in ABC Explorer + +#after 19.0.0 +binarydata.swfInside.packer = It looks like there is SWF inside this binary data tag packed with %packer%. Click here to unpack the SWF and load it as subtree. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index e8b7e4617..a91af26af 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1142,4 +1142,7 @@ menu.tools.abcexplorer = Pr\u016fzkumn\u00edk ABC contextmenu.abcexplorer = Prozkoumat ABC -button.abcexploretrait = Zobrazit vlastnost v pr\u016fzkumn\u00edku ABC \ No newline at end of file +button.abcexploretrait = Zobrazit vlastnost v pr\u016fzkumn\u00edku ABC + +#after 19.0.0 +binarydata.swfInside.packer = Vypad\u00e1 to, \u017ee uvnit\u0159 v tomto BinaryData tagu se nach\u00e1z\u00ed SWF soubor zabalen\u00fd pomoc\u00ed %packer%. Klikn\u011bte zde pro jeho rozbalen\u00ed a na\u010dten\u00ed jako podstrom.