Skip to content

Commit

Permalink
Decrypt Harman AIR SWFs via commandline
Browse files Browse the repository at this point in the history
  • Loading branch information
jindrapetrik committed Jun 25, 2023
1 parent b0f9070 commit 5fb5ac8
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
88 changes: 61 additions & 27 deletions libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
* ScaleForm GFx
*/
public boolean gfx = false;

/**
* HARMAN encryption
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -446,7 +445,7 @@ public static AbcIndexing getPlayerGlobalAbcIndex() {
public static AbcIndexing getAirGlobalAbcIndex() {
return airGlobalAbcIndex;
}

public void resetAbcIndex() {
abcIndex = null;
}
Expand Down Expand Up @@ -479,19 +478,19 @@ public AbcIndexing getAbcIndex() {

public int getNumAbcIndexDependencies() {
return numAbcIndexDependencies;
}
}

public void setAbcIndexDependencies(List<SWF> 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();
}

Expand Down Expand Up @@ -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'};
Expand All @@ -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
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
}

Expand All @@ -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':
Expand Down Expand Up @@ -2751,28 +2785,28 @@ public int deobfuscateAS3Identifiers(RenameType renameType) {
AbcIndexing ai = getAbcIndex();
Map<Tag, Map<Integer, String>> stringUsageTypesMap = new HashMap<>();
Map<Tag, Set<Integer>> stringUsagesMap = new HashMap<>();
informListeners("deobfuscate", "Getting usages...");
informListeners("deobfuscate", "Getting usages...");
for (Tag tag : getTags()) {
if (tag instanceof ABCContainerTag) {
if (tag instanceof ABCContainerTag) {
Map<Integer, String> stringUsageTypes = new HashMap<>();
Set<Integer> stringUsages = ((ABCContainerTag)tag).getABC().getStringUsages();
((ABCContainerTag)tag).getABC().getStringUsageTypes(stringUsageTypes);
Set<Integer> 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);
}
}
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);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

/**
*
Expand Down Expand Up @@ -475,6 +469,11 @@ public static void printCmdLineUsage(PrintStream out, boolean webHelp, String fi
out.println(" " + (cnt++) + ") -decompress <infile> <outfile>");
out.println(" ...Decompress <infile> and save it to <outfile>");
}

if (filter == null || filter.equals("decrypt")) {
out.println(" " + (cnt++) + ") -decrypt <infile> <outfile>");
out.println(" ...Decrypts HARMAN Air encrypted file <infile> and save it to <outfile>");
}

if (filter == null || filter.equals("swf2xml")) {
out.println(" " + (cnt++) + ") -swf2xml <infile> <outfile>");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2703,6 +2705,26 @@ private static void parseCompress(Stack<String> args) {
System.exit(result ? 0 : 1);
}

private static void parseDecrypt(Stack<String> 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<String> args) {
if (args.size() < 2) {
badArguments("decompress");
Expand Down

0 comments on commit 5fb5ac8

Please sign in to comment.