Blog. Just Blog

Преобразование символических ссылок в путь к файлу

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
"По следам наших публикаций". В одной из недавних статей я использовал код для преобразования символических ссылок на файл в привычный путь. Немного поразмыслив, я решил доработать его до полноценной универсальной функции, которая будет приводить любые "кривые" пути и символические ссылки к человекопонятному виду. Функция разворачивает переменные окружения, исправляет обратные слеши, а также обрабатывает символические ссылки вида "\SystemRoot\explorer.exe", "\Device\HarddiskVolume1\WINDOWS\win.ini", "\??\E:\asm\" и "file:///C:/Windows/explorer.exe". Поддерживаются файлы, каталоги и диски.
  1. ;------------------------------------------------------------
  2. ; Функция нормализации пути к файлу или каталогу
  3. ;------------------------------------------------------------
  4. ; Параметры:
  5. ;   lpPath - указатель на исходный путь
  6. ;   lpNorm - указатель на строку с нормализованным путем
  7. ; На выходе:
  8. ;   EAX = 1 - путь успешно нормализован
  9. ;   EAX = 0 - путь не найден
  10. ;------------------------------------------------------------
  11. proc normalize_path lpPath:DWORD,lpNorm:DWORD
  12.         _OBJ_CASE_INSENSITIVE         = 0x00000040
  13.         _FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020
  14.         _FILE_READ_DATA               = 1
  15.  
  16.         ; Тип запрашиваемой информации
  17.         _ObjectNameInformation = 1
  18.  
  19.         ; Структура для получения результата
  20.         struct _OBJECT_NAME_INFORMATION
  21.                 Length        dw ?
  22.                 MaximumLength dw ?
  23.                 Buffer        dd ?
  24.                 String        rw MAX_PATH-1
  25.         ends
  26.  
  27.         ; Структура для юникодной строки
  28.         struct _UNICODE_STRING
  29.                 Length        dw ?
  30.                 MaximumLength dw ?
  31.                 Buffer        dd ?
  32.         ends
  33.  
  34.         ; Структура для статуса выполнения операции
  35.         struct _IO_STATUS_BLOCK
  36.                 Status      dd ?
  37.                 Pointer     dd ?
  38.                 Information dd ?
  39.         ends
  40.  
  41.         ; Структура для атрибутов объекта
  42.         struct _OBJECT_ATTRIBUTES
  43.                 Length                   dd ?
  44.                 RootDirectory            dd ?
  45.                 ObjectName               dd ?
  46.                 Attributes               dd ?
  47.                 SecurityDescriptor       dd ?
  48.                 SecurityQualityOfService dd ?
  49.         ends
  50.  
  51.         locals
  52.                 hFile            dd ?
  53.                 tmp_name         rw MAX_PATH-1
  54.                 dDisk            dd ?
  55.                 szDisk           rw 4
  56.                 result           dd ?
  57.                 szTmp            rw MAX_PATH-1
  58.                 ObjectName       _UNICODE_STRING
  59.                 ObjectAttributes _OBJECT_ATTRIBUTES
  60.                 IoStatusBlock    _IO_STATUS_BLOCK
  61.                 dwSize           dd ?
  62.                 ObjectNameInfo   _OBJECT_NAME_INFORMATION
  63.         endl
  64.  
  65.         pusha
  66.  
  67.         mov     [result],0
  68.  
  69.         ; Преобразовать File URL в путь
  70.         lea     eax,[dwSize]
  71.         mov     dword [eax],MAX_PATH*2
  72.         lea     ebx,[szTmp]
  73.         invoke  PathCreateFromUrl,[lpPath],ebx,eax,0
  74.         or      eax,eax
  75.         jz      @f
  76.  
  77.         mov     ebx,[lpPath]
  78. @@:
  79.         ; Развернуть переменные окружения
  80.         lea     eax,[tmp_name]
  81.         invoke  ExpandEnvironmentStrings,ebx,eax,MAX_PATH*2
  82.  
  83.         ; Исправить обратные слеши
  84.         lea     ebx,[tmp_name]
  85.         mov     esi,ebx
  86.         mov     edi,ebx
  87. .loc_fix_slashes:
  88.         lodsw
  89.         cmp     ax,'/'
  90.         jne     @f
  91.         mov     ax,'\'
  92. @@:
  93.         stosw
  94.         or      ax,ax
  95.         jnz     .loc_fix_slashes
  96.  
  97.         ; Путь уже нормализован?
  98.         cmp     word [ebx+2],':'
  99.         jne     @f
  100.  
  101.         ; Больше ничего делать не требуется
  102.         invoke  lstrcpy,[lpNorm],ebx
  103.         mov     [result],1
  104.         jmp     .loc_ret
  105. @@:
  106.         lea     eax,[ObjectName]
  107.         invoke  RtlInitUnicodeString,eax,ebx
  108.  
  109.         push    _FILE_SYNCHRONOUS_IO_NONALERT
  110.         push    FILE_SHARE_READ+FILE_SHARE_WRITE+FILE_SHARE_DELETE
  111.         lea     eax,[IoStatusBlock]
  112.         push    eax
  113.         lea     ebx,[ObjectAttributes]
  114.         push    ebx
  115.         mov     [ebx+_OBJECT_ATTRIBUTES.Length],sizeof._OBJECT_ATTRIBUTES
  116.         lea     eax,[ObjectName]
  117.         mov     [ebx+_OBJECT_ATTRIBUTES.ObjectName],eax
  118.         mov     [ebx+_OBJECT_ATTRIBUTES.Attributes],_OBJ_CASE_INSENSITIVE
  119.         push    _FILE_READ_DATA+SYNCHRONIZE
  120.         lea     eax,[hFile]
  121.         push    eax
  122.         invoke  NtOpenFile
  123.         or      eax,eax
  124.         jz      @f
  125.  
  126.         ; Файл открыть не удалось
  127.         lea     ebx,[tmp_name]
  128.         invoke  lstrcpy,[lpNorm],ebx
  129.         jmp     .loc_ret
  130. @@:
  131.         lea     ebx,[dwSize]
  132.         ; Получить размер информации
  133.         invoke  NtQueryObject,[hFile],_ObjectNameInformation,NULL,0,ebx
  134.         ; Получить информацию об объекте
  135.         push    ebx
  136.         push    [dwSize]
  137.         lea     eax,[ObjectNameInfo]
  138.         push    eax
  139.         invoke  NtQueryObject,[hFile],_ObjectNameInformation
  140.         or      eax,eax
  141.         jz      @f
  142.  
  143.         ; Имя объекта получить не удалось
  144.         lea     ebx,[tmp_name]
  145.         invoke  lstrcpy,[lpNorm],ebx
  146.         invoke  NtClose,[hFile]
  147.         jmp     .loc_ret
  148. @@:
  149.         ; Сохранить полученное имя
  150.         lea     eax,[ObjectNameInfo]
  151.         lea     eax,[eax+_OBJECT_NAME_INFORMATION.String]
  152.         lea     ebx,[tmp_name]
  153.         invoke  lstrcpy,ebx,eax
  154.  
  155.         ; Закрыть открытый файл
  156.         invoke  NtClose,[hFile]
  157.  
  158.         ; Перебрать все диски, начиная с A:
  159.         mov     [dDisk],1
  160. .loc_find_drive:
  161.         invoke  GetLogicalDrives
  162. @@:
  163.         test    eax,[dDisk]
  164.         jnz     @f
  165.         shl     [dDisk],1
  166.         jnz     @b
  167.  
  168.         ; Диск определить не удалось
  169.         lea     ebx,[tmp_name]
  170.         invoke  lstrcpy,[lpNorm],ebx
  171.         jmp     .loc_ret
  172. @@:
  173.         lea     eax,[szDisk]
  174.         mov     dword [eax],0x003A0041 ; 'A:' в юникоде
  175.         mov     dword [eax+4],0
  176.         mov     ecx,[dDisk]
  177.         bsr     ecx,ecx
  178.         add     dword [eax],ecx
  179.  
  180.         ; Следующий диск
  181.         shl     [dDisk],1
  182.  
  183.         ; Преобразовать букву диска в строку типа \Device\HarddiskVolume1
  184.         lea     eax,[szDisk]
  185.         lea     ebx,[szTmp]
  186.         invoke  QueryDosDevice,eax,ebx,MAX_PATH*2
  187.         or      eax,eax
  188.         ; Диска нет, пропускаем
  189.         jz      @f
  190.         ; Сравнить начало пути со строкой устройства
  191.         mov     ecx,eax
  192.         lea     esi,[tmp_name]
  193.         mov     edi,ebx
  194.         repe    cmpsw
  195.         or      ecx,ecx
  196.         jnz     @f
  197.  
  198.         ; Только буква диска
  199.         lea     eax,[szDisk]
  200.         invoke  lstrcpy,[lpNorm],eax
  201.         mov     [result],1
  202.         jmp     .loc_ret
  203. @@:
  204.         cmp     ecx,1
  205.         jne     .loc_find_drive
  206.         ; Дополнительная проверка корректности пути
  207.         dec     esi
  208.         dec     esi
  209.         cmp     word [esi],'\'
  210.         jne     .loc_find_drive
  211.  
  212.         ; Диск + оставшийся путь к файлу
  213.         lea     eax,[szDisk]
  214.         invoke  lstrcpy,[lpNorm],eax
  215.         invoke  lstrcat,[lpNorm],esi
  216.         mov     [result],1
  217. .loc_ret:
  218.         popa
  219.  
  220.         mov     eax,[result]
  221.         ret
  222. endp
Параметры вызова: lpPath - указатель на юникодную строку с исходным путем, lpNorm - указатель на строку, куда будет записан нормализованный путь к файлу или каталогу. В регистре EAX возвращается результат выполнения операции: 1 - нормализация прошла успешно, 0 - что-то пошло не так, например, не найден объект, на который указывает символическая ссылка, или файл расположен на сетевом диске. В случае неудачи в lpNorm будет записан исходный путь, но с развернутыми переменными окружения и исправленными слешами, или вовсе без изменений, если никаких преобразований не выполнялось.

В современных системах, начиная с Windows Vista, вы можете встретить странные пути вида {D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}\taskmgr.exe. Первая часть пути в данном случае - это один из так называемых KNOWNFOLDERID, то есть идентификатор стандартной папки Windows. Чтобы преобразовать GUID в обычный путь, надо сперва отконвертировать строку GUID в числовой вид. Сделать это можно вручную, но лучше воспользоваться штатной функцией GUIDFromString. Она есть сразу в двух библиотеках - shell32.dll и shlwapi.dll, но импортировать ее по имени нельзя, только по ординалу. В shell32.dll это будет ординал 703 для функции GUIDFromStringA и 704 для GUIDFromStringW, в shlwapi.dll, соответственно, ординал 269 для функции GUIDFromStringA и 270 для GUIDFromStringW. Получив GUID, преобразуем его в путь при помощи функции SHGetKnownFolderPath.
  1.         ; Загрузить библиотеку shell32
  2.         invoke  LoadLibrary,shell32
  3.         ; Получить адрес GUIDFromStringW
  4.         invoke  GetProcAddress,eax,704
  5.  
  6.         or      eax,eax
  7.         jz      loc_exit
  8.         mov     [GUIDFromString],eax
  9.  
  10.         ; Получить GUID из строки
  11.         stdcall [GUIDFromString],fname,guid
  12.         or      eax,eax
  13.         jz      loc_exit
  14.         ; Получить имя папки из GUID
  15.         invoke  SHGetKnownFolderPath,guid,0,NULL,tmp
  16.         or      eax,eax
  17.         jnz     loc_exit
  18.  
  19.         ; Скопировать имя папки в нормализованный путь
  20.         invoke  lstrcpy,normalized,[tmp]
  21.  
  22.         ; Найти в строке путь после GUID
  23.         mov     esi,fname
  24. @@:
  25.         lodsw
  26.         cmp     ax,'}'
  27.         jne     @b
  28.  
  29.         ; Добавить имя файла
  30.         invoke  lstrcat,normalized,esi
Что интересно, хотя в описании функции GUIDFromString и заявлено, будто исходная строка должна быть именно GUID и завершаться нулевым символом, на деле после символа "}" могут быть другие символы. То есть выделять какую-то память под дополнительную строку и переносить туда GUID из пути вовсе не обязательно, все работает и так.

Для тех, кто любит погорячее, то же самое можно сделать при помощи COM-объектов. Ну а что, раз уж есть GUID'ы, значит где-то неподалеку должны быть и COM'ы. Первым делом пачка констант, GUID и интерфейсов, которые понадобятся для работы.
  1. CLSCTX_INPROC_SERVER    = 1
  2. S_OK                    = 0
  3.  
  4. ; GUID {4DF0C730-DF9D-4AE3-9153-AA6B82E9795A}
  5. CLSID_KnownFolderManager dd 04DF0C730h
  6.                          dw 0DF9Dh
  7.                          dw 04AE3h
  8.                          db 091h, 053h, 0AAh, 06Bh, 082h, 0E9h, 079h, 05Ah
  9.  
  10. ; GUID {8BE2D872-86AA-4D47-B776-32CCA40C7018}
  11. IID_IKnownFolderManager dd 08BE2D872h
  12.                         dw 086AAh
  13.                         dw 04D47h
  14.                         db 0B7h, 076h, 032h, 0CCh, 0A4h, 00Ch, 070h, 018h
  15.  
  16. ; IID_IKnownFolderManager Interface
  17. struct IKnownFolderManager
  18.     ; IUnknown
  19.     QueryInterface       dd ?
  20.     AddRef               dd ?
  21.     Release              dd ?
  22.     ; IKnownFolderManager
  23.     FolderIdFromCsidl    dd ?
  24.     FolderIdToCsidl      dd ?
  25.     GetFolderIds         dd ?
  26.     GetFolder            dd ?
  27.     GetFolderByName      dd ?
  28.     RegisterFolder       dd ?
  29.     UnregisterFolder     dd ?
  30.     FindFolderFromPath   dd ?
  31.     FindFolderFromIDList dd ?
  32.     Redirect             dd ?
  33. ends
  34.  
  35. ; IID_IKnownFolder Interface
  36. struct IKnownFolder
  37.     ; IUnknown
  38.     QueryInterface             dd ?
  39.     AddRef                     dd ?
  40.     Release                    dd ?
  41.     ; IKnownFolder
  42.     GetId                      dd ?
  43.     GetCategory                dd ?
  44.     GetShellItem               dd ?
  45.     GetPath                    dd ?
  46.     SetPath                    dd ?
  47.     GetIDList                  dd ?
  48.     GetFolderType              dd ?
  49.     GetRedirectionCapabilities dd ?
  50.     GetFolderDefinition        dd ?
  51. ends
Создаем экземпляр объекта IKnownFolderManager для работы с KNOWNFOLDERID. Полученный из строки числовой GUID скармливаем методу GetFolder, на выходе получаем объект IKnownFolder. Затем через его метод GetPath получаем путь, соответствующий исходному GUID. Все эти действия аналогичны вызову единственной функции SHGetKnownFolderPath.
  1.         invoke  CoInitialize,0
  2.  
  3.         ; Загрузить библиотеку shell32
  4.         invoke  LoadLibrary,shell32
  5.         ; Получить адрес GUIDFromStringW
  6.         invoke  GetProcAddress,eax,704
  7.  
  8.         or      eax,eax
  9.         jz      loc_exit
  10.         mov     [GUIDFromString],eax
  11.  
  12.         ; Получить GUID из строки
  13.         stdcall [GUIDFromString],fname,guid
  14.         or      eax,eax
  15.         jz      loc_exit
  16.  
  17.         ; Создать объект
  18.         invoke  CoCreateInstance,CLSID_KnownFolderManager,NULL,\
  19.                 CLSCTX_INPROC_SERVER,\
  20.                 IID_IKnownFolderManager,pKFMDisp
  21.         cmp     eax,S_OK
  22.         jne     loc_exit
  23.  
  24.         ; Получить имя папки из GUID
  25.         mov     eax, [pKFMDisp]
  26.         mov     eax, [eax]
  27.         stdcall dword [eax+IKnownFolderManager.GetFolder],\
  28.                 [pKFMDisp],guid,pKFDisp
  29.         or      eax,eax
  30.         jnz     loc_exit
  31.  
  32.         mov     eax, [pKFDisp]
  33.         mov     eax, [eax]
  34.         stdcall dword [eax+IKnownFolder.GetPath],[pKFDisp],0,tmp
  35.         or      eax,eax
  36.         jnz     loc_exit
  37.  
  38.         ; Скопировать имя папки в нормализованный путь
  39.         invoke  lstrcpy,normalized,[tmp]
  40.  
  41.         ; Найти в строке путь после GUID
  42.         mov     esi,fname
  43. @@:
  44.         lodsw
  45.         cmp     ax,'}'
  46.         jne     @b
  47.  
  48.         ; Добавить имя файла
  49.         invoke  lstrcat,normalized,esi
  50.  
  51.         mov     eax, [pKFDisp]
  52.         mov     eax, [eax]
  53.         stdcall dword [eax+IKnownFolder.Release],[pKFDisp]
  54.  
  55.         mov     eax, [pKFMDisp]
  56.         mov     eax, [eax]
  57.         stdcall dword [eax+IKnownFolderManager.Release],[pKFMDisp]
  58.  
  59.         ; Удалить объект
  60.         invoke  CoUninitialize
Очень хорошая программа для работы с Known Folders - это Known Folders Browser. Скачать ее можно с офсайта или по ссылке ниже.

Known Folders Browser 1.0.2Known Folders Browser 1.0.2

Known.Folders.Browser.1.0.2.zip (242,550 bytes)

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

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

Symlink.to.File.Name.Demo.zip (9,259 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (24.04.2021 в 17:26):
Добавил информацию о преобразовании различными способами KnownFolder-путей в обычные. Архив обновлен. Можно было бы нарисовать отдельную статью, но пусть тут будет все по одной теме.
ManHunter (13.02.2019 в 15:10):
Добавил обработку File URL типа "file:///C:/Windows/explorer.exe"
Архив обновлен.

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

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

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