549 lines
13 KiB
C
549 lines
13 KiB
C
|
/* Scanner mode for hping2
|
||
|
* Copyright(C) 2003 Salvatore Sanfilippo
|
||
|
* All rights reserved */
|
||
|
|
||
|
/* TODO:
|
||
|
* an application-level aware UDP scanner.
|
||
|
* add ICMP handling in replies.
|
||
|
* The algorithm is far from be optimal, also there isn't a clear
|
||
|
* way to delay smaller amounts of time then usleep(1) without
|
||
|
* to use a dummy loop.
|
||
|
* */
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/types.h>
|
||
|
#if 0
|
||
|
#include <sys/ipc.h>
|
||
|
#endif
|
||
|
#include <sys/shm.h>
|
||
|
#include <sys/sem.h>
|
||
|
#include <unistd.h>
|
||
|
#include <netdb.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <signal.h>
|
||
|
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <arpa/inet.h>
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
|
||
|
#if 0
|
||
|
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
|
||
|
/* union semun is defined by including <sys/sem.h> */
|
||
|
#else
|
||
|
/* according to X/OPEN we have to define it ourselves */
|
||
|
union semun {
|
||
|
int val; /* value for SETVAL */
|
||
|
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
|
||
|
unsigned short int *array; /* array for GETALL, SETALL */
|
||
|
struct seminfo *__buf; /* buffer for IPC_INFO */
|
||
|
};
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#include "hping2.h"
|
||
|
#include "globals.h"
|
||
|
#include "hstring.h"
|
||
|
|
||
|
#define SEM_MODE 0777
|
||
|
#define MAXPORT 65535
|
||
|
|
||
|
int opt_scan_probes = 8;
|
||
|
float avrgms = 0;
|
||
|
int avrgcount = 0;
|
||
|
|
||
|
/* ---------------------------- data structures ----------------------------- */
|
||
|
|
||
|
/* Note that while we don't use any kind of locking, to access
|
||
|
* this fields is safe. the 'retry' field is only accessed by the
|
||
|
* sendinf half, while the 'active' field is set by the receiver
|
||
|
* and tested by the sender so atomicity isn't an issue. */
|
||
|
struct portinfo {
|
||
|
int active;
|
||
|
int retry;
|
||
|
time_t sentms; /* Upss... added this that requires locking, FIXME */
|
||
|
};
|
||
|
|
||
|
/* ------------------------- shared memory related -------------------------- */
|
||
|
|
||
|
static int id; /* shared memory id */
|
||
|
|
||
|
static int shm_creat(int size)
|
||
|
{
|
||
|
id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777);
|
||
|
if (id == -1)
|
||
|
{
|
||
|
perror("[shm_creat] shmget");
|
||
|
return -1; /* on error -1 */
|
||
|
}
|
||
|
return id; /* on success > 0 */
|
||
|
}
|
||
|
|
||
|
static void *shm_attach(void)
|
||
|
{
|
||
|
void *shared;
|
||
|
|
||
|
shared = shmat(id, 0, 0);
|
||
|
if (shared == (void*) -1)
|
||
|
{
|
||
|
perror("[shm_attach] shmat");
|
||
|
return NULL; /* on error NULL */
|
||
|
}
|
||
|
return shared; /* on success the address */
|
||
|
}
|
||
|
|
||
|
static int shm_rm(void)
|
||
|
{
|
||
|
struct shmid_ds shmemds;
|
||
|
|
||
|
return shmctl(id, IPC_RMID, &shmemds);
|
||
|
}
|
||
|
|
||
|
static int shm_detach(void *addr)
|
||
|
{
|
||
|
return shmdt(addr);
|
||
|
}
|
||
|
|
||
|
static void *shm_init(int size)
|
||
|
{
|
||
|
if (shm_creat(size) == -1)
|
||
|
return NULL;
|
||
|
return shm_attach();
|
||
|
}
|
||
|
|
||
|
static void shm_close(void *addr)
|
||
|
{
|
||
|
shm_detach(addr);
|
||
|
shm_rm();
|
||
|
}
|
||
|
|
||
|
/* ------------------------------ locking ---------------------------------- */
|
||
|
|
||
|
/* Note that a mutex can't be used with shared memory (on Linux), the only left
|
||
|
* option is a semaphore, but I tried to protect the critical code
|
||
|
* using the functions above: the scanner becomes too slow. For now
|
||
|
* it's better to have nothing at all, for the future we need something
|
||
|
* like a spinlock. (btw, note that the code should be safe on x86) */
|
||
|
|
||
|
/* I left this code here, just in the case it will be useful for testing */
|
||
|
#if 0
|
||
|
static int sem_init(void)
|
||
|
{
|
||
|
int semid, sem_key;
|
||
|
|
||
|
if ((sem_key = ftok("/tmp/hpingscansem", 1)) == -1) {
|
||
|
perror("ftok");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
/* Semi-safe semaphore initialization from R.Stevens */
|
||
|
|
||
|
/* Try to create the semaphore with EXCL */
|
||
|
if ((semid = semget(sem_key, 1, IPC_CREAT|IPC_EXCL|SEM_MODE)) != -1) {
|
||
|
/* success, we need to initialize it */
|
||
|
union semun arg;
|
||
|
|
||
|
arg.val = 1;
|
||
|
if (semctl(semid, 0, SETVAL, arg) == -1) {
|
||
|
perror("semctl");
|
||
|
exit(1);
|
||
|
}
|
||
|
} else if (errno == EEXIST) {
|
||
|
if ((semid = semget(sem_key, 1, SEM_MODE)) == -1) {
|
||
|
perror("semget");
|
||
|
exit(1);
|
||
|
}
|
||
|
} else {
|
||
|
perror("semget");
|
||
|
exit(1);
|
||
|
}
|
||
|
return semid;
|
||
|
}
|
||
|
|
||
|
static int ports_lock(int semid)
|
||
|
{
|
||
|
struct sembuf op[1];
|
||
|
|
||
|
op[0].sem_num = 0;
|
||
|
op[0].sem_op = -1;
|
||
|
op[0].sem_flg = SEM_UNDO;
|
||
|
return semop(semid, op, 1);
|
||
|
}
|
||
|
|
||
|
static int ports_unlock(int semid)
|
||
|
{
|
||
|
struct sembuf op[1];
|
||
|
|
||
|
op[0].sem_num = 0;
|
||
|
op[0].sem_op = +1;
|
||
|
op[0].sem_flg = SEM_UNDO;
|
||
|
return semop(semid, op, 1);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* -------------------------------- misc ----------------------------------- */
|
||
|
static char *tcp_strflags(char *s, unsigned int flags)
|
||
|
{
|
||
|
char *ftab = "FSRPAYXY", *p = s;
|
||
|
int bit = 0;
|
||
|
|
||
|
memset(s, '.', 8);
|
||
|
s[8] = '\0';
|
||
|
while(bit < 8) {
|
||
|
if (flags & (1 << bit))
|
||
|
p[bit] = ftab[bit];
|
||
|
bit++;
|
||
|
}
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
static char *port_to_name(int port)
|
||
|
{
|
||
|
struct servent *se;
|
||
|
|
||
|
se = getservbyport(htons(port), NULL);
|
||
|
if (!se)
|
||
|
return "";
|
||
|
else
|
||
|
return se->s_name;
|
||
|
}
|
||
|
|
||
|
/* ----------------------------- ports parsing ------------------------------ */
|
||
|
static int parse_ports(struct portinfo *pi, char *ports)
|
||
|
{
|
||
|
char *args[32], *p = strdup(ports);
|
||
|
int argc, j, i;
|
||
|
|
||
|
if (!p) {
|
||
|
fprintf(stderr, "Out of memory");
|
||
|
return 1;
|
||
|
}
|
||
|
argc = strftok(",", ports, args, 32);
|
||
|
for (j = 0; j < argc; j++) {
|
||
|
int neg = 0;
|
||
|
char *a = args[j];
|
||
|
|
||
|
/* ports negation */
|
||
|
if (a[0] == '!') {
|
||
|
neg = 1;
|
||
|
a++;
|
||
|
}
|
||
|
/* range */
|
||
|
if (strchr(a, '-')) {
|
||
|
char *range[2];
|
||
|
int low, high;
|
||
|
|
||
|
strftok("-", a, range, 2);
|
||
|
if (!strisnum(range[0]) || !strisnum(range[1]))
|
||
|
goto err; /* syntax error */
|
||
|
low = strtol(range[0], NULL, 0);
|
||
|
high = strtol(range[1], NULL, 0);
|
||
|
if (low > high) {
|
||
|
int t;
|
||
|
t = high;
|
||
|
high = low;
|
||
|
low = t;
|
||
|
}
|
||
|
for (i = low; i <= high; i++)
|
||
|
pi[i].active = !neg;
|
||
|
/* all the ports */
|
||
|
} else if (!strcmp(a, "all")) {
|
||
|
for (i = 0; i <= MAXPORT; i++)
|
||
|
pi[i].active = !neg;
|
||
|
/* /etc/services ports */
|
||
|
} else if (!strcmp(a, "known")) {
|
||
|
struct servent *se;
|
||
|
setservent(0);
|
||
|
while((se = getservent()) != NULL) {
|
||
|
int port = ntohs(se->s_port);
|
||
|
if (port < 0 || port > MAXPORT)
|
||
|
continue;
|
||
|
pi[port].active = !neg;
|
||
|
}
|
||
|
/* a single port */
|
||
|
} else {
|
||
|
int port;
|
||
|
if (!strisnum(a))
|
||
|
goto err; /* syntax error */
|
||
|
port = strtol(a, NULL, 0);
|
||
|
if (port < 0 || port > MAXPORT)
|
||
|
goto err; /* syntax error */
|
||
|
pi[port].active = !neg;
|
||
|
}
|
||
|
}
|
||
|
free(p);
|
||
|
return 0;
|
||
|
err:
|
||
|
free(p);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------- output ---------------------------------- */
|
||
|
static void sender(struct portinfo *pi)
|
||
|
{
|
||
|
int i, retry = 0;
|
||
|
time_t start_time;
|
||
|
|
||
|
start_time = get_midnight_ut_ms();
|
||
|
|
||
|
while(1) {
|
||
|
int active = 0;
|
||
|
int recvd = 0;
|
||
|
retry ++;
|
||
|
for (i = 0; i < MAXPORT; i++) {
|
||
|
if (pi[i].active && pi[i].retry) {
|
||
|
active++;
|
||
|
pi[i].retry--;
|
||
|
sequence = -1;
|
||
|
dst_port = i;
|
||
|
pi[i].sentms = get_midnight_ut_ms();
|
||
|
send_tcp();
|
||
|
if (opt_waitinusec) {
|
||
|
if (usec_delay.it_interval.tv_usec)
|
||
|
usleep(usec_delay.it_interval.tv_usec);
|
||
|
} else {
|
||
|
sleep(sending_wait);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
avrgms = (float) pi[MAXPORT+1].active;
|
||
|
if (retry >= 3) {
|
||
|
if (opt_debug)
|
||
|
printf("AVRGMS %f\n", avrgms);
|
||
|
if (avrgms)
|
||
|
usleep((int) (avrgms*1000));
|
||
|
else
|
||
|
sleep(1);
|
||
|
}
|
||
|
for (i = 0; i < MAXPORT; i++) {
|
||
|
if (!pi[i].active && pi[i].retry)
|
||
|
recvd++;
|
||
|
}
|
||
|
/* More to scan? */
|
||
|
if (!active) {
|
||
|
if (!recvd)
|
||
|
sleep(1);
|
||
|
fprintf(stderr, "All replies received. Done.\n");
|
||
|
printf("Not responding ports: ");
|
||
|
for (i = 0; i < MAXPORT; i++) {
|
||
|
if (pi[i].active && !pi[i].retry)
|
||
|
printf("(%d %.11s) ", i, port_to_name(i));
|
||
|
}
|
||
|
printf("\n");
|
||
|
exit(0);
|
||
|
}
|
||
|
/* Are we sending too fast? */
|
||
|
if ((!recvd && opt_waitinusec &&
|
||
|
usec_delay.it_interval.tv_usec == 0 &&
|
||
|
(get_midnight_ut_ms() - start_time) > 500) ||
|
||
|
(opt_scan_probes-retry) <= 2)
|
||
|
{
|
||
|
if (opt_debug)
|
||
|
printf("SLOWING DONW\n");
|
||
|
usec_delay.it_interval.tv_usec *= 10;
|
||
|
usec_delay.it_interval.tv_usec ++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -------------------------------- input ---------------------------------- */
|
||
|
static void receiver(struct portinfo *pi, int childpid)
|
||
|
{
|
||
|
struct myiphdr ip;
|
||
|
char packet[IP_MAX_SIZE+linkhdr_size];
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
int len, iplen;
|
||
|
|
||
|
len = read_packet(packet, IP_MAX_SIZE+linkhdr_size);
|
||
|
if (len == -1) {
|
||
|
perror("read_packet");
|
||
|
continue;
|
||
|
}
|
||
|
/* minimal sanity checks */
|
||
|
if (len < linkhdr_size)
|
||
|
continue;
|
||
|
iplen = len - linkhdr_size;
|
||
|
if (iplen < sizeof(struct myiphdr))
|
||
|
continue;
|
||
|
/* copy the ip header in an access-safe place */
|
||
|
memcpy(&ip, packet+linkhdr_size, sizeof(ip));
|
||
|
/* check if the dest IP matches */
|
||
|
if (memcmp(&ip.daddr, &ADDR4(&local), sizeof(ip.daddr)))
|
||
|
continue;
|
||
|
/* check if the source IP matches */
|
||
|
if (ip.protocol != IPPROTO_ICMP &&
|
||
|
memcmp(&ip.saddr, &ADDR4(&remote), sizeof(ip.saddr)))
|
||
|
continue;
|
||
|
if (ip.protocol == IPPROTO_TCP) {
|
||
|
struct mytcphdr tcp;
|
||
|
int iphdrlen = ip.ihl << 2;
|
||
|
char flags[16];
|
||
|
time_t rttms;
|
||
|
int sport;
|
||
|
|
||
|
/* more sanity checks */
|
||
|
if ((iplen - iphdrlen) < sizeof(tcp))
|
||
|
continue;
|
||
|
/* time to copy the TCP header in a safe place */
|
||
|
memcpy(&tcp, packet+linkhdr_size+iphdrlen, sizeof(tcp));
|
||
|
|
||
|
/* check if the TCP dest port matches */
|
||
|
#if 0
|
||
|
printf("SRC: %d DST: %d\n",
|
||
|
ntohs(tcp.th_sport),
|
||
|
ntohs(tcp.th_dport));
|
||
|
#endif
|
||
|
if (ntohs(tcp.th_dport) != initsport)
|
||
|
continue;
|
||
|
sport = htons(tcp.th_sport);
|
||
|
if (pi[sport].active == 0)
|
||
|
continue;
|
||
|
|
||
|
|
||
|
/* Note that we don't care about a wrote RTT
|
||
|
* result due to resend on the same port. */
|
||
|
rttms = get_midnight_ut_ms() - pi[sport].sentms;
|
||
|
|
||
|
avrgcount++;
|
||
|
avrgms = (avrgms*(avrgcount-1)/avrgcount)+(rttms/avrgcount);
|
||
|
/* The avrg RTT is shared using shared memory,
|
||
|
* no locking... */
|
||
|
pi[MAXPORT+1].active = (int) avrgms;
|
||
|
|
||
|
tcp_strflags(flags, tcp.th_flags);
|
||
|
#if 0
|
||
|
printf("%5d: %s %3d %5d %5d %10ld (%2d)\n",
|
||
|
sport,
|
||
|
flags,
|
||
|
ip.ttl,
|
||
|
ip.id,
|
||
|
ntohs(tcp.th_win),
|
||
|
(long) rttms,
|
||
|
opt_scan_probes-(pi[sport].retry));
|
||
|
#endif
|
||
|
if ((tcp.th_flags & TH_SYN) || opt_verbose) {
|
||
|
printf("%5d %-11.11s: %s %3d %5d %5d\n",
|
||
|
sport,
|
||
|
port_to_name(sport),
|
||
|
flags,
|
||
|
ip.ttl,
|
||
|
ip.id,
|
||
|
ntohs(tcp.th_win));
|
||
|
fflush(stdout);
|
||
|
}
|
||
|
pi[sport].active = 0;
|
||
|
} else if (ip.protocol == IPPROTO_ICMP) {
|
||
|
struct myicmphdr icmp;
|
||
|
struct myiphdr subip;
|
||
|
struct mytcphdr subtcp;
|
||
|
int iphdrlen = ip.ihl << 2;
|
||
|
unsigned char *p;
|
||
|
int port;
|
||
|
struct in_addr gwaddr;
|
||
|
|
||
|
/* more sanity checks, we are only interested
|
||
|
* in ICMP quoting the original packet. */
|
||
|
if ((iplen - iphdrlen) < sizeof(icmp)+sizeof(subip)+sizeof(subtcp))
|
||
|
continue;
|
||
|
/* time to copy headers in a safe place */
|
||
|
p = packet+linkhdr_size+iphdrlen;
|
||
|
memcpy(&icmp, p, sizeof(subtcp));
|
||
|
p += sizeof(icmp);
|
||
|
memcpy(&subip, p, sizeof(ip));
|
||
|
p += sizeof(ip);
|
||
|
memcpy(&subtcp, p, sizeof(subtcp));
|
||
|
|
||
|
/* Check if the ICMP quoted packet matches */
|
||
|
/* check if the source IP matches */
|
||
|
if (memcmp(&subip.saddr, &ADDR4(&local), sizeof(subip.saddr)))
|
||
|
continue;
|
||
|
/* check if the destination IP matches */
|
||
|
if (memcmp(&subip.daddr, &ADDR4(&remote), sizeof(subip.daddr)))
|
||
|
continue;
|
||
|
/* check if the quoted TCP packet port matches */
|
||
|
if (ntohs(subtcp.th_sport) != initsport)
|
||
|
continue;
|
||
|
port = htons(subtcp.th_dport);
|
||
|
if (pi[port].active == 0)
|
||
|
continue;
|
||
|
pi[port].active = 0;
|
||
|
memcpy(&gwaddr.s_addr, &ip.saddr, 4);
|
||
|
printf("%5d: %3d %5d (ICMP %3d %3d from %s)\n",
|
||
|
port,
|
||
|
ip.ttl,
|
||
|
ntohs(ip.id),
|
||
|
icmp.type,
|
||
|
icmp.code,
|
||
|
inet_ntoa(gwaddr));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ---------------------------------- main ---------------------------------- */
|
||
|
static void do_exit(int sid)
|
||
|
{
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
void scanmain(void)
|
||
|
{
|
||
|
struct portinfo *pi;
|
||
|
int ports = 0, i;
|
||
|
int childpid;
|
||
|
|
||
|
pi = shm_init(sizeof(*pi)*(MAXPORT+2));
|
||
|
pi[MAXPORT+1].active = 0; /* hold the average RTT */
|
||
|
if (pi == NULL) {
|
||
|
fprintf(stderr, "Unable to create the shared memory");
|
||
|
shm_close(pi);
|
||
|
exit(1);
|
||
|
}
|
||
|
for (i = 0; i <= MAXPORT; i++) {
|
||
|
pi[i].active = 0;
|
||
|
pi[i].retry = opt_scan_probes;
|
||
|
}
|
||
|
if (parse_ports(pi, opt_scanports)) {
|
||
|
fprintf(stderr, "Ports syntax error for scan mode\n");
|
||
|
shm_close(pi);
|
||
|
exit(1);
|
||
|
}
|
||
|
for (i = 0; i <= MAXPORT; i++) {
|
||
|
if (!pi[i].active)
|
||
|
pi[i].retry = 0;
|
||
|
}
|
||
|
for (i = 0; i <= MAXPORT; i++)
|
||
|
ports += pi[i].active;
|
||
|
fprintf(stderr, "%d ports to scan, use -V to see all the replies\n", ports);
|
||
|
fprintf(stderr, "+----+-----------+---------+---+-----+-----+\n");
|
||
|
fprintf(stderr, "|port| serv name | flags |ttl| id | win |\n");
|
||
|
fprintf(stderr, "+----+-----------+---------+---+-----+-----+\n");
|
||
|
|
||
|
/* We are ready to fork, the input and output parts
|
||
|
* are separated processes */
|
||
|
if ((childpid = fork()) == -1) {
|
||
|
perror("fork");
|
||
|
shm_close(pi);
|
||
|
exit(1);
|
||
|
}
|
||
|
/* The parent is the receiver, the child the sender.
|
||
|
* it's almost the same but this way is simpler
|
||
|
* to make it working in pipe with other commands like grep. */
|
||
|
if (childpid) { /* parent */
|
||
|
Signal(SIGCHLD, do_exit);
|
||
|
Signal(SIGINT, do_exit);
|
||
|
Signal(SIGTERM, do_exit);
|
||
|
receiver(pi, childpid);
|
||
|
} else { /* child */
|
||
|
Signal(SIGINT, do_exit);
|
||
|
Signal(SIGTERM, do_exit);
|
||
|
sender(pi);
|
||
|
}
|
||
|
/* UNREACHED */
|
||
|
}
|