- Overview
- Score List Attack Tools
- Typical Types of Protections
- Recognizing Vulnerability
Typical Types of Protections
Let's look at several examples of the types of protections we found online and how they can be bypassed. While we won't specifically name any sites or games, these examples illustrate techniques that are actively in use. We'll start with the completely inadequate and move to the more secure.
No Protection
When performing the research for this article, we were a bit surprised at how many sites (20%) had no protection. When we say "no protection," we mean score lists that can be altered using a URL similar to the following:
http://www.targetsite.com/game/newscore.php?name=myname&score=99999
As you can see, anyone could post any score or name by simply switching out the name and score value.
Minor Protection
Many game programmers (30%) seem to know that sending scores via an HTTP GET request is insecure. As a result, they use HTTP POST requests, which can send the same type of information behind the scenes. The problem with this strategy is that the form variables are still being passed as plain text and can be altered via proxy programs. Figure 1 shows a screen shot of a target's POST data as it appears in WebProxy. Notice how easy it is to change the score and resubmit the variables.
Figure 1 POST variables in WebProxy.
Medium Protection
Most of the types of protection (or lack thereof) discussed to this point are found in homegrown programs that reside on individual servers. As a result, these games are not widely played or very popular. Their score lists are like low-hanging fruit and quickly become overrun by obviously false scores. But in all honesty, not many players or game owners will care or notice.
On the other hand, several web sites are dedicated to high-quality Flash and Java games. These sites often require a login, which is used to track high scores and promote competitions. Because these sites make money from advertising and selling downloadable versions of the online games, they have a vested interest in keeping score list hackers from creating havoc. Therefore, it's common to find protection built into the games. This usually involves some sort of homegrown obfuscation or encryption algorithm that merges the username with the score to create a data string or checksum that cannot be easily deciphered.
Unfortunately, these games can be decompiled. As a result, the entire encryption routine can be copied and placed into a new program that can make valid checksums on the fly. The following example demonstrates how both Java and Flash games can be decompiled to give up their secrets.
Java Applets
In this case, a user was timed on a particular event. Upon completion, the user's time would be posted to a score list. Since the game was packaged in a JAR file, we first monitored the network connection and located the full path to the game file online. The path is often included in the game's HTML page, but running a sniffer is the quickest and most accurate way to determine which files are downloaded.
Once the JAR file was obtained, we loaded it into DJ Java Decompiler, which showed us a list of the class files. We searched through the files and found what we were looking for:
String s1 = md5("15:51:15 15/20/1925"); String s2 = "?result=" + MyXOR(s1, mySpeeeed.name) + "&result2=" + MyXOR(s1, mySpeeeed.time); s2 = s2 + "&checksum=" + md5("IamACoolAntiCheatCheck" + s2); URL url = new URL(mySpeeeed.getCodeBase() + "highest.php" + s2);
Contained in these four lines is the code responsible for creating a checksum value meant to validate the posted score. When the player completes the game, the score is passed into this algorithm, which creates a checksum that is then passed to the server. At the server, the score is again passed into a similar routine, where a new checksum value is created. The server-generated checksum is then compared to the posted checksum, and if they match the score is considered valid.
The problem is that an attacker can easily see this entire algorithm! All she has to do is copy this code into her own Java program, and the protection is nullified. So much for a secure score list.
Flash SWF Files
Flash files are just as susceptible to these kinds of hacks. The only difference is the program used to decompile the source code. For this example, we'll look at a protection scheme used by a game site that hosts about 15 games. What stuck out about this particular site was that the same obfuscation routine was used by all the games. In other words, by cracking one game, the attacker could crack them all.
To protect the score, a function is called that takes the game number, score, sub-game value, and the game's URL and merges these values to create a chickBack checksum value. Here's the algorithm:
function fMakeMe(g, s, t, u) { var moveMent = 1; for (i = 0; i < g; i++) { moveMent = moveMent + s; } // end of for moveMent = moveMent * u.length; moveMent = moveMent + t; chickLeft = g; chickRight = s; chickFore = t; chickBack = moveMent; chickName = u; } // End of the function fMakeMe(20, _root.nScore, 1, _url);
This simple algorithm can easily be re-created in JavaScript, PHP, or Perl. As a result, an attacker could easily script a score list update web page to give himself a top score at will.
If you're a programmer who uses this type of internal protection to keep score list hackers from posting invalid scores, we recommend a new solution. While encryption functions cut the number of potential hackers from a few million to a few thousand, the score list will eventually become a victim.
Strong Protection
A few sites take score list security seriously and combine several protection technologies to create a fairly strong obstacle to hackers. Unfortunately, even games in this category are not completely secure because the code ultimately resides on the end user's machine. This single fact gives the attacker an upper hand, albeit a slight one. Let's consider two techniques that we used to bypass what we would consider to be strongly protected game files.
The Map Hack
The first example combined numerous types of protection that made it very hard to post a false score. The majority of the protection was tied to the fact that the score is not passed as plain text, but as an encrypted value. For example, here's a losing score of about 3,000:
DAA88DCCABDEB2D89E97D9AAA7A8BBD6D7E5EBD8CD9998A568AB6F6DD999E16E97A7AB9FC98BD8D0D06A9C89DDCF9CA0AAA1CFA8ABA892DA98DAB48DD9DAD6C8DD9E5BE4A799ADE29BCCE3AEA5A8AC889D67A99C66D99AD5AA9598D59FC9D6DDD0D3A7A6A8DFC4709BCEA4AB95B8E797DAC9DAD4E08CE2CEC89BA55BE19C7CACC6DC9EAAB2C1A2A098
This string of characters was created using the following algorithm:
String s = new String(); String s1 = user.getPlayerData(); if(s1 != null) s = s + "playerData=" + URLEncoder.encode(s1) + "&"; String s2 = "" + score; if(s2 != null) s = s + "score=" + URLEncoder.encode(s2) + "&"; String s3 = user.getHighScoreParameter("clpID"); if(s3 != null) s = s + "clpID=" + URLEncoder.encode(s3) + "&"; String s4 = user.getHighScoreParameter("language"); if(s4 != null) s = s + "language=" + URLEncoder.encode(s4) + "&"; String s5 = user.getHighScoreParameter("country"); if(s5 != null) s = s + "country=" + URLEncoder.encode(s5) + "&"; String s6 = user.getHighScoreParameter("style"); if(s6 != null) s = s + "style=" + URLEncoder.encode(s6); s = "data=" + Encode.htmlEncode(Encode.encode(s));
Every attempt at re-creating a valid post failed. So, being the impatient sort, we started to look for an alternative solution.
While collecting information, we noticed that the game downloaded a file each time the player advanced a level. Using Ethereal, we were able to re-create the file and found that it contained the information shown in Figure 2.
Figure 2 Map file.
After a few seconds of analyzing this file, we determined that it was actually a map file for the game (it was also named map01). In addition, this file includes other game variables, one of which is levelPoints. We tested this theory, monitoring the various points awarded to us for each level, and were excited to find that we were correct.
The next step was to figure out how to trick the game into loading our map file instead of the real map file. After a few minutes of thought, we took a look at another proxy program we keep in our arsenal. The Charles program is similar to WebProxy except that it sports a rather handy DNS spoofing feature (see Figure 3).
Figure 3 Charles DNS spoofing.
DNS spoofing attacks are rarely discussed, but they're truly dangerous; they can trick a victim into divulging private and sensitive information to an attacker. Basically, DNS spoofing allows an attacker to redirect a victim from a valid site such as PayPal.com to a site that looks just like PayPal.com. When the browser attempts to connect to the real PayPal.com site, it will request an IP address from a DNS server. The IP address is then used to establish the connection. With DNS spoofing in place, an attacker can insert his own IP address into the reply, which is then used by the browser. If the fake IP address looks like PayPal.com, the victim will never know the difference.
In this case, our victim is the game. First, we set up our own web server online that hosted our fake map file with an altered levelPoints value. Then we configured Charles to perform DNS spoofing on requests to the game site and redirect these requests to our own site. This step proved to be tricky thanks to some delay in the browser, but after three tries the game was tricked into downloading our map file that included a bonus score of six million. Once the map file was in place and being used by the game, we turned off the DNS spoofing and let the game process our "valid" bonus score. As expected, the score was permitted and we had a top spot on the score list.
Flash Hex Editing
We've already discussed the problems related to being able to decompile the game to view the source code. Using this method, an attacker can view sensitive algorithms that can be reverse-engineered to create valid scores. While this plan often succeeds, in some cases this method of attack won't work. For example, sites often use "reality checks" on the server to verify various components of a submitted score, or a score post may include several seemingly unrelated pieces of data that are actually used in the score validation.
After experiencing this scenario several times, we wondered whether there was some way we could alter the code of the program. The problem is that the game is stored as a hex file. While it's possible to decompile the file into readable code, you can't make any changes in the decompiler program. But you can alter the hex code of the game.
We started analyzing the physics of the target games to see whether there was some way we could give ourselves a higher score. This proved to be a bit challenging because we had to figure out how the hex code related to the source code, but after a few attempts we started to notice patterns. For example, when a variable was assigned a hard-coded integer value, we would always see 07 followed by the hex equivalent of that integer. Thus, if we altered the existing value to something more suitable, we could change the scoring engine of the game.
To illustrate this strategy, we'll look at an example that used a timing function to validate the score. Thanks to a closely monitored timer, an attacker couldn't just post a score; instead he had to post three consecutive posts within a metered period of time. Not impossible to defeat, but why focus on the score-posting function when we could simply alter how the score was calculated?
We noticed that each time a target was killed, an update was made to the final score:
root.controls.score = _root.controls.score + add_score;
The add_score variable in turn was given its value from an enemy_mine object, which was assigned its value each time a new level was started. Basically, we learned this by analyzing the code and tracing it back through the program until we found the score's source. As you can see below, the level_stats array holds various values; the score per kill is shown in bold (50):
level_stats[1] = new Array("two_legsA", 6, 22, 8, 0.100000, 50, 100, 20);
Once we located this value, we then had to find the corresponding location in the hex file (0x32 = #50):
0003e740h: 00 07 32 00 00 00 06 99 99 B9 3F 9A 99 99 99 07 ; ..2....™™¹?š™™™.
We next updated 07 32 00 to 07 4E 20, which altered our level_stats line as follows:
level_stats[1] = new Array("two_legsA", 6, 22, 8, 0.100000, 20000, 100, 20);
Now for each kill we would score 20,000 points. After testing the game locally, we found that the hack worked as expected.
We were hoping to be able to just use the local file to submit the score, but this didn't work. Internet Explorer replaced the updated SWF file with a newly downloaded one that didn't contain our hack. So once again we turned to Charles and performed a DNS spoofing attack on the game's web site, which resulted in the loading of our own game file. We disabled the spoofing and played the game until we had a high score and let the game post the score for us.