Process Hunter by Ms-Rem

Post date: Nov 20, 2010 11:53:24 PM

The attached Delphi source code is a detector for hidden processes. It has several listing methods to detect processes, making use of native apis and kernel mode drivers (The screenshot details complete available options). The article shows how the author of Process Hunter, Ms-Rem, created the application, describing the concepts used in detail alongside code executing those concepts.

Full source code of Process Hunter is attached at the bottom of the page.

Compiled: Delphi 2007

Operating System: Windows 2000 & Windows XP

Detection of hidden processes.

Article written by Ms-Rem and translated by kao from Russian to English

Source: www.wasm.ru/article.php?article=hiddndt

Many users have got used that Windows NT Task Manager shows all processes, and many consider that it is impossible to hide a process from Task Manager. Actually, process hiding is incredibly simple. There are lots of methods available for such a purpose and there are source codes available. It still amazes me that there are only a few trojans using these methods. Literally only 1 trojan from a 1000 is hidden. I think that trojan authors are lazy, since it requires extra work to hide the process and it is always easier to use ready-made sources and copy-paste them. Therefore we should expect hidden trojan processes in a near future.

Naturally, it is necessary to have protection against hidden processes. Manufacturers of antiviruses and firewals lagging behind as their products are not able to find hidden processes. For this purpose there are only a few utilities from which free-of-charge is only Klister (works only on Windows 2000). All other companies demand considerable money (kao - not true. BlackLight Beta from FSecure is free.). Furthermore, all these utilities can be easily avoided.

All programs available now, use one method for hidden process detection, therefore we have 2 choices:

* think up a method of concealment from a certain principle of detection,

* think up a method of concealment from a certain program, which is much easier.

The user who bought the commercial program cannot change it, and therefore the binding to the concrete program works reliably enough. Therefore the latter method is used in commercial rootkits (for example hxdef Golden edition). The only solution is creation free-of-charge Opensource program for detection of hidden processes. Such program shall employ several detection methods, and therefore would defeat programs concealed from one certain method. Each user can protect against binding to a certain program, it is necessary to take source codes of the program and to alter them to his own liking.

In this article I will discuss the basic methods of detection of hidden processes, show examples of a code using these methods, and to create working program for detection of hidden processes which would meet all above-stated requirements.

Detection in User Mode (ring3)

For the beginning we shall consider simple methods of detection which can be applied in 3 ring, without use of drivers. They are based on fact, that each process generates certain traces of the activity. Based on these traces we can detect hidden process. Such traces include handles opened by process, windows and created system objects. It is simple to avoid from such detection techniques, but for this purpose it is necessary to consider ALL traces left by process. Such stealth mode is not realized in any of public rootkits (unfortunately private versions are not available to me). Usermode methods are simple in implementation, safe to use, and can give a positive effect, therefore their usage should not be neglected.

For the beginning we shall be define data returned by search function, let it be linked lists:

type
PProcList = ^TProcList;
TProcList = packed record
   NextItem: pointer;
   ProcName: array [0..MAX_PATH] of Char;
   ProcId: dword;
   ParrentId: dword;
end;

Acquiring the list of processes by using ToolHelp API

We shall define reference function receiving the list of processes, we shall compare these results to results obtained by all other means:

{
Acquiring list of processes by using ToolHelp API.
}
procedure GetToolHelpProcessList(var List: PListStruct);
var
Snap: dword;
Process: TPROCESSENTRY32;
NewItem: PProcessRecord;
begin
  Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if Snap <> INVALID_HANDLE_VALUE then
     begin
      Process.dwSize := SizeOf(TPROCESSENTRY32);
      if Process32First(Snap, Process) then
         repeat
          GetMem(NewItem, SizeOf(TProcessRecord));
          ZeroMemory(NewItem, SizeOf(TProcessRecord));
          NewItem^.ProcessId  := Process.th32ProcessID;
          NewItem^.ParrentPID := Process.th32ParentProcessID;
          lstrcpy(@NewItem^.ProcessName, Process.szExeFile);
          AddItem(List, NewItem);
         until not Process32Next(Snap, Process);
      CloseHandle(Snap);
     end;
end;

It is obvious that any hidden process will not be found, therefore this function will be used as a reference for hidden process detection.

Acquiring the list of processes by using Native API

The next scanning level will be acquisition a list of processes through ZwQuerySystemInformation (Native API). It is improbable that something will be found out at this level but we should check it anyway.

{
Acquiring list of processes by using ZwQuerySystemInformation.
}
procedure GetNativeProcessList(var List: PListStruct);
var
Info: PSYSTEM_PROCESSES;
NewItem: PProcessRecord;
Mem: pointer;
begin
  Info := GetInfoTable(SystemProcessesAndThreadsInformation);
  Mem := Info;
  if Info = nil then Exit;
  repeat
   GetMem(NewItem, SizeOf(TProcessRecord));
   ZeroMemory(NewItem, SizeOf(TProcessRecord));
   lstrcpy(@NewItem^.ProcessName,
           PChar(WideCharToString(Info^.ProcessName.Buffer)));
   NewItem^.ProcessId  := Info^.ProcessId;
   NewItem^.ParrentPID := Info^.InheritedFromProcessId;
   AddItem(List, NewItem);
   Info := pointer(dword(info) + info^.NextEntryDelta);
  until Info^.NextEntryDelta = 0;
  VirtualFree(Mem, 0, MEM_RELEASE);
end;

Acquiring the list of processes by using list of opened handles.

Many hidden processes do not hide handles opened by them, hence we can construct the list of processes by enumerating opened handles using ZwQuerySystemInformation.

{
  Acquiring the list of processes by using list of opened handles.
  Returns only ProcessId.
}
procedure GetHandlesProcessList(var List: PListStruct);
var
Info: PSYSTEM_HANDLE_INFORMATION_EX;
NewItem: PProcessRecord;
r: dword;
OldPid: dword;
begin
  OldPid := 0;
  Info := GetInfoTable(SystemHandleInformation);
  if Info = nil then Exit;
  for r := 0 to Info^.NumberOfHandles do
    if Info^.Information[r].ProcessId <> OldPid then
     begin
       OldPid := Info^.Information[r].ProcessId;
       GetMem(NewItem, SizeOf(TProcessRecord));
       ZeroMemory(NewItem, SizeOf(TProcessRecord));
       NewItem^.ProcessId   := OldPid;
       AddItem(List, NewItem);
     end;
  VirtualFree(Info, 0, MEM_RELEASE);
end;

At this stage it is already possible to find out something. But we should not rely on result of such check as hiding handles is as easy as hiding processes, although some persons forget to do it.

Acquiring the list of processes by using list of the windows created by them.

It is possible to construct the list of processes having windows by building a list of all windows registered in the system and calling GetWindowThreadProcessId for each of them.

{
  Acquiring the list of processes by using list of windows.
  Returns only ProcessId.
}
procedure GetWindowsProcessList(var List: PListStruct);
function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall;
var
  ProcId: dword;
  NewItem: PProcessRecord;
begin
  GetWindowThreadProcessId(hwnd, ProcId);
   if not IsPidAdded(PList^, ProcId) then
    begin
     GetMem(NewItem, SizeOf(TProcessRecord));
     ZeroMemory(NewItem, SizeOf(TProcessRecord));
     NewItem^.ProcessId   := ProcId;
     AddItem(PList^, NewItem);
  end;
  Result := true;
end;
begin
EnumWindows(@EnumWindowsProc, dword(@List));
end;

Almost no one is hiding windows, therefore this check also allows to detect some processes, but we should not rely on this check very much.

Acquiring the list of processes by use of a direct system call.

To hide a process in user mode, one usually uses code-injection techniques and intercepts ZwQuerySystemInformation from ntdll.dll in all processes.

Functions in ntdll actually are thunks to corresponding functions in a system kernel, and contain system calls (Int 2Eh in Windows 2000 or sysenter in XP), therefore the most simple and effective way to detect processes hidden from Usermode API, will be the direct use of system calls and not using API.

Function replacing ZwQuerySystemInformation for Windows XP looks like this:

{
ZwQuerySystemInformation for Windows XP.
}
Function XpZwQuerySystemInfoCall(ASystemInformationClass: dword;
                                 ASystemInformation: Pointer;
                                 ASystemInformationLength: dword;
                                 AReturnLength: pdword): dword; stdcall;
asm
pop ebp
mov eax, $AD
call @SystemCall
ret $10
@SystemCall:
mov edx, esp
sysenter
end;

Due to different system call mechanism, for Windows 2000 this code will look differently.

{
  Системный вызов ZwQuerySystemInformation для Windows 2000.
}
Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword;
                                    ASystemInformation: Pointer;
                                    ASystemInformationLength: dword;
                                    AReturnLength: pdword): dword; stdcall;
asm
pop ebp
mov eax, $97
lea edx, [esp + $04]
int $2E
ret $10
end;

Now it is necessary to enumerate processes using above mentioned functions, not ntdll. Here is a code that does it:

{
  Acquiring the list of processes by use of a direct system call 
  ZwQuerySystemInformation.
}
procedure GetSyscallProcessList(var List: PListStruct);
var
Info: PSYSTEM_PROCESSES;
NewItem: PProcessRecord;
mPtr: pointer;
mSize: dword;
St: NTStatus;
begin
mSize := $4000; 
repeat
  GetMem(mPtr, mSize);
  St := ZwQuerySystemInfoCall(SystemProcessesAndThreadsInformation,
                              mPtr, mSize, nil);
  if St = STATUS_INFO_LENGTH_MISMATCH then
    begin 
      FreeMem(mPtr);
      mSize := mSize * 2;
    end;
until St <> STATUS_INFO_LENGTH_MISMATCH;
if St = STATUS_SUCCESS then
  begin
    Info := mPtr;
    repeat
     GetMem(NewItem, SizeOf(TProcessRecord));
     ZeroMemory(NewItem, SizeOf(TProcessRecord));
     lstrcpy(@NewItem^.ProcessName,
             PChar(WideCharToString(Info^.ProcessName.Buffer)));
     NewItem^.ProcessId  := Info^.ProcessId;
     NewItem^.ParrentPID := Info^.InheritedFromProcessId;
     Info := pointer(dword(info) + info^.NextEntryDelta);
     AddItem(List, NewItem);
    until Info^.NextEntryDelta = 0;
  end;
FreeMem(mPtr);
end;

This method detects almost 100% of user mode rootkits, for example all versions of hxdef (including Golden).

Acquiring the list of processes by analyzing handles related to them.

There is another method based on handle enumeration. The essence of method consists not in finding handles opened by process in question but in finding handles in other processes related to this process. It might be handle of the process or handle of the thread. When handle of process is found, we can find process PID using ZwQueryInformationProcess. For a thread it is possible to use ZwQueryInformationThread and obtain Id of process. All processes existing in system have been started by someone, hence parent processes will have handles of them (unless they have closed these handles), also handles of all existing processes are available to a server of Win32 subsystem (csrss.exe). Additionally, Windows NT is using Job objects actively, which allows to unite processes (for example all processes by certain user, or certain services), hence when finding handle of Job object, we should use opportunity to obtain IDs of all processes incorporated in it. It can be done by using function QueryInformationJobObject with a class of the information - JobObjectBasicProcessIdList. The code acquiring the list of processes by analyzing handles related to them looks like this:

{
Acquiring the list of processes by analyzing handles in other processes.
}
procedure GetProcessesFromHandles(var List: PListStruct; Processes, Jobs, Threads: boolean);
var
HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX;
ProcessInfo: PROCESS_BASIC_INFORMATION;
hProcess : dword;
tHandle: dword;
r, l     : integer;
NewItem: PProcessRecord;
Info: PJOBOBJECT_BASIC_PROCESS_ID_LIST;
Size: dword;
THRInfo: THREAD_BASIC_INFORMATION;
begin
HandlesInfo := GetInfoTable(SystemHandleInformation);
if HandlesInfo <> nil then
for r := 0 to HandlesInfo^.NumberOfHandles do
   if HandlesInfo^.Information[r].ObjectTypeNumber in [OB_TYPE_PROCESS, OB_TYPE_JOB, OB_TYPE_THREAD] then
    begin
      hProcess  := OpenProcess(PROCESS_DUP_HANDLE, false,
                               HandlesInfo^.Information[r].ProcessId);
                               
      if DuplicateHandle(hProcess, HandlesInfo^.Information[r].Handle,
                         INVALID_HANDLE_VALUE, @tHandle, 0, false,
                         DUPLICATE_SAME_ACCESS) then
            begin
             case HandlesInfo^.Information[r].ObjectTypeNumber of
               OB_TYPE_PROCESS : begin
                     if Processes and (HandlesInfo^.Information[r].ProcessId = CsrPid) then
                     if ZwQueryInformationProcess(tHandle, ProcessBasicInformation,
                                            @ProcessInfo,
                                            SizeOf(PROCESS_BASIC_INFORMATION),
                                            nil) = STATUS_SUCCESS then
                     if not IsPidAdded(List, ProcessInfo.UniqueProcessId) then
                        begin
                        GetMem(NewItem, SizeOf(TProcessRecord));
                        ZeroMemory(NewItem, SizeOf(TProcessRecord));
                        NewItem^.ProcessId   := ProcessInfo.UniqueProcessId;
                        NewItem^.ParrentPID  := ProcessInfo.InheritedFromUniqueProcessId;
                        AddItem(List, NewItem);
                        end; 
                     end;
               OB_TYPE_JOB     : begin
                                  if Jobs then
                                   begin
                                    Size := SizeOf(JOBOBJECT_BASIC_PROCESS_ID_LIST) + 4 * 1000;
                                    GetMem(Info, Size);
                                    Info^.NumberOfAssignedProcesses := 1000;
                                    if QueryInformationJobObject(tHandle, JobObjectBasicProcessIdList,
                                                                 Info, Size, nil) then
                                       for l := 0 to Info^.NumberOfProcessIdsInList - 1 do
                                         if not IsPidAdded(List, Info^.ProcessIdList[l]) then
                                           begin
                                            GetMem(NewItem, SizeOf(TProcessRecord));
                                            ZeroMemory(NewItem, SizeOf(TProcessRecord));
                                            NewItem^.ProcessId   := Info^.ProcessIdList[l];
                                            AddItem(List, NewItem);
                                           end;
                                    FreeMem(Info);
                                   end;
                                  end;
               OB_TYPE_THREAD  : begin
                                  if Threads then
                                  if ZwQueryInformationThread(tHandle, THREAD_BASIC_INFO,
                                                              @THRInfo,
                                                              SizeOf(THREAD_BASIC_INFORMATION),
                                                              nil) = STATUS_SUCCESS then
                                    if not IsPidAdded(List, THRInfo.ClientId.UniqueProcess) then
                                     begin
                                       GetMem(NewItem, SizeOf(TProcessRecord));
                                       ZeroMemory(NewItem, SizeOf(TProcessRecord));
                                       NewItem^.ProcessId   := THRInfo.ClientId.UniqueProcess;
                                       AddItem(List, NewItem);
                                     end;
                                 end;
             end;
             CloseHandle(tHandle);
            end;
          CloseHandle(hProcess);
        end;
VirtualFree(HandlesInfo, 0, MEM_RELEASE);
end;

Unfortunately, some of the above-stated methods allow to acquire only ProcessId, but not a name of process. Hence, we need to be able to get a name of process for PID. Of course, we should not use ToolHelp API for this purpose as this process might be hidden. Therefore we shall open process memory for reading and get a name from its PEB. PEB address can be acquired by using ZwQueryInformationProcess function. Here is a code that does all this:

function GetNameByPid(Pid: dword): string;
var
hProcess, Bytes: dword;
Info: PROCESS_BASIC_INFORMATION;
ProcessParametres: pointer;
ImagePath: TUnicodeString;
ImgPath: array[0..MAX_PATH] of WideChar;
begin
Result := '';
ZeroMemory(@ImgPath, MAX_PATH * SizeOf(WideChar));
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, Pid);
if ZwQueryInformationProcess(hProcess, ProcessBasicInformation, @Info,
                              SizeOf(PROCESS_BASIC_INFORMATION), nil) = STATUS_SUCCESS then
  begin
   if ReadProcessMemory(hProcess, pointer(dword(Info.PebBaseAddress) + $10),
                        @ProcessParametres, SizeOf(pointer), Bytes) and
      ReadProcessMemory(hProcess, pointer(dword(ProcessParametres) + $38),
                        @ImagePath, SizeOf(TUnicodeString), Bytes)  and
      ReadProcessMemory(hProcess, ImagePath.Buffer, @ImgPath,
                        ImagePath.Length, Bytes) then
        begin
          Result := ExtractFileName(WideCharToString(ImgPath));
        end;
   end;
CloseHandle(hProcess);
end;

Naturally, usermode detection methods do not end here. It is possible to think up some new methods with a little effort (for example, loading of the Dll in accessible processes by use of SetWindowsHookEx with the subsequent analysis of the list of processes where our Dll has been loaded) but for now we will settle with above mentioned methods. Advantages of these methods are that they are simple in programming, and they allow to detect processes hidden in User Mode, or poorly hidden Kernel Mode processes.. For really reliable detection of hidden processes we should write a driver and work with Windows kernel internal structures.

Kernel Mode detection

Congratulation, we made til kernel-mode hidden process detection. The main difference between these methods and usermode methods is that all lists of processes are created bypassing API calls and directly using scheduler (?) structures. It is much harder to hide from these detection methods,because these methods are based on same principles as Windows kernel and removing process from all scheduler structures will effectively disable this process altogether.

What is the process inside the kernel? Each process has the address space, descriptors, threads, etc. There are structures in the kernel related to these things. Each process is described by EPROCESS structure, structures of all processes are connected in the circular doubly-linked list. One of methods of process hiding consists in changing of pointers so that enumeration skips hidden process. Not being enumerated does not influence process' ability to function. However, EPROCESS structure always should exist, it is required for a process to function. The majority of methods of detection of hidden processes in Kernel Mode are somehow connected with detection of this structure.

Again we shall define format how received process information will be stored. This format should be convenient for data transfer from the driver (in the appendix). Let it will the following structure:

typedef struct _ProcessRecord
{
   ULONG       Visibles;
   ULONG       SignalState;
                BOOLEAN     Present;
   ULONG       ProcessId;
   ULONG       ParrentPID;
   PEPROCESS   pEPROCESS;
   CHAR        ProcessName[256];
} TProcessRecord, *PProcessRecord;

Let these structures occupy contiguous chunk of memory, and in the last structure Present flag will not be set.

Acquiring the list of processes by using ZwQuerySystemInformation in a kernel.

We shall begin as always with the simplest way, acquiring reference list of processes by using ZwQuerySystemInformation:

PVOID GetNativeProcessList(ULONG *MemSize)
{
   ULONG PsCount = 0;
   PVOID Info = GetInfoTable(SystemProcessesAndThreadsInformation);
   PSYSTEM_PROCESSES Proc;
   PVOID Mem = NULL;
   PProcessRecord Data;
   if (!Info) return NULL; else Proc = Info;
   do 
   {
      Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta);   
      PsCount++;
   } while (Proc->NextEntryDelta);
   *MemSize = (PsCount + 1) * sizeof(TProcessRecord);
   Mem = ExAllocatePool(PagedPool, *MemSize);
   if (!Mem) return NULL; else Data = Mem;
    
   Proc = Info;
   do
   {
      Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta);
      wcstombs(Data->ProcessName, Proc->ProcessName.Buffer, 255);
      Data->Present    = TRUE;
      Data->ProcessId  = Proc->ProcessId;
      Data->ParrentPID = Proc->InheritedFromProcessId;
      PsLookupProcessByProcessId((HANDLE)Proc->ProcessId, &Data->pEPROCESS);
      ObDereferenceObject(Data->pEPROCESS);
      Data++;
   } while (Proc->NextEntryDelta);
   Data->Present = FALSE;
   ExFreePool(Info);
   return Mem;
}

Let this function be our reference function as any Kernel Mode hidden processes will not be detected. But all user mode root-kits like hxdef will be detected.

In this code we use function GetInfoTable to obtain information easily. To avoid questions about what is that, here is complete code of function:

/*
  Receiving buffer with results from ZwQuerySystemInformation.
*/
PVOID GetInfoTable(ULONG ATableType)
{
   ULONG mSize = 0x4000;
   PVOID mPtr = NULL;
   NTSTATUS St;
   do
   {
      mPtr = ExAllocatePool(PagedPool, mSize);
      memset(mPtr, 0, mSize);
      if (mPtr) 
      {
         St = ZwQuerySystemInformation(ATableType, mPtr, mSize, NULL); 
      } else return NULL;
      if (St == STATUS_INFO_LENGTH_MISMATCH)
      {
         ExFreePool(mPtr);
         mSize = mSize * 2;
      }
   } while (St == STATUS_INFO_LENGTH_MISMATCH);
   if (St == STATUS_SUCCESS) return mPtr;
   ExFreePool(mPtr);
   return NULL;
}

I think that it will be easy to understand what this function does..

Acquiring list of processes using doubly-linked list of EPROCESS structures.

So, we go further. Following step will be acquiring the list of processes by iterating through the doubly-linked list of EPROCESS structures. The list begins with a header - PsActiveProcessHead, therefore for correct enumeration of processes we need to find this not exported symbol. For this purpose we will use a fact that System process is the first process in the list of processes. While being in DriverEntry we need to obtain pointer to current process by using PsGetCurrentProcess (drivers loaded using SC Manager API or ZwLoadDriver are always loaded in a context of process System), and BLink at offset ActiveProcessLinks will point to PsActiveProcessHead. It looks like this:

PsActiveProcessHead = *(PVOID *)((PUCHAR)PsGetCurrentProcess + ActiveProcessLinksOffset + 4);

Now it is possible to iterate through doubly-linked list and create the list of processes:

PVOID GetEprocessProcessList(ULONG *MemSize)
{
   PLIST_ENTRY Process;
   ULONG PsCount = 0;
   PVOID Mem = NULL;
   PProcessRecord Data;
   if (!PsActiveProcessHead) return NULL;
   Process = PsActiveProcessHead->Flink;
   while (Process != PsActiveProcessHead)
   {
      PsCount++;
      Process = Process->Flink;
   }
   PsCount++;
   *MemSize = PsCount * sizeof(TProcessRecord);
   Mem = ExAllocatePool(PagedPool, *MemSize);
   memset(Mem, 0, *MemSize);
   if (!Mem) return NULL; else Data = Mem;
   Process = PsActiveProcessHead->Flink;
   while (Process != PsActiveProcessHead)
   {
      Data->Present     = TRUE;
      Data->ProcessId   = *(PULONG)((ULONG)Process - ActPsLink + pIdOffset);
      Data->ParrentPID  = *(PULONG)((ULONG)Process - ActPsLink + ppIdOffset);
      Data->SignalState = *(PULONG)((ULONG)Process - ActPsLink + 4);
      Data->pEPROCESS   = (PEPROCESS)((ULONG)Process - ActPsLink);
      strncpy(Data->ProcessName, (PVOID)((ULONG)Process - ActPsLink + NameOffset), 16);      
      Data++;
       Process = Process->Flink;
   
   }
   return Mem;
}

To obtain a name of process, its Process Id and ParrentProcessId, we use offsets of those fields in structure EPROCESS (pIdOffset, ppIdOffset, NameOffset, ActPsLink). These offsets differ in various versions Windows, therefore their acquisition is done in separate function which you can see in a source code of program Process Hunter (in the appendix).

Any concealment of process by API interception method, will be revealed in the above-stated way. But if process is hidden by means of DKOM (Direct Kernel Object Manipulation)method, this way will not help because such processes are removed from the list of processes.

Acquiring the list of processes using lists of threads in the scheduler.

One of methods of detection of such concealment (kao - the original text is ambiguous. Author probably meant "detection of processes hidden using DKOM method") consists in obtaining the list of processes from list of threads in the scheduler. There are three doubly-linked lists of threads (KiWaitInListHead, KiWaitOutListHead, KiDispatcherReadyListHead) available in Windows 2000. Former two lists contain threads waiting for certain events, and the latter contains threads ready for execution. When processing these lists and substracting offset of the thread list in structure ETHREAD, we will obtain pointer to ETHREAD of a thread (kao - this sentence is very hard to understand in original. I hope I got it right). This structure contains several pointers to the process related to this stream, namely - struct _KPROCESS *Process (0x44, 0x150) and struct _EPROCESS *ThreadsProcess (0x22C, offsets are specified for Windows 2000). First two pointers do not have any influence on functionality of a thread, and therefore can be easily modified to hide a process. On the contrary, third pointer is used by the scheduler when switching address spaces, therefore it cannot be changed. We shall use it to recognize which process owns a thread.

This detection method is used in the program klister, whose main disadvantage is that it works only under Windows 2000 (it fails to work with certain Service Packs, though). Such problem is caused by fact that Klister uses hardcoded addresses of thread lists, but these addresses are different in all Service Packs.

Hardcoding addresses in the program is poor solution as it makes software useless when new OS updates are released and makes it possible to avoid this detection method. Therefore it is necessary to search for list addresses dynamically, by analysing code of kernel functions in which these lists are used.

For the beginning we shall try to find KiWaitItListHead and KiWaitOutListHead in Windows 2000. Addresses of these lists are used in function KeWaitForSingleObject in the following code:

.text:0042DE56                 mov     ecx, offset KiWaitInListHead
.text:0042DE5B                 test    al, al
.text:0042DE5D                 jz      short loc_42DE6E
.text:0042DE5F                 cmp     byte ptr [esi+135h], 0
.text:0042DE66                 jz      short loc_42DE6E
.text:0042DE68                 cmp     byte ptr [esi+33h], 19h
.text:0042DE6C                 jl      short loc_42DE73
.text:0042DE6E                 mov     ecx, offset KiWaitOutListHead

To obtain these addresses we should use length-disassembler (we shall use LDasm written by me) on KeWaitForSingleObject function. When the index (pOpcode) will point to a command "mov ecx, KiWaitInListHead", (pOpcode + 5) will point to "test al, al", and (pOpcode + 24) - to "mov ecx, KiWaitOutListHead". After that we can obtain KiWaitItListHead and KiWaitOutListHead addresses from (pOpcode + 1) and (pOpcode + 25) accordingly. The code which searches for these addresses looks like this:

void Win2KGetKiWaitInOutListHeads()
{
   PUCHAR cPtr, pOpcode;
   ULONG Length;
   
   for (cPtr = (PUCHAR)KeWaitForSingleObject; 
        cPtr < (PUCHAR)KeWaitForSingleObject + PAGE_SIZE; 
             cPtr += Length) 
   {
      Length = SizeOfCode(cPtr, &pOpcode);
      if (!Length) break;
      
      if (*pOpcode == 0xB9 && *(pOpcode + 5) == 0x84 && *(pOpcode + 24) == 0xB9)
      {
         KiWaitInListHead  = *(PLIST_ENTRY *)(pOpcode + 1);
         KiWaitOutListHead = *(PLIST_ENTRY *)(pOpcode + 25);
         break;
      }
   }
   return;
}

KiDispatcherReadyListHead in Windows 2000 is found in a similar fashion, by searching function KeSetAffinityThread for the following code:

.text:0042FAAA                 lea     eax, KiDispatcherReadyListHead[ecx*8]
.text:0042FAB1                 cmp     [eax], eax

Here is function that will search for KiDispatcherReadyListHead:

void Win2KGetKiDispatcherReadyListHead()
{
   PUCHAR cPtr, pOpcode;
   ULONG Length;
   
   for (cPtr = (PUCHAR)KeSetAffinityThread; 
        cPtr < (PUCHAR)KeSetAffinityThread + PAGE_SIZE; 
             cPtr += Length) 
   {
      Length = SizeOfCode(cPtr, &pOpcode);
      if (!Length) break;      
      if (*(PUSHORT)pOpcode == 0x048D && *(pOpcode + 2) == 0xCD && *(pOpcode + 7) == 0x39)
      {
         KiDispatcherReadyListHead = *(PVOID *)(pOpcode + 3);
         break;
      }
   }
   return;
}

Unfortunately, Windows XP kernel differs very much from Windows 2000 kernel. The scheduler in XP does not have three, but only two lists of threads: KiWaitListHead and KiDispatcherReadyListHead. It is possible to find KiWaitListHead by scanning function KeDelayExecutionThread for the following code:

.text:004055B5                 mov     dword ptr [ebx], offset KiWaitListHead
.text:004055BB                 mov     [ebx+4], eax

Such search is carried out by a following code:

void XPGetKiWaitListHead()
{
   PUCHAR cPtr, pOpcode;
   ULONG Length;
   for (cPtr = (PUCHAR)KeDelayExecutionThread; 
        cPtr < (PUCHAR)KeDelayExecutionThread + PAGE_SIZE; 
             cPtr += Length)
   {
      Length = SizeOfCode(cPtr, &pOpcode);
      if (!Length) break;
      if (*(PUSHORT)cPtr == 0x03C7 && *(PUSHORT)(pOpcode + 6) == 0x4389) 
      {
         KiWaitInListHead = *(PLIST_ENTRY *)(pOpcode + 2);
         break;
      }
   }
   return;
}

The most difficult problem is to find KiDispatcherReadyListHead. A problem lies infact that KiDispatcherReadyListHead address is not present in any of exported functions. Therefore it is necessary to use more complicated search algorithm for its calculation. We shall start our search from function KiDispatchInterrupt and there is only one place of interest in it containing a following code:

.text:00404E72                 mov     byte ptr [edi+50h], 1
.text:00404E76                 call    sub_404C5A
.text:00404E7B                 mov     cl, 1
.text:00404E7D                 call    sub_404EB9

The First call in this code points to function in which there is a reference to KiDispatcherReadyListHead. However, search for the KiDispatcherReadyListHead address becomes more complicated because the relevant code in this function is different in Windows XP SP1 and SP2. In SP2 it looks like this:

.text:00404CCD                 add     eax, 60h
.text:00404CD0                 test    bl, bl
.text:00404CD2                 lea     edx, KiDispatcherReadyListHead[ecx*8]
.text:00404CD9                 jnz     loc_401F12
.text:00404CDF                 mov     esi, [edx+4]

And in SP1:

.text:004180FE                 add     eax, 60h
.text:00418101                 cmp     [ebp+var_1], bl
.text:00418104                 lea     edx, KiDispatcherReadyListHead[ecx*8]
.text:0041810B                 jz      loc_418760
.text:00418111                 mov     esi, [edx]

Search for only one instruction "lea" is unreliable, therefore we shall check also that after "lea" command there is a command with rel32 displacement (function IsRelativeCmd in LDasm). The full code that searches for KiDispatcherReadyListHead will look like this:

void XPGetKiDispatcherReadyListHead()
{
   PUCHAR cPtr, pOpcode;
   PUCHAR CallAddr = NULL;
   ULONG Length;
   for (cPtr = (PUCHAR)KiDispatchInterrupt; 
        cPtr < (PUCHAR)KiDispatchInterrupt + PAGE_SIZE; 
             cPtr += Length)
   {
      Length = SizeOfCode(cPtr, &pOpcode);
      if (!Length) return;
      if (*pOpcode == 0xE8 && *(PUSHORT)(pOpcode + 5) == 0x01B1) 
      {
         CallAddr = (PUCHAR)(*(PULONG)(pOpcode + 1) + (ULONG)cPtr + Length);
         break;
      }
   }
   if (!CallAddr || !MmIsAddressValid(CallAddr)) return;
   for (cPtr = CallAddr; cPtr < CallAddr + PAGE_SIZE; cPtr += Length)
   {
      Length = SizeOfCode(cPtr, &pOpcode);
      if (!Length) return;
      if (*(PUSHORT)pOpcode == 0x148D && *(pOpcode + 2) == 0xCD && IsRelativeCmd(pOpcode + 7))
      {
         KiDispatcherReadyListHead = *(PLIST_ENTRY *)(pOpcode + 3);
         break;
      }
   }
   return;
}

After a finding address of thread list, we can easily enumerate their processes by using the following function:

void ProcessListHead(PLIST_ENTRY ListHead)
{
   PLIST_ENTRY Item;
   if (ListHead)
   {
      Item = ListHead->Flink;
      while (Item != ListHead)
      {
         CollectProcess(*(PEPROCESS *)((ULONG)Item + WaitProcOffset));
         Item = Item->Flink;
      }
   }
   return;
}

CollectProcess is a utility function that adds process to the list, if it is not there yet.

Acquiring the list of processes by intercepting system calls.

Any working process cooperates with system through API, and the majority of these inquiries turn into references to a kernel through the interface of system calls. Certainly, process can exist without using any API calls, but then it cannot carry out any useful (or harmful) deed. In general, the idea consists in intercepting system calls by using our handler, and obtaining pointer to EPROCESS of current process in our handler. The list of pointers shall be collected during collect certain period of time, and it will not include processes that did not carry out any system calls while this information was gathered (for example, processes whose threads are in a waiting state).

Interrupt 2Eh is used to make a system call in windows 2000, therefore we need to change a descriptor of corresponding interrupt in idt to intercept system calls. For this purpose we need to detect location of idt in memory by using sidt command. This command returns following structure:

typedef struct _Idt
{
   USHORT Size;
   ULONG  Base;
} TIdt;

The code for changing interrupt vector 2Eh will look like this:

void Set2kSyscallHook()
{
   TIdt Idt;
   __asm
   {
      pushad
      cli
      sidt [Idt]
      mov esi, NewSyscall
      mov ebx, Idt.Base
      xchg [ebx + 0x170], si
      rol esi, 0x10
      xchg [ebx + 0x176], si
      ror esi, 0x10
      mov OldSyscall, esi
      sti
      popad
   }
}

Of course it is necessary to restore everything before unloading the driver:

void Win2kSyscallUnhook()
{
   TIdt Idt;
   __asm
   {
      pushad
      cli
      sidt [Idt]
      mov esi, OldSyscall
      mov ebx, Idt.Base
      mov [ebx + 0x170], si
      rol esi, 0x10
      mov [ebx + 0x176], si
      sti
      xor eax, eax
      mov OldSyscall, eax
      popad
   }
}

Windows XP uses commands sysenter/sysexit (which have appeared in processors Pentium 2) to perform system calls. Functionality of these commands is controlled by model-specific registers (MSR). Address of a system call handler is set in MSR register SYSENTER_EIP_MSR (number 0x176). Reading MSR of the register is carried out by rdmsr command, and we need to set ECX = number of the register to be read, and the result is located in pair of registers EDX:EAX. In our case, SYSENTER_EIP_MSR register is 32 bit register, therefore EDX will be 0, and EAX will contain address of system call handler. Similarly, we can write into MSR register by using wrmsr instruction. There is one thing to note: when writing into 32-bit MSR register, EDX should be nulled, otherwise it will cause exceptions and will lead to immediate crash of system.

By taking all this into account, the code replacing system call handler will look like this:

void SetXpSyscallHook()
{
   __asm
   {
      pushad
      mov ecx, 0x176
      rdmsr
      mov OldSyscall, eax
      mov eax, NewSyscall
      xor edx, edx
      wrmsr
      popad
   }
}

And code restoring old system call handler looks like this:

void XpSyscallUnhook()
{
   __asm
   {
      pushad
      mov ecx, 0x176
      mov eax, OldSyscall
      xor edx, edx
      wrmsr
      xor eax, eax
      mov OldSyscall, eax
      popad
   }
}

One more Windows XP feature is that system call can be made both through sysenter, and through int 2Eh, therefore we need to replace both handlers with ours.

Our new system call handler should obtain pointer to EPROCESS of the current process and if it is new process, it should add this process into our list.

Accordingly, new system call handler will look like this:

void __declspec(naked) NewSyscall()
{
   __asm
   {
      pushad
      pushfd
      push fs
      mov di, 0x30
      mov fs, di
      mov eax, fs:[0x124]
      mov eax, [eax + 0x44]
      push eax
      call CollectProcess
      pop fs
      popfd
      popad
      jmp OldSyscall
   }
}

To obtain full list of processes this code should work for some period of time and therefore we have the following problem: if process contained in the list will be removed, at the subsequent iteration through the list we will obtain invalid pointer, as a result we will either falsely detect a hidden process or will generate BSOD. The solution to this situation is - using PsSetCreateProcessNotifyRoutine to register our Callback function which will be called when creating or terminating a process. When process is being terminated, it should be deleted from our list. Callback function has the following prototype:

VOID
(*PCREATE_PROCESS_NOTIFY_ROUTINE) (
    IN HANDLE  ParentId,
    IN HANDLE  ProcessId,
    IN BOOLEAN  Create
    );

Installation of callback routine is performed like this:

PsSetCreateProcessNotifyRoutine (NotifyRoutine, FALSE);

And removal like this:

PsSetCreateProcessNotifyRoutine (NotifyRoutine, TRUE);

Here is one problem, Callback function is always called in a context of process to be terminated, hence it is impossible to delete process from lists directly in this callback function. For this purpose we shall use system work items, first we shall allocate memory for a work item by using IoAllocateWorkItem, and then we shall place the task in queue of a working thread by using of IoQueueWorkItem (kao - not sure about this sentence.. ). In handler procedure we will not only delete terminated processes from the list but also will add freshly created processes. Here is a code for handler:

void WorkItemProc(PDEVICE_OBJECT DeviceObject, PWorkItemStruct Data)
{
   KeWaitForSingleObject(Data->pEPROCESS, Executive, KernelMode, FALSE, NULL);
   DelItem(&wLastItem, Data->pEPROCESS);
   ObDereferenceObject(Data->pEPROCESS);
   IoFreeWorkItem(Data->IoWorkItem);
   ExFreePool(Data);
   return;
}
void NotifyRoutine(IN HANDLE  ParentId,
                   IN HANDLE  ProcessId,
                   IN BOOLEAN Create)
{
   PEPROCESS       process; 
   PWorkItemStruct Data;
   if (Create) 
   {
      PsLookupProcessByProcessId(ProcessId, &process);
      if (!IsAdded(wLastItem, process)) AddItem(&wLastItem, process);
      ObDereferenceObject(process);
   } else
   {
      process = PsGetCurrentProcess();
        
      ObReferenceObject(process);
      Data = ExAllocatePool(NonPagedPool, sizeof(TWorkItemStruct));
      Data->IoWorkItem = IoAllocateWorkItem(deviceObject);
      
      Data->pEPROCESS  = process;
      IoQueueWorkItem(Data->IoWorkItem, WorkItemProc, DelayedWorkQueue, Data);
   }
   return;
}

This is rather reliable way of hidden process detection, as no process can manage without system calls, but some processes can be in a waiting state for a long time and will not carry out system calls during long period of time. Such processes will not be detected.

If desired, it is easy to bypass this method of detection. For this purpose it is necessary to change a method of performing system calls in hidden processes (redirect to another interrupt or callgate in GDT). It is especially easy to do so in Windows XP as it is enough to patch KiFastSystemCall in ntdll.dll and to create a corresponding gate for a system call. In Windows 2000 it is a little bit more difficult, as calls to int 2E are scattered throughout ntdll, but it is not so complex to find and patch all these places too, therefore it is not smart to rely on results of this check.

Acquiring the list of processes by iterating through handle table list.

If you have ever tried to hide process by removing it from list of PsActiveProcesses, perhaps you have noticed that when you enumerate handles by using ZwQuerySystemInformation, handles of hidden process are also enumerated and it is possible to detect their ProcessId. It is because for convenience of handle enumaration, all handle tables are incorporated in doubly-linked list HandleTableList. Offset of this list in HANDLE_TABLE structure for Windows 2000 is equal 0x054, and for Windows XP - 0x01C, this list with begins HandleTableListHead. HANDLE_TABLE structure contains pointer to process owning it (QuotaProcess), offset of this pointer in Windows 2000 is equal 0x00C, and in Windows XP - 0x004. By iterating through the list of handle tables we can build process list.

First we need to find HandleTableListHead. Kernel disassembly has shown that references to it are located deeply in the functions, therefore code disassembly method which we used earlier, cannot be used here. To find HandleTableListHead it is possible to use fact that HandleTableListHead is a global kernel variable and therefore it is located in one of kernel file sections and all other elements of HandleTableList are located in dynamically allocated memory and consequently always will be behind kernel address space limits. From this follows, that we need to acquire pointer to HandleTable of any process, and to iterate through linked-list until we find an element that is located in the kernel address space. This element will be HandleTableListHead.

To calculate imagebase and size of system kernel we will use ZwQuerySystemInformation with class SystemModuleInformation. It will return a table of descriptors for all loaded modules and first element always is "system". Taking into account all aforementioned, code that searches for HandleTableListHead looks like this:

void GetHandleTableListHead()
{
   PSYSTEM_MODULE_INFORMATION_EX Info = GetInfoTable(SystemModuleInformation);
   ULONG NtoskrnlBase = (ULONG)Info->Modules[0].Base;
   ULONG NtoskrnlSize = Info->Modules[0].Size;
   PHANDLE_TABLE HandleTable = *(PHANDLE_TABLE *)((ULONG)PsGetCurrentProcess() + HandleTableOffset);
   PLIST_ENTRY HandleTableList = (PLIST_ENTRY)((ULONG)HandleTable + HandleTableListOffset);
   PLIST_ENTRY CurrTable;
   ExFreePool(Info);
   for (CurrTable = HandleTableList->Flink; 
        CurrTable != HandleTableList; 
        CurrTable = CurrTable->Flink)
   {
      if ((ULONG)CurrTable > NtoskrnlBase && (ULONG)CurrTable < NtoskrnlBase + NtoskrnlSize) 
      {
         HandleTableListHead = CurrTable;
         break;
      }
   }   
}

This code is rather universal as it works in any Windows NT version, and can be applied not only to search for HandleTableListHead, but also for any other lists having similar structure.

After acquiring address of HandleTableListHead we can traverse through handle tables and construct list of processes based on this information:

void ScanHandleTablesList()
{
   PLIST_ENTRY CurrTable;
   PEPROCESS QuotaProcess;
   for (CurrTable =  HandleTableListHead->Flink;
        CurrTable != HandleTableListHead;
        CurrTable =  CurrTable->Flink)
   {
      QuotaProcess = *(PEPROCESS *)((PUCHAR)CurrTable - HandleTableListOffset + QuotaProcessOffset);
      if (QuotaProcess) CollectProcess(QuotaProcess);
   }
}

This method of detection is used in F-Secure Black Light and in the last version KProcCheck. I think you will easily find a way to fool this detection method.

Acquiring list of processes by scanning PspCidTable.

There is one interesting thing to note - if process is removed from PsActiveProcesses list, it does not prevent opening process by using a call to OpenProcess. There is a method of hidden process detection that tries to detect processes by bruteforcing Pid and calling OpenProcess. I am not present this method, as it does not have any advantages, I would even say that it is "through-the-ass" method. But the fact of its existence means that in system there is one more list of processes besides PsActiveProcesses which is used by OpenProcess. When bruteforcing ProcessId we will certainly note that one process can be opened using a several different Pid, and it suggests that the second list of processes is something like HANDLE_TABLE. To verify this guess, we shall glance in function ZwOpenProcess:

PAGE:0049D59E ; NTSTATUS __stdcall NtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, 
                                                 POBJECT_ATTRIBUTES ObjectAttributes,PCLIENT_ID ClientId)
PAGE:0049D59E                 public NtOpenProcess
PAGE:0049D59E NtOpenProcess   proc near              
PAGE:0049D59E
PAGE:0049D59E ProcessHandle   = dword ptr  4
PAGE:0049D59E DesiredAccess   = dword ptr  8
PAGE:0049D59E ObjectAttributes= dword ptr  0Ch
PAGE:0049D59E ClientId        = dword ptr  10h
PAGE:0049D59E
PAGE:0049D59E                 push    0C4h
PAGE:0049D5A3                 push    offset dword_413560 ; int
PAGE:0049D5A8                 call    sub_40BA92
PAGE:0049D5AD                 xor     esi, esi
PAGE:0049D5AF                 mov     [ebp-2Ch], esi
PAGE:0049D5B2                 xor     eax, eax
PAGE:0049D5B4                 lea     edi, [ebp-28h]
PAGE:0049D5B7                 stosd
PAGE:0049D5B8                 mov     eax, large fs:124h
PAGE:0049D5BE                 mov     al, [eax+140h]
PAGE:0049D5C4                 mov     [ebp-34h], al
PAGE:0049D5C7                 test    al, al
PAGE:0049D5C9                 jz      loc_4BE034
PAGE:0049D5CF                 mov     [ebp-4], esi
PAGE:0049D5D2                 mov     eax, MmUserProbeAddress
PAGE:0049D5D7                 mov     ecx, [ebp+8]
PAGE:0049D5DA                 cmp     ecx, eax
PAGE:0049D5DC                 jnb     loc_520CDE
PAGE:0049D5E2 loc_49D5E2:                             
PAGE:0049D5E2                 mov     eax, [ecx]
PAGE:0049D5E4                 mov     [ecx], eax
PAGE:0049D5E6                 mov     ebx, [ebp+10h]
PAGE:0049D5E9                 test    bl, 3
PAGE:0049D5EC                 jnz     loc_520CE5
PAGE:0049D5F2 loc_49D5F2:                           
PAGE:0049D5F2                 mov     eax, MmUserProbeAddress
PAGE:0049D5F7                 cmp     ebx, eax
PAGE:0049D5F9                 jnb     loc_520CEF
PAGE:0049D5FF loc_49D5FF:                            
PAGE:0049D5FF                 cmp     [ebx+8], esi
PAGE:0049D602                 setnz   byte ptr [ebp-1Ah]
PAGE:0049D606                 mov     ecx, [ebx+0Ch]
PAGE:0049D609                 mov     [ebp-38h], ecx
PAGE:0049D60C                 mov     ecx, [ebp+14h]
PAGE:0049D60F                 cmp     ecx, esi
PAGE:0049D611                 jz      loc_4CCB88
PAGE:0049D617                 test    cl, 3
PAGE:0049D61A                 jnz     loc_520CFB
PAGE:0049D620 loc_49D620:                            
PAGE:0049D620                 cmp     ecx, eax
PAGE:0049D622                 jnb     loc_520D0D
PAGE:0049D628 loc_49D628: 
PAGE:0049D628                 mov     eax, [ecx]
PAGE:0049D62A                 mov     [ebp-2Ch], eax
PAGE:0049D62D                 mov     eax, [ecx+4]
PAGE:0049D630                 mov     [ebp-28h], eax
PAGE:0049D633                 mov     byte ptr [ebp-19h], 1
PAGE:0049D637 loc_49D637:                      
PAGE:0049D637                 or      dword ptr [ebp-4], 0FFFFFFFFh
PAGE:0049D63B loc_49D63B:                     
PAGE:0049D63B                                        
PAGE:0049D63B                 cmp     byte ptr [ebp-1Ah], 0
PAGE:0049D63F                 jnz     loc_520D34
PAGE:0049D645 loc_49D645:                            
PAGE:0049D645                 mov     eax, PsProcessType
PAGE:0049D64A                 add     eax, 68h
PAGE:0049D64D                 push    eax
PAGE:0049D64E                 push    dword ptr [ebp+0Ch]
PAGE:0049D651                 lea     eax, [ebp-0D4h]
PAGE:0049D657                 push    eax
PAGE:0049D658                 lea     eax, [ebp-0B8h]
PAGE:0049D65E                 push    eax
PAGE:0049D65F                 call    SeCreateAccessState
PAGE:0049D664                 cmp     eax, esi
PAGE:0049D666                 jl      loc_49D718
PAGE:0049D66C                 push    dword ptr [ebp-34h] ; PreviousMode
PAGE:0049D66F                 push    ds:stru_5B6978.HighPart
PAGE:0049D675                 push    ds:stru_5B6978.LowPart ; PrivilegeValue
PAGE:0049D67B                 call    SeSinglePrivilegeCheck
PAGE:0049D680                 test    al, al
PAGE:0049D682                 jnz     loc_4AA7DB
PAGE:0049D688 loc_49D688:                          
PAGE:0049D688                 cmp     byte ptr [ebp-1Ah], 0
PAGE:0049D68C                 jnz     loc_520D52
PAGE:0049D692                 cmp     byte ptr [ebp-19h], 0
PAGE:0049D696                 jz      loc_4CCB9A
PAGE:0049D69C                 mov     [ebp-30h], esi
PAGE:0049D69F                 cmp     [ebp-28h], esi
PAGE:0049D6A2                 jnz     loc_4C1301
PAGE:0049D6A8                 lea     eax, [ebp-24h]
PAGE:0049D6AB                 push    eax
PAGE:0049D6AC                 push    dword ptr [ebp-2Ch]
PAGE:0049D6AF                 call    PsLookupProcessByProcessId
PAGE:0049D6B4 loc_49D6B4:                            

As you see, this code copies given pointers in the safe way, checking if they point to user addresses, checks access rights and presence of the privilege "SeDebugPrivilege", then extracts ProcessId from CLIENT_ID structure and passes it to PsLookupProcessByProcessId function whose purpose is to obtain EPROCESS based on ProcessId. Remaining part of function is useless to us, therefore we should glance in PsLookupProcessByProcessId now:

PAGE:0049D725                 public PsLookupProcessByProcessId
PAGE:0049D725 PsLookupProcessByProcessId proc near    
PAGE:0049D725                                        
PAGE:0049D725
PAGE:0049D725 ProcessId       = dword ptr  8
PAGE:0049D725 Process         = dword ptr  0Ch
PAGE:0049D725
PAGE:0049D725                 mov     edi, edi
PAGE:0049D727                 push    ebp
PAGE:0049D728                 mov     ebp, esp
PAGE:0049D72A                 push    ebx
PAGE:0049D72B                 push    esi
PAGE:0049D72C                 mov     eax, large fs:124h
PAGE:0049D732                 push    [ebp+ProcessId]
PAGE:0049D735                 mov     esi, eax
PAGE:0049D737                 dec     dword ptr [esi+0D4h]
PAGE:0049D73D                 push    PspCidTable
PAGE:0049D743                 call    ExMapHandleToPointer
PAGE:0049D748                 mov     ebx, eax
PAGE:0049D74A                 test    ebx, ebx
PAGE:0049D74C                 mov     [ebp+ProcessId], STATUS_INVALID_PARAMETER
PAGE:0049D753                 jz      short loc_49D787
PAGE:0049D755                 push    edi
PAGE:0049D756                 mov     edi, [ebx]
PAGE:0049D758                 cmp     byte ptr [edi], 3
PAGE:0049D75B                 jnz     short loc_49D77A
PAGE:0049D75D                 cmp     dword ptr [edi+1A4h], 0
PAGE:0049D764                 jz      short loc_49D77A
PAGE:0049D766                 mov     ecx, edi
PAGE:0049D768                 call    sub_4134A9
PAGE:0049D76D                 test    al, al
PAGE:0049D76F                 jz      short loc_49D77A
PAGE:0049D771                 mov     eax, [ebp+Process]
PAGE:0049D774                 and     [ebp+ProcessId], 0
PAGE:0049D778                 mov     [eax], edi
PAGE:0049D77A loc_49D77A:                                                                 
PAGE:0049D77A                 push    ebx
PAGE:0049D77B                 push    PspCidTable
PAGE:0049D781                 call    ExUnlockHandleTableEntry
PAGE:0049D786                 pop     edi
PAGE:0049D787 loc_49D787:                           
PAGE:0049D787                 inc     dword ptr [esi+0D4h]
PAGE:0049D78D                 jnz     short loc_49D79A
PAGE:0049D78F                 lea     eax, [esi+34h]
PAGE:0049D792                 cmp     [eax], eax
PAGE:0049D794                 jnz     loc_52388A
PAGE:0049D79A loc_49D79A:                                                             
PAGE:0049D79A                 mov     eax, [ebp+ProcessId]
PAGE:0049D79D                 pop     esi
PAGE:0049D79E                 pop     ebx
PAGE:0049D79F                 pop     ebp
PAGE:0049D7A0                 retn    8

What we see here, confirms presence of the second table of the processes organized as HANDLE_TABLE. The table is called PspCidTable and contains lists of processes and threads, and is also being used in functions PsLookupProcessThreadByCid and PsLookupThreadByThreadId. As we can see, handle and pointer to handle table is passed to function ExMapHandleToPointer, which (if handle is valid) returns pointer to an element of the table describing given handle - HANDLE_TABLE_ENTRY. After we process ntoskrnl.pdb with PDBdump and dig through obtained log, it is possible to figure out the following:

struct _HANDLE_TABLE_ENTRY {
// static data ------------------------------------
// non-static data --------------------------------
  /*<thisrel this+0x0>*/ /*|0x4|*/ void* Object;
  /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long ObAttributes;
  /*<thisrel this+0x0>*/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;
  /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long Value;
  /*<thisrel this+0x4>*/ /*|0x4|*/ unsigned long GrantedAccess;
  /*<thisrel this+0x4>*/ /*|0x2|*/ unsigned short GrantedAccessIndex;
  /*<thisrel this+0x6>*/ /*|0x2|*/ unsigned short CreatorBackTraceIndex;
  /*<thisrel this+0x4>*/ /*|0x4|*/ long NextFreeTableEntry;
  };// <size 0x8>
We can recover HANDLE_TABLE_ENTRY structure from this: 
typedef struct _HANDLE_TABLE_ENTRY 
{
    union 
    {
        PVOID                    Object;
        ULONG                    ObAttributes;
        PHANDLE_TABLE_ENTRY_INFO InfoTable;
        ULONG                    Value;
    };
    union 
   {
       union 
       {
          ACCESS_MASK GrantedAccess;
          struct 
          {
              USHORT GrantedAccessIndex;
              USHORT CreatorBackTraceIndex;
          };
       };
       LONG NextFreeTableEntry;
   };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

What's the use of it? First of all, we are interested into contents of Object field, which is the sum of the pointer to object described by this handle and a usage flag of the given element of the table (I will explain this in more details a little bit later). Field GrantedAccess, which specifies permitted access rights to the object from this handle, is rather interesting. For example, it is possible to open a file for reading, modify field GrantedAccess and to write to this file. Such method can be used for reading/writing of files which may not be opened with required access rights (for example - files locked by another process). But we shall return to our problem - to obtain the list of processes by analyzing PspCidTable.

For this purpose we need to understand handle table format, in order to iterate through this list. There is a serious difference between Windows 2000 and Windows XP here. Handle table formats a significantly different and we should analyze each OS separately.

For the beginning we shall analyze handle table in Windows 2000 as it is much easier to understand this format. For the beginning we shall glance in a code of function ExMapHandleToPointer:

PAGE:00493285 ExMapHandleToPointer proc near          
PAGE:00493285                                        
PAGE:00493285
PAGE:00493285 HandleTable     = dword ptr  8
PAGE:00493285 Handle          = dword ptr  0Ch
PAGE:00493285
PAGE:00493285                 push    esi
PAGE:00493286                 push    [esp+Handle]
PAGE:0049328A                 push    [esp+4+HandleTable]
PAGE:0049328E                 call    ExpLookupHandleTableEntry
PAGE:00493293                 mov     esi, eax
PAGE:00493295                 test    esi, esi
PAGE:00493297                 jz      short loc_4932A9
PAGE:00493299                 push    esi
PAGE:0049329A                 push    [esp+4+HandleTable]
PAGE:0049329E                 call    ExLockHandleTableEntry
PAGE:004932A3                 neg     al
PAGE:004932A5                 sbb     eax, eax
PAGE:004932A7                 and     eax, esi
PAGE:004932A9 loc_4932A9:                            
PAGE:004932A9                 pop     esi
PAGE:004932AA                 retn    8
PAGE:004932AA ExMapHandleToPointer endp

Here we call ExMapHandleToPointer function which performs search on HANDLE_TABLE, and call ExLockHandleTableEntry which sets Lock Bit. To understand internals of handle table we should disassemble both these functions. We shall begin with ExpLookupHandleTableEntry:

PAGE:00493545 ExpLookupHandleTableEntry proc near     
PAGE:00493545                                        
PAGE:00493545
PAGE:00493545 HandleTable     = dword ptr  0Ch
PAGE:00493545 Handle          = dword ptr  10h
PAGE:00493545
PAGE:00493545                 push    esi
PAGE:00493546                 push    edi
PAGE:00493547                 mov     edi, [esp+Handle]
PAGE:0049354B                 mov     eax, 0FFh
PAGE:00493550                 mov     ecx, edi
PAGE:00493552                 mov     edx, edi
PAGE:00493554                 mov     esi, edi
PAGE:00493556                 shr     ecx, 12h
PAGE:00493559                 shr     edx, 0Ah
PAGE:0049355C                 shr     esi, 2
PAGE:0049355F                 and     ecx, eax
PAGE:00493561                 and     edx, eax
PAGE:00493563                 and     esi, eax
PAGE:00493565                 test    edi, 0FC000000h
PAGE:0049356B                 jnz     short loc_49358A
PAGE:0049356D                 mov     eax, [esp+HandleTable]
PAGE:00493571                 mov     eax, [eax+8]
PAGE:00493574                 mov     ecx, [eax+ecx*4]
PAGE:00493577                 test    ecx, ecx
PAGE:00493579                 jz      short loc_49358A
PAGE:0049357B                 mov     ecx, [ecx+edx*4]
PAGE:0049357E                 test    ecx, ecx
PAGE:00493580                 jz      short loc_49358A
PAGE:00493582                 lea     eax, [ecx+esi*8]
PAGE:00493585 loc_493585:                             
PAGE:00493585                 pop     edi
PAGE:00493586                 pop     esi
PAGE:00493587                 retn    8
PAGE:0049358A loc_49358A:                                                       
PAGE:0049358A                 xor     eax, eax
PAGE:0049358C                 jmp     short loc_493585
PAGE:0049358C ExpLookupHandleTableEntry endp

In addition to it, I shall show HANDLE_TABLE structure obtained from ntoskrnl.pdb:

struct _HANDLE_TABLE {  
  // static data ------------------------------------  
  // non-static data -------------------------------- 
  /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long Flags;
  /*<thisrel this+0x4>*/ /*|0x4|*/ long HandleCount;
  /*<thisrel this+0x8>*/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY*** Table; 
  /*<thisrel this+0xc>*/ /*|0x4|*/ struct _EPROCESS* QuotaProcess; 
  /*<thisrel this+0x10>*/ /*|0x4|*/ void* UniqueProcessId; 
  /*<thisrel this+0x14>*/ /*|0x4|*/ long FirstFreeTableEntry;
  /*<thisrel this+0x18>*/ /*|0x4|*/ long NextIndexNeedingPool;
  /*<thisrel this+0x1c>*/ /*|0x38|*/ struct _ERESOURCE HandleTableLock;  
  /*<thisrel this+0x54>*/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList;
  /*<thisrel this+0x5C>*/ /*|0x10|*/ struct _KEVENT HandleContentionEvent;
}; // <size 0x6c>

Based on these data we can recover structure of the handle table:

typedef struct _WIN2K_HANDLE_TABLE 
{
   ULONG                 Flags;
   LONG                  HandleCount;
   PHANDLE_TABLE_ENTRY **Table;
   PEPROCESS             QuotaProcess;
   HANDLE                UniqueProcessId;
   LONG                  FirstFreeTableEntry;
   LONG                  NextIndexNeedingPool;
   ERESOURCE             HandleTableLock;
   LIST_ENTRY            HandleTableList;
   KEVENT                HandleContentionEvent;
} WIN2K_HANDLE_TABLE , *PWIN2K_HANDLE_TABLE ;

Considering all of the above it is obvious, that handle value consists of three parts which are indexes in the three-level table of objects. Now we shall look in function ExLockHandleTableEntry:

PAGE:00492E2B ExLockHandleTableEntry proc near        
PAGE:00492E2B                                         
PAGE:00492E2B
PAGE:00492E2B var_8           = dword ptr -8
PAGE:00492E2B var_4           = dword ptr -4
PAGE:00492E2B HandleTable     = dword ptr  8
PAGE:00492E2B Entry           = dword ptr  0Ch
PAGE:00492E2B
PAGE:00492E2B                 push    ebp
PAGE:00492E2C                 mov     ebp, esp
PAGE:00492E2E                 push    ecx
PAGE:00492E2F                 push    ecx
PAGE:00492E30                 push    ebx
PAGE:00492E31                 push    esi
PAGE:00492E32                 xor     ebx, ebx
PAGE:00492E34 loc_492E34:                                                                
PAGE:00492E34                 mov     eax, [ebp+Entry]
PAGE:00492E37                 mov     esi, [eax]
PAGE:00492E39                 test    esi, esi
PAGE:00492E3B                 mov     [ebp+var_8], esi
PAGE:00492E3E                 jz      short loc_492E89
PAGE:00492E40                 jle     short loc_492E64
PAGE:00492E42                 mov     eax, esi
PAGE:00492E44                 or      eax, 80000000h      // set WIN2K_TABLE_ENTRY_LOCK_BIT
PAGE:00492E49                 mov     [ebp+var_4], eax
PAGE:00492E4C                 mov     eax, [ebp+var_8]
PAGE:00492E4F                 mov     ecx, [ebp+Entry]
PAGE:00492E52                 mov     edx, [ebp+var_4]
PAGE:00492E55                 cmpxchg [ecx], edx
PAGE:00492E58                 cmp     eax, esi
PAGE:00492E5A                 jnz     short loc_492E64
PAGE:00492E5C                 mov     al, 1
PAGE:00492E5E loc_492E5E:                             
PAGE:00492E5E                 pop     esi
PAGE:00492E5F                 pop     ebx
PAGE:00492E60                 leave
PAGE:00492E61                 retn    8
PAGE:00492E64 loc_492E64:               
PAGE:00492E64                 mov     eax, ebx
PAGE:00492E66                 inc     ebx
PAGE:00492E67                 cmp     eax, 1
PAGE:00492E6A                 jb      loc_4BC234
PAGE:00492E70                 mov     eax, [ebp+HandleTable]
PAGE:00492E73                 push    offset unk_46D240 ; Timeout
PAGE:00492E78                 push    0               ; Alertable
PAGE:00492E7A                 push    0               ; WaitMode
PAGE:00492E7C                 add     eax, 5Ch
PAGE:00492E7F                 push    0               ; WaitReason
PAGE:00492E81                 push    eax             ; Object
PAGE:00492E82                 call    KeWaitForSingleObject
PAGE:00492E87                 jmp     short loc_492E34
PAGE:00492E89 loc_492E89:                         
PAGE:00492E89                 xor     al, al
PAGE:00492E8B                 jmp     short loc_492E5E
PAGE:00492E8B ExLockHandleTableEntry endp

This code checks 31st bit in Object element of HANDLE_TABLE_ENTRY structure, sets it and, if it is set - waits for HandleContentionEvent in HANDLE_TABLE. For us only the fact of setting TABLE_ENTRY_LOCK_BIT in important, as it is a part of the object address, and if flag is not set, we shall obtain invalid address. Now that we have understood format of the handle table, it is possible to write a code that iterates through objects in the table:

void ScanWin2KHandleTable(PWIN2K_HANDLE_TABLE HandleTable)
{
   int i, j, k;
   PHANDLE_TABLE_ENTRY Entry;
   for (i = 0; i < 0x100; i++)
   {
      if (HandleTable->Table[i])
      {
         for (j = 0; j < 0x100; j++)
         {
            if (HandleTable->Table[i][j])
            {
               for (k = 0; k < 0x100; k++)
               {
                  Entry = &HandleTable->Table[i][j][k];
                  if (Entry->Object) 
                    ProcessObject((PVOID)((ULONG)Entry->Object | WIN2K_TABLE_ENTRY_LOCK_BIT));
               }
            }
         }
      }
   }
}

This code processes all objects in the table and calls ProcessObject function for each of them. ProcessObject detects object type and processes it in appropriate way. Here a code of this function:

void ProcessObject(PVOID Object)
{
   POBJECT_HEADER ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
   if (ObjectHeader->Type == *PsProcessType) CollectProcess(Object);
   if (ObjectHeader->Type == *PsThreadType)  ThreadCollect(Object);
}

OK, we have understood table of objects in Windows 2000, now it is time to start analysis in Windows XP. We shall begin with disassembling ExpLookupHandleTableEntry function:

PAGE:0048D3C1 ExpLookupHandleTableEntry proc near    
PAGE:0048D3C1                                       
PAGE:0048D3C1
PAGE:0048D3C1 HandleTable     = dword ptr  8
PAGE:0048D3C1 Handle          = dword ptr  0Ch
PAGE:0048D3C1
PAGE:0048D3C1                 mov     edi, edi
PAGE:0048D3C3                 push    ebp
PAGE:0048D3C4                 mov     ebp, esp
PAGE:0048D3C6                 and     [ebp+Handle], 0FFFFFFFCh
PAGE:0048D3CA                 mov     eax, [ebp+Handle]
PAGE:0048D3CD                 mov     ecx, [ebp+HandleTable]
PAGE:0048D3D0                 mov     edx, [ebp+Handle]
PAGE:0048D3D3                 shr     eax, 2
PAGE:0048D3D6                 cmp     edx, [ecx+38h]
PAGE:0048D3D9                 jnb     loc_4958D6
PAGE:0048D3DF                 push    esi
PAGE:0048D3E0                 mov     esi, [ecx]
PAGE:0048D3E2                 mov     ecx, esi
PAGE:0048D3E4                 and     ecx, 3     // ecx - table level
PAGE:0048D3E7                 and     esi, not 3 // esi - pointer to first table
PAGE:0048D3EA                 sub     ecx, 0
PAGE:0048D3ED                 jnz     loc_48DEA4
PAGE:0048D3F3                 lea     eax, [esi+eax*8] 
PAGE:0048D3F6 loc_48D3F6:                            
PAGE:0048D3F6                 pop     esi
PAGE:0048D3F7 loc_48D3F7:                            
PAGE:0048D3F7                 pop     ebp
PAGE:0048D3F8                 retn    8
PAGE:0048DEA4 loc_48DEA4:                            
PAGE:0048DEA4                 dec     ecx
PAGE:0048DEA5                 mov     ecx, eax
PAGE:0048DEA7                 jnz     loc_52F57A
PAGE:0048DEAD                 shr     ecx, 9
PAGE:0048DEB0                 mov     ecx, [esi+ecx*4]  
PAGE:0048DEB3 loc_48DEB3:                            
PAGE:0048DEB3                 and     eax, 1FFh
PAGE:0048DEB8                 lea     eax, [ecx+eax*8]
PAGE:0048DEBB                 jmp     loc_48D3F6
PAGE:0052F57A loc_52F57A:                             
PAGE:0052F57A                 shr     ecx, 13h
PAGE:0052F57D                 mov     edx, ecx
PAGE:0052F57F                 mov     ecx, [esi+ecx*4]
PAGE:0052F582                 shl     edx, 13h
PAGE:0052F585                 sub     eax, edx
PAGE:0052F587                 mov     edx, eax
PAGE:0052F589                 shr     edx, 9
PAGE:0052F58C                 mov     ecx, [ecx+edx*4]
PAGE:0052F58F                 jmp     loc_48DEB3

Now we shall look at structure HANDLE_TABLE from ntoskrnl.pdb:

struct _HANDLE_TABLE {
// static data ------------------------------------
// non-static data --------------------------------
  /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long TableCode;
  /*<thisrel this+0x4>*/ /*|0x4|*/ struct _EPROCESS* QuotaProcess;
  /*<thisrel this+0x8>*/ /*|0x4|*/ void* UniqueProcessId;
  /*<thisrel this+0xc>*/ /*|0x10|*/ struct _EX_PUSH_LOCK HandleTableLock[4];
  /*<thisrel this+0x1c>*/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList;
  /*<thisrel this+0x24>*/ /*|0x4|*/ struct _EX_PUSH_LOCK HandleContentionEvent;
  /*<thisrel this+0x28>*/ /*|0x4|*/ struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;
  /*<thisrel this+0x2c>*/ /*|0x4|*/ long ExtraInfoPages;
  /*<thisrel this+0x30>*/ /*|0x4|*/ unsigned long FirstFree;
  /*<thisrel this+0x34>*/ /*|0x4|*/ unsigned long LastFree;
  /*<thisrel this+0x38>*/ /*|0x4|*/ unsigned long NextHandleNeedingPool;
  /*<thisrel this+0x3c>*/ /*|0x4|*/ long HandleCount;
  /*<thisrel this+0x40>*/ /*|0x4|*/ unsigned long Flags;
  /*<bitfield this+0x40>*/ /*|0x1|*/ unsigned char StrictFIFO:0:1;
  };  // <size 0x44>

We can recover format of _XP_HANDLE_TABLE structure based on this information:

typedef struct _XP_HANDLE_TABLE 
{
   ULONG                    TableCode;
   PEPROCESS                QuotaProcess;
   PVOID                    UniqueProcessId;
   EX_PUSH_LOCK             HandleTableLock[4];
   LIST_ENTRY               HandleTableList;
   EX_PUSH_LOCK             HandleContentionEvent;
   PHANDLE_TRACE_DEBUG_INFO DebugInfo;
   LONG                     ExtraInfoPages;
   ULONG                    FirstFree;
   ULONG                    LastFree;
   ULONG                    NextHandleNeedingPool;
   LONG                     HandleCount;
   LONG                     Flags;
   UCHAR                    StrictFIFO;
} XP_HANDLE_TABLE, *PXP_HANDLE_TABLE;

From the code above, it is obvious that function ExpLookupHandleTableEntry gets TableCode value from HANDLE_TABLE structure and based on its two low-order bits, calculates number of levels of the table. The remaining bits are pointer to the first level table. Hence HANDLE_TABLE in Windows XP can have from one up to three levels, thus the size of the table at any level is equal 1FFh. When quantity of records in the table increases, the system can automatically increase a level count. It is obvious, that the table will have the second level when quantity of records exceeds 0x200, and the third level - at quantity greater 0x40000. It is not known whether the system reduces of number of levels to the table when freeing (kao - releasing?) objects, I did not notice such behaviour.

Function ExLockHandleTableEntry does not exist in Windows XP, therefore the code blocking an element of the table is located in function ExMapHandleToPointer. Lest disassemble this function and look what it does:

PAGE:0048F61E ExMapHandleToPointer proc near          
PAGE:0048F61E                                        
PAGE:0048F61E
PAGE:0048F61E var_8           = dword ptr -8
PAGE:0048F61E var_4           = dword ptr -4
PAGE:0048F61E HandleTable     = dword ptr  8
PAGE:0048F61E Handle          = dword ptr  0Ch
PAGE:0048F61E
PAGE:0048F61E                 mov     edi, edi
PAGE:0048F620                 push    ebp
PAGE:0048F621                 mov     ebp, esp
PAGE:0048F623                 push    ecx
PAGE:0048F624                 push    ecx
PAGE:0048F625                 push    edi
PAGE:0048F626                 mov     edi, [ebp+Handle]
PAGE:0048F629                 test    di, 7FCh
PAGE:0048F62E                 jz      loc_4A2A36
PAGE:0048F634                 push    ebx
PAGE:0048F635                 push    esi
PAGE:0048F636                 push    edi
PAGE:0048F637                 push    [ebp+HandleTable]
PAGE:0048F63A                 call    ExpLookupHandleTableEntry
PAGE:0048F63F                 mov     esi, eax
PAGE:0048F641                 test    esi, esi
PAGE:0048F643                 jz      loc_4A2711
PAGE:0048F649                 mov     [ebp+var_4], esi
PAGE:0048F64C loc_48F64C:                                                              
PAGE:0048F64C                 mov     ebx, [esi]
PAGE:0048F64E                 test    bl, 1
PAGE:0048F651                 mov     [ebp+var_8], ebx
PAGE:0048F654                 jz      loc_508844
PAGE:0048F65A                 lea     eax, [ebx-1]
PAGE:0048F65D                 mov     [ebp+Handle], eax
PAGE:0048F660                 mov     eax, [ebp+var_8]
PAGE:0048F663                 mov     ecx, [ebp+var_4]
PAGE:0048F666                 mov     edx, [ebp+Handle]
PAGE:0048F669                 cmpxchg [ecx], edx
PAGE:0048F66C                 cmp     eax, ebx
PAGE:0048F66E                 jnz     loc_50884C
PAGE:0048F674                 mov     eax, esi
PAGE:0048F676 loc_48F676:                            
PAGE:0048F676                 pop     esi
PAGE:0048F677                 pop     ebx
PAGE:0048F678 loc_48F678:                           
PAGE:0048F678                 pop     edi
PAGE:0048F679                 leave
PAGE:0048F67A                 retn    8
PAGE:0048F67A ExMapHandleToPointer endp

When ExpLookupHandleTableEntry function returns pointer to HANDLE_TABLE_ENTRY, we check low-order bit of Object field and, if it is set, it is cleared (kao - there is typo in original here... Hopefully I got it right...) and, if it is not set, we wait until it is set. Hence when obtaining the address of object we should not set the high-order bit (as in Windows 2000), but clear low-order bit. In view of the aforesaid, we can make a code that scans the table of objects:

void ScanXpHandleTable(PXP_HANDLE_TABLE HandleTable)
{
   int i, j, k;
   PHANDLE_TABLE_ENTRY Entry;
   ULONG TableCode = HandleTable->TableCode & ~TABLE_LEVEL_MASK;
   switch (HandleTable->TableCode & TABLE_LEVEL_MASK)
   {
      case 0 :
        for (i = 0; i < 0x200; i++)
        {
           Entry = &((PHANDLE_TABLE_ENTRY)TableCode)[i];
           if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));
        }           
      break;
      case 1 :
        for (i = 0; i < 0x200; i++)
        {
           if (((PVOID *)TableCode)[i])
           {
              for (j = 0; j < 0x200; j++)
              {
                 Entry = &((PHANDLE_TABLE_ENTRY *)TableCode)[i][j];
                 if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));
              }
           }
        }        
      break;
      case 2 :
        for (i = 0; i < 0x200; i++)
        {
           if (((PVOID *)TableCode)[i])
           {
              for (j = 0; j < 0x200; j++)
              {
                 if (((PVOID **)TableCode)[i][j])
                 {
                    for (k = 0; k < 0x200; k++)
                    {
                       Entry = &((PHANDLE_TABLE_ENTRY **)TableCode)[i][j][k];
                       if (Entry->Object) 
                         ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));
                    }
                 }
              }
           }
        }       
      break;
   }
}

So we have understood format of object tables. Now to enumerate processes we need to obtain PspCidTable address. As you have probably guessed, we shall search for it in function PsLookupProcessByProcessId, first call in it will contain PspCidTable address. Here a code:

void GetPspCidTable()
{
   PUCHAR cPtr, pOpcode;
   ULONG Length;
   for (cPtr = (PUCHAR)PsLookupProcessByProcessId; 
        cPtr < (PUCHAR)PsLookupProcessByProcessId + PAGE_SIZE; 
             cPtr += Length)
   {
      Length = SizeOfCode(cPtr, &pOpcode);
      if (!Length) break;
      if (*(PUSHORT)cPtr == 0x35FF && *(pOpcode + 6) == 0xE8) 
      {
         PspCidTable = **(PVOID **)(pOpcode + 2);
         break;
      }
   }
}

Now we know how to process PspCidTable. It is easily possible to view all objects in tables of all processes in a similar fashion, and to analyze objects which belong to hidden processes, in the same way as we did in UserMode. If you have understood with all aforementioned, I think you can do it.

If the system has a process, then sootvestsvenno it will have its threads. If we can catch the thread switching, it will be possible from the data to build a list of processes. To begin with a brief look at the mechanism of switching streams. At regular intervals (10-15 ms) system timer generates an interrupt that causes the scheduler, and if the time slice associated with the current thread has expired, the thread switch occurs. Needless thread switching is performed non-exported kernel functions SwapContext, which nevertheless can be found in the debug symbols for ntoskrnl.exe. This function is called by the scheduler at the end of time quantum flux, or while waiting for the flow of any event. In the first case, this function is called from KiDispatchInterrupt, in the second case of non-exported functions located deep within the nucleus, which in turn is called from KeWaitForSingleObject, KeDelayExecutionThread and KeWaitForMultipleObjects. SwapContext function parameters are passed in registers and have the following assignment: cl - defines the processing mode APC, edi - a pointer to the stream returns control, esi - a pointer to a thread gets control, ebx - a pointer to the PCR. We are only interested in pointers to switching currents passed in registers esi and edi. First we need to find the address of the function SwapContext. To do this we will disassemble and look KiDispatchInterrupt code like:

.text:00404E76                 call    sub_404C5A
.text:00404E7B                 mov     cl, 1
.text:00404E7D                 call    SwapContext

From this site we will retrieve the address SwapContext. This search method is very reliable and versatile as it allows to find SwapContext in any Windows, since it is 2000, and ending with 2003 server including all service pack. And here is the code performs search SwapContext:

void GetSwapContextAddress()
{

PUCHAR cPtr, pOpcode;

ULONG Length;


for (cPtr = (PUCHAR)KiDispatchInterrupt;

cPtr < (PUCHAR)KiDispatchInterrupt + PAGE_SIZE;

cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) break;

if (*(PUSHORT)pOpcode == 0x01B1 && *(pOpcode + 2) == 0xE8)

{

pSwapContext = (PVOID)(*(PULONG)(pOpcode + 3) + (ULONG)cPtr + 7);

break;

}

}

return;

}

After finding the address SwapContext we need to hook it. The only possible way in this case - it is a splicing code. To do this, copy the first few instructions to the intercepted function in the buffer at its end put jmp to continue, but the beginning of its function is replaced by jmp on his handler. In this case, if the copied code into the buffer we will meet the team containing relative offset, then you need to correct, otherwise we get the inevitable BSOD. For installation and removal of such hooks, we use the following code:

#define MemOpen()  __asm cli; __asm mov eax, cr0; __asm mov oData, eax; \
                   __asm and eax, 0xFFFEFFFF; __asm mov cr0, eax;
#define MemClose() __asm mov eax, oData; __asm mov cr0, eax; __asm sti;
UCHAR SaveOldFunction(PUCHAR Proc, PUCHAR Old)
{

ULONG Size;

PUCHAR pOpcode;

ULONG Offset;

PUCHAR oPtr;

ULONG Result = 0;

Offset = (ULONG)Proc - (ULONG)Old;

oPtr = Old;

while (Result < 5)

{

Size = SizeOfCode(Proc, &pOpcode);

memcpy(oPtr, Proc, Size);

if (IsRelativeCmd(pOpcode)) *(PULONG)((ULONG)pOpcode - (ULONG)Proc + (ULONG)oPtr + 1) += Offset;

oPtr += Size;

Proc += Size;

Result += Size;

}

*(PUCHAR)((ULONG)Old + Result) = 0xE9;

*(PULONG)((ULONG)Old + Result + 1) = Offset - 5;

return (UCHAR)Result;

}
PVOID HookCode(PVOID TargetProc, PVOID NewProc)
{

ULONG Address;

PVOID OldFunction;

PVOID Proc = TargetProc;

ULONG oData;

Address = (ULONG)NewProc - (ULONG)Proc - 5;

MemOpen();

OldFunction = ExAllocatePool(NonPagedPool, 20);

*(PULONG)OldFunction = (ULONG)Proc;

*(PUCHAR)((ULONG)OldFunction + 4) = SaveOldFunction((PUCHAR)Proc, (PUCHAR)((ULONG)OldFunction + 5));

*(PUCHAR)Proc = 0xE9;

*(PULONG)((ULONG)Proc + 1) = Address;

MemClose();

return (PVOID)((ULONG)OldFunction + 5);

}
void UnhookCode(PVOID OldProc)
{

PUCHAR Proc, pMem;

PUCHAR pOpcode;

ULONG Size, ThisSize;

ULONG SaveSize, Offset;

ULONG oData;

Proc = (PUCHAR)(*(PULONG)((ULONG)OldProc - 5));

pMem = Proc;

SaveSize = *(PUCHAR)((ULONG)OldProc - 1);

Offset = (ULONG)Proc - (ULONG)OldProc;

MemOpen();

memcpy(Proc, OldProc, SaveSize);

ThisSize = 0;

while (ThisSize < SaveSize)

{

Size = SizeOfCode(Proc, &pOpcode);

if (IsRelativeCmd(pOpcode)) *(PULONG)((ULONG)pOpcode + 1) -= Offset;

Proc += Size;

ThisSize += Size;

}

MemClose();

ExFreePool((PVOID)((ULONG)OldProc - 5));

return;

}
SwapContext handler itself will look like this:
void __declspec(naked) NewSwapContext()
{

__asm

{

pushad

pushfd

push edi

call ThreadCollect

push esi

call ThreadCollect

popfd

popad

jmp OldSwapContext

}

}

The method of splicing certainly easy and convenient, but when used incorrectly it can create quite a lot of problems. The hazards of the time of installation and removal of hooks, as on a multiprocessor system (either on a system with Hiperthreading processor) to another thread can call the intercepted function in the patch when its code is not yet complete. Let's make a rough estimate of this probability. The average frequency of calling this function on a dual Pentium April 2400 was equal to 785 calls per second. Assume that the patch beginning of the function is 0.01 microseconds (actually much less), while we get the probability of equal zeal 0.00000785, ie to 127,380 starts, one drop of the system. For such a program as a rootkit detector (which is also rarely run), it is quite reasonable value, but if this method will be used to permanently apply the program (eg antivirus software), then use the splice should only once (for the installation of hooks without their withdrawal) and only at boot (the boot driver). In this case, the probability of a crash so close to zero that it can never be taken into account. So far I have not seen a single case of a crash because of this reason. But there is another, far more than the real cause of instability of splicing (and any other methods of hooks too). If the driver admits unloading and unloading removes the hooks, then unloading can occur when a thread is inside the handler interception. The probability of this depends on the alignment of the content handler to intercept and on the frequency of calls intercepted functions. Experience has shown that the probability of a crash when unloading the driver is large enough and put up with it did not, but this can be avoided if the sync driver unload with a handler function. And even better - make the driver nonpaged, that will save us from the brakes when you synchronize. As you see, when properly used splicing can be absolutely safe, although lack of understanding of his work may lead to very nonweak glitch, so use, but with caution.

Appendix:

All of the above methods of detecting hidden processes implemented in my program Process Hunter. Bypass all the description of the method of detection would be very difficult, but probably the authors of rootkits implement its circumvention by intercepting IOCTL from application to the driver. Reliable protection against such circumvention is to use a private version of the detector. Since the program comes with source code, you can decide to change all that apply to IOCTL, names of modules, windows, etc.

Only Delphi source code is included in the archive.