端口和CGI的扫描实现<首发于2003年黑客防线11期>
WriteBy: LionD8
email: liond8@eyou.com
Website: http://liond8.126.com
一. DIY一个端口扫描器之-高级技术
端口的扫描技术到现在大致分为两种,一种就是低级传统的扫描器,还有就是高级技术的。今天我们就来讲讲高级技术的原理及其实现在基本代码。
经过测试我们知道正在LISTEN的端口,如果接收在一个SYN包(就是TCP握手的第一次)那么它就会返回一个SYN|ACK(0x12)包,如果一个
关闭的端口接收到SYN包就会返回一个PSH|RST|SYN(0x14)的包并且SYN序列号为0。如果远程主机不存在,那么不返回任何数据包。
根据上面的分析我们就可以构造一个扫描器了。
如果我们对目标机器发一个SYN包,如果接收到一个SYN|ACK(0x12)的包我们就知道远程端口是存活的。如果接收到PSH|RST|SYN(0x14)那么就确定那个端口没有开放。
好的那么我们在发包前就需要建立一个侦听线程来接收返回的数据包,并加一分析。
1.定义侦听线程:
DWORD WINAPI ListeningFunc(LPVOID lpvoid)
{
首先就需要建立一个原始套结字。
SOCKET rawsock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);
然后取得本机的IP地址,确定一个端口绑定rawsock。
struct hostent *pHostent;
CHAR name[100]={0};
gethostname(name, 100);
pHostent=gethostbyname(name);
把本机IP地址复制到addr_in.sin_addr.S_un.S_addr中。
memcpy(&addr_in.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length);
绑定。
int ret=bind(rawsock, (struct sockaddr *)&addr_in, sizeof(addr_in));
设置SIO_RCVALL 接收所有的数据包
DWORD lpvBuffer = 1;
DWORD lpcbBytesReturned = 0;
WSAIoctl(rawsock, SIO_RCVALL, &lpvBuffer, sizeof(lpvBuffer), NULL, 0, &lpcbBytesReturned, NULL, NULL);
然后剩下的就是对数据包的捕获分析了。用一个死循环来不断的捕获接收到的数据包,分析如果是存活端口返回的包就打出来,不是的话就放弃,不做任何处理,继续捕获下一个数据包。
while (TRUE)
{
SOCKADDR_IN from={0};
int size=sizeof(from);
char RecvBuf[256]={0};
//接收数据包
ret=recvfrom(rawsock,RecvBuf,sizeof(RecvBuf),0,(struct sockaddr*)&from,&size);
char* sourceip=inet_ntoa(* (struct in_addr *)&from.sin_addr);
if(ret!=SOCKET_ERROR)
{
// 分析数据包
IPHEADER *lpIPheader;
lpIPheader=(IPHEADER *)RecvBuf;
if (lpIPheader->proto==IPPROTO_TCP)
{
TCPHEADER *lpTCPheader=(TCPHEADER*)(RecvBuf+sizeof(IPHEADER));
//判断是不是远程开放端口返回的数据包
if (lpTCPheader->th_seq != 0 && lpTCPheader->th_flag==0x12)
{
//如果是,就从TCP头中提出端口源端口信息,打印出来。
printf("===%s:%d\n",sourceip,ntohs(lpTCPheader->th_sport));
}
}
}
} // end while
} 一上就是我们要建立的侦听分析线程。
IPHEADER 和 TCPHEADER的定义分别如下。
typedef struct ip_head //定义IP首部
{
unsigned char h_verlen; //4位首部长度,4位IP版本号
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度(字节)
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位(如SYN,ACK,等)
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (如ICMP,TCP等)
unsigned short checksum; //16位IP首部校验和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IPHEADER;
typedef struct tcp_head //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度/6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCPHEADER;
侦听搞定了,剩下的问题就是怎么构造SYN
包发送了。一般SYN包的发送都是自己构造IP头和TCP头部,然后用一个TCP伪头来计算效验和,一般情况下这样打造的。这样自己造SYN也是可以的,
但是我们的程序效率就会大大降低,我们用另外一个高效率方法。怎么才可以发出一个SYN包而不用自己构造呢?知道有个connect()API吗?本来那
是用来建立联接用的。它里面就隐藏了TCP的三次握手,如果我们在发出一个SYN包后就关闭套结字,是不是就能起到发一个SYN包的功效了啊?所以就必须
设置套结字为非阻塞的。
2. 发SYN包的实现如下。
DWORD WINAPI scan(LPVOID lp)
{
//lp为自己定义的一个结构地址,用来传递扫描目标的IP地址和端口信息。
// 如下:
// typedef struct //定义一个传递IP和端口,信息的结构
// {
// ULONG IP;
// USHORT port;
// }INFOR;
SOCKET sock=NULL;
SOCKADDR_IN addr_in={0};
TCHAR SendBuf[256]={0};
INFOR* lpInfor=(INFOR*)lp;
int iErr;
ULONG ul=1;
USHORT port=lpInfor->port;
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(port);
addr_in.sin_addr.S_un.S_addr=lpInfor->IP;
if ((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
printf("Socket Setup Error!\n");
iErr=ioctlsocket(sock,FIONBIO,(unsigned long*)&ul); //设置sock为非阻塞
connect(sock,(struct sockaddr *)&addr_in,sizeof(addr_in)); //发送SYN包
closesocket(sock);
return 0;
}
最主要的侦听和发送线程都完成了,剩下的就只是,怎么提取IP地址和端口然后传给发送函数了
int main(int argc, char* argv[])
{
WSADATA WSAData;
INFOR infor={0};
ULONG StartIP=0, EndIP=0;
int number=0;
if ( WSAStartup(MAKEWORD(2,2), &WSAData)!=0 )
{
printf("InitWSAStartup Error!\n");
return 0;
}
//创建一个嗅包的线程分析接收到的包。
CreateThread(NULL,0,ListeningFunc,&tempnum,NULL,NULL);
Sleep(500); //等待线程的初始化完成
ULONG StartIP=0, EndIP=0;
StartIP=ntohl(inet_addr(argv[1]));
EndIP =ntohl(inet_addr(argv[2]));
for ( ; StartIP <= EndIP ; StartIP++) //从第一个IP到最后一个IP
{
infor.IP=htonl(StartIP);
int Num=ListNum; //ListNum为定义的端口列表的长度
while ( Num-- )
{
infor.port=PortList[Num]; //从列表中取得端口值,准备传递给发包函数
scan(&infor); //对目标IP,端口发送SYN包.
}
} //end for
Sleep(2000); //最后等待2s,等最后发出的包返回。
printf("Scan completely.");
return 1;
} //主线程返回 程序结束。
以上全部代码基本上就是一个扫描器了。根据实际测试上面的这种扫描方法速度是相当快的。
二. CGI的扫描器
CGI
的扫描前提是开放了80(Web服务)才可以利用的。首先我们必须和远程的80端口建立联接。然后通过提交GET请求,再根据返回的信息加以判断的。例如
返回的200代表成功,一切正常。404代表无法找到指定位置的资源。403代表资源不可用等。然后我们侦听返回的数据是不是有"HTTP/1.1
200"这样的子串。有代表请求成功,否则请求失败。
大致的实现如下:
int main(int argc, char* argv[])
{
WSADATA WSAData;
FILE* fp=NULL;
fp= fopen("cgi.lst","r");
//cgi.lst是个CGI漏洞的列表里面的全部是类似"GET /_vti_bin/shtml.exe"这样的。
WSAStartup(MAKEWORD(2,2), &WSAData);
INFOR infor={0};
// INFOR 结构用来传递CGI信息和IP信息
// 定义如下:
// typedef struct
// {
// char sendbuf[100];
// char IP[20];
// }INFOR;
if (argc !=2 ) return 0;
memcpy (infor.IP,argv[1],strlen(argv[1]));
printf("Scan start.......\n");
// 从文件中读取要扫描的CGI信息
while ( fgets(infor.sendbuf,100,fp) !=NULL )
{
HANDLE h=0;
h=CreateThread(NULL,0,scan,&infor,NULL,NULL); //创建一个线程扫描
if ( h == NULL )
printf("CreateThread false\n");
WaitForSingleObject(h,INFINITE); //等待一次扫描结束
memset(infor.sendbuf,0,100);
}
printf("Scan completely.\n");
} // end main
scan线程定义如下:
DWORD WINAPI scan(LPVOID lp)
{
SOCKET sock=NULL;
SOCKADDR_IN target={0};
int error=0;
char buf[256]={0};
char* p=NULL;
INFOR* lpInfor =(INFOR*)lp;
if ( (sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
printf("Socket Setup Error!\n");
return false;
}
target.sin_family=AF_INET;
target.sin_port=htons(80);
target.sin_addr.S_un.S_addr=inet_addr(lpInfor->IP);
error=connect (sock, (struct sockaddr* )&target, sizeof(target)); //连接
if (error == SOCKET_ERROR)
{
printf("Connect false!\n");
return 0;
}
send(sock,lpInfor->sendbuf,100,0); //发送GET请求
recv(sock,buf,256,0); //接收返回的信息
p=strstr(buf,"HTTP/1.1 200"); //查找返回的信息中有有没有HTTP/1.1 200子串。
if ( p!=NULL) //200的意思是一切正常,对GET和POST请求的应答文档跟在后面
{
printf("%s",lpInfor->sendbuf); //把扫描到的漏洞打出来
}
closesocket(sock);
return 1;
}
如转载:请说明作者信息,表明首发刊物。