diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d79dec81..0b8da0b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. - Harman AIR 51 float support compatibility - FlashDevelop project export - option to export AIR project (select correct type in the file save dialog) - FLA/FlashDevelop/IDEA export - option to add link to all classes (sound, font, images) so no class is missed during compilation +- Harman AIR 51 unpacker for binarydata with custom key ### Fixed - [#2266] StartSound/2 and VideoFrame tags, classNames not taken as dependencies (needed chars) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java index 0497bdf71..424e073d5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java @@ -47,9 +47,22 @@ public class HarmanBinaryDataEncrypt { /** * Encrypts data. * @param data Data to encrypt + * @param key Key * @return Encrypted data */ - public static byte[] encrypt(byte[] data) { + public static byte[] encrypt(byte[] data, String key) { + + byte[] customKey = null; + + if (key != null) { + customKey = new byte[16]; + int keyLen = key.length(); + + for(int i = 0; i < 16; ++i) { + customKey[i] = (byte)key.charAt(i % keyLen); + } + } + byte[] result; try { SecureRandom random = new SecureRandom(); @@ -69,7 +82,7 @@ public class HarmanBinaryDataEncrypt { byte[] ivBytes = getIv(hashBytes, random1, random2); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); - SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + SecretKeySpec keySpec = new SecretKeySpec(customKey != null ? customKey : keyBytes, "AES"); byte[] dataPadded = new byte[(int) encryptedDataLen]; random.nextBytes(dataPadded); System.arraycopy(data, 0, dataPadded, 0, data.length); @@ -153,12 +166,25 @@ public class HarmanBinaryDataEncrypt { /** * Decrypts data. * @param data Encrypted data + * @param key * @return Decrypted data */ - public static byte[] decrypt(byte[] data) { + public static byte[] decrypt(byte[] data, String key) { if (data.length < 32) { return null; } + + byte[] customKey = null; + + if (key != null) { + customKey = new byte[16]; + int keyLen = key.length(); + + for(int i = 0; i < 16; ++i) { + customKey[i] = (byte)key.charAt(i % keyLen); + } + } + long encryptedLen = data.length; long encryptedLenXorHash = unpack(data, 0); long decryptedLenXorRandom1 = unpack(data, 4); @@ -181,7 +207,7 @@ public class HarmanBinaryDataEncrypt { byte[] ivBytes = getIv(hashBytes, random1, random2); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); - SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + SecretKeySpec keySpec = new SecretKeySpec(customKey != null ? customKey : keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); @@ -203,8 +229,8 @@ public class HarmanBinaryDataEncrypt { */ public static void main(String[] args) throws IOException { byte[] data = new byte[]{'A', 'B', 'C'}; - byte[] encrypted = encrypt(data); - byte[] decrypted = decrypt(encrypted); + byte[] encrypted = encrypt(data, null); + byte[] decrypted = decrypt(encrypted, null); if (!Arrays.equals(data, decrypted)) { throw new RuntimeException("Cannot encrypt/decrypt"); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java index 71599ba7f..e9f4793b7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java @@ -47,9 +47,9 @@ public class HarmanAirPacker implements Packer { } @Override - public boolean decrypt(InputStream is, OutputStream os) throws IOException { + public boolean decrypt(InputStream is, OutputStream os, String key) throws IOException { byte[] encryptedData = Helper.readStream(is); - byte[] decryptedData = HarmanBinaryDataEncrypt.decrypt(encryptedData); + byte[] decryptedData = HarmanBinaryDataEncrypt.decrypt(encryptedData, null); if (decryptedData == null) { return false; } @@ -58,9 +58,9 @@ public class HarmanAirPacker implements Packer { } @Override - public boolean encrypt(InputStream is, OutputStream os) throws IOException { + public boolean encrypt(InputStream is, OutputStream os, String key) throws IOException { byte[] data = Helper.readStream(is); - byte[] encryptedData = HarmanBinaryDataEncrypt.encrypt(data); + byte[] encryptedData = HarmanBinaryDataEncrypt.encrypt(data, null); if (encryptedData == null) { return false; } @@ -77,4 +77,9 @@ public class HarmanAirPacker implements Packer { public String getIdentifier() { return "harmanair"; } + + @Override + public boolean usesKey() { + return false; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPackerWithKey.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPackerWithKey.java new file mode 100644 index 000000000..baeb100b7 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPackerWithKey.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010-2024 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.harman.HarmanBinaryDataEncrypt; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Harman AIR SDK packer. + * + * @author JPEXS + */ +public class HarmanAirPackerWithKey implements Packer { + + @Override + public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag) { + if (dataTag.binaryData.getLength() < 32) { + return false; + } + return null; + } + + @Override + public Boolean suitableForData(byte[] data) { + if (data.length < 32) { + return false; + } + return null; + } + + @Override + public boolean decrypt(InputStream is, OutputStream os, String key) throws IOException { + byte[] encryptedData = Helper.readStream(is); + byte[] decryptedData = HarmanBinaryDataEncrypt.decrypt(encryptedData, key); + if (decryptedData == null) { + return false; + } + os.write(decryptedData); + return true; + } + + @Override + public boolean encrypt(InputStream is, OutputStream os, String key) throws IOException { + byte[] data = Helper.readStream(is); + byte[] encryptedData = HarmanBinaryDataEncrypt.encrypt(data, key); + if (encryptedData == null) { + return false; + } + os.write(encryptedData); + return true; + } + + @Override + public String getName() { + return "Harman AIR SDK with custom key"; + } + + @Override + public String getIdentifier() { + return "harmanair_key"; + } + + @Override + public boolean usesKey() { + return true; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker16Bit.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker16Bit.java index 10055f2e4..8c93f7f95 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker16Bit.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker16Bit.java @@ -42,7 +42,7 @@ public class MochiCryptPacker16Bit implements Packer { } @Override - public boolean decrypt(InputStream is, OutputStream os) throws IOException { + public boolean decrypt(InputStream is, OutputStream os, String key) throws IOException { byte[] payload = Helper.readStream(is); if (!handleXor(payload)) { return false; @@ -103,7 +103,7 @@ public class MochiCryptPacker16Bit implements Packer { } @Override - public boolean encrypt(InputStream is, OutputStream os) throws IOException { + public boolean encrypt(InputStream is, OutputStream os, String key) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DeflaterOutputStream def = new DeflaterOutputStream(baos); Helper.copyStreamEx(is, def); @@ -133,4 +133,10 @@ public class MochiCryptPacker16Bit implements Packer { public String getIdentifier() { return "mochicrypt16"; } + + @Override + public boolean usesKey() { + return false; + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker32Bit.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker32Bit.java index 6d7eeca51..e018dbfb7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker32Bit.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker32Bit.java @@ -42,7 +42,7 @@ public class MochiCryptPacker32Bit implements Packer { } @Override - public boolean decrypt(InputStream is, OutputStream os) throws IOException { + public boolean decrypt(InputStream is, OutputStream os, String key) throws IOException { byte[] payload = Helper.readStream(is); if (!handleXor(payload)) { return false; @@ -103,7 +103,7 @@ public class MochiCryptPacker32Bit implements Packer { } @Override - public boolean encrypt(InputStream is, OutputStream os) throws IOException { + public boolean encrypt(InputStream is, OutputStream os, String key) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DeflaterOutputStream def = new DeflaterOutputStream(baos); Helper.copyStreamEx(is, def); @@ -133,4 +133,9 @@ public class MochiCryptPacker32Bit implements Packer { public String getIdentifier() { return "mochicrypt32"; } + + @Override + public boolean usesKey() { + return false; + } } 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 index f3d298449..8006c3b8e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java @@ -53,21 +53,23 @@ public interface Packer { * * @param is Data to unpack * @param os Stream to write unpacked data to + * @param key Key * @return True if it was unpacked correctly, False if it is not suitable * for unpacking or an error happened. * @throws IOException On I/O error */ - public boolean decrypt(InputStream is, OutputStream os) throws IOException; + public boolean decrypt(InputStream is, OutputStream os, String key) throws IOException; /** * Pack the data * * @param is Data to pack * @param os Stream to write packed data to + * @param key Key * @return True if packed successfully, False if error happened. * @throws IOException On I/O error */ - public boolean encrypt(InputStream is, OutputStream os) throws IOException; + public boolean encrypt(InputStream is, OutputStream os, String key) throws IOException; /** * Human readable name of this packer @@ -82,4 +84,10 @@ public interface Packer { * @return Identifier of this packer */ public String getIdentifier(); + + /** + * Checks whether the packer uses encryption key. + * @return True if key is used + */ + public boolean usesKey(); } 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 38e1162cc..5febea555 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 @@ -23,6 +23,7 @@ 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.HarmanAirPacker; +import com.jpexs.decompiler.flash.packers.HarmanAirPackerWithKey; import com.jpexs.decompiler.flash.packers.MochiCryptPacker16Bit; import com.jpexs.decompiler.flash.packers.MochiCryptPacker32Bit; import com.jpexs.decompiler.flash.packers.Packer; @@ -68,6 +69,9 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter @Internal public Packer usedPacker; + + @Internal + public String packerKey; @Internal private PackedBinaryData sub; @@ -75,7 +79,8 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter private static final Packer[] PACKERS = { new MochiCryptPacker16Bit(), new MochiCryptPacker32Bit(), - new HarmanAirPacker() + new HarmanAirPacker(), + new HarmanAirPackerWithKey() }; public static Packer[] getAvailablePackers() { @@ -103,10 +108,10 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter } @Override - public boolean unpack(Packer packer) { + public boolean unpack(Packer packer, String key) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - if (!packer.decrypt(new ByteArrayInputStream(binaryData.getRangeData()), baos)) { + if (!packer.decrypt(new ByteArrayInputStream(binaryData.getRangeData()), baos, key)) { return false; } } catch (IOException ex) { @@ -114,6 +119,7 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter } sub = new PackedBinaryData(swf, this, new ByteArrayRange(baos.toByteArray())); usedPacker = packer; + packerKey = key; return true; } @@ -135,7 +141,7 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter String packerAdd = ""; BinaryDataInterface binaryData = this; if (usedPacker != null) { - unpack(usedPacker); + unpack(usedPacker, packerKey); if (sub != null) { is = new ByteArrayInputStream(sub.getDataBytes().getRangeData()); binaryData = sub; @@ -233,7 +239,7 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter sub.pack(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos)) { + if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos, packerKey)) { return false; } } catch (IOException ex) { @@ -282,4 +288,9 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter public String getClassExportFileName(String className) { return className; } + + @Override + public String getPackerKey() { + return packerKey; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java index 84bcc8eea..e68e1c362 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java @@ -43,9 +43,10 @@ public interface BinaryDataInterface extends Exportable { /** * Unpacks the data. * @param packer Packer + * @param key Key * @return True if the data was unpacked */ - public boolean unpack(Packer packer); + public boolean unpack(Packer packer, String key); /** * Detects the packer. @@ -57,6 +58,12 @@ public interface BinaryDataInterface extends Exportable { * @return Used packer */ public Packer getUsedPacker(); + + /** + * Gets the used packer key. + * @return Packer key + */ + public String getPackerKey(); /** * Sets the data bytes. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java index 23d091f16..ca6996161 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java @@ -43,6 +43,7 @@ public class PackedBinaryData implements TreeItem, BinaryDataInterface { private PackedBinaryData sub; private Packer usedPacker; private SWF innerSwf; + private String packerKey; /** * Constructor. @@ -57,10 +58,10 @@ public class PackedBinaryData implements TreeItem, BinaryDataInterface { } @Override - public boolean unpack(Packer packer) { + public boolean unpack(Packer packer, String key) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - if (!packer.decrypt(new ByteArrayInputStream(data.getRangeData()), baos)) { + if (!packer.decrypt(new ByteArrayInputStream(data.getRangeData()), baos, key)) { return false; } } catch (IOException ex) { @@ -68,6 +69,7 @@ public class PackedBinaryData implements TreeItem, BinaryDataInterface { } sub = new PackedBinaryData(swf, this, new ByteArrayRange(baos.toByteArray())); usedPacker = packer; + packerKey = key; return true; } @@ -159,7 +161,7 @@ public class PackedBinaryData implements TreeItem, BinaryDataInterface { } ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos)) { + if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos, packerKey)) { return false; } } catch (IOException ex) { @@ -254,4 +256,9 @@ public class PackedBinaryData implements TreeItem, BinaryDataInterface { parts.add(0, binaryData.getClassExportFileName(className)); return String.join("_", parts); } + + @Override + public String getPackerKey() { + return packerKey; + } } diff --git a/libsrc/ffdec_lib/testdata/as3_harman/bin/harman.xml b/libsrc/ffdec_lib/testdata/as3_harman/application.xml similarity index 61% rename from libsrc/ffdec_lib/testdata/as3_harman/bin/harman.xml rename to libsrc/ffdec_lib/testdata/as3_harman/application.xml index e569d68a7..909905244 100644 --- a/libsrc/ffdec_lib/testdata/as3_harman/bin/harman.xml +++ b/libsrc/ffdec_lib/testdata/as3_harman/application.xml @@ -1,25 +1,23 @@ - + - com.jpexs.Harman + harmanencrypted 1.0 - Harman + harman_encrypted - Harman + harman_encrypted - JPEXS + - Harman - harman_encrypted.swf + harman_encrypted + bin/harman_encrypted.swf standard false true true true true - 800 - 600