Имитатор касаний. Ч3: Программная часть

Цифры нажимались как бы сами собой, быстро следуя в чётком порядке. Это было очень круто! Кряк даже пустил слезу от умиления, а енот довольно потирал свои ловкие ручонки.) Наверняка ему не терпится сделать ещё один интересный проект.
+++
Проанализировав данные, полученные с помощью обратной разработки в части 1 и части 2, можем прикинуть алгоритм работы имитатора касаний, написать приложение и взломать пароль! Этим сегодня и займёмся.
— Кто-нибудь, разбудите программиста!
В видеоформате какие-то вещи получаются нагляднее и проще для понимания, чем в тексте. Поэтому предлагаю вам посмотреть видео об этом проекте.
Содержания видео и статей в данном случае отличаются и дополняют друг друга.
❯ Начинаем шкодить прошивку для STM32
Использовать будем библиотеку HAL, так как хочется максимально быстро и просто получить рабочий прототип и проверить идеи. Хорошие уроки по STM32.

При настройке микроконтроллера в Куб МХ включаем отладку serial wire.
Настраиваем тактирование.
RST подаём на вход мк. Включаем подтяжку к +, так как активный низкий уровень. Без подтяжки могут быть шумы.
VIBRO подаём на вход мк. Включаем подтяжку к –, так как активный высокий уровень.
INT — выход, push-pull.
PWR_KEY — выход управления кнопкой блокировки через транзистор.
Настраиваем I2C. Режим ведомого, указываем такой же адрес, как у контроллера сенсорной панели. 0b111000 = 56
Обратного разработчика и монтажника вы уже знаете, а это наш шкодер — Катюня Константиновна:

❯ Что делает телефон, если не принять ни одного байта по I2C
Компилируем, прошиваем, пробуем включить.
Телефон (или только экран?) даже не включается. Мы питаем преобразователь уровней от питания экрана. Почему-то так не хочет работать. Поэтому запитаем преобразователь уровней не от питания LCD, а от +1,8 В на плате преобразователя. Теперь экран включается без проблем.
Может показаться, что STM32 откликнется на свой адрес, даже если не принимать посылаемые ему посылки. Ведь мы сконфигурировали I2C в Кубе.
Но нет. Первая посылка от телефона остаётся без ответа (вместо ACK видим NACK, на картинке просто N).
Интересно, что, не получив ответа от адреса 70, телефон один раз сбрасывает контроллер тача и 10 раз (с паузами и активацией сброса) пробует писать по адресу ВА, затем по адресу 80. Возможно, такие адреса у других моделей контроллеров сенсорной панели. Это обычное дело встроить в прошивку смартфона несколько драйверов для разных моделей модуля (экрана, сенсорной панели, и т д). Например, вот участок схемы для Explay A351:
Пишем строчку HAL_I2C_Slave_Receive (&hi2c1,&i2c_data[0],1,300);
(принимаем 1 байт по I2C1, сохраняем в i2c_data[0], 300 мс– время ожидания посылки до формирования ошибки приёма)
и микроконтроллер начинает откликаться на свой адрес (получаем ACK, сокращённо А).

❯ Прошивка STM32 версии 1
Файл main.c можно посмотреть тут.
В коде достаточно просто разобраться, везде есть комментарии.
Контроллер в бесконечном цикле ждёт команд от компьютера по UART.
Каждый раз передаётся 4 байта.
Формат такой: код команды, XHYH, XL, YL.
XH и YH занимают по 4 бита, вместе 1 байт.
Команда 10 — сделать касание.
Например, для касания цифры 0 X=120, Y=2E1 [0×10, 0×12,0×20,0xE1]
Команда 20 — включить телефон [0×20,0×00,0×00,0×00]
Команда 30 — разблокировать телефон [0×30,0×00,0×00,0×00]
Команда 40 — смах заставки. Начальная точка X=06F, Y=35E (можно ввести другие координаты).
[0×40,0×03,0×6F,0×5E]
В зависимости от полученной команды выполняются нужные действия.
Для простоты и быстроты решения основных задач проекта используются простые вещи (например, блокирующие циклы вместо прерываний, библиотека HAL и т. д.) и реализован только основной код без каких-то защит и проверок. Поэтому контроллер может зависнуть, если что-то пойдёт не так или команды будут не в задуманном порядке. Тем не менее, это работает и решает основные и самые интересные задачи.
При имитации касаний пакеты посылаются процессору смартфона так же, как это делает контроллер сенсорной панели.
При приёме команды и после выполнения действий микроконтроллер посылает приложению строки вида Command received, Making touch, OK, и др.
Отлаживать прошивку stm32 и взаимодействовать с ним можно с помощью UART через терминал, отправляя, например, код команды и координаты касаний. Но удобнее и интереснее делать это в своём приложении. Поэтому параллельно с прошивкой будем разрабатывать управляющее приложение-интерфейс на Питоне.
❯ Пишем приложение
На чём лучше написать оконное приложение на Python: Tkinter или PyQT?
Tkinter лучше документирован, и с ним немного проще работать. Но в библиотеке PyQt5 есть больше виджетов, включая редакторы дат, индикатор выполнения, ползунок, средство выбора шрифта, панель инструментов и многострочное поле ввода текста. Он также поддерживает шаблон MVC и включает всплывающие подсказки, которые могут быть полезны новым пользователям.
Tkinter против PyQt: выбор правильной библиотеки GUI для ваших проектов на Python — хороший обзор (коротко и по делу). Сравнение.
Мне понравилась библиотека PyQt. Все проекты, созданные на PyQt, кроссплатформенные. То есть созданную программу можно запустить на любой операционной системе.
С помощью QT Designer можно создать симпатичные интерфейсы. Например:

Как будет выглядеть интерфейс нашего приложения?
Мы работаем с com портом. Поэтому можно сделать элементы управления для него.
В дальнейшем можно будет убрать настройки ком порта на отдельную форму, которая будет вызываться из меню сверху.
А сейчас для простоты сделаем всё в основном окне.
Сначала попробовал использовать PyQT 6, но приложение не запускалось. И я перешёл на PyQT 5. Дело в том, для 6й версии нужна Windows 10 или выше, а у меня стояла семёрка.
Как можно подключить графический интерфейс в приложение?
- После создания интерфейса в Qt Designer преобразовать файл.ui в Питон файл через pyqt tools.
- Загрузить пользовательский интерфейс из файла.ui в наше приложение при его запуске.
Второй способ удобен, так как все изменения интерфейса после сохранения автоматически подтягиваются в наш Питон файл. А ещё сам интерфейс отделён от логики, описываемой в Питон файле.
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('basic.ui', self) # Load the .ui file
self.show() # Show the GUI
- How to Import a PyQt5 .ui File in a Python GUI
- PyQt5 для начинающих.
- Отличная статья про оформление графического интерфейса, как создать и загрузить значок приложения. Простой GUI калькулятор на Python #1. Дизайн приложения
- Хорошее видео с объяснением конструкции
if __name__ == '__main__': #Что-то вроде "Если запускаем данный файл как основной (верхнего уровня), то выполняем этот иф".
Хотелось бы, чтобы приложение отслеживало подключение нашего устройства и автоматически подключалось к нему. Но, видимо, при использовании последовательного (COM) порта так не получится.
Файлы приложения можно посмотреть здесь.
Интерфейс версии 1 получился такой:

Работу системы вживую можно посмотреть здесь.
Вот мы добрались до взлома пароля последовательным перебором вариантов.
Пароль 4-значный, цифры 0…9 — значит, возможно 10^4 вариантов.
❯ Кто будет перебирать пароли: микроконтроллер или Питон приложение?
Вариант 1.
Приложение на компьютере — это лишь удобный интерфейс для посылки команд микроконтроллеру. Нажимаем кнопочку «Взломать пароль», приложение посылает контроллеру соответствующую команду. А он делает всё необходимое для её выполнения:
- Отправляет на телефон касания четырёх цифр с некоторой паузой.
- Проверяет активность вибромотора.
Если есть вибрация — пароль неверный. Увеличиваем цифру на 1 и посылаем следующий пароль (4 цифры).Если вибрации нет, то пароль верный. Это предположительно, будем наблюдать за телефоном во время экспериментов.
То есть основная роль у микроконтроллера.
Вариант 2.
Микроконтроллер выполняет только простые действия, вроде посылки телефону касания цифры, а всей логикой занимается приложение на компьютере. В таком случае приложению нужно чётко отслеживать состояние смартфона и микроконтроллера.
Вариант, когда перебором занимается микроконтроллер, мне кажется проще. В таком случае он сам выполняет все нужные действия и не нужно постоянно передавать приложению на компьютер посылки с текущим состоянием смартфона и самого микроконтроллера.
Во второй версии приложения и прошивки добавляем кнопку «Взломать пароль с обработчиком и соответствующий код.
Файлы приложения и прошивки лежат в репозитории проекта.
Интерфейс версии 2 выглядит так:

Всё то же самое, только добавилась кнопка «Взломать пароль».
В версии 2 добавилась команда 50 — перебрать пароль, начиная от N=Ц1Ц2Ц3Ц4 (Ц — это цифра от 0 до 9)
Команда, Ц1Ц2, Ц3Ц4, 00
[0×50,0×00,0×00,0×00]
При приёме команды на взлом пароля в микроконтроллере запускается цикл, в котором последовательно перебираются все варианты от 0000 до 9999 с шагом 1. Каждой цифре соответствуют свои координаты касания на тачскрине, которые мы выяснили ранее.
По UART отправляется текущий пароль и слово WRONG, если он неверный.
Управлять имитатором можно из простого терминала, но удобнее работать из специального приложения, которое мы написали. Особенно, если добавлять какие-то более сложные функции. Например, делать касания в произвольных местах экрана, кликая мышкой по картинке.
Пароль найден! ☺ 2171
Работу системы вживую можно посмотреть здесь.
❯ Убираем пропуски паролей
На этапе отладки я заснял процесс перебора паролей. На видео заметно, что некоторые варианты пропускаются.
Только что было касание цифры 2, она ввелась в пароль, и очень быстро перескок на 1. Двойка ещё не погасла. Стоит увеличить паузу между касаниями цифр (меняем с 20 на 50 мс).

На другом кадре видно, что анимация (дёрганье точек) при неверном пароле ещё не закончилась, а уже есть новое касание.

В итоге первая цифра не ввелась в пароль и вместо четырёх цифр ввелось только 3. Следующий вариант пароля начинает вводится в 4-ю цифру предыдущего. То есть пропускается сразу два варианта пароля.
Увеличим задержку до ввода следующего варианта ключа с 200 до 400 мс.
Всё становится замечательно, пропусков больше нет.

Как видно, по видео весьма удобно наблюдать и отлаживать относительно быстрые процессы.
❯ Полезные хитрости
«Программирование» комментариями.
Очень удобно сначала придумать и словесно записать алгоритм в комментарии, а потом по пунктам описывать его кодом. Так и комментарии для кода сразу будут готовы, и можно придумать и описать алгоритм целиком.
Ведь в комментарии можно «напрограммировать» что угодно и компилятор не будет ругаться. Это позволяет сосредоточиться на идее, а не отвлекаться на написание кода именно тем способом, как этого требует язык программирования и как это понравится компилятору.
Иногда в комментарии сразу писался и готовый код, если сразу приходила такая идея. Часто код писался сразу, без комментариев. Когда что удобнее.
Комментарии получаются довольно подробными, что весьма помогает быстро разобраться с кодом, если видишь его впервые (например, читателю статьи), или вспомнить что ж ты такое делал несколько месяцев/лет назад в своём проекте.
Пример.
Отладка работы с СОМ портом в приложении.
Работу приложения с СОМ портом удобно отлаживать через терминал. Мне нравится Terminal v1.93b.

А создать виртуальное соединение между приложением и терминалом поможет программка com0com. Создаём пару виртуальных портов, соединённых между собой. Подключаем к одному приложение, ко второму терминал и отлаживаем.
Весьма удобно, сразу видно что передаётся и почему не работает.
Макросы в терминале.
Для удобства отправки сложных конструкций можно сделать макросы. Знак $ нужен для записи числа в 16-ричном виде. Потом просто нажимаем М1, М2, М3 (в примере на картинке разблокировать, смахнуть заставку, подобрать пароли, начиная с 2141).

❯ Заставляем строки приниматься целиком, а не кусками
Микроконтроллер отправляет через UART-USB преобразователь данные в виртуальный COM порт строки вида Command received. В коде микроконтроллера это выглядит так:
uint8_t str[] = «Command received\0»;
HAL_UART_Transmit (&huart1, str, 16, 30);
Приложение на PyQt читает строки так:
rx_data = self.serial.readLine()
rx_str = str(rx_data, 'utf-8')# Переводим массив байт в строку
#Помещаем строку в поле для вывода текста
self.plainTextEditLog.appendPlainText(rx_str)
Проблема в том, что строки читаются кусками (частями). Причём части время от времени меняются. Пример вывода в текстовое поле:
C
ommand recei
ved
E
nab
led
C
ommand r
eceived
C
ommand
received
Задав вопрос на QnA, я получил ответ от уважаемого Александра (NeiroNx):
«Они отображаются по мере приема данных и опроса буфера приема (аппаратного).
Само собой опрос буфера приема не синхронизирован с отправкой данных.
Он опрашивается настолько часто что там только всегда куски передачи.
Принимайте в свой буфер, накапливайте и обрабатывайте по приему символа конца строки (»\r\n»)».
Спасибо вам, добрый человек!
Проблема ясна, решение примерно понятно. Так и сделаем.
Объявляем в конструкторе окна строку — свой буфер. Накапливаем её до прихода символа конца строки, потом разделяем строку на этом символе и помещаем в список. Затем, если конечный элемент списка — пустая строка, то выводим все элементы списка кроме последнего в своё текстовое поле.
#Буфер для накапливания строк из ком порта.
self.serialStrbuffer = ""
def onRead(self):
rx_data = self.serial.readAll()#Считываем куски строк из буфера
rx_str = str(rx_data, 'utf-8')# Переводим массив байт в строку
#Соединяем куски в цельные строки в нашем буфере. Разделитель "", т е без разделителя.
self.serialStrbuffer = "".join([self.serialStrbuffer, rx_str])
#print(self.serialStrbuffer)
# Разделяем строки на подстроки на символе конца строки
tempList = self.serialStrbuffer.split("\r\n")
#print(tempList)
#Как только приходит кусок строки, содержащий символ конца строки \r\n,
# в списке становится на одну строку больше. Значит в первом элементе списка целая строка.
#Иногда прилетает сразу 2 целых строки. Поэтому сделаем цикл. Последний элемент списка
# содержит символ конца строки.
length = len(tempList)
if length>1 and tempList[length-1] == '':# Если строка не одна и последняя пустая
#for string in tempList:
#self.plainTextEditLog.appendPlainText(string)
#Выводим все строки из списка, кроме последней.
for i in range(0,length-1,1):
self.plainTextEditLog.appendPlainText(tempList[i])
self.serialStrbuffer = ""#очищаем буфер
# очищаем список, (он и так очистится, т к очистили буфер)
При отладке удобно посмотреть значения переменных в терминале (код писался в VS Code):

❯ Что можно ещё доработать?
Многое можно оптимизировать и сделать по-другому. Основные и самые интересные задачи проекта (сделать обратную разработку тачскрина, создать имитатор касаний и взломать пароль) решены. Второстепенное хотелось сделать проще и быстрее.
Основные доработки:
- Текущая версия прошивки имитатора работает без прерываний, использует приём данных по интерфейсам в блокирующем режиме (на циклах). Каких-то защит и проверок не предусмотрено. Поэтому если делать определённые действия (например, отправлять команды имитатору) не в логическом задуманном по порядке, то программа зависнет.
- Для простоты и скорости написания прошивки использовалась библиотека HAL. При оптимизации можно заменить её на LL или CMSIS.
- Можно продумать и улучшить протокол обмена (добавить контрольную сумму, тщательнее продумать структуру посылок и т. д.). Пример.
- Доработать компьютерное приложение. Интерфейс, логику работы и т. д. При желании можно делать касания в любой точке экрана, а не только цифр пароля.
- Убрать UART преобразователь и сделать подключение микроконтроллера напрямую по USB.
- И др.
❯ Коротко о главном
Если просто настроить STM32 в Кубе, но не принимать ни одного байта по I2C, то он не сформирует ACK.
В смартфоне часто встроено несколько драйверов для разных моделей одного и того же модуля (экрана, тачскрина и т. д.).
На PyQt можно быстро и просто написать приложение для проверки каких-то идей. В Qt Designer получаются симпатичные интерфейсы. Есть 2 варианта использования созданного интерфейса в коде приложения.
Проект развивался постепенно. После обратной разработки тачскрина мы начали проектировать имитатор касаний. Железную часть, затем программную. Сначала удалось смахнуть заставку и передать касания нужных цифр процессору смартфона, а позже взломать пароль автоматическим перебором.
Чтобы мыслить рационально, нужно съесть печеньку, не забывать про перерывы.
Для отладки процессов бывает весьма удобно заснять их на видео.
Часто удобно использовать пару виртуальных COM портов, соединённых друг с другом (например, через программку com0com).
В терминале можно записывать макросы для сложных посылок и отправлять их одной кнопкой.
Идею можно сперва записать-«запрограммировать» комментариями, а потом кодом. В итоге останутся подробные комментарии, весьма помогающие быстро и просто разобраться с кодом впервые или по прошествии длительного времени.
Чтобы строки из COM порта в приложении принимались не кусками, можно накапливать их в своём буфере и разделять по символу конца строки.
❯ Конец.)
Мне этот проект очень понравился, сделал его залпом. Удалось попробовать много интересного, проверить предположения, получить новые навыки и сделать забавные вещи.
При желании можно делать касания не только цифр, но и в любой точке. Кликая мышкой по картинке.
Поставленные цели достигнуты, всё основное работает, а впереди виднеются ПЕЧЕНЬКИ! ещё столько интересных проектов!

Программист наелся и спит, а Кряку с монтажником уже не терпится сделать что-нибудь интересное. Поэтому пойдём дальше.
Понравился проект, статья — поделитесь впечатлением!
Не стесняйтесь писать комментарии, интересно ваше мнение:)
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩

Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.