or how I learned not to trust the client.
Mathletics is an online math learning platform which, among other things, hosts global competitive math games. I took a look at their platform after hearing about it from a friend, and was curious about how points are handled on the client side (yes, I wanted to know if you could cheat the system). When you sign in to mathletics and press "Play", you are directed to the "Live Mathletics" page: From there, I tried playing a normal game against computers, and checked what was happening behind the scenes: Of course, it took a while to find out what exactly was happening, but in the end, it was pretty simple. First, the client requests a new match by POSTing the specs to their wonderful IIS servers (/LiveMathleticsMatchEngineService-R167/MatchService.asmx/FindMatch):
{
"request": {
"IsComputerPlayerOnly": true,
"CultureCode": null,
"UserAuthToken": "XXXXXXXXXXXXXXX-XXXXX_XXXXX_XXXXXXXXXXXXXXX,",
"GameLevel": 1,
"Speed": 4
}
}
The server responds with something like this:
{
"d": {
"__type": "PppLearning.Mathletics.MatchEngine.Service.ProxyEntities.MatchResponse",
"MatchID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"StartUTC": "\/Date(1234567890123)\/",
"UserMatchList": [],
"Seed": "12334567",
"ScoreBoardServiceUri": "http://mzaue2.liveresults.mathletics.com/LiveMathleticsScoreboardService-R167/",
"IsServiceEnabled": false,
"CurrentServiceVersion": null,
"IsError": false,
"ResponseCode": 0,
"ResponseMessage": null
}
}
Note the Seed and MatchID values. Once this config has been recieived, stuff goes on as usual and you play your game. Once the match is over, the results are POSTed:
{
"request": {
"UserAuthToken": "XXXXXXXXXXXXXXX-XXXXX_XXXXX_XXXXXXXXXXXXXXX,",
"LevelID": 1,
"TotalTime": 60,
"Attempts": 43,
"IsComputerPlayerOnly": true,
"OperationEnum": 0,
"MatchId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"UserScore": {
"Score": 41,
"Status": 2
},
"SessionID": null,
"bonusLevelIncremented": false,
"Hash": "5915f7c9c5a9f3b94c4c66cfc1ba3e34"
}
}
The server responds with an array as well, but it's mostly full of gibberish (besides the confirmation that we received 41 points). At this point, I knew there must be a way to fake the amount of points received, especially considering the amount of trust the server put in the client for other parts of the response. The tedious task of revese-engineering the javascript began... and with great success! The "Hash" that is included to ensure validity of the results is actually a sad combination of various match params:
md5(MatchId + "-" + UserScore.Score + "-" + UserScore.Status + "-" + magicalgo(Seed)); // Muh Security
The only difficult part in all of that was finding the magical algorithm (magicalgo) that they processed the match seed with, which is actually kind of retarded:
function magicalgo(b) {
var a = b || "333";
a = a.substr(a.length - 3);
if (a === "000") {
a = "333";
}
return b % a;
}
Once all this was figured out, all that was left was to write an appropriately-titled userscript to provide a sufficient interface for use:
The script is available here. Load it on any page of the "Live Mathletics" and press Shift + D to show the interface. Mathletics donates $1 to UNICEF for every 250,000 points earned by mathletics users. Imagine the difference we could make with this script...