mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-06 17:36:13 +00:00
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -46,6 +46,11 @@ public class SWFHeader {
|
||||
* ScaleForm GFx
|
||||
*/
|
||||
public boolean gfx = false;
|
||||
|
||||
/**
|
||||
* Harman SWF Encryption
|
||||
*/
|
||||
public boolean encrypted = false;
|
||||
|
||||
/**
|
||||
* Frame rate
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user