Μετατροπή ενός απλού tcp client σε port scanner

Εικόνα: kegior
Submitted by kegior on Σαβ, 13/06/2009 - 02:54

Λίγα λόγια για το tcpscan

Μπορείτε να το βρείτε εδώ.

Με το πρόγραμμα tcpscan
μπορείτε να πραγματοποιήσετε tcp συνδέσεις με απομακρυσμένους
υπολογιστές σε μία πόρτα που θα ορίσετε από τη γραμμή εντολών, καθώς
τρέχετε το πρόγραμμα, πχ:  tcpscan Scylla.cs.uoi.gr 22
Το tcpscan
για να συνδεθεί με τον απομακρυσμένο υπολογιστή χρησιμοποιεί υποδοχές
(sockets), και την κλήση connect. Θα εξηγήσουμε τα βασικά του σημεία:

To
αρχείο etc/protocol του  linux περιέχει πληροφορίες για τα διαθέσιμα
πρωτόκολλα δικτύου. Εμείς το μόνο που έχουμε να κάνουμε είναι να
ψάξουμε σε αυτό το αρχείο το αναγνωριστικό του πρωτοκόλλου tcp που θα
χρησιμοποιήσουμε. Για να το πετύχουμε αυτό χρησιμοποιούμε τη συνάρτηση getprotobyname:

struct protoent *protocol;

protocol = getprotobyname( "tcp" );

 

Στη συνέχεια χτίζουμε τις υποδοχές με της οποίες θα γίνει η επικοινωνία με τη συνάρτηση socket:

int sd;
int rval; 
int port; 
sd = socket( PF_INET, SOCK_STREAM, protocol->p_proto );

Το sd είναι πλέον το αναγνωριστικό της υποδοχής μας.

Απομένει
να συνδεθούμε με τον απομακρυσμένο υπολογιστή. Το linux και όλη
οικογένεια λειτουργικών POSIX μας προσφέρει τη συνάρτηση:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);                                          

  • sockfd: το αναγνωριστικό της υποδοχής που θα χρησιμοποιήσουμε
  • struct sockaddr:
     Η διεύθυνση του απομακρυσμένου υπολογιστή. Εμείς θα χρησιμοποιήσουμε
    την sockaddr_in  struct διότι μας προσφέρει τη δυνατότητα εκτός από τη
    διεύθυνση να ορίσουμε και πόρτα επικοινωνίας.
  • addrlen: Το μήκος της διεύθυνσης.

memset( &socketaddr, 0, sizeof(socketaddr)); /* initialize it     */ 
socketaddr.sin_family = AF_INET; /* set the fam type to Internet */
socketaddr.sin_port = htons( port );  /* set port to argv[2]     */

Τώρα απομένει να χτίσουμε τη διεύθυνση του απομακρυσμένου υπολογιστή. Σε αυτό θα μας βοηθήσει η συνάρτηση struct hostent *gethostbyname(const char *name). Το όρισμά της μπορεί να είναι είτε μια διεύθυνση IP είτε ένα όνομα (hostname):

hostaddr = gethostbyname(argv[1]);
if ( !hostaddr )
{
fprintf( stderr, "gethostbyname(): %s\n", hstrerror(h_errno) );
return (h_errno);
}    
    
/* Now we'll use memcpy() to place it in our sockaddr_in struct: */
   
memcpy( &socketaddr.sin_addr, hostaddr->h_addr, hostaddr->h_length ); 
   
/* At this point, we're ready to connect to the remote host:     */

rval = connect( sd, (struct sockaddr *) &socketaddr, sizeof(socketaddr) );
if ( rval == -1 )
{
perror( "connect()" );
return (errno);
}

Πλέον
έχουμε συνδεθεί επιτυχώς με το απομακρυσμένο σύστημα και μπορούμε να
ανταλλάξουμε πληροφορίες. Αυτό το κομμάτι όμως δε θα μας απασχολήσει
εδώ.

 

Μετατροπή σε port scanner

 

Είναι πολύ εύκολο να μετατρέψουμε το παραπάνω πρόγραμμα σε ένα port scanner. Aς δούμε πως:

Αφαιρούμε
από το tcpconnect τις γραμμές κώδικα που υλοποιεί τη μεταβίβαση
μηνυμάτων δηλαδή από τη γραμμή      printf("\nConnection with %s on
port %d established!\n\a",argv[1],port); και κάτω.

Προσθέτουμε τις παρακάτω γραμμές σε ένα loop:

 

for (port=0;port<PORT_MAX;port++) {

if (setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof tv))

{

perror("setsockopt");

return -1;

}

memset( &socketaddr, 0, sizeof(socketaddr)); /* initialize it     */ 

socketaddr.sin_family = AF_INET; /* set the fam type to Internet */

socketaddr.sin_port = htons( port );  /* set port to argv[2]     */

hostaddr = gethostbyname(argv[1]);

if ( !hostaddr )

{

fprintf( stderr, "gethostbyname(): %s\n", hstrerror(h_errno) );

return (h_errno);

}       

memcpy( &socketaddr.sin_addr, hostaddr->h_addr, hostaddr->h_length ); 

[code]rval = connect( sd, (struct sockaddr *) &socketaddr, sizeof(socketaddr) );

if ( rval == -1 )

{

perror( "connect()" );

return (errno);

}

close(sd);

}

Και κάτω από το if (rval == -1) προσθέτουμε τον εξής κώδικα:

else {
printf(“Found OPEN port: %d\n”,port);
}

Πλέον,
ο port scanner μας είναι έτοιμος, αλλά έχει ένα μικρό προβληματάκι.
Πολλές φορές η connect δε μπορεί να συνδεθεί κατευθείαν σε μία πόρτα,
οπότε περιμένει ένα ακαθόριστο χρονικό διάστημα μέχρι να συνδεθεί η να
απορριφθεί η αίτηση σύνδεσης. Το φαινόμενο αυτό κάνει τον port scanner
μας αργό.

Για να αντιμετωπίσουμε το παραπάνω πρόβλημα χρησιμοποιούμε non_blocking socket, τη συνάρτηση select και δομή timeval:

#include <fcntl.h>
fd_set myset;
struct timeval tv;
socklen_t lon;
int valopt;
int lp,hp,ans;

Με την παρακάτω εντολή μετατρέπουμε την υποδοχή μας σε non-blocking:

fcntl(sd, F_SETFL, (flags = fcntl(sd,F_GETFL)) | O_NONBLOCK );

Αυτό
σημαίνει ότι κάθε κλήση connect θα επιστρέφει -1 δηλαδή κωδικό λάθους.
Το κλειδί εδώ είναι ότι ο κωδικός λάθους που θα επιστρέφεται σε
περίπτωση timeout θα είναι γνωστός: EINPROGRESS

Οπότε
το μόνο που πρέπει να κάνουμε είναι να ελέγξουμε αρχικά με μία if τον
κωδικό λάθους. Αν αυτός είναι ο EINPROGRESS, τότε καθορίζουμε το χρόνο
του timeout :

tv.tv_sec = TIMEOUT_TIME;
tv.tv_usec = 0;

Στη συνέχεια αρχικοποιούμε τον περιγραφέα myset που θα χρειαστούμε για τη select με τον περιγραφέα της υποδοχής μας:

FD_ZERO(&myset);
FD_SET(sd, &myset);

Έπειτα καλούμε τη select:

if (select(sd+1, NULL, &myset, NULL, &tv) > 0) {
lon = sizeof(int);
getsockopt(sd, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon);
if (!valopt) {
printf("--- Found OPEN Port: %d\n",port);                  

}
else {
printf("------ TIMEOUT on port: %d\n",port);     
}

Η
select μας βοηθάει να προσδιορίσουμε μια συνθήκη λάθους για κάποιον από
τους περιγραφείς μας.  Η σύνταξή της είναι η ακόλουθη:

 

#include <sys/time.h>

#include <sys/types.h>
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

FD_SET(int fd, fd_set *fdset);
FD_CLR(int fd, fd_set *fdset);
FD_ISSET(int fd, fd_set *fdset);

FD_ZERO(fd_set *fdset);

Για περισσότερες πληροφορίες σχετικά με τη select ανατρέξτε εδώ

Κατεβάστε το ολοκληρωμένο TCPscan από εδώ

5
Your rating: None Average: 5 (4 votes)