В прошлом месяце OnePlus представила рекламную акцию для своего будущего OnePlus 6T. Предпосылка была проста: коснитесь или щелкните экран, чтобы разблокировать загадочные призы. Чем больше вы нажмете, тем лучше будет приз. После клика до первого приза, всего 60 кликов, я решил попробовать автоматизировать процесс.
Если вы зайдете на сайт игры со своего рабочего стола, вы получите сообщение с просьбой открыть его на мобильном устройстве. К счастью, достаточно просто симулировать мобильное устройство с помощью инструментов разработчика Google Chrome.
xdotool
Щелкнуть несколько тысяч раз с помощью такого инструмента, как AutoHotkey, было бы легко. К сожалению, AutoHotKey недоступен в Linux, но есть альтернатива: xdotool. С помощью нескольких быстрых поисков в Google я смог настроить этот скрипт bash, чтобы он начал кликать за меня. За несколько часов я набрал до 60 000 кликов.
Следующей вехой было 300 000 кликов. Я хотел попробовать, чтобы увидеть, какие призы стоят за таким огромным количеством кликов. К сожалению, у метода xdotool было несколько недостатков:
- Я больше не мог пользоваться своим компьютером
- Это было относительно медленно
- Браузер иногда зависал (вероятно, из-за того, что инструменты разработки хранили много информации)
Веб-сокеты
При проверке сетевых запросов я обнаружил, что игра не использует обычные HTTP-запросы, а отправляет всю информацию через вебсокеты. У меня был опыт имитации HTTP-запросов, но я не знал, как имитировать фреймы веб-сокетов.
Во-первых, я попытался смоделировать щелчок по экрану, используя старый добрый javascript. Взяв код из этого вопроса stackoverflow, я смог отправить событие click, но по какой-то причине счетчик не увеличивался. Озадаченный, я включил отладчик JS непосредственно перед запуском события click. Где-то в мешанине минифицированного JS скрипт проверял легитимность события клика с помощью свойства isTrusted. По сути, isTrusted — это функция безопасности в браузерах, позволяющая определить, был ли щелчок сгенерирован пользователем или javascript. Я не нашел простого способа обойти это.
Когда я копался в JavaScript, мне пришла в голову идея обойти обработчик событий и напрямую выполнить код, который отправлял сообщение в веб-сокет. Из-за минимизированного кода и моего незнания веб-сокетов мне не удалось найти точный код, который отправлял кадр.
Затем я попытался найти объект Websocket в памяти, чтобы самостоятельно отправлять кадры, используя его. Мне не повезло найти объект в памяти. Что, если я просто создам новое соединение?
Из того, что я мог видеть, на самом деле было отправлено только два вида фреймов: один для аутентификации и фрейм для каждого события клика.
Рамка аутентификации*:
420[“authenticate user”,{“username”:”raybb”,”token”:”ff45fb994e3124a5e0ce7bf7f27745dcbe62e71275532f982498b798",”squad”:””,”locale”:”en-gb”,”count”:62301,”csrf”:”0xsn5yq9SAb65OvTXDl3OjzRoVPgr1zVvDtzoVR4ZhjdptbHClRSVaSJDXKwXy8b”}]
Примеры кадров (1-й, 2-й и 10-й)*:
421[“tap_logs”,{“username”:”raybb”,”timestamp”:1540163213460,”touchX”:19,”touchY”:78}] 422[“tap_logs”,{“username”:”raybb”,”timestamp”:1540163297196,”touchX”:389,”touchY”:737}] 4200[“tap_logs”,{“username”:”raybb”,”timestamp”:1540163347125,”touchX”:306,”touchY”:442}]
*данные кадры имеют измененные значения по соображениям конфиденциальности.
В результате экспериментов я обнаружил:
token
иcsrf
: не менялись между обновлениямиcount
: количество нажатий при загрузке страницы (изменение не повлияло)- Фрейм авторизации всегда начинался с 420 (который мы будем называть счетчиком фреймов).
- Счетчик кадров в основном увеличился на 1
- Крайние левые цифры счетчика кадров никогда не достигали 43. Например:
[420, 421, 422, ..., 429, 4200, 4201, ..., 4299, 42000, ...]
Имея это в виду, я сделал свою первую попытку смоделировать щелчок с помощью веб-сокета:
const socket = new WebSocket('wss://socket-unlock.oneplus.com/socket.io/?EIO=3&transport=websocket'); socket.addEventListener('open', function(event { console.log("opened"); socket.send(authFrame); socket.send(firstTap); });
Он успешно подключится, но первое нажатие не отправит 🤔
Получается, что сервер принимает тапы только после того, как отправил ответ, подтверждающий аутентификацию. Кадр подтверждения всегда начинается с 430 и содержит ваше имя пользователя. Так что все, что мне нужно было сделать, это дождаться возврата этого кадра, а затем отправить свои краны.
const socket = new WebSocket('wss://socket-unlock.oneplus.com/socket.io/?EIO=3&transport=websocket'); socket.addEventListener('open', function(event) { console.log("opened"); socket.send(authFrame); }); socket.addEventListener('message', function(event) { if (event.data.indexOf("raybb") > -1) { if (event.data.indexOf("430") > -1) { socket.send(firstTap); } } });
После выяснения этого было довольно легко написать остальную часть кода. Полный сценарий можно посмотреть здесь.
Единственная проблема с этим методом заключалась в том, что отправка нажатий в конечном итоге замедлялась. После отправки нескольких тысяч нажатий скрипт станет заметно медленнее и останется на этой скорости. Я подозреваю, что это было связано с рекурсивным характером сценария, который требует создания множества слоев переменных с областью видимости. Я изменил код, чтобы отправить 100 нажатий на каждом уровне рекурсии. Это, казалось, немного помогло, но замедление все еще было заметным. Затем я заблокировал загрузку всех скриптов на веб-сайте OnePlus (по сути, это была просто пустая страница). Это тоже немного помогло.
Я так и не нашел решения для скрипта замедления. Конкурс подходил к концу слишком долго. Это не было большой проблемой, и, прежде чем я это понял, я был на пути к 1 миллиону нажатий. Возможно, я не выиграл никаких призов от этого, но я узнал о событиях кликов, веб-сокетах, xdotool и немного больше о просмотре сайтов с помощью отладчика.
Спасибо за прочтение 🤓👏