eurica

numbers for people.

HTML5/Facebook Game Hacking How-to: Mars Frontier

Fair warning, about 30 seconds after I published this I was banned from Mars Frontier, so if that’s the kind of thing that bothers you, read the terms. This is just me sniffing around out of curiosity.

I gave Mars Frontier a try. It’s a Facebook game that’s a proof-of-concept by YC-backed Spin Punch. It’s the first Facebook game I’ve tried, but I think it’s standard in that it wants you to babysit it, mainly by clicking your resource harvesters every few hours and telling them to “Collect All”.

I’m not going to do that.

The application is all in JavaScript, so I naively assumed I could just inspect the button in the DOM and see what gets triggered. But it looks like everything is in a <canvas> tag.

Ok, next up, let’s see what the network traffic, when I click on “Collect All” there’s an XHR request with these parameters:

myarg:[["CAST_SPELL",0,"HARVEST_ALL"]]
serial:3
session:c5fdead974f4af429859b63c8ecbccd74f595313b710a16410d7d73abb31d7bc


That’s promising. I assume the words “CAST_SPELL” are because Spin Punch is building a generic game engine. Unfortuneatly it’s not simply a case of sending an ajax request to that URL, I tried a few ways, but I wasn’t very optimistic as soon as I saw “signed_request:xO4tDfTCkIfJG…”

No worries, let’s call the JavaScript function directly. A quick “Save As, Webpage Complete“, and “Find in Files” for “HARVEST_ALL” turns up “…Y([“CAST_SPELL”,0,”HARVEST_ALL“])…” in compiled-client-CSVa3c7acb0f59bfe2e31895fae07e3e798V.js which is great.

But opening the Console (Ctrl+Shift+J in Google Chrome, which is what I’m using here) and trying to run Y directly gives us “ReferenceError: Y is not defined”. Since the actual game is running in an IFrame, we have a few options, but the most obvious is to run the script in the context of the IFrame:

window.frames[0].eval('Y(["CAST_SPELL",0,"HARVEST_ALL"])')

returns

Unsafe JavaScript attempt to access frame with URL https://mfprod.spinpunch.com/?spin_campaign=www.marsfrontier.com from frame with URL https://apps.facebook.com/marsfrontier/?spin_campaign=www.marsfrontier.com. Domains, protocols and ports must match.

Except that doesn’t work, since we’re trying to hit a different domain — and cross-site scripting is usually associated with acts more nefarious than clicking “Collect All” in a video game. We have a few choices:

  • Figure out the URL of the IFrame and open it up somewhere we can hit it with a console. Getting the URL is non-trivial since window.frames[0].src returns the same error.
  • Run Chrome with the “–disable-web-security” flag (this is actually what I did, since it un-errors all sorts of things you shouldn’t do)
  • Change the context of the console to the IFrame.

Switching the console to point to “iframe_canvas_fb_https (mfprod.spinpunch.com)” works so well it makes me sad that we even tried to build webapps a decade ago. (The second option works just fine, but this doesn’t open up my primary browser to XSS attacks).

Now all that’s left is to run the collect all command every minute:

f = function(){Y(["CAST_SPELL",0,"HARVEST_ALL"]); };
window.setInterval(f,60*1000);

Best of all, the game remains playable.

…and I thought that was it, but that’s not enough. It worked while I was testing, but after a while the app times out. I tried to find some functions that would convince Mars that I’m still there, clicking on stuff:

f = function(){Y(["CAST_SPELL",0,"HARVEST_ALL"]); Dm(); canvas.click() };
window.setInterval(f,60*1000);

But that doesn’t seem to work. Fortunately we aren’t out of tricks.

Let’s go back to running Chrome with Web security off and now I’ll just load the page and hit “Collect All” every time.

We can’t load it in an iframe because Facebook sends a “X-Frame-Options:DENY” presumably to prevent clickjacking (which is pretty close to what we’re doing, and Google take that stuff seriously :). But there’s no reason we can’t just pop it open in a new window.

A quick check of

w = window.open('https://apps.facebook.com/marsfrontier/?spin_campaign=www.marsfrontier.com');
// wait for it to load...
w.frames[0].eval('Y(["CAST_SPELL",0,"HARVEST_ALL"])')

Now all we need to do is script it to do it regularly, we pop open the console, and paste in:

w = window.open('https://apps.facebook.com/');
f_load = function() {
w.location = "https://apps.facebook.com/marsfrontier/?spin_campaign=www.marsfrontier.com&zzz="+(new Date()).getTime()
window.setTimeout("w.frames[0].eval('Y(["CAST_SPELL",0,"HARVEST_ALL"])')",30*1000)
}
f_load();window.setInterval("f_load()", 30*60*1000)

And now it’s happily running in its own window in the background — at the somewhat annoying cost of leaving my primary browser insecure.

Addendum (and actual cheats):

At first, this technique doesn’t seem to be useful for real cheating, running “for(i=0;i<1000;i++) Y(["UNIT_REPAIR_TICK"])" doesn't seem to have any effect. So it's more about cheating their business model than anything else, but there's still a lot you can fiddle with.

There's a gamedata object that you can play around with and though you can't change anything that's enforced on the server, changing some aspects of the gamedata object seem to work:

gamedata.attack_time=600 //Gives you 10 minutes to attack a base
gamedata.aggro_radius=-1 //Causes enemies to ignore you unless they’re hit by splash damage.
//At least it did at first, the weaponized Curiosity rovers seem to ignore this.

Also: “gamedata.tech.load_runner_production.cost_iron[1]=1″ and “gamedata.tech.load_runner_production.cost_water[1]=1″ seems to work, but changing the time didn’t. That makes sense in that the build commands are asynchronously performed on the server.

For some reason
gamedata.buildings.central_computer.build_cost_iron[3]=100
gamedata.buildings.central_computer.build_cost_water[3]=100

sends back some server errors though.

But there’s still a lot of wiggle room with the stuff that’s enforced on the client side. One option is to pick a particular unit, build a lot of them and then super-charge them:

amedata.units.gun_truck.maxvel=50
gamedata.units.gun_truck.max_hp[1]=999999
gamedata.units.gun_truck.armor=999999

Give you zippy powerful gun trucks that (combined with an aggro_radius of -1 gives them a goofy bad advantage). Turning these cheats into something easy to use — like a bookmarklet is left as an exercise for the reader.

Actually, you can nerf every unit except one, then destroy all the other units:
neuter_units=function(my_unit, my_level) {
gamedata.aggro_radius=0
gamedata.attack_time=900
var gu=gamedata.units;
for(var u in gu){
console.log(u)
console.log(gu[u])
gu[u].max_hp=[1,1,1,1,1,1,1,1]
gu[u].armor=1
gu[u].maxvel=.1
gu[u].turn_rate=1
}
gu[my_unit].armor=99999999
gu[my_unit].max_hp[my_level-1]=99999999
gu[my_unit].turn_rate=400
gu[my_unit].maxvel=25
}
neuter_units("muscle_box",2)

Comments are closed.