March 5, 2017
Brack im Sack - augmented reality contest
Brack.ch - one the largest online stores in Switzerland for electronics, software and all things digital are currently having an augmented reality contest. They place virtual bags all around Switzerland and as soon as somebody finds one, a new one spawns at a new location. This concept was probably inspired and adapted from the mobile game Pokémon Go. Here is how you participate:
- 1. browse brackimsack.ch[1] on your smartphone
- 2. accept that the site will request your current location
- 3. complete the tutorial to get a CHF 10.- voucher
- 4. Sign up and load the first contest
- 5. Walk in the right direction with the compass-like UI until you find the item (don't expect that you can walk the whole way, as they might be a few miles away)
Disclaimer
I simply love AR (augmented reality). I like the idea and where things are going in this field. I'm an active player of Pokémon Go and follow the latest news in the community, so I know what is good/bad for the game and what some developers create for astonishing tools just as an addition to the game.
As a tech-enthusiast and software engineer I like to know how things work.
Btw. There is no incentive for cheating, because the prices you can win aren't that great anyway (hairdryer, torch, blender, coffee machine, cutlery, energy drinks, coffee, ...). I did not cheat and will not get any gain from this.
Traffic analysis
While using the site and analysing the traffic one thing had to be clear:
“The client must have the exact coordinates”
The first step was to analyse the requests from my phone using Burp Suite proxy[2] to do a MITM (because the site uses TLS, we need a self-signed certificate installed on the phone) and log the HTTP requests.
Request #1
POST /api/bag/next/47.00000000000000/9.00000000000000?bc=0&api_token=xxx HTTP/1.1 Host: brackimsack.ch X-Requested-With: XMLHttpRequest Accept-Language: en-us Accept: */* Origin: https://brackimsack.ch Content-Length: 0 User-Agent: Mozilla/5.0 Referer: https://brackimsack.ch/de/bag?bc=0 DNT: 1 X-CSRF-TOKEN: xxx Cookie: XSRF-TOKEN=xxx; laravel_session=xxx Connection: close
This is the more interesting request of the two, because this MUST contain some sort of information of the location of the current bag.
Request #2
POST /api/position/log/47.00000000000000/9.00000000000000?api_token=xxx HTTP/1.1 Host: brackimsack.ch X-Requested-With: XMLHttpRequest Accept-Language: en-us Accept: */* Origin: https://brackimsack.ch Content-Length: 0 User-Agent: Mozilla/5.0 Referer: https://brackimsack.ch/de/bag DNT: 1 X-CSRF-TOKEN: xxx Cookie: XSRF-TOKEN=xxx; laravel_session=xxx Connection: close
This request logs the current location to the server every 20 seconds. (Probably to verify the authenticy before they give the more valuable prices to the winner.)
Reverse Engineering
The response of request #1 contains the following (base64 encoded for readability)
AFkXGg9ZQVlPTFVIQ0JMTUNL WVdZFxUcWUFZQlVISkJKS0pL WVdZCxcaGB5ZQVkzHgkSCBoO WVdZCxcaGB4kHQlZQVlZV1kI DxoJD1lBSkhJTEIG
When you decode the string you get an unreadable cipher, which means they obfuscated the response to make it harder to solve ;-)
Client code
As this is a basic web application, all the code to interpret the obfuscated response is in possesion of the client.
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous">
</script>
<script src="/js/all.js"></script>
This snippet is found at the very bottom of the HTML source code.
The content of all.js is minified, which makes the code not readable at all.
var _0x6095=["\x76\x61\x72\x20\x41\x50\x50\x20\x3D\x20\ ... ...
Luckily we have de-minifiers such as: jsbeautifier.org[3]
(function(_0x8953x3) {
var _0x8953x4 = {};
function _0x8953x5(_0x8953x6) {
if (_0x8953x4[_0x8953x6]) {
return _0x8953x4[_0x8953x6]['exports']
};
var _0x8953x1 = _0x8953x4[_0x8953x6] = {
i: _0x8953x6,
l: false,
exports: {}
};
_0x8953x3[_0x8953x6]['call'](_0x8953x1['exports'], _0x8953x1, _0x8953x1['exports'], _0x8953x5);
_0x8953x1['l'] = true;
return _0x8953x1['exports']
}
...
...
return _0x8953x5(_0x8953x5['s'] = 0)
})([function(_0x8953x1, _0x8953x2) {
eval('var APP = APP || {};\x0A\x0AAPP.BROWSERCHECK = {\x0A\x0A ... ...
sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxl ...');
}])
The bottom part is what caught my attention, because it contains an eval and a very large base64 encoded string.
After de-obfuscating that part again, this function was found:
var jsEncode = {
encode: function (s, k) {
var enc = "";
var str = "";
// make sure that input is string
str = s.toString();
for (var i = 0; i < s.length; i++) {
// create block
var a = s.charCodeAt(i);
// bitwise XOR
var b = a ^ k;
enc = enc + String.fromCharCode(b);
}
return enc;
}
};
The second parameter k (for key) is hard-coded as 123.
//console.log(jsEncode.encode(data,\"123\"));
Voilà, when we now execute this function with the HTTP response we get this:
{"lat":"47.0000000","lng":"9.0000000","place":"xxx","place_fr":"","start":10000}
Conclusion
Turns out they really send the exact latitude/longitude of the current bag to the client. But to win a price you would have to spoof your GPS location. Again, I play Pokémon Go and hate spoofers destroying the game.
Sources:
[1] https://brackimsack.ch[2] https://portswigger.net/burp
[3] http://jsbeautifier.org
Tags: brack im sack, augmented reality, reverse engineering
« back
About the author
human, software engineer, tech enthusiast, security researcher
E-Mail: blog@cipher.digital
-----BEGIN PGP PUBLIC KEY BLOCK----- Version: OpenPGP (RSA-2048) xsBNBFjAYL4BCACsmBS6zE+0b7mZVtQhmfnRn3+IIQfT6WlE6izM39Q42yxj Hf2GOZU15Xc1x5RM9ZZx7HnMyTQWJMkwCzEba4Ju8dbn8gbFzLFp+mXAWQVJ NOhsLvt58X/k1nQ3HYaYAbJPFE4k89zlFUjBG+a1Qs0kNg5RkaSTcE4iV6L4 749LYRba1VFK1p3eIFmIh1zQnzwFY1WYJjvXHURZel8MA0BJTkmfOW4MRHZL lz8mjmTeWoRyxismRDprEtGynK7oIb3qUKAIr5MtoyESHBhVR+EpWHP0+06T IfOsrsp8maNztXRQRKZxHzNZj/ayGpxBGO19e0/6jNpWGI5Nflwo/oHbABEB AAHNI0RpZ2l0YWxDaXBoZXIgPGJsb2dAY2lwaGVyLmRpZ2l0YWw+wsByBBAB CAAmBQJYwGDQBgsJCAcDAgkQMsB3T2XG/XYEFQgCCgMWAgECGwMCHgEAAOzo B/4obbCU7u4f8kXQiaqAhSCjjyR5ZzdApPCh9i9XJ0qGTULTUuBrin1JDXSj HoiByL2mYh92+I8S+YMWLMiTQzl9O4wx+A0eDnfwbs5jKJSQt5Pc8NMlwWKU pG+R7escZ7le/qJYMgGPUWzFhgaKi8jueMW/NJSmPu/Tu4V9nhyxG9oaV3oP rF+W0bekP84tDJ477clRSSK9ZzjMbLL1PWuNmCd8Gsnd3fyP1WcadIMDrnBB sb+7AQ9eTywJ4Yzogh+cWjwy+TkkfEyCJ0X2n5WPURWc0YOFVqhcV4TYDR4v CHSbh+r7OVKIjqdQKDJwAUCYeSkePbxJYmzRoaTd2+RgzsBNBFjAYL4BCADE i8WrXxZWn42DlKDpnwTFBo/8asY4SJ22Zagkoj3cVvkechDWqQnWD753y5Xo gymfPnNjoQGmClDaQoZ29kC4kHTmBPICHCCLvV/7YVCZC4WPpSnpklbllmk7 S8WTnyEm09gniGyLVy5st6MYmFDB4VnfXpzVYtpyEOyIfGV+JmuT90L872xc +rI1/UuZA15k8M+ViD2xDlBMz3fbWxbt/KEUvbGoh2RW6SBJl1/z33ainQmO oqygZtHhoFybqf/OUAHzASPcy+E4byWBIqwDDumKWfsd1YYkUgPMIxEvNaU3 2Olh5+2HX1y8WAf5cIfXUDfmZ88HmWVVXAK9JjztABEBAAHCwF8EGAEIABMF AljAYNEJEDLAd09lxv12AhsMAACSqQf/Tz5KsfN3Yr82jXeO7jEWqI8yUaV2 vfK2JNfQXMIYDezIPxZU/sOOz9QF5gzHaLzt6moDQzHTZy9IE6q4l5gH1Wcm 1rX2b2b4ST3ThRzuDcfSCDZvUIAQ0WEBlXJZbCMwV8Rs5vsvv/CeXaT19zMb CGD+23A1dKDSDmnlycCSDlTK0dc4flc8qqsMAXXtV7F370L3r76GQGj/ap57 k8K5l8VOqNCU2E8PJ1nU3Kf0fpaPJCpmDp51iZB6Ndx7ujb3qCzt5ND0Nqpz 8wuA9uuzf7LdYsz6MdDo3u8cBYeT2KA2pOA6W1SJgSx62Z4hFZxS5nseW3al tfhqcXA+Ox3+gw== =GS1N -----END PGP PUBLIC KEY BLOCK-----