Хакер - Игра вслепую. Управляем Android-смартфоном через ADB

Хакер - Игра вслепую. Управляем Android-смартфоном через ADB

hacker_frei

https://t.me/hacker_frei

rexby

Од­нажды мне понадо­билось, что­бы одноплат­ник Raspberry Pi при заг­рузке перево­дил под­клю­чен­ный к нему телефон в режим USB-tethering. На помощь при­шел ADB — интерфейс отладки Android-устрой­ств. Сущес­тву­ет нес­коль­ко спо­собов авто­мати­зиро­вать работу при­ложе­ний на Android-смар­тфо­не с помощью это­го интерфей­са, и в статье мы рас­смот­рим один из них.

За­дачу мне уда­лось решить так: с помощью ADB получи­лось открыть вер­хнюю штор­ку и через меню нас­тро­ек выб­рать нуж­ный режим. Пос­ле чего скрипт, выпол­няющий эту пос­ледова­тель­ность дей­ствий, был помещен в /etc/rc.local и стар­товал при заг­рузке мик­рокомпь­юте­ра. Мож­но ли похожим обра­зом авто­мати­зиро­вать дру­гие про­цес­сы на Android-смар­тфо­не? Конеч­но!

В кон­це 2021 года в Дубае про­ходил матч за зва­ние чем­пиона мира по шах­матам. Инте­рес к это­му событию в Рос­сии был подог­рет тем, что про­тив дей­ству­юще­го чем­пиона мира — Маг­нуса Кар­лсе­на — выс­тупал рос­сиянин Ян Непом­нящий. Мы с при­яте­лем сле­дили за ходом тур­нира и даже решили сыг­рать в тан­деме про­тив Маг­нуса. Но пос­коль­ку сам он был занят чем­пиона­том, то приш­лось сра­жать­ся с его элек­трон­ным кло­ном — прог­раммой Play Magnus для Android.

Уро­вень слож­ности в прог­рамме зада­ется воз­растом Маг­нуса Кар­лсе­на. Мас­терс­тво про­фес­сиональ­ных шах­матис­тов поража­ет: дой­ти до уров­ня 13 лет — воз­раста, в котором Маг­нус стал одним из самых молодых грос­смей­сте­ров мира, — нам так и не уда­лось. Так­же впе­чат­ляет спо­соб­ность шах­матис­тов играть, не гля­дя на дос­ку. Рекорд при­над­лежит Тимуру Гаре­еву: одновре­мен­ная игра на 48 дос­ках всле­пую! Мой при­ятель Женя тоже решил поп­ракти­ковать этот уди­витель­ный навык. А у меня соз­рел план помочь ему в этом: написать скрипт — обер­тку для шах­матно­го при­ложе­ния.

Ло­гика работы скрип­та прос­та: вво­дишь на кла­виату­ре компь­юте­ра свой ход, нап­ример 'e2e4', и прог­рамма через ADB выпол­няет необ­ходимую для это­го пос­ледова­тель­ность нажатий (тапов) на телефо­не. Затем вся исто­рия игры копиру­ется в буфер обме­на смар­тфо­на, отку­да попада­ет на компь­ютер. На экран выводит­ся пос­ледний ход — ответ шах­матной прог­раммы. Для луч­шего понима­ния матери­ала статьи можешь пос­мотреть, что получи­лось в ито­ге.

Скрин­шот при­ложе­ния Play Magnus

ПРО ADB

ADB (Android Debug Bridge) — это инс­тру­мент для отладки устрой­ств на базе ОС Android. Его воз­можнос­ти доволь­но обширны: мож­но переме­щать фай­лы с компь­юте­ра на телефон и обратно. Для это­го слу­жат коман­ды adb push filename и adb pull filename.

Что­бы уста­новить или уда­лить при­ложе­ние, дос­таточ­но ввес­ти adb install program.apk или adb uninstall <имя пакета>. А для под­клю­чения к коман­дной обо­лоч­ке телефо­на исполь­зуй adb shell. ADB поз­воля­ет делать еще мно­го чего. Но глав­ное для нас — с его помощью мож­но ими­тиро­вать работу с тачс­кри­ном телефо­на (делать тапы и свай­пы). Если ты ни разу не исполь­зовал ADB, то самое вре­мя поп­робовать! Тем более «Хакер» уже под­робно пи­сал на эту тему.

Пе­ред пер­вым исполь­зовани­ем нуж­но вклю­чить отладку по USB. Для это­го откры­вай «Нас­трой­ки» и перехо­ди в раз­дел «Сис­тема», а затем «О телефо­не». Здесь ищи пункт «Номер сбор­ки» и кли­кай по нему нес­коль­ко раз. Пос­ле этих дей­ствий в раз­деле «Сис­тема» появит­ся допол­нитель­ный пункт «Для раз­работ­чиков». Заходи туда, ищи и акти­вируй пункт «Отладка по USB».

Пос­ле вклю­чения ADB в телефо­не нуж­но будет уста­новить ADB-сер­вер на компь­юте­ре. Если ты поль­зователь Linux, то для это­го дос­таточ­но ввес­ти в кон­соли коман­ду sudo apt install adb. Если же на тво­ем ком­пе уста­нов­лена Windows, то при­дет­ся ска­чать ар­хив с ути­лита­ми. Рас­пакуй его в удоб­ное мес­то, нап­ример C:\platform-tools. Пос­ле чего откры­вай коман­дную стро­ку (нажимай Win + R и вво­ди cmd) и перехо­ди в пап­ку с ути­литой ADB, вве­дя коман­ду cd C:\platform-tools.

Те­перь можешь под­клю­чать телефон к компь­юте­ру по USB-кабелю. Воз­можно, тебе понадо­бит­ся перевес­ти его из режима «Заряд­ка» в режим «Переда­ча фай­лов». Пос­ле чего в коман­дной стро­ке набери adb devices. При пер­вом под­клю­чении на телефо­не дол­жен появить­ся зап­рос на раз­решение отладки с нового устрой­ства. Отметь галоч­кой «Всег­да раз­решать отладку с это­го компь­юте­ра» и жми OK.

Мо­жешь про­дол­жить поль­зовать­ся ADB через USB-кабель, но такой спо­соб не всег­да удо­бен. Что­бы перек­лючить телефон на работу по сети, нуж­но ввес­ти коман­ду adb tcpip 5555. Здесь 5555 — TCP-порт, на котором служ­ба ADB будет ожи­дать под­клю­чения от ПК. Пос­ле чего про­вод мож­но отсо­еди­нить, а на компь­юте­ре ввес­ти коман­ду adb connect <IP-адрес телефона>. В резуль­тате все коман­ды ADB будут работать так же, как если бы телефон был под­клю­чен по кабелю. Дан­ная нас­трой­ка сох­раня­ется до перезаг­рузки телефо­на. Одна­ко теперь нуж­но быть бди­тель­ным, что­бы слу­чай­но не пре­дос­тавить дос­туп к телефо­ну зло­умыш­ленни­ку. Если к телефо­ну под­клю­чит­ся нез­накомое устрой­ство, он покажет сооб­щение об этом. Отоз­вать выдан­ные раз­решения мож­но в раз­деле «Для раз­работ­чиков» рядом с тем пун­ктом, где ты раз­решал отладку по USB. Перек­лючит обратно на работу по про­воду коман­да adb usb.

ПИШЕМ КОД

Прог­рамми­ровать будем на Python 3. Преж­де все­го импорти­руем необ­ходимые модули и зададим путь к ути­лите ADB в сис­теме.

#!/usr/bin/env python3

import os

import sys

import time

from subprocess import Popen, PIPE

ADB = 'adb' # 'C:\platform-tools\adb.exe' для Windows

На­пишем фун­кцию help.

def help():

print(f'Usage: {sys.argv[0]} [-b | --black] [-h | --help]\n')

print(f' -b, --black: play with black pieces, by default you play with white')

print(f' -h, --help: show this help and exit\n')

Ос­новную логику работы реали­зуем в клас­се Screen. Зададим парамет­ры board_x0board_y0board_x1board_y1 — коор­динаты x и y левого вер­хне­го и пра­вого ниж­него края дос­ки соот­ветс­твен­но. Кон­стан­ты menu и close_menu — положе­ние кно­пок для откры­тия и зак­рытия меню. Кон­стан­ты save_gameok_button и back_button — коор­динаты кноп­ки сох­ранения пар­тии, кно­пок OK и отме­ны пос­ледне­го хода соот­ветс­твен­но (см. рисунок). Pawn — мас­сив с коор­дината­ми кно­пок для выбора фигур вза­мен пеш­ки, ког­да она ходит на пос­леднюю горизон­таль.

Пос­коль­ку коор­динаты боль­шинс­тва эле­мен­тов зависят от раз­решения экра­на, то их при­дет­ся опре­делять для каж­дого устрой­ства инди­виду­аль­но. Делать это удоб­но, если акти­виро­вать пункт «Отоб­ражать касания» в раз­деле нас­тро­ек «Для раз­работ­чиков». Вве­дем перемен­ную game, где будем хра­нить ход игры. А так­же зададим TAP_SLEEP — задер­жку меж­ду тапами по экра­ну и MOVE_SLEEP — паузу пос­ле каж­дого хода. Ее необ­ходимо уста­новить такой, что­бы прог­рамма успе­вала делать ответный ход.

class Screen:

board_x0 = 0

board_y0 = 412

board_x1 = 1080

board_y1 = 1492

menu = 1000, 70

close_menu = 80, 70

save_game = 550, 580

ok_button = 545, 1115

back_button = 860, 1870

pawn = [(450, 830), (450, 1020),

(450, 1200), (450, 1380)]

game = ''

TAP_SLEEP = 0.2

MOVE_SLEEP = 3

Вид прог­раммы при вызове меню. Акти­виро­ван­ная фун­кция «Отоб­ражать касания» показы­вает коор­динаты касаний в стро­ке навер­ху
Вид прог­раммы при сох­ранении пар­тии
Вид прог­раммы при выборе фигуры вза­мен пеш­ки

При нажатии кноп­ки «Сох­ранить пар­тию» все сде­лан­ные с начала игры ходы записы­вают­ся в буфер обме­на в спе­циаль­ной шах­матной нотации PGN. Но проб­лема в том, что ADB не уме­ет работать с буфером обме­на. Решить проб­лему поможет при­ложе­ние Clipper, которое поз­воля­ет читать из буфера и писать в него пос­редс­твом широко­веща­тель­ных сооб­щений (broadcast intents). Код при­ложе­ния открыт. Но не пережи­вай, ком­пилиро­вать его под Android не при­дет­ся. Раз­работ­чики позабо­тились о том, что­бы в репози­тории был готовый apk-файл. Для его уста­нов­ки перей­ди в пап­ку со ска­чан­ным фай­лом прог­раммы и вве­ди коман­ду adb install clipper.apk.

На­пишем фун­кцию __init__(), в которой запус­тим при­ложе­ние Clipper, а так­же вычис­лим раз­мер одной клет­ки игро­вого поля, исполь­зуя недав­но задан­ные кон­стан­ты.

def __init__(self):

cmd = f'{ADB} shell am startservice ca.zgrs.clipper/.ClipboardService &>/dev/null'

p = Popen(cmd.split())

self.field_size = (self.board_x1 - self.board_x0)/8

Так­же опре­делим фун­кцию _tap_screen(), которая будет при­нимать коор­динаты x и y и ими­тиро­вать нажатие на экран телефо­на в этом мес­те.

def _tap_screen(self, x, y):

os.system(f'{ADB} shell input tap {x} {y}')

time.sleep(self.TAP_SLEEP)

Для удобс­тва соз­дадим еще одну фун­кцию _tap_board(), которая отли­чает­ся от _tap_screen() толь­ко тем, что коор­динаты x и y будут отсчи­тывать­ся отно­ситель­но дос­ки, а не экра­на. За ноль при этом выберем левый ниж­ний угол игро­вого поля.

def _tap_board(self, x, y):

x += self.board_x0

y = self.board_y1 - y

os.system(f'{ADB} shell input tap {x} {y}')

time.sleep(self.TAP_SLEEP)

Для копиро­вания хода игры в буфер обме­на слу­жит фун­кция _copy_game(). Она ими­тиру­ет нажатия в нуж­ных мес­тах экра­на. Дан­ные из буфера получим, обра­тив­шись к при­ложе­нию Clipper коман­дой adb shell am broadcast -a clipper.get. В выводе этой коман­ды инте­ресу­ющие нас дан­ные содер­жатся в поле data.

def _copy_game(self):

self._tap_screen(*self.menu)

self._tap_screen(*self.save_game)

self._tap_screen(*self.ok_button)

self._tap_screen(*self.close_menu)

cmd = f'{ADB} shell am broadcast -a clipper.get'

p = Popen(cmd.split(), stdin=PIPE, stdout=PIPE)

out = p.stdout.read().decode('utf-8')

out = out.split('data=')[1]

out = out[1:-2]

return out

Что­бы делать ходы, напишем фун­кцию move(), которая будет при­нимать на вход стро­ку вида 'e2e4' в любом регис­тре. Если вмес­то хода будет переда­на стро­ка 'back', то прог­рамма отме­нит пос­ледний сде­лан­ный ход. Для вычис­ления коор­динат нуж­ной нам клет­ки на дос­ке вос­поль­зуем­ся стро­ковым методом find. Он оты­щет в стро­ке abcdefgh (или 12345678) бук­ву (или циф­ру) нашего хода и вер­нет соот­ветс­тву­ющий индекс.

Имен­но на столь­ко кле­ток нуж­но будет отсту­пить от начала дос­ки, что­бы тап­нуть по нуж­ному нам полю. В слу­чае игры чер­ными фигура­ми порядок букв abcdefgh, как и цифр 12345678, сле­дует поменять на обратный, так как ори­ента­ция дос­ки в при­ложе­нии изме­нит­ся. Если пеш­ка ходит на пос­леднюю горизон­таль, то допол­нитель­ная бук­ва опре­делит фигуру, ее заменя­ющую. Нап­ример, если сде­лать ход a7a8r, то пеш­ка будет замене­на ладь­ей (rook).

def move(self, mov):

mov = mov.lower()

if mov == 'back':

self._tap_screen(*self.back_button)

time.sleep(self.MOVE_SLEEP/2)

return

c = 'abcdefgh'

n = '12345678'

p = 'qrbn'

if is_black:

c = c[::-1]

n = n[::-1]

start_pos_x = int(c.find(mov[0]) * self.field_size + self.field_size/2)

start_pos_y = int(n.find(mov[1]) * self.field_size + self.field_size/2)

end_pos_x = int(c.find(mov[2]) * self.field_size + self.field_size/2)

end_pos_y = int(n.find(mov[3]) * self.field_size + self.field_size/2)

self._tap_board(start_pos_x, start_pos_y)

self._tap_board(end_pos_x, end_pos_y)

if mov[4:]:

self._tap_screen(*self.pawn[p.find(mov[4])])

time.sleep(self.MOVE_SLEEP)

Ос­талось написать фун­кцию answer(), получа­ющую ответ Маг­нуса. Сто­ит пре­дус­мотреть слу­чай, ког­да исто­рия ходов не изме­нилась пос­ле оче­ред­ного хода игро­ка. Это говорит о том, что был сде­лан некор­рек­тный ход. Это впол­не воз­можно при игре всле­пую. В таком слу­чае игро­ку будет показа­но сооб­щение 'ILLEGAL MOVE!'. Если ход был сде­лан вер­но, то в перемен­ную answer запишем пос­ледний ход в игре — ответ шах­матной прог­раммы. Перемен­ная state содер­жит резуль­тат игры. Пока игра не завер­шена, там будет сим­вол *, как это опре­деле­но в PGN. В кон­це игры в state ока­жут­ся начис­ленные игро­кам очки.

def answer(self):

new_game = self._copy_game()

if new_game == self.game:

answer = 'illegal move!'.upper()

state = self.game.split()[-1]

else:

answer, state = new_game.split()[-2:]

self.game = new_game

return answer, state

Класс Screen готов. Прис­тупим к написа­нию основно­го цик­ла прог­раммы. Будем выводить help, если был задан параметр -h коман­дной стро­ки. А так­же уста­нав­ливать флаг is_black, отве­чающий за цвет фигур игро­ка, в зависи­мос­ти от наличия или отсутс­твия парамет­ра -b. Далее соз­дадим экзем­пляр s клас­са Screen и запус­тим бес­конеч­ный цикл ходов — отве­тов до тех пор, пока игра не закон­чится. Кри­тери­ем кон­ца игры будет отсутс­твие сим­вола * в перемен­ной state. Кро­ме того, в слу­чае игры чер­ными фигура­ми пред­варитель­но нуж­но счи­тать пер­вый ход белых. Закон­чить игру в любой момент мож­но, нажав Ctrl + C.

if __name__ == '__main__':

if len(sys.argv) < 2:

is_black = False

else:

if sys.argv[1] in ['-h', '--help']:

help()

sys.exit()

is_black = '-b' in sys.argv[1]

if is_black:

print('You play for black')

else:

print('You play for white')

s = Screen()

if is_black:

answer, state = s.answer()

print(f"Magnus' move: {answer}")

while True:

try:

move = input('Your move: ')

s.move(move)

if move != 'back':

answer, state = s.answer()

if '*' in state:

print(f"Magnus' move: {answer}")

else:

print(f"Last move: {answer}")

print(f"Result: {state}")

break

except KeyboardInterrupt:

print()

sys.exit()

На этом все. Перед запус­ком скрип­та не забудь убе­дить­ся, что в выводи­мом коман­дой adb devices спис­ке есть твой телефон.

Заключение

Ду­маю, ты обра­тил вни­мание, что, отправ­ляя коман­ды в телефон, мы никак не кон­тро­лиро­вали кор­рек­тность их исполне­ния. Тоже сво­его рода «игра всле­пую». Это, конеч­но, сущес­твен­ный недос­таток такого под­хода к управле­нию устрой­ством.

При желании мож­но допол­нить алго­ритм работы обратной связью, нап­ример получая скрин­шот экра­на с помощью команд

adb shell screencap /sdcard/screen.png

adb pull /sdcard/screen.png

Так­же сто­ит отме­тить, что ADB-сер­вер мож­но уста­новить на сам Android и управлять телефо­ном с дру­гого телефо­на либо даже с него самого. Для это­го вос­поль­зуйся Termux — эму­лято­ром тер­минала для Android. О нем я недав­но опуб­ликовал статью в «Хакере». Код прог­раммы я выложил на GitHub. Можешь сме­ло брать его за осно­ву для соз­дания собс­твен­ных скрип­тов. Желаю уда­чи!

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei



Report Page