為什麼要寫一個端口掃描的程序,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);
}