端口映射:再谈进程和端口的映射


   有关进程和端口映射文章已经有很多了,我把我对fport分析也写出来,让大家知道fport是如何工作.
fport.exe是由foundstone team出品免费软件Software,可以列出系统中所有开放端口都是由那些进程打开.而下
面所描述思路方法是基于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)

个参数得到执行成功后句柄
第 2个参数DesiredAccess为个常数,可以是下列值:
    # SECTION_QUERY       0x0001
    # SECTION_MAP_WRITE   0x0002
    # SECTION_MAP_READ    0x0004
    # SECTION_MAP_EXECUTE 0x0008
    # SECTION_EXTEND_SIZE 0x0010

    # SECTION_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED|SECTION_QUERY|\
                            SECTION_MAP_WRITE |      \
                            SECTION_MAP_READ |       \
                            SECTION_MAP_EXECUTE |    \
                            SECTION_EXTEND_SIZE)
    (见ntddk.h)
第 3个参数是个结构,包含要打开对象类型等信息,结构定义如下:
    typedef struct _OBJECT_ATTRIBUTES {
        ULONG Length;
        HANDLE RootDirectory;
        PUNICODE_STRING ObjectName;
        ULONG Attributes;
        PVOID SecurityDescriptor;        // Pos to type SECURITY_DESCRIPTOR
        PVOID SecurityQualityOfService;  // Pos to type SECURITY_QUALITY_OF_SERVICE
    } OBJECT_ATTRIBUTES;
    typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
    (见ntdef.h)
对于这个结构化用个宏完成:
    # InitializeObjectAttributes( p, n, a, r, s ) { \
        (p)->Length = ( 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,获得句柄
    ( !NT_SUCCESS( status ))
        false;
    pMapPhysicalMemory=MapViewOfFile(pHandle,FILE_MAP_READ,
                           0,0x30000,0x1000);
    //从内存地址0x30000开始映射0x1000个字节
    ( GetLastError!=0)
        false;                    
    true;
}

    为什么要从0x30000开始映射呢,是这样,我们知道,在Windows NT/2000下,系统分为内核模式和用户模式,也就是我们
所说Ring0和Ring3,在Windows NT/2000下,我们所能够看到进程都运行在Ring3下,般情况下,系统进程(也就是
进程)页目录(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)

个参数返回打开对象句柄
第 2个参数DesiredAccess为个常数,可以是下列值:
    # FILE_READ_DATA            ( 0x0001 )    // file & pipe
    # FILE_LIST_DIRECTORY       ( 0x0001 )    // directory
    # FILE_WRITE_DATA           ( 0x0002 )    // file & pipe
    # FILE_ADD_FILE             ( 0x0002 )    // directory
    # FILE_APPEND_DATA          ( 0x0004 )    // file
    # FILE_ADD_SUBDIRECTORY     ( 0x0004 )    // directory
    # FILE_CREATE_PIPE_INSTANCE ( 0x0004 )    // named pipe
    # FILE_READ_EA              ( 0x0008 )    // file & directory
    # FILE_WRITE_EA             ( 0x0010 )    // file & directory
    # FILE_EXECUTE              ( 0x0020 )    // file
    # FILE_TRAVERSE             ( 0x0020 )    // directory
    # FILE_DELETE_CHILD         ( 0x0040 )    // directory
    # FILE_READ_ATTRIBUTES      ( 0x0080 )    // all
    # FILE_WRITE_ATTRIBUTES     ( 0x0100 )    // all
    # FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
    # FILE_GENERIC_READ         (STANDARD_RIGHTS_READ     |\
                                       FILE_READ_DATA           |\
                                       FILE_READ_ATTRIBUTES     |\
                                       FILE_READ_EA             |\
                                       SYNCHRONIZE)
    # FILE_GENERIC_WRITE        (STANDARD_RIGHTS_WRITE    |\
                                       FILE_WRITE_DATA          |\
                                       FILE_WRITE_ATTRIBUTES    |\
                                       FILE_WRITE_EA            |\
                                       FILE_APPEND_DATA         |\
                                       SYNCHRONIZE)
    # FILE_GENERIC_EXECUTE      (STANDARD_RIGHTS_EXECUTE  |\
                                       FILE_READ_ATTRIBUTES     |\
                                       FILE_EXECUTE             |\
                                       SYNCHRONIZE)
        (见ntdef.h)
第 3个参数是个结构,包含要打开对象类型等信息,结构定义见上面所述
第 4个参数返回打开对象属性,是个结构,定义如下:
    typedef struct _IO_STATUS_BLOCK {
        union {
            NTSTATUS Status;
            PVOID Poer;
        };

        ULONG_PTR Information;
    } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

    # d(_WIN64)
    typedef struct _IO_STATUS_BLOCK32 {
        NTSTATUS Status;
        ULONG Information;
    } IO_STATUS_BLOCK32, *PIO_STATUS_BLOCK32;
    #end
    (见ntddk.h)
第 5个参数ShareAccess是个常数,可以是下列值:
    # FILE_SHARE_READ                 0x00000001  // winnt
    # FILE_SHARE_WRITE                0x00000002  // winnt
    # FILE_SHARE_DELETE               0x00000004  // winnt
    (见ntddk.h)
第 6个参数OpenOptions也是个常数,可以是下列值:
    # FILE_DIRECTORY_FILE                     0x00000001
    # FILE_WRITE_THROUGH                      0x00000002
    # FILE_SEQUENTIAL_ONLY                    0x00000004
    # FILE_NO_INTERMEDIATE_BUFFERING          0x00000008
    # FILE_SYNCHRONOUS_IO_ALERT               0x00000010
    # FILE_SYNCHRONOUS_IO_NONALERT            0x00000020
    # FILE_NON_DIRECTORY_FILE                 0x00000040
    # FILE_CREATE_TREE_CONNECTION             0x00000080
    # FILE_COMPLETE_IF_OPLOCKED               0x00000100
    # FILE_NO_EA_KNOWLEDGE                    0x00000200
    # FILE_OPEN_FOR_RECOVERY                  0x00000400
    # FILE_RANDOM_ACCESS                      0x00000800
    # FILE_DELETE_ON_CLOSE                    0x00001000
    # FILE_OPEN_BY_FILE_ID                    0x00002000
    # FILE_OPEN_FOR_BACKUP_INTENT             0x00004000
    # FILE_NO_COMPRESSION                     0x00008000
    # FILE_RESERVE_OPFILTER                   0x00100000
    # FILE_OPEN_REPARSE_POINT                 0x00200000
    # FILE_OPEN_NO_RECALL                     0x00400000
    # FILE_OPEN_FOR_FREE_SPACE_QUERY          0x00800000
    # FILE_COPY_STRUCTURED_STORAGE            0x00000041
    # FILE_STRUCTURED_STORAGE                 0x00000441
    # FILE_VALID_OPTION_FLAGS                 0x00ffffff
    # FILE_VALID_PIPE_OPTION_FLAGS            0x00000032
    # FILE_VALID_MAILSLOT_OPTION_FLAGS        0x00000032
    # 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);   
    (GetLastError!=0)
        NULL;
    InitializeObjectAttributes( &attributes,&physmemString,
                            OBJ_CASE_INSENSITIVE,0, NULL );
    status = ZwOpenFile ( &pDeviceHandle,0x100000, &attributes, &iosb, 3,0);
    ( !NT_SUCCESS( status ))
        NULL;
}

    接着,用ZwQueryInformation获得系统当前所以进程所建立句柄及其相关信息,原型如下:
NTSYSAPI
NTSTATUS
NTAPI
ZwQueryInformation(
    IN SYSTEM_INFORMATION_CLASS InformationClass,
    IN OUT PVOID Information,
    IN ULONG InformationLength,
    OUT PULONG ReturnLength OPTIONAL
    };
(这个结构Microsoft没有公开,参见Gary Nebbett<<Windows NT/2000 Native API Reference>>)

个参数是个枚举常数,设置要查询系统信息类型,ZwQueryInformation支持54个系统信息查询,我们要用到
是它第16号功能,进行HandleInformation查询.
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>>)
第 2个参数输出查询结果
第 3个参数设置缓冲区长度
第 4个参数返回正确执行需要缓冲区大小
代码如下:
# HandleInformation 16
PULONG GetHandleList
{
    ULONG cbBuffer = 0x1000;    //先设定个较小缓冲空间
    PULONG pBuffer = ULONG[cbBuffer]; //分配内存
    NTSTATUS Status;

    do
        {
        Status = ZwQueryInformation(
                    HandleInformation,
                    pBuffer, cbBuffer * * pBuffer, NULL);

        (Status STATUS_INFO_LENGTH_MISMATCH)
        {
            //如果返回信息为缓冲区长度不够,那么重新分配内存
            delete pBuffer;
            pBuffer = ULONG[cbBuffer *= 2];
        }
        (!NT_SUCCESS(Status))
        {
            //如果是其他信息,返回
            delete pBuffer;
            false;
        }
        }
        while (Status STATUS_INFO_LENGTH_MISMATCH);
    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)
    {
        ((pProcesses[i].ProcessID) ProcessId)
        {
            objTYPE1 = (DWORD)hDeviceTcpUdp;
            objTYPE2 = (DWORD)pProcesses[i].Handle;
            (objTYPE1objTYPE2)
            {
                HandleObject = (DWORD)pProcesses.Object;
                HandleObject;
        }
    }
    0;
}

这个内核对象地址是个线性地址,我们需要把这个地址转换为物理地址,并得到些相关数据.在fport中,换算是这样进行:
(具体描述见WebCrazy文章<<小议Windows NT/2000分页机制>>)
void * NewmapPhy;

void GetPTE(DWORD objAddress)
{
    DWORD physmemBuff;
    DWORD Address1,Address2,Address3,Address4;
    DWORD * Address;

    physmemBuff = (DWORD)pMapPhysicalMemory;
    Address1 = physmemBuff+(objAddress>>0x16)*4;
    Address = (DWORD *)Address1;
    Address1 = * Address;
    Address2 = objAddress & 0x3FF000;
    Address3 = Address1 & 0x0FFFFF000;
    Address4 = Address2 + Address3;
    NewmapPhy = MapViewOfFile(ghPhysicalMemory,FILE_MAP_READ,0,Address4,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, NoCache)
//dwProcessesID为进程标识ID
//Handle为进程打开句柄,并且经过比较为\Device\Tcp或\Device\Udp类型
//NoCache为PTE结构中个值
{
    HANDLE hProc,DupHandle=NULL;
    HANDLE hEven=NULL;
        OVERLAPPED overlap;
    u_ openport;
    i=0;
    char procName[256]={0};
      portflag=0;

    overlap.Internal = 0;
    overlap.InternalHigh = 0;
    overlap.Off = 0;
    overlap.OffHigh = 0;
    hEven=CreateEvent(0,1,0,0);
    overlap.hEvent = hEven;

    hProc = OpenProcess(PROCESS_DUP_HANDLE,
                        0,
                        dwProcessesID);
    (hProc)
    {
        DuplicateHandle(hProc,
                        (HANDLE)Handle,
                        GetCurrentProcess,
                        &DupHandle,
                        0,
                        FALSE,
                        2);
        CloseHandle( hProc );
        (DupHandle)
        {
            TDI_CONNECTION_INFO    TdiConnInfo={0};
            TDI_CONNECTION_INFORMATION TdiConnInformation={0};
            DWORD dwRetu=0;   

            (NoCache0x2)
            {
                TdiConnInformation.RemoteAddressLength= 4;
                (DeviceIoControl(DupHandle,0x210012,
                            &TdiConnInformation,(TdiConnInformation),
                            &TdiConnInfo,(TdiConnInfo),
                            0,&overlap))
                //进行TDI查询,得到连接相关信息
                {
                    openport = ntohs((u_)TdiConnInfo.ReceivedTsdus);
                    procname = GetProcName(dwProcessesID);  //得到进程标识ID进程名称
                    prf("PID = %4d ProcessName = %15s PORT = %4d\n",dwProcessesID,procName,openport);
                }
            }
            (NoCache0x1)
            {
                TdiConnInformation.RemoteAddressLength= 3;
                (DeviceIoControl(DupHandle,0x210012,
                            &TdiConnInformation,(TdiConnInformation),
                            &TdiConnInfo,(TdiConnInfo),
                            0,&overlap))
                //进行TDI查询,得到连接相关信息
                {
                    openport = ntohs((u_)TdiConnInfo.ReceivedTsdus);
                    procname = GetProcName(dwProcessesID);  //得到进程标识ID进程名称
                    prf("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

Tags:  端口映射软件 端口映射工具 路由器端口映射 端口映射

延伸阅读

最新评论

发表评论