hping3/scan.c

549 lines
13 KiB
C
Raw Permalink Normal View History

2022-04-13 18:01:39 +08:00
/* 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 */
}