Blog. Just Blog

Как получить имя файла, зная его Handle

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
В одной программе у меня появилась необходимость получить имя открытого файла, когда известен его хэндл. Полазив по этим вашим интернетам, я нашел немало решений этой задачи, в основном бездумно скопированных с одного сайта на другой. Пришлось разбираться и систематизировать все самому. Итак, самый простой и приятный способ, чтобы получить имя файла по его хэндлу - использовать функцию GetFinalPathNameByHandle.
  1.         ; Получить имя файла по его хэндлу
  2.         invoke GetFinalPathNameByHandle,[hFile],lpName,MAX_PATH,0
На вход подаем хэндл открытого файла, на выходе получаем полный путь к нему. Красота! Но, к сожалению, этот способ работает только на Windows Vista и более новых системах. Если вашему приложению требуется поддержка Windows XP, то придется проверять наличие этой функции в экспорте библиотеки kernel32. Для старых систем есть другой способ. Открытый файл проецируется в память с помощью функций CreateFileMapping и MapViewOfFile, затем с помощью функции GetMappedFileName можно узнать полный путь к спроецированному файлу:
  1.         ; Создать проекцию файла
  2.         invoke  CreateFileMapping,[hFile],NULL,PAGE_READONLY,0,1,NULL
  3.         mov     [hFileMap],eax
  4.         invoke  MapViewOfFile,[hFileMap],FILE_MAP_READ,0,0,1
  5.         mov     [pMem],eax
  6.  
  7.         ; Получить имя спроецированного файла
  8.         invoke  GetCurrentProcess
  9.         invoke  GetMappedFileName,eax,[pMem],lpName,MAX_PATH
  10.  
  11.         ; Прибраться за собой
  12.         invoke  UnmapViewOfFile,[pMem]
  13.         invoke  CloseHandle,[hFileMap]
Имя носителя в строке пути записывается в виде "\Device\HarddiskVolume1", для приведения его к более привычному виду надо пройтись по всем доступным буквам дисков, установленных в системе, каждую преобразовать в имя устройства и сравнить с именем устройства в пути к файлу. Для облегчения операции преобразования символических ссылок я нарисовал отдельную функцию. Способ с проецированием файла неплохой, но у него есть один существенный недостаток: проецирование не работает с файлами нулевой длины. Например, вы не сможете получить имя вновь созданного пустого файла, тогда как функция GetFinalPathNameByHandle прекрасно справляется с этой задачей.

Осталось собрать все полученные знания в одну функцию. Вот что у меня получилось:
  1. ;-------------------------------------------------------------------
  2. ; Функция получения полного имени файла по его handle
  3. ;-------------------------------------------------------------------
  4. proc get_file_name_from_handle hFile:DWORD, lpName:DWORD
  5.         locals
  6.                 szName   rb MAX_PATH
  7.                 szTmp    rb MAX_PATH
  8.                 szDisk   dd ?
  9.                 hFileMap dd ?
  10.                 pMem     dd ?
  11.         endl
  12.  
  13.         ; Сохранить все регистры
  14.         pusha
  15.  
  16.         ; Определить адрес функции GetFinalPathNameByHandle
  17.         invoke  LoadLibrary,.szDll
  18.         invoke  GetProcAddress,eax,.szFunc
  19.         or      eax,eax
  20.         ; Такой функции нет, работаем на старой Windows
  21.         jz      .loc_old_windows
  22. .loc_new_windows:
  23.         ; Vista+
  24.         lea     ebx,[szName]
  25.         stdcall eax,[hFile],ebx,MAX_PATH,0
  26.         cmp     dword [ebx],'\\?\'
  27.         jne     @f
  28.         add     ebx,4
  29. @@:
  30.         invoke  lstrcpy,[lpName],ebx
  31.         jmp     .loc_ret
  32.  
  33. .loc_old_windows:
  34.         ; Old Windows
  35.         invoke  GetFileSize,[hFile],NULL
  36.         or      eax,eax
  37.         ; Файл с нулевой длиной спроецировать нельзя
  38.         jz      .loc_ret
  39.  
  40.         ; Спроецировать файл в память
  41.         invoke  CreateFileMapping,[hFile],NULL,PAGE_READONLY,0,1,NULL
  42.         or      eax,eax
  43.         jz      .loc_ret
  44.         mov     [hFileMap],eax
  45.         invoke  MapViewOfFile,[hFileMap],FILE_MAP_READ,0,0,1
  46.         mov     [pMem],eax
  47.  
  48.         ; Получить имя спроецированного файла
  49.         lea     ebx,[szName]
  50.         invoke  GetCurrentProcess
  51.         invoke  GetMappedFileName,eax,[pMem],ebx,MAX_PATH
  52.  
  53.         ; Прибраться за собой
  54.         invoke  UnmapViewOfFile,[pMem]
  55.         invoke  CloseHandle,[hFileMap]
  56.  
  57.         ; Перебрать все диски, начиная с A:
  58.         mov     [szDisk],'A:'
  59.  
  60. .loc_find_drive:
  61.         ; Преобразовать букву диска в строку типа \Device\HarddiskVolume1
  62.         lea     eax,[szDisk]
  63.         lea     ebx,[szTmp]
  64.         invoke  QueryDosDevice,eax,ebx,MAX_PATH
  65.         or      eax,eax
  66.         ; Диска нет, пропускаем
  67.         jz      @f
  68.         ; Сравнить начало пути со строкой устройства
  69.         mov     ecx,eax
  70.         lea     esi,[szName]
  71.         mov     edi,ebx
  72.         repe    cmpsb
  73.         cmp     ecx,1
  74.         jne     @f
  75.         ; Дополнительная проверка корректности пути
  76.         dec     esi
  77.         cmp     byte [esi],'\'
  78.         jne     @f
  79.  
  80.         ; Диск + оставшийся путь к файлу
  81.         lea     eax,[szDisk]
  82.         invoke  lstrcpy,[lpName],eax
  83.         invoke  lstrcat,[lpName],esi
  84.         jmp     .loc_ret
  85. @@:
  86.         ; Все диски проверили?
  87.         lea     eax,[szDisk]
  88.         cmp     byte [eax],'Z'
  89.         jnz     @f
  90.  
  91.         ; Путь к файлу как есть (сетевой диск и т.п.)
  92.         lea     ebx,[szName]
  93.         invoke  lstrcpy,[lpName],ebx
  94.         jmp     .loc_ret
  95. @@:
  96.         ; Следующий диск
  97.         inc     byte [eax]
  98.         jmp     .loc_find_drive
  99.  
  100. .loc_ret:
  101.         ; Восстановить все регистры
  102.         popa
  103.         ret
  104.  
  105. .szDll  db 'kernel32.dll',0
  106. .szFunc db 'GetFinalPathNameByHandleA',0
  107.  
  108. endp
Параметры вызова: hFile - хэндл открытого файла, lpName - указатель на строку, куда будет записан путь к файлу в случае удачного выполнения. Функция самодостаточная, не требует никаких дополнительных переменных в сегменте данных. Вторую часть кода можно немного оптимизировать, например, не перебирая все буквы дисков подряд, а получив список имеющихся в системе дисков функцией GetLogicalDriveStrings. Хотя какой-то значительный прирост скорости вряд ли будет заметен.

Теперь давайте рассмотрим низкоуровневые способы получения информации об имени файла, если нам известен его хэндл. Первый способ основан на функции NtQueryObject. Раньше она относилась к категории недокументированных, сейчас с документацией все более-менее хорошо. Одна из ее возможностей - получение имени объекта. Хэндл файла является объектом, а имя этого объекта по сути и есть имя файла. Как обычно бывает с нестандартными решениями, придется немного расширить кругозор FASM на предмет нужных структур и констант.
  1. ; Тип запрашиваемой информации
  2. ObjectNameInformation = 1
  3.  
  4. ; Структура для получения результата
  5. struct OBJECT_NAME_INFORMATION
  6.         Length dw ?
  7.         MaximumLength dw ?
  8.         Buffer     dd ?
  9.         String     rw MAX_PATH-1
  10. ends
  11.  
  12. ; Переменная для приема промежуточных данных
  13. tmp  dd ?
  14.  
  15. ; Структура должна быть выровнена на границу DWORD
  16. align 4
  17. ObjectNameInfo OBJECT_NAME_INFORMATION
Обратите внимание, что структура OBJECT_NAME_INFORMATION в сегменте данных обязательно должна быть выровнена на границу DWORD. В противном случае функция не вернет результат, хотя статус будет "выполнено успешно". Получение информации выполняется в два этапа. Первым проходом получаем размер строки, при необходимости проверяем, чтобы все поместилось в отведенную под это дело память, затем вторым проходом получаем саму строку имени файла.
  1.         ; Получить размер информации
  2.         invoke  NtQueryObject,[hFile],ObjectNameInformation,NULL,0,tmp
  3.         ; Получить информацию об объекте
  4.         invoke  NtQueryObject,[hFile],ObjectNameInformation,\
  5.                 ObjectNameInfo,[tmp],tmp
В случае успешного выполнения строка с полным именем файла будет записана в поле структуры OBJECT_NAME_INFORMATION.String. В отличие от предыдущих способов, строка будет в юникоде без вариантов. Имя носителя в строке также записывается в виде "\Device\HarddiskVolume1" и тому подобных, поэтому для получения человекопонятной буквы диска надо будет адаптировать код из приведенной выше функции get_file_name_from_handle. Функция NtQueryObject отлично работает на старых версиях Windows и не имеет никаких проблем при обработке хэндлов файлов с нулевой длиной.

Следующий способ использует функцию NtQueryInformationFile. С ее помощью можно получить огромное количество информации об открытом файле, в том числе и имя файла. Описываем нужные структуры и константы:
  1. ; Тип запрашиваемой информации
  2. FileNameInformation = 9
  3.  
  4. ; Структура для статуса выполнения операции
  5. struct  IO_STATUS_BLOCK
  6.         Status  dd ?
  7.         Pointer dd ?
  8.         Information dd ?
  9. ends
  10.  
  11. ; Структура для получения результата
  12. struct FILE_NAME_INFORMATION
  13.         FileNameLength dd ?
  14.         FileName rw MAX_PATH
  15. ends
  16.  
  17. ; Структура должна быть выровнена на границу DWORD
  18. align 4
  19. FileInformation FILE_NAME_INFORMATION
  20. IoStatusBlock   IO_STATUS_BLOCK
Структура FILE_NAME_INFORMATION также должна быть выровнена на границу DWORD, иначе придется мучить отладчик в поисках причины непонятного поведения вашей программы. А весь код укладывается в одну строчку:
  1.         ; Получить информацию о файле
  2.         invoke  NtQueryInformationFile,[hFile],IoStatusBlock,\
  3.                 FileInformation,MAX_PATH*2,FileNameInformation
Нужные нам данные записываются в структуру FILE_NAME_INFORMATION в поле FileName. Как и в случае с именем объекта, строка имени файла также будет в юникоде. Но, как говорится, есть одно "НО". Строка имени файла возвращается без буквы диска, поэтому при использовании этого способа придется дополнительно шаманить с определением полного пути. Иногда логику разработчиков Windows понять невозможно, лично я не могу придумать причину, по которой нельзя было сразу добавить в путь какое-нибудь обозначение носителя.

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

Примеры программ с исходными текстами (FASM)Примеры программ с исходными текстами (FASM)

File.Name.from.a.File.Handle.Demo.zip (4,566 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 2790 | Комментариев: 3

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
huntingspace (08.03.2019 в 12:39):
Очень интересные варианты поиска имя файла. Мне тоже как-то, лет пять назад,  приходилось писать на ассемблере не только функцию поиска имени файла по хэндлу, но и наоборот - поиска хэндла по имени файла.
ManHunter (31.01.2019 в 11:06):
Хендл - уникальный идентификатор объекта, через который производятся операции с данным объектом. Например, открыли файл - получили хэндл открытого файла, загрузили DLL - получили хэндл, с помощью которого можно выполнять различные действия с DLL, загрузили иконку - получили хэндл иконки.
u-b0at (31.01.2019 в 06:02):
Эх, ещё бы знать что такое handle файла?

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.006 сек.) / Память: 4.5 Mb
Наверх