Безопасное извлечение USB-устройств

Дата публикации 21 фев 2008

Безопасное извлечение USB-устройств — Архив WASM.RU

Цель данной статьи - описание метода безопасного извлечения USB-устройств. В данном случаи под USB-устройствами будут, подразумевается в основном устройства хранения информации (Flash, HDD), но все описанные ниже действия применимы и устройствам других классов. Существует мнение, что данная функция не нужна при обращении к USB-устройствам. Обычно нет ничего страшного в том, что устройство было извлечено небезопасно. В крайнем случае, может потеряться информация, которая еще не была записана на USB (например, если у вас включено кэширование записи на диск). Но все же осторожность не повредит. Следующий вопрос, который может возникнуть, это зачем писать то, что уже реализовано в Windows. Например, быстро вызвать окно безопасного отключения устройств, можно выполнив команду:

rundll32.exe shell32.dll,Control_RunDLL hotplug.dll

Здесь можно заметить, что реализация Microsoft не всем нравится. Тем более что можно развить мысль, например, вести поиск процессов, из-за которых невозможно безопасное извлечение. Т.е. процессов, которые имеют незакрытые дескрипторы файлов или папок, расположенных на съемном носителе.

Итак, начнем с того, что построим список всех устройств компьютера, с указанием, какие из них поддерживают безопасное извлечение. Все устройства в компьютере связаны “родственными узами”, т.е. могут быть представлены узлами некого дерева. Это выглядит примерно так:

Код (Text):
  1.  
  2. HTREE\ROOT\0
  3.   ROOT\ACPI_HAL\0000 'Однопроцессорный компьютер с ACPI'
  4.     ACPI_HAL\PNP0C08\0 'Microsoft ACPI-совместимая система'
  5.       ACPI\GENUINEINTEL_-_X86_FAMILY_15_MODEL_2\_0 'Intel(R) Celeron(R) CPU 2.40GHz'
  6.       ACPI\PNP0A03\0 'Шина PCI'
  7.         PCI\VEN_8086&DEV_2560&SUBSYS_0000&REV_03\3&267A616A&0&00 'Intel(R) 82845G/GL/GE/PE/GV Processor to I/O Controller - 2560 '
  8.         PCI\VEN_8086&DEV_2561&SUBSYS_0000&REV_03\3&267A616A&0&08 'Intel(R) 82845G/GL/GE/PE/GV Processor to AGP Controller - 2561 '
  9.           PCI\VEN_10DE&DEV_0181&SUBSYS_31021458&REV_C1\4&BCE2C57&0&0008 'NVIDIA GeForce4 MX 440 with AGP8X'
  10.             DISPLAY\SAM01F9\5&EB43F14&1&22446688&01&00 'Модуль подключения монитора' [REMOVEABLE]
  11.             PCI\VEN_8086&DEV_24C2&SUBSYS_53568086&REV_02\3&267A616A&0&E8 'Intel(R) 82801DB/DBM USB Universal Host Controller - 24C2 '
  12.             USB\ROOT_HUB20\4&3B39A3A1&0 'Корневой USB концентратор'
  13.             USB\VID_04CF&PID_8818\100 'Запоминающее устройство для USB' [REMOVEABLE]
  14.               USBSTOR\DISK&VEN_SAMSUNG&PROD_MP0402H&REV_UC10\100&0 'SAMSUNG MP0402H USB Device'

Существуют несколько способов обхода графа (двоичного дерева) – рекурсивный и нерекурсивный методы [1],[2]. Для простоты применим рекурсивный метод.

Необходимые нам функции относятся к группе “PnP Configuration Manager Functions”, имеют префикс “CM_” и экспортируются библиотекой setupapi.dll.

Для использования этой библиотеки в MASM32 необходимо подключить setupapi.inc. Описание можно получить в MSDN, но на всякий случай я приведу здесь краткое описание основных используемых функций.

CM_Locate_DevNode – используется нами для получения корневого узла дерева устройств.

Код (Text):
  1.  
  2. CM_Locate_DevNode(
  3.     OUT PDEVINST   pdnDevInst,
  4.     IN DEVINSTID   pDeviceID,  OPTIONAL
  5.     IN ULONG       ulFlags
  6.     );

Параметры

  • pdnDevInst – указатель на переменную, куда будет записан дескриптор устройства
  • pDeviceID – указатель на строку с идентификатором устройства. Если ноль, то возвращается корневой узел дерева устройств
  • ulFlags – обычно функцию вызывают с флагом CM_LOCATE_DEVNODE_NORMAL
CM_Get_DevNode_Status – позволяет получить статус устройства по которому можно определить, можно ли извлечь данное устройство. Если в статусе флаг DN_REMOVABLE установлен, то устройство можно извлечь.

Код (Text):
  1.  
  2. CM_Get_DevNode_Status(
  3.     OUT PULONG     pulStatus,
  4.     OUT PULONG     pulProblemNumber,
  5.     IN DEVINST     dnDevInst,
  6.     IN ULONG       ulFlags
  7.     );

Параметры

  • pulStatus – указатель на переменную типа DWORD со статусом устройства
  • pulProblemNumber – указатель на переменную типа DWORD с номером ошибки
  • dnDevInst – идентификатор устройства
  • ulFlags – не используется. Должен быть нулем

CM_Get_Device_ID_Size – выдает размер строки идентификатора устройства

Код (Text):
  1.  
  2. CM_Get_Device_ID_Size(
  3.     OUT PULONG     pulLen,
  4.     IN DEVINST     dnDevInst,
  5.     IN ULONG       ulFlags
  6.     );

Параметры

  • pulLen – указатель на переменную для записи длины строки
  • dnDevInst – идентификатор устройства
  • ulFlags – не используется. Должен быть нулем
CM_Get_Device_ID – позволяет узнать идентификатор устройства.

Код (Text):
  1.  
  2. CM_Get_Device_ID(
  3.     IN DEVINST     dnDevInst,
  4.     OUT PTCHAR     Buffer,
  5.     IN ULONG       BufferLen,
  6.     IN ULONG       ulFlags
  7.     );

Параметры

  • dnDevInst – идентификатор устройства
  • Buffer – указатель на буфер для записи строки идентификатора устройства
  • BufferLen – длина строки идентификатора устройства
  • ulFlags – не используется. Должен быть нулем

CM_Get_Child – необходима для получения потомка данного узла

Код (Text):
  1.  
  2. CM_Get_Child(
  3.     OUT PDEVINST   pdnDevInst,
  4.     IN DEVINST     dnDevInst,
  5.     IN ULONG       ulFlags
  6.     );

Параметры

  • pdnDevInst – указатель на идентификатор устройства потомка
  • dnDevInst – идентификатор устройства
  • ulFlags – не используется. Должен быть нулем

CM_Get_Sibling – необходима для получения узла, находящегося следом за данным

Код (Text):
  1.  
  2. CM_Get_Sibling(
  3.     OUT PDEVINST   pdnDevInst,
  4.     IN DEVINST     DevInst,
  5.     IN ULONG       ulFlags
  6.     );

Параметры

  • pdnDevInst – указатель на идентификатор смежного устройства
  • dnDevInst – идентификатор устройства
  • ulFlags – не используется. Должен быть нулем

CM_Locate_DevNode – позволяет получить дескриптор устройства по строке идентификатору

Код (Text):
  1.  
  2. CM_Locate_DevNode (
  3.     OUT PDEVINST   pdnDevInst,
  4.     IN DEVINSTID   pDeviceID,  OPTIONAL
  5.     IN ULONG       ulFlags,
  6.     );

Параметры

  • pdnDevInst – указатель на дескриптор устройства
  • pDeviceID – указатель на строку идентификатор устройства
  • ulFlags – флаг мы будем использовать CM_LOCATE_DEVNODE_NORMAL

CM_Request_Device_Eject – как раз эта функция и позволяет безопасно извлечь устройство

Код (Text):
  1.  
  2. CM_Request_Device_Eject(
  3.     IN DEVINST         dnDevInst,
  4.     OUT PPNP_VETO_TYPE pVetoType,
  5.     OUT LPTSTR         pszVetoName,
  6.     IN ULONG           ulNameLength,
  7.     IN ULONG           ulFlags
  8.     );

Параметры

  • dnDevInst – идентификатор устройства
  • pVetoType – в данном случае не нужно
  • pszVetoName – в данном случае не нужно
  • ulNameLength – в данном случае не нужно
  • ulFlags – не используется. Должен быть нулем

CM_Get_DevNode_Registry_Property – извлекает спец. информацию об устройстве из соответствующих ключей реестра.

Код (Text):
  1.  
  2. CM_Get_DevNode_Registry_Property(
  3.     IN DEVINST     dnDevInst,
  4.     IN ULONG       ulProperty,
  5.     OUT PULONG     pulRegDataType, OPTIONAL
  6.     OUT PVOID      Buffer, OPTIONAL
  7.     IN OUT PULONG pulLength,
  8.     IN ULONG       ulFlags
  9.     );

Параметры

  • dnDevInst – идентификатор устройства
  • ulProperty – константа с префиксом CM_DRP_ которая идентифицирует, какое из свойств устройство необходимо. Все эти константы объявлены в файле cfgmgr32.h.
  • pulRegDataType – не обязательный параметр может быть NULL. Указатель на переменную, содержащую тип запрашиваемого значения.
  • Buffer – адрес буфера для возвращаемого значения. Если этот параметр NULL, то функция вернет размер возвращаемой строки
  • pulLength – указатель на переменную для записи размера полученной строки.
  • ulFlags – не используется. Должен быть нулем

Теперь приступаем непосредственно к коду. Ниже приводится фрагмент, отвечающий за обход дерева устройств. Что бы не загромождать код, устройства будем перечислять в виде списка.

Код (Text):
  1.  
  2. EnumDevice proc dDevInst:DWORD
  3.  
  4. LOCAL   NewDevInst  :DWORD
  5. LOCAL   len     :DWORD
  6.     ;Получение статуса устройства
  7.     invoke  CM_Get_DevNode_Status,addr Status,addr ProblemNum,dDevInst,0
  8.     ;Получение идентификатора устройства
  9.     invoke  CM_Get_Device_ID_Size,addr DevLen,dDevInst,0
  10.     inc     DevLen
  11.     invoke  CM_Get_Device_ID,dDevInst,addr aDeviceId,DevLen,0
  12.     ;Получение описания устройства
  13.     mov     len,sizeof aBuffer
  14.     invoke CM_Get_DevNode_Registry_Property,dDevInst,CM_DRP_FRIENDLYNAME,0,\
  15.             addr aBuffer, addr len,0
  16.     .if eax!=CR_SUCCESS
  17.         invoke CM_Get_DevNode_Registry_Property,dDevInst,CM_DRP_DEVICEDESC,0,\
  18.                 addr aBuffer, addr len,0
  19.     .endif
  20.     .if eax!=CR_SUCCESS
  21.         mov aBuffer[0],0
  22.     .endif
  23.     ;выводим информацию
  24.     .if fFiltering==1
  25.         .if (Status & DN_REMOVABLE)
  26.             invoke  AddItemDev
  27.         .endif
  28.     .else
  29.         invoke  AddItemDev
  30.     .endif
  31.     ;ищем потомков 
  32.     invoke  CM_Get_Child,addr NewDevInst,dDevInst,0
  33.     .if eax == CR_SUCCESS
  34.         invoke  EnumDevice,NewDevInst
  35.     .endif
  36.     ;ищем соседей
  37.     invoke  CM_Get_Sibling,addr NewDevInst,dDevInst,0
  38.     .if eax == CR_SUCCESS
  39.         invoke  EnumDevice,NewDevInst
  40.     .endif
  41. Ret
  42.  
  43. EnumDevice EndP

Вызов данной процедуры происходит следующим образом:

Код (Text):
  1.  
  2. ;получаем корневой элемент
  3. invoke  CM_Locate_DevNode,addr dnDevInst,NULL,CM_LOCATE_DEVNODE_NORMAL
  4. ;запускаем обход дерева
  5. invoke  EnumDevice,dnDevInst

По полученному полю Status можно выяснить, предусмотрено ли безопасное извлечение устройства.

Код (Text):
  1.  
  2. .if (Status & DN_REMOVABLE)
  3.     ;Устройство можно извлечь
  4. .else
  5.     ;Извлечение не поддерживается
  6. .endif

Следующий код является ключевым, именно он и позволяет безопасно отключить найденное устройство.

Код (Text):
  1.  
  2. ………………
  3. ;по идентификатору устройства узнаем его дескриптор
  4. invoke CM_Request_Device_Eject, dnDevInst, NULL, NULL, NULL, NULL
  5. ………………

При таким вызове система сама оповестит пользователя о том, что диск больше не доступен или о том, что в данный момент устройство используется и не может быть извлечено. Если необходимо что бы система не реагировала, то можно вызвать функцию с такими параметрами:

Код (Text):
  1.  
  2. .data
  3. pVetoType dd 0
  4. pVetoName db 5 dup (0)
  5. .code
  6. ………………
  7. mov pVetoType,0
  8. mov pVetoName[0],0
  9. invoke CM_Request_Device_Eject,dnDevInst,addr pVetoType,addr pVetoName,MAX_PATH,NULL
  10. ………………

Также предусмотрена возможность получения иконки, ассоциированной с каким либо устройством (иконки можно видеть в диспетчере устройств). Для этого лишь необходим GUID класса устройства. Его можно получить, например, функцией CM_Get_DevNode_Registry_Property с параметром CM_DRP_CLASSGUID.

Код (Text):
  1.  
  2. .data
  3. ClassImageList  ClassImageListData
  4. .code
  5. ………………
  6. invoke SetupDiGetClassImageList,addr ClassImageList
  7. ………………
  8. invoke SetupDiGetClassImageIndex,addr ClassImageList,addr ClassGUID,addr dwImageIndex
  9. ………………
  10. invoke SetupDiDestroyClassImageList,addr ClassImageList

Следующий важный момент - как отловить момент подключения отключения устройства. Для этого существует специальное сообщение WM_DEVICECHANGE посылаемое всем главным окнам в системе.

Код (Text):
  1.  
  2. .if eax == WM_DEVICECHANGE
  3.     mov    eax,wParam
  4.     .if eax == DBT_DEVICEARRIVAL
  5.         mov  eax,lParam
  6.         assume eax:ptr _DEV_BROADCAST_HDR
  7.         mov  eax,[eax].dbch_devicetype
  8.         assume eax:nothing
  9.         .if  eax == DBT_DEVTYP_VOLUME
  10.             mov eax,lParam
  11.             assume eax:ptr _DEV_BROADCAST_VOLUME
  12.             mov edx,[eax].dbcv_unitmask
  13.             xor ebx,ebx
  14.             .while ebx<26
  15.                 mov ecx,edx
  16.                 and edx,01h
  17.                 .if edx==1
  18.                     mov cx,word ptr [eax].dbcv_flags
  19.                     .if cx == 00h
  20.                         ; Добавлен новый диск
  21.                     .endif
  22.                     .break
  23.                 .else
  24.                     mov edx,ecx
  25.                     shr edx,1
  26.                 .endif
  27.                 inc ebx
  28.             .endw
  29.         assume eax:nothing
  30.         .endif
  31.     .elseif eax == DBT_DEVNODES_CHANGED
  32.         ; Добавлено/удалено устройство
  33.     .endif

В приложении к статье можно найти программу, выполняющую вышеперечисленные действия.

Кроме того, можно запретить все устройства хранения для USB. Похожим способом можно отключать и другие устройства.

Код (Text):
  1.  
  2. ChangeStateUSBDevice proc fState:DWORD
  3. LOCAL   hDevInfoSet :DWORD
  4. LOCAL   dwTmp       :DWORD
  5. LOCAL   BufStr[255] :BYTE
  6. LOCAL   Result  :DWORD
  7. mov     Result,FALSE
  8. mov     pcp.ClassInstallHeader.cbSize,sizeof SP_CLASSINSTALL_HEADER
  9. mov     pcp.ClassInstallHeader.InstallFunction,DIF_PROPERTYCHANGE
  10. mov     eax,fState
  11. mov     pcp.StateChange,eax
  12. mov     pcp.Scope,DICS_FLAG_GLOBAL
  13. invoke  SetupDiGetClassDevs,addr DISK_GUID,NULL,NULL,DIGCF_PRESENT
  14. mov     hDevInfoSet,eax
  15. mov     DevInfoData.cbSize,sizeof SP_DEVINFO_DATA
  16. xor     ecx,ecx
  17. loop1:
  18.     push        ecx
  19.     invoke  SetupDiEnumDeviceInfo,hDevInfoSet, ecx, addr DevInfoData
  20.     .if eax==0
  21.         jmp @
  22.     .endif
  23.  
  24.     invoke  SetupDiGetDeviceRegistryProperty,hDevInfoSet,\
  25.                     addr DevInfoData,SPDRP_COMPATIBLEIDS,\
  26.                     addr dwTmp,addr BufStr,sizeof BufStr,NULL
  27.  
  28.     invoke  lstrcmp,addr BufStr,offset aUSBSTOR
  29.     .if eax==0
  30.         invoke  SetupDiSetClassInstallParams,hDevInfoSet,\
  31.                 addr DevInfoData,addr pcp,sizeof SP_PROPCHANGE_PARAMS
  32.         .if eax==0
  33.             jmp @
  34.         .endif
  35.        
  36.         invoke  SetupDiCallClassInstaller,DIF_PROPERTYCHANGE,\
  37.                 hDevInfoSet, addr DevInfoData
  38.         .if eax==0
  39.             jmp @
  40.         .endif
  41.         mov     Result,TRUE
  42.     .endif
  43.     pop ecx
  44.     inc ecx
  45. jmp loop1
  46. invoke  SetupDiDestroyDeviceInfoList,hDevInfoSet
  47. @:
  48. mov eax,Result
  49. Ret
  50.  
  51. ChangeStateUSBDevice EndP

Приложения:

  • DevEject.rar – Исходный текст программы, перечисляющие устройства и позволяющие безопасно извлечь выбранное устройство.
  • USB_Disable.rar – Программа может отключать/включать USB накопители.
  • Dr_USB.rar – вариация на тему «безопасное извлечение USB-устройств.» Программа в форме меню выводит список извлекаемых устройств с указанием буквы логического диска, если это устройство предназначено для хранения информации.

Литература

© GMax

0 1.847
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532