hex.pp.ua

Определение имени файла по хэндлу. Приведение имени из 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.

Успехов.

система комментирования CACKLE

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


Разделы сайта
Главная
Блог
Native API
NTFS и ReFS
Микроконтроллеры
Справочник NTDLL
Коды NTSTATUS
Разное

Избранное
NTFS Stream Explorer
Native Shell
Тенгвар

Остальное
nvpnhcknn (архив)
English pages
Контакты

Ленты atom
Лента Atom сайта Лента Atom блога



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