Adventure with Android. Part 4.

So, my next step was to read real file, skip first 7 bytes and call decrypt function. To do this I wrote following code:

bool DecryptFile(const char* inputFileName, const char* outputFileName)
{
    FILE* fileHandle = fopen(inputFileName, "rb");
    if (fileHandle == nullptr)
    {
        LOGW("DH: Failed to open input file '%s'", inputFileName);
        return false;
    }
…

And to my surprise code failed on fopen. I checked errno and found that it is EACCES. It means that application does not have enough rights to read this file. I spent few hours trying to figure out where should I place my file, so my application will be able to read it. And one of the most confusing and annoying part about Android is lack of consistency. On one phone has is here, another phone will have it there and on Samsung it is always in third place. Anyway, after few hours I figured it out. I placed them at this location:
/storage/emulated/0/Android/data/com.Android2/files

Keep in mind that com.Android2 is name of package for your application. When I created project in Visual Studio 2019 it got automatic name Android2 and in AndroidManifest.xml you can see this:
package="com.$(ApplicationName)"

and resulting name will be com.Android2. For you it could be Android1 or Android3. Or if you gave name to your application it will be something else. And finally, application able to read file and decryption was also successful. Here is final function:

bool DecryptFile(const char* inputFileName, const char* outputFileName)
{
    FILE* fileHandle = fopen(inputFileName, "rb");
    if (fileHandle == nullptr)
    {
        LOGW("DH: Failed to open input file '%s'", inputFileName);
        return false;
    }

    if (fseek(fileHandle, 0L, SEEK_END) == -1)
    {
        LOGW("DH: Failed to seek to the end of the file '%s'", inputFileName);
        return false;
    }

    long fileSize = ftell(fileHandle);

    if (fileSize == -1L)
    {
        LOGW("DH: Failed to get file length'%s'", inputFileName);
        return false;
    }

    if (fseek(fileHandle, 0L, SEEK_SET) == -1)
    {
        LOGW("DH: Failed to seek to the start of the file '%s'", inputFileName);
        return false;
    }

    void* buffer = malloc(fileSize);
    if (fread(buffer, fileSize, 1, fileHandle) != 1)
    {
        LOGW("DH: Failed to read content of the file '%s'", inputFileName);
        return false;
    }

    fclose(fileHandle); // We did read all data from file and we can close it

    if (fileSize <= 7 || strncmp((const char*)buffer, SevenBytesPrefix, 7) != 0)
    {
        free(buffer);
        return true;
    }

    void* bufferToDecrypt = ((unsigned char*)buffer) + 7;
    size_t bufferSize = fileSize - 7;

    const char* key = Password;
    size_t len = 0;
    unsigned char* decryptedData = (unsigned char*)xxtea_decrypt(bufferToDecrypt, bufferSize, key, strlen(key), &len);
    if (decryptedData == nullptr)
    {
        LOGW("DH: Decryption failed for file '%s'", inputFileName);
        return false;
    }

    free(buffer); // We don't need original data anymore

    FILE* outFileHandle = fopen(outputFileName, "wb");
    if (outFileHandle == nullptr)
    {
        LOGW("DH: Failed to open output file '%s'", outputFileName);
        return false;
    }

    if (fwrite(decryptedData, len, 1, outFileHandle) != 1)
    {
        LOGW("DH: Failed to write content of the file '%s'", outputFileName);
        return false;
    }

    fclose(outFileHandle);

    free(decryptedData); // data saved and there is time to free it

    return true;
}

Finally, some good news. Just as note, code is using SevenBytesPrefix and Password that you have to define. And at the end I wrote application that decrypts whole directory:

void CreateNewPaths(char* newPath, char* directory, char* fileName)
{
    strcpy(newPath, directory);
    strcat(newPath, "/");
    strcat(newPath, fileName);
}

void DecryptDirectory(char* inputDirectory, char* outputDirectory)
{
    if (mkdir(outputDirectory, 0777) != 0)
    {
        if (errno != EEXIST)
        {
            LOGW("DH: Failed to create directory '%s'", outputDirectory);
            return;
        }
    }

    DIR* directory = opendir(inputDirectory);
    if (directory == nullptr)
    {
        LOGW("DH: Failed to enumerate directory '%s'", inputDirectory);
        return;
    }

    struct dirent* directoryEntry;
    while (true)
    {
        errno = 0;
        directoryEntry = readdir(directory);
        if (directoryEntry == nullptr)
        {
            if (errno != 0)
            {
                LOGW("DH: Failed to get next file in directory '%s'", inputDirectory);
                return;
            }

            return;
        }

        char inputPath[1024];
        char outputPath[1024];

        switch (directoryEntry->d_type)
        {
            case DT_DIR:
                if (strcmp(directoryEntry->d_name, ".") == 0 || strcmp(directoryEntry->d_name, "..") == 0)
                {
                    continue;
                }

                CreateNewPaths(inputPath, inputDirectory, directoryEntry->d_name);
                CreateNewPaths(outputPath, outputDirectory, directoryEntry->d_name);

                DecryptDirectory(inputPath, outputPath);

                break;

            case DT_REG:
                CreateNewPaths(inputPath, inputDirectory, directoryEntry->d_name);
                CreateNewPaths(outputPath, outputDirectory, directoryEntry->d_name);

                DecryptFile(inputPath, outputPath);

                break;
        }
    }
}

And you have to call it from main like this:
DecryptDirectory("/storage/emulated/0/Android/data/com.Android2/files/in", "/storage/emulated/0/Android/data/com.Android2/files/out");

Program will take everything in input directory and decrypt it and put it in output directory recreating directory structure. At the end you have to do following:

Step 1.
Extract everything from .APK directory

Step 2.
Run ADB command line and use this command to copy files to Android:
ADB push C:\MyExtractedData /storage/emulated/0/Android/data/com.Android2/files/in

Step 3.
Run your application from Visual Studio

Step 4.
Copy decrypted data from Android to your PC by using this command in ADB command line:
ADB pull /storage/emulated/0/Android/data/com.Android2/files/out C:\MyDecryptedData

Keep in mind that default emulator has quite limited storage, so it is good idea to delete these two directories after you finish. Run ADB command line and then run following commands:
ADB shell
rm -r /storage/emulated/0/Android/data/com.Android2/files/in
rm -r /storage/emulated/0/Android/data/com.Android2/files/out

First major step is completed. But after inspection of decrypted data, I found another 7 bytes header but slightly different and then typical zlib header. I wrote another console application, this time in C#:
        

        static void Main(string[] args)
        {
            DecompressDirectory(args[0], args[1]);
        }

        private static void DecompressDirectory(string inputDirectory, string outputDirectory)
        {
            Directory.CreateDirectory(outputDirectory);

            foreach(string fileName in Directory.EnumerateFiles(inputDirectory, "*.*", SearchOption.TopDirectoryOnly))
            {
                string outputFileName = Path.Combine(outputDirectory, Path.GetFileName(fileName));
                DecompressFile(fileName, outputFileName);
            }

            foreach (string fileName in Directory.EnumerateDirectories(inputDirectory, "*.*", SearchOption.TopDirectoryOnly))
            {
                string outputFileName = Path.Combine(outputDirectory, Path.GetFileName(fileName));
                DecompressDirectory(fileName, outputFileName);
            }
        }

        private static void DecompressFile(string inputFileName, string outputFileName)
        {
            using (FileStream originalFileStream = File.OpenRead(inputFileName))
            {
                originalFileStream.Seek(7, SeekOrigin.Begin);

                using (FileStream decompressedFileStream = File.Create(outputFileName))
                {
                    using (ZlibStream decompressionStream = new ZlibStream(originalFileStream, SharpCompress.Compressors.CompressionMode.Decompress))
                    {
                        decompressionStream.CopyTo(decompressedFileStream);
                    }
                }
            }
        }

You need to add SharpCompress package as default C# implementations cannot read that Zlib stream. After that you will have decrypted and decompressed data of everything. Finally my goal was achieved.

But after looking around I found that some files are missing. For example I found that some .plist files does not have corresponding .png file. After spending quite some time thinking that I was doing something wrong, I found that that game was downloading some files when it starts first time and they actually were never present in .APK file. After messing around I found that game storing data here:
/data/data/<game packageid>/files/<version of app>

And also I found that in some cases APK file has slightly outdated version of files. Looks like game updating them on the fly. At the end I have to do following:

Step 1.
Start game on emulator, run it for quite some time and

Step 2.

Start ABD command line and executed following commands:
ABD shell
su
cp -avr /data/data/<game packageid>/files/<version of app> /storage/emulated/0/Android/data/com.Android2/files/in

And repeat run decrypt and decompress application. But this time I put them in different directory on my PC to differentiate their origin.

Sometimes game was redownloading files when new version is released, and I have to repeat process for /data/data files again. In some case new game version was released and because ARM emulator does not have store, I have to find new .APK file and install it and repeat whole process again.

This part is finished, but it is not the end of story. More about that in next post.

Comments

Post comment