ubuntu下端口关联检测

来源:互联网 发布:淘宝限时打折怎么设置 编辑:程序博客网 时间:2024/06/11 06:01

           端口检测目的意义: 

                    在网络入侵中,端口扫描几乎是所有网络入侵过程中的必然前奏,入侵者首先通过端口扫描来发现目标主机的操作系统类型,提供的服务和系统所使用的软件版本及其他各种相关信息,然后采取针对性的攻击手段。

                   因此对端口扫描进行检测,发现可能出现的攻击行为,可有效的配合入侵检测。

          实现背景:

                   端口关联检测中最麻烦的部分应该是根据端口找所关联的进程,在网上找了很长时间,相关资料很少很少,倒是有个博主在讨论服务器netstat -anp 无法获取部分所对应的进程pid和程序名是因为socket文件的inode太大,超过INT_MAX,对netstat源码 的实现提到了端口关联进程。故根据 安卓源码和netstat 源码 简单实现了对端口关联的检测。

         相关知识简介:

        Ubuntu下网络连接文件简介

     由于本实验检测的是在ipv4ipv6下的tcpudp本连接中端口的关联情况,所以用到以下4种文件

         1/proc/net/tcp文件,这里记录的是ipv4下所有tcp连接的情况。

         2proc/net/tcp6文件,这里记录的是ipv6下所有tcp连接的情况。

         3/proc/net/udp文件,这里记录的是ipv4下所有udp连接的情况。

         4proc/net/udp6文件,这里记录的是ipv6下所有udp连接的情况。

      这4种文件都用到以下数据:

           local_address 0101007F:0035本地IP(网络字节序):本地端口(网络字节序)

           rem_address 00000000:0000远端IP(网络字节序):远端端口(网络字节序)

           st 0A套接字状态,不同套接字对应不同的值

       enum {  

    TCP_ESTABLISHED = 1,  

    TCP_SYN_SENT,  // 2

    TCP_SYN_RECV,  // 3

    TCP_FIN_WAIT1,  // 4

    TCP_FIN_WAIT2,  // 5

    TCP_TIME_WAIT,  // 6

    TCP_CLOSE,  // 7

    TCP_CLOSE_WAIT, // 8

    TCP_LAST_ACK,  // 9

    TCP_LISTEN,  // 0A

    TCP_CLOSING,    // 1 /* Now a valid state */  

    TCP_MAX_STATES    /* leave at the end! */  

};

         tx_queue:rx_queue 00000000:00000000发送队列中的数据长度:状态是ESTABLISHED,表示的时接受队列中的数据长度,状态是LISTEN,表示已完成队列的长度

         tr tm->when 00:00000000定时器类型,0表示没有启动定时器。1表示重传定时器,4表示持续定时器,2表示连接定时器、FIN_WAIT_2定时器或TCP保活定时器,3表    示TIME_WAIT定时器

         retrnsmt 00000000超时重传次数  

         uid 0用户id

        timeout 0持续定时器或者保活定时器周期性发送出去但未被确认的TCP段数目,收到ACK后清零。

        inode 11864 1 0000000000000000 100 0 0 10 0   

       11864套接字对应的inode

       1   sock结构的引用数

       0000000000000000 sock结构的实例地址

       100 RTO,单位是clock_t

      0用来计算延时确认的估值

      0快速确认数和是否启用的标志位的或运算结果

     10当前拥塞窗口大小

     0 如果满启动阀值大于0x7ffffff显示-1,否则表示慢启动阀值

 Socket文件中inode分析

       socket文件的inode存在于LinuxVFS虚拟文件系统中。VFS是一个异构文件系统之上的软件粘合层,可以让open()read()write()等系统调用不用关心底层的存储介质和文件系统类型就可以工作。通过VFS,一个抽象的通用访问接口屏蔽了底层文件系统和物理介质的差异性,每一种类型的文件系统代码都隐藏了实现的细节。对于VFS层和内核的其它部分而言,每一种类型的文件系统看起来都是一样的。

      VFS inode和磁盘文件系统中的inode不同。比如ext2文件系统,它的inode位于磁盘上划分的块组中,每个inode 128字节。在分割扇区时,系统会先做出一堆inode供以后使用,inode的数量关系着系统中可以建立的文件及目录总数。磁盘上的每个文件都有且仅有一个inode,即使文件中没有数据,inode也存在。

VFS inode只存在于内存中,可以通过inode缓存访问。每个文件在VFS中都有相应的inode结点,包括普通文件、目录以及特殊文件,如socketpipe等。在需要某个文件的时候系统会在内存中为其建立相应的inode数据结构,建立的inode结构将形成一个链表,可以通过遍历这个链表去得到我们需要的文件结点。但是,VFS对于不同文件的inode号分配方式是不同的。

   具体如下:

      对于ext2等磁盘文件系统中的普通文件,每个文件在磁盘上都有对应的inode,该inode号也是已知的。在访问这些文件时,VFS会在内存中为这个文件分配VFS inode,将在磁盘文件系统中确定的inode号赋给inode结构。可见,一般普通文件的inode号都不会太大。

对于socket等特殊文件来说,并没有像磁盘文件一样的inode号的来源,内核在实现中维护了一个unsigned long型的静态变量来保存目前分配的inode号的最大值,新分配的inode号在此基础上加1来实现。这个静态变量的值会一直增大而不会减小,直至机器重启。

    具体实现步骤:

    1.通过网络文件查找获取端口数据

     查找/proc/net/tcp/proc/net/udp/proc/net/tcp6/proc/net/udp6,从文件数据中提取所需要的本地ip,本地port,远端ip,远端port,套接字状态,发送队列中的数据长度,接收队列中的数据长度,以及套接字对应的inode

 

    2.获取端口所关联的进程

  • 首先遍历所有的/proc/PID/fd目录,如果某个进程非本账号所有,则无权访问/proc/PID目录,跳过该PID,访问下一个。
  • 当某个/proc/PID/fd目录中有一个或多个socket句柄时,则读该/proc/PID目录下的cmdline文件,获取该进程的所执行程序的名称;同时,记录/proc/PID/fd目录下所有socket句柄的inode号。
  • 当所有进程遍历完成后,根据在网络文件proc/net/{tcp,udp,tcp6,udp6}获取的inode节点号,匹配你记录的所有socket句柄的inode后,若相等,则拥有该socket句柄的进程即为该端口关联的进程。
    代码块分析

    1.socket文件inode信息结构:

     static struct prg_node {

     struct prg_node *next;

     int inode;

     char name[PROGNAME_WIDTH];

   }*prg_hash[PRG_HASH_SIZE];

    name数组里存储进程pid以及进程名

    2.ipv4 ipv6ip地址 网络字节序到主机字节序的转化。

     static void addr2str(int , const void * , char * );

    3./proc/net/tcp/proc/net/udp中连接数据的获取和输出。

     static void ipv4(const char * , const char  *);

    4./proc/net/tcp6/proc/net/udp6中连接数据的获取和输出。

    static void ipv6(const char *, const char *);

    5.prg_node结构的信息存储于链表中。

    static void prg_cache_add(int  , char * );

    6.遍历链表查看是否存在相等的inode节点,若有,将节点关联进程信息返回。

    static const char *prg_cache_get(int  );

    7.清空链表。

     static void prg_cache_clear( );

    8.筛选类型为socket0000的句柄,并使用形-参返回对应的inode节点号。

    static void extract_type_1_socket_inode(const char a[], long *  );

    static void extract_type_2_socket_inode(const char a[], long *  );

    9.实现遍历/proc/pid/fd和查看/proc/pid/cmdline获取信息。

    static void prg_cache_load( );


  代码如下:

#include <errno.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <unistd.h>#include <ctype.h>#include <fcntl.h>#include <netdb.h>#include <paths.h>#include <pwd.h>#include <getopt.h>#include <sys/param.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/ioctl.h>#include <net/if.h>#include <dirent.h>#define PROGNAME_WIDTH 20#define PROGNAME_WIDTHs PROGNAME_WIDTH1(PROGNAME_WIDTH)#define PROGNAME_WIDTH1(s) PROGNAME_WIDTH2(s)#define PROGNAME_WIDTH2(s) #s#define PRG_HASH_SIZE 211#define PRG_HASHIT(x) ((x) % PRG_HASH_SIZE)#ifndef LINE_MAX#define LINE_MAX 4096#endif#define PATH_PROC   "/proc"#define PATH_FD_SUFF"fd"#define PATH_FD_SUFFl       strlen(PATH_FD_SUFF)#define PATH_PROC_X_FD      PATH_PROC "/%s/" PATH_FD_SUFF#define PATH_CMDLINE"cmdline"#define PATH_CMDLINEl       strlen(PATH_CMDLINE)#define PRG_LOCAL_ADDRESS "local_address"#define PRG_INODE "inode"#define PRG_SOCKET_PFX    "socket:["#define PRG_SOCKET_PFXl (strlen(PRG_SOCKET_PFX))#define PRG_SOCKET_PFX2   "[0000]:"#define PRG_SOCKET_PFX2l  (strlen(PRG_SOCKET_PFX2))static char prg_cache_loaded = 0;typedef union iaddr iaddr;typedef union iaddr6 iaddr6;static struct prg_node {    struct prg_node *next;    int inode;    char name[PROGNAME_WIDTH];} *prg_hash[PRG_HASH_SIZE];union iaddr {    unsigned u;    unsigned char b[4];};union iaddr6 {    struct {        unsigned a;//unsigned int a; 无符号整型        unsigned b;        unsigned c;        unsigned d;    } u;    unsigned char b[16];};static const char *state2str(unsigned state){    switch(state){    case 0x1: return "ESTABLISHED";    case 0x2: return "SYN_SENT";    case 0x3: return "SYN_RECV";    case 0x4: return "FIN_WAIT1";    case 0x5: return "FIN_WAIT2";    case 0x6: return "TIME_WAIT";    case 0x7: return "CLOSE";    case 0x8: return "CLOSE_WAIT";    case 0x9: return "LAST_ACK";    case 0xA: return "LISTEN";    case 0xB: return "CLOSING";    default: return "UNKNOWN";    }}/* addr + : + port + \0 */#define ADDR_LEN INET6_ADDRSTRLEN + 1 + 5 + 1static void addr2str(int af, const void *addr, char *buf){    if (inet_ntop(af, addr, buf, ADDR_LEN) == NULL)         *buf = '\0';      return;    }static void prg_cache_add(int inode, char *name){    unsigned hi = PRG_HASHIT(inode);    struct prg_node **pnp,*pn;    prg_cache_loaded=2;    for (pnp=prg_hash+hi;(pn=*pnp);pnp=&pn->next) {if (pn->inode==inode) {    /* Some warning should be appropriate here       as we got multiple processes for one i-node */    return;}    }    if (!(*pnp=malloc(sizeof(**pnp)))) return;    pn=*pnp;    pn->next=NULL;    pn->inode=inode;    if (strlen(name)>sizeof(pn->name)-1) name[sizeof(pn->name)-1]='\0';    strcpy(pn->name,name);}static const char *prg_cache_get(int inode){    unsigned hi=PRG_HASHIT(inode);    struct prg_node *pn;    for (pn=prg_hash[hi];pn;pn=pn->next)if (pn->inode==inode) return(pn->name);    return("-");}static void prg_cache_clear(void){    struct prg_node **pnp,*pn;    if (prg_cache_loaded == 2)for (pnp=prg_hash;pnp<prg_hash+PRG_HASH_SIZE;pnp++)    while ((pn=*pnp)) {*pnp=pn->next;free(pn);    }    prg_cache_loaded=0;}static void extract_type_1_socket_inode(const char lname[], long * inode_p) {    /* If lname is of the form "socket:[12345]", extract the "12345"       as *inode_p.  Otherwise, return -1 as *inode_p.       */    if (strlen(lname) < PRG_SOCKET_PFXl+3) *inode_p = -1;    else if (memcmp(lname, PRG_SOCKET_PFX, PRG_SOCKET_PFXl)) *inode_p = -1;    else if (lname[strlen(lname)-1] != ']') *inode_p = -1;    else {        char inode_str[strlen(lname + 1)];  /* e.g. "12345" */        const int inode_str_len = strlen(lname) - PRG_SOCKET_PFXl - 1;        char *serr;        strncpy(inode_str, lname+PRG_SOCKET_PFXl, inode_str_len);        inode_str[inode_str_len] = '\0';        *inode_p = strtol(inode_str,&serr,0);        if (!serr || *serr || *inode_p < 0 || *inode_p >= INT_MAX)             *inode_p = -1;    }}static void extract_type_2_socket_inode(const char lname[], long * inode_p) {    /* If lname is of the form "[0000]:12345", extract the "12345"       as *inode_p.  Otherwise, return -1 as *inode_p.       */    if (strlen(lname) < PRG_SOCKET_PFX2l+1) *inode_p = -1;    else if (memcmp(lname, PRG_SOCKET_PFX2, PRG_SOCKET_PFX2l)) *inode_p = -1;    else {        char *serr;        *inode_p=strtol(lname + PRG_SOCKET_PFX2l,&serr,0);        if (!serr || *serr || *inode_p < 0 || *inode_p >= INT_MAX)             *inode_p = -1;    }}static void prg_cache_load(void){    char line[LINE_MAX],eacces=0;    int procfdlen,fd,cmdllen,lnamelen;    char lname[30],cmdlbuf[512],finbuf[PROGNAME_WIDTH];    long inode;    const char *cs,*cmdlp;    DIR *dirproc=NULL,*dirfd=NULL;    struct dirent *direproc,*direfd;    if (prg_cache_loaded ) return;    prg_cache_loaded=1;    cmdlbuf[sizeof(cmdlbuf)-1]='\0';    if (!(dirproc=opendir(PATH_PROC))) goto fail;    while (errno=0,direproc=readdir(dirproc)) {#ifdef DIRENT_HAVE_D_TYPE_WORKSif (direproc->d_type!=DT_DIR) continue;#endiffor (cs=direproc->d_name;*cs;cs++)    if (!isdigit(*cs)) break;if (*cs)     continue;procfdlen=snprintf(line,sizeof(line),PATH_PROC_X_FD,direproc->d_name);if (procfdlen<=0 || procfdlen>=sizeof(line)-5)     continue;errno=0;dirfd=opendir(line);if (! dirfd) {    if (errno==EACCES) eacces=1;    continue;}line[procfdlen] = '/';cmdlp = NULL;while ((direfd = readdir(dirfd))) {#ifdef DIRENT_HAVE_D_TYPE_WORKS    if (direfd->d_type!=DT_LNK) continue;#endif    if (procfdlen+1+strlen(direfd->d_name)+1>sizeof(line)) continue;    memcpy(line + procfdlen - PATH_FD_SUFFl, PATH_FD_SUFF "/",   PATH_FD_SUFFl+1);    strcpy(line + procfdlen + 1, direfd->d_name);    lnamelen=readlink(line,lname,sizeof(lname)-1);            lname[lnamelen] = '\0';  /*make it a null-terminated string*/            extract_type_1_socket_inode(lname, &inode);            if (inode < 0) extract_type_2_socket_inode(lname, &inode);            if (inode < 0) continue;    if (!cmdlp) {if (procfdlen - PATH_FD_SUFFl + PATH_CMDLINEl >=     sizeof(line) - 5)     continue;strcpy(line + procfdlen-PATH_FD_SUFFl, PATH_CMDLINE);fd = open(line, O_RDONLY);if (fd < 0)     continue;cmdllen = read(fd, cmdlbuf, sizeof(cmdlbuf) - 1);if (close(fd))     continue;if (cmdllen == -1)     continue;if (cmdllen < sizeof(cmdlbuf) - 1)     cmdlbuf[cmdllen]='\0';if ((cmdlp = strrchr(cmdlbuf, '/')))     cmdlp++;else     cmdlp = cmdlbuf;    }    snprintf(finbuf, sizeof(finbuf), "%s/%s", direproc->d_name, cmdlp);    prg_cache_add(inode, finbuf);}closedir(dirfd); dirfd = NULL;    }    if (dirproc) closedir(dirproc);    if (dirfd) closedir(dirfd);    if (!eacces) return;    if (prg_cache_loaded == 1) {    fail:fprintf(stderr,("(No info could be read for \"-p\": geteuid()=%d but you should be root.)\n"),geteuid());    }    elsefprintf(stderr, ("(Not all processes could be identified, non-owned process info\n" " will not be shown, you would have to be root to see it all.)\n"));}static void ipv4(const char *filename, const char *label) {    FILE *fp = fopen(filename, "r");    if (fp == NULL) {        return;    }    char buf[BUFSIZ];    fgets(buf, BUFSIZ, fp);    while (fgets(buf, BUFSIZ, fp)){        char lip[ADDR_LEN];        char rip[ADDR_LEN];        iaddr laddr, raddr;        unsigned lport, rport, state, txq, rxq, num;unsigned tr,tmWhen,ret;unsigned uid,timeout,inode;        int n = sscanf(buf, " %d: %x:%x %x:%x %x %x:%x %x:%x %x %x %x %d",                       &num, &laddr.u, &lport, &raddr.u, &rport,                       &state, &txq, &rxq,&tr,&tmWhen,&ret,&uid,&timeout,&inode);        if (n == 14) {            addr2str(AF_INET, &laddr, lip);            addr2str(AF_INET, &raddr, rip);printf("%10d   %-15s   %5d   %-15s   %6d  %6d  %s   %12s     %5d   %15s\n",lport,lip,rport,rip,txq,rxq,label,state2str(state),inode,prg_cache_get(inode));        }    }    fclose(fp);}static void ipv6(const char *filename, const char *label) {    FILE *fp = fopen(filename, "r");    if (fp == NULL) {        return;    }    char buf[BUFSIZ];    fgets(buf, BUFSIZ, fp);    while (fgets(buf, BUFSIZ, fp)){        char lip[ADDR_LEN];        char rip[ADDR_LEN];        iaddr6 laddr6, raddr6;        unsigned lport, rport, state, txq, rxq, num;     unsigned tr,tmWhen,ret;unsigned uid,timeout,inode;        int n = sscanf(buf, " %d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x %x:%x %x:%x %x %x %x %d",                       &num, &laddr6.u.a, &laddr6.u.b, &laddr6.u.c, &laddr6.u.d, &lport,                       &raddr6.u.a, &raddr6.u.b, &raddr6.u.c, &raddr6.u.d, &rport,                       &state, &txq, &rxq,&tr,&tmWhen,&ret,&uid,&timeout,&inode);        if (n == 20) {            addr2str(AF_INET6, &laddr6,  lip);            addr2str(AF_INET6, &raddr6,  rip);     printf("%10d   %-15s   %5d   %-15s   %6d  %6d  %s  %12s     %5d   %15s\n",lport,lip,rport,rip,txq,rxq,label,state2str(state),inode,prg_cache_get(inode));        }    }    fclose(fp);}int main(int argc, char *argv[]){    prg_cache_load();printf("Local Port   Local Address     Fport   Foreign Address   Recv-Q  Send-Q  proto        state     inode   Pid/Program name\n");    ipv4("/proc/net/tcp",  "tcp");    ipv4("/proc/net/udp",  "udp");    ipv6("/proc/net/tcp6", "tcp6");    ipv6("/proc/net/udp6", "udp6");prg_cache_clear();    return 0;}


  运行结果:

 

原创粉丝点击