Decrypting Brawlhalla SWZ Files


Disclaimer

The information included in this post is for educational purposes only. Any material on this webpage may not be reproduced, retransmitted, or redisplayed other than for personal or educational use.

Introduction

SWZ files are Runtime Shared Libraries (RSLs) that contain data that have been digitally signed by Adobe (if the files were created by them) in the Apache Flex, formerly Adobe Flex framework. Prior to Apache Flex, SWZ files were stored in the Flash Player cache and could be accessed by any application, regardless of that application's originating domain. They only needed to be downloaded to the client once, and they were not cleared from the client's disk when the browser's cache was cleared.

Brawlhalla SWZ File Format

Brawlhalla SWZ files store string objects, all of which are zlib-compressed and encrypted. The file format uses a XOR cipher that incorporates the WELL512 pseudo-random number generator to provide a random key for each operation. According to barncastle, each file uses the below structure and reads StringEntries until compressedSize exceeds the end of the file:

struct Header
{
uint32_BE Checksum;
uint32_BE Seed; // XOR with EncryptionKey
StringEntry Entries[x];
}

struct StringEntry
{
uint32_BE EncodedCompressedSize // XOR'd
uint32_BE EncodedDecompressedSize // XOR'd
uint32_BE Checksum;

// zlib compressed then XOR'd
byte EncodedZlibCompressedData[decodedCompressedSize];
}

The checksum in the Header structure is used to verify that the correct secret key is being used for the selected file, whereas the checksum in the StringEntry structure is used to verify the integrity of the encoded and compressed data within each string entry.

Encryption Process

The encryption process is handled by "RawData.dll", an Adobe Native Extension that is invoked by "BrawlhallaAir.swf". This extension contains three methods:

Instructions

The following is a list of instructions required to decrypt Brawlhalla's SWZ files:

1. Visit this URL and left-click on "View raw" below "BrawlhallaDumper.zip".

2. Head to the location of the completed download and extract the contents of the compressed (zipped) folder "BrawlhallaDumper" into the directory of your choice.

3. Head to where Brawlhalla is installed. Copy the "Dynamic.swz", "Engine.swz", "Game.swz", and "Init.swz" SWZ files from that directory and paste them into the directory where the "BrawlhallaDumper" application is located.

4. Drag and drop one or more of the SWZ files onto the "BrawlhallaDumper" application. Terminal will launch, and you will be prompted to enter a global encryption key. You will need JPEXS Free Flash Decompiler to find this key.

5. Once you have installed JPEXS Free Flash Decompiler, run it. In JPEXS Free Flash Decompiler, left-click on "Open" and head to where Brawlhalla is installed in the in-app file explorer. Then, left-click on "BrawlhallaAir.swf" and subsequently left-click on "Open" at the bottom-right corner of the in-app file explorer to open and read it.

6. Head to the menu bar and left-click on "Tools". Left-click on "Text search" and type "ANE_RawData.Init" into the "Search text:" text field. Ensure that "Scope" is set to "Current SWF" and that "Search in AS" is selected. Left-click on "OK".

7. JPEXS Free Flash Decompiler should return one search result with an obfuscated name. Left-click on this search result, and then left-click on "Go to".

8. You will now be focused on an obfuscated function within BrawlhallaAir.swf's ActionScript source. The secret key is displayed in parentheses on the first line of this function. In the case of Brawlhalla patch 8.13, the secret key is 865948130. Copy this key.

9. Return to the BrawlhallaDumper application and paste the secret key into the "Enter the global encryption key" text field. Press the "Enter" key. The BrawlhallaDumper application will then create a folder named "Dump" in the same directory as the SWZ files and export the decrypted text files there.

Sample Code
XOR Cipher and WELL512 Pseudo-random Number Generator Combined Approach

The following C++ code simulates the encryption process handled by "RawData.dll". The header seed is sourced from this URL. The initial input data is set to a string that reads "Hello, World!"

#include <cstdint>  // Provide fixed-width integer types
#include <iostream> // Provide input and output facilities
#include <vector> // Define the std::vector container for encapsulating dynamic arrays

class WELL512 {
public:
// Initialize WELL512 pseudo-random number generator with 32-bit seed
WELL512(uint32_t seed) {
// Initialize first element of state array with seed
state[0] = seed;
// Fill the rest of state array with pseudo-random numbers based on seed
for (int i = 1; i < 16; ++i) {
state[i] = (1812433253U * (state[i - 1] ^ (state[i - 1] >> 30)) + i);
}
index = 0;
}

// Generate next 32-bit pseudo-random number in WELL512 sequence
uint32_t next() {
uint32_t a = state[index];
uint32_t c = state[(index + 13) & 15];
uint32_t b = a ^ c ^ (a << 16) ^ (c << 15);
c = state[(index + 9) & 15];
c ^= (c >> 11);
a = state[index] = b ^ c;
uint32_t d = a ^ ((a << 5) & 0xDA442D24);
index = (index + 15) & 15;
a = state[index];
state[index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28);
return state[index];
}

// Return lowest 8 bits (1 byte) of 32-bit pseudo-random number generated by next() function
uint8_t nextByte() {
return static_cast<uint8_t>(next() & 0xFF);
}

private:
uint32_t state[16]; // State array for WELL512
int index; // Index for current state
};

// Encrypt input data using XOR cipher with WELL512-generated pseudo-random byte stream
std::vector<uint8_t> encrypt(const std::vector<uint8_t>& data, uint32_t seed) {
// Initialize WELL512 instance with seed
WELL512 prng(seed);
// Create std::vector to hold encrypted data that is same size as input data
std::vector<uint8_t> encrypted(data.size());

// XOR each byte of data with pseudo-random byte generated by prng.nextByte() and store it in std::vector holding encrypted data
for (size_t i = 0; i < data.size(); ++i) {
encrypted[i] = data[i] ^ prng.nextByte();
}

return encrypted;
}

// Decrypt input data using same process as encryption since XOR is a symmetric operation
std::vector<uint8_t> decrypt(const std::vector<uint8_t>& data, uint32_t seed) {
return encrypt(data, seed);
}

// Display contents of std::vector<uint8_t> as characters
void printData(const std::vector<uint8_t>& data) {
// Cast each uint8_t to a char and print it
for (uint8_t byte : data) {
std::cout << static_cast<char>(byte);
}
// Display newline after printing all bytes
std::cout << "\n";
}

int main() {
std::string plaintext = "Hello, World!";

// Given values
uint32_t headerSeed = 731341442;
uint32_t secretKey = 865948130;

// XOR header seed and secret key to derive actual seed
uint32_t seed = headerSeed ^ secretKey;

// Convert string to vector of bytes
std::vector<uint8_t> plaintextBytes(plaintext.begin(), plaintext.end());

// Encrypt plaintext
std::vector<uint8_t> encryptedText = encrypt(plaintextBytes, seed);

// Decrypt ciphertext
std::vector<uint8_t> decryptedText = decrypt(encryptedText, seed);

std::cout << "Plaintext: " << plaintext << "\n\n";
std::cout << "Encrypted: ";
printData(encryptedText);
std::cout << "Decrypted: ";
printData(decryptedText);

return 0;
}