為什么要寫一個端口掃描的程序,Java來寫不是很方便嗎?
因為我也沒有想到什么例子能夠方便的來練習。於是想到以前找到的端口掃描的C代碼,於是想用他們來練習。
掃描服務端端口的方式有許多種,最簡單的就是直接去連接該端口,復雜一些的就是看SYN的應答。其他方式就不說了。
下面的portScan.c位於jni目錄下:(原本可在linux下運行,修改部分代碼,使之能夠返回結果給Java層的調用。)
#include<stdio.h> #include<stdlib.h> #include<jni.h> #include <android/log.h> #include<sys/socket.h> #include<netdb.h> #include<string.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> #include<fcntl.h> #include<time.h> #include<sys/types.h> #define TIMEOUT 5 //由於把socket設置為非阻塞,使用select函數,觀察其 //5秒后的是否連接成功,連接不成功則認為其端口沒有開放 #define LOG_TAG "System.out" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) struct servenet{ char * s_name; char** s_aliases; int s_port; char* s_proto; }; //可復用工具方法,其實也不用寫的這么復雜,因為JNI提供了一個轉換函數,你可以自己去查,但是可以了解c代碼調用Java方法的過程。 //返回char數組的首地址 //將java的String對象類型的數據轉換為C語言的char數組類型 char* Jstring2Cstr(JNIEnv* env,jstring jstr) { char * rtn=NULL; jclass clsString=(*env)->FindClass(env,"java/lang/String"); jstring strEncode=(*env)->NewStringUTF(env,"GB2312"); //下一句不知道為什么不能被保存,有沖突!!!!!!!// jmethodID mid=(*env)->GetMethodID(env,clsString,"getBytes","(Ljava/lang/String;)[B"); //第三個參數為java中的方法的簽名,可以通過java -s 加上包名.類名 查看 jbyteArray jba=(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strEncode); //相當於執行了String的getBytes(“GB2312”)方法 jsize len=(*env)->GetArrayLength(env,jba); jbyte* jbt=(*env)->GetByteArrayElements(env,jba,JNI_FALSE);//獲取元素 if(len > 0){ rtn=(char*)malloc(len+1); memcpy(rtn,jbt,len);//將jbyte*復制到rtn中去 rtn[len]=0;//=='\0' } (*env)->ReleaseByteArrayElements(env,jba,jbt,0); //jbyte是新開辟的空間,現在不用了所以要釋放,0表示釋放jba指向的全部內存空間 return rtn; } jintArray startScan(JNIEnv*,char* argv,jint start_port,jint end_port);
//下面是主函數,返回int數組 jintArray Java_com_linux_portscaner_MainActivity_scan(JNIEnv*env,jobject clazz, jstring argv,jint start_port,jint end_port){ LOGD("%s","in c before start"); char *host_argv=Jstring2Cstr(env,argv); jintArray rtn=startScan(env,host_argv,start_port,end_port); //return (**env).NewStringUTF(env,"hello from native c"); return rtn; } //int main(int argc,char** argv) //參數是目標機的IP jintArray startScan(JNIEnv*env,char* argv,jint start_port,jint end_port) { //int port[65535]={0};index=0;//返回開放的端口號組成的數組 //char* rtn="\n"; struct sockaddr_in server; int ret; int len; int count=0;//number of ports that are open int scanport;int end_p=(int)end_port;//將jint轉換為int型 jint portsOpen[20]={0}; jintArray jArray=(*env)->NewIntArray(env,20);//int a[20] int sockfd; int flag;//標示是ip還是主機名 struct hostent *host;//存放主機信息 fd_set rset; fd_set wset; struct servenet *sp; // if(argc<2) // { // fprintf(stderr,"Please enter the server's IP or hostname!\n"); // exit(1); // } // if((host=gethostbyname("www.baidu.com"))==NULL) if((host=gethostbyname(argv))==NULL) flag=0; else flag=1;//domain name for(scanport=(int)start_port;scanport<=end_p;scanport++) { if (-1==(sockfd=socket(AF_INET,SOCK_STREAM,0))) { //perror("can not create socket\n"); LOGD("%s","can not create socket"); return; //exit(1); } memset(&server,0,sizeof(struct sockaddr_in)); server.sin_family = AF_INET; if(!flag){ server.sin_addr.s_addr = inet_addr(argv); }else server.sin_addr=*((struct in_addr*)host->h_addr); server.sin_port = htons(scanport); int flag2 = fcntl(sockfd, F_GETFL,0); fcntl(sockfd,F_SETFL, flag2|O_NONBLOCK); struct timeval tm; tm.tv_sec = TIMEOUT; tm.tv_usec = 0; //connect為非阻塞,連接不成功立即返回-1 if (!connect(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr))){ sp=getservbyport(htons(scanport),"tcp"); //printf("tcp port %d is open:%s\n ",scanport,sp->s_name); //close(sockfd); //return (**env).NewStringUTF(env,"open:"+scanport); LOGD("open1:%d",scanport); portsOpen[count++]=scanport; //rtn=(**env).NewStringUTF(env,strcat(rtn,&scanport)); }//假如連接不成功,則運行select,直到超時 else { FD_ZERO(&rset); FD_ZERO(&wset); FD_SET(sockfd, &rset); FD_SET(sockfd, &wset); int error; //錯誤代碼 int len = sizeof(error); //5秒后查看socket的狀態變化 if (select(sockfd+1,&rset,&wset,NULL,&tm)>0){ getsockopt(sockfd, SOL_SOCKET, SO_ERROR,&error, &len ); if(error == 0) //printf("Port %d is opened\n", scanport); //close(sockfd); //return (**env).NewStringUTF(env,"open2:"+scanport); LOGD("open2:%d",scanport); portsOpen[count++]=scanport; //rtn=(**env).NewStringUTF(env,strcat(rtn,&scanport)); } } close(sockfd); } //return rtn; (*env)->SetIntArrayRegion(env,jArray, 0, 19, portsOpen);//將portsOpen中的值復制到jintArray中去,數組copy return jArray; } //void Java_com_example_hellondk_MainActivity_scan(JNIEnv*env,jobject clazz){ // LOGD("%s","in c before start"); // startScan(); //}
程序需要在有網絡的條件下運行,故需在manifest中加入internet權限。
startScan函數需要兩個參數:端口號的大小范圍,來自用戶界面的用戶輸入,Java 代碼如下:
package com.linux.portscaner; import java.io.IOException; import android.app.Activity; import android.content.Context; import android.net.ConnectivityManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.Menu; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends Activity { private static TextView tv1=null; private EditText ipOrHost,startPort,endPort; //本地方法 public native int[] scan(String argv,int start_port,int end_port); static{ System.loadLibrary("portScan"); } static Handler UIupdater=new Handler(){ @Override public void handleMessage(Message msg) { //String buffer=(String)msg.obj; int []ports=(int[])msg.obj; StringBuilder sb=new StringBuilder(); sb.append("open:"); for(int i=0;i<ports.length;i++){ if(ports[i]!=0) sb.append(ports[i]).append(" "); } tv1.setText(sb.toString()); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv1=(TextView)findViewById(R.id.tv1); ipOrHost=(EditText)findViewById(R.id.ipOrHost); startPort=(EditText)findViewById(R.id.startPort); endPort=(EditText)findViewById(R.id.endPort); /*try { Runtime.getRuntime().exec("su"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ } public void scanRemote(View v){ ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); boolean hasNet=cm.getActiveNetworkInfo().isAvailable(); if(hasNet){ tv1.setText("正在掃描,請耐心等待。。。"); Thread t=new Thread(new Runnable() { @Override public void run() { int[] openedPort=scan(ipOrHost.getText().toString(), Integer.parseInt(startPort.getText().toString()), Integer.parseInt(endPort.getText().toString())); MainActivity.UIupdater.obtainMessage(0, openedPort).sendToTarget(); } }); t.start(); } else { tv1.setText("請檢查網絡連接"); return; } //tv1.setText("open:"+openedPort); } //A/libc(4643): Fatal signal 11 (SIGSEGV) at 0x00004522 (code=1), //thread 4685 (Thread-305) public void scanLocal(View v){ new Thread(new Runnable() { @Override public void run() { int[] openedPort=scan("10.0.0.2", Integer.parseInt(startPort.getText().toString()), Integer.parseInt(endPort.getText().toString()));//"localhost" MainActivity.UIupdater.obtainMessage(0, openedPort).sendToTarget(); } }).start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
布局文件就不列出來了,程序運行后可以嘗試這掃描百度等,但是速度異常的慢,你可以試試掃描某網站的79~90端口。
下面貼上一個syn掃描的linux程序,但是要想讓它能工作在android上比上邊的要費勁。因為它用到了raw_socket需要root權限。
但是NDK的lib不能夠獲得root權限,只有shell能獲得。你可以寫一個助手程序,讓你的linux程序運行在shell中,接收端口號等命令行參數,
將掃描結果存儲到文件或數據庫或與java端建立socket連接或助手程序創建原始套接字后建立UNIX本地套接字傳遞文件描述符給要進行端口掃描的NDK的Native code。
但是自己學的很膚淺,未能進一步實現。下面的代碼實驗證明是不成功的,直接調用su程序並不能獲得root權限。另外有一點需要注意,新開了一條線程專門用來接收包,
沒有加入對時間和數量的控制。join來等待線程執行結束,但是線程中有個循環。
#include<jni.h> #include <android/log.h> #include<stdio.h> //printf #include<string.h> //memset #include<stdlib.h> //for exit(0); #include<sys/socket.h> #include<errno.h> //For errno - the error number #include<pthread.h> #include<netdb.h> //hostend #include<arpa/inet.h> #include<netinet/tcp.h> //Provides declarations for tcp header #include<netinet/ip.h> //Provides declarations for ip header #include <unistd.h> //fork #define LOG_TAG "System.out" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) void * receive_ack( void *ptr ); int process_packet(unsigned char* , int); unsigned short csum(unsigned short * , int ); char * hostname_to_ip(char * ); int get_local_ip (char *); jintArray startScan(JNIEnv*env,char* argv,jint start_port,jint end_port); struct pseudo_header //needed for checksum calculation { unsigned int source_address; unsigned int dest_address; unsigned char placeholder; unsigned char protocol; unsigned short tcp_length; struct tcphdr tcp; }; struct in_addr dest_ip; //可復用工具方法 //返回char數組的首地址 //將java的String對象類型的數據轉換為C語言的char數組類型 char* Jstring2Cstr(JNIEnv* env,jstring jstr) { char * rtn=NULL; jclass clsString=(*env)->FindClass(env,"java/lang/String"); jstring strEncode=(*env)->NewStringUTF(env,"GB2312"); //下一句不知道為什么不能被保存,有沖突!!!!!!!// jmethodID mid=(*env)->GetMethodID(env,clsString,"getBytes","(Ljava/lang/String;)[B"); //第三個參數為java中的方法的簽名,可以通過java -s 加上包名.類名 查看 jbyteArray jba=(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strEncode); //相當於執行了String的getBytes(“GB2312”)方法 jsize len=(*env)->GetArrayLength(env,jba); jbyte* jbt=(*env)->GetByteArrayElements(env,jba,JNI_FALSE);//獲取元素 if(len > 0){ rtn=(char*)malloc(len+1); memcpy(rtn,jbt,len);//將jbyte*復制到rtn中去 rtn[len]=0;//=='\0' } (*env)->ReleaseByteArrayElements(env,jba,jbt,0); //jbyte是新開辟的空間,現在不用了所以要釋放,0表示釋放jba指向的全部內存空間 return rtn; } jintArray Java_com_linux_portscaner_MainActivity_scan(JNIEnv*env,jobject clazz, jstring argv,jint start_port,jint end_port){ LOGD("%s","portScan.c before start"); char *host_argv=Jstring2Cstr(env,argv); jintArray rtn=startScan(env,host_argv,start_port,end_port); //return (**env).NewStringUTF(env,"hello from native c"); return rtn; } //參數是目標機的IP jintArray startScan(JNIEnv*env,char* argv,jint start_port,jint end_port) { system("su"); //Create a raw socket int s = socket (AF_INET, SOCK_RAW , IPPROTO_TCP); if(s < 0) { LOGD("Error creating socket. Error number : %d . Error message : %s \n" , errno , strerror(errno)); //exit(0); return; } else { LOGD("Socket created.\n"); } jint portsOpen[20]={0}; jintArray jArray=(*env)->NewIntArray(env,20);//int a[20] //Datagram to represent the packet char datagram[4096]; //IP header struct iphdr *iph = (struct iphdr *) datagram; //TCP header struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip)); struct sockaddr_in dest; struct pseudo_header psh; char *target =argv; //argv[1]; // if(argc < 2) // { // printf("Please specify a hostname \n"); // exit(1); // } if( inet_addr( target ) != -1) { dest_ip.s_addr = inet_addr( target ); } else { char *ip = hostname_to_ip(target); if(ip != NULL) { LOGD("%s resolved to %s \n" , target , ip); //Convert domain name to IP dest_ip.s_addr = inet_addr( hostname_to_ip(target) ); } else { LOGD("Unable to resolve hostname : %s" , target); //exit(1); return; } } int source_port = 43591; char source_ip[20]; get_local_ip( source_ip ); LOGD("Local source IP is %s \n" , source_ip); memset (datagram, 0, 4096); /* zero out the buffer */ //Fill in the IP Header iph->ihl = 5; iph->version = 4; iph->tos = 0; iph->tot_len = sizeof (struct ip) + sizeof (struct tcphdr); iph->id = htons (54321); //Id of this packet iph->frag_off = htons(16384); iph->ttl = 64; iph->protocol = IPPROTO_TCP; iph->check = 0; //Set to 0 before calculating checksum iph->saddr = inet_addr ( source_ip ); //Spoof the source ip address iph->daddr = dest_ip.s_addr; iph->check = csum ((unsigned short *) datagram, iph->tot_len >> 1); //TCP Header tcph->source = htons ( source_port ); tcph->dest = htons (80); tcph->seq = htonl(1105024978); tcph->ack_seq = 0; tcph->doff = sizeof(struct tcphdr) / 4; //Size of tcp header tcph->fin=0; tcph->syn=1; tcph->rst=0; tcph->psh=0; tcph->ack=0; tcph->urg=0; tcph->window = htons ( 14600 ); // maximum allowed window size tcph->check = 0; //if you set a checksum to zero, your kernel's IP stack should fill in the correct checksum during transmission tcph->urg_ptr = 0; //IP_HDRINCL to tell the kernel that headers are included in the packet int one = 1; const int *val = &one; if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0) { LOGD ("Error setting IP_HDRINCL. Error number : %d . Error message : %s \n" , errno , strerror(errno)); //exit(0); return; } /* LOGD("Starting sniffer thread...\n"); char *message1 = "Thread 1"; int iret1; pthread_t sniffer_thread; if( pthread_create( &sniffer_thread , NULL , receive_ack , (void*) message1) < 0) { LOGD ("Could not create sniffer thread. Error number : %d . Error message : %s \n" , errno , strerror(errno)); //exit(0); return; } */ LOGD("Starting to send syn packets\n"); int port; dest.sin_family = AF_INET; dest.sin_addr.s_addr = dest_ip.s_addr; for(port = start_port ; port <= end_port ; port++) //set the port { tcph->dest = htons ( port ); tcph->check = 0; // if you set a checksum to zero, your kernel's IP stack should fill in the correct checksum during transmission psh.source_address = inet_addr( source_ip ); psh.dest_address = dest.sin_addr.s_addr; psh.placeholder = 0; psh.protocol = IPPROTO_TCP; psh.tcp_length = htons( sizeof(struct tcphdr) ); memcpy(&psh.tcp , tcph , sizeof (struct tcphdr)); tcph->check = csum( (unsigned short*) &psh , sizeof (struct pseudo_header)); //Send the packet if ( sendto (s, datagram , sizeof(struct iphdr) + sizeof(struct tcphdr) , 0 , (struct sockaddr *) &dest, sizeof (dest)) < 0) { LOGD ("Error sending syn packet. Error number : %d . Error message : %s \n" , errno , strerror(errno)); //exit(0); return; } } // pthread_join( sniffer_thread , NULL); // printf("%d" , iret1); // pthread_kill(sniffer_thread,0); //return 0; pid_t pid=fork(); if(pid==-1){ LOGD("Can't fork process!"); return; } if(!pid){//child start_sniffer(); } return NULL; } /* Method to sniff incoming packets and look for Ack replies */ void * receive_ack( void *ptr ) { //Start the sniffer thing start_sniffer(); } int start_sniffer() { int sock_raw; //int count=0;//count the number of open ports int saddr_size , data_size; struct sockaddr saddr; unsigned char *buffer = (unsigned char *)malloc(65536); //It's Big! LOGD("Sniffer initialising...\n"); fflush(stdout); //Create a raw socket that shall sniff sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP); if(sock_raw < 0) { LOGD("Socket Error\n"); fflush(stdout); return 1; } saddr_size = sizeof saddr; while(1) { //Receive a packet data_size = recvfrom(sock_raw , buffer , 65536 , 0 , &saddr , &saddr_size); if(data_size <0 ) { LOGD("Recvfrom error , failed to get packets\n"); fflush(stdout); return 1; } //Now process the packet //portsOpen[count++]= process_packet(buffer , data_size); } close(sock_raw); LOGD("Sniffer finished."); fflush(stdout); return 0; } int process_packet(unsigned char* buffer, int size) { //Get the IP Header part of this packet struct iphdr *iph = (struct iphdr*)buffer; struct sockaddr_in source,dest; unsigned short iphdrlen; if(iph->protocol == 6) { struct iphdr *iph = (struct iphdr *)buffer; iphdrlen = iph->ihl*4; struct tcphdr *tcph=(struct tcphdr*)(buffer + iphdrlen); memset(&source, 0, sizeof(source)); source.sin_addr.s_addr = iph->saddr; memset(&dest, 0, sizeof(dest)); dest.sin_addr.s_addr = iph->daddr; if(tcph->syn == 1 && tcph->ack == 1 && source.sin_addr.s_addr == dest_ip.s_addr ) { LOGD("Port %d open\n" , ntohs(tcph->source)); //portsOpen[count++]=ntohs(tcph->source); fflush(stdout); return ntohs(tcph->source); } } } /* Checksums - IP and TCP */ unsigned short csum(unsigned short *ptr,int nbytes) { register long sum; unsigned short oddbyte; register short answer; sum=0; while(nbytes>1) { sum+=*ptr++; nbytes-=2; } if(nbytes==1) { oddbyte=0; *((u_char*)&oddbyte)=*(u_char*)ptr; sum+=oddbyte; } sum = (sum>>16)+(sum & 0xffff); sum = sum + (sum>>16); answer=(short)~sum; return(answer); } /* Get ip from domain name */ char* hostname_to_ip(char * hostname) { struct hostent *he; struct in_addr **addr_list; int i; if ( (he = gethostbyname( hostname ) ) == NULL) { // get the host info herror("gethostbyname"); return NULL; } addr_list = (struct in_addr **) he->h_addr_list; for(i = 0; addr_list[i] != NULL; i++) { //Return the first one; return inet_ntoa(*addr_list[i]) ; } return NULL; } /* Get source IP of system , like 192.168.0.6 */ int get_local_ip ( char * buffer) { int sock = socket ( AF_INET, SOCK_DGRAM, 0); const char* kGoogleDnsIp = "8.8.8.8"; int dns_port = 53; struct sockaddr_in serv; memset( &serv, 0, sizeof(serv) ); serv.sin_family = AF_INET; serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp); serv.sin_port = htons( dns_port ); int err = connect( sock , (const struct sockaddr*) &serv , sizeof(serv) ); struct sockaddr_in name; socklen_t namelen = sizeof(name); err = getsockname(sock, (struct sockaddr*) &name, &namelen); const char *p = inet_ntop(AF_INET, &name.sin_addr, buffer, 100); close(sock); }
