Added #2038, #2028, #2034, #2036 Support for Harman AIR encrypted SWFs (Read-only)

This commit is contained in:
Jindra Petřík
2023-06-24 21:06:47 +02:00
parent 11d19da76c
commit ec10ded3ad
12 changed files with 226 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
- [#1998] Setting for maximum number of items in the cache - allows less memory consumption (Defaults to 500 per cache)
- [#2038], [#2028], [#2034], [#2036] Support for Harman AIR encrypted SWFs (Read-only)
### Fixed
- [#2004] Freezing when a shape has nonimage character set as fill
@@ -3035,6 +3036,10 @@ All notable changes to this project will be documented in this file.
[alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8
[alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7
[#1998]: https://www.free-decompiler.com/flash/issues/1998
[#2038]: https://www.free-decompiler.com/flash/issues/2038
[#2028]: https://www.free-decompiler.com/flash/issues/2028
[#2034]: https://www.free-decompiler.com/flash/issues/2034
[#2036]: https://www.free-decompiler.com/flash/issues/2036
[#2004]: https://www.free-decompiler.com/flash/issues/2004
[#2008]: https://www.free-decompiler.com/flash/issues/2008
[#2007]: https://www.free-decompiler.com/flash/issues/2007

View File

@@ -0,0 +1,141 @@
/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
*
* @author JPEXS
*
* Based on swfdecrypt.py by NathaanTFM
*/
public class HarmanDecryption {
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
private static int sum(byte[] data) {
int s = 0;
for (int i = 0; i < data.length; i++) {
s += data[i] & 0xff;
}
return s;
}
private static long getkey(byte[] data) {
int dsum = sum(data);
int dmod = dsum % GLOBAL_KEY.length();
String s = GLOBAL_KEY.substring(dmod) + GLOBAL_KEY.substring(0, dmod);
s += " EncryptSWF ";
s += "" + dsum;
long ret = 0;
for (int i = 0; i < s.length(); i++) {
int code = s.charAt(i);
ret *= 31;
ret += code;
}
return ret & 0xffffffffL;
}
private static long unpack(byte data[], int start) {
return (data[start] & 0xff)
+ ((long) (data[start + 1] & 0xff) << 8)
+ ((long) (data[start + 2] & 0xff) << 16)
+ ((long) (data[start + 3] & 0xff) << 24);
}
public InputStream decrypt(InputStream is, byte[] header) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
header[0] -= 32; // to uppercase
long key = getkey(header);
//get the length
DataInputStream dais = new DataInputStream(is);
byte encryptedLengthBytes[] = new byte[4];
dais.readFully(encryptedLengthBytes);
long encryptedLength = unpack(encryptedLengthBytes, 0);
int decryptedLength = (int) (encryptedLength ^ key);
//padded length
int paddedLength = (int) (decryptedLength + 0x1F) & ~0x1F;
//aes iv
byte aesIV[] = new byte[16];
System.arraycopy(header, 0, aesIV, 0, header.length); //header
System.arraycopy(encryptedLengthBytes, 0, aesIV, 8, 4); //encrypted length
aesIV[12] = (byte) (key & 0xff);
aesIV[13] = (byte) ((key >> 8) & 0xff);
aesIV[14] = (byte) ((key >> 16) & 0xff);
aesIV[15] = (byte) ((key >> 24) & 0xff);
for (int i = 0; i < 16; i++) {
aesIV[i] ^= GLOBAL_KEY.charAt(i);
}
// aes key
// this one is stored at the end of the file
byte aesKey[] = new byte[32];
//int aesKeyIdx = 8 + 4 + paddedLength; //skip header, size, data
byte data[] = new byte[paddedLength];
dais.readFully(data);
byte aesKeyData[] = new byte[32];
dais.readFully(aesKeyData);
for (int i = 0; i < 32; i += 4) {
long value = unpack(aesKeyData, i);
if ((i & 4) == 4) {
value -= key;
} else {
value += key;
}
aesKey[i] = (byte) (value & 0xff);
aesKey[i + 1] = (byte) ((value >> 8) & 0xff);
aesKey[i + 2] = (byte) ((value >> 16) & 0xff);
aesKey[i + 3] = (byte) ((value >> 24) & 0xff);
}
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(aesIV));
byte decryptedData[] = cipher.doFinal(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(decryptedData);
return new ByteArrayInputStream(Arrays.copyOfRange(decryptedData, 0, decryptedLength));
}
}

View File

@@ -190,6 +190,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -211,6 +214,9 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
/**
* Class representing SWF file
@@ -293,6 +299,11 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
* ScaleForm GFx
*/
public boolean gfx = false;
/**
* HARMAN encryption
*/
public boolean encrypted = false;
@Internal
public OpenableList openableList;
@@ -414,7 +425,10 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
"ZWS", // LZMA compressed Flash
"GFX", // Uncompressed ScaleForm GFx
"CFX", // Compressed ScaleForm GFx
"ABC" // Non-standard LZMA compressed Flash
"ABC", // Non-standard LZMA compressed Flash
"fWS", //Harman encrypted uncompressed Flash,
"cWS", //Harman encrypted ZLib compressed Flash,
"zWS" //Harman encrypted LZMA compressed Flash
);
/**
@@ -1106,10 +1120,13 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
}
public byte[] getHeaderBytes() {
return getHeaderBytes(compression, gfx);
return getHeaderBytes(compression, gfx, encrypted);
}
private static byte[] getHeaderBytes(SWFCompression compression, boolean gfx) {
return getHeaderBytes(compression, gfx, false);
}
private static byte[] getHeaderBytes(SWFCompression compression, boolean gfx, boolean encrypted) {
if (compression == SWFCompression.LZMA_ABC) {
return new byte[]{'A', 'B', 'C'};
}
@@ -1133,6 +1150,10 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
ret[1] = 'W';
ret[2] = 'S';
}
if (!gfx && encrypted) {
ret[0] += 32; //to lowercase
}
return ret;
}
@@ -1463,6 +1484,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SWFHeader header = decompress(is, baos, true);
gfx = header.gfx;
encrypted = header.encrypted;
compression = header.compression;
lzmaProperties = header.lzmaProperties;
uncompressedData = baos.toByteArray();
@@ -1964,7 +1986,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
SWFHeader header = new SWFHeader();
header.version = version;
header.fileSize = fileSize;
header.gfx = headerData[1] == 'F' && headerData[2] == 'X';
header.gfx = headerData[1] == 'F' && headerData[2] == 'X';
return header;
}
@@ -1984,6 +2006,20 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
sos.write(getHeaderBytes(SWFCompression.NONE, header.gfx));
sos.writeUI8(header.version);
sos.writeUI32(fileSize);
switch (hdr[0]) {
case 'c':
case 'z':
case 'f':
header.encrypted = true;
HarmanDecryption dec = new HarmanDecryption();
try {
is = dec.decrypt(is, hdr); //Note: this call will uppercase hdr[0]
} catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException ex) {
throw new SwfOpenException(AppResources.translate("error.swf.decryptionProblem"));
}
break;
}
switch (hdr[0]) {
case 'C': { // CWS, CFX

View File

@@ -46,6 +46,11 @@ public class SWFHeader {
* ScaleForm GFx
*/
public boolean gfx = false;
/**
* Harman SWF Encryption
*/
public boolean encrypted = false;
/**
* Frame rate

View File

@@ -911,6 +911,9 @@ public final class Configuration {
@ConfigurationCategory("script")
public static ConfigurationItem<Boolean> warningInitializersClass = null;
@ConfigurationDefaultBoolean(true)
@ConfigurationName("warning.cannotencrypt")
public static ConfigurationItem<Boolean> warningCannotEncrypt = null;
private enum OSId {
WINDOWS, OSX, UNIX

View File

@@ -47,3 +47,4 @@ package.default = <default package>
error.swf.invalid = Invalid SWF file, wrong signature.
error.swf.headerTooShort = SWF header is too short.
error.abc.invalid = Invalid ABC file.
error.swf.decryptionProblem = Invalid SWF file, decryption failed.

View File

@@ -47,3 +47,6 @@ package.default = <v\u00fdchoz\u00ed bal\u00ed\u010dek>
error.swf.invalid = Neplatn\u00fd SWF soubor, \u0161patn\u00e1 signatura.
error.swf.headerTooShort = SWF hlavi\u010dka je p\u0159\u00edli\u0161 kr\u00e1tk\u00e1.
error.abc.invalid = Neplatn\u00fd ABC soubor.
#after 18.4.1
error.swf.decryptionProblem = Neplatn\u00fd SWF soubor, selhalo de\u0161ifrov\u00e1n\u00ed.

View File

@@ -1268,6 +1268,7 @@ public class Main {
result.sourceInfo = sourceInfo;
boolean hasVideoStreams = false;
boolean hasEncrypted = false;
for (Openable openable : result) {
openable.setOpenableList(result);
@@ -1289,6 +1290,10 @@ public class Main {
}
}
if (swf.encrypted) {
hasEncrypted = true;
}
swf.addEventListener(new EventListener() {
@Override
public void handleExportingEvent(String type, int index, int count, Object data) {
@@ -1344,6 +1349,16 @@ public class Main {
});
}
if (hasEncrypted) {
View.execInEventDispatchLater(new Runnable() {
@Override
public void run() {
ViewMessages.showMessageDialog(getDefaultMessagesComponent(), AppStrings.translate("warning.cannotencrypt").replace("%file%", sourceInfo.getFileTitleOrName()), AppStrings.translate("message.warning"), JOptionPane.WARNING_MESSAGE, Configuration.warningCannotEncrypt);
}
});
}
return result;
}

View File

@@ -691,3 +691,6 @@ config.description.warning.initializers.class = Show warning on AS3 class trait
#after 18.4.1
config.name.maxCachedNum = Maximum number of cached items per single cache
config.description.maxCachedNum = Maximum number of cached items before older items are removed from cache. Lower value = less memory, slower app. Higher value = more memory, faster app. Set this to 0 to unlimited caching.
config.name.warning.cannotencrypt = Warn when cannot save encrypted
config.description.warning.cannotencrypt = Show warning when cannot save SWF file which was encrypted using HARMAN Air encryption.

View File

@@ -681,3 +681,6 @@ config.description.warning.initializers.class = Zobrazovat varov\u00e1n\u00ed p\
#after 18.4.1
config.name.maxCachedNum = Maxim\u00e1ln\u00ed po\u010det prvk\u016f v jedn\u00e9 cache
config.description.maxCachedNum = Maxim\u00e1ln\u00ed po\u010det cachovan\u00fdch prvk\u016f p\u0159ed t\u00edm, ne\u017e jsou star\u0161\u00ed prvky vymaz\u00e1ny. Ni\u017e\u0161\u00ed hodnota = m\u00e9n\u011b pam\u011bti, pomalej\u0161\u00ed aplikace. Vy\u0161\u0161\u00ed hodnota = v\u00edce pam\u011bti, rychlej\u0161\u00ed aplikace. Nastavte sem hodnotu 0 pro nekone\u010dn\u00e9 cachov\u00e1n\u00ed.
config.name.warning.cannotencrypt = Varovat kdy\u017e nelze ulo\u017eit za\u0161ifrovan\u00e9
config.description.warning.cannotencrypt = Zobrazit varov\u00e1n\u00ed kdy\u017e nelze ulo\u017eit SWF soubor kter\u00fd byl \u0161ifrov\u00e1n pomoc\u00ed HARMAN Air \u0161ifrov\u00e1n\u00ed.

View File

@@ -1135,4 +1135,7 @@ warning.initializers.class = The "extends" object is also used in script initial
contextmenu.addScript.doaction = Add frame script - DoAction
contextmenu.addScript.buttoncondaction = Add button event script - BUTTONCONDACTION
contextmenu.addScript.clipactionrecord = Add instance event script - CLIPACTIONRECORD
contextmenu.addScript.doinitaction = Add sprite init script - DoInitAction
contextmenu.addScript.doinitaction = Add sprite init script - DoInitAction
#after 18.4.1
warning.cannotencrypt = WARNING: The file %file% was encrypted using HARMAN Air encryption.\r\nIt was successfully decrypted to be loaded, but if you want to save modified file later,\r\nthe encryption will be stripped ( = not encrypted).

View File

@@ -1120,4 +1120,7 @@ warning.initializers.class = Polo\u017eky class jako extends a/nebo implements j
contextmenu.addScript.doaction = P\u0159idat frame skript - DoAction
contextmenu.addScript.buttoncondaction = P\u0159idat skript ud\u00e1losti tla\u010d\u00edtka - BUTTONCONDACTION
contextmenu.addScript.clipactionrecord = P\u0159idat skript ud\u00e1losti instance - CLIPACTIONRECORD
contextmenu.addScript.doinitaction = P\u0159idat inicializa\u010dn\u00ed skript spritu - DoInitAction
contextmenu.addScript.doinitaction = P\u0159idat inicializa\u010dn\u00ed skript spritu - DoInitAction
#after 18.4.1
warning.cannotencrypt = VAROV\u00c1N\u00cd: Soubor %file% byl za\u0161ifrov\u00e1n pomoc\u00ed HARMAN Air \u0161ifrov\u00e1n\u00ed.\r\nByl \u00fasp\u011b\u0161n\u011b roz\u0161ifrov\u00e1n pro na\u010dten\u00ed, ale pokud budete cht\u00edt soubor ulo\u017eit,\r\n\u0161ifrov\u00e1n\u00ed se ztrat\u00ed ( = nebude za\u0161ifrov\u00e1n).