Применение недокументированных функций в системе Windows

Использование недокументированных функций, довольно широко используется в современных программных продуктах. Они позволяют получить доступ к скрытым или к интересным свойствам системы. А иногда и ускорить выполнение какого-то участка кода. Но у них есть один довольно серьёзный минус, который перечёркивает все те плюсы, которые были описаны выше. Так как они не документированы, за их поддержку и работу, особо никто не отвечает. Поэтому всю ответственность за возможную не работу берёте на себя только вы. К тому же, разные версии Windows могут и не поддерживать эти функции. Так что выбор использовать или нет, только за вами.

Пока вы думаете, мы разберём несколько примеров, которые помогут разобраться в этом с виду тёмном лесу. Все примеры будут относиться к среде Delphi, но при понимании темы, думаем, не сложно повторить это в других средах и на других языках. Где искать такие функции? Можно конечно вооружившись дизассемблером исследовать ntdll.dll, но можно посетить, например сайт http://undocumented.ntinternals.net и разбираться уже в нём.

Пожалуй, начнём. Стартанём с довольно стандартной функции NtQuerySystemInformation. Название у неё довольно обманчивое, потому что с помощью неё можно получить не только доступ к системной информации, но и, например информацию о процессах и много чего ещё.

NtQuerySystemInformation(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass, // Структура SYSTEM_INFORMATION_CLASS
OUT PVOID SystemInformation, // Массив для получения информации
IN ULONG SystemInformationLength, // Длина массива
OUT PULONG ReturnLength OPTIONAL ); // Информация о выполнении функции

Пробуем вызвать эту функцию, но сталкиваемся с полным непониманием со стороны борландовского компилятора, что от него нужно. Просветить его можно только с помощью прямого вызова этой функции из ntdll.dll. Но перед этим, её нужно описать. Прототип есть, так что вперёд:

function NtQuerySystemInformation(infoClass: PSYSTEM_PROCESS_INFORMATION;
buffer: Pointer;
bufSize: DWORD;
returnSize: Dword): DWORD; stdcall;external ntdll.dll

Теперь в заголовке пропишем строку "initialization" и после этого слова запишем следующий код:

var libvar:cardinal;
….
initialization
libvar:= LoadLibrary(NTDLL.DLL); // Загружаем библиотеку
if libvar = 0 then // Если она не загрузилась,
exit; //то выходим
NtQuerySystemInformation:= GetProcAddress(libvar,NtQuerySystemInformation); // Загружаем функцию
if @NtQuerySystemInformation <>nil then // Если не загрузилась,
exit; // то выходим
finalization
FreeLibrary(libvar); // При выходе освобождаем память

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

Функция, с которой мы будем работать, может принимать довольно много вариантов структур. Мы рассмотрим только несколько. А описание всех структур, конечно же, в msdn. Этой структурой будет SYSTEM_BASIC_INFORMATION. Её кстати тоже надо объявить прямо в коде и вызвать из ntdll.dll, ведь она также является недокументированной.

SYSTEM_BASIC_INFORMATION = packed record
AlwaysZero: ULONG; // Всегда 0
MaximumIncrement: ULONG; // Часовые единицы измерения
PageSize: ULONG; // Размер страницы памяти
NumberOfPhysicalPages: ULONG; // Количество страниц памяти
LowestPhysicalPage: ULONG; // Минимальное количество страниц
HighestPhysicalPage: ULONG; // Максимальное количество страниц
AllocationGranularity: ULONG; // Зарезервировано
LowestUserAddress: POINTER;// Минимальное количество пользователей
HighestUserAddress: POINTER; // Максимальное количество пользователей
ActiveProcessors: POINTER; // Активные процессоры
NumberProcessors: BYTE; // Количество процессоров
Filler: array [0..2] of BYTE;
end;

Теперь нам уже ничего не мешает вызвать эту функцию:

var
status:longint;
SysInf:TSystem_Basic_Information;
begin
status:= NtQuerySystemInformation(0, @SysInf,sizeof(SysInf),nil);
if status <> 0 then
exit;
ShowMessage(IntToStr(SysInf.NumberProcessors));
end;

В этом примере мы получаем количество процессоров, которые присутствуют на машине. Сами же Майкрософт не рекомендуют использовать недокументированные функции и рядом с описанием NtQuerySystemInformation, расположилась обычная WinAPI GetSystemInfo. Конечно, в данном случае наиболее предпочтительнее вызывать более стандартный аналог, но что если, например, задачи нестандартные? Рассмотрим например структуру [http://undocumented.ntinternals.net/UserMode/UndocumentedFunctions/SystemInformation/Structures/
SYSTEM_PROCESS_INFORMATION.html|SYSTEM_PROCESS_INFORMATION], которая позволяет получить максимально доступную (на таком уровне) информацию о процессе. И теперь взглянем на стандартный прототип PROCESS_INFORMATION. Ну что, есть разница? А ведь в SPI это ещё не все значения.

Теперь перейдём к более наглядной демонстрации. Мы не будем снова описывать эту структуру, просто это итак подразумевается. Перейдём лучше сразу к вызову:

var
SystemInfo, ProcessInfo: PSYSTEM_PROCESS_INFORMATION;
ReturnLength: DWORD;
ntst,status:Integer;
begin
ReturnLength := 0;
ntst:= NtQuerySystemInformation(5, nil, 0, ReturnLength);
if ntst = 0 then // Если функция равна нулю,
exit; // то выход
if ReturnLength > 0 then
begin
GetMem(SystemInfo,ReturnLength); // Выделяем память под структуру
try
NtQuerySystemInformation(SystemProcessesAndThreadsInformation,SystemInfo,ReturnLength,
ReturnLength); // Снова вызываем функцию, надо для определения количества памяти
ProcessInfo:= Systeminfo;
repeat
ProcessInfo :=Pointer(DWORD(ProcessInfo) + ProcessInfo.NextOffset); // Указатель на данные в памяти этой структуры
ListBox1.Items.Add(ProcessInfo.ModuleName) // Результаты выводим например на ListBoix
until ProcessInfo^.NextOffset = 0; // Крутим пока смещение не будет равно нулю
finally
FreeMem(SystemInfo); // Освобождаем память
end;
end;
end;

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

  1. Описать эту функцию с помощью LoadLibrary();
  2. Отключить нужную библиотеку;
  3. Через GetProcAddress получить адрес экспортируемой функции;
  4. Вызвать эту функцию и наслаждаться работой.

Удачи в работе с недокументированными функциями!