Pull to refresh

И снова Diamond Dash

Reading time5 min
Views8.6K
16 миллионовКак только я прочитал в недавней статье о написании бота для игры Diamond Dash в Google+, чуть-чуть не дотянувшего до 2 миллионов очков, у меня сразу зачесались руки написать аналогичного бота, который достиг бы этой цели — и, после нескольких дней экспериментов, этот результат действительно был достигнут.

Впрочем, теперь этого было уже мало — с применением другого подхода, на тот момент мой подвиг уже был опережен и даже переплюнут (финальный результат, судя по статье, приближается к 4 миллионам очков). Поэтому я решил сделать ещё один очевидный шаг — попробовать просто подменить запрос к серверу с результатом игры. Как можно видеть по скриншоту, попытка завершилась более чем удачно.

Принцип


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

Как серверу защититься от подобных подделок? Строго говоря — никак, это невозможно в данной ситуации в принципе. Запрос, информирующий сервер о результате игры, создается на клиенте — значит, все правила его формирования уже у нас. Тем не менее, усложнить задачу «хацкерам» можно следующими способами (и их комбинациями, конечно):
  • подписывая передаваемую информацию зашитым в клиенте ключом,
  • подписывая передаваемую информацию ключом, полученным с сервера,
  • подписывая передаваемую информацию текущим временем (передаваемым отдельным полем),
  • шифруя передаваемую информацию,
  • обфусцируя код клиента, чтобы помешать узнать алгоритм шифрования, алгоритм подписи и значение прошитого ключа.

Давайте посмотрим, какие же из этих техник применили разработчики рассматриваемой игры.

Операция «перехват»


Ставим какой-нибудь сниффер веб-трафика — например, Fiddler. Он умеет перехватывать только HTTP (да и то не всегда), но тут нам его хватит. Запускаем сниффер, открываем страницу с игрой, и отыгрываем одну партию. Возращаемся к Fiddler, смотрим.



Ага, запросов к серверам игрушки (ddg.wooga.com) негусто — всё как и ожидалось. Очень хорошо видно начало игры (запрос к /game/use_life/ — использовать сердечко жизни, которое тратится при каждой игре) и её окончание (/game/eor/ — end of round), и ничего между.

Запросов в конце игры два — один к ddg.wooga.com/game/eor, другой к ddg.t.wooga.com/w/eor. Начнем препарировать первый.

Запрос:
POST ddg.wooga.com/game/eor/?timestamp=1314395650744&signature=V5rwMEQJS_2diWc...ulj2VBs%3D&session=1100447b5bb0d08c12 HTTP/1.1
Host: ddg.wooga.com
Connection: keep-alive
Content-Length: 99
Origin: 8kubpeu8314p2efdd7ljdo-a-oz-opensocial.googleusercontent.com
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1
content-type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3
Cookie: _dd_rails_session=BAh7BkkiD3Nlc3Npb25faWQGOgZFRiIljA4NmUzZDZiNjk5MGYxZDQzNWQ%3D--3e580eae77a1037b347dae6229bb6d

{"user_id":"104465643407894900734","level":21,"sound":1,"score":144768,"xp":130,"gems_removed":348}

Ответ:
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 26 Aug 2011 21:54:12 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Vary: Accept-Encoding
Status: 200 OK
Cache-Control: no-cache
X-UA-Compatible: IE=Edge,chrome=1
Set-Cookie: _dd_rails_session=BAh7BkkiD3GOgZFR...wNDUxNTc%3D--b85c8953db82adcb0; path=/; HttpOnly
X-Runtime: 0.009475
Content-Length: 120

{"timestamp":"1314395652","user_id":"104465643407894900734","signature":"PAfgcneDa83-fR5_-CzJWpQ="}
(здесь и далее некоторые строки сокращены просто для того, чтобы уместиться без переносов)

Простой и понятный JSON, ничем не шифрованный. Правда, в адресе передается timestamp, signature и session — стало быть, какая-то подпись есть. Ответ, по сути, не особо содержателен — сервер со своей стороны тоже выплевывает данные подписи.

Активные действия


Ну что ж, пробуем втупую: даже не начиная игры, отошлём полностью идентичный запрос, заменив только значение поля score на 99999999. Благо, что у Fiddler для этого есть подходящий инструмент — Request Builder.

Как ни странно, в ответ получаем точно такой же 200 OK, разве что с другим timestamp и signature. Удивляемся беззаботной реакции сервера, лезем обновлять страницу с игрой. Обновив, удивляемся ещё больше: игра нам поверила, только вместо 99 миллионов засчитала 16 777 215. Похоже, это «потолок» — в БД это скорее всего хранится в виде чего-то типа UNSIGNED MEDIUMINT (трехбайтового целого), у которого как раз такое максимальное значение.

На этом вроде можно и остановиться, но после подобных провисаний в плане «безопасности» приложения, невольно задаешься вопросом — а нет ли тут бо́льших дыр?

Идем дальше


Как мы видим, в запросе к серверу передается наш user_id — идентификатор нашей страницы в Google+. А подпись, видимо, нужна именно чтобы сторонний сервер мог понять, действительно ли этот user_id принадлежит нам. Хм, а точно ли понимает он это? Или, может, он настолько доверчив, что позволит «выиграть» даже от чужого имени?

Лезем на страницу одного из друзей, тащим его id из адреса, подсовываем всё в тот же запрос (забив даже на исправление timestamp). Ага, тут вроде все хорошо: 400 Bad Request… Хотя нет, погодите-ка:
HTTP/1.1 400 Bad Request
Server: nginx
Date: Fri, 26 Aug 2011 22:07:06 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Status: 400 Bad Request
Cache-Control: no-cache
X-UA-Compatible: IE=Edge,chrome=1
Set-Cookie: _dd_rails_session=BAh7BkkiZ...Tk5NmFkOTA%3D--b96793945142dd62b0e5ea9a; path=/; HttpOnly
X-Runtime: 0.002774
Content-Length: 124

Signature mismatch! Got: V5rwMEQJS_2diQ8sdZMXBs= computed: gcLO6NdBJtwBtOwrWIpwL9LliuA=

Computed?! По сути, ситуация примерно такова: мы пытаемся зайти в чужой аккаунт; не зная пароля, вводим что попало; в ответ получаем не только сообщение об ошибке, но и правильный пароль. Прекрасно.

Послушно копируем сигнатуру из ответа, подставляем в запрос, отправляем. Вы наверняка уже догадались, что в ответ приходит совершенно доброжелательный 200 OK. И вполне ожидаемый результат:


(сверху — мой аккаунт, снизу — «подопытного» друга)

Заключение или Почему всё это — «ненастоящее» читерство


Команду разработчиков игры Diamond Dash, наверное, стоило бы обвинить в халатности — показывать корректную подпись в ответ на ошибочную явно не стоило… Но и слишком строгими к ним быть нельзя. В конце концов — это лишь казуальная игрушка, и накруток в ней не избежать, как уже было сказано в начале. По сути, в ней был применен единственно верный способ борьбы с читерством: таблица рейтинга составляется только из друзей пользователя. Таким образом, результаты накруток остаются видны только друзьям накручивающих (впрочем, учитывая баг с сигнатурой, это уже не совсем так).

Спасибо за уделённое внимание — и надеюсь, моя статья в первую очередь окажется полезна не при читерстве, а при разработке защит от него :)
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+82
Comments47

Articles

Change theme settings