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:


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

010100100110000101101110011001000110111101101101010000110110100101110000011010000110010101110010

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-----