1、tcp協定的缺陷
任何協定都有缺陷,tcp協定同樣如此,tcp協定的三次握手是有一個不能輕易發現的缺陷,這個缺陷容易引起旁路中斷,引起中間人攻擊,可以讓中間人篡改資料包,Man-in-the-MiddleAttack,我們先看下圖,看懂了,就可以繼續了。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSP9EVT41kaNBTQ6JWa1cVWwZ0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwkzNyEjNzgTM3EzNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
實際上,這個缺陷就是在伺服器和客戶機真正建立連接配接之前,我們可以發送RST包,先告訴伺服器斷開,然後我們模拟伺服器給用戶端發包,直接串入到他們的網絡中間。
在TCP層,tcp的意思是傳輸控制協定,有個FLAGS字段,這個字段有以下幾個辨別:SYN, FIN, ACK, PSH, RST, URG. 對于我們日常的分析有用的就是前面的五個字段,SYN,建立連接配接,FIN包的意思是finish 關閉連接配接,ACK是回答響應,而RST表示我異常中斷,連結重置,PSH才是真正的資料傳送。對于我們來說,關鍵的截斷就在于RST 和 FIN,我們選取RST包。
2 、抓包
2.1 抓包方式1 交換機鏡像
這種方式是直接從交換機鏡像端口複制資料,可以抓該交換機口路徑下的所有包,缺點是不能抓所有包。在網絡安全中,為了友善對一個或多個網絡接口的流量進行分析(如IDS 入侵檢測、網絡分析儀等),可以通過配置交換機或路由器來把一個或多個端口(VLAN)的資料轉發到某一個端口,這就是端口鏡像,來實作對網絡的監聽。
2.2 抓包方式2 網橋
在路由器和交換機之間串入網橋,抓所有包,這種方式是可以抓所有包,缺點是流量更大。通常的網橋做法,可以使用兩個網卡,在兩個網卡之間既可以用旁路方式,也可以使用串入方式,在傳輸層這一層,既可以抓取tcp包,也可以抓取udp包,這取決于需求。
2.3 抓包方式3 網關
網關方式是比較安全的方式,一般是純軟體旁路方式抓包。旁路方式抓包是不能阻截udp的,除非我們動用arp 協定,在網絡中下毒arp,讓所有主機誤認為我們發放病毒的機器是網關,這種方式不可取,本身具有arp防火牆的主機是不會受幹擾的。
為了友善示範,顯然我們制作一個網關,在網關上旁路抓包是比較安全的,下面是建立RAW SOCKET,抓取所有流經網關的原始資料包,然後一層一層往下走,代碼其實是适應各種環境的,無論交換機鏡像,還是旁路抓包都是這麼寫,網橋的方式不一樣,可以通過linux核心子產品流經處理的方式發送到使用者态去分析,這裡暫時不讨論這種方式。一下是旁路方式抓包。
if((m_helperSocket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))==SOCKET_ERROR)
{
return 0;
}
if(setsockopt(m_helperSocket, IPPROTO_IP, IP_HDRINCL, (char *)&optval, sizeof(optval))==SOCKET_ERROR)
{
return 0;
}
if((m_sniffSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP))==SOCKET_ERROR)
{
return 0;
}
PIP_ADAPTER_INFO pAdapterInfo = m_pAdapterInfo;
u_long in = 0;
do
{
if (strcmp (in_szSourceDevice, pAdapterInfo->AdapterName ) == 0)
{
break;
}
in++;
pAdapterInfo = pAdapterInfo->Next; // Progress through
}
while(pAdapterInfo);
struct sockaddr_in src;
memset(&src, 0, sizeof(src));
src.sin_addr.S_un.S_addr = inet_addr (pAdapterInfo->IpAddressList.IpAddress.String);
src.sin_family = AF_INET;
src.sin_port = 0;
if (bind(m_sniffSocket,(struct sockaddr *)&src,sizeof(src)) == SOCKET_ERROR)
{
return 0;
}
int j=1;
if (WSAIoctl(m_sniffSocket, SIO_RCVALL, &j, sizeof(j), 0, 0, &in,0, 0) == SOCKET_ERROR)
{
return 0;
}
3、建立線程,查詢流經資料包
一個最大的IP包為64K,因為一個包是由兩個位元組來表示的,建立64K資料包緩存,下面隻是樣例寫法,實際上需要建立緩存連結清單,逐次分析每個包。
3.1 抓取IP
int ThreadHandler(void * in_pParam)
{
// libnet_t *LibnetHandle;
//char *DestAddr = "192.168.1.178";
// char *SourAddr = "192.168.1.1";
//LibnetHandle = libnet_init(LIBNET_LINK, g_DevName, NULL);
// if (LibnetHandle == NULL)
// {
//
// return -1;
// }
// unsigned int DestAddrNumber = libnet_name2addr4(LibnetHandle, DestAddr, LIBNET_RESOLVE);
// unsigned int SourAddrNumber = libnet_name2addr4(LibnetHandle, SourAddr, LIBNET_RESOLVE);
cprocess* _this = (cprocess*)in_pParam;
int res = 0;
char *pkt_data = (char *)malloc(65536); //64K的緩存!
char m_pLogString[256];
Packet p;
if (pkt_data == NULL)
{
return 0;
}
SetEvent(_this->m_hThrdReadyEvent);
// Capture packet
string strName;
do
{
//#define GET_IP(x,buf) sprintf(buf,"%d,%d,%d,%d", x&0xFF, (x>>8)&0xFF, (x>>16)&0xFF, (x>>24)&0xFF)
res = recvfrom(_this->m_sniffSocket,pkt_data,65536,0,0,0); //
if(res > 0)
{
ZeroMemory(&p, sizeof (Packet));
unsigned int Number = _this->DecodeIP((u_int8_t*)pkt_data, res, &p);
if (p.banned == 1)
{
_this->FilterHttpRequestXP(&p);
char ip_string_src[17];
char ip_string_dst[17];
memcpy (ip_string_src, inet_ntoa(p.iph->ip_src), 17);
memcpy (ip_string_dst, inet_ntoa(p.iph->ip_dst), 17);
sprintf (m_pLogString,
"關鍵詞 \'%s\' 被偵測從 %s 到 %s. 暗影聯盟日志",
p.matched, ip_string_src, ip_string_dst);
_this->m_pFilterLog->AddLog(m_pLogString);
}
}
}
while (res > 0);
free(pkt_data);
return 1;
}
3.2 解析IP包
unsigned int DecodeIP(u_int8_t * pkt, const u_int32_t len, Packet * p)
{
u_int32_t ip_len; /* length from the start of the ip hdr to the pkt end ip包長度*/
u_int32_t hlen; /* ip header length ip標頭長度 一個IP包如tcp包即使隻有一個位元組的應用層資料也是要大于40位元組的 */
/* lay the IP struct over the raw data */
p->iph = (IPHdr *) pkt;
/* 小于一個IP標頭的長度立刻退出 */
if(len < IP_HEADER_LEN)
{
p->iph = NULL;
return -1;
}
if(IP_VER(p->iph) != 4)
{
p->iph = NULL;
return -1;
}
/* set the IP datagram length */
ip_len = ntohs(p->iph->ip_len);
/* set the IP header length */
hlen = IP_HLEN(p->iph) << 2;
/* header length sanity check */
if(hlen < IP_HEADER_LEN)
{
p->iph = NULL;
return -1;
}
//抓包後分析的包長度
if (ip_len > len)
{
ip_len = len;
}
if(ip_len < hlen)
{
p->iph = NULL;
return -1;
}
/* test for IP options */
p->ip_options_len = hlen - IP_HEADER_LEN;
if(p->ip_options_len > 0)
{
p->ip_options_data = pkt + IP_HEADER_LEN;
}
/* set the remaining packet length */
ip_len -= hlen;
/* check for fragmented packets */
p->frag_offset = ntohs(p->iph->ip_off);
/*
* get the values of the reserved, more
* fragments and don't fragment flags
*/
p->rf = (u_int8_t)((p->frag_offset & 0x8000) >> 15);
p->df = (u_int8_t)((p->frag_offset & 0x4000) >> 14);
p->mf = (u_int8_t)((p->frag_offset & 0x2000) >> 13);
/* mask off the high bits in the fragment offset field */
p->frag_offset &= 0x1FFF;
if(p->frag_offset || p->mf)
{
/* set the packet fragment flag */
p->frag_flag = 1;
}
/* if this packet isn't a fragment 如果不是一個分片代碼*/
if(!(p->frag_flag))
{
if(p->iph->ip_src.S_un.S_addr == g_SelfIPAddress)
//iphdr *pip = (iphdr *) pkt;
{
//這個包是從本地發出去的ip包
g_Direction = 0;
}
else
{
if(p->iph->ip_dst.S_un.S_addr == g_SelfIPAddress)
{
//這個包是個進來的ip包
g_Direction = 1;
}
else g_Direction = -1;//内網位址
}
//2013-02-01 注釋,做家長行為管理,不進行協定分析
if(g_Direction >=0)
{
//unsigned int t = time(NULL);
//return Analyze_Protocol(pip,t,g_Direction);
}
//2009-04-7 先進行協定分析,以下屏蔽
//2013-02-01 又重新打開,做家長行為管理
/* Decode only TCP headers */
if (p->iph->ip_proto == IPPROTO_TCP)
{
DecodeTCP(pkt + hlen, ip_len, p);
}
}
else
{
/* set the payload pointer and payload size */
p->data = pkt + hlen;
p->dsize = (u_short) ip_len;
return -1;
}
return -1;
}
3.3 解析tcp
///解析tcp包,pkt傳進來的是ip包
void DecodeTCP(u_int8_t * pkt, const u_int32_t len, Packet * p)
{
u_int32_t hlen; /* TCP header length */
if(len < 20)
{
p->tcph = NULL;
return;
}
/* lay TCP on top of the data cause there is enough of it! */
p->tcph = (TCPHdr *) pkt;
/* multiply the payload offset value by 4 */
hlen = TCP_OFFSET(p->tcph) << 2;
if(hlen < 20)
{
p->tcph = NULL;
return;
}
if(hlen > len)
{
p->tcph = NULL;
return;
}
/* if options are present, decode them */
p->tcp_options_len = hlen - 20;
if(p->tcp_options_len > 0)
{
p->tcp_options_data = pkt + 20;
}
/* set the data pointer and size */
p->data = (u_int8_t *) (pkt + hlen);
if(hlen < len)
{
p->dsize = (u_short)(len - hlen);
}
else
{
p->dsize = 0;
}
if (p->tcph->th_flags & TH_ACK && p->tcph->th_flags & TH_PSH)
{
if(p->tcph->th_dport != htons(80) &&
p->tcph->th_dport != htons(443) )
return ;
DecodeHTTP(p->data, p->dsize, p);
}
return;
}
上面的的流程就是一層一層往下解析,我們還要做的就是建立資料特征向量,分析資料,儲存資料,同樣可以完成資料回放。
4 、包分析和阻斷流程
5、IP 校驗算法
在發送IP包的過程中,需要用到校驗算法
unsigned short CalcIPSum(unsigned short * w, int blen)
{
unsigned int cksum;
/* IP must be >= 20 bytes */
cksum = w[0];
cksum += w[1];
cksum += w[2];
cksum += w[3];
cksum += w[4];
cksum += w[5];
cksum += w[6];
cksum += w[7];
cksum += w[8];
cksum += w[9];
blen -= 20;
w += 10;
while( blen ) /* IP-hdr must be an integral number of 4 byte words */
{
cksum += w[0];
cksum += w[1];
w += 2;
blen -= 4;
}
cksum = (cksum >> 16) + (cksum & 0x0000ffff);
cksum += (cksum >> 16);
return (unsigned short) (~cksum);
}
6、制作界面
制作一個簡單的界面,從網卡上抓包,這裡其實做實驗可以從本地做,如果做好了,可以做一個網關,區域網路中的所有網絡主機填寫網關位址為本網關,就可以從網卡上以原始資料包方式抓取所有包。例如linux和windows server系列是可以抓包和發送原始資料包的,但是windows7 和 10 這種系列沒有權限發送原始資料包,隻能抓取,ok,原理已經清楚了,我們一步一步建立這樣的界面和程式。
下一節我們繼續探讨網絡中抓到包以後如何把包解析出來。