Μετατροπή ενός απλού tcp client σε port scanner
Λίγα λόγια για το 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 από εδώ