如何获取NtQueryInformationProcess所需的缓冲区大小?

时间:2019-03-10 05:40:58

标签: windows delphi

NtQueryInformationProcess的ReturnLength参数记录如下:

  

ReturnLength

     

指向变量的指针,函数在该变量中返回所请求信息的大小。如果该函数成功,则为ProcessInformation参数所指向的写入缓冲区的信息大小,但是如果缓冲区太小,则为成功接收信息所需的最小缓冲区大小。

特别感兴趣的部分是:如果缓冲区太小,这是成功接收信息所需的缓冲区的最小大小。

我希望函数返回特定信息类所需的缓冲区大小。

我尝试了以下操作:

 // for static linking

function NtQueryInformationProcess(ProcessHandle        : THANDLE;
                               ProcessInformationClass  : DWORD;
                               ProcessInformation       : PPEB;
                               ProcessInformationLength : DWORD;
                               ReturnLength             : PDWORD)
     : NTSTATUS; stdcall; external ntdll;

 // for dynamic linking

 type
   TNtQueryInformationProcess
     = function (ProcessHandle       : THANDLE;
            ProcessInformationClass  : DWORD;
            ProcessInformation       : PPEB;
            ProcessInformationLength : DWORD;
            ReturnLength             : PDWORD) : NTSTATUS; stdcall;

var
  ProcessHandle : THANDLE;

  Peb           : TPEB;
  BufferSize    : DWORD;
  ReturnLength  : DWORD;
  NtResult      : NTSTATUS;
  NtdllHandle   : HMODULE;
  NtQueryInformationProcessPtr : TNtQueryInformationProcess;

begin
 ProcessHandle := OpenProcess(PROCESS_ALL_ACCESS,
                              FALSE,
                              GetCurrentProcessId());

 ZeroMemory(@Peb, sizeof(Peb));
 BufferSize   := sizeof(PROCESS_BASIC_INFORMATION);
 ReturnLength := 0;

 NtResult := NtQueryInformationProcess(ProcessHandle,
                                       ProcessBasicInformation,
                                    @Peb,
                                    BufferSize,
                                   @ReturnLength);

 writeln('NTSTATUS     : ', IntToHex(NtResult, 0));
 writeln('ReturnLength : ', ReturnLength);

 // try calling the function by address

 NtdllHandle := LoadLibrary('ntdll.dll');

 pointer(NtQueryInformationProcessPtr) := GetProcAddress(NtdllHandle,
                                             'NtQueryInformationProcess');

 // reinitialize just to be safe

 ZeroMemory(@Peb, sizeof(Peb));
 BufferSize   := sizeof(PROCESS_BASIC_INFORMATION);
 ReturnLength := 0;

 NtResult := NtQueryInformationProcessPtr(ProcessHandle,
                                          ProcessBasicInformation,
                                       @Peb,
                                       BufferSize,
                                       @ReturnLength);

 writeln('NTSTATUS     : ', IntToHex(NtResult, 0));
 writeln('ReturnLength : ', ReturnLength);

 writeln('program end.');
 readln;
end.

当我将缓冲区的大小设置为ProcessBasicInformation的正确大小时,一切都按预期工作,但是,如果我将BufferSize设置为零(例如),希望该函数在ReturnLength变量中返回必要的缓冲区大小,则会得到返回0xC0000004(大小不匹配),这与预期的BUT相同,但ReturnLength变量未设置为成功获取ProcessBasicInformation所需的大小。

鉴于ReturnLength参数的描述,我希望API将ReturnLength设置为请求的信息类所需的任何大小。

我的问题是:代码中是否有错误或API是否无法按文档所述工作?还是我误解了ReturnLength参数的描述?

谢谢您的帮助。

2 个答案:

答案 0 :(得分:3)

许多Win32 API旨在通过使用nil指针调用缓冲区大小并为ReturnLength指定0来告诉您正确的缓冲区大小。 Nt api的工作原理通常有所不同:

您只需将任意大小的缓冲区传递给它,它将通过返回STATUS_INFO_LENGTH_MISMATCH来告诉您是否需要更多缓冲区。

我还建议您使用Jedi Api Library而不是自己翻译(Nt Api)函数/标题。

编辑:似乎您正在尝试读取ProcessBasicInformation类,该类具有固定的大小,并且不返回指向PEB的指针,而是指向PROCESS_BASIC_INFORMATION结构/记录的指针。

以下是您要执行的操作的示例:

// uses JwaNative

function TDebugThread.ReadPEB: Boolean;
var
  nts: NTSTATUS;
  pbi: PROCESS_BASIC_INFORMATION;
  dwBytes: DWORD;
begin
  Result := False;
  nts := NtQueryInformationProcess(pi.hProcess,
    ProcessBasicInformation, @pbi, SizeOf(pbi), @dwBytes);

  if nts <> STATUS_SUCCESS then
    Exit;

  New(PEB);
  Result := ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, PEB, SizeOf(PEB^),
    @dwBytes);

end;

在下面的示例中使用NtQuerySystemInformation(完整的示例可以在我的GitHub repository上找到)。

{ TProcessList }
constructor TProcessList.Create(const AOwnsObjects: Boolean = True);
var
  Current: PSystemProcesses;
  SystemProcesses : PSystemProcesses;
  dwSize: DWORD;
  nts: NTSTATUS;
begin
  inherited Create(AOwnsObjects);

  dwSize := 200000;
  SystemProcesses := AllocMem(dwSize);

  nts := NtQuerySystemInformation(SystemProcessesAndThreadsInformation,
      SystemProcesses, dwSize, @dwSize);

  while nts = STATUS_INFO_LENGTH_MISMATCH do
  begin
    ReAllocMem(SystemProcesses, dwSize);
    nts := NtQuerySystemInformation(SystemProcessesAndThreadsInformation,
      SystemProcesses, dwSize, @dwSize);
  end;

  if nts = STATUS_SUCCESS then
  begin
    Current := SystemProcesses;
    while True do
    begin
      Self.Add(TProcess.Create(Current^));
      if Current^.NextEntryDelta = 0 then
        Break;

      Current := PSYSTEM_PROCESSES(DWORD_PTR(Current) + Current^.NextEntryDelta);
    end;
  end;

  FreeMem(SystemProcesses);
end;

编辑:要回答@ScienceAmateur注释,以下测试代码的ReturnLength始终返回0:

uses
  Windows,
  System.SysUtils,
  Rtti,
  JwaNative,
  JwaWinType,
  JwaNtSecApi,
  JwaNtStatus;


function NtStatusErrorMessage(const nts: NTSTATUS): String;
begin
  Result := SysErrorMessage(LsaNtStatusToWinError(nts));
end;

var
  nts: NTSTATUS;
  pic: PROCESS_INFORMATION_CLASS;
  Buffer: Pointer;
  pil: DWORD;
  ReturnLength: DWORD;
begin
  for pic := Low(PROCESS_INFORMATION_CLASS) to High(PROCESS_INFORMATION_CLASS) do
  begin
    Buffer := nil;
    pil := 0;
    ReturnLength := 0;
    nts := NtQueryInformationProcess(GetCurrentProcess,
      ProcessBasicInformation, Buffer, pil, @ReturnLength);
    WriteLn(Format('%s: returned 0x%.8x and ReturnLength: %d', [TRttiEnumerationType.GetName(pic), nts, ReturnLength]));
  end;

  WriteLn('Finished.');

  if DebugHook <> 0 then
    ReadLn;

输出:

ProcessBasicInformation: returned 0xC0000004 and ReturnLength: 0
ProcessQuotaLimits: returned 0xC0000004 and ReturnLength: 0
ProcessIoCounters: returned 0xC0000004 and ReturnLength: 0
ProcessVmCounters: returned 0xC0000004 and ReturnLength: 0
ProcessTimes: returned 0xC0000004 and ReturnLength: 0
ProcessBasePriority: returned 0xC0000004 and ReturnLength: 0
ProcessRaisePriority: returned 0xC0000004 and ReturnLength: 0
ProcessDebugPort: returned 0xC0000004 and ReturnLength: 0
ProcessExceptionPort: returned 0xC0000004 and ReturnLength: 0
ProcessAccessToken: returned 0xC0000004 and ReturnLength: 0
ProcessLdtInformation: returned 0xC0000004 and ReturnLength: 0
ProcessLdtSize: returned 0xC0000004 and ReturnLength: 0
ProcessDefaultHardErrorMode: returned 0xC0000004 and ReturnLength: 0
ProcessIoPortHandlers: returned 0xC0000004 and ReturnLength: 0
ProcessPooledUsageAndLimits: returned 0xC0000004 and ReturnLength: 0
ProcessWorkingSetWatch: returned 0xC0000004 and ReturnLength: 0
ProcessUserModeIOPL: returned 0xC0000004 and ReturnLength: 0
ProcessEnableAlignmentFaultFixup: returned 0xC0000004 and ReturnLength: 0
ProcessPriorityClass: returned 0xC0000004 and ReturnLength: 0
ProcessWx86Information: returned 0xC0000004 and ReturnLength: 0
ProcessHandleCount: returned 0xC0000004 and ReturnLength: 0
ProcessAffinityMask: returned 0xC0000004 and ReturnLength: 0
ProcessPriorityBoost: returned 0xC0000004 and ReturnLength: 0
ProcessDeviceMap: returned 0xC0000004 and ReturnLength: 0
ProcessSessionInformation: returned 0xC0000004 and ReturnLength: 0
ProcessForegroundInformation: returned 0xC0000004 and ReturnLength: 0
ProcessWow64Information: returned 0xC0000004 and ReturnLength: 0
ProcessImageFileName: returned 0xC0000004 and ReturnLength: 0
ProcessLUIDDeviceMapsEnabled: returned 0xC0000004 and ReturnLength: 0
ProcessBreakOnTermination: returned 0xC0000004 and ReturnLength: 0
ProcessDebugObjectHandle: returned 0xC0000004 and ReturnLength: 0
ProcessDebugFlags: returned 0xC0000004 and ReturnLength: 0
ProcessHandleTracing: returned 0xC0000004 and ReturnLength: 0
MaxProcessInfoClass: returned 0xC0000004 and ReturnLength: 0

答案 1 :(得分:0)

我对此功能有类似的问题。问题在于大小取决于位数,因为您需要提供指针的大小。

这些是函数的参数:

NTSTATUS(WINAPI*)(
    _In_ HANDLE ProcessHandle,
    _In_ PROCESSINFOCLASS ProcessInformationClass,
    _Out_ PVOID ProcessInformation,
    _In_ ULONG ProcessInformationLength,
    _Out_opt_ PULONG ReturnLength);

可以使用多个不同的PROCESSINFOCLASS调用NtQueryInformationProcess函数。然后,这确定ProcessInformation将是哪种类型。 它可以指向DWORD,例如,如果您使用ProcessDebugPort调用它,或者指向PROCESS_BASIC_INFORMATION,如果您使用ProcessBasicInformation调用它。

如果在调用函数时提供sizeof(DWORD),则在32位环境下您的应用将可以运行。但是在64位中则不会。

这是因为在unsigned long的32位DWORD中,其大小与unsigned long ptr相同,但是在64位中,指针是64位。

因此,在这种情况下,您真正​​需要的大小是指针的大小,而不是类型,也不是您在调用中引用的变量。

基本上,此方法适用于所有地方:

DWORD debuggerPortNumber = 0;
NTSTATUS status = NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &debuggerPortNumber, sizeof(PDWORD), nullptr);

对于64位应用程序,这会中断:

DWORD debuggerPortNumber = 0;
NTSTATUS status = NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &debuggerPortNumber, sizeof(DWORD), nullptr);