再谈进程与端口的映射(WSS-Articles-02003)

Author  : ilsy 
Email   :
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<>)

第一个参数是一个枚举常数,设置要查询的系统信息类型,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<>)
第二个参数输出查询的结果
第三个参数设置缓冲区的长度
第四个参数返回函数正确执行需要的缓冲区的大小
代码如下:
#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<>
WebCrazy<<小议Windows NT/2000分页机制>>
NTDDK

关于我们:

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

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


与我联系: , 欢迎到我的留言簿