再谈突破TCPIP过滤防火墙进入内网.docx
《再谈突破TCPIP过滤防火墙进入内网.docx》由会员分享,可在线阅读,更多相关《再谈突破TCPIP过滤防火墙进入内网.docx(21页珍藏版)》请在冰点文库上搜索。
![再谈突破TCPIP过滤防火墙进入内网.docx](https://file1.bingdoc.com/fileroot1/2023-5/1/3c2d3096-78c8-4b36-8909-67ebbdc9e043/3c2d3096-78c8-4b36-8909-67ebbdc9e0431.gif)
再谈突破TCPIP过滤防火墙进入内网
再谈突破TCP-IP过滤/防火墙进入内网(icmp篇)
作者:
TOo2y
一现状
二ICMP协议转发数据报原理
三QQicmp工作流程
四QQicmp代码分析
五小结
六QQicmp源代码
一、现状
随着Internet网络的普及,各个中大型公司均建立了自己的局域网络,并与Internet相连接,而公司内部人员上网的限制也逐渐成为一个大家关心的话题。
目前最为流行的网络工具大多是基于IP协议的,而其中最主要的两个协议就是TCP和UDP协议。
HTTP,FTP等上层协议均是建立在TCP协议之上了,而DNS,ICQ,TFTP等则是建立在UDP协议之上的。
往往我们会遇到这样情况:
公司禁止了UDP协议(很大一部分的网络通讯软件都是建立在UDP协议之上的),而仅开通了TCP协议。
这样,我们就可以通过TCP协议来为我们转发UDP数据报,具体实现原理可以参看eyas的《突破TCP-IP过滤/防火墙进入内网》,里面详细讨论了如何实现TCP与UDP数据报之间的相互转发,在此就不多说了。
现在进入正题,如何实现利用ICMP数据报突破网关的限制?
二、ICMP转发数据报原理
ICMP协议(InternetControlMessagesProtocol,网际控制报文协议)是一种多功能的协议,在网络上有很多用处,比如ICMP扫描,拒绝服务(DOS)攻击,隧道攻击,以及我们最常用到的PING程序。
而现在就利用ICMP协议来为我们转送UPD/TCP数据(假设本机被禁止了UPD或TCP协议)。
大家知道一般的防火墙都是过滤了来自外部主机的回送请求(EchoRequest)报文,也就是我们平时说的PING数据报,但为了让内部主机能够探测外部主机的当前状态,防火墙大都不会过滤回送应答(EchoReply)数据报,而且ICMP报文可以在广域网上传送,这样我们就可以利用它来突破网关的种种限制。
由于本地主机被禁止了UDP/TCP协议,但在网关上却没有被禁止,我们就可以先将UDP/TCP数据报以ICMP的形式发送到网关,然后网关再将它解码,构造成UDP/TCP数据报发送到我们的目的服务器;同样,服务器发送来的UDP/TCP数据报被网关所接收,网关将其解码后,以ICMP的形式发送到本地主机,本机再解码构包后发送到客户端程序,这样就实现了对网关限制的突破,一次发送/接收共需要两次解包和构包。
本文主要针对使用ICMP协议来转发UDP数据报的功能,并以OICQ为背景,至于利用ICMP协议来突破TCP的限制,也大同小异。
三、QQicmp工作流程
以下是QQicmp的工作流程图:
QQ客户端<--UDP-->QQicmp(l)<--ICMP-->QQicmp(g)<--UDP-->Tencent服务器
其中QQ客户端和QQicmp(l)都运行在本机上,而QQicmp(g)则是运行在网关上(QQicmp(l)与QQicmp(g)均是同一程序,只是运行模式不同:
-l运行于本地主机,-g运行于网关上),Tencent服务器我想大家都清楚吧。
QQ客户端与QQicmp(l),QQicmp(g)与Tencent服务器之间以UDP通信,QQicmp(l)与QQicmp(g)之间则是以ICMP通信。
发送数据报时:
首先QQicmp(l)在特定端口监听来自QQ客户端的UDP数据报,解码构包后以ICMP的形式发送到网关;QQicmp(g)在网关上监听来自QQicmp(l)的ICMP数据报,解码构包后以UDP的形式发送到腾讯服务器。
接收数据报时:
首先QQicmp(g)在网关上接收来自腾讯服务器的UDP数据报,解码构包后以ICMP的形式发送到QQicmp(l);当QQicmp(l)接收到ICMP数据报后,同样解码构包,然后以UDP的形式发送到QQ客户端。
四、QQicmp代码分析
Win2000/xp都提供了自己构造数据报的功能,也就是我们可以自己定义发送IP数据报的各项内容,当然也可以监听通过主机的基于IP协议的各种数据报。
为了发送ICMP数据报及接收所有的IP数据报,我们必须自定义数据报的格式及校验和的求解:
ypedefstructipheader
{
unsignedcharh_lenver;//头部长度及版本
unsignedchartos;//服务类型
unsignedshorttotal_len;//报文总长度
unsignedshortident;//信息包标志
unsignedshortfrag_and_flags;//标志及分段偏移量
unsignedcharttl;//生命周期
unsignedcharproto;//协议类型
unsignedshortchecksum;//IP校验和
unsignedintsourceip;//源IP地址
unsignedintdestip;//目的IP地址
}IPHEADER,*PIPHEADER;
typedefstructicmpheader
{
unsignedchartype;//ICMP类型:
0->回送应答8->回送请求
unsignedcharcode;//代码
unsignedshortchecksum;//ICMP校验和
unsignedshortid;//标识符
unsignedshortseq;//序号
}ICMPHEADER,*PICMPHEADER;
unsignedshortchecksum(unsignedshort*buffer,intsize)//校验和的求法
{
unsignedlongcksum=0;
while(size>0)//各位求和
{
cksum+=*buffer++;
size-=sizeof(unsignedshort);
}
if(size)
cksum+=*(unsignedchar*)buffer;
cksum=(cksum>>16)+(cksum&0xffff);//移位,位与运算
cksum+=(cksum>>16);
return(unsignedshort)(~cksum);//再取反
}
首先,我们更改QQ客户端里的服务器地址为127.0.0.1,端口改为QQicmp(l)的监听端口,当然你也可以保持默认的8000,这样QQicmp(l)就应该选在8000端口监听QQ客户端的数据。
总之,QQ客户端里服务器端口应该和QQicmp(l)里所选的端口相同。
同时,QQ客户端也在端口4000(假设为非内网主机上的第一个QQ)监听来自QQicmp(l)的数据报。
我们可以看到,QQicmp(l)的主要作用之一是接收来自QQ客户端的UPD数据报,
sock[0][0]=socket(AF_INET,SOCK_DGRAM,0);//创建基于UDP协议的套接字
bind(sock[0][0],(structsockaddr*)&sin[0][1],addrlen);//绑定到指定地址,指定端口上
iret=recvfrom(sock[0][0],msgrecv,sizeof(msgrecv),0,(structsockaddr*)&tempr,&addrlen);//接收来自QQ客户端的UDP数据
然后以ICMP数据报的形式发送到QQicmp(g),在此需要自己构造ICMPEchoReply数据报,并将接收到的UDP数据填充到ICMP报文的数据段,
sock[0][1]=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);//创建ICMP协议的原始套接字,用来发送自定义数据报
bind(sock[0][1],(structsockaddr*)&sin[0][2],addrlen);//并捆绑到指定地址,指定端口上
if(istbcs==0)//填充ICMP数据报头部
{
icmphdr.type=0;//类型:
echoreply
icmphdr.code=0;//代码
icmphdr.checksum=0;//先将校验和设置为零
icmphdr.id=htons(65456);//序号
icmphdr.seq=htons(65456);//标志符,用以过滤数据报
memset(msgsend,0,sizeof(msgsend));
memcpy(msgsend,&icmphdr,sizeof(icmphdr));
istbcs+=sizeof(icmphdr);
}
memcpy(msgsend+istbcs,msgrecv,iret);//将接收到的UDP数据报的内容提取,放到即将发送的ICMP数据报内
icmphdr.checksum=checksum((USHORT*)&msgsend,ileft);//计算ICMP校验和
memcpy(msgsend,&icmphdr,sizeof(icmphdr));//重新填充ICMP头部
iret=sendto(sock[0][1],msgsend,istbcs,0,(structsockaddr*)&sin[0][3],addrlen);//发送ICMP数据报网关
同时,QQicmp(l)监听通过本机的IP数据报,筛选出来自QQicmp(g)既网关的数据报,
sock[1][0]=socket(AF_INET,SOCK_RAW,IPPROTO_IP);//创建原始套接字,接收所有的IP数据报
bind(sock[1][0],(structsockaddr*)&sin[1][1],addrlen);//绑定到指定地址,指定端口上
DWORDdwbufferlen[10];
DWORDdwbufferinlen=1;
DWORDdwbytesreturned=0;
WSAIoctl(sock[1][0],SIO_RCVALL,&dwbufferinlen,sizeof(dwbufferinlen),&dwbufferlen,sizeof(dwbufferlen),&dwbytesreturned,NULL,NULL);
//设置为接收所有的数据报,需要mstcpip.h头文件。
iret=recvfrom(sock[1][0],msgrecv,sizeof(msgrecv),0,(structsockaddr*)&temp1,&addrlen);//接收所有数据报
if(iret<=28)//文件过小
{
continue;
}
if((icmphdr->type!
=0)||(icmphdr->code!
=0)||((icmphdr->id)!
=htons(65456))||((icmphdr->seq)!
=htons(65456)))
//不符合接收条件
{
continue;
}
memcpy(msgsend+istbcs,msgrecv,iret);//将接收到的ICMP数据报的内容提取,准备以UDP的形式发送
解包后,用UDP数据报将接收到的来自网关的ICMP数据发送到QQ客户端,
idx=28;//ICMP数据报的前20字节是IP头部,接着的8字节是ICMP头部
iret=sendto(sock[1][1],&msgsend[idx],ileft,0,(structsockaddr*)&sin[1][3],addrlen);//发送到QQ客户端
我们创建了两个线程在两个方向(udp-->icmp,icmp-->udp)上接收并传送数据,如果某个线程出错,就重新创建该线程,而未出错的线程则保持不变,
hthreads[0]=CreateThread(NULL,0,u2i,(LPVOID)0,NULL,&hthreadid[0]);//创建接收udp数据,发送icmp数据的线程0
hthreads[1]=CreateThread(NULL,0,i2u,(LPVOID)1,NULL,&hthreadid[1]);//创建接收icmp数据,发送udp数据的线程1
while
(1)
{
dwret=WaitForMultipleObjects(2,hthreads,false,INFINITE);//等待某个线程的结束
if(dwret==WAIT_FAILED)//出错
{
cout<<"WaitForMultipleObjectsError:
"<return-1;
}
log=dwret-WAIT_OBJECT_0;
if(log==0)//线程0结束
{
CloseHandle(hthreads[0]);//关闭线程handle
closesocket(sock[0][1]);//关闭套接字
hthreads[0]=CreateThread(NULL,0,u2i,(LPVOID)0,NULL,&hthreadid[0]);//重新创建线程0
}
elseif(log==1)//线程1结束
{
CloseHandle(hthreads[1]);
closesocket(sock[1][0]);
hthreads[1]=CreateThread(NULL,0,i2u,(LPVOID)1,NULL,&hthreadid[1]);
}
以上就是QQicmp(l)的工作原理,QQicmp(g)运行在网关上,虽然模式不同,但工作原理是一样的,只是数据报的流动方向有点差异。
五、小结
本文利用了ICMP协议来传输数据,但由于ICMP协议自身的原因,可靠性就不可能得到很好的保证。
其实,你还可以用一些其他的方法来突破网关的限制,比如最近网上常谈到的ARP协议在某些情况下就可以使用,当然前提是你要获得网关的某些权限。
以上谈到的各种方法,本质上都是利用其他未被禁止的协议来转发被禁止协议需要传送的数据,然后再用原本使用的协议将数据发送到目的主机。
六、附源代码
#include
#include
#include
#defineimaxsize64*1024
typedefstructipheader
{
unsignedcharh_lenver;
unsignedchartos;
unsignedshorttotal_len;
unsignedshortident;
unsignedshortfrag_and_flags;
unsignedcharttl;
unsignedcharproto;
unsignedshortchecksum;
unsignedintsourceip;
unsignedintdestip;
}ipheader;
typedefstructicmpheader
{
unsignedchartype;
unsignedcharcode;
unsignedshortchecksum;
unsignedshortseq;
unsignedshortid;
}icmpheader;
unsignedshortchecksum(unsignedshort*buffer,intsize)
{
unsignedlongcksum=0;
while(size>0)
{
cksum+=*buffer++;
size-=sizeof(unsignedshort);
}
if(size)
cksum+=*(unsignedchar*)buffer;
cksum=(cksum>>16)+(cksum&0xffff);
cksum+=(cksum>>16);
return(unsignedshort)(~cksum);
}
intiaddrlen=sizeof(structsockaddr_in);
SOCKETsocki[2][2];
structsockaddr_insini[2][4],sag,sal,tempir,tempis;
DWORDWINAPIu2i(LPVOIDnum)
{
UNREFERENCED_PARAMETER(num);
charmsgrecv[imaxsize]={0},msgsend[imaxsize]={0};
fd_setfdread,fdwrite;
intiret,ret,istbcs=0,ileft,idx=0;
structicmpheadericmphdr;
memset(&icmphdr,0,sizeof(icmphdr));
icmphdr.code=0;
icmphdr.id=htons(65456);
icmphdr.seq=htons(65456);
icmphdr.type=0;
icmphdr.checksum=checksum((unsignedshort*)&icmphdr,sizeof(icmphdr));
if((socki[0][1]=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))==INVALID_SOCKET)
{
cout<<"Socketsocki[0][1]Error:
"<return-1;
}
if(bind(socki[0][1],(structsockaddr*)&sini[0][2],iaddrlen)==SOCKET_ERROR)
{
cout<<"Bindsocki[0][1]Error:
"<return-1;
}
while
(1)
{
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_SET(socki[0][0],&fdread);
FD_SET(socki[0][1],&fdwrite);
if((ret=select(0,&fdread,&fdwrite,NULL,NULL))==SOCKET_ERROR)
{
cout<<"Selectinthread0Error:
"<break;
}
if(ret>0)
{
if(FD_ISSET(socki[0][0],&fdread))
{
iret=recvfrom(socki[0][0],msgrecv,sizeof(msgrecv),0,(structsockaddr*)&tempir,&iaddrlen);
if(iret==SOCKET_ERROR)
{
cout<<"\nRecvfromsocki[0][0]Error:
"<break;
}
elseif(iret==0)
{
break;
}
if(tempir.sin_port!
=sini[0][0].sin_port)
{
sini[0][0].sin_port=tempir.sin_port;
sini[1][3].sin_port=tempir.sin_port;
}
cout<<"\nThread0Recv"<if(istbcs==0)
{
memset(msgsend,0,sizeof(msgsend));
memcpy(msgsend,&icmphdr,sizeof(icmphdr));
istbcs+=sizeof(icmphdr);
}
memcpy(msgsend+istbcs,msgrecv,iret);
istbcs+=iret;
memset(msgrecv,0,sizeof(msgrecv));
}
elseif(FD_ISSET(socki[0][1],&fdwrite))
{
ileft=istbcs;
idx=0;
while(ileft>0)
{
if(sini[0][3].sin_addr.s_addr==htonl(0))
{
cout<<"sini[0][3].sin_addr.s_addr==htonl(0)"<istbcs=0;
memset(msgsend,0,sizeof(msgsend));
break;
}
iret=sendto(socki[0][1],&msgsend[idx],ileft,0,(structsockaddr*)&sini[0][3],iaddrlen);
if(iret==SOCKET_ERROR)
{
cout<<"Sendtosocki[0][1]Error:
"<break;
}
elseif(iret==0)
break;
cout<<"Thread0send"<ileft-=iret;
idx+=iret;
}
memset(msgsend,0,sizeof(msgsend));
istbcs=0;
}
Sleep(20);
}
}
return0;
}
DWORDWINAPIi2u(LPVOIDnum)
{
UNREFERENCED_PARAMETER(num);
fd_setfdread,fdwrite;
charmsgrecv[imaxsize]={0},msgsend[imaxsize]={0};
intret,iret,idx,istbcs=0,ileft;
DWORDdwbufferlen[10];
DWORDdwbufferinlen=1;
DWORDdwbytesreturned=0;
structipheader*iphdr;
structicmpheader*icmphdr;
if((socki[1][0]=socket(AF_INET,SOCK_RAW,IPPROTO_IP))==INVALID_SOCKET)
{
cout<<"Socketsocki[1][0]Error:
"<return-1;
}
if(bind(socki[1][0],(structsockaddr*)&sini[1][1],iaddrlen)==SOC