The above-described issue was solved as follows: ADB managed to drop down the status bar and select the required mode in the settings menu. Then a script with ADB commands was created in /
folder to automate this process on each boot. But are there any other things you can implement with ADB in a similar way? Oh, sure!
In late 2021, World Chess Championship was held in Dubai. Interest in this event in Russia was fueled by the fact that the Russian Ian Nepomniachtchi had challenged the current world champion Magnus Carlsen. My friend and I closely watched the tournament and even decided to play in tandem against Magnus. But since he was busy with the championship, we had to play against his electronic clone: the Play Magnus app for Android.
The difficulty level in the app is set by the age of Magnus Carlsen whose professional skills are truly amazing: we never managed to reach the ’13 years’ level – at this age, Magnus became one of the youngest grandmasters in the world. Another impressive ability of professional chess players is playing without looking at the chessboard. The record belongs to Timur Gareev who simultaneously played blindfold on 48 boards! My friend was eager to master this skill, and I decided to help him by writing a script acting as a wrapper for the chess app.
The script logic is simple: you enter your move (e.g. ‘e2e4’) on the computer keyboard, and the program performs a sequence of taps on the screen of your phone via ADB. Then the entire history of the game is copied to the phone clipboard and transmitted to the computer. The last move (i.e. the countermove of the chess app) is displayed on the screen. For a better understanding of what’s going on, you can watch a demo of this scrip in action.
ADB
Android Debug Bridge (ADB) is a handy debugging tool for Android devices. Among other things, it allows you to move files from your computer to your phone and vice versa using the commands adb
and adb
.
To install or remove an app, just type adb
or adb
. To connect to the phone command shell, use the adb
command. ADB supports plenty of other functions, but for my purposes, it was important that it can simulate interactions with the phone’s touchscreen (i.e. make taps and swipes). Overall, I strongly recommend using ADB if you are a hacker pentester specializing in Android.
Prior to using ADB for the first time, you have to enable USB debugging on your phone. To do this, open “Settings”, go to the “System” section, and then to “About phone”. Here, look for the item “Build Number” and tap it several times. An additional item, “Developer options”, will appear in the “System” section. Go there and activate “USB debugging”.
After enabling ADB on your phone, you have to install the ADB server on your computer. If you are a Linux user, enter the command sudo
in the command-line interpreter. On a Windows PC, you have to download the archive with utilities. Unzip it to any location you like (e.g. C:\
). Then open the command prompt by pressing Win + R and typing cmd
and go to the folder containing the ADB utility by typing cd
.
Now you can connect your phone to the computer using a USB cable. You may need to switch your phone from “Charge” mode to “File Transfer” mode. Then type adb
in the command line. The first time you connect, you should grant debugging permissions to the new device. Check the “Always allow from this computer” box on your phone and click OK.
You can continue using ADB via a USB cable, but it’s not always handy. To operate remotely over network, enter the command adb
: ADB will wait for a connection from your PC on TCP port 5555
. After that, you can disconnect the cable and enter the command adb
on your computer.
Now all ADB commands will work as if the phone were connected with a cable. This setting remains in effect until the phone is rebooted. Now you have to be careful: make sure not to allow an attacker to connect to your phone by accident. If an unknown device connects to the phone, it will notify you by displaying a message. You can “revoke USB debugging permissions” in the “Developer options” section next to the item enabling USB debugging. The adb
command switches the phone back to the USB cable.
Writing code
I am going to write my script in Python 3. First of all, I have to import the required modules and define the path to the ADB utility in the system.
#!/usr/bin/env python3import osimport sysimport timefrom subprocess import Popen, PIPEADB = 'adb' # 'C:\platform-tools\adb.exe' for Windows
Writing the help
function.
def help(): print(f'Usage: {sys.argv[0]} [-b | --black] [-h | --help]\n') print(f' -b, --black: play the Black pieces, by default you play the White') print(f' -h, --help: show this help and exit\n')
The main logic will be implemented in a class called Screen
. I set the parameters board_x0
, board_y0
, board_x1
, and board_y1
representing the x and y coordinates of the upper left and buttom right edges of the chessboard, respectively. The menu
and close_menu
represent positions of the buttons that open and close the menu. The save_game
, ok_button
and back_button
stand for coordinates of the Export Game button, OK button, and Undo Last Move button, respectively (see the screenshot). Pawn
is an array of coordinates of buttons used to select a new queen, rook, bishop, or knight to replace a pawn when it moves to its last rank.
Since coordinates of most elements depend on the screen resolution, they have to be set for each device individually. To determine coordinates, I suggest activating “Show taps” and “Pointer location” functions in “Developer options”. I add the game
variable to store the progress of the game. I also define TAP_SLEEP
(delay between taps on the screen) and MOVE_SLEEP
(pause after each move). It should be large enough to let the program make a countermove.
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
When you press the “Export Game” button, all moves made since the beginning of the game are written to the clipboard using the special chess notation, PGN. But the problem is that ADB cannot directly get data from the clipboard. To solve this problem, I use the Clipper app that allows to read from the clipboard and write to it using broadcast intents. Clipper is an open-source app, but don’t worry: you don’t have to compile it for Android. The repository contains a ready-use apk file. Download and then install it by executing the adb
command in the folder containing the apk file.
Time to write the __init__(
function: it will run the Clipper app and calculate the size of chessboard squares using the recently defined constants.
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
Then I define the _tap_screen(
function that takes x and y coordinates and simulates tapping the phone screen at this location.
def _tap_screen(self, x, y): os.system(f'{ADB} shell input tap {x} {y}') time.sleep(self.TAP_SLEEP)
For convenience purposes, I create another function: _tap_board(
. Its only difference from _tap_screen(
is that its x and y coordinates are relative to the chessboard, not the screen, and the reference point is placed in the bottom left corner of the playing area.
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)
The _copy_game(
function is used to export the game progress to the clipboard. It simulates taps in the respective places of the screen. To get data from the buffer, the Clipper app is called by executing the command adb
. The data I need are contained in the output of this command in the data
field.
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
To make moves, I am going to write the move(
function that takes as input a string in the ‘e2e4’ format ignoring case distinctions. If the ‘back’ string is passed instead of a move, the program will undo the last move I’ve made. To calculate the coordinates of the required chessboard square, I use the string method find
. It searches for the move letter (or number) in the string abcdefgh
(or 12345678
) and returns its index.
This index represents the position of the required square on the chessboard to tap on. If I play the Black, the order of the letters abcdefgh
(as well as the numbers 12345678
) should be reversed, because the app flips the chessboard. If a pawn moves to its last rank, then an additional letter will determine the piece that replaces it. For instance, if you make the move a7a8r
, then the pawn will be replaced by a 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)
Finally, I have to write the answer(
function that receives Magnus’ countermoves. A situation when the game progress doesn’t change after the player’s move must be taken into account as well. It occurs if the player’s move was incorrect. Such a situation is quite possible when you play blindfold chess. In this case, the ‘ILLEGAL MOVE!’ message will be displayed. If the move was correct, then the last move in the game (i.e. countermove of the chess program) has to be written to the answer
variable. The state
variable contains the result of the game. The *
character in state
means that the game is ongoing (see the PGN description). At the end of the game, state
will contain the White and Black scores.
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
The Screen
class is ready now. It’s time to write the main loop of the program. Help
will be displayed if the -h
command line option is specified. The is_black
flag is responsible for the color of the player’s pieces; it will be set by the -b
parameter. Next, I create an s
class instance object and launch an infinite loop with moves and countermoves; it runs until the game ends. The criterion for the end of the game is the absence of the *
character in the state
variable. In addition, if the player is playing the Black, the program must first read the initial move of the White. You can exit the game at any time by pressing 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 the Black') else: print('You play the 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()
That’s it. Prior to running the script, make sure that your phone is on the adb
list.
Conclusions
You may have noticed that I don’t control the execution of commands sent to the phone. Yet another ‘blindfold game’… Of course, this is a significant drawback of the approach used. If you want, you can add feedback to the above algorithm, e.g. by taking a screenshot with the following commands:
adb shell screencap /sdcard/screen.png
adb pull /sdcard/screen.png
Note that the ADB server can also be installed on Android to control the target phone from another phone (or even from itself!). To do this, you can use Termux as a terminal emulator for Android. The program code is available on GitHub. Feel free to use it as a basis for your own scripts. Good luck!