首页  编辑  

列出当前进程和端口的对应

Tags: /超级猛料/Network.网络通讯/Sockt编程/   Date Created:

列出当前进程和端口的对应

再谈进程与端口的映射

发布者: ILSY(at: er263.net) · 发布日期: 2002-02-05 · 来源: whitecell.org

Author  : ilsy

Email   : ilsy@whitecell.org

HomePage: http://www.whitecell.org

日  期:2002-02-06

类  别:安全

关键字:进程 PDE PTE 分页 内核对象 线性地址 物理地址

   关于进程与端口映射的文章已经有很多了,我把我对fport的分析也写出来,让大家知道fport是如何工作的.

fport.exe是由foundstone team出品的免费软件,可以列出系统中所有开放的端口都是由那些进程打开的.而下

面所描述的方法是基于fport v1.33的,如果和你机器上的fport有出入,请检查fport版本.

   首先,它检测当前用户是否拥有管理员权限(通过读取当前进程的令牌可知当前用户是否具有管理权限,请参考

相关历程),如果没有,打印一句提示后退出,然后设置当前进程的令牌,接着,用ZwOpenSection函数打开内核对象

\Device\PhysicalMemory,这个对象用于对系统物理内存的访问.ZwOpenSection函数的原型如下:

NTSYSAPI

NTSTSTUS

NTAPI

ZwOpenSection(

   Out PHANDLE sectionHandle;

   IN ACCESS_MASK DesiredAccess;

   IN POBJECT_ATTRIBUTES ObjectAttributes

   };

(见ntddk.h)

第一个参数得到函数执行成功后的句柄

第二个参数DesiredAccess为一个常数,可以是下列值:

   #define SECTION_QUERY       0x0001

   #define SECTION_MAP_WRITE   0x0002

   #define SECTION_MAP_READ    0x0004

   #define SECTION_MAP_EXECUTE 0x0008

   #define SECTION_EXTEND_SIZE 0x0010

   #define SECTION_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED|SECTION_QUERY|\

                           SECTION_MAP_WRITE |      \

                           SECTION_MAP_READ |       \

                           SECTION_MAP_EXECUTE |    \

                           SECTION_EXTEND_SIZE)

   (见ntddk.h)

第三个参数是一个结构,包含要打开的对象类型等信息,结构定义如下:

   typedef struct _OBJECT_ATTRIBUTES {

       ULONG Length;

       HANDLE RootDirectory;

       PUNICODE_STRING ObjectName;

       ULONG Attributes;

       PVOID SecurityDescriptor;        // Points to type SECURITY_DESCRIPTOR

       PVOID SecurityQualityOfService;  // Points to type SECURITY_QUALITY_OF_SERVICE

   } OBJECT_ATTRIBUTES;

   typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;

   (见ntdef.h)

对于这个结构的初始化用一个宏完成:

   #define InitializeObjectAttributes( p, n, a, r, s ) { \

       (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \

       (p)->RootDirectory = r;                             \

       (p)->Attributes = a;                                \

       (p)->ObjectName = n;                                \

       (p)->SecurityDescriptor = s;                        \

       (p)->SecurityQualityOfService = NULL;               \

       }

   (见ntdef.h)

那么,打开内核对象\Device\PhysicalMemory的语句如下:

WCHAR    PhysmemName[] =        L"\\Device\\PhysicalMemory";

void *  pMapPhysicalMemory;

HANDLE  pHandle;

bool    OpenPhysicalMemory()

{

   NTSTATUS    status;

   UNICODE_STRING    physmemString;

   OBJECT_ATTRIBUTES attributes;

   RtlInitUnicodeString( &physmemString, PhysmemName ); //初始化Unicode字符串,函数原型见ntddk.h    

   InitializeObjectAttributes( &attributes, &physmemString,

                    OBJ_CASE_INSENSITIVE, NULL, NULL ); //初始化OBJECT_ATTRIBUTES结构

   status = ZwOpenSection(pHandle, SECTION_MAP_READ, &attributes ); //打开内核对象\Device\PhysicalMemory,获得句柄

   if( !NT_SUCCESS( status ))

       return false;

   pMapPhysicalMemory=MapViewOfFile(pHandle,FILE_MAP_READ,

                          0,0x30000,0x1000);

   //从内存地址0x30000开始映射0x1000个字节

   if( GetLastError()!=0)

       return false;                    

   return true;

}

   为什么要从0x30000开始映射呢,是这样,我们知道,在Windows NT/2000下,系统分为内核模式和用户模式,也就是我们

所说的Ring0和Ring3,在Windows NT/2000下,我们所能够看到的进程都运行在Ring3下,一般情况下,系统进程(也就是System

进程)的页目录(PDE)所在物理地址地址为0x30000,或者说,系统中最小的页目录所在的物理地址为0x30000.而页目录(PDE)由

1024项组成,每项均指向一页表(PTE),每一页表也由1024个页组成,而每页的大小为4K,1024*4=4096(0x1000),所以,上面从物

理地址0x30000开始映射了0x1000个字节.(具体描述见WebCrazy的文章<<小议Windows NT/2000的分页机制>>)

   程序打开打开内核对象\Device\PhysicalMemory后,继续用函数ZwOpenFile打开内核对象\Device\Tcp和Device\Udp,ZwOpenFile

函数的原型如下:

NTSYSAPI

NTSTATUS

NTAPI

ZwOpenFile(

   OUT PHANDLE FileHandle,

   IN ACCESS_MASK DesiredAccess,

   IN POBJECT_ATTRIBUTES ObjectAttributes,

   OUT PIO_STATUS_BLOCK IoStatusBlock,

   IN ULONG ShareAccess,

   IN ULONG OpenOptions

   );

(见ntddk.h)

第一个参数返回打开对象的句柄

第二个参数DesiredAccess为一个常数,可以是下列值:

   #define FILE_READ_DATA            ( 0x0001 )    // file & pipe

   #define FILE_LIST_DIRECTORY       ( 0x0001 )    // directory

   #define FILE_WRITE_DATA           ( 0x0002 )    // file & pipe

   #define FILE_ADD_FILE             ( 0x0002 )    // directory

   #define FILE_APPEND_DATA          ( 0x0004 )    // file

   #define FILE_ADD_SUBDIRECTORY     ( 0x0004 )    // directory

   #define FILE_CREATE_PIPE_INSTANCE ( 0x0004 )    // named pipe

   #define FILE_READ_EA              ( 0x0008 )    // file & directory

   #define FILE_WRITE_EA             ( 0x0010 )    // file & directory

   #define FILE_EXECUTE              ( 0x0020 )    // file

   #define FILE_TRAVERSE             ( 0x0020 )    // directory

   #define FILE_DELETE_CHILD         ( 0x0040 )    // directory

   #define FILE_READ_ATTRIBUTES      ( 0x0080 )    // all

   #define FILE_WRITE_ATTRIBUTES     ( 0x0100 )    // all

   #define FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)

   #define FILE_GENERIC_READ         (STANDARD_RIGHTS_READ     |\

                                      FILE_READ_DATA           |\

                                      FILE_READ_ATTRIBUTES     |\

                                      FILE_READ_EA             |\

                                      SYNCHRONIZE)

   #define FILE_GENERIC_WRITE        (STANDARD_RIGHTS_WRITE    |\

                                      FILE_WRITE_DATA          |\

                                      FILE_WRITE_ATTRIBUTES    |\

                                      FILE_WRITE_EA            |\

                                      FILE_APPEND_DATA         |\

                                      SYNCHRONIZE)

   #define FILE_GENERIC_EXECUTE      (STANDARD_RIGHTS_EXECUTE  |\

                                      FILE_READ_ATTRIBUTES     |\

                                      FILE_EXECUTE             |\

                                      SYNCHRONIZE)

       (见ntdef.h)

第三个参数是一个结构,包含要打开的对象类型等信息,结构定义见上面所述

第四个参数返回打开对象的属性,是一个结构,定义如下:

   typedef struct _IO_STATUS_BLOCK {

       union {

           NTSTATUS Status;

           PVOID Pointer;

       };

       ULONG_PTR Information;

   } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

   #if defined(_WIN64)

   typedef struct _IO_STATUS_BLOCK32 {

       NTSTATUS Status;

       ULONG Information;

   } IO_STATUS_BLOCK32, *PIO_STATUS_BLOCK32;

   #endif

   (见ntddk.h)

第五个参数ShareAccess是一个常数,可以是下列值:

   #define FILE_SHARE_READ                 0x00000001  // winnt

   #define FILE_SHARE_WRITE                0x00000002  // winnt

   #define FILE_SHARE_DELETE               0x00000004  // winnt

   (见ntddk.h)

第六个参数OpenOptions也是一个常数,可以是下列的值:

   #define FILE_DIRECTORY_FILE                     0x00000001

   #define FILE_WRITE_THROUGH                      0x00000002

   #define FILE_SEQUENTIAL_ONLY                    0x00000004

   #define FILE_NO_INTERMEDIATE_BUFFERING          0x00000008

   #define FILE_SYNCHRONOUS_IO_ALERT               0x00000010

   #define FILE_SYNCHRONOUS_IO_NONALERT            0x00000020

   #define FILE_NON_DIRECTORY_FILE                 0x00000040

   #define FILE_CREATE_TREE_CONNECTION             0x00000080

   #define FILE_COMPLETE_IF_OPLOCKED               0x00000100

   #define FILE_NO_EA_KNOWLEDGE                    0x00000200

   #define FILE_OPEN_FOR_RECOVERY                  0x00000400

   #define FILE_RANDOM_ACCESS                      0x00000800

   #define FILE_DELETE_ON_CLOSE                    0x00001000

   #define FILE_OPEN_BY_FILE_ID                    0x00002000

   #define FILE_OPEN_FOR_BACKUP_INTENT             0x00004000

   #define FILE_NO_COMPRESSION                     0x00008000

   #define FILE_RESERVE_OPFILTER                   0x00100000

   #define FILE_OPEN_REPARSE_POINT                 0x00200000

   #define FILE_OPEN_NO_RECALL                     0x00400000

   #define FILE_OPEN_FOR_FREE_SPACE_QUERY          0x00800000

   #define FILE_COPY_STRUCTURED_STORAGE            0x00000041

   #define FILE_STRUCTURED_STORAGE                 0x00000441

   #define FILE_VALID_OPTION_FLAGS                 0x00ffffff

   #define FILE_VALID_PIPE_OPTION_FLAGS            0x00000032

   #define FILE_VALID_MAILSLOT_OPTION_FLAGS        0x00000032

   #define FILE_VALID_SET_FLAGS                    0x00000036

   (见ntddk.h)

       

那么,打开内核对象\Device\Tcp和\Device\Udp的语句如下:    

WCHAR physmemNameTcp[]=L"\\Device\\TCP";

WCHAR physmemNameUdp[]=L"\\Device\\UDP";

HANDLE pTcpHandle;

HANDLE pUdpHandle;

HANDLE OpenDeviceTcpUdp(WCHAR * deviceName)

{

   NTSTATUS    status;

   UNICODE_STRING    physmemString;

   OBJECT_ATTRIBUTES attributes;

   IO_STATUS_BLOCK iosb;

   HANDLE pDeviceHandle;

   RtlInitUnicodeString(&physmemString, deviceName);    

   if(GetLastError()!=0)

       return NULL;

   InitializeObjectAttributes( &attributes,&physmemString,

                           OBJ_CASE_INSENSITIVE,0, NULL );

   status = ZwOpenFile ( &pDeviceHandle,0x100000, &attributes, &iosb, 3,0);

   if( !NT_SUCCESS( status ))

       return NULL;

}

   接着,程序用ZwQuerySystemInformation函数获得系统当前所以进程的所建立的句柄及其相关信息,函数的原型如下:

NTSYSAPI

NTSTATUS

NTAPI

ZwQuerySystemInformation(

   IN SYSTEM_INFORMATION_CLASS SystemInformationClass,

   IN OUT PVOID SystemInformation,

   IN ULONG SystemInformationLength,

   OUT PULONG ReturnLength OPTIONAL

   };

(这个函数结构Microsoft没有公开,参见Gary Nebbett<<Windows NT/2000 Native API Reference>>)

第一个参数是一个枚举常数,设置要查询的系统信息类型,ZwQuerySystemInformation支持54个系统信息的查询,我们要用到的

是它的第16号功能,进行SystemHandleInformation查询.

SYSTEM_HANDLE_INFORMATION结构定义如下:

   typedef struct _SYSTEM_HANDLE_INFORMATION{

       ULONG ProcessID;        //进程的标识ID

       UCHAR ObjectTypeNumber;        //对象类型

       UCHAR Flags;             //0x01 = PROTECT_FROM_CLOSE,0x02 = INHERIT

       USHORT Handle;             //对象句柄的数值

       PVOID  Object;            //对象句柄所指的内核对象地址

       ACCESS_MASK GrantedAccess;      //创建句柄时所准许的对象的访问权

   }SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

   (这个函数结构Microsoft没有公开,参见Gary Nebbett<<Windows NT/2000 Native API Reference>>)

第二个参数输出查询的结果

第三个参数设置缓冲区的长度

第四个参数返回函数正确执行需要的缓冲区的大小

代码如下:

#define SystemHandleInformation 16

PULONG GetHandleList()

{

   ULONG cbBuffer = 0x1000;    //先设定一个较小的缓冲空间

   PULONG pBuffer = new ULONG[cbBuffer]; //分配内存

   NTSTATUS Status;

   do

       {

       Status = ZwQuerySystemInformation(

                   SystemHandleInformation,

                   pBuffer, cbBuffer * sizeof * pBuffer, NULL);

       if (Status == STATUS_INFO_LENGTH_MISMATCH)

       {

           //如果返回的错误信息为缓冲区长度不够,那么重新分配内存

           delete [] pBuffer;

           pBuffer = new ULONG[cbBuffer *= 2];

       }

       else if (!NT_SUCCESS(Status))

       {

           //如果是其他错误信息,返回

           delete [] pBuffer;

           return false;

       }

       }

       while (Status == STATUS_INFO_LENGTH_MISMATCH);

   return pBuffer;

}

因为如果一个进程打开了端口,那么它肯定会建立类型为\Device\Tcp和\Device\Udp的内核对象,所以,我们在当前进程中打开

上述的两个内核对象,在打开的同时保存了打开的句柄,这样,我们可以在上面获得的句柄列表中的当前进程中查找对象句柄的

数值和我们保存的两个打开的内核对象的句柄数值相同的句柄,并得到其句柄所指向的内核对象的地址.代码如下:

DWORD TcpHandle;

DWORD UdpHandle;

DWORD GetTcpUdpObject(PULONG pBuffer,HANDLE pHandle,DWORD ProcessId)

{

   DWORD objTYPE1,objTYPE2,HandleObject;

   PSYSTEM_HANDLE_INFORMATION pProcesses = (PSYSTEM_HANDLE_INFORMATION)(pBuffer+1);

 

   for (i=0;i< * pBuffer;i++)

   {

       if ((pProcesses[i].ProcessID) == ProcessId)

       {

           objTYPE1 = (DWORD)hDeviceTcpUdp;

           objTYPE2 = (DWORD)pProcesses[i].Handle;

           if(objTYPE1==objTYPE2)

           {

               HandleObject = (DWORD)pProcesses.Object;

               return HandleObject;

       }

   }

   return 0;

}

这个内核对象地址是一个线性地址,我们需要把这个地址转换为物理地址,并得到一些相关的数据.在fport中,换算是这样进行的:

(具体描述见WebCrazy的文章<<小议Windows NT/2000的分页机制>>)

void * NewmapPhy;

void GetPTE(DWORD objAddress)

{

   DWORD physmemBuff;

   DWORD newAddress1,newAddress2,newAddress3,newAddress4;

   DWORD * newAddress;

   physmemBuff = (DWORD)pMapPhysicalMemory;

   newAddress1 = physmemBuff+(objAddress>>0x16)*4;

   newAddress = (DWORD *)newAddress1;

   newAddress1 = * newAddress;

   newAddress2 = objAddress & 0x3FF000;

   newAddress3 = newAddress1 & 0x0FFFFF000;

   newAddress4 = newAddress2 + newAddress3;

   NewmapPhy = MapViewOfFile(ghPhysicalMemory,FILE_MAP_READ,0,newAddress4,0x1000);

   //重新映射物理内存,得到当前线性地址所指向的PTE的物理地址内容

}

然后在根据内核对象的线性地址得到这个地址所指向的物理页,得到体现当前内核对象内容的页,其结构如下:

typedef struct {

   ULONG Present;

   ULONG WriteTable;

   ULONG User;

   ULONG WriteThru;

   ULONG NoCache;

   ULONG Accessed;

   ULONG Dirty;

   ULONG PageSize;

   ULONG Global;

   ULONG Available;

   ULONG Pfn;

} PTE, *PPTE;

(注:我不能保证这个结构的正确性,但我们只会用到其中的两个值,对程序来说,这个结构是可以工作的,^_^)

代码如下:

ULONG CurrWriteTable;

ULONG NoCache;

void GetMustPar(DWORD objAddress)

{

   DWORD CurrAddress;

   CurrAddress = objAddress & 0xFFF;

   PPTE pte = (PPTE)(VOID *)((DWORD)NewmapPhy+CurrAddress);

   CurrWriteTable = pte->WriteTable;

   CurrNoCache = Pte->NoCache;

}

好了,我们现在想要得到的都已经得到了,下面需要做的是遍历进程,用每一个进程中的每一个句柄(呵呵,不是每一个句柄,

在Windows NT下,\Device\Tcp和\Device\Udp的句柄类型值为0x16,在Windows 2000下这个值为0x1A)的核心地址用上面所描

述的办法得到其PTE内容,得到其WriteTable值,如果与内核对象\Device\Tcp和\Device\Udp相等,那么这个句柄就有可能打开

了一个端口,再对这个句柄进行确认,就可以了.确认的代码如下:

typedef struct _TDI_CONNECTION_INFO {

   ULONG          State;

   ULONG          Event;

   ULONG          TransmittedTsdus;

   ULONG          ReceivedTsdus;

   ULONG          TransmissionErrors;

   ULONG          ReceiveErrors;

   LARGE_INTEGER  Throughput;

   LARGE_INTEGER  Delay;

   ULONG          SendBufferSize;

   ULONG          ReceiveBufferSize;

   BOOLEAN        Unreliable;

} TDI_CONNECTION_INFO, *PTDI_CONNECTION_INFO;

typedef struct _TDI_CONNECTION_INFORMATION {

   LONG   UserDataLength;

   PVOID  UserData;

   LONG   OptionsLength;

   PVOID  Options;

   LONG   RemoteAddressLength;

   PVOID  RemoteAddress;

} TDI_CONNECTION_INFORMATION, *PTDI_CONNECTION_INFORMATION;

(以上结构见tdi.h)

void GetOpenPort(DWORD dwProcessesID,USHORT Handle,int NoCache)

//dwProcessesID为进程标识ID

//Handle为进程打开的句柄,并且经过比较为\Device\Tcp或\Device\Udp类型

//NoCache为PTE结构中的一个值

{

   HANDLE hProc,DupHandle=NULL;

   HANDLE hEven=NULL;

       OVERLAPPED overlap;

   u_short openport;

   int i=0;

   char procName[256]={0};

   int  portflag=0;

   overlap.Internal = 0;

   overlap.InternalHigh = 0;

   overlap.Offset = 0;

   overlap.OffsetHigh = 0;

   hEven=CreateEvent(0,1,0,0);

   overlap.hEvent = hEven;

   hProc = OpenProcess(PROCESS_DUP_HANDLE,

                       0,

                       dwProcessesID);

   if(hProc)

   {

       DuplicateHandle(hProc,

                       (HANDLE)Handle,

                       GetCurrentProcess(),

                       &DupHandle,

                       0,

                       FALSE,

                       2);

       CloseHandle( hProc );

       if(DupHandle)

       {

           TDI_CONNECTION_INFO    TdiConnInfo={0};

           TDI_CONNECTION_INFORMATION TdiConnInformation={0};

           DWORD dwRetu=0;    

           if(NoCache==0x2)

           {

               TdiConnInformation.RemoteAddressLength= 4;

               if(DeviceIoControl(DupHandle,0x210012,

                           &TdiConnInformation,sizeof(TdiConnInformation),

                           &TdiConnInfo,sizeof(TdiConnInfo),

                           0,&overlap))

               //进行TDI查询,得到连接的相关信息

               {

                   openport = ntohs((u_short)TdiConnInfo.ReceivedTsdus);

                   procname = GetProcName(dwProcessesID);  //得到进程标识ID的进程名称

                   printf("PID = %4d ProcessName = %15s PORT = %4d\n",dwProcessesID,procName,openport);

               }

           }

           if(NoCache==0x1)

           {

               TdiConnInformation.RemoteAddressLength= 3;

               if(DeviceIoControl(DupHandle,0x210012,

                           &TdiConnInformation,sizeof(TdiConnInformation),

                           &TdiConnInfo,sizeof(TdiConnInfo),

                           0,&overlap))

               //进行TDI查询,得到连接的相关信息

               {

                   openport = ntohs((u_short)TdiConnInfo.ReceivedTsdus);

                   procname = GetProcName(dwProcessesID);  //得到进程标识ID的进程名称

                   printf("PID = %4d ProcessName = %15s PORT = %4d\n",dwProcessesID,procName,openport);

               }

           }

       }

   }

   CloseHandle(hEven);

   CloseHandle(DupHandle);

}

以上是我对fport.exe的分析及其实现代码,演示程序可以从whitecell.org下载,如果你发现有问题,请通知我,^_^

参考:

fport.exe

Gary Nebbett<<Windows NT/2000 Native API Reference>>

WebCrazy<<小议Windows NT/2000分页机制>>

NTDDK

关于我们:

WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。

WSS 主页:http://www.whitecell.org/

WSS 论坛:http://www.whitecell.org/forum/