icmp篇 突破TCP-IP过滤/防火墙进入内网


随着Internet网络的普及 , 各个中大型公司均建立了自己的局域网络 。而公司内部人员上网的限制也逐渐成为一个大家关心的话题 。目前最为流行的网络工具大多是基于TCP/IP协议的 , 而其中最主要的两个协议就是TCP和UDP协议 。HTTP , FTP等上层协议均是建立在TCP协议之上了 , 而DNS , ICQ , TFTP等则是建立在UDP协议之上的 。往往我们会遇到这样情况:公司禁止了UDP协议 , 因为很大一部分的网络通讯软件都是建立在UDP协议之上的 , 而开通了TCP协议 。这样 , 我们就可以通过TCP协议来为我们转发UDP数据报 , 具体实现原理可以参看eyas的《突破TCP-IP过滤/防火墙进入内网》 , 里面详细讨论了如何实现TCP与UDP数据报之间的相互转发 , 也可以参看本文相关软件T-QQ的源代码 , 里面也包含了TCP与UDP相互转发的功能 , 在此就不多说了 。现在进入正题 , 如何实现用ICMP数据报来突破网关的限制?
ICMP协议(Internet Control Messages Protocol, 网际控制报文协议)是一种多功能的协议 , 在网络上有很多用处 , 比如ICMP扫描 , 拒绝服务(Dos)攻击 , 隧道攻击 , 以及我们最常用到的PING程序 。而我们就是利用ICMP协议来为我们传送(TCP/UDP)数据 。大家知道一般的防火墙都是过滤了来自外部主机的回送请求(echo Request)报文 , 但为了是自己能够探测外部主机的当前状态 , 防火墙都不会过滤掉回送应答(echo Reply)数据报 , 而且ICMP报文可以在广域网上传送 , 这样我们就可以利用它来突破网关的种种限制 。本文主要针对使用ICMP协议来转发UDP数据报的功能 , 并以OICQ为背景 , 至于突破TCP的限制 , 也大同小异 。
以下是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通信 。Win2000/xp都提供了自己构造数据报的功能 , 也就是我们可以自己定义发送数据报的各项内容 , 当然也可以监听通过主机的基于IP协议的各种数据报 。为了发送ICMP数据报及接收所有的IP数据报 , 我们必须自定义数据报的格式及校验和的求解:
typedef struct ipheader
{
unsigned char h_lenver; //头部长度及版本
unsigned char tos; //服务类型
unsigned short total_len; //报文总长度
unsigned short ident; //信息包标志
unsigned short frag_and_flags; //标志及分段偏移量
unsigned char ttl; //生命周期
unsigned char proto; //协议类型
unsigned short checksum; //IP校验和
unsigned int sourceip; //源IP地址
unsigned int destip; //目的IP地址
}ipheader;typedef struct icmpheader
{
unsigned char type; //ICMP类型 0->回送应答 8->回送请求
unsigned char code; //代码
unsigned short checksum; //校验和
unsigned short seq; //序号
unsigned short id; //标识符
}icmpheader;unsigned short checksum(unsigned short *buffer,int size)
{
unsigned long cksum=0;
while(size>0) //各位求和
{
cksum =*buffer;
size-=sizeof(unsigned short);
}
if(size)
cksum =*(unsigned char *)buffer;
cksum=(cksum>>16) (cksum & 0xffff);
cksum =(cksum>>16);
return (unsigned short)(~cksum); //再求补
}首先 , 我们更改QQ客户端里的服务器地址为127.0.0.1 , 端口改为QQicmp(l)的监听QQ客户端端口 , 当然你也可以保持默认的8000 ,
这样QQicmp(l)就应该在8000端口监听QQ客户端的数据 。
同时 , QQ客户端也在端口4000(假设为非内网主机上的第一个QQ)监听来自QQicmp(l)的数据报 。
我们可以看到 , QQicmp(l)的主要作用就是将接收到了来自QQ客户端的UPD数据报 ,
sock[0][0]=socket(AF_INET,SOCK_DGRAM,0); //创建基于UDP协议的套接字
bind(sock[0][0],(struct sockaddr *)&sin[0][1],addrlen); //绑定到指定地址 , 指定端口上
iret=recvfrom(sock[0][0],msgrecv,sizeof(msgrecv),0,(struct sockaddr *)&tempr,&addrlen); //接收来自QQ客户端的UDP数据然后以ICMP数据报的形式发送到QQicmp(g) , 在此需要自己构造ICMP echo Reply数据报,并将接收到的UDP数据报填充到ICMP报文的数据段 ,
sock[0][1]=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP); //创建ICMP协议的原始套接字 , 用来发送自定义数据报
bind(sock[0][1],(struct sockaddr *)&sin[0][2],addrlen); //并捆绑到指定地址 , 指定端口上icmphdr.type=0; //类型:echo reply
icmphdr.code=0; //代码
icmphdr.id=htons(65456); //序号
icmphdr.seq=htons(65456); //标志符 , 用以过滤数据报
icmphdr.checksum=0;if(istbcs==0) //填充ICMP数据报头部
{
memset(msgsend,0,sizeof(msgsend));
memcpy(msgsend,&icmphdr,sizeof(icmphdr));
istbcs =sizeof(icmphdr);
}
memcpy(msgsend istbcs,msgrecv,iret); //将接收到的UDP数据报的内容提取 , 准备以ICMP的形式发送iret=sendto(sock[0][1],msgsend,istbcs,0,(struct sockaddr *)&sin[0][3],addrlen); //发送到网关
同时 , QQicmp(l)监听通过本机的IP数据报 , 筛选出来自QQicmp(g)及网关的数据报 ,
sock[1][0]=socket(AF_INET,SOCK_RAW,IPPROTO_IP); //创建原始套接字 , 接收所有的IP数据报
bind(sock[1][0],(struct sockaddr *)&sin[1][1],addrlen); //绑定到指定地址 , 端口DWord dwbufferlen[10];
DWORD dwbufferinlen=1;
DWORD dwbytesreturned=0;
WSAIoctl(sock[1][0],SIO_RCVALL,&dwbufferinlen,sizeof(dwbufferinlen),&dwbufferlen,sizeof(dwbufferlen),&dwbytesreturned,NULL,NULL);
//设置为接收所有的数据报 , 需要mstcpip.h头文件 , T-QQ相关文件里就有 , 或安装SDKiret=recvfrom(sock[1][0],msgrecv,sizeof(msgrecv),0,(struct sockaddr *)&temps,&addrlen); //接收所有数据报
if(iret<=28) //文件过小
{
break;
}
if((icmphdr->type!=0) || (icmphdr->code!=0) || ((icmphdr->id)!=htons(65456)) || ((icmphdr->seq)!=htons(65456)))
//不符合接收条件
{
break;
}memcpy(msgsend istbcs,msgrecv,iret); //将接收到的ICMP数据报的内容提取 , 准备以UDP的形式发送
解包后 , 用UDP数据报将接收到的来自网关的数据发送到QQ客户端 ,
idx=28; //ICMP数据报的前20字节是IP头部 , 接着的8字节是ICMP头部 ,
iret=sendto(sock[1][1],&msgsend[idx],ileft,0,(struct sockaddr *)&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发送数据的线程1while(1)
{
dwret=WaitForMultipleObjects(2,hthreads,false,INFINITE); //等待某个线程的结束
if(dwret==WAIT_FAILED) //等待出错
{
cout<<"WaitForMultipleObjects Error: "<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
}
else if(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)运行在网关上 , 虽然模式不同 , 但工作原理是一样的 , 只是数据报的流动方向有点差异 。
QQicmp之源代码如下:#include
#include
#include
#include #pragma comment (lib,"ws2_32")
#define maxsize 64*1024typedef struct ipheader
{
unsigned char h_lenver;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceip;
unsigned int destip;
}ipheader;typedef struct icmpheader
{
unsigned char type;
unsigned char code;
unsigned short checksum;
unsigned short seq;
unsigned short id;
}icmpheader;unsigned short checksum(unsigned short *buffer,int size)
{
unsigned long cksum=0;
while(size>0)
{
cksum =*buffer;
size-=sizeof(unsigned short);
}
if(size)
cksum =*(unsigned char *)buffer;
cksum=(cksum>>16) (cksum & 0xffff);
cksum =(cksum>>16);
return (unsigned short)(~cksum);
}void start()
{
cout<<" ---------------------------------------------------n";
cout<<" || || n";
cout<<" || QQicmp (ICMP转发) || n";
cout<<" || || n";
cout<<" || Author:TOo2y SafeChina || n";
cout<<" || || n";
cout<<" ---------------------------------------------------"<}void usage()
{
cout<<"nUsage:rntQQicmp -l[-g] ip port"<cout<<"tQQicmp -h"<cout<<"Example:rn";
cout<<"tQQicmp -l 192.168.0.1 8000"<cout<<"tQQicmp -g 61.144.238.156 11282"<cout<<"Attention:"<cout<<"t选项 -l : 运行于本机上 , ip填网关地址 , port为本地监听客户端端口;"<cout<<"t选项 -g : 运行于网关上 , ip填腾讯服务器地址 , port为自定义端口;"<cout<<"t选项 -h : 查看相关帮助文件 。"<}int addrlen=sizeof(struct sockaddr_in);
SOCKET sock[2][2];
struct sockaddr_in sin[2][4],sag,sal,tempr,temps;DWORD WINAPI u2i(LPVOID num)
{
UNREFERENCED_PARAMETER(num);
char msgrecv[maxsize]={0},msgsend[maxsize]={0};
fd_set fdread,fdwrite;
int iret,ret,istbcs=0;
struct icmpheader icmphdr;memset(&icmphdr,0,sizeof(icmphdr));
icmphdr.code=0;
icmphdr.id=htons(65456);
icmphdr.seq=htons(65456);
icmphdr.type=0;
icmphdr.checksum=checksum((unsigned short *)&icmphdr,sizeof(icmphdr));if((sock[0][1]=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))==INVALID_SOCKET)
{
cout<<"Socket sock[0][1] Error: "<return -1;
}
if(bind(sock[0][1],(struct sockaddr *)&sin[0][2],addrlen)==SOCKET_ERROR)
{
cout<<"Bind sock[0][1] Error: "<return -1;
}while(1)
{
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_SET(sock[0][0],&fdread);
FD_SET(sock[0][1],&fdwrite);if((ret=select(0,&fdread,&fdwrite,NULL,NULL))==SOCKET_ERROR)
{
cout<<"Select in thread 0 Error: "<break;
}
if(ret>0)
{
if(FD_ISSET(sock[0][0],&fdread))
{
iret=recvfrom(sock[0][0],msgrecv,sizeof(msgrecv),0,(struct sockaddr *)&tempr,&addrlen);
if(iret==SOCKET_ERROR)
{
cout<<"nRecvfrom sock[0][0] Error: "<break;
}
else if(iret==0)
{
cout<<"Iret==0"<break;
}
cout<<"nThread 0 Recv "<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));
}
else if(FD_ISSET(sock[0][1],&fdwrite))
{while(istbcs>0)
{if(sin[0][3].sin_addr.s_addr==htonl(0))
{
cout<<"sin[0][3].sin_addr.s_addr==htonl(0)"<istbcs=0;
memset(msgsend,0,sizeof(msgsend));
break;
}iret=sendto(sock[0][1],msgsend,istbcs,0,(struct sockaddr *)&sin[0][3],addrlen);
if(iret==SOCKET_ERROR)
{
cout<<"Sendto sock[0][1] Error: "<break;
}
cout<<"Thread 0 send "<istbcs-=iret;
}
memset(msgsend,0,sizeof(msgsend));
istbcs=0;
}
Sleep(20);
}
}
return 0;
}
DWORD WINAPI i2u(LPVOID num)
{
UNREFERENCED_PARAMETER(num);
fd_set fdread,fdwrite;
char msgrecv[maxsize]={0},msgsend[maxsize]={0};
int ret,iret,idx,istbcs=0,ileft;
DWORD dwbufferlen[10];
DWORD dwbufferinlen=1;
DWORD dwbytesreturned=0;
struct ipheader *iphdr;
struct icmpheader *icmphdr;if((sock[1][0]=socket(AF_INET,SOCK_RAW,IPPROTO_IP))==INVALID_SOCKET)
{
cout<<"Socket sock[1][0] Error: "<return -1;
}
if(bind(sock[1][0],(struct sockaddr *)&sin[1][1],addrlen)==SOCKET_ERROR)
{
cout<<"Bind sock[1][0] Error: "<return -1;
}WSAIoctl(sock[1][0],SIO_RCVALL,&dwbufferinlen,sizeof(dwbufferinlen),&dwbufferlen,sizeof(dwbufferlen),
&dwbytesreturned,NULL,NULL);
iphdr=(struct ipheader *)msgrecv;
icmphdr=(struct icmpheader *)(msgrecv sizeof(struct ipheader));while(1)
{
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_SET(sock[1][0],&fdread);
FD_SET(sock[1][1],&fdwrite);if((ret=select(0,&fdread,&fdwrite,NULL,NULL))==SOCKET_ERROR)
{
cout<<"Select in thread 1 Error: "<break;
}
if(ret>0)
{
if(FD_ISSET(sock[1][0],&fdread))
{
{
iret=recvfrom(sock[1][0],msgrecv,sizeof(msgrecv),0,(struct sockaddr *)&temps,&addrlen);
if(iret==SOCKET_ERROR)
{
cout<<"Recvfrom sock[1][0] Error: "<break;
}if(iret<=28)
{
break;
}
if((icmphdr->type!=0) || (icmphdr->code!=0) || ((icmphdr->id)!=htons(65456)) || ((icmphdr->seq)!=htons(65456)))
{
break;
}
if((sin[1][0].sin_addr.s_addr!=htonl(0)) && (sin[1][0].sin_addr.s_addr!=temps.sin_addr.s_addr))
break;
}
else if(sin[1][0].sin_addr.s_addr==htonl(0))
{
sin[1][0].sin_addr.s_addr=temps.sin_addr.s_addr;
sin[0][3].sin_addr.s_addr=temps.sin_addr.s_addr;
cout<<"sin[0][3] ==> "<}cout<<"nThread 1 Recv "<istbcs =iret;
memset(msgrecv,0,sizeof(msgrecv));
}
}
else if(FD_ISSET(sock[1][1],&fdwrite))
{
ileft=istbcs-28;
idx=28;
while(ileft>0)
{
iret=sendto(sock[1][1],&msgsend[idx],ileft,0,(struct sockaddr *)&sin[1][3],addrlen);
if(iret==SOCKET_ERROR)
{
cout<<"Sendto sock[1][1] Error: "<break;
}
cout<<"Thread 1 send "<ileft-=iret;
idx =iret;
}
istbcs=0;
memset(msgsend,0,sizeof(msgsend));
}
Sleep(20);
}
}
return 0;
}
int main(int argc,char *argv[])
{
WSADATA wsa;
BOOL gl;
HANDLE hthreads[2];
DWORD hthreadid[2];
struct hostent *hp;
char cname[100];
int dwret,log;system("cls.exe");
start();if(argc==2)
{
if(strcmp(argv[1],"-h")==0)
{
ShellExecute(NULL,"open","help.txt",NULL,NULL,SW_SHOWMAXIMIZED);
return 0;
}
else
{
usage();
return -1;
}
}
else if(argc!=4)
{
usage();
return -1;
}
if(!strcmp(argv[1],"-g"))
gl=true;
else if(!strcmp(argv[1],"-l"))
gl=false;
else
{
usage();
return -1;
}if(WSAStartup(MAKEWORD(2,2),&wsa)!=0)
{
cout<<"WSAStartup Error: "<return -1;
}gethostname(cname,sizeof(cname));
hp=gethostbyname(cname);
for(int ipnum=0;hp->h_addr_list[ipnum]!=NULL;ipnum)
sag.sin_addr=*(in_addr *)hp->h_addr_list[ipnum];
sag.sin_family=AF_INET;
sag.sin_port=htons(65456);sal=sag;
if(ipnum>1)
sal.sin_addr=*(in_addr *)hp->h_addr_list[ipnum-2];if(gl)
{
sin[0][0].sin_addr.s_addr=inet_addr(argv[2]);
sin[0][0].sin_family=AF_INET;
sin[0][0].sin_port=htons(8000);sin[0][1].sin_addr.s_addr=htonl(INADDR_ANY);
sin[0][1].sin_family=AF_INET;
sin[0][1].sin_port=htons(atoi(argv[3]));sin[0][2]=sal;memset(&sin[0][3],0,sizeof(sin[0][3]));
sin[0][3].sin_family=AF_INET;
}
else
{
sin[0][0].sin_addr.s_addr=inet_addr("127.0.0.1");
sin[0][0].sin_family=AF_INET;
sin[0][0].sin_port=htons(4000);sin[0][1].sin_addr.s_addr=htonl(INADDR_ANY);
sin[0][1].sin_family=AF_INET;
sin[0][1].sin_port=htons(atoi(argv[3]));sin[0][2]=sal;sin[0][3].sin_addr.s_addr=inet_addr(argv[2]);
sin[0][3].sin_family=AF_INET;
}
sin[1][0]=sin[0][3];
sin[1][1]=sin[0][2];
sin[1][2]=sin[0][1];
sin[1][3]=sin[0][0];if((sock[0][0]=socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET)
{
cout<<"Socket sock[0][0] Error: "<return -1;
}
if(bind(sock[0][0],(struct sockaddr *)&sin[0][1],addrlen)==SOCKET_ERROR)
{
cout<<"Bind sock[0][0] Error: "<return -1;
}
sock[1][1]=sock[0][0];cout<<"n正常工作中..."<hthreads[1]=CreateThread(NULL,0,i2u,(LPVOID)1,NULL,&hthreadid[1]);
while(1)
{
dwret=WaitForMultipleObjects(2,hthreads,false,INFINITE);
if(dwret==WAIT_FAILED)
{
cout<<"WaitForMultipleObjects Error: "<return -1;
}
log=dwret-WAIT_OBJECT_0;
if(log==0)
{
CloseHandle(hthreads[0]);
closesocket(sock[0][1]);
hthreads[0]=CreateThread(NULL,0,u2i,(LPVOID)0,NULL,&hthreadid[0]);
}
else if(log==1)
{
CloseHandle(hthreads[1]);
closesocket(sock[1][0]);
hthreads[1]=CreateThread(NULL,0,i2u,(LPVOID)1,NULL,&hthreadid[1]);
}
else
{
for(int no1=0;no1<2;no1)
{
CloseHandle(hthreads[no1]);
for(int no2=0;no2<2;no2)
closesocket(sock[no1][no2]);
}
}
}
WSACleanup();
return 0;
}本文相关软件T-QQ主要针对禁止使用QQ的网关 , 提供UDP , TCP及ICMP数据报转发功能 ,
本软件同样适用于各种基于UDP协议的通信软件 。其中的TCP数据报转发功能 , 也可以使用UDP数据报来转发TCP数据 。
【icmp篇 突破TCP-IP过滤/防火墙进入内网】