枚举本地-远程NT系统进程

发表于:2007-07-14来源:作者:点击数: 标签:
作者:eyas 以下所说的都不是新技术,也不是新方法,偶只是收集,总结。好久以前写的,一直没好意思贴出来。让各位见笑了。 ------------------------------------------------------------------------------------------------------- 首先说明本文并没有什
作者:eyas

以下所说的都不是新技术,也不是新方法,偶只是收集,总结。好久以前写的,一直没好意思贴出来。让各位见笑了。
-------------------------------------------------------------------------------------------------------
 

首先说明本文并没有什么新的技术,只是做一些归纳总结吧。在这过程中参考了部分书籍和网上的一些资料,加上自己的一些理解,列举枚举本地/远程NT系统进程的的几种方法,希望对大家有些帮助。


   Windows2000中有个工具taskmgr.exe就可以比较详细的查看当前系统进程信息,但是那是Windows GUI程序,有时候是不是觉得命令行下的东西更方便呢?其实已经有不少命令行下的枚举系统进程的工具了,M$的Resource Kit中好象也有,但去了解他们是怎么实现的,自己动手做出来,是不是更有意思呢:)


   进程通常被定义为一个正在运行的程序的实例,它由两部分组成:


   <1>操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。


   <2>地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间,如线程的堆栈和堆分配空间。


   枚举系统进程的实现方法大概有四种,其中有一种可以用来枚举远程NT系统的进程,前提是有远程系统的管理员权限。


<<第一部分:调用PSAPI函数枚举系统进程>>


  M$的Windows NT开发小组开发了自己Process Status函数,包含在PSAPI.DLL文件中,这些函数只能在高于NT4.0以后的版本中使用。PSAPI一共有14个函数[实际PSAPI.DLL输出函数有19个,但其中有5个函数有两个版本,分别是ANSI和Unicode版本],通过调用这些函数,我们可以很方便的取得系统进程的所有信息,例如进程名、进程ID、父进程ID、进程优先级、映射到进程空间的模块列表等等。为了方便起见,以下的例子程序只获取进程的名字和ID。


  简单的程序如下:


/*************************************************************************


Module:ps.c


说明:调用PSAPI函数枚举系统进程名和ID,Only for NT/2000


*************************************************************************/


#include <windows.h>


#include <stdio.h>


#include "psapi.h"


#pragma comment(lib,"psapi.lib")





void PrintProcessNameAndID( DWORD processID )


{


  char szProcessName[MAX_PATH] = "unknown";


  //取得进程的句柄


  HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |


                  PROCESS_VM_READ,


                   FALSE, processID );


  //取得进程名称


  if ( hProcess )


  {


    HMODULE hMod;


    DWORD cbNeeded;


    if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) )


       GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName) );


  }


  //回显进程名称和ID


  printf( "\n%-20s%-20d", szProcessName, processID );


  CloseHandle( hProcess );


}





void main( )


{


  DWORD aProcesses[1024], cbNeeded, cProcesses;


  unsigned int i;


  //枚举系统进程ID列表


  if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )


    return;


  // Calculate how many process identifiers were returned.


  //计算进程数量


  cProcesses = cbNeeded / sizeof(DWORD);


  // 输出每个进程的名称和ID


  for ( i = 0; i < cProcesses; i++ )


     PrintProcessNameAndID( aProcesses[i] );


  return;


}


基于PSAPI,shotgun写了个比较完整的命令行下内存进程/模块查看器,可以显示内存中所有的进程及进程调用的所有模块文件(DLL),可以用来协助程序、DLL的调试,也可以用来查找DLL木马和后门。有兴趣的读者可以从http://www.patching.net/shotgun/ps.zip下载,压缩包包含C++源代码。





<<第二部分:调用ToolHelp API枚举本地系统进程>>


   在第一部分提到的PSAPI函数只能枚举NT系统的进程,在Windows9x环境下我们可以通过调用ToolHelp API函数来达到枚举系统进程的目的。M$的Windows NT开发小组因为不喜欢ToolHelp函数,所以没有将这些函数添加给Windows NT,所以他们开发了自己的Process Status函数,就是第一部分提到的PSAPI了。但是后来M$已经将ToolHelp函数添加给了Windows 2000。ToolHelp共有12个函数,通过调用这些函数可以方面的取得本地系统进程的详细信息,以下这个简单的例子只调用了三个函数,获取我们所需要系统进程名字和进程ID。程序如下:


/**********************************************************************


Module:ps.c


说明:调用ToolHelp函数枚举本地系统进程名和ID,Only for 9x/2000


**********************************************************************/


#include <windows.h>


#include <tlhelp32.h>


#include <stdio.h>


int main()


{


   HANDLE     hProcessSnap = NULL;


  PROCESSENTRY32 pe32   = {0};


  hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);


  if (hProcessSnap == (HANDLE)-1)


  {


     printf("\nCreateToolhelp32Snapshot() failed:%d",GetLastError());


    return 1;


  }


  pe32.dwSize = sizeof(PROCESSENTRY32);


   printf("\nProcessName     ProcessID");


  if (Process32First(hProcessSnap, &pe32))


  {


    do


    {


       printf("\n%-20s%d",pe32.szExeFile,pe32.th32ProcessID);


    }


    while (Process32Next(hProcessSnap, &pe32));


  }


  else


  {


     printf("\nProcess32Firstt() failed:%d",GetLastError());


  }


  CloseHandle (hProcessSnap);


  return 0;


}





<<第三部分:调用NTDLL.DLL中未公开API枚举本地系统进程>>


   第一部分和第二部分说的是调用MS公开的API来枚举系统进程,在NTDLL.DLL中其实有一个未公开API,也可以用来枚举系统进程。此方法是从别处看来的,我可没这本事自己发现哦,出处记不清楚了,好像是pwdump2 中的源代码中的一部分吧。


    OK!那个未公开API就是NtQuerySystemInformation,使用方法如下,以下代码是从pwdump2中修改了一点点而来的:


////////////////////////////////////////////////////////////////////////////////////////////////


#include <windows.h>


#include <stdio.h>


#include <stdlib.h>





typedef unsigned long NTSTATUS;





typedef unsigned short USHORT;


typedef unsigned long ULONG;


typedef unsigned long DWORD;


typedef long LONG;





typedef __int64 LONGLONG;





typedef struct {


  USHORT Length;


  USHORT MaxLen;


  USHORT *Buffer;


} UNICODE_STRING;





struct process_info {


  ULONG NextEntryDelta;


  ULONG ThreadCount;


  ULONG Reserved1[6];


  LARGE_INTEGER CreateTime;


  LARGE_INTEGER UserTime;


  LARGE_INTEGER KernelTime;


  UNICODE_STRING ProcessName;


  ULONG BasePriority;


  ULONG ProcessId;


};





typedef NTSTATUS (__stdcall *NtQuerySystemInformation1)(


    IN ULONG SysInfoClass,


         IN OUT PVOID SystemInformation,


         IN ULONG SystemInformationLength,


         OUT PULONG RetLen


         );


int main()


{


  HINSTANCE hNtDll;


  NtQuerySystemInformation1 NtQuerySystemInformation;


  NTSTATUS rc;


  ULONG ulNeed = 0;


  void *buf = NULL;


  size_t len = 0;


  struct process_info *p ;


  int done;





  hNtDll = LoadLibrary ("NTDLL");


  if (!hNtDll)


    return 0;


  NtQuerySystemInformation = (NtQuerySystemInformation1)GetProcAddress (hNtDll,


                           "NtQuerySystemInformation");


    if (!NtQuerySystemInformation)


       return 0;





  do {


    len += 0x1000;


    buf = realloc (buf, len);


    if (!buf)


       return 0;


    rc = NtQuerySystemInformation (5, buf, len, &ulNeed);


  } while (rc == 0xc0000004); // STATUS_INFO_LEN_MISMATCH





  if (rc <0) {


    free (buf);


    return 0;


  }





   printf("\nProcessName     ProcessID");





  p = (struct process_info *)buf;


  done = 0;





  while (!done) {


    if ((p->ProcessName.Buffer != 0))


    {


       printf("\n%-20S%d",p->ProcessName.Buffer,p->ProcessId);


    }


    done = p->NextEntryDelta == 0;


    p = (struct process_info *)(((char *)p) + p->NextEntryDelta);


  }


  free (buf);


  FreeLibrary (hNtDll);


  return 0;


}





<<第四部分:从PDH中取得本地/远程系统进程信息>>


   前面说的三种方法都只能枚举本地的系统进程,如何枚举远程系统的进程呢?目前我只知道从PDH中取得进程信息。


   OK!我先简单的说说PDH是什么东西,hoho~难的偶也不会。PDH是英文Performance Data Helper的缩写,Windows NT一直在更新这个称为Performance Data的数据库,这个数据库包含了大量的信息,例如CPU使用率,内存使用率,系统进程信息等等一大堆有用的信息,可以通过注册表函数来访问。注意哦,Windows 9x中并没有配置这个数据库。但是,这个数据库中的信息布局很复杂,很多人并不愿意使用它,包括我。而且刚开始的时候,它也没有自己特定的函数,只能通过现有的注册表函数来操作。后来,为了使该数据库的使用变得容易,MS开发了一组Performance Data Helper函数,包含在PDH.DLL文件中。


    Windows 2000默认是允许远程注册表操作的,所以我们就可以通过连接远程系统的注册表,从它的PDH中取得我们所需要的系统进程信息了,当然这需要远程系统的Admin权限。


    OK!我们下面所举的例子是直接利用注册表函数来从本地/远程系统的PDH数据库中取得我们所需要的数据的,我们并没有利用PDH API。


    程序代码如下:


/**************************************************************************


Module:ps.c


Author:mikeblas@nwlink.com


Modify:ey4s<ey4s@21cn.com>


Http://www.ey4s.org


Date:2001/6/23


**************************************************************************/


#include <stdio.h>


#include <windows.h>


#include <Winnetwk.h>





#define INITIAL_SIZE     51200


#define EXTEND_SIZE      12800


#define REGKEY_PERF     "software\\microsoft\\windows nt\\currentversion\\perflib"


#define REGSUBKEY_COUNTERS "Counters"


#define PROCESS_COUNTER   "process"


#define PROCESSID_COUNTER  "id process"


#define UNKNOWN_TASK     "unknown"


#define MaxProcessNum      52//最大进程数量





#pragma comment(lib,"mpr.lib")





typedef struct ProcessInfo


{


    char ProcessName[128];


    DWORD dwProcessID;


}pi;





void banner();


int ConnIPC(char *,char *,char *);


DWORD GetProcessInfo(pi *,char *,char *,char *);





int main(int argc,char **argv)


{


    int i,iRet;


    pi TaskList[MaxProcessNum];


    banner();


    if(argc==1)


    {


       iRet=GetProcessInfo(TaskList,NULL,NULL,NULL);


       printf("\nProcess Info for [LOCAL]:");


    }


    else if(argc==4)


    {


       iRet=GetProcessInfo(TaskList,argv[1],argv[2],argv[3]);


       printf("\nProcess Info for [%s]:",argv[1]);


    }


    else


    {


       printf("\nUsage:%s <ip> <UserName> <Pass>",argv[0]);


        return 1;


    }


    if(iRet>0)   


       for(i=0,printf("\nProcessName     ProcessID");


            i<iRet;


            printf("\n%-20s %d",TaskList[i].ProcessName,TaskList[i].dwProcessID),i++); 


    return 0;


}


DWORD GetProcessInfo(pi *ProList,char *ip,char *user,char *pass)


{


  DWORD rc,dwType,dwSize,i,dwProcessIdTitle,dwProcessIdCounter,dwRet=-1;


   HKEY             hKeyNames;


   LPSTR             buf = NULL,p,p2;


   CHAR             szSubKey[1024],szProcessName[MAX_PATH];


   PPERF_DATA_BLOCK       pPerf;


   PPERF_OBJECT_TYPE       pObj;


  PPERF_INSTANCE_DEFINITION   pInst;


   PPERF_COUNTER_BLOCK      pCounter;


   PPERF_COUNTER_DEFINITION   pCounterDef;


    HKEY       ghPerfKey =NULL, // get perf data from this key


          ghMachineKey = NULL; // get title index from this key


    BOOL bRemote=FALSE;


  //


  // Look for the list of counters.  Always use the neutral


  // English version, regardless of the local language. We


  // are looking for some particular keys, and we are always


  // going to do our looking in English.  We are not going


  // to show the user the counter names, so there is no need


  // to go find the corresponding name in the local language.


  //


    __try


    {


       if((ip)&&(user)&&(pass))


        {


            if(ConnIPC(ip,user,pass)!=0)


           {


              printf("\nConnect to %s failed.",ip);


              __leave;


           }


            else


              bRemote=TRUE;


        }


        //连接本地or远程注册表


       if(RegConnectRegistry(ip,HKEY_PERFORMANCE_DATA,


            &ghPerfKey)!=ERROR_SUCCESS)


        {


            printf("\nRegConnectRegistry() 1 failed:%d",GetLastError());


            __leave;


        }


       if(RegConnectRegistry(ip,HKEY_LOCAL_MACHINE,


            &ghMachineKey)!=ERROR_SUCCESS)


        {


            printf("\nRegConnectRegistry() 2 failed:%d",GetLastError());


            __leave;


        }





       sprintf( szSubKey, "%s\\%03x", REGKEY_PERF,MAKELANGID( LANG_ENGLISH, SUBLANG_NEUTRAL));


       if(RegOpenKeyEx(ghMachineKey,szSubKey,0,KEY_READ,&hKeyNames)!=ERROR_SUCCESS)


            __leave;


        // 从counter names取得需要的缓冲区大小


       if(RegQueryValueEx(hKeyNames,REGSUBKEY_COUNTERS,NULL,&dwType,NULL,&dwSize)!= ERROR_SUCCESS)


            __leave;


        //分配内存


        buf = (LPSTR) malloc( dwSize );


        if (buf == NULL)


            __leave;


       memset( buf, 0, dwSize );





        // read the counter names from the registry


       if(RegQueryValueEx(ghPerfKey,REGSUBKEY_COUNTERS,NULL,&dwType,(LPBYTE) buf,&dwSize)!= ERROR_SUCCESS)


            __leave;


        //


        // now loop thru the counter names looking for the following counters:


        //


        //   1.  "Process"     process name


        //   2. "ID Process"    process id


        //


        // the buffer contains multiple null terminated strings and then


        // finally null terminated at the end. the strings are in pairs of


        // counter number and counter name.


        //


        p = buf;


        while (*p)


        {


           if (p>buf)


             for( p2=p-2; isdigit(*p2); p2--) ;


           if (stricmp(p, PROCESS_COUNTER) == 0)


           {


              // look backwards for the counter number


             for( p2=p-2; isdigit(*p2); p2--) ;


              strcpy( szSubKey, p2+1 );


           }


            else if (stricmp(p, PROCESSID_COUNTER) == 0)


           {


              // look backwards for the counter number


             for( p2=p-2; isdigit(*p2); p2--) ;


              dwProcessIdTitle = atol( p2+1 );


           }


           // next string


           p += (strlen(p) + 1);


        }


        // free the counter names buffer


       free( buf );


        // allocate the initial buffer for the performance data


       dwSize = INITIAL_SIZE;


        buf = (LPSTR) malloc( dwSize );





        while (TRUE)


        {


           if (buf == NULL)


              __leave;


            memset( buf, 0, dwSize );


            rc=RegQueryValueEx(ghPerfKey,szSubKey,NULL,&dwType,(LPBYTE) buf,&dwSize);


            pPerf = (PPERF_DATA_BLOCK) buf;


           // check for suclearcase/" target="_blank" >ccess and valid perf data block signature


           if ((rc == ERROR_SUCCESS) &&


                  (dwSize > 0) &&


                 (pPerf)->Signature[0] == (WCHAR)'P' &&


                 (pPerf)->Signature[1] == (WCHAR)'E' &&


                 (pPerf)->Signature[2] == (WCHAR)'R' &&


                 (pPerf)->Signature[3] == (WCHAR)'F' )


              break;


           // if buffer is not big enough, reallocate and try again


           if (rc == ERROR_MORE_DATA)


           {


              dwSize += EXTEND_SIZE;


              buf = (LPSTR) realloc( buf, dwSize );


           }


            else __leave;


        }


        // set the perf_object_type pointer


        pObj = (PPERF_OBJECT_TYPE) ((DWORD)pPerf + pPerf->HeaderLength);


        //loop thru the performance counter definition records looking


        //for the process id counter and then save its offset


       pCounterDef = (PPERF_COUNTER_DEFINITION) ((DWORD)pObj + pObj->HeaderLength);


        for (i=0; i<(DWORD)pObj->NumCounters; i++)


        {


           if (pCounterDef->CounterNameTitleIndex == dwProcessIdTitle)


           {


              dwProcessIdCounter = pCounterDef->CounterOffset;


              break;


           }


            pCounterDef++;


        }





        pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pObj + pObj->DefinitionLength);


        // loop thru the performance instance data extracting each process name


        // and process id


        for (i=0; i < (DWORD)pObj->NumInstances-1 && i<MaxProcessNum; i++)


        {


           // pointer to the process name


           p = (LPSTR) ((DWORD)pInst + pInst->NameOffset);


           // convert it to ascii


           rc = WideCharToMultiByte( CP_ACP,0,(LPCWSTR)p,-1,szProcessName,sizeof(szProcessName),NULL,NULL);


           // if we cant convert the string then use a default value


           if (!rc) strcpy( ProList[i].ProcessName, UNKNOWN_TASK );


            else strncpy(ProList[i].ProcessName, szProcessName,sizeof(ProList[i].ProcessName)-1);


           // get the process id


            pCounter = (PPERF_COUNTER_BLOCK) ((DWORD)pInst + pInst->ByteLength);


            ProList[i].dwProcessID = *((LPDWORD) ((DWORD)pCounter + dwProcessIdCounter));


           // next process


            pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pCounter + pCounter->ByteLength);


        }


       dwRet=i;


    }//end of try


    __finally


    {


        if (buf) free( buf );


       RegCloseKey( hKeyNames );


       RegCloseKey( HKEY_PERFORMANCE_DATA );


       if(bRemote)


        {


            char tmp[52],tmp2[96];


            strncpy(tmp,ip,sizeof(tmp)-1);


            wsprintf(tmp2,"\\\\%s\\ipc$",tmp);


            WNetCancelConnection2(tmp2,CONNECT_UPDATE_PROFILE,TRUE);


        }


    }


    return dwRet;


}


///////////////////////////////////////////////////////////////////////////////////////


int ConnIPC(char *RemoteName,char *User,char *Pass)


{


    NETRESOURCE nr;


    char RN[50]="\\\\";





    strncat(RN,RemoteName,sizeof(RN)-11);


    strcat(RN,"\\ipc$");





    nr.dwType=RESOURCETYPE_ANY;


    nr.lpLocalName=NULL;


    nr.lpRemoteName=RN;


    nr.lpProvider=NULL;





    if(WNetAddConnection2(&nr,Pass,User,FALSE)==NO_ERROR)


        return 0;


    else


        return 1;


}


///////////////////////////////////////////////////////////////////////////////////////


void banner()


{


    printf("\nPsList ==>Local and Remote process list"


            "\nPower by ey4s<ey4s@21cn.com>"


            "\nhttp://www.ey4s.org"


            "\n2001/6/22\n");


}


/////////////////////////////////////////////////////////////////////////////////////


   程序在Windows2000、VC++6.0环境下编译,运行良好。注意哦,远程机器要允许IPC连接和远程操作注册表才可以哦,并且需要Admin权限,编译好的程序在我的主页http://www.ey4s.org有下载。

原文转自:http://www.ltesting.net