Определение имени файла по хэндлу. Приведение имени из Win32 в Nt-формат

Как получить имя файла по его хэндлу и привести имя из Win32 в Nt-формат




Настоящая статья фактически делится на 2 большие части. В первой части будет рассмотрено приведение имени файла из формата Win32 к «родному» имени вида \Device\HarddiskVolume1\WINNT. Во второй части — получение имени файла по его хэндлу.

Отметим, если по Win32-имени текущее Native-имя устанавливается однозначно (небольшое исключение из правила есть для сетевых файлов, о нем будет написано ниже), то обратное преобразование, то есть, получение из \Device\HardiskVolume3\WINNT имени K:\WINNT, явно может не быть однозначным даже в случае компьютера без службы сервера, ибо существует программа subst. Поэтому сравнивать имена файлов, если приспичит, надо только после преобразования их в Native-формат. Чем мы и займемся.

Первая функция будет получать по имени раздела его Native-имя. Делать она это будет через чтение элементов пространства имен диспетчера объектов. Как и положено, она может оперировать как буквенными обозначениями разделов, так и через GUID, приписанный разделу. Ибо и для того и для другого создается символическая ссылка, разрешнием которой функция и занимается. Используются функции ZwOpenDirectoryObject, ZwOpenSymbolicLinkObject, и ZwQuerySymbolicLinkObject.

Функция записывает в буфер szBuffer Native-имя для szWin32Name.


NTSTATUS NTAPI GetDriveName(IN PWSTR szWin32Name, OUT PWSTR szBuffer, IN USHORT BufferLen)
{
   HANDLE hDir = NULL;
   UNICODE_STRING usRoot;
   RtlInitUnicodeString(&usRoot,L"\\??");
   OBJECT_ATTRIBUTES oa = {sizeof(OBJECT_ATTRIBUTES), NULL, &usRoot, OBJ_CASE_INSENSITIVE};

   NTSTATUS ns = ZwOpenDirectoryObject(&hDir, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &oa);

   if (ns == STATUS_SUCCESS)
   {
       HANDLE hSym = NULL;

       RtlInitUnicodeString(&usRoot, szWin32Name);
       oa.RootDirectory = hDir;

       ns = ZwOpenSymbolicLinkObject(&hSym, SYMBOLIC_LINK_QUERY, &oa);
       if (ns == STATUS_SUCCESS)
       {
           UNICODE_STRING usSym;
           usSym.Length = BufferLen;
           usSym.MaximumLength = usSym.Length;
           usSym.Buffer = szBuffer;

           ns = ZwQuerySymbolicLinkObject(hSym, &usSym, NULL);

           ZwClose(hSym);
       }

       ZwClose(hDir);
   }

   return ns;
}

А вторая будет делать то же самое, только для полного имени файла. Она при необходимости вызывается рекурсивно, также правильно обрабатывает префиксы (см. код). И еще отличие — буфер она будет выделять сама, указатель на него и будет возвращаться.

Функция выделяет буфер для Native-имени и возвращает в нем соответствующее имя для Win32-имени файла.

PWSTR NTAPI GetNativeNameEx(IN PWSTR szFileName)
{
   if (!szFileName) return NULL;

   ULONG szLen = wcslen(szFileName);

   //обработка переменных окружения
   if ((szLen>0) && (szFileName[0] == '%'))
   {
       PWSTR Result = NULL;

       ULONG BytesNeeded = 0;

       UNICODE_STRING usFN;
       RtlInitUnicodeString(&usFN,szFileName);

       UNICODE_STRING usOut = {0,0,0};

       NTSTATUS ns = RtlExpandEnvironmentStrings_U(NULL, &usFN, &usOut, &BytesNeeded);

       if ((ns == STATUS_BUFFER_TOO_SMALL) && (BytesNeeded))
       {
           if (Result = (PWSTR)malloc(BytesNeeded))
           {
               usOut.Buffer = Result;
               usOut.MaximumLength = (USHORT)BytesNeeded;

               ns = RtlExpandEnvironmentStrings_U(NULL, &usFN, &usOut, &BytesNeeded);

               if (ns == STATUS_SUCCESS)
               {
                   PWSTR Result2 = Result;
                   Result = GetNativeNameEx(Result2);
                   free(Result2);
               }
               else
               {
                   free(Result);
                   Result = NULL;
               }
           }
       }

       return Result;
   }

   //обработка префиксов \??\, \?\\ и \\.\
   if ((szLen>4) && (szFileName[0] == '\\') && (szFileName[3] == '\\'))
   {
       if (
           ((szFileName[1] == '?') && (szFileName[2] == '?')) ||
           ((szFileName[1] == '\\') && (szFileName[2] == '?')) ||
           ((szFileName[1] == '\\') && (szFileName[2] == '.'))
           )
       return GetNativeNameEx(&(szFileName[4]));
   }

   //обработка префикса \\компьютер (UNC-имя)
   if ((szLen>2) && (szFileName[0] == '\\') && (szFileName[1] == '\\'))
   {
       ULONG LanLen = wcslen(DD_LANMANREDIRECTOR_DEVICE_NAME);
       PWSTR Result = (PWSTR)malloc((szLen+LanLen)*sizeof(WCHAR));
       if (Result)
       {
           wcscpy(Result,DD_LANMANREDIRECTOR_DEVICE_NAME);
           wcscat(Result,szFileName+1);
       }
       return Result;
   }

   //обработка обычных файлов
   if ((szLen >= 2) && (szFileName[1] == ':'))
   {
       WCHAR Drive[3] = {szFileName[0],szFileName[1],'\0'};
       WCHAR DriveDevice[64];
       RtlZeroMemory(DriveDevice,64*sizeof(WCHAR));

       NTSTATUS ns = GetDriveNameEx(Drive,DriveDevice,128);

       if (ns == STATUS_SUCCESS)
       {
           PWSTR Result = (PWSTR)malloc((wcslen(DriveDevice)+wcslen(szFileName)+4)*sizeof(WCHAR));
           if (Result)
           {
               wcscpy(Result,DriveDevice);
               ULONG L = wcslen(Result);

               if (Result[L-1] == '\\')
               Result[L-1] = '\0';

               if (wcslen(szFileName) > 3)
               {
                   wcscat(Result,L"\\");
                   wcscat(Result,szFileName+3);
               }

               return Result;
           }
       }
   }

   //обработка префикса \System32
   if ((!_wcsnicmp(szFileName,L"\\System32",9)) && ((szFileName[9] == '\\') || (szFileName[9] == '\0')))
   {
       PWSTR Result = NULL;
       PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+40);
       if (szTmp)
       {
           swprintf(szTmp,L"%%SystemRoot%%%s",szFileName);
           Result = GetNativeNameEx(szTmp);
           free(szTmp);
       }
       return Result;
   }

   //обработка префикса System32
   if ((!_wcsnicmp(szFileName,L"System32",8)) && ((szFileName[8] == '\\') || (szFileName[8] == '\0')))
   {
       PWSTR Result = NULL;
       PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+40);
       if (szTmp)
       {
           swprintf(szTmp,L"%%SystemRoot%%\\%s",szFileName);
           Result = GetNativeNameEx(szTmp);
           free(szTmp);
       }
       return Result;
   }

   //обработка префикса \SystemRoot
   if ((!_wcsnicmp(szFileName,L"\\SystemRoot",11)) && ((szFileName[11] == '\\') || (szFileName[11] == '\0')))
   {
       PWSTR Result = NULL;
       PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+8);
       if (szTmp)
       {
           swprintf(szTmp,L"%%SystemRoot%%%s",(PWSTR)(&(szFileName[11])));
           Result = GetNativeNameEx(szTmp);
           free(szTmp);
       }
       return Result;
   }

   //обработка префикса SystemRoot
   if ((!_wcsnicmp(szFileName,L"SystemRoot",10)) && ((szFileName[10] == '\\') || (szFileName[10] == '\0')))
   {
       PWSTR Result = NULL;
       PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+8);
       if (szTmp)
       {
           swprintf(szTmp,L"%%SystemRoot%%\\%s",(PWSTR)(&(szFileName[10])));
           Result = GetNativeNameEx(szTmp);
           free(szTmp);
       }
       return Result;
   }

   return NULL;
}

Бросается в глаза некоторая исключительность префиксов типа \SystemRoot. На самом деле такие префиксы, как видно в коде, разрешаются через добавление % перед и после префикса и повторный разбор уже как переменной окружения (через RtlExpandEnvironmentStrings_U). Явная реализация здесь двух пар префиксов просто призвана продемонстрировать эту методику.

По поводу неоднозначности Native-имени. При подключении сетевого диска в проводнике после \Device\LanmanRedirector до имени компьютера возможно использование дополнительных символов, например, в Windows 2000 добавляется имя диска и номер сессии, а в NT4 — только имя диска. Вот именно в них-то и может быть отличие в именах. Полное имя может выглядеть как \Device\LanmanRedirector\;X:0\Server\Share\Dir\File или как \Device\LanmanRedirector\;Y:1\Server\Share\Dir\File, но, в действительности, это будет один и тот же файл \Device\LanmanRedirector\Server\Share\Dir\File. На Windows NT 4.0 имя файла может иметь вид \Device\LanmanRedirector\U:\Server\Share\Dir\File. Способ получения Native-имени, предложенный здесь, из-за отсутствия обработки префикса \Device\LanmanRedirector\ не всегда будет давать «настоящее» имя файлов по его Win32-имени, чтоб это было так, необходимо использовать следующую функцию. Критерием наличия дополнительных символов в имени, которые надо убрать, будет наличие символа ':' в следующем разделе между '\' после LanmanRedirector.

Функция дополняет GetNativeName, проверяя выходное имя на предмет наличия префикса «\Device\LanmanRedirector», и при необходимости преобразует его.

PWSTR NTAPI GetNativeName(IN PWSTR szFileName)
{
   PWSTR Result = GetNativeNameEx(szFileName);
   if (Result)
   {
       ULONG LanLen = wcslen(DD_LANMANREDIRECTOR_DEVICE_NAME);
       ULONG Len = wcslen(Result);
       if ((Len > LanLen + 1) && (!_wcsnicmp(Result,DD_LANMANREDIRECTOR_DEVICE_NAME,LanLen)) && (Result[LanLen] == '\\'))
       {
           PWSTR pSlash = wcschr(&(Result[LanLen+1]),'\\');
           PWSTR pDots = wcschr(&(Result[LanLen+1]),':');
           if ((pDots) && (pDots < pSlash))
           {
               PWSTR Result2 = (PWSTR)malloc(Len);
               if (Result2)
               {
                   swprintf(Result2,L"%s%s",DD_LANMANREDIRECTOR_DEVICE_NAME,pSlash);
                   free(Result);
                   Result = Result2;
               }
           }
       }
   }
   return Result;
}

Еще стоит отметить, что при разрешении имени файла по UNC-имени MUP (Multiple UNC Provider) использует всегда «стандартное» имя, без дополнительных символов.

А теперь вторая часть. По hFile получить FileName не просто, а очень просто. Делать мы это будем через ZwQueryObject. На самом деле мы решим более общую задачу, а именно, рассмотрим правильное использование функции ZwQueryObject исходя из того, что требуемый размер буфера неизвестен, и не только на примере запроса имени файла.

Функция возвращает информацию класса ObjectInformationClass об объекте ObjectHandle.

NTSTATUS NTAPI GetObjInfo(IN HANDLE ObjectHandle, IN OBJECT_INFORMATION_CLASS ObjectInformationClass, OUT PVOID * ObjectInformation, IN OUT PULONG RealSize OPTIONAL, IN ULONG FixedSize OPTIONAL)
{
   ULONG Dummy = 0;

   if (RealSize)
   {
       NTSTATUS ns = STATUS_INFO_LENGTH_MISMATCH;
       Dummy = ( FixedSize ? FixedSize : (RealSize ? (*RealSize) : 0 ) );

       for (;;)
       {
           if ( (ObjectInformation && (*ObjectInformation) ) || (!Dummy) )
           ns = ZwQueryObject(ObjectHandle, ObjectInformationClass, ObjectInformation ? (*ObjectInformation) : NULL, Dummy, &Dummy);

           if (((ns == STATUS_BUFFER_TOO_SMALL) || (ns == STATUS_INFO_LENGTH_MISMATCH) || (ns == STATUS_BUFFER_OVERFLOW)) && (Dummy))
           {
               if (Dummy > (*RealSize))
               if (ObjectInformation)
               if (*ObjectInformation)
               {
                   free(*ObjectInformation);
                   *ObjectInformation = NULL;
                   (*RealSize) = 0;
               }

               if (ObjectInformation)
               if (!(*ObjectInformation))
               if ((*ObjectInformation = malloc(Dummy)))
               {
                   (*RealSize) = Dummy;
               }
               else
               {
                   ns = STATUS_NO_MEMORY;
                   break;
               }
           }
           else
               break;
       }

       return ns;
   }
   else
   {
       return ZwQueryObject(ObjectHandle, ObjectInformationClass, ObjectInformation ? (*ObjectInformation) : NULL, FixedSize, &Dummy);
   }
}

Осталось только разобраться в параметрах функции. Буфер для информации она получает по указателю на его адрес (потому как если размера буфера будет недостаточно, придется его выделять по новой и возвращать его новый адрес). Аналогично получается и возвращается размер буфера. Параметр FixedSize используется в качестве точного размера буфера, если требуемый размер буфера известен заранее, например, в буфер помещается известная структура. Особенности реализации этой функции такие. Во-первых, она позволяет использовать только часть буфера, как в случае фиксированноого размера запрашиваемой информации, так и в случае заранее неизвестного размера информации (как раз в случае определения имени). При необходимости будет выделен буфер требуемого размера. Во-вторых — будет ниже, а пока вернемся к нашим хэндлам.

//Функция возвращает имя объекта
NTSTATUS NTAPI GetObjName(IN HANDLE ObjectHandle, OUT POBJECT_NAME_INFORMATION * ObjectInformation, IN OUT PULONG Size)
{
   return GetObjInfo(ObjectHandle, ObjectNameInformation, (PVOID*)ObjectInformation, Size, 0);
}

Теперь рассмотрим особенности получения имени для объектов типа File. При запросе имени для канала (Pipe), если тот открыт в синхронном режиме, запрос имени также встает в очередь, и поток останавливается. Вообще говоря, запрос имени объекта типа File не всегда корректен, исходя из той роли, которую играют такие объекты в системе, потому гарантировать определенную реакцию на такой запрос не представляется возможным. В общем случае возможно и принудительное завершение потока, ведь неизвестно, как система и отдельные драйвера могут охранять свои внутренности. По этой причине запрос имени файла всегда надо выполнять в отдельном потоке, дабы потом была возможность прибить этот поток. Аналогичная реакция в принципе может быть прогнозируема для объектов типа Key, но примеры «неадекватного» поведения системы для ключей реестра пока неизвестны.

А теперь обещанное «во-вторых», так сказать, на сладкое. Глядя на формат функций ZwQueryEvent, ZwQueryInformationAtom, ZwQueryInformationJobObject, ZwQueryInformationPort, ZwQueryInformationProcess, ZwQueryInformationThread, ZwQueryInformationToken, ZwQueryIoCompletion, ZwQueryKey, ZwQueryMutant, ZwQueryObject, ZwQuerySection, ZwQuerySecurityObject, ZwQuerySemaphore, ZwQueryTimer можно заметить определенное сходство в передаваемых параметрах. В качестве домашнего задания предлагаю реализовать «универсальную» функцию для запросов свойств объектов, одним из параметров которой и будет реально вызываемая функция. Слово «универсальную» взято в кавычки, потому что есть объект File и функция ZwQueryInformationFile, которая реализована по-другому. Зато нет худа без добра, глядим на ZwQueryVolumeInformationFile и пишем еще одну «универсальную» функцию (только в этом случае советую передать еще параметры для определения необходимости ожидания на объекте, если возвращен результат STATUS_PENDING). По аналогии «универсального геттера» возможно создание и «универсального сеттера». Он, кстати, будет попроще, раз точно известно, что именно устанавливается, можно передавать адрес самого буфера и обойтись только FixedSize.

Успехов.



Автор: Сергей Васкецов
Дата: 03.02.2003


При копировании материалов хорошим тоном будет указание авторства и ссылка на сайт.