История взлома всех игр в Telegram

    Сейчас компьютерные игры везде. Присутствуют они и в Telegram. Расскажу о том, как были взломаны практически все игры этого мессенджера, обойдя самых первоклассных игроков, находящихся в топах скорбордов. Хочу поделится результатами исследований. О различных методиках взлома, читинга и путях обхода логики игр под катом.



    @gamebot


    Первая игра, которая была рассмотрена несколько месяцев назад — LumberJack, играя дровосеком, нужно рубить ветки так, что бы они не придавили игрока. Цель игры в том, что бы срубить как можно больше веток за определенное количество времени.



    Изначально, хотелось попрактиковаться в графическом читинге игр, то есть на основе графических данных на мониторе принимать решение. Программа должна эмулировать реакцию человека, отправляя нужные комбинации клавиш, исходя из ситуации на экране. Принцип построения логики программы для текущей игры следующий. Делается скриншот экрана размером 600x1 пиксель по правую сторону дерева. Не весь экран, потому что на процесс снятия скриншота такого большого пространства уходит больше времени. Затем программа в 6 точках проверяет цвет пикселей и на основе этого рассчитывает траекторию движения игрока сразу для 6 веток. Если справа есть ветка, уйти влево, если нет — остаться справа. За один ход делается 2 удара топором. Выполняются ходы, затем снова делается скриншот и цикл повторяется. Так будет продолжаться, пока не закончится время.



    Код программы для python 2.7 в ОС 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..'

    Для запуска нужно установить следующие зависимости

    sudo apt-get install xautomation  
    pip install pyscreenshot

    За эмуляцию клавиш в среде Linux отвечает утилита xte, подробнее можно почитать тут. За снятие скриншота выбранного участка экрана отвечает библиотека pyscreenshot, подробнее читать тут. Для работы программы нужно задать первую точку (самую нижнюю ветку справа, или место, где она могла бы находится), для этого можно воспользоваться функцией get_mouse(). Высота между ветками — 100 пикселей. Задержки между нажатиями клавиш и задержка между снятием скриншотов выставлена методом проб и ошибок. Меньше, чем эти значения, установить не вышло, программа не успевала обрабатывать изображение или нажимать клавиши. Пример работы представлен на видео.



    800 очков ни один человек набрать не сможет, так что результат работы программы можно считать победой.

    Процесс написания и отладки занял довольно продолжительное время, так что нужно рассмотреть другие варианты решений.

    Анализируя HTTP запросы, при окончании игры отправляется два типа запросов. Если пользователь не достиг нового рекорда.

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

    Новый рекорд

    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
    

    Достаточно лишь подменить some_score на какое то значение, и новое число добавляется в таблицу.

    В base64 отправляются данные о аккаунте, то есть id, имя игрока, который кликнул на игру, имя игры, а так же id чата.

    Стоит отметить, что эта игра относится к боту @gamebot, на котором есть еще две игры, Math Battle и Corsairs. Более подробно была рассмотрена игра Math Battle.



    HTTP запрос с количеством баллов отправляется похожий. Стоит попробовать отправить запрос через инспектор в браузере.

    Открыв режим отладчика, открыть исходный код main.min.js. Поставить несколько брекпоинтов (точек останова), запустить игру и найти переменную r, в которой хранится количество очков. Через консоль это значение можно менять.



    Отключив режим дебага, выполнится функция отправки очков и таким образом можно отправить уже заданное количество очков. Что бы не кликать много раз, включая\отключая инспектор, стоит немного разобраться в исходниках main.min.js, отредактированный сервисом JS Beautifier. Вот три интересные функции.

    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()
    	})
    }
    

    Функция na() вызывается, когда достигнут новый рекорд, ca() нужна просто для получения scoreboard игры. Кстати, решение, какую функцию вызвать происходит в функции U() в этой строке.

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

    При изменении параметра r и вызове функции na() должен быть включен режим дебага. Должно получится примерно так.



    Исходный код немного обфусцирован, это усложняет его анализ, но основные вещи ясны.

    Игра Corsairs, относящаяся так же к боту @gamebot решается всеми вышеописанными методами. За различные запросы на сервер я был забанен, и не могу добавляться в scoreboard, аккаунт находится в бан листе. Нужно быть осторожным при тестировании игр этого бота.

    @gamee


    Довольно популярным был бот @gamee. Выбрана игра Qubo.



    Запрос, отправляемый в конце игры следующий

    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\"}"}

    Подменить score, как в предыдущем боте, уже не получится. Генерируются хэши, которые подписывают score, play_time и тд, что бы таким простым способом нельзя было накрутить очки. Отладчик не очень помог, так как в нем было очень много переменных.



    Пришлось анализировать код вручную. В исходном коде страницы игры можно найти такой кусок кода.



    Примечательно, что скрипты подключаются не обычным способом, а через запрос в js, и поэтому их не видно в инспекторе. Любопытные два файла — gameUI.min.js и gameUIdesktop.min.js. В первом файле нашлась такая функция, которая является методом объекта gameeUI.

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

    Очевидно, что входящий параметр e — очки, отправляемые на сервер. Отправив в консоли строку gameeUI.saveScore(some_score), можно получить заветное количество баллов.

    К этому боту относятся игры «3+3», «Karate Kido», «Space Traveler», «Hexonix» и тд. Все решаются вышеописанным способом. Можно сделать вывод, что когда одна из игр очередного бота решается определенным способом, то им решаются и остальные игр этого бота.

    @GamesHDBot


    Довольно сложную по логике можно назвать игру «Galaxy Space Shooter». Красивая графика, много плюх в процессе игры, можно зарабатывать очки и монеты.



    Но достаточно было заглянуть в инспектор, найти объект TlgAdapter и его метод putScore.



    @ludeiBot


    Любопытным оказался этот бот. Игра для тестирования — «Jumping Submarine».



    Запрос при окончании игры следующий.

    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}

    Очки подменить просто. Примечательно то, что в открытом виде посылается user_id. Если узнать id всех пользователей в чате, то можно сделать следующую атаку типа спам. Сделать это можно через API Telegram.



    Идея в том, что можно мало того, что менять очки другого игрока в таблице, так еще и спамить в общем чате, отсылая уведомления о новом победителе текущей игры. Это довольно серьезная атака, если правильно ее сделать. Нельзя определить, кто на самом деле накручивает очки, и даже после исключения человека из чата (атакующего или того, кто «спамит») — все равно можно продолжать безобразничать. Можно «спамить» от имени любого участника чата, включая админа, заставив админа чата исключить определенных людей. Единственный способ прекратить такое мракобесие — админу чата удалить сообщение с предложенной игрой.

    К этому боту относятся так же игры «iBasket», «Sumon», «Angry Piano».

    @MeduzaGameBot


    К этому боту относятся 5 игр, и их можно применять для того типа атаки, который описан выше.
    К примеру игра, всем знакомый сапер.



    Запрос следующий.

    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

    Здесь еще проще. Обычным GET запросом, зная id чата и id пользователей можно устраивать спам.

    @foragamesbot


    Тут всего одна игра — «DevRunner»



    После окончания игры отправляется два запроса

    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

    Второй запрос позволяет увидеть, на каком месте игрок с текущим количеством баллов. До того, как было начато тестирование, на первом месте был игрок с 2000-3000 баллов, а забавно то, что всего играло в игру около 13к людей. Получается никто из этих людей не додумался перехитрить эту игру :) Подменив запросы с количеством очков 111113 оказался на первом месте.



    @brugamebot


    Бота с этой игрой можно определенно назвать самыми скучными среди всех.

    Пример — шахматы. Никакого рейтинга, оповещение о победе и так далее. Игра только client-side.



    @microgamesbot


    Эта игра так же одна в этом боте, «Jumper Frog».



    Запрос в конце игры

    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}

    Часть токена вырезана, но смысл должен быть ясен. На сервер отправляется в качестве данных только score. Подменить его просто. Вопрос в другом, как сервер распознал, что играл определенный человек, и записал его в scoreboard в чате телеграмма? А все просто — сервер берет данные из заголовка Referer токен, упомянутый выше. Странный костыль, насмешил. Логичный вопрос, что будет, если зайти по прямой ссылке на эту игру? Ничего особенного, вот только когда набрать очки, в ответ на запрос выше (без Referer) придет ответ такого вида.



    Ловится ошибка, которая раскрывает абсолютные пути к файлам. В совместимости с LFI, например, уже можно достать много полезных данных. Но так как задача стояла в обнаружении недочетов игр, а не проводить полноценный пентест конкретной игры, на этом было решено остановится.

    @foxgamebot


    Теперь будет описана игра «Tricky Fox», которая является самой сложной по своей структуре среди всех, имеет относительно всех других игр высокий уровень безопасности, и что бы обойти ее, ушло много времени. Автор молодец, создал очень годную игру, было интересно ее разобрать. Процесс решения этой игры хочется описать подробно. Задача игрока — поедать куриц, прыгая из острова на остров. Зажимая левую клавишу мыши или палец на экране телефона можно заставить лисицу лететь нужное расстояния.



    При окончании игры отправляется такой запрос

    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..."}

    Что за хэш пока не ясно. Это явно не hex или base64. В исходном 30 тысяч строк, если переформатировать код в читаемый вид. Был использован сервис JS Beautifier. Логично поискать какую нибуть подстроку по такому фильтру — post". Находится такая функция.

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

    Поискав эту функцию через поиск, можно найти несколько мест, где она вызывается. К примеру из этих объектов — this.ApiService, или this.scoreView. Но ни один из них не был доступен через инспектор. Тогда было решено разобраться, что за хэш отправляется и как его декодировать.

    Далее поиск производился уже по тексту setScore. Нашлась такая замечательная функция, которая объясняла примерно, как производится шифрование.

    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()
            })
        }

    Сначала JSON формат еще не известных данных переводится в строку, затем эта строка разбивается на символы в массиве, меняется задом на перед порядок этих символов и строка склеивается заново. Небольшая антиреверс фишка от разработчика? Поставив брекпоинт как на скриншоте был получен доступ ко всем текущим функциям и объектам, доступными конкретно в строке 14016.



    Теперь можно получить доступ к некоторым интересным параметрам.



    Получается, что все данные шифруются алгоритмом AES 256 в режиме ECB. Но вот что не понятно, воспользовавшись хорошим сервисом по симметричному шифрованию получил.



    В то время, как программа выдавала другой результат.



    Получается алгоритм шифрования тоже изменен, очередная фишка для антиреверса и процесса эксплуатации. Впрочем это не самая большая проблема. На сервер отправляется не количество очков, а массивы данных в формате JSON (отформатировано для читаемости, в запросе нет переносов и ни единого пробела).

    {
    	"_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
    			}
    		}
    	}
    }

    Исходя из логики _t, это время, когда была съедена очередная курица в миллисекундах, _f — координаты острова или курицы, _s — номер, _n — следующий остров с съеденной курицей. Получается на сервере обрабатываются не то, сколько очков набрано, а данные о какие данные были зафиксированы при съедении куриц. Очень хорошая идея с точки зрения безопасности игры.

    Проведя несколько игр, не вышло определить, что такое _p и _r, скорее всего они для отвлечения внимания и усложнения понимания кода, очередная фишка разработчика. Но все переменные варьировались в определенных пределах и было написан скрипт, который исходя из нужного количества времени и необходимого количества очков генерировал бы JSON массивы.

    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)



    Результат нужно было вставить в консоль и затем сделать подмену HTTP запроса. Увы это работало для нескольких очков, затем аккаунт был забанен. Видимо не все было учтено в скрипте.

    Пришлось брать другой тестовый аккаунт, продолжать изучать исходный код и проводить тестирования. В исходнике много разных странных вещей присутствует, например реализованы все виды шифрования AES, или вот такой странный кусок кода с таблицами хэшей.



    Или вот, например, непонятно зачем нужный ключ в base64 (при шифровании передавался md5 ключ).



    И вот его расшифровка.



    Это не все странности, найденные в коде. Впрочем, после еще нескольких часов отлаживания нашлось кое что интересное, функция catchAnimals.

    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()
            })
        }

    Из нее становится понятно, как генерируются массив. Каждый раз, когда лисица съедает очередную курицу, вызывается эта функция и к существующему массиву добавляется новый блок данных, который помещается в переменную _n. А еще, в score записывалось значение, которое было переведено в другую систему исчисления, вместо десятичной, в score записывалось число в двадцатеричной системе, очередной антиреверс прием от разработчика. Вместо усовершенствования скрипта, было решено поставить брекпоинт и подменить score прямо на лету.



    Прям перед установлением score в коде. Теперь в консоли поменять значение и продолжить выполнение скрипта.



    И вот так я был вознагражден за труды.



    В общий скорборд добавился немного позднее.



    Примечательно то, что изменить очки можно всего одним массивом. В параметре _s записать нужное количество очков в 20чной системе исчисления, все это зашифровав кастомным алгоритмом aes, с сгенерированным ключем(md5 формат). Стоит отметить, что ключ выдается исходя из id чата, и не меняется в процессе игры. Исходя из id аккаунта в этой игре, можно сделать вывод, что в нее играло не менее 100 тысяч человек.

    Хочется отметить, что выполнить эту атаку удалось благодаря уязвимости на сервере. Кода принимается JSON, расшифровывается, скорее всего, из массива данных берется массив с максимальным _s, и не важно, что он всего один. Поправить это просто — нужно парсить весь массив на предмет инкрементного увеличения _s, проверяя, что бы значение этого параметра соответствовало номеру вхождения в дереве JSON.

    Какой можно сделать вывод? Все, что обрабатывается на стороне клиента, можно модифицировать, подменить, и не важно, насколько сложно шифруются данные для отправки и насколько сложно обфусцирован код. Очень порадовала игра «Tricky Fox», разработчик молодец. Пусть это всего лишь игра, на которую нужно тратить час-два максимум, все же стоит принимать меры по предотвращению читерства и обходу логики. На фоне других более популярных игр с халатным отношением к модификации очков очень хорошо сделана игра. Если ты, уважаемый читатель, хочешь разработать свою игру, то разобрав практически кейсы из этой статьи сможешь создать не только красивую, интересную, но и довольно защищенную игру.

    Если у тебя есть интересная игра, которую стоит рассмотреть, и она не решается вышеперечисленными методами, отправь мне в ВК или Telegram или комментарии, постараюсь рассмотреть. Для перехвата запросов использовал BurpSuite.

    P.S. Для подмены очков многих игр можно использовать сервис Telegram Cheats, для совсем ленивых. Но там не все игры можно накрутить.
    Share post

    Comments 29

      +1

      У меня есть онлайн настолка, там хоть и нет лидербордов, но можете поковырять на предмет преимуществ над другими игроками :)


      Зато следующую игру я планирую писать без серверной части, жалко что никто не смог защититься. Может вообще не делать онлайновый лидерборд?

        0
        это скорее текстовый квест, чем игра, причем серверный) квестов в тг тоже много
          0
          Ну как, там карточки же, существа, всякое такое… По крайней мере если проигрываешь, то игру подвесить точно можно
        +3
        История взлома всех игр в Telegram

        Достаточно очевидно, что если вся игровая механика реализована на клиенте, а на сервер отправляются только результаты, то можно отправить на сервер любые результаты. Так что я бы не назвал это "взломом" :)
        Обфускация исходников и формата запросов — не панацея, обход — только вопрос времени.


        История взлома всех игр в Telegram

        В Telegram больше нет игр, кроме тех, что перечислены в статье? А если найду?

          0
          Проблема в том, что таких игр 98% среди всех. Об этом стоило написать, что бы разрабы начали переносить хоть часть логики на бекенд. Взломом можно назвать общую массу игр, если за 2-3 дня смог перебрать все популярные игры. И игры для рассмотрения брались из многих источников, в том числе и из games.tlgrm.ru. Если какую то игру не рассмотрел, то она либо типична по решению, либо не популярна.
          –3
          Всех игр? Добро пожаловать: @ChatWarsBot
            –1
            Что за нелепая попытка самопиара? Очевидно, что речь про html5 игры, которые взламывать — как щелкать орехи.
              0
              Гм, вперед! Поломай «Tricky Foxy», гений, и выйди на первое место. И попробуй это сделать не подсматривая в статью.
              0
              Это текстовый квест сделанный через бота. В текущую тему не очень вписывается
              –1
              Ну да — классика подхода security by obscurity
                0

                *security through obscurity

                  0

                  И так и так пишут т.к. по смыслу это эквивалентные фразы.

                    0
                    Вы не правы. Чем более популярна игра, тем более высокая должна быть безопасность игры. Когда в игру играют сотнями в чатах, десятками тысяч (если есть scoreboard), то какая разница, где реализованна логика, если ты на первом месте?
                +1
                чем хороши статьи про ботов, что все эти методы можно применять и для автоматизированного тестирования в случае если нет другого инструмента.
                  +1
                  gameeUI.saveScore(some_score)
                  Можно еще воспользоваться этим:
                  GameeWeb.updateScore(1337)
                  Прописывать в момент смерти.
                    0
                    как момент смерти отлавливать будешь?
                      0
                      В принципе, его можно программно не отлавливать. Сделать любое движение, которое приведет к смерти и ~перед смертью выполнить команду
                      i.imgur.com/s49SVIr.gifv
                        0
                        можно, но зачем усложнять?)
                    +2
                    Для игр из бота @gamebot (дровосек и математический батл) есть способ еще проще. Открыть исходный код программы, найти «random» и поменять на любое число, сохранить. В итоге получится, что в дровосеке деревья появляются только с одной стороны, а примеры, которые надо решить — одинаковые. В игре Corsairs (от того же бота), найти «bulletSpeed» и поменять стартовую константу в 130 и ту строчку, где она увеличивается на 1.1. С остальными платформами не пробовал, из @gamebot одни из первых игры были. Сложнее подобрать красивое число, 777 например (вот здесь есть доказательство @gamestion (не реклама, и группу нашел совершенно случайно)).
                      +1
                      Круто! Есть чем перед девчонками похвастаться!
                        0
                        Особенно если эта девочка HR
                        +1
                        Чит для дровосека: открываем консоль, вводим Math.random = function() { return 0.5; } и рубим ветки с одной стороны
                        Для кораблика: опять же в консоли function fixBulletSpeed() { bulletSpeed = 1; setTimeout(fixBulletSpeed, 1); }; fixBulletSpeed(); уворачиваемся, как нео, от снарядов.
                        Правда результаты на сервер не отправлял, т.к. меня забанили после наглой попытки послать curl'ом такой же запрос, подменив Highscore на 1000000
                          +1
                          P.S. первый чит также работает для math battle. Вот улучшенная модификация:
                          Math.random = function() { return 0.5; }; function clicker() { document.getElementById('button_wrong').click(); setTimeout(clicker, 100); }; clicker();

                          Возможно у вас надо будет жать другую кнопку.
                            0
                            Найс)
                          +1

                          Очень приятно увидеть плоды своих трудов в этой статье. (tricky foxy)

                            0
                            Сам разраб Лисицы оценил, спасибо!)
                              0
                              Вот по примеру его игры нужно строить фронт енд логику
                                0
                                А зачем, если вы все равно его взломали?
                                  +1
                                  Потому что делал это долго, сделать это было проблематично, и большинство людей просто не станет так сильно заморачиваться. И взломал, потому что был баг в работе сервера. Если бы не он, было бы все намного сложнее. И я был первым, кто нашел этот баг среди 100к людей, и второй среди тех, кто когда либо ломал эту игру. Так что это говорит о прекрасно проделанной работе одного разработчика.

                            Only users with full accounts can post comments. Log in, please.