Есть такой вектор атак, как BadUSB, — его суть заключается в эмуляции работы клавиатуры и выполнении операций на компьютере под видом обычного ввода от пользователя. Клавиатура обычно не вызывает подозрений у ОС и антивируса, поэтому такие атаки сложно отследить. Сегодня мы посмотрим, как создать свой девайс этого класса — в корпусе флешки и с беспроводной связью.
Как ты понимаешь, за годы существования проблемы способов реализации придумано уже достаточно много. Это может быть как классический, хорошо всем известный Rubber Ducky, так и весьма экзотический вариант с перепрошивкой флешки с подходящим контроллером. Также народ придумал некоторое количество реализаций на Arduino и совместимом Digispark.
Кроме того, однозначно стоит упомянуть и о Pill Duck, так как своей концепцией именно этот проект наиболее близок к тому, что я покажу в статье. У Pill Duck есть хорошее и подробное описание, так что всячески рекомендую тебе ознакомиться с ним, если ты настроен в деталях разобраться в проблеме.
Сразу скажу, что я не ставил перед собой цель превзойти упомянутые устройства. Скорее это мой личный эксперимент на тему дистанционного пульта управления для компьютера, так что оценивать его стоит в первую очередь именно с такой точки зрения.
USB HID
USB (Universal Serial Bus), как ясно из названия, представляет собой универсальную последовательную шину, которая де-факто является стандартом в настоящее время (вернее, даже целым семейством стандартов). Она практически полностью заменила собой RS-232, LPT, PS/2 и используется преимущественно для связи ПК с периферийными устройствами.
INFO
Следует заметить, что рабочие места для наиболее ответственных задач до сих пор оснащаются средствами ввода с интерфейсами PS/2. Это как раз связано с проблемой обеспечения безопасности подобных систем. Так что отправляться на штурм какой-нибудь условной АЭС со своей Rubber Ducky на USB — занятие не только глупое, но и заранее обреченное на провал.
Однако из основных достоинств протокола USB вытекают и его недостатки. В первую очередь это сложная процедура обмена информацией между девайсами, особенно в начальный момент. Причина проблемы заключается в использованной концепции Plug’n’play, которая подразумевает, что периферия при подключении сразу же инициализируется. Ведомое устройство передает хосту информацию о себе, что позволяет системе подгрузить нужный драйвер и приступить к работе.
С точки зрения конечного пользователя, безусловно, это очень круто, однако как раз из-за универсальности спецификации USB составляют несколько многостраничных томов. К счастью, наша задача — эмуляция клавиатуры и мыши — достаточно простая и распространенная, что несколько облегчает жизнь.
Итак, интересующие нас устройства относятся к классу HID (Human Interface Device), и если мы сообщим хосту, что его новая периферия — это стандартная клавиатура, то установка специальных драйверов не потребуется и будут использованы стандартные. В интернете есть неплохие статьи о кастомном HID-устройстве, но это не совсем наш случай.
Тебе нужно запомнить следующее: обмен данными в протоколе USB всегда инициируется хостом и происходит пакетами. Их размер описан в дескрипторах девайса, которые хост обязательно запрашивает во время инициализации.
Прошивка МК
Самый простой на сегодня способ собрать собственное устройство с USB — взять подходящий микроконтроллер и написать для него нужную прошивку. Теоретически нам подойдет едва ли не любой МК, ведь USB тоже можно эмулировать средствами GPIO и нужными библиотеками (эмулировать USB для эмуляции HID и «пользовательского ввода» — в этом определенно есть что-то безумно заманчивое). Однако разумнее, конечно же, выбрать микроконтроллер с необходимой нам периферией.
Наиболее известная в мире плата Arduino с такой функциональностью — Leonardo на ATmega32u4. Этот МК уже содержит в своем составе аппаратный блок USB, а Arduino IDE предлагает на выбор несколько скетчей и библиотек (для мыши и клавиатуры). Также подойдет и более мощная версия на ARM — Arduino Due. Но лично мне ближе микроконтроллеры STM32, тем более что некоторый опыт работы с ними уже имеется. Поэтому в основу проекта лег STM32F103C8T6. Очень удобно, что эта микросхема доступна в составе отладочной платы Blue Pill, которая облегчает прототипирование устройства.
Дескрипторы
Для старта возьмем за основу один из примеров libopencm3, в котором эмулируется движение мыши. Наибольший интерес для нас представляет именно дескриптор, вот как он выглядит:
const struct usb_device_descriptor dev_descr = { // Дескриптор устройства .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, .bcdUSB = 0x0200, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = 0x0483, // VID .idProduct = 0x5710, // PID .bcdDevice = 0x0200, .iManufacturer = 1, // Номера строк в usb_strings[], .iProduct = 2, // начиная с первой (!), а не .iSerialNumber = 3, // с нулевой, как можно было бы ожидать .bNumConfigurations = 1, };
static const uint8_t hid_report_descriptor[] = { 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ 0x09, 0x02, /* USAGE (Mouse) */ 0xa1, 0x01, /* COLLECTION (Application) */ 0x09, 0x01, /* USAGE (Pointer) */ 0xa1, 0x00, /* COLLECTION (Physical) */ 0x05, 0x09, /* USAGE_PAGE (Button) */ 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */ 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ 0x95, 0x03, /* REPORT_COUNT (3) */ 0x75, 0x01, /* REPORT_SIZE (1) */ 0x81, 0x02, /* INPUT (Data,Var,Abs) */ 0x95, 0x01, /* REPORT_COUNT (1) */ 0x75, 0x05, /* REPORT_SIZE (5) */ 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ 0x09, 0x30, /* USAGE (X) */ 0x09, 0x31, /* USAGE (Y) */ 0x09, 0x38, /* USAGE (Wheel) */ 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ 0x75, 0x08, /* REPORT_SIZE (8) */ 0x95, 0x03, /* REPORT_COUNT (3) */ 0x81, 0x06, /* INPUT (Data,Var,Rel) */ 0xc0, /* END_COLLECTION */ 0x09, 0x3c, /* USAGE (Motion Wakeup) */ 0x05, 0xff, /* USAGE_PAGE (Vendor Defined Page 1) */ 0x09, 0x01, /* USAGE (Vendor Usage 1) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ 0x75, 0x01, /* REPORT_SIZE (1) */ 0x95, 0x02, /* REPORT_COUNT (2) */ 0xb1, 0x22, /* FEATURE (Data,Var,Abs,NPrf) */ 0x75, 0x06, /* REPORT_SIZE (6) */ 0x95, 0x01, /* REPORT_COUNT (1) */ 0xb1, 0x01, /* FEATURE (Cnst,Ary,Abs) */ 0xc0 /* END_COLLECTION */ };
static const struct { struct usb_hid_descriptor hid_descriptor; struct { uint8_t bReportDescriptorType; uint16_t wDescriptorLength; } __attribute__((packed)) hid_report; } __attribute__((packed)) hid_function = { .hid_descriptor = { .bLength = sizeof(hid_function), .bDescriptorType = USB_DT_HID, .bcdHID = 0x0100, .bCountryCode = 0, .bNumDescriptors = 1, }, .hid_report = { .bReportDescriptorType = USB_DT_REPORT, .wDescriptorLength = sizeof(hid_report_descriptor), } };
Добрая половина этих параметров стандартна для многих совместимых устройств, так что можешь даже не забивать ими голову. Нас же здесь больше всего интересуют параметры PID (Product ID) и VID (Vendor ID). Изменив их, можно притвориться практически любым устройством любого производителя (правда, есть сомнения в правовом статусе такого притворства, так что подумай дважды).
const struct usb_endpoint_descriptor hid_endpoint = { // Дескриптор конечной точки .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, // Адрес конечной точки IN .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, .wMaxPacketSize = 4, // Максимальная длина пакета .bInterval = 0x02, // Р?нтервал РѕРїСЂРѕСЃР° РІ миллисекундах };
const struct usb_interface_descriptor hid_iface = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_HID, .bInterfaceSubClass = 1, /* boot */ .bInterfaceProtocol = 2, /* mouse */ .iInterface = 0, .endpoint = &hid_endpoint, .extra = &hid_function, .extralen = sizeof(hid_function), };
В дескрипторе конечной точки нас интересуют ее адрес .bEndpointAddress = 0x81 , максимальная длина пакета .wMaxPacketSize = 4 и интервал опроса .bInterval = 0x02, . Адрес конечной точки для нашей цели РЅРµ имеет принципиального значения, его РјРѕР¶РЅРѕ РЅРµ трогать. Что Р¶Рµ касается максимального размера пакета, то РѕРЅ обязательно должен соответствовать структуре отчета, описанной РІ hid_report_descriptor[] . В данном случае это четыре байта.
const struct usb_interface ifaces[] = {{ .num_altsetting = 1, .altsetting = &hid_iface, } };
const struct usb_config_descriptor config = { .bLength = USB_DT_CONFIGURATION_SIZE, .bDescriptorType = USB_DT_CONFIGURATION, .wTotalLength = 0, .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0xC0, .bMaxPower = 0x32, .interface = ifaces, };
static const char *usb_strings[] = { // Строки, отображаемые в описании устройства "Black Sphere Technologies", "HID Demo", "DEMO", };
Завершают определения строки usb_strings[] , которые ты тоже можешь прописать РїРѕ своему РІРєСѓСЃСѓ (Рё чувству СЋРјРѕСЂР°).
Рассмотрим теперь подробнее дескриптор отчета. Ответ стандартной мыши РЅР° запрос РѕС‚ хоста состоит РёР· четырех байт. Первый передает состояние РєРЅРѕРїРѕРє (младшие три бита — правая, левая Рё средняя РєРЅРѕРїРєРё, старшие пять Р±РёС‚ РЅРµ задействованы). Рђ оставшиеся три байта отвечают Р·Р° перемещение РїРѕ РѕСЃСЏРј X, Y Рё вращение колесика. Рти байты представляют СЃРѕР±РѕР№ целое число СЃРѕ знаком (диапазон РѕС‚ –127 РґРѕ 127). Его значения РїСЂРё этом соответствуют единичному относительному перемещению указателя.
Хорошо, с мышью немного разобрались, а что насчет клавиатуры? На самом деле почти все аналогично. Однако теперь отчет длиннее и состоит из восьми байт. Биты первого байта отвечают за клавиши-модификаторы: RIGHT_GUI , RIGHT_ALT , RIGHT_SHIFT , RIGHT_CTRL , LEFT_GUI , LEFT_ALT , LEFT_SHIFT , LEFT_CTRL . Следующий байт зарезервирован для совместимости, в принципе его можно выкинуть. Дальше идут шесть байт, каждый из которых отвечает одной нажатой клавише: такой мультитач на шесть касаний, не считая модификаторов. Дескриптор клавиатуры выглядит следующим образом:
... 0x05, 0x01, 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) 0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x19, 0xE0, // Usage Minimum (0xE0) 0x29, 0xE7, // Usage Maximum (0xE7) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null) 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null) 0x19, 0x00, // Usage Minimum (0x00) 0x29, 0x65, // Usage Maximum (0x65) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x65, // Logical Maximum (101) 0x75, 0x08, // Report Size (8) 0x95, 0x06, // Report Count (6) 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null) 0xC0, // End Collection …
Для упрощения работы с дескрипторами USB есть хороший сайт, который позволяет анализировать и редактировать дескрипторы. Кроме того, существует официально рекомендуемое приложение USB HID Descriptor tool. Оно доступно только в версии для Windows, но и в Wine тоже заведется.
Составное устройство
С устройствами ввода и их дескрипторами мы разобрались. Теперь возникает следующий вопрос: можно ли объединить в одном устройстве и клавиатуру, и мышь? Тут нам на помощь приходит мануал по созданию составных устройств. Достаточно в дескрипторы отчетов для мыши и клавиатуры добавить поле report id , Рё РёС… РјРѕР¶РЅРѕ будет объединить. Теперь ответы нашей периферии станут длиннее РЅР° РѕРґРёРЅ байт, РЅРѕ С…РѕСЃС‚, читая его значение, будет знать, РѕС‚ какого устройства отчет.
В итоге наш финальный HID-дескриптор выглядит так:
... 0x05, 0x01, 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) 0x85, 0x01, // Report ID 0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x19, 0xE0, // Usage Minimum (0xE0) 0x29, 0xE7, // Usage Maximum (0xE7) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null) 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null) 0x19, 0x00, // Usage Minimum (0x00) 0x29, 0x65, // Usage Maximum (0x65) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x65, // Logical Maximum (101) 0x75, 0x08, // Report Size (8) 0x95, 0x06, // Report Count (6) 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null) 0xC0, // End Collection 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x85, 0x02, // Report ID 0x05, 0x09, // Usage Page (Buttons) 0x19, 0x01, // Usage Minimum (01) 0x29, 0x03, // Usage Maximum (03) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (0) 0x95, 0x03, // Report Count (3) 0x75, 0x01, // Report Size (1) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x05, // Report Size (5) 0x81, 0x01, // Input (Constant) ;5 bit padding 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) 0x15, 0x81, // Logical Minimum (-127) 0x25, 0x7F, // Logical Maximum (127) 0x75, 0x08, // Report Size (8) 0x95, 0x02, // Report Count (2) 0x81, 0x06, // Input (Data, Variable, Relative) 0xC0, 0xC0, // End Collection,End Collection …
Главное — не забыть поправить максимальную длину отчета устройства, она теперь равна девяти. Сами отчеты окажутся следующими:
Клавиатура 1 REPORT ID = 1 2 MOD_KEYS 3 RESERVED 4 KEY1 5 KEY2 6 KEY3 7 KEY4 8 KEY5 9 KEY6
Мышь 1 REPORT ID = 2 2 KEYS 3 X 4 Y
Осталось только инициализировать интерфейс. Тут в примере можно ничего не менять, на старте драйвер вызывает функцию hid_set_config , регистрирующую конечную точку 0x81, которую РІ дальнейшем будет опрашивать наш С…РѕСЃС‚. Р’ ответ РѕРЅ получит указанные выше отчеты. Что Р¶Рµ касается функции hid_control_request , то она служит просто заглушкой и в данном случае ни на что не влияет.
Эмулируем клавиатуру
Теперь разберемся с имитацией нажатия клавиши. Для примера возьмем клавишу a СЃ РєРѕРґРѕРј 0x04. Важно обратить внимание, что РєРѕРґС‹ клавиш, выдаваемые клавиатурой, — это РІРѕРІСЃРµ РЅРµ ASCII, Рё Рѕ раскладке клавиатура тоже ничего РЅРµ знает, это РІСЃРµ РїСЂРѕРёСЃС…РѕРґРёС‚ уровнем выше. Так как Р¶Рµ выглядит нажатие клавиши Р° ? Это два последовательных отчета — первый о нажатии клавиши, а второй о ее отпускании (если забыть про то, что клавишу надо отпустить, выйдет конфуз).
uint8_t pres_a[] = {1, 0, 0, 0x04, 0, 0, 0, 0, 0}; uint8_t rel_a[] = {1, 0, 0, 0, 0, 0, 0, 0, 0};
usbd_ep_write_packet(usbd_dev, 0x81, pres_a, 9); usbd_ep_write_packet(usbd_dev, 0x81, rel_a, 9);
Перейти обратно к новости
|