Кто убил ПВУ?
(Статья написана https://t.me/klatmansklad)
Привет! Хочу рассказать про одно интересное событие, которое приключилось с нами на просторах крипто мира. Начнём с него, так как этот проект был у всех на слуху, и если вы в крипте больше года, то, скорее всего, вы тоже о нем знаете. Итак - речь пойдет о PVU.
Конец лета 2021 года. Я отмечаю день рождения близкого друга в одном из заведений нашего города. Звонит телефон, я поднимаю трубку.
«Привет, тут вышла игра Plants vs Undead, можно заработать»
«А что нужно делать?» - спрашиваю я.
«Нужно автоматизировать полив растений и отгон ворон.»
«Зачем кому-то нужно поливать растения и отгонять ворон?» - вновь интересуюсь я
«Этим ты зарабатываешь деньги»
Я смеюсь и прощаюсь с собеседником, пообещав, что в свободное время уделю этому внимание.
Если честно, на тот момент для меня, как для человека не знакомого с p2e, это звучало как полнейшая чушь.
◦ Название игры, нагло скопированное у PopCap - разработчика франшизы Plants vs Zombies
◦ Странный геймплей, который заключается в монотонном поливе растений.
◦ Какие-то вороны, какие-то земли… Похоже на очередной таймкиллер.
Разве на этом можно заработать деньги?
На следующий день я все же решил рассмотреть проект детальнее. Когда я взглянул на количество подписчиков в социальных сетях, я, скажу честно, обалдел. Это был первый дискорд канал, что я видел, в который НЕ ПОМЕЩАЛОСЬ все комьюнити. Попросту нельзя было присоединиться из-за достигнутого лимита в пользователях. Телеграмм группа в 300к юзеров. Такого я ещё не видел у крипто инди проекта. Что ж, интерес был подогрет. Самое время что-то сделать.
Часть 2. Как в это можно играть?!
Взяв у товарища аккаунт, я решил осмотреться. Игрушка оказалась обычной браузеркой, которая к тому же дико глючила из-за наплыва пользователей. Разработчики оказались не слишком продвинутыми - архитектура оказалась не scalable (разработчики не смогли подружить сервер с таким количеством игроков) поэтому ребята просто разделили участников на группы, которые могут одновременно заходить в игру по расписанию... По расписанию, Карл, в 2021!
Вся игра была написана на обычном JS, а общение с сервером посредством HTTP.
Никакого дополнительного шифрования контента пакетов не было. Оно и понятно - какой смысл скрывать содержимое, если игра написана на JS, который может понять каждый, кто хоть немного знаком с программированием.
Создать бота было бы супер просто, если бы не эти гребаные очереди! Как же тяжело что-то делать, когда сама игра, по факту, не работает, а 80% времени лагает.
Спустя часы страданий и попа боли, бот, который поливает наши страдающие от засухи цветы, готов!
https://www.youtube.com/watch?v=aNfLiw_qSUA - Видео, демонстрирующее полный функционал бота (Ссылки и группы указанные под видео неактуальны)
Осталось выследить и уничтожить ворон, параллельно поливая растения с глобальной карты. Странно, никакой информации о них не видно.. или же я не туда смотрю? Да, так и есть, игрушка создает еще одно web socket соединение, из которого получает всю необходимую информацию. Посмотрим что же тут есть… Ого! Да тут же вся инфа.
Причем не только та часть карты, куда мы отправляемся, а вообще ВСЯ, да еще и в реальном времени. Вот это прям конфета!
Тратим еще некоторое время (гребаные очереди, хочется застрелиться) и домашний вариант бота готов.
Спустя какое-то время разработчики сообщают о новом грандиозном апдейте. Грандиозен он только в головах разрабов, ведь он предполагает, что игроки без нфт растений не смогут обменивать LE -> PVU. На фоне этой новости люди начинают массово продавать токен и его цена падает в пару раз за 40 минут.
Ну все, игра точно сдулась! Или все же нет... Вдруг разработчики начинают писать о том, что они приняли во внимание негативное отношение комьюнити к этому апдейту и поэтому дадут возможность людям игровым способом нафармить NFT растения. К моему удивлению цена токена после этого заявления начинает увеличиваться и в итоге достигает своего предыдущего значения. Нонсенс)
На волне хайпа перед апдейтом они так же анонсируют серверсайд античит и да, действительно, многие аккаунты абузеров начинают отлетать, но, конечно же, не наши - мы, как правильные абузеры, используем прокси и не кидаем все PVU со всех аков на один.
После этого апдейта и случается первая интересная штука.
Однажды, сев за компьютер чтобы пофиксить какой-то минорный баг в боте, я замечаю, что все запросы завершаются с ошибкой Bad Gateway. Обычно это значит, что сервер недоступен. Странно, игра открыта в браузере и все вроде работает... Хм, они "переехали" на другой субдомен и все запросы теперь идут на:
https://staging.plantvsundead.com Вместо https://backend-farm.plantvsundead.com.
Если кто-то, кто читает эту статью, хоть как-то связан с IT, то он знает, что staging - это предпродакшн среда (если проще - тестовый сервер), куда выкатывают обновления для финального тестирования и проверок на правильность интеграции с 3rd party сервисами.
Что ж, интересно, меняем URLы в боте, делаем правки и идем спать. Ночью меня будят с криками "все сломалось, ничего не поливает и не отгоняет". Сонный и злой я сажусь дебажить все это дело и вижу, что авторизация проходит, а вот дальше – снова ошибка 502. Да, так и есть, как вы догадались, они сменили базовый URL на дефолтный. Меняем обратно, заливаем и идем спать.. Но вдруг в голову приходит одна мысль, которая напрочь отбивает сон:
"Логин проходит!!!"
Для людей, которые не особо разбираются в этих скучных программерских штуках я постараюсь объяснить простым языком.
Итак, процесс логина в современном web3 мире выглядит примерно следующим образом.
1) Мы коннектим наш web3 кошелек. В нашем случае это, к примеру, Metamask.
2) Public Key из Метамаска отправляется на сервер.
3) Сервер, естественно, просто так этому не доверяет и хочет удостовериться, что пользователь владеет этим кошельком, поэтому просит подписать сообщение с помощью Private Key.
Благодаря этому сервер может проверить, действительно ли пользователь является владельцем этого кошелька. В результате он присылает вам так называемый Session Token, который постоянно отправляется в запросах серверу.
Обычно это все оборачивают в JWT (Json Web Token), который нужен для того, чтобы подписать сгенерированные сервером данные, чтобы злоумышленник на стороне клиента не смог подкидывать серверу всякую дрянь. В этот ключ включена подпись и благодаря нему сервер может проверить, действительно ли пользователь является владельцем этого кошелька.
В PVU он выглядел примерно так:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwdWJsaWNBZGRyZXNzIjoiMHgwYTM0YTU1NjVlOTUyYWIyNzc4N2EyMWEzZjRlNTRiMzIxYWMwMzQwIiwibG9naW5UaW1lIjoxNjI5MDU0ODkxODgyLCJjcmVhdGVEYXRlIjoiMjAyMS0wOC0xMSAxODo1MjowNSIsImlhdCI6MTYyOTA1NDg5MX0.rmYW0kWaHKKWxEOjjH02WbPvwaS0YpfOgA4p4m_sqUs
{
"publicAddress": "0x0a34a5565e952ab27787a21a3f4e54b321ac0340",
"loginTime": 1629054891882,
"createDate": "2021-08-11 18:52:05",
"iat": 1629054891
}
Итак, мы видим, что в токене есть адрес нашего кошелька.
Фух, закончили со скучной и непонятной тех частью, она нам понадобится чуть позднее, в ней и будет заключаться самое интересное =).
И так, вернемся к тому, что логин проходит на staging (тестовом) сервере и нам возвращает токен авторизации. Интересно, а запущен ли staging после смены url доменов? Проверяем и радуемся - успешный логин.
Проверяем, пускает ли в игру с этим токеном? Получается, мы можем авторизоваться на одном сервере, а использовать токен на другом. Круто, но что нам это дает? А то, что на забаненных аккаунтах логин сервер посылает нас на три буквы и не выдает нам токен. Попробуем получить его со staging сервера. Как вы догадались - успешно =)
Подытожим: Мы смогли авторизоваться на забаненном аккаунте на тестовом сервере, где получили действующий токен, как на обычном (незабаненном) аккаунте. Получается, что используя это маленькое ухищрение мы можем продолжать играть/выводить c забаненных аккаунтов.
Помните, как в конце PVU, ежедневные поливы на дереве (которые, по сути своей, являлись ежедневными квестами с наградами) стали увеличиваться с ненормальной скоростью, а после разрабы выключили дерево и снесли браузерную версию игры?
Этот инцидент случился после того, как группа жадных и недалеких людей, найдя баг, начали использовать его на всю катушку, вовсе не думая о последствиях своих действий. И из-за этого они закономерно спалились на нереалистичных цифрах полива дерева в ежедневном квесте.
Что это был за баг, каковы его причины и почему жадность — это плохо, я и расскажу вам в этой части.
Однажды, после "разбана" очередных аккаунтов наших "клиентов" с помощью метода, описанного мной выше, мы решили переписать кусочек нашего софта, а именно ту часть, которая отвечает за логин. Впоследствии человек, которому мы все это делали, сказал, что ему дало в 2 раза больше LE и расходок, чем должно было дать (для тех кто не застал прекрасный гем, коим являлось PVU, LE – внутриигровой расходник, который не имеет денежной ценности, в отличии от токенов PVU, которые хранятся в блокчейне и имеют прямой денежный эквивалент).
Этот человек очень обрадовался, но мы на тот момент не придали этому значения. Позже вечером ситуация повторилась, и когда еще один человек рассказал нам про двойное количество LE и расходников, мы решили все таки разобраться: А откуда же у людей двойная награда за выполнение ежедневного квеста?
Начав наше "домашнее расследование" мы обнаружили, что для каждого из аккаунтов у нас есть 2 токена авторизации и используя каждый из них мы можем поливать дерево раз в сутки! Логика сервера позволяла это, а баланс аккаунта, который записан в базе данных, оставался общим. Вот это приятная находка, не находите? Погнали абузить!
Но подождите, стоп, х2 - это конечно приятно, но раз уязвимость игры позволила нам сделать х2, возможно по той же логике получится сделать и х100?
С этой оптимистичной мыслью мы и принялись искать уязвимость. Вы наверняка сейчас думаете, какая же могла быть изощренная уязвимость в игре с маркет каппой в несколько сотен миллионов долларов и такой огромной аудиторией? Наверняка в игре, имеющей такое колоссальное количество денег в своем распоряжении, не может быть каких-то очевидных проблем с написанием кода. Насколько же надо было ухищриться, чтобы найти эксплойт, потенциально позволяющий вынести несколько миллионов из игры, а также впоследствии ставший одной из основных причин закрытия и смерти PVU? Если вы задались хотя бы одним из этих вопросов, то самое интересное впереди.
Итак, после тщательного изучения внутриигровых токенов мы и обнаружили ту самую неочевидную, но простую ошибку, которая была в игре с самого первого дня. И так, давайте же разберем, в чем была проблема.
Вот как выглядел первый токен:
{
"publicAddress": "0x0a34a5565e952ab27787a21a3f4e54b321ac0340",
"loginTime": 1629054891882,
"createDate": "2021-08-11 18:52:05",
"iat": 1629054891
}
{
"publicAddress": "0x0A34A5565E952aB27787A21a3f4E54b321AC0340",
"loginTime": 1629054891882,
"createDate": "2021-08-11 15:37:02",
"iat": 1629053416
На первый взгляд кажется, что все нормально. Но при повторном логине сервер "обнуляет" предыдущий токен (а вместе с ним и полученные награды), и он становится не валидным. Хм, странно... Хотя подождите..
0x0a34a5565e952ab27787a21a3f4e54b321ac0340
0x0A34A5565E952aB27787A21a3f4E54b321AC0340
Кошелек один и тот же, однако регистр букв отличается.
Что нам это дает? - На этапе поливов дерева сервер считает, что это два разных аккаунта! Как же мы это обнаружили? Все дело в том, что когда мы решили переписать ту часть кода, которая отвечает за логин, мы забыли привести строку к нижнему регистру, (т.е не заменили большие буквы на маленькие) благодаря чему некоторые из наших клиентов и получали двойные награды за ежедневные квесты.
Это была, безусловно, интересная находка. Получается, что мы можем сгенерировать огромное количество вариантов одного кошелька, просто меняя буквы с больших на маленькие и логиниться с ними, поливая дерево вновь и вновь! При этом с одного аккаунта мы можем делать огромное количество паблик ключей, а именно равное 2^N, где N - количество букв в кошельке, в теории позволяя нам сделать те самые х100(а может и х1000?), о которых я и писал ранее. Звучит круто =)
Давайте еще раз осмыслим вышесказанное:
"а А" - Вот это убило ПВУ! Прописная и заглавная буквы в одном и том же паблик ключе позволили неограниченное количество раз поливать дерево и собирать за это награды в тех количествах, которые были нужны (Бесконечно).
Про то, что было дальше, история, конечно же, умалчивает. Однако есть основания полагать, что если делать все планомерно и не спеша, то можно получить суммы с шестью-семью нулями. Особенно легко это было сделать именно тем, кто обнаружил этот баг раньше других.
Спустя полтора месяца кроме нас нашлись еще какие-то люди, которые узнали об этой особенности игры. Эти ребята решили не стесняться и в лучших традициях широкой русской души разнесли баг по большому количеству людей. Закономерно, количество поливов стало неконтролируемым, и все заметили, что происходит что-то неладное, в том числе и разработчики, которые быстро устранили уязвимость, а впоследствии полностью убрали браузерную версию игры и сделали ее десктопной.
Ну что же, спасибо всем, кто дочитал до конца. Вот мы и разобрали, как и что погубило криптогиганта и прародителя P2E пирамид, на котором в свое время заработали свой первый банк огромное количество криптанов ру – сегмента.
Подписывайтесь на наш телеграмм канал (https://t.me/klatmansklad), будем благодарны за актив и репосты. В скором времени ожидайте новую историю о том, как Walken хоронили.