..предыдущая часть – теория (рекомендуется к прочтению).
Немного растолковав спецификацию USB на свой лад, перейдём к практике.
Для непросвещённых, программирование универсального интерфейса USB сулит много проблем и большинство энтузиастов бросают это дело ещё на взлёте. Инженеры Microsoft посчитали, что здесь не место проповедовать гуманность, и возложили всю рутину на плечи самих программистов. Так-что приготовьтесь к длительной осаде бастиона. Именно поэтому я уделил всю предыдущую часть на теоретическую муть, приняв которую можно будет чувствовать себя в этом направлении, немного уверенней.
В этой части:
1. Системная поддержка
Поскольку прямого доступа к портам устройств в защищённом режиме у нас нет, все запросы придётся отправлять драйверу, посредством специально предназначенной для этих целей функцией DeviceIoControl(). Если с точки зрения подсистемы безопасности мы не замышляем ничего мутного, драйвер выполнит наш запрос, иначе облом. В большинстве случаях, чтению системных структур никто не препятствует, а вот на запись – юзеру могут наложить запрет.
Основным источником различного рода информации является системный реестр, куда диспетчер Plug-and-Play сохраняет свойства всех сконфигурированных девайсов для следующей загрузки ОС. Основная ветка находится по адресу
1.1. Менеджер настройки устройств SetupDi**()
Пара функций SetupDiGetClassDevs() и SetupDiEnumDeviceInfo() позволяют организовать циклический поиск информации в реестре. Первая вернёт хэндл требуемого набора информации, а вторая – заполняет структуру SP_DEVINFO_DATA, которая служит проводником на конкретное устройство в этом наборе. Вот их прототипы:
Здесь, в первый аргумент мы должны положить указатель на идентификатор класса USB, а в последний – флаг, предписывающий искать реально имеющиеся на данный момент устройства (present). Поскольку сейчас нас интересует класс USB, то в первое поле кладём линк на 16-байтную константу GUID_DEVCLASS_USB. Если вы захотите собрать инфу о каких-либо иных классах устройств, то ознакомьтесь с полным списком из 46 возможных вариантов:
Здесь уже интересней.. Индекс во-втором аргументе служит для организации циклического обхода всех устройств в данном наборе. При первом вызове, эта функция вернёт информацию не о Flash-накопителе, а о хост-контролёре, поскольку узел Node начинается именно с него и он тоже принадлежит к классу USB. Значит нужно будет увеличить индекс на 1 и повторно вызвать эту функцию. Тогда получим линк на сл.девайс в цепочке – корневой концентратор, и только на третий раз можем наткнуться на флеш. И то, если повезёт, т.к. контролёров у нас аж 5-штук, и у каждого своё древо устройств. Таким образом, индекс позволяет в цикле обходить всё древо, пока не найдём нужное устройство. Функция возвращает нуль, если в наборе больше нет устройств данного класса.
Отдельного внимания заслуживает и структура SP_DEVINFO_DATA. Она является ключевой для менеджера конфигурации СМ (config manager), а указатель на её содержимое, в виде аргумента ожидает почти весь состав библиотеки setupapi.dll. При каждом вызове с увеличением индекса, SetupDiEnumDeviceInfo() перезаписывает эту структуру новыми данными, где можно найти GUID очередного устройства, и что немаловажно – в члене "DevInst" хэндл девайса, для функций конфигуратора с префиксом CM_**().
По непонятной причине, в описании этой функции Microsoft не делает акцент на данном хэндле, лишь вскользь упоминая о нём. В результате, столкнувшись с диспетчером конфигурации вообще не ясно, от куда брать чёртов хэндл для CM_GET**(). Можно-же было обозвать это поле иначе, например прицепив к нему ярлык "бла-бла-бла Hndl", ан нет.. нужно, чтобы всё было через известное место.
Теперь, обнаружив хаб и порт нашей USB-Flash, мы можем послать по этому хэндлу команду на безопасное извлечение флеш, посредством функции CM_Request_Device_Eject(). Советую красным фломастером отметить структуру SP_DEVINFO_DATA и поле "DevInst" в ней!
1.2. Чтение свойств и характеристик устройств
Будем считать, что двойкой SetupDiGetClassDevs() и SetupDiEnumDeviceInfo() мы получили указатель на свой брелок в реестре. Следующие две функции позволяют вытягивать уже его свойства, в виде текстовых строк и hex-значений – это SetupDiGetDeviceRegistryProperty() и родственная ей SetupDiGetDevicePropertyW(). Учтите, что вторая возвращает строки в кодировке юникод (нет ansi версии), выводить на консоль которые можно при помощи printf() со-спецификатором %ls (long-string).
Самих-же свойств – как звёзд на небе, и я собрал их в отдельный инклуд (см.скрепку в конце статьи).
Для первой функции константы начинаются с префикса SPDRP_**, а для второй – DEVPKEY_Device_**. Вот их прототипы:
Судя по приведённой выше схеме, чтобы найти устройство USB-Flash среди всех остальных, придётся искать его по какому-то признаку, ведь SetupDiEnumDevsInfo() будет возвращать и контролёры, и хабы, и (если есть) иные физические устройства, типа: колонки, веб-камеры и прочее барахло. Значит нужен штам, который безошибочно определит именно Mass-Storage-Device. Хорошей идеей будет использовать в этом качестве имя сервиса обслуживающего данное устройство – в текстовом виде его возвращает SetupDiGetDeviceRegistryProperty() с ключом SPDRP_SERVICE=4. Наши USB-Flash драйвы работают под управлением сервиса "USBSTOR" (драйвер), на который и сделаем ставку. Вот фрагмент кода обнаружения USB-Flash, по указанному алго:
1.3. Порт и имя концентратора Hub
Чтобы посылать устройству USB-Flash пакеты "Setup", нам нужны 2 свойства девайса – имя хаба в системном пространстве имён, и номер его порта, куда подключена флеш. Это основная проблема, с которой сталкиваемся при программировании интерфейса USB. Конечно-же можно запросить эти данные у стороннего софта, но это не наши методы. Если мы хотим написать универсальный софт, то определять концентратор и порт нужно динамически, чтобы код не был привязан к конкретной конфигурации и одному устройству. Иначе, если пользователь воткнёт брелок в другой порт Usb – вся конструкция рухнет, как карточный дом.
Обычно с номером порта проблем не возникает – после того, как мы найдём устройство, порт возвращает SetupDiGetDeviceRegistryProperty() с ключом SPDRP_ADDRESS. А вот имя хаба (под которым он числится в системе) выглядит как субстанция подозрительного характера, типа:
Это-же имя можно обнаружить и в пространстве имён системных объектов, если запустить софт WinObj (см.скрин ниже). Обратите внимание на идентификатор GUID хабов – у всех версий концентраторов USB-1,2,3 он одинаковый и имеет константу
Проблема в том, что в таком виде это имя не возвращает ни один из ключей в запросах характеристик устройств. Стандартными средствами я не нашёл возможности получить и более дружелюбное его имя: \Device\USBPDO-4 (Physical Device Object).
Однако половину этой строки можно заполучить посредством вызова SetupDiGetDeviceProperty() с ключом DEVPKEY_Device_Parent (родитель устройства). Этот вызов возвращает стринг вида:
Более того, в своём выхлопе ключ не возвращает и идентификатор GUID, поэтому его нужно будет добавить в ручную, путём конкатенации (соединении) двух строк через lstrcat(). Другими словами, помещаем в секцию-данных заготовку в требуемом формате, и скопировав в буфер "hubStr" имя хаба, присавокупляем к нему строку GUID'а. Вот пример этого участка кода:
Так мы получим имя хаба, и теперь можно открыть его функцией CreateFile().
Полученный в ответ хэндл позволит через DeviceIoControl() отправлять любые пакеты "Setup" устройствам USB, в том числе и на чтение дескрипторов.
2. DeviceIoControl() – формат пакета "Setup"
Этот гигант способен на многое, но в данном случае мы будем использовать его для отправки устройству запросов на чтение 5-ти стандартных дескрипторов флешки. Управляющий код для функции DeviceIoControl() должен быть IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION с константой
Открыв свободный доступ к дескрипторам для прикладных программ, инженеры ограничили его на уровне драйверов. Так, с этим управляющим кодом мы можем только читать без записи (в поле bmRequestType принудительно выставляется 0x80), и только дескрипторы (bRequest сбрасывается до значения 0x06). Если-же нужен доступ на запись, то можно воспользоваться кодом IOCTL_USB_USER_REQUEST =
На программной уровне, для него предусмотрена одноимённая структура, которую (с учётом ограничения драйверов) я оформил так:
Теперь этот пакет необходимо завернуть в конверт Request, в котором указывается USB-порт назначения, и сразу-же после пакета следует буфер "Data", для приёма запрашиваемых дескрипторов. Функция DeviceIoControl() на входе ожидает именно конверт Request, а не распакованный пакет. Размер приёмного буфа можно ограничить одним кило, хотя встречаются исходники, где программисты выделяют и по 2, и даже 4 Кб. К чему такая расточительность непонятно, т.к. размер одного дескриптора макс. 64-байта. Если мы и запросим буфер сразу для 10-ти дескрипторов, то в сумме получим всего 640 байт, но никак не 4К.
В предыдущей статье с теорией говорилось, что на один запрос дескриптора-конфигурации, устройство возвращает в придачу сразу все остальные дескрипторы, а это: Interface, Endpoint и String. Значит когда мы оформляем пакеты, можно использовать следующие предопределённые данные (красным выделены поля в структуре):
Два первых поля здесь драйвер всё-равно проигнорирует и запишет туда 0x80 (стандартный запрос от устройства к хосту) и 0x06 = чтение дескрипторов. Далее, в аргументах мы подставляем константы требуемых дескрипторов и размеры буферов для приёма данных. Исключением из правил является запрос на чтение строк, когда младший байт аргумента ожидает идентификатор языка 0x0409 = ингиш, и непосредственно индекс запрашиваемой строки. Пример вызова DeviceIoControl() с этапом оформления пакета представлен ниже:
3. Практика – сбор информации об USB-Flash, безопасное извлечение устройств
В приведённом ниже примере я собрал всё сказанное под один капот, и постарался вывести на консоль наиболее значимую информацию о своей флешке. В коде присутствуют ещё парочка функций, которые являются вспомогательными, т.е. можно обойтись без них. Первая GetLogicalDriveStrings() возвращает имена всех томов, которые смонтированы на данный момент в системе. К примеру на своём узле я получил от этой функции буквы: C:, D:, E:, F:
Чтобы узнать, какая из них принадлежит USB-Flash, можно в цикле вскармливая эти литеры, запрашивать GetDriveType() проверяя её выхлоп на константу DRIVE_REMOVABLE=2 (съёмное устройство). Теперь у нас есть буква флеш-драйва, по которой функцией QueryDosDevice() можно узнать имя объекта, в системном пространстве имён типа: Device\HarddiskVolume2.
В таком формате его потребует CreateFile(), чтобы открыть флеш и через GetVolumeInformation() собрать информацию о файловой системе накопителя, его общим/свободном пространстве, метке и серийнике. Вот собственно и исходник:
Здесь видно, что у моего устройства USB-2.0 всего две конечные точки: канал ЕР1 служит для записи(Out), а канал ЕР2 на чтение(In). Если у кого имеется флешка USB-3, просьба показать кол-во её конечных точек – программа должна определить их сама, для чего предусмотрен цикл. Интересует, два или четыре пайпа в устройствах USB-3.
4. Функции из библиотеки WinUsb.dll
В арсенале системы имеется библиотека winUsb.dll, которая ходит со-своим драйвером winUsb.sys. Она была включена в состав ОС для устройств, у которых нет класс-драйвера в системе. К числу таких девайсов можно отнести платы Arduino, сотовые телефоны, собранные "на коленке" разнообразные поделки на PIC-контролёрах, и прочие.
Библиотека выдаёт на экспорт всего 22 функции, но они предоставляют более гибкий и удобный интерфейс общения с устройствами. Либа прекрасно документирована на сайте MSDN, поэтому с прототипами функций проблем не возникает, а полный их список можно посмотреть в любом дизассемблере типа Win32Dasm:
Обратите внимание, что библиотека помимо чтения позволяет производить и запись в пайпы, причём с правами смертного юзера. Проблема в том, что например, на моей машине драйвер winUsb.sys не подгружается автоматически при запуске системы, и не факт, что он будет загружен на удалённом узле. Соответственно, чтобы использовать эти функции, нужно будет сначала подтянуть драйвер в оперативную память, а это не всегда удобно. Вывод – если для устройства в системе имеются класс-драйверы (типа HID, Storage, Audio, коммуникации CDC и прочие), то лучше не рисковать и использовать их, по приведённому выше алго. К winUsb.dll следует обращаться в крайних случаях, когда других вариантов уже нет.
5. Снифф пакетов USB
Интересную информацию можно почерпнуть посредством снифферов USB. В природе, лично мне встречались несколько таких зверьков, но больше всех порадовал "USB-Lyzer". Во-первых, он уже в коробке идёт со-встроенным драйвером usbPcap.sys, так-что установив этот софт появляется возможность ловить USB-пакеты и в завоевавшем рынок Wireshark. Во-вторых, при запуске лузера сразу становится очевидно, что он был написан грамотно, с учётом всех мелочей. 30-дневную пробную версию софтины можно скачать с их сайта по
6. Заключение
Устройства USB поддерживают и интерфейс SCSI, который намного мощнее обычного. Но к сожалению ограниченный объём статьи не позволяет охватить всё сразу, так-что придётся отложить это дело до лучших времён. Если проснифать пакеты, то оказывается, что в своей массе, система поддерживает диалог с шиной USB исключительно по SCSI, периодически посылая ей пакеты CBW (Command Block Wrapper) и принимая CSW (Command Status Wrapper). Реализовать данный обмен можно посредством всё того-же DeviceIoControl() с управляющим кодом IOCTL_SCSI_PASS_THROUGH_DIRECT.
В скрепку кладу исполняемый файл представленного выше кода, и пару инклудов, с описанием необходимых структур. На этом тчк, всем удачи и до скорого.
Немного растолковав спецификацию USB на свой лад, перейдём к практике.
Для непросвещённых, программирование универсального интерфейса USB сулит много проблем и большинство энтузиастов бросают это дело ещё на взлёте. Инженеры Microsoft посчитали, что здесь не место проповедовать гуманность, и возложили всю рутину на плечи самих программистов. Так-что приготовьтесь к длительной осаде бастиона. Именно поэтому я уделил всю предыдущую часть на теоретическую муть, приняв которую можно будет чувствовать себя в этом направлении, немного уверенней.
В этой части:
1. Функции менеджера настройки: SetupDi**(), CM_Get**() ;
2. DeviceIoControl() – формат пакета Setup ;
3. Практика – сбор информации об USB-Flash накопителях, безопасное извлечение устройств ;
4. Назначение и состав библиотеки WinUsb.dll ;
5. Сниффинг пакетов USB ;
6. Заключение.
------------------------------------------1. Системная поддержка
Поскольку прямого доступа к портам устройств в защищённом режиме у нас нет, все запросы придётся отправлять драйверу, посредством специально предназначенной для этих целей функцией DeviceIoControl(). Если с точки зрения подсистемы безопасности мы не замышляем ничего мутного, драйвер выполнит наш запрос, иначе облом. В большинстве случаях, чтению системных структур никто не препятствует, а вот на запись – юзеру могут наложить запрет.
Основным источником различного рода информации является системный реестр, куда диспетчер Plug-and-Play сохраняет свойства всех сконфигурированных девайсов для следующей загрузки ОС. Основная ветка находится по адресу
[HKLM\SYSTEM\CurrentControlSet\Enum\USB]
. Разбирать эту огромную базу вручную нереально, поскольку данные об одном устройстве могут быть размазаны тонким слоем по всему периметру реестра. Поэтому мы воспользуемся услугами системного менеджера конфигурации, функции которого сосредоточены в библиотеке setupapi.dll. Ознакомимся с основными из них..1.1. Менеджер настройки устройств SetupDi**()
Пара функций SetupDiGetClassDevs() и SetupDiEnumDeviceInfo() позволяют организовать циклический поиск информации в реестре. Первая вернёт хэндл требуемого набора информации, а вторая – заполняет структуру SP_DEVINFO_DATA, которая служит проводником на конкретное устройство в этом наборе. Вот их прототипы:
C-подобный:
HDEVINFO SetupDiGetClassDevsA
ClassGuid dd 0 ;// указатель на GUID класса устройств
Enumerator dd 0 ;// 0
hwndParent dd 0 ;// 0
Flags dd 0 ;// DIGCF_PRESENT = искать только активные девайсы
Здесь, в первый аргумент мы должны положить указатель на идентификатор класса USB, а в последний – флаг, предписывающий искать реально имеющиеся на данный момент устройства (present). Поскольку сейчас нас интересует класс USB, то в первое поле кладём линк на 16-байтную константу GUID_DEVCLASS_USB. Если вы захотите собрать инфу о каких-либо иных классах устройств, то ознакомьтесь с полным списком из 46 возможных вариантов:
C-подобный:
;//
;// Следующие идентификаторы классов GUID определяет ОС.
;// Всего: 46 единиц для fn. SetupDiGetClassDevs()
;//
GUID_DEVCLASS_1394 dd 0x6bdd1fc1, 0x11d0810f, 0x0008c7be, 0x2f09e22b
GUID_DEVCLASS_1394DEBUG dd 0x66f250d6, 0x4a647801, 0xa8ee39b1, 0x240b450a
GUID_DEVCLASS_ADAPTER dd 0x4d36e964, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_APMSUPPORT dd 0xd45b1c18, 0x11d1c8fa, 0x0000779f, 0x30f505f8
GUID_DEVCLASS_AVC dd 0xc06ff265, 0x48f0ae09, 0x75162c81, 0x83ba7c3d
GUID_DEVCLASS_BATTERY dd 0x72631e54, 0x11d078a4, 0xaa00f7bc, 0x2ab3b700
GUID_DEVCLASS_BLUETOOTH dd 0xe0cbf06c, 0x4647cd8b, 0x3b268abb, 0x74f9f043
GUID_DEVCLASS_CDROM dd 0x4d36e965, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_COMPUTER dd 0x4d36e966, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_DISKDRIVE dd 0x4d36e967, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_DISPLAY dd 0x4d36e968, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_ENUM1394 dd 0xc459df55, 0x11d1db08, 0xa00009b0, 0xf61f08c9
GUID_DEVCLASS_FDC dd 0x4d36e969, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_GPS dd 0x6bdd1fc3, 0x11d0810f, 0x0008c7be, 0x2f09e22b
GUID_DEVCLASS_HDC dd 0x4d36e96a, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_HIDCLASS dd 0x745a17a0, 0x11d074d3, 0xa000feb6, 0xda570fc9
GUID_DEVCLASS_IMAGE dd 0x6bdd1fc6, 0x11d0810f, 0x0008c7be, 0x2f09e22b
GUID_DEVCLASS_KEYBOARD dd 0x4d36e96b, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_LEGACYDRIVER dd 0x8ecc055d, 0x11d1047f, 0x000037a5, 0xd13e75f8
GUID_DEVCLASS_MEDIA dd 0x4d36e96c, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_MODEM dd 0x4d36e96d, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_MONITOR dd 0x4d36e96e, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_MOUSE dd 0x4d36e96f, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_MTD dd 0x4d36e970, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_MULTIFUNCTION dd 0x4d36e971, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_MULTIPORTSERIAL dd 0x50906cb8, 0x11d1ba12, 0x00005dbf, 0x30f505f8
GUID_DEVCLASS_NET dd 0x4d36e972, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_NETCLIENT dd 0x4d36e973, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_NETSERVICE dd 0x4d36e974, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_NETTRANS dd 0x4d36e975, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_NODRIVER dd 0x4d36e976, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_PCMCIA dd 0x4d36e977, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_PNPPRINTERS dd 0x4658ee7e, 0x11d1f050, 0xc000bdb6, 0xa772a34f
GUID_DEVCLASS_PORTS dd 0x4d36e978, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_PRINTER dd 0x4d36e979, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_PRINTERUPGRADE dd 0x4d36e97a, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_PROCESSOR dd 0x50127dc3, 0x415e0f36, 0xb34ccca6, 0x650b91be
GUID_DEVCLASS_SBP2 dd 0xd48179be, 0x11d1ec20, 0xc000b8b6, 0xa772a34f
GUID_DEVCLASS_SCSIADAPTER dd 0x4d36e97b, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_SMARTCARDREADER dd 0x50dd5230, 0x11d1ba8a, 0x00005dbf, 0x30f505f8
GUID_DEVCLASS_SOUND dd 0x4d36e97c, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_SYSTEM dd 0x4d36e97d, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_TAPEDRIVE dd 0x6d807884, 0x11cf7d21, 0x00081c80, 0x1803e12b
GUID_DEVCLASS_UNKNOWN dd 0x4d36e97e, 0x11cee325, 0x0008c1bf, 0x1803e12b
GUID_DEVCLASS_USB dd 0x36fc9e60, 0x11cfc465, 0x45445680, 0x00005453 ;//<---- Наш клиент
GUID_DEVCLASS_VOLUME dd 0x71a27cdd, 0x11d0812a, 0x0008c7be, 0x2f09e22b
GUID_DEVCLASS_VOLUMESNAPSHOT dd 0x533c5b84, 0x11d2ec70, 0xc0000595, 0xafde794f
GUID_DEVCLASS_WCEUSBS dd 0x25dbce51, 0x4a726c8f, 0x4cb56d8a, 0x35c84f2b
C-подобный:
BOOL SetupDiEnumDeviceInfo
DeviceInfoSet dd 0 ;// хэндл набора (возвращает предыдущая функция)
MemberIndex dd 0 ;// индекс устройства в наборе, начиная с нуля
DeviceInfoData dd 0 ;// указатель на структуру PSP_DEVINFO_DATA
struct SP_DEVINFO_DATA
cbSize dd sizeof.SP_DEVINFO_DATA
ClassGuid db 16 dup(0) ;// сюда получим GUID обнаруженного устройства
DevInst dd 0 ;// handle для fn.CM_GET**
Reserved dd 0 ;// резерв..
ends
Здесь уже интересней.. Индекс во-втором аргументе служит для организации циклического обхода всех устройств в данном наборе. При первом вызове, эта функция вернёт информацию не о Flash-накопителе, а о хост-контролёре, поскольку узел Node начинается именно с него и он тоже принадлежит к классу USB. Значит нужно будет увеличить индекс на 1 и повторно вызвать эту функцию. Тогда получим линк на сл.девайс в цепочке – корневой концентратор, и только на третий раз можем наткнуться на флеш. И то, если повезёт, т.к. контролёров у нас аж 5-штук, и у каждого своё древо устройств. Таким образом, индекс позволяет в цикле обходить всё древо, пока не найдём нужное устройство. Функция возвращает нуль, если в наборе больше нет устройств данного класса.
Отдельного внимания заслуживает и структура SP_DEVINFO_DATA. Она является ключевой для менеджера конфигурации СМ (config manager), а указатель на её содержимое, в виде аргумента ожидает почти весь состав библиотеки setupapi.dll. При каждом вызове с увеличением индекса, SetupDiEnumDeviceInfo() перезаписывает эту структуру новыми данными, где можно найти GUID очередного устройства, и что немаловажно – в члене "DevInst" хэндл девайса, для функций конфигуратора с префиксом CM_**().
По непонятной причине, в описании этой функции Microsoft не делает акцент на данном хэндле, лишь вскользь упоминая о нём. В результате, столкнувшись с диспетчером конфигурации вообще не ясно, от куда брать чёртов хэндл для CM_GET**(). Можно-же было обозвать это поле иначе, например прицепив к нему ярлык "бла-бла-бла Hndl", ан нет.. нужно, чтобы всё было через известное место.
Теперь, обнаружив хаб и порт нашей USB-Flash, мы можем послать по этому хэндлу команду на безопасное извлечение флеш, посредством функции CM_Request_Device_Eject(). Советую красным фломастером отметить структуру SP_DEVINFO_DATA и поле "DevInst" в ней!
1.2. Чтение свойств и характеристик устройств
Будем считать, что двойкой SetupDiGetClassDevs() и SetupDiEnumDeviceInfo() мы получили указатель на свой брелок в реестре. Следующие две функции позволяют вытягивать уже его свойства, в виде текстовых строк и hex-значений – это SetupDiGetDeviceRegistryProperty() и родственная ей SetupDiGetDevicePropertyW(). Учтите, что вторая возвращает строки в кодировке юникод (нет ansi версии), выводить на консоль которые можно при помощи printf() со-спецификатором %ls (long-string).
Самих-же свойств – как звёзд на небе, и я собрал их в отдельный инклуд (см.скрепку в конце статьи).
Для первой функции константы начинаются с префикса SPDRP_**, а для второй – DEVPKEY_Device_**. Вот их прототипы:
C-подобный:
BOOL SetupDiGetDeviceRegistryPropertyA ;//<--- Ansi функция
DeviceInfoSet dd 0 ;// хэндл набора от SetupDiGetClassDevs()
DeviceInfoData dd 0 ;// указатель на структуру PSP_DEVINFO_DATA
Property dd 0 ;// ключ SPDRP_** (требуемое свойство)
PropertyRegDataType dd 0 ;// 0
PropertyBuffer dd 0 ;// адрес приёмного буфера для строки/значения
PropertyBufferSize dd 0 ;// размер этого буфа = 256
RequiredSize dd 0 ;// 0
BOOL SetupDiGetDevicePropertyW ;//<--- Unicode функция
DeviceInfoSet dd 0 ;// хэндл набора от SetupDiGetClassDevs()
DeviceInfoData dd 0 ;// указатель на структуру PSP_DEVINFO_DATA
PropertyKey dd 0 ;// ключ DEVPKEY_Device_**
PropertyType dd 0 ;// указатель на переменную для идентификатора (не важен, но для вызова нужен)
PropertyBuffer dd 0 ;// адрес приёмного буфера для строки
PropertyBufferSize dd 0 ;// размер этого буфа = 256*2
RequiredSize dd 0 ;// 0
Flags dd 0 ;// 0
Судя по приведённой выше схеме, чтобы найти устройство USB-Flash среди всех остальных, придётся искать его по какому-то признаку, ведь SetupDiEnumDevsInfo() будет возвращать и контролёры, и хабы, и (если есть) иные физические устройства, типа: колонки, веб-камеры и прочее барахло. Значит нужен штам, который безошибочно определит именно Mass-Storage-Device. Хорошей идеей будет использовать в этом качестве имя сервиса обслуживающего данное устройство – в текстовом виде его возвращает SetupDiGetDeviceRegistryProperty() с ключом SPDRP_SERVICE=4. Наши USB-Flash драйвы работают под управлением сервиса "USBSTOR" (драйвер), на который и сделаем ставку. Вот фрагмент кода обнаружения USB-Flash, по указанному алго:
C-подобный:
.data
devData SP_DEVINFO_DATA
hndl dd 0
index dd 0
buff rb 1024
;//------------------
.code
start:
;// Получаем хэндл класса устройств USB
invoke SetupDiGetClassDevs,GUID_DEVCLASS_USB,0,0,DIGCF_PRESENT
mov [hndl],eax
;// Поиск в цикле Flash-накопителя – заполняем структуру SP_DEVINFO_DATA
@findNextDevice:
invoke SetupDiEnumDeviceInfo,[hndl],[index],devData
or eax,eax ;// выйти, если больше нет устройств данного класса
jz @exit
;// Запрашиваем имя сервиса, который обслуживает данное устройство
invoke SetupDiGetDeviceRegistryProperty,[hndl], devData, SPDRP_SERVICE,\
0,buff,256,0
call @f
db 'USBSTOR' ;// маска для поиска
@@: pop esi ;// её адрес
mov edi,buff ;// адрес найденного сервиса
mov ecx,7 ;// длина строки/маски
repe cmpsb ;// сравнить строки EDI с ESI!
or ecx,ecx ;// по всей строке совпало?
jnz @next ;// пропустить, если нет
;//------------------------
;// Мы здесь, значит нашли USB-Flash - читаем нужные его свойства!
;//------------------------
invoke SetupDiGetDeviceProperty,,,,
invoke SetupDiGetDeviceRegistryProperty,,,,
jmp @exit
@next: inc [index] ;// если облом, индекс +1 (инкремент)
jmp @findNextDevice ;// продолжить поиск..
@exit: cinvoke exit,0
1.3. Порт и имя концентратора Hub
Чтобы посылать устройству USB-Flash пакеты "Setup", нам нужны 2 свойства девайса – имя хаба в системном пространстве имён, и номер его порта, куда подключена флеш. Это основная проблема, с которой сталкиваемся при программировании интерфейса USB. Конечно-же можно запросить эти данные у стороннего софта, но это не наши методы. Если мы хотим написать универсальный софт, то определять концентратор и порт нужно динамически, чтобы код не был привязан к конкретной конфигурации и одному устройству. Иначе, если пользователь воткнёт брелок в другой порт Usb – вся конструкция рухнет, как карточный дом.
Обычно с номером порта проблем не возникает – после того, как мы найдём устройство, порт возвращает SetupDiGetDeviceRegistryProperty() с ключом SPDRP_ADDRESS. А вот имя хаба (под которым он числится в системе) выглядит как субстанция подозрительного характера, типа:
\\?\USB#ROOT_HUB20#4&17a4e2cc&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}
Это-же имя можно обнаружить и в пространстве имён системных объектов, если запустить софт WinObj (см.скрин ниже). Обратите внимание на идентификатор GUID хабов – у всех версий концентраторов USB-1,2,3 он одинаковый и имеет константу
{f18a0e88-c30c-11d0-8815-00a0c906bed8}
:Проблема в том, что в таком виде это имя не возвращает ни один из ключей в запросах характеристик устройств. Стандартными средствами я не нашёл возможности получить и более дружелюбное его имя: \Device\USBPDO-4 (Physical Device Object).
Однако половину этой строки можно заполучить посредством вызова SetupDiGetDeviceProperty() с ключом DEVPKEY_Device_Parent (родитель устройства). Этот вызов возвращает стринг вида:
USB\ROOT_HUB20\4&17a4e2cc&0
, т.е. вместо хештегов(#) – прямые слэши(\), и соответственно придётся пройтись по этой строке, заменив их на решётки(#).Более того, в своём выхлопе ключ не возвращает и идентификатор GUID, поэтому его нужно будет добавить в ручную, путём конкатенации (соединении) двух строк через lstrcat(). Другими словами, помещаем в секцию-данных заготовку в требуемом формате, и скопировав в буфер "hubStr" имя хаба, присавокупляем к нему строку GUID'а. Вот пример этого участка кода:
C-подобный:
.data
hubName db '\\?\'
hubStr db 64 dup(0)
hubGuid db '#{f18a0e88-c30c-11d0-8815-00a0c906bed8}',0
devData SP_DEVINFO_DATA
hndl dd 0
dpt dd 0
buff rb 1024
;//------------------
.code
start:
;//.....
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_Parent,dpt,buff,256*2,0,0 ;// в буфере имя хаба в юникоде
call Unicode2Ansi ;// переводим имя в ansi
push ecx ;// в ECX процедура вернёт длину имени хаба
mov eax,'\#' ;// AH = что искать, AL = на что менять
push eax ;// аргумент для "ParseBuffString"
call ParseBuffString ;// приводим имя хаба в нужный вид!
pop ecx ;// длина строки с именем хаба
mov esi,buff ;// источник
mov edi,hubStr ;// приёмник
rep movsb ;// скопируем имя из буфера, к себе в заготовку
invoke lstrcat,hubName,hubGuid ;//<--- добавим к заготовке GUID хаба
cinvoke printf,<10,' Kernel name.....: %s',0>,hubName ;// вывод на консоль
;//.....
;//----- Вспомогательные процедуры --------------
proc Unicode2Ansi
xor ecx,ecx ;// очистить ECX под размер строки
mov esi,buff ;// источник
mov edi,esi ;// ..он-же приёмник.
@@: lodsw ;// берём из ESI по 2-байта юникоде
or ax,ax ;// если нуль,
je @f ;// ..выйти из цикла.
stosb ;// записать 1-байт ansi в приёмник
inc ecx ;// размер строки +1
jmp @b ;// пройтись по всей строке..
@@: mov byte[edi],0 ;// вставить в хвост терминальный нуль
ret ;// выход из процедуры – в ECX длина строки ansi
endp
proc ParseBuffString char ;//<---- меняет в строке символы
invoke lstrlen,buff ;// получить длину строки
xchg ecx,eax ;// отправить её в ECX
mov edi,buff ;// источник для scasb (поиск байта)
mov eax,[char] ;// EAX = аргумент (AL = что ищем, AH = на что меняем)
@@: repne scasb ;// поиск AL в строке EDI..
or ecx,ecx ;// конец строки?
je @f ;// да – на выход!
mov byte[edi-1],ah ;// иначе: перезаписать найденный символ
jmp @b ;// на повтор..
@@: ret ;// return
endp
Так мы получим имя хаба, и теперь можно открыть его функцией CreateFile().
Полученный в ответ хэндл позволит через DeviceIoControl() отправлять любые пакеты "Setup" устройствам USB, в том числе и на чтение дескрипторов.
2. DeviceIoControl() – формат пакета "Setup"
Этот гигант способен на многое, но в данном случае мы будем использовать его для отправки устройству запросов на чтение 5-ти стандартных дескрипторов флешки. Управляющий код для функции DeviceIoControl() должен быть IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION с константой
0x00220410
. Это самый простой вариант чтения, и доступен он даже пользователю с правами юзера.Открыв свободный доступ к дескрипторам для прикладных программ, инженеры ограничили его на уровне драйверов. Так, с этим управляющим кодом мы можем только читать без записи (в поле bmRequestType принудительно выставляется 0x80), и только дескрипторы (bRequest сбрасывается до значения 0x06). Если-же нужен доступ на запись, то можно воспользоваться кодом IOCTL_USB_USER_REQUEST =
0x00220438
. Вот как выглядит стандартный пакет чтения дескрипторов:На программной уровне, для него предусмотрена одноимённая структура, которую (с учётом ограничения драйверов) я оформил так:
C-подобный:
struct USB_SETUP_PACKET
bmRequestType db 0x80 ;// тип запроса: стандартный, к хосту
bRequest db 0x06 ;// код запроса: дескрипторы
wValueLow db 0 ;// 0
wValueHigh db 0 ;// Device/Config/Interface/Endpoint/String descr
wIndex dw 0 ;// 0 или индекс строки в базе
wLenght dw 0 ;// число байт для приёма/передачи
ends
Теперь этот пакет необходимо завернуть в конверт Request, в котором указывается USB-порт назначения, и сразу-же после пакета следует буфер "Data", для приёма запрашиваемых дескрипторов. Функция DeviceIoControl() на входе ожидает именно конверт Request, а не распакованный пакет. Размер приёмного буфа можно ограничить одним кило, хотя встречаются исходники, где программисты выделяют и по 2, и даже 4 Кб. К чему такая расточительность непонятно, т.к. размер одного дескриптора макс. 64-байта. Если мы и запросим буфер сразу для 10-ти дескрипторов, то в сумме получим всего 640 байт, но никак не 4К.
C-подобный:
struct USB_DESCRIPTOR_REQUEST
ConnectionIndex dd 0 ;// USB-порт
SetupPacket USB_SETUP_PACKET ;// вложенный в эту структуру пакет
Data db 1024 dup(0) ;// на выходе, содержит извлечённые дескрипторы
ends
В предыдущей статье с теорией говорилось, что на один запрос дескриптора-конфигурации, устройство возвращает в придачу сразу все остальные дескрипторы, а это: Interface, Endpoint и String. Значит когда мы оформляем пакеты, можно использовать следующие предопределённые данные (красным выделены поля в структуре):
Два первых поля здесь драйвер всё-равно проигнорирует и запишет туда 0x80 (стандартный запрос от устройства к хосту) и 0x06 = чтение дескрипторов. Далее, в аргументах мы подставляем константы требуемых дескрипторов и размеры буферов для приёма данных. Исключением из правил является запрос на чтение строк, когда младший байт аргумента ожидает идентификатор языка 0x0409 = ингиш, и непосредственно индекс запрашиваемой строки. Пример вызова DeviceIoControl() с этапом оформления пакета представлен ниже:
C-подобный:
.data
hubName db 128 dup(0) ;// подготовленная строка с именем хаба
usbPort dd 0 ;// здесь должен лежать номер порта нашей Flash
hubHndl dd 0 ;// сюда вернётся хэндл хаба
retSize dd 0 ;// будет размером выхлопа
align 16
request USB_DESCRIPTOR_REQUEST ;// конверт запроса
;//--------------
.code
start:
;//......
;// Открываем хаб, чтобы получить его хэндл
invoke CreateFile,hubName,GENERIC_READ + GENERIC_WRITE,\
FILE_SHARE_READ + FILE_SHARE_WRITE,\
0,OPEN_EXISTING,0,0
mov [hubHndl],eax
;// Оформляем пакет "Setup"
mov eax,[usbPort]
mov [request.ConnectionIndex],eax
mov [request.SetupPacket.wValueHigh], USB_DEVICE_DESCRIPTOR_TYPE ;//<-- тип дескриптора
mov [request.SetupPacket.wValueLow ], 0
mov [request.SetupPacket.wIndex ], 0
mov [request.SetupPacket.wLenght ], sizeof.USB_DEVICE_DESCRIPTOR
;// Обращаемся к драйверу с запросом на чтение дескриптора
invoke DeviceIoControl,[hubHndl],IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,\
request, sizeof.USB_DESCRIPTOR_REQUEST,\
request, sizeof.USB_DESCRIPTOR_REQUEST, retSize, 0
;// Выводим содержимое дескриптора на консоль
mov esi,request.Data ;//<---- указатель на буфер с данными
movzx eax,[esi + USB_DEVICE_DESCRIPTOR.bcdUSB]
movzx ebx,al
shr eax,8
movzx ecx,[esi + USB_DEVICE_DESCRIPTOR.bNumConfigurations]
movzx edx,[esi + USB_DEVICE_DESCRIPTOR.bMaxPacketSize]
cinvoke printf,<10,' Device type.....: USB-%x.%x',\
10,' Total config....: %d',\
10,' EP0 packet size.: %d byte',0>,eax,ebx,ecx,edx
3. Практика – сбор информации об USB-Flash, безопасное извлечение устройств
В приведённом ниже примере я собрал всё сказанное под один капот, и постарался вывести на консоль наиболее значимую информацию о своей флешке. В коде присутствуют ещё парочка функций, которые являются вспомогательными, т.е. можно обойтись без них. Первая GetLogicalDriveStrings() возвращает имена всех томов, которые смонтированы на данный момент в системе. К примеру на своём узле я получил от этой функции буквы: C:, D:, E:, F:
Чтобы узнать, какая из них принадлежит USB-Flash, можно в цикле вскармливая эти литеры, запрашивать GetDriveType() проверяя её выхлоп на константу DRIVE_REMOVABLE=2 (съёмное устройство). Теперь у нас есть буква флеш-драйва, по которой функцией QueryDosDevice() можно узнать имя объекта, в системном пространстве имён типа: Device\HarddiskVolume2.
В таком формате его потребует CreateFile(), чтобы открыть флеш и через GetVolumeInformation() собрать информацию о файловой системе накопителя, его общим/свободном пространстве, метке и серийнике. Вот собственно и исходник:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//----------
section '.inc' data readable
include 'equates\setupapi.inc'
include 'equates\usbstor.inc'
;//----------
.data
ntName db '\\?\GLOBALROOT'
dosName db 32 dup(0)
hubName db '\\?\'
hubStr db 64 dup(0)
hubGuid db '#{f18a0e88-c30c-11d0-8815-00a0c906bed8}',0
hubHndl dd 0
usbPort dd 0
epCount dd 0
volume dd 0
volTotal dd 0,0
volFree dd 0,0
volLabel db 16 dup(0)
volFsys db 08 dup(0)
volSn dd 0
maxLen dd 0
flags dd 0
sysTime SYSTEMTIME
gByte dd 1024*1024*1024
fResult1 dq 0
fResult2 dq 0
hndl dd 0
index dd 0
dpt dd 0
retSize dd 0
align 16
request USB_DESCRIPTOR_REQUEST
align 16
devData SP_DEVINFO_DATA
align 16
buff dd 0
;//----------
.code
start: invoke SetConsoleTitle,<'*** USB-Flash storage info ***',0>
invoke GetLogicalDriveStringsA,64,buff
mov esi,buff
@@: push esi
invoke GetDriveType,esi
pop esi
cmp eax,DRIVE_REMOVABLE ;// съёмный диск?
je @ok ;// да!
add esi,4
cmp byte[esi],0
jne @b
cinvoke printf,<10,' ERROR! USB-Flash not found.',0>
jmp @exit
@ok: push esi
mov eax,[esi]
mov [volume],eax
cinvoke printf,<10,' Volume DOS-name.....: %s',0>,esi
pop esi
mov byte[esi+2],0
invoke QueryDosDevice,esi,dosName,32
cinvoke printf,<10,' Volume kernel name..: %s',10,0>,ntName
;//**** Получаем хэндл набора USB ******************
invoke SetupDiGetClassDevs,GUID_DEVCLASS_USB,0,0,DIGCF_PRESENT
mov [hndl],eax
;//**** Поиск USB-Flash в цикле ********************
@findNextDevice:
invoke SetupDiEnumDeviceInfo,[hndl],[index],devData
or eax,eax
jz @exit
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,SPDRP_SERVICE,\
0,buff,256,0
call @f
db 'USBSTOR'
@@: pop esi
mov edi,buff
mov ecx,7
repe cmpsb
or ecx,ecx
jnz @next
;//**** Нашли! Выводим информацию из реестра **************
cinvoke printf,<10,' Driver info:',0>
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_DriverProvider, dpt,buff,256*4,0,0
invoke CharToOem,buff,buff
cinvoke printf,<10,' Provider........: %ls',0>,buff
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_DriverInfPath, dpt,buff,256*4,0,0
cinvoke printf,<10,' File name.......: %ls',0>,buff
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_DriverVersion, dpt,buff,256*4,0,0
cinvoke printf,<10,' Version.........: %ls',0>,buff
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_DriverDate, dpt,buff,256*4,0,0
invoke FileTimeToSystemTime,buff,sysTime
movzx eax,[sysTime.wDay]
movzx ebx,[sysTime.wMonth]
movzx ecx,[sysTime.wYear]
cinvoke printf,<10,' Create date.....: %02d.%02d.%d',0>,eax,ebx,ecx
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_DriverRank, dpt,buff,256*4,0,0
cinvoke printf,<10,' Driver rank.....: 0x%08x',0>,[buff]
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_SERVICE, 0,buff,256,0
invoke CharToOem,buff,buff
cinvoke printf,<10,' System service..: %s',0>,buff
mov [buff],0
;//**********************************
cinvoke printf,<10,10,' Hub info:',0>
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_LOCATION_PATHS,0,buff,256,0
mov eax,'# '
push eax
call ParseBuffString
cinvoke printf,<10,' Location path...: %s',0>,buff
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_Parent,dpt,buff,256*4,0,0
call Unicode2Ansi
push ecx
mov eax,'\#'
push eax
call ParseBuffString
pop ecx
mov esi,buff
mov edi,hubStr
rep movsb
invoke lstrcat,hubName,hubGuid
cinvoke printf,<10,' Kernel name.....: %s',0>,hubName
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_COMPATIBLEIDS,0,buff,256,0
mov eax,'& '
push eax
call ParseBuffString
cinvoke printf,<10,' Device class....: %s',0>,buff
;//*****************************
cinvoke printf,<10,10,' Device info:',0>
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_LOCATION_INFORMATION,0,buff,256,0
invoke CharToOem,buff,buff
cinvoke printf,<10,' Location path...: %s',0>,buff
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,\
0,buff,256,0
invoke CharToOem,buff,buff
cinvoke printf,<10,' Kernel name.....: %s',0>,buff
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_ADDRESS,0,buff,256,0
mov eax,dword[buff]
mov [usbPort],eax ;//<---------- Запомнить порт устройства!!!
cinvoke printf,<10,' Hub port index..: %d',0>,[buff]
;//*****************************
cinvoke printf,<10,10,' Vendor info:',0>
invoke SetupDiGetDeviceRegistryProperty,[hndl],devData,\
SPDRP_DEVICEDESC,0,buff,256,0
invoke CharToOem,buff,buff
cinvoke printf,<10,' Device type.....: %s',0>,buff
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_BusReportedDeviceDesc, dpt,buff,256*4,0,0
invoke CharToOem,buff,buff
cinvoke printf,<10,' Device name.....: %ls',0>,buff
invoke SetupDiGetDeviceProperty,[hndl],devData,\
DEVPKEY_Device_InstanceId, dpt,buff,256*4,0,0
call Unicode2Ansi
mov edi,buff
mov al,'&'
repne scasb
mov byte[edi-1],' '
mov al,'\'
repne scasb
mov byte[edi-1],0
push edi
cinvoke printf,<10,' Vendor Id.......: %s',0>,buff
pop edi
cinvoke printf,<10,' Serial number...: %s',0>,edi
;//******************************************
cinvoke printf,<10,10,' Volume info:',0>
invoke GetDiskFreeSpaceEx,volume,0,volTotal,volFree
invoke GetVolumeInformation,volume,volLabel,16,volSn,maxLen,flags,volFsys,8
finit
fild qword[volTotal]
fidiv [gByte]
fstp [fResult1]
fild qword[volFree]
fidiv [gByte]
fstp [fResult2]
cinvoke printf,<10,' Capacity........: %.2f Gb',\
10,' Free space......: %.2f Gb',0>,\
dword[fResult1],dword[fResult1+4],\
dword[fResult2],dword[fResult2+4]
mov eax,[volSn]
movzx ebx,ax
shr eax,16
cinvoke printf,<10,' Volume label....: %s',\
10,' Volume serial...: %04X-%04X',\
10,' File system.....: %s',0>,\
volLabel,eax,ebx,volFsys
;//######[ READ DESCRIPTORS ]##########################
cinvoke printf,<10,10,' [xxxxxxxxxxxxxxx STANDART DESCRIPTORS xxxxxxxxxxxxxx]',10,0>
invoke CreateFile,hubName,GENERIC_READ + GENERIC_WRITE,\
FILE_SHARE_READ + FILE_SHARE_WRITE,\
0,OPEN_EXISTING,0,0
mov [hubHndl],eax
;// Запрос стандартных дескрипторов устройства
mov eax,[usbPort]
mov [request.ConnectionIndex],eax
mov [request.SetupPacket.bRequest ], GET_DESCRIPTOR
mov [request.SetupPacket.wValueHigh], USB_DEVICE_DESCRIPTOR_TYPE
mov [request.SetupPacket.wValueLow ], 0
mov [request.SetupPacket.wIndex ], 0
mov [request.SetupPacket.wLenght ], sizeof.USB_DEVICE_DESCRIPTOR
invoke DeviceIoControl,[hubHndl],IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,\
request, sizeof.USB_DESCRIPTOR_REQUEST,\
request, sizeof.USB_DESCRIPTOR_REQUEST, retSize, 0
mov esi,request.Data
movzx eax,[esi + USB_DEVICE_DESCRIPTOR.bcdUSB]
movzx ebx,al
shr eax,8
movzx ecx,[esi + USB_DEVICE_DESCRIPTOR.bNumConfigurations]
movzx edx,[esi + USB_DEVICE_DESCRIPTOR.bMaxPacketSize]
cinvoke printf,<10,' Device type.....: USB-%x.%x',\
10,' Total config....: %d',\
10,' EP0 packet size.: %d byte',0>,eax,ebx,ecx,edx
;// Запрос дескриптора "конфигурации" и сразу всех остальных
mov [request.SetupPacket.wValueHigh], USB_CONFIG_DESCRIPTOR_TYPE
mov [request.SetupPacket.wValueLow ], 0 ;// Номер конфигурации
mov [request.SetupPacket.wIndex ], 0
mov [request.SetupPacket.wLenght ], sizeof.USB_CONFIGURATION_DESCRIPTOR * 16
invoke DeviceIoControl,[hubHndl],IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,\
request, sizeof.USB_DESCRIPTOR_REQUEST,\
request, sizeof.USB_DESCRIPTOR_REQUEST, retSize, 0
mov esi,request.Data
movzx eax,[esi + USB_CONFIGURATION_DESCRIPTOR.MaxPower]
shl eax,1
cinvoke printf,<10,' Max power.......: %d mA',0>,eax
;// Читаем сразу дескриптор "интерфейса"
mov esi,request.Data
add esi,sizeof.USB_CONFIGURATION_DESCRIPTOR
push esi
movzx eax,[esi + USB_INTERFACE_DESCRIPTOR.bNumEndpoints]
mov [epCount],eax
dec [epCount] ;//<------- Всего конечных точек для цикла!!!
mov ebx,eax
inc ebx
cinvoke printf,<10,' Total Endpoints.: %d + EP0 = %d',0>,eax,ebx
;// Читаем сразу дескрипторы "конечных точек EP"
jmp @f
bulk db 'Bulk',0
iso db 'Isochronius',0
input db 'In',0
output db 'Out',0
@@: pop esi
add esi,sizeof.USB_INTERFACE_DESCRIPTOR
push esi
movzx eax,[esi + USB_ENDPOINT_DESCRIPTOR.bEndpointAddress]
movzx ebx,[esi + USB_ENDPOINT_DESCRIPTOR.wMaxPacketSize]
movzx ecx,[esi + USB_ENDPOINT_DESCRIPTOR.bmAttributes]
movzx edi,[esi + USB_ENDPOINT_DESCRIPTOR.bInterval]
imul edi,125
mov edx,input
test al,10000000b ;//<---- проверить In или Out
jnz @f
mov edx,output
@@: and al,11b ;//<---- оставить только номер
mov ebp,bulk
cmp ecx,2 ;//<---- Bulk или изохронная
je @f
mov ebp,iso
@@: cinvoke printf,<10,' Endpoint info...: EP%d, 0.%d ms, %d byte, %s, %s',0>,eax,edi,ebx,ebp,edx
pop esi ;//<---- Цикл вывода EP1..EP16 по счётчику
@printEP_info:
add esi,sizeof.USB_ENDPOINT_DESCRIPTOR
push esi
movzx eax,[esi + USB_ENDPOINT_DESCRIPTOR.bEndpointAddress]
movzx ebx,[esi + USB_ENDPOINT_DESCRIPTOR.wMaxPacketSize]
movzx ecx,[esi + USB_ENDPOINT_DESCRIPTOR.bmAttributes]
movzx edi,[esi + USB_ENDPOINT_DESCRIPTOR.bInterval]
imul edi,125
mov edx,input
test al,10000000b
jnz @f
mov edx,output
@@: and al,11b
mov ebp,bulk
cmp ecx,2
je @f
mov ebp,iso
@@: cinvoke printf,<10,' Endpoint info...: EP%d, 0.%d ms, %d byte, %s, %s',0>,eax,edi,ebx,ebp,edx
pop esi
dec [epCount] ;//<--- есть ещё точки?
jnz @printEP_info ;//<--- да, на повтор..
;// Запрос дескриптора "строк"
mov [request.SetupPacket.wValueHigh], USB_STRING_DESCRIPTOR_TYPE
mov [request.SetupPacket.wValueLow ], 2 ;// Индекс строки
mov [request.SetupPacket.wIndex ], 0x0409 ;// ID языка
mov [request.SetupPacket.wLenght ], 128
invoke DeviceIoControl,[hubHndl],IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,\
request, sizeof.USB_DESCRIPTOR_REQUEST,\
request, sizeof.USB_DESCRIPTOR_REQUEST, retSize, 0
mov esi,request.Data
lea esi,[esi + USB_STRING_DESCRIPTOR.bString]
cinvoke printf,<10,10,' String..........: %ls',10,0>,esi
;//********* Безопасное извлечение устройства *************
cinvoke printf,<10,' Safe eject Usb-Flash: y/n... ',0>
cinvoke getch
cmp al,'y'
jne @f
cinvoke printf,<'y',0>
mov eax,[devData.DevInst]
invoke CM_Request_Device_Eject,eax,0,0,0,0
@@: cinvoke printf,<10,' Press any key for exit...',10,0>
;//**** Индексированный поиск устройств, пока не найдём.
@next: inc [index]
jmp @findNextDevice
@exit: cinvoke getch ;//<---- GAME OVER!!!
cinvoke exit,0
;//###########<--- ПРОЦЕДУРЫ --->#######################
proc Unicode2Ansi
xor ecx,ecx
mov esi,buff
mov edi,esi
@@: lodsw
or ax,ax
je @f
stosb
inc ecx
jmp @b
@@: mov byte[edi],0
ret
endp
proc ParseBuffString char
invoke lstrlen,buff
xchg ecx,eax
mov edi,buff
mov eax,[char]
@@: repne scasb
or ecx,ecx
je @f
mov byte[edi-1],ah
jmp @b
@@: ret
endp
;//---------------
section '.idata' import data readable
library msvcrt,'msvcrt.dll',setupapi,'setupapi.dll',\
kernel32,'kernel32.dll',user32,'user32.dll'
import msvcrt, printf,'printf',fflush,'fflush',getch,'_getch',exit,'exit'
import setupapi,SetupDiGetClassDevs,'SetupDiGetClassDevsA',\
SetupDiEnumDeviceInfo,'SetupDiEnumDeviceInfo',\
SetupDiGetDeviceProperty,'SetupDiGetDevicePropertyW',\
SetupDiGetDeviceRegistryProperty,'SetupDiGetDeviceRegistryPropertyA',\
CM_Request_Device_Eject,'CM_Request_Device_EjectA'
include 'api\kernel32.inc'
include 'api\user32.inc'
Здесь видно, что у моего устройства USB-2.0 всего две конечные точки: канал ЕР1 служит для записи(Out), а канал ЕР2 на чтение(In). Если у кого имеется флешка USB-3, просьба показать кол-во её конечных точек – программа должна определить их сама, для чего предусмотрен цикл. Интересует, два или четыре пайпа в устройствах USB-3.
4. Функции из библиотеки WinUsb.dll
В арсенале системы имеется библиотека winUsb.dll, которая ходит со-своим драйвером winUsb.sys. Она была включена в состав ОС для устройств, у которых нет класс-драйвера в системе. К числу таких девайсов можно отнести платы Arduino, сотовые телефоны, собранные "на коленке" разнообразные поделки на PIC-контролёрах, и прочие.
Библиотека выдаёт на экспорт всего 22 функции, но они предоставляют более гибкий и удобный интерфейс общения с устройствами. Либа прекрасно документирована на сайте MSDN, поэтому с прототипами функций проблем не возникает, а полный их список можно посмотреть в любом дизассемблере типа Win32Dasm:
Код:
+++++++++++++++++++ EXPORTED FUNCTIONS ++++++++++++++++++
Number of Functions = 0022 (decimal)
Addr:10002530 Ord: 1 (0001h) Name: WinUsb_AbortPipe
Addr:1000231C Ord: 2 (0002h) Name: WinUsb_ControlTransfer
Addr:100025F4 Ord: 3 (0003h) Name: WinUsb_FlushPipe
Addr:100016C0 Ord: 4 (0004h) Name: WinUsb_Free
Addr:1000172E Ord: 5 (0005h) Name: WinUsb_GetAssociatedInterface
Addr:10001B27 Ord: 6 (0006h) Name: WinUsb_GetCurrentAlternateSetting
Addr:10001984 Ord: 7 (0007h) Name: WinUsb_GetDescriptor
Addr:100026B8 Ord: 8 (0008h) Name: WinUsb_GetOverlappedResult
Addr:10001DCF Ord: 9 (0009h) Name: WinUsb_GetPipePolicy
Addr:10001FE4 Ord: 10 (000Ah) Name: WinUsb_GetPowerPolicy
Addr:100012A8 Ord: 11 (000Bh) Name: WinUsb_Initialize
Addr:1000120D Ord: 12 (000Ch) Name: WinUsb_ParseConfigurationDescriptor
Addr:100011C5 Ord: 13 (000Dh) Name: WinUsb_ParseDescriptors
Addr:100018C0 Ord: 14 (000Eh) Name: WinUsb_QueryDeviceInformation
Addr:1000185B Ord: 15 (000Fh) Name: WinUsb_QueryInterfaceSettings
Addr:10001BE4 Ord: 16 (0010h) Name: WinUsb_QueryPipe
Addr:100020BA Ord: 17 (0011h) Name: WinUsb_ReadPipe
Addr:1000246C Ord: 18 (0012h) Name: WinUsb_ResetPipe
Addr:10001A63 Ord: 19 (0013h) Name: WinUsb_SetCurrentAlternateSetting
Addr:10001C98 Ord: 20 (0014h) Name: WinUsb_SetPipePolicy
Addr:10001EAF Ord: 21 (0015h) Name: WinUsb_SetPowerPolicy
Addr:100021EB Ord: 22 (0016h) Name: WinUsb_WritePipe
Обратите внимание, что библиотека помимо чтения позволяет производить и запись в пайпы, причём с правами смертного юзера. Проблема в том, что например, на моей машине драйвер winUsb.sys не подгружается автоматически при запуске системы, и не факт, что он будет загружен на удалённом узле. Соответственно, чтобы использовать эти функции, нужно будет сначала подтянуть драйвер в оперативную память, а это не всегда удобно. Вывод – если для устройства в системе имеются класс-драйверы (типа HID, Storage, Audio, коммуникации CDC и прочие), то лучше не рисковать и использовать их, по приведённому выше алго. К winUsb.dll следует обращаться в крайних случаях, когда других вариантов уже нет.
5. Снифф пакетов USB
Интересную информацию можно почерпнуть посредством снифферов USB. В природе, лично мне встречались несколько таких зверьков, но больше всех порадовал "USB-Lyzer". Во-первых, он уже в коробке идёт со-встроенным драйвером usbPcap.sys, так-что установив этот софт появляется возможность ловить USB-пакеты и в завоевавшем рынок Wireshark. Во-вторых, при запуске лузера сразу становится очевидно, что он был написан грамотно, с учётом всех мелочей. 30-дневную пробную версию софтины можно скачать с их сайта по
Ссылка скрыта от гостей
– настоятельно рекомендую:6. Заключение
Устройства USB поддерживают и интерфейс SCSI, который намного мощнее обычного. Но к сожалению ограниченный объём статьи не позволяет охватить всё сразу, так-что придётся отложить это дело до лучших времён. Если проснифать пакеты, то оказывается, что в своей массе, система поддерживает диалог с шиной USB исключительно по SCSI, периодически посылая ей пакеты CBW (Command Block Wrapper) и принимая CSW (Command Status Wrapper). Реализовать данный обмен можно посредством всё того-же DeviceIoControl() с управляющим кодом IOCTL_SCSI_PASS_THROUGH_DIRECT.
В скрепку кладу исполняемый файл представленного выше кода, и пару инклудов, с описанием необходимых структур. На этом тчк, всем удачи и до скорого.