As you may know, you can enumerate processes of a specific Terminal Server or Citrix session using the NtQuerySystemInformation function.
On x86 system the code below works fine:
var
Status : NTSTATUS;
RetLength : DWORD;
SessionInfo : SYSTEM_SESSION_PROCESS_INFORMATION;
CurrentProcess : PSystemProcesses;
begin
SessionInfo.SessionId := SessionId;
SessionInfo.Buffer := nil;
SessionInfo.SizeOfBuf := 0;
Status := NtQuerySystemInformation(SystemSessionProcessesInformation, @SessionInfo, SizeOf(SessionInfo), @RetLength);
while (Status = STATUS_INFO_LENGTH_MISMATCH) do
begin
SessionInfo.SizeOfBuf := RetLength;
SessionInfo.Buffer := GetMemory(RetLength);
try
Status := NtQuerySystemInformation(SystemSessionProcessesInformation, @SessionInfo, SizeOf(SessionInfo), @RetLength);
if NT_SUCCESS(Status) then
begin
CurrentProcess := SessionInfo.Buffer;
while True do
begin
// Your code here
if (CurrentProcess^.NextEntryDelta = 0) then
Break;
Cardinal(CurrentProcess) := Cardinal(CurrentProcess) + CurrentProcess^.NextEntryDelta;
end;
Exit;
end;
finally
FreeMemory(SessionInfo.Buffer);
end;
end;
SetLastError(RtlNtStatusToDosError(Status));
RaiseLastOSError;
end;While this works fine on Windows XP and 2003 x86, it fails to work correctly on the x64 versions of Windows XP and 2003 (or maybe even higher).
The problem is that RetLength is always SizeOf(SYSTEM_SESSION_PROCESS_INFORMATION) and thus we are in an endless loop!
If you disassemble an a call to NtQuerySystemInformation, for example in the CreateToolhelp32Snapshot API, you will see that it just executes NtQuerySystemInformation and increases the buffer size in a loop.
Ok, let’s do it in the same way :-)
var
Status : NTSTATUS;
SessionInfo : SYSTEM_SESSION_PROCESS_INFORMATION;
CurrentProcess : PSystemProcesses;
begin
SessionInfo.SessionId := SessionId;
SessionInfo.Buffer := nil;
SessionInfo.SizeOfBuf := 0;
Status := STATUS_INFO_LENGTH_MISMATCH;
while (Status = STATUS_INFO_LENGTH_MISMATCH) do
begin
SessionInfo.SizeOfBuf := SessionInfo.SizeOfBuf + MEMORY_DELTA_SIZE;
SessionInfo.Buffer := GetMemory(SessionInfo.SizeOfBuf);
try
Status := NtQuerySystemInformation(SystemSessionProcessesInformation, @SessionInfo, SizeOf(SessionInfo), nil);
if NT_SUCCESS(Status) then
begin
CurrentProcess := SessionInfo.Buffer;
while True do
begin
// Your code here
if (CurrentProcess^.NextEntryDelta = 0) then
Exit;
Cardinal(CurrentProcess) := Cardinal(CurrentProcess) + CurrentProcess^.NextEntryDelta;
end;
Exit;
end;
finally
FreeMemory(SessionInfo.Buffer);
end;
end;
SetLastError(RtlNtStatusToDosError(Status));
RaiseLastOSError;
end;But this code doesn’t work as well. The reason is that you need to place the whole buffer in a contiguous memory area.
So finally the working code is :
var
Status : NTSTATUS;
SessionInfo : PSYSTEM_SESSION_PROCESS_INFORMATION;
CurrentProcess : PSystemProcesses;
Size : DWORD;
begin
Size := 0;
Status := STATUS_INFO_LENGTH_MISMATCH;
while (Status = STATUS_INFO_LENGTH_MISMATCH) do
begin
Size := Size + MEMORY_DELTA_SIZE;
SessionInfo := GetMemory(Size);
try
SessionInfo^.SessionId := SessionId;
SessionInfo^.SizeOfBuf := Size - SizeOf(SessionInfo^);
Cardinal(SessionInfo^.Buffer) := Cardinal(@SessionInfo^.Buffer) + SizeOf(SessionInfo^.Buffer);
Status := NtQuerySystemInformation(SystemSessionProcessesInformation, SessionInfo, Size, nil);
if NT_SUCCESS(Status) then
begin
CurrentProcess := SessionInfo^.Buffer;
while True do
begin
// Your core here
if (CurrentProcess^.NextEntryDelta = 0) then
Exit;
Cardinal(CurrentProcess) := Cardinal(CurrentProcess) + CurrentProcess^.NextEntryDelta;
end;
Exit;
end;
finally
FreeMemory(SessionInfo);
end;
end;
SetLastError(RtlNtStatusToDosError(Status));
RaiseLastOSError;
end;
[...] 13) Getting a session process list on x64 may fail if you do not allocate buffers correctly – described here [...]