diff --git a/CHANGELOG.md b/CHANGELOG.md index 5205c61598..7d15617e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### 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) +- Decrypt Harman AIR SWFs via commandline ### Fixed - [#2004] Freezing when a shape has nonimage character set as fill diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index c0fcc0b64b..549363459b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -299,7 +299,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * ScaleForm GFx */ public boolean gfx = false; - + /** * HARMAN encryption */ @@ -409,10 +409,9 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { @Internal private AbcIndexing abcIndex; - + private int numAbcIndexDependencies = 0; - - + private static AbcIndexing playerGlobalAbcIndex; private static AbcIndexing airGlobalAbcIndex; @@ -446,7 +445,7 @@ public static AbcIndexing getPlayerGlobalAbcIndex() { public static AbcIndexing getAirGlobalAbcIndex() { return airGlobalAbcIndex; } - + public void resetAbcIndex() { abcIndex = null; } @@ -479,19 +478,19 @@ public AbcIndexing getAbcIndex() { public int getNumAbcIndexDependencies() { return numAbcIndexDependencies; - } - + } + public void setAbcIndexDependencies(List swfs) { abcIndex = null; getAbcIndex(); - for (SWF swf:swfs) { - for (Tag tag:swf.tags) { + for (SWF swf : swfs) { + for (Tag tag : swf.tags) { if (tag instanceof ABCContainerTag) { - abcIndex.addAbc(((ABCContainerTag)tag).getABC()); + abcIndex.addAbc(((ABCContainerTag) tag).getABC()); } } } - abcIndex.rebuildPkgToObjectsNameMap(); + abcIndex.rebuildPkgToObjectsNameMap(); numAbcIndexDependencies = swfs.size(); } @@ -1125,7 +1124,8 @@ public byte[] getHeaderBytes() { 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'}; @@ -1150,7 +1150,7 @@ private static byte[] getHeaderBytes(SWFCompression compression, boolean gfx, bo ret[1] = 'W'; ret[2] = 'S'; } - + if (!gfx && encrypted) { ret[0] += 32; //to lowercase } @@ -1178,7 +1178,7 @@ private byte[] saveToByteArray(boolean gfx, boolean includeImported) throws IOEx sos.writeFIXED8(frameRate); sos.writeUI16(frameCount); - sos.writeTags(includeImported ? getTags() : getLocalTags()); + sos.writeTags(includeImported ? getTags() : getLocalTags()); if (hasEndTag) { sos.writeUI16(0); } @@ -1961,6 +1961,40 @@ public static boolean decompress(InputStream fis, OutputStream fos) { } } + /** + * Decrypts Harman AIR encryption + * @param is + * @param os + * @return + * @throws IOException + */ + public static boolean decrypt(InputStream is, OutputStream os) throws IOException { + byte[] hdr = new byte[8]; + + // SWFheader: signature, version and fileSize + if (is.read(hdr) != 8) { + throw new SwfOpenException(AppResources.translate("error.swf.headerTooShort")); + } + + decodeHeader(hdr); + + switch (hdr[0]) { + case 'c': + case 'z': + case 'f': + HarmanDecryption dec = new HarmanDecryption(); + try { + is = dec.decrypt(is, hdr); //Note: this call will uppercase hdr[0] + os.write(hdr); + Helper.copyStream(is, os); + return true; + } catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException ex) { + throw new SwfOpenException(AppResources.translate("error.swf.decryptionProblem")); + } + } + return false; + } + private static void decodeLZMAStream(InputStream is, OutputStream os, byte[] lzmaProperties, long fileSize) throws IOException { Decoder decoder = new Decoder(); if (!decoder.SetDecoderProperties(lzmaProperties)) { @@ -1986,7 +2020,7 @@ public static SWFHeader decodeHeader(byte[] headerData) throws IOException { 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; } @@ -2006,7 +2040,7 @@ private static SWFHeader decompress(InputStream is, OutputStream os, boolean all sos.write(getHeaderBytes(SWFCompression.NONE, header.gfx)); sos.writeUI8(header.version); sos.writeUI32(fileSize); - + switch (hdr[0]) { case 'c': case 'z': @@ -2751,20 +2785,20 @@ public int deobfuscateAS3Identifiers(RenameType renameType) { AbcIndexing ai = getAbcIndex(); Map> stringUsageTypesMap = new HashMap<>(); Map> stringUsagesMap = new HashMap<>(); - informListeners("deobfuscate", "Getting usages..."); + informListeners("deobfuscate", "Getting usages..."); for (Tag tag : getTags()) { - if (tag instanceof ABCContainerTag) { + if (tag instanceof ABCContainerTag) { Map stringUsageTypes = new HashMap<>(); - Set stringUsages = ((ABCContainerTag)tag).getABC().getStringUsages(); - ((ABCContainerTag)tag).getABC().getStringUsageTypes(stringUsageTypes); + Set stringUsages = ((ABCContainerTag) tag).getABC().getStringUsages(); + ((ABCContainerTag) tag).getABC().getStringUsageTypes(stringUsageTypes); stringUsageTypesMap.put(tag, stringUsageTypes); stringUsagesMap.put(tag, stringUsages); } } - + for (Tag tag : getTags()) { - if (tag instanceof ABCContainerTag) { - ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(stringUsageTypesMap.get(tag),stringUsagesMap.get(tag), deobfuscated, renameType, true); + if (tag instanceof ABCContainerTag) { + ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(stringUsageTypesMap.get(tag), stringUsagesMap.get(tag), deobfuscated, renameType, true); ((ABCContainerTag) tag).getABC().constants.clearCachedMultinames(); ((ABCContainerTag) tag).getABC().constants.clearCachedDottedChains(); tag.setModified(true); @@ -2772,7 +2806,7 @@ public int deobfuscateAS3Identifiers(RenameType renameType) { } for (Tag tag : getTags()) { if (tag instanceof ABCContainerTag) { - ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(stringUsageTypesMap.get(tag),stringUsagesMap.get(tag), deobfuscated, renameType, false); + ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(stringUsageTypesMap.get(tag), stringUsagesMap.get(tag), deobfuscated, renameType, false); ((ABCContainerTag) tag).getABC().constants.clearCachedMultinames(); ((ABCContainerTag) tag).getABC().constants.clearCachedDottedChains(); tag.setModified(true); @@ -2790,11 +2824,11 @@ public int deobfuscateAS3Identifiers(RenameType renameType) { sc.setModified(true); } } - deobfuscation.deobfuscateInstanceNames(true, deobfuscated, renameType, getTags(), new HashMap<>()); - + deobfuscation.deobfuscateInstanceNames(true, deobfuscated, renameType, getTags(), new HashMap<>()); + for (Tag tag : getTags()) { if (tag instanceof ABCContainerTag) { - ai.refreshAbc(((ABCContainerTag)tag).getABC()); + ai.refreshAbc(((ABCContainerTag) tag).getABC()); } } return deobfuscated.size(); diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index c032bf9b5f..ab1c252cd3 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -124,7 +124,6 @@ import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; -import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.FileAttributesTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; @@ -168,9 +167,7 @@ import com.sun.jna.Platform; import com.sun.jna.platform.win32.Kernel32; import gnu.jpdf.PDFJob; -import java.awt.Color; import java.awt.Graphics; -import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.Paper; import java.io.BufferedInputStream; @@ -231,12 +228,9 @@ import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.helpers.SerializableImage; import gnu.jpdf.PDFGraphics; -import java.awt.AlphaComposite; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Point; -import java.awt.Rectangle; -import java.util.Comparator; /** * @@ -475,6 +469,11 @@ public static void printCmdLineUsage(PrintStream out, boolean webHelp, String fi out.println(" " + (cnt++) + ") -decompress "); out.println(" ...Decompress and save it to "); } + + if (filter == null || filter.equals("decrypt")) { + out.println(" " + (cnt++) + ") -decrypt "); + out.println(" ...Decrypts HARMAN Air encrypted file and save it to "); + } if (filter == null || filter.equals("swf2xml")) { out.println(" " + (cnt++) + ") -swf2xml "); @@ -1008,6 +1007,9 @@ public static String[] parseArguments(String[] arguments) throws IOException { } else if (command.equals("decompress")) { parseDecompress(args); System.exit(0); + } else if (command.equals("decrypt")) { + parseDecrypt(args); + System.exit(0); } else if (command.equals("swf2xml")) { parseSwf2Xml(args, charset); System.exit(0); @@ -2703,6 +2705,26 @@ private static void parseCompress(Stack args) { System.exit(result ? 0 : 1); } + private static void parseDecrypt(Stack args) { + if (args.size() < 2) { + badArguments("decrypt"); + } + + boolean result = false; + try { + try (InputStream fis = new BufferedInputStream(new StdInAwareFileInputStream(args.pop())); OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + result = SWF.decrypt(fis, fos); + System.out.println(result ? "OK" : "FAIL"); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + System.exit(1); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(result ? 0 : 1); + } private static void parseDecompress(Stack args) { if (args.size() < 2) { badArguments("decompress");