📜 ⬆️ ⬇️

The history of hacking all the games in Telegram

Now computer games are everywhere. They are present in the Telegram. I'll tell you about how almost all the games of this messenger were hacked, beating the most first-class players in the tops of the skorbord. I want to share the results of research. On the various methods of hacking, cheating and ways to bypass the logic of games under the cut.



@gamebot


The first game, which was considered a few months ago - LumberJack , playing lumberjack, you need to cut branches so that they would not crush the player. The goal of the game is to cut as many branches as possible in a certain amount of time.


')
Initially, I wanted to practice the graphic reading of games, that is, on the basis of the graphic data on the monitor, to make a decision. The program should emulate the reaction of a person by sending the necessary key combinations based on the situation on the screen. The principle of constructing the logic of the program for the current game is as follows. A screenshot of the 600x1 pixel screen is made on the right side of the tree. Not the whole screen, because the process of taking a screenshot of such a large space takes more time. Then the program at 6 points checks the color of the pixels and, based on this, calculates the player’s trajectory for 6 branches at once. If there is a branch on the right, go left, if not - stay on the right. In one move, 2 hits are made with an ax. Moves are performed, then a screenshot is taken again and the cycle repeats. This will continue until time runs out.



Program code for python 2.7 on Ubuntu 16.04

import os, time import pyscreenshot as ImageGrab from Xlib import display def move_left(): os.system("xte 'key Left'") def move_right(): os.system("xte 'key Right'") def exist_branch(x, y): box = (x, y - 6 * 100, x+1, y) im = ImageGrab.grab(box) rgb_im = im.convert('RGB') x, y = im.size result = [] for i in range(0, 6): r, g, b = rgb_im.getpixel((0, y - 1 - i * 100)) summa = r + g + b if summa < 700: result.append(True) else: result.append(False) return result def get_mouse(): while True: data = display.Display().screen().root.query_pointer()._data x = data["root_x"] y = data["root_y"] print '%s,%s - %s' % (str(x), str(y), exist_branch(x, y)) def main(): start_x = 1543 start_y = 641 while True: branches = exist_branch(start_x, start_y) cons_str = "" for elem in branches: if elem: cons_str += 'Left ' else: cons_str += 'Right ' print cons_str for elem in branches: if elem: move_left() time.sleep(0.03) move_left() else: move_right() time.sleep(0.03) move_right() time.sleep(0.2) try: #get_mouse() time.sleep(5) main() except: print 'Exit..' 

To run, you need to install the following dependencies

 sudo apt-get install xautomation pip install pyscreenshot 

The xte utility is responsible for key emulation in the Linux environment; you can read more here . The pyscreenshot library is responsible for taking a screenshot of the selected screen area, read more here . For the program to work, you need to set the first point (the lowest branch on the right, or the place where it could be located), for this you can use the get_mouse () function. The height between the branches is 100 pixels. The delay between keystrokes and the delay between taking screenshots is set by trial and error. It did not work out less than these values, the program did not have time to process the image or press the keys. An example of the work presented in the video.



800 points no one can score, so that the result of the program can be considered a victory.

The process of writing and debugging took quite a long time, so you need to consider other solutions.

Analyzing HTTP requests, at the end of the game, two types of requests are sent. If the user has not reached a new record.

 POST /api/getHighScores HTTP/1.1 Host: tbot.xyz Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Length: 197 Content-Type: text/plain;charset=UTF-8 Connection: close data=[..some_base_64_code..] 

New record

 POST /api/setScore HTTP/1.1 Host: tbot.xyz User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0 Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Referer: https://tbot.xyz/lumber/ Content-Length: 206 Content-Type: text/plain;charset=UTF-8 Connection: close data=[..some_base_64_code..]&score=some_score 

It is enough to replace some_score with some value, and the new number is added to the table.

In base64, the account information is sent, that is, the id, the name of the player who clicked on the game, the name of the game, as well as the chat id.

It is worth noting that this game belongs to the @gamebot bot, on which there are two more games, Math Battle and Corsairs . The game Math Battle was reviewed in more detail.



HTTP request with the number of points sent similar. It is worth trying to send a request through the inspector in the browser.

Open the debugger mode, open the source code main.min.js. Put a few breakpoints (breakpoints), start the game and find the variable r, which stores the number of points. This value can be changed via the console.



Turning off the debug mode, the function of sending points will be executed and thus you can send an already specified number of points. In order not to click many times, including \ disabling the inspector, it is worth a little to understand the source code of main.min.js, edited by the JS Beautifier service. Here are three interesting features.

 function ba(a, b, d) { var c = new XMLHttpRequest, e = [], f; for (f in b) e.push(encodeURIComponent(f) + "=" + encodeURIComponent(b[f])); c.onreadystatechange = function() { 4 == c.readyState && 200 == c.status && d(JSON.parse(c.responseText)) }; c.open("POST", a, !0); c.send(e.join("&")) } function na() { n && ba("/api/setScore", { data: n, score: r || 0 }, function(a) { e = a.scores; Y(); I(); a["new"] && l && (z = !0, x(M, "shown", z)) }) } function ca() { n && ba("/api/getHighScores", { data: n }, function(a) { e = a.scores; Y(); I() }) } 

The na () function is called when a new record is reached, ca () is needed just to get the scoreboard of the game. By the way, the decision what function to call occurs in the function U () in this line.

 r > Z ? na() : ca(); 

When the r parameter is changed and the na () function is called, the debug mode must be enabled. It should be something like this.



The source code is a bit obfuscated, which complicates its analysis, but the basic things are clear.

The game Corsairs, also related to the @gamebot bot, is solved by all the methods described above. For various requests to the server, I was banned, and I can not be added to the scoreboard, the account is in the ban list. You need to be careful when testing the games of this bot.

@gamee


The @gamee bot was quite popular. Qubo game selected.



Request sent at the end of the game next

 POST /set-web-score-qkfnsog26w7173c9pk7whg0iau7zwhdkfd7ft3tn HTTP/1.1 Host: bots.gameeapp.com Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Content-Length: 247 Origin: https://www.gameeapp.com Connection: close {"score":2,"url":"/game/u0yXP5o-7c87f893be9fbc72d9dae3826b7037a53d331dd1","play_time":76,"hash":"{\"ct\":\"q3Qv2QtaFAlihlaAvZSp+ahHSq7y4Uut5bLd80nYX1pp9rz0jh03si8Nx2HIe91x\",\"iv\":\"93b55f8f4105a269e74a58bec5e0e0a0\",\"s\":\"da96bc85b70f149d\"}"} 

Change the score, as in the previous bot, will not work. Hashes that sign scores, play_time, etc., are generated so that you can’t score points in such a simple way. The debugger did not help much, as there were so many variables in it.



I had to analyze the code manually. In the source code of the game page you can find such a piece of code.



It is noteworthy that the scripts are not connected in the usual way, but through a query in js, and therefore they are not visible in the inspector. Curious two files are gameUI.min.js and gameUIdesktop.min.js . In the first file was found such a function, which is a method of the gameeUI object.

 saveScore: function(e) { var a = window.location.pathname, t = gameeUI.user, n = (new Date).getTime(), o = $("#dataId").data(), i = CryptoJS.AES.encrypt(JSON.stringify({ score: e, timestamp: n }), o.id, { format: CryptoJSAesJson }).toString(), s = { score: e, url: a, play_time: gameeUI.playTime, hash: i, username: t, anonymous_id: gameeUI.anonymous_id }; if (isFacebook()) { var r = FacebookUserData.getUserData(); s.app_scoped_user_id = r.app_scoped_user_id, s.user_id = r.user_id } gameeUI.sendScoreData(s) } 

Obviously, the input parameter e - points sent to the server. By sending the string gameeUI.saveScore (some_score) to the console, you can get the coveted number of points.

This bot includes the games "3 + 3", "Karate Kido", "Space Traveler", "Hexonix" and so on. All are solved as described above. It can be concluded that when one of the games of the next bot is solved in a certain way, then the rest of the games of this bot are solved.

@GamesHDBot


Quite complicated by logic, you can call the game "Galaxy Space Shooter". Beautiful graphics, a lot of splashes in the game, you can earn points and coins.



But it was enough to look into the inspector, to find the TlgAdapter object and its putScore method.



@ludeiBot


Curious was this bot. Game for testing - "Jumping Submarine".



Request at the end of the game next.

 POST /v1/setscore HTTP/1.1 Host: telegram-games.ludei.com Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/json;charset=UTF-8 Content-Length: 73 Origin: https://angrypianohtml5.ludei.com Connection: close {"inlineId":"token","userId":"user_id","score":some_score} 

Points to change just. It is noteworthy that the user_id is sent in open form. If you find out the id of all users in the chat, you can make the following spam type attack. This can be done through the Telegram API.



The idea is that you can not only change the points of another player in the table, but also spam in the general chat by sending notifications about the new winner of the current game. This is a pretty serious attack if done correctly. It is impossible to determine who is actually winding up his glasses, and even after removing a person from the chat (the attacker or the one who is “spamming”), one can still continue to behave indistinctly. You can "spam" on behalf of any member of the chat, including the admin, forcing the admin to exclude certain people from the chat. The only way to stop such obscurantism - chat admin to delete the message with the proposed game.

This bot also includes the games "iBasket", "Sumon", "Angry Piano".

@MeduzaGameBot


This bot includes 5 games and can be used for the type of attack described above.
For example, the game, all familiar sapper.



Request the following.

 GET /embed/telegram-game-bot/?user_id=user_id&inline_message_id=chat_id&score=score HTTP/1.1 Host: meduza.io Accept: application/json, text/plain, */* Accept-Language: en-US,en;q=0.5 Connection: close 

Here is even easier. The usual GET request, knowing the chat id and user id, you can arrange spam.

@foragamesbot


There is only one game - "DevRunner"



After the game ends, two requests are sent.

 POST /game/scored HTTP/1.1 Host: devrunner.fora-soft.com User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded Referer: https://devrunner.fora-soft.com/?uid=453743655&imi=AgAAAKwAAAAnlAsbCh1sirM6oOQ Content-Length: 97 Cookie: _ga=GA1.2.2104866484.1507563316; _gid=GA1.2.1543721993.1507563316 Connection: close user_id=453743655&score=111111&chat_id=&message_id=&inline_message_id=AgAAAKwAAAAnlAsbCh1sirM6oOQ 

 POST /game/scores_image HTTP/1.1 Host: devrunner.fora-soft.com Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded Content-Length: 47 Connection: close crutches=1&bugs=2&scores=31&level=0&rank=Intern 

The second query allows you to see where the player with the current score is. Before testing was started, the player was in the first place with 2000-3000 points, and the funny thing is that about 13k people played the game. It turns out none of these people thought of outwitting this game :) By replacing requests with the number of points 111113 was in the first place.



@brugamebot


Bot with this game can definitely be called the most boring among all.

An example is chess. No rating, notification of victory, and so on. The game is only client-side.



@microgamesbot


This game is also alone in this bot, “Jumper Frog”.



Request at the end of the game

 POST /score HTTP/1.1 Host: microgames-ijwqbqxfic.now.sh User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/json; charset=UTF-8 Referer: https://microgames-ijwqbqxfic.now.sh/games/jumper_frog/?token=eyJhbGciOiJIUzUxMiJ9.eyJnYW1lIjoia... Content-Length: 14 Connection: close {"score": 700} 

Part of the token is cut, but the meaning should be clear. Only score is sent to the server as data. It is easy to replace it. The question is different, how did the server recognize that a certain person was playing and write it in the scoreboard in the chat telegram? And everything is simple - the server takes the data from the Referer header token mentioned above. A strange crutch, mocked. The logical question is, what happens if you go to a direct link to this game ? Nothing special, only when you score points, in response to the request above (without Referer) the answer of this kind will come.



An error is caught that reveals the absolute paths to the files. In compatibility with LFI, for example, it is already possible to get a lot of useful data. But since the task was to detect shortcomings of games, and not to conduct a full-fledged pentest of a specific game, it was decided to stop at that.

@foxgamebot


Now the game “Tricky Fox” will be described, which is the most complex in its structure among all, has a high level of security relative to all other games, and it took a lot of time to bypass it. The author is well done, he created a very suitable game, it was interesting to disassemble it. The process of solving this game I want to describe in detail. The player's task is to eat the hens, jumping from island to island. By clamping the left mouse button or finger on the phone screen, you can force the fox to fly the desired distance.



When the game ends, such a request is sent.

 POST /game-api/expandScore HTTP/1.1 Host: play.alexbelov.xyz Accept: application/json, text/plain, */* Accept-Language: en-US,en;q=0.5 Content-Type: application/json;charset=utf-8 Content-Length: 432 Connection: close {"hash":"d5139c23e94113af55baa5a5b48c42f03c0438d768588aa28057f3da72c938aa4e9db142b6ba0dacbde4aa0fd6ebedf1447d4493f53608a4ff321fee1549c115fc5e3eca98c23c45539982ae08a8ce2627db050eeb73fb13339727b03294739d98b88e9b372be8df37689393794d894108e6c5afe024bfbe451a955100d02649eb5e8fb0091a2186f2303be5a2d4af374cbb1ad0cfd3914b8dbe406ebcd4fe0443f0d05224067088043ac51962ada7207d480d249a10ab595ae0da3627942637","session":"bf102fd528a0..."} 

What kind of hash is not yet clear. This is clearly not hex or base64. In the original 30 thousand lines, if you reformat the code in a readable form. JS Beautifier service was used. It is logical to search for any substring by such a filter - post ". Such a function is found.

 key: "setScore", value: function(e) { console.log(e); var t = "expandScore"; return this.apiRequest(t, { hash: e }, { method: "post" }) } 

Looking for this function through the search, you can find several places where it is called. For example from these objects - this.ApiService, or this.scoreView. But none of them was available through the inspector. Then it was decided to figure out what the hash is sent and how to decode it.

Then the search was carried out in the text setScore. There was such a great feature that explained roughly how encryption is performed.

 key: "saveResults", value: function() { var e = this, t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, n = window, r = n.gameMe, i = f.default.extend({ key: r._shard }, p), o = d.encrypt(i, JSON.stringify(t).split("").reverse().join("")); this.ApiService.setScore(o).then(function(t) { e.updateMe() }) } 

First, the JSON format of still unknown data is translated into a string, then this string is broken up into characters in the array, changes back to the order of these characters and the string is glued together again. Mostly anti-reverse feature from developer? Putting brekpoint as in the screenshot was obtained access to all current functions and objects that are available specifically in line 14016.



Now you can access some interesting options.



It turns out that all data is encrypted using the AES 256 algorithm in ECB mode. But what is not clear, using the good service for symmetric encryption received.



While the program produced a different result.



It turns out the encryption algorithm is also changed, the next chip for the anti-reverse and the process of operation. But this is not the biggest problem. Not the number of points is sent to the server, but arrays of data in the JSON format (formatted for readability, the request has no hyphenation and not a single space).

 { "_s":"1", "_f":[200,440.412834676673], "_p":0.61, "_r":0.44048586944056, "_t":1507933273148, "_n":{ "_s":"2", "_f":[200,531.1135607680883], "_p":0.64, "_r":0.5711409299481298, "_t":1507933274848, "_n":{ "_s":"3", "_f":[200,583], "_p":0.73, "_r":0.06360827768619635, "_t":1507933277447, "_n":{ "_s":"4", "_f":[200,374.96787925000024], "_p":0.54, "_r":0.4264300320950012, "_t":1507933278662 } } } } 

Based on the logic of _t, this is the time when the next chicken was eaten in milliseconds, _f is the coordinates of the island or hen, _s is the number, _n is the next island with the chicken eaten. It turns out that the server does not process how many points were scored, but what data were recorded when the hens were eaten. A very good idea in terms of game security.

Having spent several games, it did not turn out to determine what _p and _r are, most likely they are to divert attention and complicate the understanding of the code, another developer trick. But all the variables varied within certain limits and a script was written that, based on the necessary amount of time and the required number of points, would generate JSON arrays.

 import time import json import random score = 3 #time = time.time() * 1000 time = 1507761372191 def generate(counter, time, score): if counter < 10: _s = counter else: _s = chr(counter + 87) _f = [200, random.randrange(360, 600) + random.randrange(2*10^14, 9*10**14)/(10.0*10**14)] _p = random.randrange(15, 80) / 100.0 _r = random.randrange(2*10^19, 9*10**19)/(10.0*10**19) _t = time result = {'_s' : _s, '_f' : _f, '_p' : _p, '_r' : _r, '_t' : _t} if counter < score: time += random.randrange(1000, 6000) result['_n'] = generate(counter + 1, time, score) return result def start_generate(time, score): result = generate(1, time, score) result = json.dumps(result) result = result.split(' ') result = ''.join(result) return result def console_str(time, score): return "d.encrypt(i, '" + start_generate(time, score) + "'.split('').reverse().join(''));" print console_str(time, score) 



The result had to be inserted into the console and then the HTTP request was substituted. Alas, it worked for a few points, then the account was banned. Apparently not everything was taken into account in the script.

I had to take another test account, continue to study the source code and conduct testing. In the source, many different strange things are present, for example, all types of AES encryption are implemented, or there is such a strange piece of code with hash tables.



Or, for example, it is not clear why the required key is in base64 (the md5 key was transmitted during encryption).



And here is his transcript.



These are not all oddities found in the code. However, after a few more hours of debugging, something interesting was found, the catchAnimals function.

 key: "catchAnimals", value: function() { var e = this, t = [this.foxOffsetX, this.foxy.position.y], n = this.getHitAreaAnimal(this.foxy, t, this.foxOffsetX); if (n) { window.score = ++this.score; var r = v.Utils.getRandomInt(0, 100) / 100; r < .9 ? gameSounds && ion.sound.play("chicken_3") : gameSounds && ion.sound.play("chicken_1"), this.scoreView.setScore(this.score), this._passIslands || (this._passIslands = {}); var i = { _s: this.score.toString(20), _f: t, _p: r, _r: Math.random() * r, _t: (new Date).getTime() }, o = this.getListHead(this._passIslands); o._t ? o._n = i : y.default.extend(o, i), isWebGLRenderer && game.getFPS() > 45 && f.Main.CanvasWidth > 2500 && ! function() { var t = new l.ScoreIncrementer; t.addScore(1, n.animalType, function() { e.removeChild(t), t.destroy(), t = null }), e.addChild(t) }(), this.animationAttractor.append(n.getRebornNumber(), n, function(e) { return e.explode() }) } 

From it it becomes clear how the array is generated. Each time a fox eats another hen, this function is called and a new data block is added to the existing array, which is placed in the _n variable. And also, the score recorded the value that was transferred to another system of calculation, instead of decimal, the score recorded the number in the decadecimal system, the next anti-reverse technique from the developer. Instead of improving the script, it was decided to set brekpoint and replace the score on the fly.



Right before setting the score in the code. Now in the console, change the value and continue the execution of the script.



And so I was rewarded for my work.



Into the general skorbord was added a little later.



It is noteworthy that you can change points with just one array. In the _s parameter, write the required number of points in the 20th calculus system, all this is encrypted with the custom algorithm aes, with the generated key (md5 format). It is worth noting that the key is issued based on the chat id, and does not change during the game. Based on the account id in this game, we can conclude that at least 100 thousand people played it.

It should be noted that this attack was possible due to the vulnerability on the server. The code is accepted by JSON, it is deciphered, most likely, the array with the maximum _s is taken from the data array, and it does not matter that it is only one. To fix this is simple - you need to parse the entire array for incremental increment _s, checking that the value of this parameter matches the entry number in the JSON tree.

What can be concluded? Everything that is processed on the client side can be modified, changed, and it does not matter how difficult the data is sent to be sent and how difficult the code is obfuscated. Very pleased with the game "Tricky Fox", the developer is well done. Let it be just a game, on which you need to spend an hour or two maximum, yet it is worth taking measures to prevent cheating and bypassing logic. Compared to other more popular games with a negligent attitude to modifying points, the game is very well done. If you, dear reader, want to develop your own game, then by sorting out the cases from this article you can create not only a beautiful, interesting, but also a fairly secure game.

If you have an interesting game that is worth considering, and it is not solved by the methods listed above, send me VK or Telegram or comments, I will try to consider. For interception of requests used BurpSuite.

PS For replacing the points of many games, you can use the Telegram Cheats service, for the very lazy. But there is not all games can be screwed.

Source: https://habr.com/ru/post/339596/


All Articles