Exploring Solitaire Cash

Solitaire Cash, provided by Papaya Gaming Inc., is a free app on the App Store that allows users to compete for real cash based on their solitaire performance. In this article, I will examine the app's claims of being "totally fair and skill-based" from a technical standpoint.


What is Solitaire Cash?

Since its global launch in 2019, Solitaire Cash has become a highly popular iOS app, boasting over 300,000 reviews and securing the #2 rank in Casino games.

The game's premise is straightforward: players, including you and a random group, stake real money or Gems (with sufficient Gems enabling play for real money), each receiving the same hand. A score is given to each player at the end of their game depending on two factors:

  1. How long it took them to complete their game.
  2. How many of the four decks they filled since not all hands are solvable.

Whoever ends the game with the highest score wins most, or all, of the buy-in money.


Digging into the application

Why look into the app?

Coming from the world of sneaker & retail botting, I have a lot of experience using the backend APIs of Android and iOS applications as an alternate method to purchase items. Compared to simply viewing network requests in a browser, it usually takes a bit more work to uncover what mobile applications are doing.

Mobile apps often use tactics such as SSL certificate pinning, request encryption, and loading shared objects to obscure function calls, among other techniques, complicating efforts to analyze an app's functionality. Yet, it's common to find developers becoming complacent with their app’s complex defenses, lowering their guard against bots or exposing critical information.

With all of this in mind, I was very curious about the internals of Solitaire Cash, specifically relating to how deck information was shared with the players. Quickly reading some reviews on the App Store revealed many claims of cheaters running rampant, so I wanted to see for myself how Papaya Gaming attempts to protect the integrity of their game.


Laying the groundwork

As I mentioned previously, there can be a steep barrier to entry when it comes to dissecting the processes of mobile applications – iOS in particular. Below, I've laid out a list of items and tools I will be using to take a look at the game, some of which may be difficult to come by:

  • Jailbroken iPhone with SSL unpinning capabilities
  • Charles Proxy
  • Ghidra
  • Frida

There are a lot of good resources out there for both jailbreaking iPhones and setting them up to get around SSL certificate pinning, so I won't go into that in this article.


Checking out the application

After setting up Charles Proxy on my jailbroken iPhone, I launched the app to understand its operations better. Immediately, dozens of requests are fired off to different domains, but silver-tiger-api.papayagaming.com particularly stands out. With requests to routes like:

  • /getCollectibleGifts
  • /getUserState

I was very confident that this is how the app is interacting with the Papaya servers. Unfortunately, all of the communication appears to be encrypted. To get more information, we'll have to continue peeling back layers of the app's defenses.

Encrypted data of type "application/x-thrift"

Going deeper

Seeing as we won't get more information from intercepting the unpinned requests, we're realistically left with one option – reverse engineering the application's binaries.

At this point, we know there must be code in the app that handles both the encryption and decryption of requests. If we can find out where this takes place in the code, we stand a chance of hooking into these calls and viewing the plaintext communication. To do so, we first need to get our hands on the IPA.


Obtaining the IPA

Unlike Android applications where you can easily extract the app and immediately view the contents within it, Apple handles the distribution and security of their apps differently. While APKs and IPAs are both ZIP files containing the application's contents, applications installed on Apple devices are encrypted with Apple's FairPlay DRM software. This would make it very hard to continue our search of what the app is doing, but thanks to some fantastic open-source code, frida-ios-dump, this won't be an issue for us.

Following the README from the repository, we first must install Frida locally and on the device, along with the tool's dependencies. Once this is completed, we can simply call:
python .\dump.py "Solitaire Cash"
and we are left with our decrypted Solitaire Cash IPA!


First look at the internals

Extracting the contents of the IPA, we will eventually find these files:

Contents of the decrypted Solitaire Cash IPA

Notably, the SolitaireCash file, which is the main binary for the application, is fairly small at just 87kb. After tossing it into Detect It Easy to take a look at the strings, I immediately noticed references to Unity scattered throughout. I'm not well-versed in game development, but it'd make sense that this file prepares to load the Unity game, which is likely where the most interesting code exists.

References to Unity in SolitiareCash binary

Understanding Unity applications

This was entirely new territory for me. I spent a lot of time in the extracted IPA reviewing different binaries, running string searches, and trying to get a feel for how everything is set up. Eventually, I came across forums where users discuss hacking Unity games, and finally stumbled upon Il2CppDumper-GUI.

To explain Unity games a bit and the importance of this repository, I'll pull from Unity's documentation. "The IL2CPP backend converts MSIL (Microsoft Intermediate Language) code (for example, C# code in scripts) into C++ code, then uses the C++ code to create a native binary file (for example, .exe, .apk, or .xap) for your chosen platform."

Game hacking forums detailed how the IL2CPP binary included with Unity games is the key to modifying the behavior of a game, or in our case, better understanding how it operates.

By using IL2CPPDumper, we can get important information like all string literals, function declarations, and the offsets needed to eventually hook these functions. Best of all, IL2CPPDumper should work with mobile applications the same as it would with desktop games.


Using IL2CPPDumper-GUI

Now that we have an idea of how we can get more information into the app, let's run IL2CPPDumper-GUI.

Our executable file will be the IL2CPP executable for the game which in our case is UnityFramework. The global-metadata.dat can be found in Data/Managed/Metadata.

If everything works, the output will look like this after clicking "Start dumping"

Successful IL2CPPDumper output

Reviewing the dumped contents

To get a better sense of the game and ensure we're on the right track, we can review stringliteral.json for some interesting results. Searching tiger shows us the domain where we previously saw the encrypted communication take place.

This confirms we are on the right track!

At this point, I had to spend a lot of time reviewing the disassembled code in Ghidra to figure out where to go next. During my search, I found a lot of intriguing references, such as those in the below image, which I unfortunately didn't have time to check out further. I'm assuming this may be used by developers to troubleshoot the game.

However, I was able to narrow down where we might be able to hook into the encrypted communication.


Using Ghidra to investigate requests

Searching through the UnityFramework binary in Ghidra, I wanted to find references to their HTTP client since this had to be instrumental in building requests, along with decrypting them. I soon came across this code:

Ghidra pseudocode from UnityFramework

The compression code caught my attention. It appeared that, before encrypting the contents of a request and sending it off to the silver-tiger API, the data was first compressed. I had a strong hunch that this is where we could finally begin to hook into the app and uncover some of the communication.

Hooking into Solitaire Cash with Frida

Finally, we're at a point where we can review the plaintext data being sent to the Papaya servers. In the past, I've used Frida extensively with Android applications and some iOS apps, but hooking into functions within UnityFramework would prove to be less straightforward.

Using ChatGPT and again referencing some hacking forums, I developed this Frida script. Note that the offset used can be pulled from the IL2CPPDumper output in the dump.cs file by searching for CompressionUtils

dump.cs snippet where Compress offsets are found

After running the Frida script with
frida -U -f "com.papaya.solitairecash" -l ./frida-intercept-solitaire-deck.js and starting a new game, we immediately see this output:


Taking a look at the deck shown, which I've also uploaded as a Gist here, and comparing it side-by-side with my game's screenshot, it's clear that we have access to the entire deck as soon as the game starts.


Closing thoughts

I've shown in this article that a determined attacker can immediately intercept a game's deck, determine the best sequence of moves, and achieve the highest score possible.

This article isn't meant to call out Papaya Gaming, nor do the heavy lifting for would-be attackers, but to instead showcase that no amount of security measures or tactics can conceal critical data that should not be shared with the client.

When it comes to people wagering their money, sometimes with prizes up to $50, there is a real risk that thousands of dollars, if not more, could be won by people using software to cheat the game.

For the same reason that poker websites don't share the deck and cards of other people until it's absolutely necessary, Solitaire Cash might benefit from a "just-in-time" system of information delivery where cards are sent to the user as they require them.

Indeed, a solution like this would require redesigning how their server and client communicate. The requirement for a stable network might negatively impact user experience. But, this would provide a much more "totally fair and skill-based" game compared to what is currently in place.