//
// $Id: PSInfo.cpp,v 1.1 2004/06/01 17:05:21 bakerj Exp $
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the OVAL Query Interpreter.
//
// The OVAL Query Interpreter is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License.
//
// The OVAL Query Interpreter is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with the OVAL
// Query Interpreter; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//
//****************************************************************************************//

#include "PSInfo.h"

typedef LONG KPRIORITY;

#define STATUS_SUCCESS 0x00000000

typedef LONG NTSTATUS;

typedef enum _PROCESSINFOCLASS {
	ProcessBasicInformation,
	ProcessQuotaLimits,
	ProcessIoCounters
} PROCESSINFOCLASS;

typedef struct _RTL_DRIVE_LETTER_CURDIR {
    unsigned short   Flags;
    unsigned short   Length;
    unsigned int   TimeStamp;
    STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR;

typedef struct _CURDIR {
	UNICODE_STRING DosPath;
    void     *Handle;
} CURDIR;

typedef struct _RTL_USER_PROCESS_PARAMETERS {
	unsigned int   MaximumLength;
	unsigned int   Length;
	unsigned int   Flags;
	unsigned int   DebugFlags;
	void     *ConsoleHandle;
	unsigned int   ConsoleFlags;
	void     *StandardInput;
	void     *StandardOutput;
	void     *StandardError;
	struct   _CURDIR CurrentDirectory;
    UNICODE_STRING DllPath;
    UNICODE_STRING ImagePathName;
    UNICODE_STRING CommandLine;
	void     *Environment;
	unsigned int   StartingX;
	unsigned int   StartingY;
	unsigned int   CountX;
	unsigned int   CountY;
	unsigned int   CountCharsX;
	unsigned int   CountCharsY;
	unsigned int   FillAttribute;
	unsigned int   WindowFlags;
	unsigned int   ShowWindowFlags;
	UNICODE_STRING WindowTitle;
	UNICODE_STRING DesktopInfo;
	UNICODE_STRING ShellInfo;
	UNICODE_STRING RuntimeData;
	RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];   
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

typedef struct _PEB {
    char InheritedAddressSpace;
	char ReadImageFileExecOptions;
    char BeingDebugged;
    char SpareBool;
    void     *Mutant;
    void     *ImageBaseAddress;
    void *Ldr;
    RTL_USER_PROCESS_PARAMETERS *P;
    void     *SubSystemData;
    void     *ProcessHeap;
    void     *FastPebLock;
    void     *FastPebLockRoutine;
    void     *FastPebUnlockRoutine;
    unsigned int   EnvironmentUpdateCount;
    void     *KernelCallbackTable;
    unsigned int   SystemReserved[2];
} PEB, *PPEB;

NTSTATUS
(NTAPI *ZwQueryInformationProcess)(
	IN HANDLE ProcessHandle,
	IN PROCESSINFOCLASS ProcessInformationClass,
	OUT PVOID ProcessInformation,
	IN ULONG ProcessInformationLength,
	OUT PULONG ReturnLength
	);

typedef struct _PROCESS_BASIC_INFORMATION {
	NTSTATUS ExitStatus;
	PPEB PebBaseAddress;
	KAFFINITY AffinityMask;
	KPRIORITY BasePriority;
	ULONG UniqueProcessId;
	ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Class PSInfo  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

PSInfo::PSInfo(string osNameIn, DBInterface *dbIn)
{
	pi_tablename = "";
	pi_tablename.append(osNameIn);
	pi_tablename.append("PSInfo");

	db = dbIn;
}

PSInfo::~PSInfo()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void PSInfo::Run()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//

	// Delete any old data from this table.

	db->ClearTable(pi_tablename);

	// Gather new data and put in table.

	 GetPSInfo();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void PSInfo::DumpPSInfo(DWORD pid)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//

	HANDLE hProcess;							// a process handle
	PROCESS_BASIC_INFORMATION pbi;				// results from our NT native call
	ULONG length = 0;
	NTSTATUS result;
	PEB peb;									// The infamous NT Process Environment Block..accessible via user mode
	ULONG bytes_read = 0;						// how many bytes has ReadProcessMemory read?
	RTL_USER_PROCESS_PARAMETERS rtl_user_proc;	// this is where the good process info is
	WCHAR* strImagePathName = NULL; 
	WCHAR* strCommandLine = NULL; 
	WCHAR* strCurrentDirectory = NULL; 
	unsigned short str_buf = 512;
	unsigned short largest_buf = 512;			// is the largest buffer we need for reading UNICODE strings

	string sqlStmt = "";

	// Make a string version of the PID that will be used in the SQL statement and in any
	// error messages.

	char pidBuf[8];
	itoa(pid, pidBuf, 10);

	// Get the address of the 'ZwQueryInformationProcess' function in 'ntdll.dll'.

	if( !(*(DWORD*)&ZwQueryInformationProcess = (DWORD)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")),"ZwQueryInformationProcess")))
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (PSInfo) Could not get a handle to the 'ZwQueryInformationProcess' function in 'ntdll.dll' for process with pid = ");
		errorMessage.append(pidBuf);
		errorMessage.append(".\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	}

	// Open the process object.  The ZwQueryInformationProcess() function that is called
	// next requires that the process be opened with PROCESS_QUERY_INFORMATION access.

	if((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)) != NULL)
	{
		// figure out the PEB address using this NT Native Call ;-)

		if((result = ZwQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &length)) == STATUS_SUCCESS)
		{
			// Ok, we should have the PEB address now time to get the PEB itself!

			if(ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(PEB), &bytes_read))
			{
				if(ReadProcessMemory(hProcess, peb.P, &rtl_user_proc, sizeof(RTL_USER_PROCESS_PARAMETERS), &bytes_read))
				{
					//-----------------------------------------//
					//----         Image Path Name         ----//
					//-----------------------------------------//

					// Find the most amount of space we need.  Add 2 to this for a UNICODE
					// NULL character.

					str_buf = rtl_user_proc.ImagePathName.Length;
					str_buf = str_buf + 2;
					if (str_buf > largest_buf) largest_buf = str_buf;

					// Get the image path name.

					if((strImagePathName = (WCHAR*)malloc(str_buf)) != NULL)
					{
						ZeroMemory(strImagePathName, str_buf);
						if(rtl_user_proc.ImagePathName.Buffer != 0x0000)
						{
							// SMC-AUDIT: REVIEW: can we count on
							// rtl_user_proc.ImagePathName.Buffer being length
							// ImagePathName.Length or less?
							//
							// ARB: I'm assuming we can.  Can't find much documentation on the
							// RTL_USER_PROCESS_PARAMETERS structure.  But ImagePathName is
							// a UNICODE_STRING which states that the Length member specifies
							// the length, in bytes, of the string pointed to by the Buffer
							// member, not including the terminating NULL character, if any.

							if(ReadProcessMemory(hProcess,
												 rtl_user_proc.ImagePathName.Buffer,
												 strImagePathName,
												 str_buf,
												 &bytes_read))
							{
								// success
							}
						}
					}

					//-----------------------------------------//
					//----          Command Line           ----//
					//-----------------------------------------//

					// Find the most amount of space we need.  Add 2 to this for a UNICODE
					// NULL character.

					str_buf = rtl_user_proc.CommandLine.Length;
					str_buf = str_buf + 2;
					if (str_buf > largest_buf) largest_buf = str_buf;

					// Get the command line.

					if((strCommandLine = (WCHAR*)malloc(str_buf)) != NULL)
					{
						ZeroMemory(strCommandLine, str_buf);
						if(rtl_user_proc.CommandLine.Buffer != 0x0000)
						{
							// SMC-AUDIT: REVIEW: can we count on
							// rtl_user_proc.CommandLine.Buffer being length
							// CommandLine.Length or less?
							//
							// ARB: I'm assuming we can.  Can't find much documentation on the
							// RTL_USER_PROCESS_PARAMETERS structure.  But CommandLine is
							// a UNICODE_STRING which states that the Length member specifies
							// the length, in bytes, of the string pointed to by the Buffer
							// member, not including the terminating NULL character, if any.

							if(ReadProcessMemory(hProcess,
												 rtl_user_proc.CommandLine.Buffer,
												 strCommandLine,
												 str_buf,
												 &bytes_read))
							{
								// success
							}
						}
					}

					//-----------------------------------------//
					//----        Current Directory        ----//
					//-----------------------------------------//

					// Find the most amount of space we need.  Add 2 to this for a UNICODE
					// NULL character.

					str_buf = rtl_user_proc.CurrentDirectory.DosPath.Length;
					str_buf = str_buf + 2;
					if (str_buf > largest_buf) largest_buf = str_buf;

					// Get the current directory.

					if((strCurrentDirectory = (WCHAR*)malloc(str_buf)) != NULL)
					{
						ZeroMemory(strCurrentDirectory, str_buf);
						if(rtl_user_proc.CurrentDirectory.DosPath.Buffer != 0x0000)
						{
							// SMC-AUDIT: REVIEW: can we count on
							// CurrentDirectory.DosPath.Buffer being less than
							// CurrentDirectory.DosPath.Length?
							//
							// ARB: I'm assuming we can.  Can't find much documentation on the
							// RTL_USER_PROCESS_PARAMETERS structure.  But DosPath is
							// a UNICODE_STRING which states that the Length member specifies
							// the length, in bytes, of the string pointed to by the Buffer
							// member, not including the terminating NULL character, if any.

							if(ReadProcessMemory(hProcess,
												 rtl_user_proc.CurrentDirectory.DosPath.Buffer,
												 strCurrentDirectory,
												 str_buf,
												 &bytes_read))
							{
								// success
							}
						}
					}
				}
			}
			else
			{
				string errorMessage = "";
				errorMessage.append("\nERROR: (PSInfo) Unable to get the PEB for process with pid = ");
				errorMessage.append(pidBuf);
				errorMessage.append(".\n\n");
				cerr << errorMessage;
				Log::WriteLog(errorMessage);
			}

			char* buf = NULL;
			buf = (char*)malloc(largest_buf);
			if(buf == NULL)
			{
				string errorMessage = "";
				errorMessage.append("\nERROR: (PSInfo) Could not allocate space for SQL statement for process with pid = ");
				errorMessage.append(pidBuf);
				errorMessage.append(".\n\n");
				cerr << errorMessage;
				Log::WriteLog(errorMessage);
			}
			else
			{
				// Prepare the SQL statement.

				sqlStmt.append("INSERT INTO ");
				sqlStmt.append(pi_tablename);
				sqlStmt.append(" VALUES ('");

				// PID

				sqlStmt.append(pidBuf);

				// PPID

				ZeroMemory(buf, largest_buf);
				_snprintf(buf, largest_buf-1, "%d", pbi.InheritedFromUniqueProcessId);
				buf[largest_buf-1] = '\0';

				sqlStmt.append("','");
				sqlStmt.append(buf);

				// PRIORITY

				ZeroMemory(buf, largest_buf);
				_snprintf(buf, largest_buf-1, "%d", pbi.BasePriority);
				buf[largest_buf-1] = '\0';

				sqlStmt.append("','");
				sqlStmt.append(buf);

				// IMAGEPATH

				ZeroMemory(buf, largest_buf);
				_snprintf(buf, largest_buf-1, "%S", strImagePathName);
				buf[largest_buf-1] = '\0';

				sqlStmt.append("','");
				sqlStmt.append(Common::FixQuotes(buf));

				// COMMANDLINE

				ZeroMemory(buf, largest_buf);
				_snprintf(buf, largest_buf-1, "%S", strCommandLine);
				buf[largest_buf-1] = '\0';

				sqlStmt.append("','");
				sqlStmt.append(Common::FixQuotes(buf));

				// CURRENTDIR

				ZeroMemory(buf, largest_buf);
				_snprintf(buf, largest_buf-1, "%S", strCurrentDirectory);
				buf[largest_buf-1] = '\0';

				sqlStmt.append("','");
				sqlStmt.append(Common::FixQuotes(buf));

				sqlStmt.append("')");

				// Update the database.

				db->ExecSQL(sqlStmt);
			}
			free(buf);
		}
		else
		{
			string errorMessage = "";
			errorMessage.append("\nERROR: (PSInfo) Unable to get address of the PEB for process ");
			errorMessage.append("with pid = ");
			errorMessage.append(pidBuf);
			errorMessage.append(".\n\n");
			cerr << errorMessage;
			Log::WriteLog(errorMessage);
		}

		CloseHandle(hProcess);
	}
	else
	{
		string errorMessage = "";

		if(GetLastError() == ERROR_ACCESS_DENIED)
		{
			errorMessage.append("\nERROR: (PSInfo) Do not have the correct privileges to ");
			errorMessage.append("open process object for pid = ");
			errorMessage.append(pidBuf);
			errorMessage.append(". Need SEDebugPrivilege.\n\n");
		}
		else
		{
			errorMessage.append("\nERROR: (PSInfo) Unable to open process object for pid = ");
			errorMessage.append(pidBuf);
			errorMessage.append(".\n\n");
		}

		cerr << errorMessage;
		Log::WriteLog(errorMessage);
	}

	free(strImagePathName);
	free(strCommandLine);
	free(strCurrentDirectory);
}

void PSInfo::GetPSInfo()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//

	HKEY hkey;						// handle to the currently open registry key
	DWORD bytes_needed = 0;			// for follow-up RegQuery-s
	DWORD last_index;				// the last index of all the counters in Perfmon
	DWORD result = 0;				// the result of RegQueryValue
	DWORD page_size = 0;			// page size of the machine...should be 4K on NT/x86
	LPBYTE title_data = NULL;		// pointer to a list of Perfmon counter titles
	LPBYTE object_data = NULL;
	LPTSTR* titles;

	// Before we start alloc'ing the mem away, let's at least get the page size.

	SYSTEM_INFO sys_info;
	GetSystemInfo(&sys_info);
	page_size = sys_info.dwPageSize;

	// We first get a list of all the performance counter names

	if((result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							  "Software\\Microsoft\\Windows NT\\CurrentVersion\\Perflib",
							  0,
							  KEY_QUERY_VALUE,
							  &hkey)) != ERROR_SUCCESS)
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (PSInfo) Unable to open registry ");
		errorMessage.append("key in order to get a list of all the performance counter ");
		errorMessage.append("names.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	}
 
	// SMC-AUDIT: REVIEW: what happens if someone sets the "Last Counter" value to a
	// non-DWORD? could that trigger an overflow in &last_index?  Should you double-check
	// the type of the key first?  Or is this only modifiable by admin?
	//
	// ARB: If the data in "Last Counter" is bigger than the size of last_index, then
	// RegQueryValueEx will return the value ERROR_MORE_DATA.  This will cause this function
	// to print an error message and exit.

	bytes_needed = sizeof(last_index);
	if((result = RegQueryValueEx(hkey,
								 "Last Counter",
								 0,
								 NULL,
								 (LPBYTE)&last_index,
								 &bytes_needed)) != ERROR_SUCCESS)
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (PSInfo) Unable to retrieve registry ");
		errorMessage.append("value needed to obtain a list of all the performance counter ");
		errorMessage.append("names.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	}

	RegCloseKey(hkey);

	// HKEY_PERFORMANCE_DATA is only available on Windows NT/2000/XP.  The following will
	// not work on Windows 95/98/ME.
	//
	// NOTE: Window NT - If hKey specifies HKEY_PERFORMANCE_DATA and the lpData buffer is
	// too small, RegQueryValueEx returns ERROR_MORE_DATA but lpcbData does not return the
	// required buffer size. This is because the size of the performance data can change
	// from one call to the next. In this case, you must increase the buffer size and call
	// RegQueryValueEx again passing the updated buffer size in the lpcbData parameter.
	// Repeat this until the function succeeds. You need to maintain a separate variable
	// to keep track of the buffer size, because the value returned by lpcbData is
	// unpredictable. 

	result = ERROR_MORE_DATA;
	bytes_needed = page_size;
	DWORD tmpBytesNeeded = 0;

	while (result == ERROR_MORE_DATA)
	{
		if (bytes_needed <= tmpBytesNeeded) bytes_needed = tmpBytesNeeded + page_size;
		tmpBytesNeeded = bytes_needed;

		if((title_data = (LPBYTE)realloc(title_data, bytes_needed)) == NULL)
		{
			string errorMessage = "";
			errorMessage.append("\nERROR: (PSInfo) Could not allocate space ");
			errorMessage.append("to hold a list of all the performance counter names.\n\n");
			cerr << errorMessage;
			Log::WriteLog(errorMessage);
			return;
		}

		result = RegQueryValueEx(HKEY_PERFORMANCE_DATA,
								 "Counter 009",
								 0,
								 NULL,
								 title_data,
								 &bytes_needed);
	}

	if(result != ERROR_SUCCESS)
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (PSInfo) Unable to get a list of all ");
		errorMessage.append("the performance counter names.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	}
	
	// Make sure title_data is correctly double NULL terminated.

	title_data[bytes_needed-2] = NULL;
	title_data[bytes_needed-1] = NULL;

	// The format of title_data is pretty messed up...see StorePerfData.  Here we call
	// StorePerfData to package title_data in a way we can easily get to.
	//
	// titles will eventually be an array of string pointers.  The strings in this case
	// are ASCII.  last_index is the actual ARRAY index...thus we need last_index + 1
	// pointers.

	if((titles = (LPTSTR*)malloc((last_index + 1) * sizeof(char*))) == NULL)
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (PSInfo) Could not allocate space ");
		errorMessage.append("to hold a list of all the performance titles.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	}
    StorePerfTitles(title_data, titles, last_index);    

	// Try to enable debug privileges.  Without this privilege, we will be unable to
	// open all the processes to gather information.

	if(Common::EnablePrivilege(SE_DEBUG_NAME) == true)
	{
		// Although RegQueryValue gives us the needed byte count when it fails, we
		// still need to wrap it in a while loop, b/c the data is dynamic...thus the
		// bytes needed value could be incorrect...

		result = ERROR_MORE_DATA;
		bytes_needed = page_size;
		DWORD tmpBytesNeeded = 0;

		while (result == ERROR_MORE_DATA)
		{
			if (bytes_needed <= tmpBytesNeeded) bytes_needed = tmpBytesNeeded + page_size;
			tmpBytesNeeded = bytes_needed;

			// NOTE: We saw an issue where the application was failing here wether
			// realloc succeeded or failed.  Originally, realloc failed and the error
			// was written to the screen, but then the application quit and returned
			// the user to the command prompt.  No idea why, and this was only happening
			// on a single test system that was used to test different versions of SQL
			// server.  This app worked fine on this machine until one day, don't know
			// what was changed on the machine.

			if((object_data = (LPBYTE)realloc(object_data, bytes_needed)) == NULL)
			{
				string errorMessage = "";
				errorMessage.append("\nERROR: (PSInfo) Could not ");
				errorMessage.append("allocate space to hold process performance objects.");
				errorMessage.append("\n\n");
				cerr << errorMessage;
				Log::WriteLog(errorMessage);
				return;
			}

			result = RegQueryValueEx(HKEY_PERFORMANCE_DATA,
									 "230",
									 0,
									 0,
									 object_data,
									 &bytes_needed);
		}

		if(result != ERROR_SUCCESS)
		{
			string errorMessage = "";
			errorMessage.append("\nERROR: (PSInfo) Unable to get the object data for ");
			errorMessage.append("the performance counter names.\n\n");
			cerr << errorMessage;
			Log::WriteLog(errorMessage);
			return;
		}

		ParseProcessData(object_data,titles);

		// If there is a problem disabling the privileges, an error should be logged
		// and the program should exit immediately.

		if (Common::DisableAllPrivileges() == false)
		{
			string errorMessage = "";

			errorMessage.append("\nERROR: Unable to disable all privileges.  The program ");
			errorMessage.append("will terminate.\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);

			exit(0);
		}
	}
	else
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (PSInfo) Could not enable debug privileges that ");
		errorMessage.append("allows this application to look at all running process.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	}

	free(titles);
	free(object_data);
	free(title_data);
}

void PSInfo::ParseProcessData(LPBYTE pdata, LPTSTR* titles)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//
	//
	//  NOTE: We are only interested in the Processes object. RegQueryValueEx() (in 
	//  GetProcessInfo()) is feed the value of 230 to retreive the processes object.
	//  For some reason, this also gives us the Thread object and the JobObjectsDetails.
	//  It looks like the Processes object is the first one that we get so instead of
	//  looping through the three objects, we just look at the first object.
	//
	//------------------------------------------------------------------------------------//

	PERF_DATA_BLOCK* pdatablock;			// pointer to performance data header struct
	PERF_OBJECT_TYPE* pobject;				// pointer to current object
	PERF_INSTANCE_DEFINITION* pinstance;	// pointer to current instance
	PERF_COUNTER_BLOCK* pcntblock;			// pointer to a counter block
	PERF_COUNTER_DEFINITION* pdefn;			// pointer to counter definition
	void* counter_data;						// a pointer to the actual data
	DWORD currentPID;

	LONG instance_index;					// generic index to the current instance
	DWORD counter_defn_index;				// generic index to current counter defn	

	pdatablock = (PERF_DATA_BLOCK*)pdata;

	// Get first object.  This should be the Process object.

	pobject = GetFirstObject(pdatablock);

	if (pobject->NumInstances >= 0)
	{
		// Get the location of the first PERF_INSTANCE_DEFINITION from the current
		// PERF_OBJECT_TYPE.

		pinstance = FirstInstance(pobject);

		// Loop through each PERF_INSTANCE_DEFINITION structure in the current
		// PERF_OBJECT_TYPE.  
		
		for (instance_index = 0; instance_index < pobject->NumInstances; instance_index++)
		{
			// There are three special cases we should skip.  The "Total" instance,
			// the Idle process (PID = 0), and the current process.  We first test to
			// see if the current instance is the generic "Total".  If it is, we skip
			// to the next instance of the for loop.  Then we get the ID of the
			// current process by calling GetCurrentProcessId().  However, we must
			// wait until we actually look at the PIDs to perform the skip of the
			// Idle and current processes.

			if (wcsncmp(GetInstanceName(pinstance),L"_Total",wcslen(L"_Total"))==0)
			{
				continue;
			}

			currentPID = GetCurrentProcessId();

			// Get the location of the PERF_COUNTER_BLOCK from the current
			// PERF_OBJECT_TYPE.  NOTE: There is only one PERF_COUNTER_BLOCK for each
			// object and it signifies the start of the Instance and Counter Data
			// structures.

			pcntblock = GetCounterBlockFromInstance(pinstance);

			// Get the location of the first PERF_COUNTER_DEFINITION from the current
			// PERF_OBJECT_TYPE.

			pdefn = FirstCounterDefn(pobject);

			// Loop through each PERF_COUNTER_DEFINITION structure in the current
			// PERF_OBJECT_TYPE.  For each one, check the titles array to see if the
			// PERF_COUNTER_DEFINITION object that we are looking at describes the
			// process data.

			for (counter_defn_index = 0; counter_defn_index < pobject->NumCounters; counter_defn_index++)
			{
				counter_data = GetCounterData(pdefn,pcntblock);
				
				if (titles[pdefn->CounterNameTitleIndex] != NULL)
				{
					if (_tcsncmp(titles[pdefn->CounterNameTitleIndex],TEXT("ID Process"),_tcslen(TEXT("ID Process")))==0)
					{
						// This is where we use the PIDs found earlier to skip the Idle,
						// System, and current processes.
						//
						// NOTE: The NT system process has a PID = 2 and the Win2K system
						// process has a PID = 8.

						if ((*(DWORD*)counter_data != currentPID) &&
							(*(DWORD*)counter_data != 0) &&
							(*(DWORD*)counter_data != 2) &&
							(*(DWORD*)counter_data != 8))
						{
							DumpPSInfo(*(ULONG*)counter_data);
						}
					}
				}
				
				pdefn = NextCounterDefn(pdefn);
			}

			// Get the next PERF_INSTANCE_DEFINITION structure stored in this object.

			pinstance = NextInstance(pinstance);
		}
	}
}

void PSInfo::StorePerfTitles(LPBYTE title_data, LPTSTR* titles, DWORD last_title_index)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  This function parses a string taken from the registry which is in the following
	//  form:
	//
	//    "index\0title\0nextindex\0nexttitle\0"...and ends with \0\0"
	//
	//  We put this into a previously allocated array of pointers (titles)
	//
	//------------------------------------------------------------------------------------//

	DWORD index = 0;
    LPTSTR data_string;

	// Zero out the array

	for(index = 0; index<=last_title_index;index++)
	{
	    titles[index] = NULL;
	}

	// Load it up with the values
	//
	// SMC-AUDIT: REVIEW: what if the registry key is not in the format you assume? (i.e.
	// null-terminated).  Then the lstrlent() will go into la-la land.  So, the question
	// is - can a malicious process owner modify the registry key values, e.g. the titles?
	//
	// ARB: We made sure title_data was double null terminated after we read it from the
	// registry.

	index = 0;	
    for(data_string = (LPTSTR)title_data; *data_string; data_string += (lstrlen(data_string)+1))
	{
		// SMC-AUDIT: REVIEW: I don't understand this _ttol call (I don't know what _ttol
		// is).  Is this guaranteed to return an index that's less than last_title_index?
		//
		// ARB: _ttol() is the TCHAR version of atol().  Better question is why use _ttol?
		// I need to figure out the whole unicode thing someday soon.

        index = _ttol(data_string);
        data_string += (lstrlen(data_string)+1);
        titles[index] = (LPTSTR)data_string;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~  Performance Data Navigation Functions  ~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

PERF_OBJECT_TYPE* PSInfo::GetFirstObject(PERF_DATA_BLOCK* pdata)
{
	 return (PERF_OBJECT_TYPE*)((DWORD)pdata + pdata->HeaderLength);
}

PERF_OBJECT_TYPE* PSInfo::NextObject(PERF_OBJECT_TYPE* pobject)
{
	return (PERF_OBJECT_TYPE*)((DWORD)pobject + pobject->TotalByteLength);
}

PERF_COUNTER_DEFINITION* PSInfo::FirstCounterDefn(PERF_OBJECT_TYPE* pobject)
{
	return (PERF_COUNTER_DEFINITION*)((DWORD)pobject + pobject->HeaderLength);
}

PERF_COUNTER_DEFINITION* PSInfo::NextCounterDefn(PERF_COUNTER_DEFINITION* pdef)
{
	return (PERF_COUNTER_DEFINITION*)((DWORD)pdef + pdef->ByteLength);
}

PERF_COUNTER_BLOCK* PSInfo::GetCounterBlockFromObject(PERF_OBJECT_TYPE* pobject)
{
	return (PERF_COUNTER_BLOCK*)((DWORD)pobject + pobject->DefinitionLength);
}

PERF_COUNTER_BLOCK* PSInfo::GetCounterBlockFromInstance(PERF_INSTANCE_DEFINITION* pinstance)
{
	return (PERF_COUNTER_BLOCK*)((DWORD)pinstance + pinstance->ByteLength);
}

PERF_INSTANCE_DEFINITION* PSInfo::FirstInstance(PERF_OBJECT_TYPE* pobject)
{
	return (PERF_INSTANCE_DEFINITION *)((DWORD)pobject + pobject->DefinitionLength);
}

PERF_INSTANCE_DEFINITION* PSInfo::NextInstance(PERF_INSTANCE_DEFINITION* pinstance)
{
	PERF_COUNTER_BLOCK* pblock;
	
	pblock = (PERF_COUNTER_BLOCK*)((DWORD)pinstance + pinstance->ByteLength);
    return (PERF_INSTANCE_DEFINITION*)((DWORD)pblock + pblock->ByteLength);
}

void* PSInfo::GetCounterData(PERF_COUNTER_DEFINITION* pdefn, PERF_COUNTER_BLOCK* pblock)
{
	return (void*)((DWORD)pblock + pdefn->CounterOffset);
}

LPWSTR PSInfo::GetInstanceName(PERF_INSTANCE_DEFINITION* pinstance)
{
	return (LPWSTR)((DWORD)pinstance + pinstance->NameOffset);
}

