原始套接字 傳送 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裡面】
3.自己計算TCP首部校驗和【 在實驗的時候,我發現如果自己不手動算,而是把check欄位設定為0的話,核心並不會自動計算校驗和,如果校驗和不正確的話,會出現SYN包被丟棄的情況】: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);
① 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++冒號操作符。