What is the best way to stop people hacking the PHP-based highscore table of a Flash game

29,864

Solution 1

This is a classic problem with Internet games and contests. Your Flash code works with users to decide a score for a game. But users aren't trusted, and the Flash code runs on the user's computer. You're SOL. There is nothing you can do to prevent an attacker from forging high scores:

  • Flash is even easier to reverse engineer than you might think it is, since the bytecodes are well documented and describe a high-level language (Actionscript) --- when you publish a Flash game, you're publishing your source code, whether you know it or not.

  • Attackers control the runtime memory of the Flash interpreter, so that anyone who knows how to use a programmable debugger can alter any variable (including the current score) at any time, or alter the program itself.

The simplest possible attack against your system is to run the HTTP traffic for the game through a proxy, catch the high-score save, and replay it with a higher score.

You can try to block this attack by binding each high score save to a single instance of the game, for instance by sending an encrypted token to the client at game startup, which might look like:

hex-encoding( AES(secret-key-stored-only-on-server, timestamp, user-id, random-number))

(You could also use a session cookie to the same effect).

The game code echoes this token back to the server with the high-score save. But an attacker can still just launch the game again, get a token, and then immediately paste that token into a replayed high-score save.

So next you feed not only a token or session cookie, but also a high-score-encrypting session key. This will be a 128 bit AES key, itself encrypted with a key hardcoded into the Flash game:

hex-encoding( AES(key-hardcoded-in-flash-game, random-128-bit-key))

Now before the game posts the high score, it decrypts the high-score-encrypting-session key, which it can do because you hardcoded the high-score-encrypting-session-key-decrypting-key into the Flash binary. You encrypt the high score with this decrypted key, along with the SHA1 hash of the high score:

hex-encoding( AES(random-128-bit-key-from-above, high-score, SHA1(high-score)))

The PHP code on the server checks the token to make sure the request came from a valid game instance, then decrypts the encrypted high score, checking to make sure the high-score matches the SHA1 of the high-score (if you skip this step, decryption will simply produce random, likely very high, high scores).

So now the attacker decompiles your Flash code and quickly finds the AES code, which sticks out like a sore thumb, although even if it didn't it'd be tracked down in 15 minutes with a memory search and a tracer ("I know my score for this game is 666, so let's find 666 in memory, then catch any operation that touches that value --- oh look, the high score encryption code!"). With the session key, the attacker doesn't even have to run the Flash code; she grabs a game launch token and a session key and can send back an arbitrary high score.

You're now at the point where most developers just give up --- give or take a couple months of messing with attackers by:

  • Scrambling the AES keys with XOR operations

  • Replacing key byte arrays with functions that calculate the key

  • Scattering fake key encryptions and high score postings throughout the binary.

This is all mostly a waste of time. It goes without saying, SSL isn't going to help you either; SSL can't protect you when one of the two SSL endpoints is evil.

Here are some things that can actually reduce high score fraud:

  • Require a login to play the game, have the login produce a session cookie, and don't allow multiple outstanding game launches on the same session, or multiple concurrent sessions for the same user.

  • Reject high scores from game sessions that last less than the shortest real games ever played (for a more sophisticated approach, try "quarantining" high scores for game sessions that last less than 2 standard deviations below the mean game duration). Make sure you're tracking game durations serverside.

  • Reject or quarantine high scores from logins that have only played the game once or twice, so that attackers have to produce a "paper trail" of reasonable looking game play for each login they create.

  • "Heartbeat" scores during game play, so that your server sees the score growth over the lifetime of one game play. Reject high scores that don't follow reasonable score curves (for instance, jumping from 0 to 999999).

  • "Snapshot" game state during game play (for instance, amount of ammunition, position in the level, etc), which you can later reconcile against recorded interim scores. You don't even have to have a way to detect anomalies in this data to start with; you just have to collect it, and then you can go back and analyze it if things look fishy.

  • Disable the account of any user who fails one of your security checks (for instance, by ever submitting an encrypted high score that fails validation).

Remember though that you're only deterring high score fraud here. There's nothing you can do to prevent if. If there's money on the line in your game, someone is going to defeat any system you come up with. The objective isn't to stop this attack; it's to make the attack more expensive than just getting really good at the game and beating it.

Solution 2

You may be asking the wrong question. You seem focused on the methods people are using to game their way up the high score list, but blocking specific methods only goes so far. I have no experience with TamperData, so I can't speak to that.

The question you should be asking is: "How can I verify that submitted scores are valid and authentic?" The specific way to do that is game-dependent. For very simple puzzle games, you might send over the score along with the specific starting state and the sequence of moves that resulted in the end state, and then re-run the game on the server side using the same moves. Confirm that the stated score is the same as the computed score and only accept the score if they match.

Solution 3

An easy way to do this would be to provide a cryptographic hash of your highscore value along with the score it self. For example, when posting the results via HTTP GET: http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095

When calculating this checksum, a shared secret should be used; this secret should never be transmitted over the network, but should be hard coded within both the PHP backend and the flash frontend. The checksum above was created by prepending the string "secret" to the score "500", and running it through md5sum.

Although this system will prevent a user from posting arbitrary scores, it does not prevent a "replay attack", where a user reposts a previously calculated score and hash combination. In the example above, a score of 500 would always produce the same hash string. Some of this risk can be mitigated by incorporating more information (such as a username, timestamp, or IP address) in the string which is to be hashed. Although this will not prevent the replay of data, it will insure that a set of data is only valid for a single user at a single time.

To prevent any replay attacks from occurring, some type of challenge-response system will have to be created, such as the following:

  1. The flash game ("the client") performs an HTTP GET of http://example.com/highscores.php with no parameters. This page returns two values: a randomly generated salt value, and a cryptographic hash of that salt value combined with the shared secret. This salt value should be stored in a local database of pending queries, and should have a timestamp associated with it so that it can "expire" after perhaps one minute.
  2. The flash game combines the salt value with the shared secret and calculates a hash to verify that this matches the one provided by the server. This step is necessary to prevent tampering with salt values by users, as it verifies that the salt value was actually generated by the server.
  3. The flash game combines the salt value with the shared secret, high score value, and any other relevant information (nickname, ip, timestamp), and calculates a hash. It then sends this information back to the PHP backend via HTTP GET or POST, along with the salt value, high score, and other information.
  4. The server combines the information received in the same way as on the client, and calculates a hash to verify that this matches the one provided by the client. It then also verifies that the salt value is still valid as listed in the pending query list. If both these conditions are true, it writes the high score to the high score table and returns a signed "success" message to the client. It also removes the salt value from the pending query list.

Please keep in mind that the security of any of the above techniques is compromised if the shared secret is ever accessible to the user

As an alternative, some of this back-and-forth could be avoided by forcing the client to communicate with the server over HTTPS, and insuring that the client is preconfigured to trust only certificates signed by a specific certificate authority which you alone have access to.

Solution 4

I like what tpqf said, but rather than disabling an account when cheating is discovered, implement a honeypot so whenever they log in, they see their hacked scores and never suspect that they have been marked as a troll. Google for "phpBB MOD Troll" and you'll see an ingenious approach.

Solution 5

In the accepted answer tqbf mentions that you can just do a memory search for the score variable ("My score is 666 so I look for the number 666 in memory").

There's a way around this. I have a class here: http://divillysausages.com/blog/safenumber_and_safeint

Basically, you have an object to store your score. In the setter it multiplies the value that you pass it with a random number (+ and -), and in the getter you divide the saved value by the random multiplicator to get the original back. It's simple, but helps stop memory search.

Also, check out the video from some of the guys behind the PushButton engine who talk about some of the different ways you can combat hacking: http://zaa.tv/2010/12/the-art-of-hacking-flash-games/. They were the inspiration behind the class.

Share:
29,864

Related videos on Youtube

Alejadro Xalabarder
Author by

Alejadro Xalabarder

Flash developer / games designer / creative technologist. Learning JAVA and C# + answering Flash / ActionScript questions.

Updated on May 14, 2020

Comments

  • Alejadro Xalabarder
    Alejadro Xalabarder almost 4 years

    I'm talking about an action game with no upper score limit and no way to verify the score on the server by replaying moves etc.

    What I really need is the strongest encryption possible in Flash/PHP, and a way to prevent people calling the PHP page other than through my Flash file. I have tried some simple methods in the past of making multiple calls for a single score and completing a checksum / fibonacci sequence etc, and also obfuscating the SWF with Amayeta SWF Encrypt, but they were all hacked eventually.

    Thanks to StackOverflow responses I have now found some more info from Adobe - http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html and https://github.com/mikechambers/as3corelib - which I think I can use for the encryption. Not sure this will get me around CheatEngine though.

    I need to know the best solutions for both AS2 and AS3, if they are different.

    The main problems seem to be things like TamperData and LiveHTTP headers, but I understand there are more advanced hacking tools as well - like CheatEngine (thanks Mark Webster)

    • mpm
      mpm about 12 years
      you should put score limits, there is no reason why you could not it, per level , in total,ect ...
    • Mike
      Mike over 11 years
      First link is not available any more
  • Adam
    Adam over 15 years
    Anyone following this approach must also include a time, and validate the time, otherwise you are vulnerable to replay attacks.
  • Alejadro Xalabarder
    Alejadro Xalabarder over 15 years
    The problem with that approach is that these cheats brag to each other on message boards etc, so it wouldn't really deter them.
  • James Aguilar
    James Aguilar over 15 years
    I made a online game community, and we designed our entire reward system to reward a top 10th percentile score instead of just the highest scorer and it has worked out great, so trying to sidestep around the problem helps too!
  • Luke
    Luke over 15 years
    This is a technique I've used in the past as well for a voting system. There is no way you can prevent people from cheating, what you can do however is record their behavior so you can check if there are cheating. For our voting system we used to store time stamps and IP addresses. [cont.]
  • Luke
    Luke over 15 years
    That way we could at least see patterns of cheating behavior and disqualify people when necessary. We've used it successfully a number of times.
  • Chris Garrett
    Chris Garrett over 14 years
    But that also removes the positive feedback of an instant high score for actual players, so you're reducing the quality of game play.
  • aehiilrs
    aehiilrs almost 14 years
    Wireshark. Wireshark is always the hole.
  • Sigtran
    Sigtran about 13 years
    yeah, ive tried this exact method... well... i somehow got legit users being logged as cheaters (the function works all the time on my computer, but it doesnt work on some other computers)... so now I just allowed for cheating... I log people who dont pass the above check & I log people with really high scores & then manually abuse them... by making their scores * -1 :P they usually take the joke well.
  • Kerrek SB
    Kerrek SB over 12 years
    Nice! In short, unless the game runs on the server (or at least has a sanity check running on the server), you cannot prevent abuse. Same problem with multiplayer shooters: If the client is modified to produce better aiming, you can't prevent that in the network protocol.
  • mkto
    mkto over 12 years
    "One way that it does this is by assigning 2 randomized values from php into the flash (which you cannot grab or see even if running a realtime flash data grabber)" - Please enlighten me, is there a way to pass values from php to flash without being monitored by firebug or any other tool?
  • I3ck
    I3ck over 7 years
    I guess one could also log all user actions and replay them on the server to verify / determine the high score