最后一个暑假,因为已经有一个项目在手,不想把自己整得太累,但又不能太闲,于是选择了一个可以拖拖踏踏做的简单事情,做一套端口扫描。这种平时集中精力几天就可以做完的事情,结果真是拖拖踏踏了很久才做完。
进入正题。端口扫描,即是通过一些扫描的手段来探知某些ip的端口情况,主要为探知端口的开放情况与端口的应用类型。
由于各个设备的系统与防火墙的差异,某一单一的端口扫描方式难以应用于所有的系统,而且扫描方并不知道扫描对方的系统情况。为了应对不同情况,有必要采用多种扫描方式协同扫描。在此我使用了connect、tcp syn、tcp fin、helo 这几种方式的扫描。具体的介绍可参考 端口扫描技术。
希望自己的扫描能够高效而准确,于是对扫描策略做了如下设计:
syn、fin扫描方式用于快速端口扫描,使用多线程发送扫描包,另有一处于混杂模式的socket用户接收处理所有的扫描响应。有人说syn扫描方式不适合使用多线程,他的解释是:“使用多线程容易发生数据报的串位现象,也就是原来应该这个线程接收的数据报被另 一个线程接收,接收后,这个数据报就会被丢弃,而等待线程只好在超时之后再发送一个SYN数据报,等待应答。这样,所用的时间反而会增加”。但这并不是问题,注意我这里用的是混杂的原始套接字,所有的数据包都不会漏,只要对这些数据包的处理逻辑写好就没有任何问题。
快速端口扫描设计图:
connect、helo方式用于精确扫描,使用多线程connect端口,同时使用这些线程获取反馈信息。最后处理数据并录入到数据库,这里的重点在于确认端口的开放与判断端口的应用类型。通过helo方式,可以获取到端口返回的应用信息,对返回的信息进行处理识别即可。
精确端口扫描设计图:
扫描结果都将存入数据库:
UI:哈哈,自从在linux上编程,除了我电脑虚拟机的linux,我的服务器上的linux都是没ui的,因此再也没写过任何的C++UI,如果还是用命令行查看端口扫描的结果,未免太寒酸,而且只能自己使用。于是用php粗略地做了个UI。
但是使用php执行端口扫描进程需要http服务具有管理员权限,因为我的c++扫描程序使用到了原始套接字,linux下一般必须有root权限才能使用原始套接字。如果没法通过加sudo的方式执行扫描进程,那么就需要修改httpd的源码重新编译安装后再将httpd配置root权限。具体可参考:httpd转root。
以下讲解一些关键性的代码:
专用数据库代码的封装:
#ifndef SQLUSE_H_INCLUDED #define SQLUSE_H_INCLUDED #include <mysql/mysql.h> #include <string> using namespace std; #define LOGINSIZE 32 #define SQLFORMATSIZE 512 #define data_statu_end 0 #define data_statu_wait 1 #define data_statu_rewait 2 #define data_statu_error 3 #define data_statu_run 4 #define data_type_unkonw 0 struct model_data { int id; string created_at; string updated_at; string ip; int port; string info; int type; int statu; }; char *sqlencode(const char *str,const int len); class sqluse { char sqlhost[LOGINSIZE]; unsigned int sqlport; char dbname[LOGINSIZE]; char username[LOGINSIZE]; char userpass[LOGINSIZE]; char charset[LOGINSIZE]; MYSQL *con; public: char insertdataformat[SQLFORMATSIZE]; char waitdataselectformat[SQLFORMATSIZE]; char statudataupdateformat[SQLFORMATSIZE]; char infodataupdateformat[SQLFORMATSIZE]; char suprunidformat[SQLFORMATSIZE]; char supresultformat[SQLFORMATSIZE]; bool getsqlset(); bool getformatdata(); bool connect(); bool setcharset(); public: sqluse(); ~sqluse(); MYSQL_RES *query(char *sql); bool insertdata(string ip,int port,int type=1,int statu=1); int getwaitdata(model_data *arr,int datanum=1); bool resetdata(int id); bool seterrordata(int id,int statu); bool updateinfodata(int id,string info="",int type=data_type_unkonw); }; #endif
#include "sqluse.h" #include <cstdlib> #include <cstdio> #include <cstring> const char encodechar[]="',;"; char *sqlencode(const char *str,const int len) { if(str==NULL) { puts("vv"); return NULL; } int cnt=0; int elen=strlen(encodechar); for(int i=0;i<len;i++) { for(int j=0;j<elen;j++) { if(str[i]==encodechar[j]) { cnt++; } } } char *s=new char[len+cnt*2+1]; int slen=0; for(int i=0;i<len;i++) { s[slen]=str[i]; for(int j=0;j<elen;j++) { if(str[i]==encodechar[j]) { s[slen++]='\'; s[slen]=str[i]; break; } } slen++; } s[slen]='