端口检测目的意义:
在网络入侵中,端口扫描几乎是所有网络入侵过程中的必然前奏,入侵者首先通过端口扫描来发现目标主机的操作系统类型,提供的服务和系统所使用的软件版本及其他各种相关信息,然后采取针对性的攻击手段。
因此对端口扫描进行检测,发现可能出现的攻击行为,可有效的配合入侵检测。
实现背景:
端口关联检测中最麻烦的部分应该是根据端口找所关联的进程,在网上找了很长时间,相关资料很少很少,倒是有个博主在讨论服务器netstat -anp无法获取部分所对应的进程pid和程序名是因为socket文件的inode太大,超过INT_MAX,对netstat源码 的实现提到了端口关联进程。故根据 安卓源码和netstat 源码 简单实现了对端口关联的检测。
相关知识简介:
Ubuntu下网络连接文件简介
由于本实验检测的是在ipv4与ipv6下的tcp和udp本连接中端口的关联情况,所以用到以下4种文件
1、/proc/net/tcp文件,这里记录的是ipv4下所有tcp连接的情况。
2、proc/net/tcp6文件,这里记录的是ipv6下所有tcp连接的情况。
3、/proc/net/udp文件,这里记录的是ipv4下所有udp连接的情况。
4、proc/net/udp6文件,这里记录的是ipv6下所有udp连接的情况。
这4种文件都用到以下数据:
local_address 0101007F:0035本地IP(网络字节序):本地端口(网络字节序)
rem_address 00000000:0000远端IP(网络字节序):远端端口(网络字节序)
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存在于Linux的VFS虚拟文件系统中。VFS是一个异构文件系统之上的软件粘合层,可以让open()、read()、write()等系统调用不用关心底层的存储介质和文件系统类型就可以工作。通过VFS,一个抽象的通用访问接口屏蔽了底层文件系统和物理介质的差异性,每一种类型的文件系统代码都隐藏了实现的细节。对于VFS层和内核的其它部分而言,每一种类型的文件系统看起来都是一样的。
VFS inode和磁盘文件系统中的inode不同。比如ext2文件系统,它的inode位于磁盘上划分的块组中,每个inode 128字节。在分割扇区时,系统会先做出一堆inode供以后使用,inode的数量关系着系统中可以建立的文件及目录总数。磁盘上的每个文件都有且仅有一个inode,即使文件中没有数据,inode也存在。
VFS inode只存在于内存中,可以通过inode缓存访问。每个文件在VFS中都有相应的inode结点,包括普通文件、目录以及特殊文件,如socket、pipe等。在需要某个文件的时候系统会在内存中为其建立相应的inode数据结构,建立的inode结构将形成一个链表,可以通过遍历这个链表去得到我们需要的文件结点。但是,VFS对于不同文件的inode号分配方式是不同的。
具体如下:
对于ext2等磁盘文件系统中的普通文件,每个文件在磁盘上都有对应的inode,该inode号也是已知的。在访问这些文件时,VFS会在内存中为这个文件分配VFS inode,将在磁盘文件系统中确定的inode号赋给inode结构。可见,一般普通文件的inode号都不会太大。
对于socket等特殊文件来说,并没有像磁盘文件一样的inode号的来源,内核在实现中维护了一个unsigned long型的静态变量来保存目前分配的inode号的最大值,新分配的inode号在此基础上加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 和ipv6中ip地址 网络字节序到主机字节序的转化。
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.筛选类型为socket和0000的句柄,并使用形-参返回对应的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 + 1 static 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",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,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_WORKS if (direproc->d_type!=DT_DIR) continue; #endif for (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()); } else fprintf(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,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,rip,label,state2str(state),inode,prg_cache_get(inode)); } } fclose(fp); } static void ipv6(const char *filename,fp)){ char lip[ADDR_LEN]; char rip[ADDR_LEN]; iaddr6 laddr6,raddr6; unsigned lport,num; unsigned tr," %d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x %x:%x %x:%x %x %x %x %d",&laddr6.u.a,&laddr6.u.b,&laddr6.u.c,&laddr6.u.d,&raddr6.u.a,&raddr6.u.b,&raddr6.u.c,&raddr6.u.d,&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",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; }
运行结果: