inital public release
This commit is contained in:
22
examples/banner-grab/Makefile
Normal file
22
examples/banner-grab/Makefile
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
CFLAGS+=-I../../lib/ -Wall
|
||||
LDFLAGS+=-lpcap -levent -levent_extra -lm
|
||||
VPATH=../../lib/
|
||||
|
||||
# from dpkg-buildflags --get CFLAGS, but use stack-protector-all and fPIC
|
||||
GCCHARDENING=-g -O2 -fstack-protector-all --param=ssp-buffer-size=4 -Wformat -Wformat-security -Werror=format-security -fPIC
|
||||
# from gpkg-buildflags --get LDFLAGS, + -z,now
|
||||
LDHARDENING=-Wl,-Bsymbolic-functions -Wl,-z,relro,-z,now
|
||||
|
||||
CFLAGS+=$(GCCHARDENING)
|
||||
LDFLAGS+=$(LDHARDENING)
|
||||
|
||||
|
||||
all: banner-grab-tcp
|
||||
|
||||
banner-grab-tcp: banner-grab-tcp.o logger.o
|
||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f banner-grab-tcp *.o
|
54
examples/banner-grab/README
Normal file
54
examples/banner-grab/README
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
TCP Banner Grab
|
||||
======
|
||||
|
||||
This utility will connect (TCP) to ip addresses provide over stdin, optionally
|
||||
send them a small message, and wait for their response. The response is then
|
||||
printed along with their IP address on stdout. Status messages appear on stderr.
|
||||
|
||||
|
||||
USING:
|
||||
-----
|
||||
make
|
||||
#echo -e -n "GET / HTTP/1.1\r\nHost: %s\r\n\r\n" > http-req
|
||||
zmap -p 80 -N 1000 -o - | ./banner-grab-tcp -p 80 -c 100 -d http-req > http-banners.out
|
||||
|
||||
|
||||
OPTIONS:
|
||||
-----
|
||||
-c, --concurent Number of connections that can be going on at once.
|
||||
This, combined with timeouts, will decide the maximum
|
||||
rate at which banners are grabbed. If this value
|
||||
is set higher than 1000, you should use
|
||||
`ulimit -SSn 1000000` and `ulimit -SHn 1000000` to
|
||||
avoid running out of file descriptors (typically capped
|
||||
at 1024).
|
||||
|
||||
-p, --port The port which to connect to hosts on
|
||||
|
||||
-t, --conn-timeout Connection timeout (seconds). Give up on a host if connect
|
||||
has not completed by this time. Default: 4 seconds.
|
||||
|
||||
-r, --read-timeout Read timeout (seconds). Give up on a host if after
|
||||
connecting (and optionally sending data), it does
|
||||
not send any response by this time. Default: 4 seconds.
|
||||
|
||||
-v, --verbosity Set status verbosity. Status/error messages are outputed
|
||||
on stderr. This value can be 0-5, with 5 being the most
|
||||
verbose (LOG_TRACE). Default: 3 (LOG_INFO)
|
||||
|
||||
-f, --format Format to output banner responses. One of 'hex', 'ascii',
|
||||
or 'base64'.
|
||||
'hex' outputs ascii hex characters, e.g. 48656c6c6f.
|
||||
'ascii' outputs ascii, without separators, e.g. Hello
|
||||
'base64' outputs base64 encoding, e.g. SGVsbG8=
|
||||
Default is base64.
|
||||
|
||||
-d, --data Optional data file. This data will be sent to each host
|
||||
upon successful connection. Currently, this file does
|
||||
not allow null characters, but supports up to 4
|
||||
occurances of the current host's IP address, by replacing
|
||||
%s with the string (inet_ntoa) of that host's IP address.
|
||||
|
||||
|
||||
|
440
examples/banner-grab/banner-grab-tcp.c
Normal file
440
examples/banner-grab/banner-grab-tcp.c
Normal file
@ -0,0 +1,440 @@
|
||||
/*
|
||||
* Banner Grab Copyright 2013 Regents of the University of Michigan
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "logger.h"
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ulimit.h>
|
||||
|
||||
#define MAX_BANNER_LEN 1024
|
||||
#define BASE64_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
struct config {
|
||||
uint16_t port;
|
||||
int connect_timeout; // how long to wait for connecting (seconds)
|
||||
int read_timeout; // how long to wait once connected for the banner (seconds)
|
||||
int current_running;
|
||||
int max_concurrent;
|
||||
struct event_base *base;
|
||||
struct bufferevent *stdin_bev;
|
||||
int stdin_closed;
|
||||
enum {FORMAT_HEX, FORMAT_BASE64, FORMAT_ASCII} format;
|
||||
char *send_str;
|
||||
long send_str_size;
|
||||
|
||||
struct stats_st {
|
||||
int init_connected_hosts; // Number of hosts we have even tried to connect to
|
||||
int connected_hosts; // # hosts that picked up
|
||||
int conn_timed_out; // # hosts that timed out during connection
|
||||
int read_timed_out; // # hosts that connected, but sent no data (banner)
|
||||
int timed_out; // # hosts that timed out at all (conn_timed_out+read_timed_out)?
|
||||
int completed_hosts; // # hosts that presented a banner
|
||||
} stats;
|
||||
};
|
||||
|
||||
|
||||
struct state {
|
||||
struct config *conf;
|
||||
uint32_t ip;
|
||||
enum {CONNECTING, CONNECTED, RECEIVED} state;
|
||||
};
|
||||
|
||||
void stdin_readcb(struct bufferevent *bev, void *arg);
|
||||
|
||||
void print_status(evutil_socket_t fd, short events, void *arg)
|
||||
{
|
||||
struct event *ev;
|
||||
struct config *conf = arg;
|
||||
struct event_base *base = conf->base;
|
||||
struct timeval status_timeout = {1, 0};
|
||||
ev = evtimer_new(base, print_status, conf);
|
||||
evtimer_add(ev, &status_timeout);
|
||||
(void)fd; (void)events;
|
||||
|
||||
log_info("banner-grab-tcp", "(%d/%d in use) - Totals: %d inited, %d connected, %d conn timeout, %d read timeout %d completed",
|
||||
conf->current_running, conf->max_concurrent,
|
||||
conf->stats.init_connected_hosts,
|
||||
conf->stats.connected_hosts, conf->stats.conn_timed_out,
|
||||
conf->stats.read_timed_out, conf->stats.completed_hosts);
|
||||
}
|
||||
|
||||
void decrement_cur_running(struct state *st)
|
||||
{
|
||||
struct config *conf = st->conf;
|
||||
conf->current_running--;
|
||||
log_debug("banner-grab-tcp", "done, down to %d",
|
||||
conf->current_running);
|
||||
if (evbuffer_get_length(bufferevent_get_input(conf->stdin_bev)) > 0) {
|
||||
stdin_readcb(conf->stdin_bev, conf);
|
||||
}
|
||||
free(st);
|
||||
|
||||
if (conf->stdin_closed && conf->current_running == 0) {
|
||||
// Done
|
||||
log_info("banner-grab-tcp", "done");
|
||||
print_status(0, 0, conf);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void connect_cb(struct bufferevent *bev, short events, void *arg)
|
||||
{
|
||||
struct state *st = arg;
|
||||
struct config *conf = st->conf;
|
||||
struct in_addr addr;
|
||||
addr.s_addr = st->ip;
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
struct timeval tv = {st->conf->read_timeout, 0};
|
||||
log_debug("banner-grab-tcp", "%s connected", inet_ntoa(addr));
|
||||
|
||||
// Send data
|
||||
if (conf->send_str) {
|
||||
struct evbuffer *evout = bufferevent_get_output(bev);
|
||||
// HACK!!! TODO: make some messy parser that replaces ${IP} with IP etc
|
||||
// and allow null characters
|
||||
evbuffer_add_printf(evout, conf->send_str,
|
||||
inet_ntoa(addr), inet_ntoa(addr), inet_ntoa(addr), inet_ntoa(addr));
|
||||
}
|
||||
|
||||
// Change from connect timeout to read timeout
|
||||
bufferevent_set_timeouts(bev, &tv, &tv);
|
||||
|
||||
// Update state/stats
|
||||
st->state = CONNECTED;
|
||||
st->conf->stats.connected_hosts++;
|
||||
} else {
|
||||
if (st->state == CONNECTED) {
|
||||
// Print out that we just didn't receive data
|
||||
printf("%s X\n", inet_ntoa(addr));
|
||||
fflush(stdout);
|
||||
|
||||
st->conf->stats.read_timed_out++;
|
||||
} else {
|
||||
st->conf->stats.conn_timed_out++;
|
||||
}
|
||||
log_debug("banner-grab-tcp", "%s bailing..", inet_ntoa(addr));
|
||||
bufferevent_free(bev);
|
||||
st->conf->stats.timed_out++;
|
||||
decrement_cur_running(st);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab these bytes, and close the connection.
|
||||
// Even if we don't need to read any bytes,
|
||||
// we have to have this so that libevent thinks we have
|
||||
// a read event, so that it can timeout TCP connects
|
||||
// (as a read timeout)
|
||||
void read_cb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
struct evbuffer *in = bufferevent_get_input(bev);
|
||||
struct state *st = arg;
|
||||
size_t len = evbuffer_get_length(in);
|
||||
struct in_addr addr;
|
||||
addr.s_addr = st->ip;
|
||||
|
||||
log_debug("banner-grab-tcp", "read_cb for %s", inet_ntoa(addr));
|
||||
|
||||
if (len > MAX_BANNER_LEN) {
|
||||
len = MAX_BANNER_LEN;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
// Grab the banner
|
||||
unsigned int i;
|
||||
unsigned char *buf = malloc(len+1);
|
||||
|
||||
st->state = RECEIVED;
|
||||
|
||||
if (!buf) {
|
||||
log_fatal("banner-grab-tcp", "cannot alloc %d byte buf", len+1);
|
||||
return;
|
||||
}
|
||||
evbuffer_remove(in, buf, len);
|
||||
|
||||
printf("%s ", inet_ntoa(addr));
|
||||
|
||||
if (st->conf->format == FORMAT_ASCII) {
|
||||
// Ascii
|
||||
buf[len] = '\0';
|
||||
printf("%s\n", buf);
|
||||
} else if (st->conf->format == FORMAT_HEX) {
|
||||
// Hex output
|
||||
for (i=0; i<len; i++) {
|
||||
printf("%02x", buf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
} else if (st->conf->format == FORMAT_BASE64) {
|
||||
// Base64
|
||||
int i=0;
|
||||
char out[4] = {0,0,0,0};
|
||||
while (i < len) {
|
||||
uint32_t value = 0;
|
||||
value += (i < len) ? buf[i++] << 16 : 0;
|
||||
value += (i < len) ? buf[i++] << 8 : 0;
|
||||
value += (i < len) ? buf[i++] : 0;
|
||||
out[0] = BASE64_ALPHABET[(value >> 18) & 0x3F];
|
||||
out[1] = BASE64_ALPHABET[(value >> 12) & 0x3F];
|
||||
out[2] = BASE64_ALPHABET[(value >> 6) & 0x3F];
|
||||
out[3] = BASE64_ALPHABET[(value ) & 0x3F];
|
||||
if (i < len) {
|
||||
printf("%c%c%c%c", out[0], out[1], out[2], out[3]);
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
switch (len % 3) {
|
||||
case 1:
|
||||
out[2] = '=';
|
||||
case 2:
|
||||
out[3] = '=';
|
||||
default:
|
||||
break;
|
||||
}
|
||||
printf("%c%c%c%c\n", out[0], out[1], out[2], out[3]);
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
free(buf);
|
||||
st->conf->stats.completed_hosts++;
|
||||
}
|
||||
bufferevent_free(bev);
|
||||
decrement_cur_running(st);
|
||||
}
|
||||
|
||||
void grab_banner(struct state *st)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
struct bufferevent *bev;
|
||||
struct timeval read_to = {st->conf->connect_timeout, 0};
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(st->conf->port);
|
||||
addr.sin_addr.s_addr = st->ip;
|
||||
log_debug("banner-grab-tcp", "connect %s:%d",
|
||||
inet_ntoa(addr.sin_addr), st->conf->port);
|
||||
|
||||
bev = bufferevent_socket_new(st->conf->base, -1, BEV_OPT_CLOSE_ON_FREE);
|
||||
|
||||
bufferevent_set_timeouts(bev, &read_to, &read_to);
|
||||
|
||||
bufferevent_setcb(bev, read_cb, NULL, connect_cb, st);
|
||||
bufferevent_enable(bev, EV_READ);
|
||||
|
||||
st->state = CONNECTING;
|
||||
|
||||
st->conf->stats.init_connected_hosts++;
|
||||
|
||||
if (bufferevent_socket_connect(bev,
|
||||
(struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
log_warn("banner-grab-tcp", "could not connect socket %d (%d open) %d %d",
|
||||
bufferevent_getfd(bev), st->conf->current_running, errno, ENFILE);
|
||||
perror("connect");
|
||||
|
||||
|
||||
bufferevent_free(bev);
|
||||
decrement_cur_running(st);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void stdin_eventcb(struct bufferevent *bev, short events, void *ptr) {
|
||||
struct config *conf = ptr;
|
||||
|
||||
if (events & BEV_EVENT_EOF) {
|
||||
log_debug("banner-grab-tcp",
|
||||
"received EOF; quitting after buffer empties");
|
||||
conf->stdin_closed = 1;
|
||||
if (conf->current_running == 0) {
|
||||
log_info("banner-grab-tcp", "done");
|
||||
print_status(0, 0, conf);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stdin_readcb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
struct evbuffer *in = bufferevent_get_input(bev);
|
||||
struct config *conf = arg;
|
||||
|
||||
log_debug("banner-grab-tcp", "stdin cb %d < %d ?",
|
||||
conf->current_running, conf->max_concurrent);
|
||||
|
||||
while (conf->current_running < conf->max_concurrent &&
|
||||
evbuffer_get_length(in) > 0) {
|
||||
char *ip_str;
|
||||
size_t line_len;
|
||||
char *line = evbuffer_readln(in, &line_len, EVBUFFER_EOL_LF);
|
||||
struct state *st;
|
||||
if (!line)
|
||||
break;
|
||||
log_debug("banner-grab-tcp", "line: %s", line);
|
||||
|
||||
ip_str = line;
|
||||
/*
|
||||
port_str = strstr(line, ":") + 1;
|
||||
if (!port_str)
|
||||
port_str = strstr(line, " ") + 1;
|
||||
if (!port_str)
|
||||
break;
|
||||
|
||||
*(port_str-1) = '\0';
|
||||
port = atoi(port_str);
|
||||
*/
|
||||
//printf("scanning %s:%d\n", ip
|
||||
|
||||
conf->current_running++;
|
||||
st = malloc(sizeof(*st));
|
||||
st->conf = conf;
|
||||
st->ip = inet_addr(ip_str);
|
||||
grab_banner(st);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct event_base *base;
|
||||
struct event *status_timer;
|
||||
struct timeval status_timeout = {1, 0};
|
||||
int c;
|
||||
struct option long_options[] = {
|
||||
{"concurrent", required_argument, 0, 'c'},
|
||||
{"port", required_argument, 0, 'p'},
|
||||
{"conn-timeout", required_argument, 0, 't'},
|
||||
{"read-timeout", required_argument, 0, 'r'},
|
||||
{"verbosity", required_argument, 0, 'v'},
|
||||
{"format", no_argument, 0, 'f'},
|
||||
{"data", required_argument, 0, 'd'},
|
||||
{0, 0, 0, 0} };
|
||||
|
||||
struct config conf;
|
||||
int ret;
|
||||
FILE *fp;
|
||||
|
||||
log_init(stderr, LOG_INFO);
|
||||
|
||||
ret = ulimit(4, 1000000); // Allow us to open 1 million fds (instead of 1024)
|
||||
if (ret < 0) {
|
||||
log_fatal("banner-grab-tcp", "cannot set ulimit");
|
||||
perror("ulimit");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
base = event_base_new();
|
||||
conf.base = base;
|
||||
|
||||
// buffer stdin as an event
|
||||
conf.stdin_bev = bufferevent_socket_new(base, 0, BEV_OPT_DEFER_CALLBACKS);
|
||||
bufferevent_setcb(conf.stdin_bev, stdin_readcb, NULL, stdin_eventcb, &conf);
|
||||
bufferevent_enable(conf.stdin_bev, EV_READ);
|
||||
|
||||
// Status timer
|
||||
status_timer = evtimer_new(base, print_status, &conf);
|
||||
evtimer_add(status_timer, &status_timeout);
|
||||
|
||||
// Defaults
|
||||
conf.max_concurrent = 1;
|
||||
conf.current_running = 0;
|
||||
memset(&conf.stats, 0, sizeof(conf.stats));
|
||||
conf.connect_timeout = 4;
|
||||
conf.read_timeout = 4;
|
||||
conf.stdin_closed = 0;
|
||||
conf.format = FORMAT_BASE64;
|
||||
conf.send_str = NULL;
|
||||
|
||||
// Parse command line args
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
c = getopt_long(argc, argv, "c:p:t:r:v:f:d:",
|
||||
long_options, &option_index);
|
||||
|
||||
if (c < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'c':
|
||||
conf.max_concurrent = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
conf.port = atoi(optarg);
|
||||
break;
|
||||
case 't':
|
||||
conf.connect_timeout = atoi(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
conf.read_timeout = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
if (atoi(optarg) >= 0 && atoi(optarg) <= 5) {
|
||||
log_init(stderr, atoi(optarg));
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (strcmp(optarg, "hex") == 0) {
|
||||
conf.format = FORMAT_HEX;
|
||||
} else if (strcmp(optarg, "base64") == 0) {
|
||||
conf.format = FORMAT_BASE64;
|
||||
} else if (strcmp(optarg, "ascii") == 0) {
|
||||
conf.format = FORMAT_ASCII;
|
||||
} else {
|
||||
log_fatal("banner-grab-tcp", "Unknown format '%s'; use 'hex', 'base64', or 'ascii'",
|
||||
optarg);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
fp = fopen(optarg, "r");
|
||||
if (!fp) {
|
||||
log_error("banner-grab-tcp", "Could not open send data file '%s':", optarg);
|
||||
perror("fopen");
|
||||
exit(-1);
|
||||
}
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
conf.send_str_size = ftell(fp);
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
//assert(conf.send_str_size < 10000); // jumbo frames?
|
||||
conf.send_str = malloc(conf.send_str_size+1);
|
||||
if (!conf.send_str) {
|
||||
log_fatal("banner-grab-tcp", "Could not malloc %d bytes", conf.send_str_size+1);
|
||||
}
|
||||
if (fread(conf.send_str, conf.send_str_size, 1, fp) != 1) {
|
||||
log_fatal("banner-grab-tcp", "Couldn't read from send data file '%s':", optarg);
|
||||
}
|
||||
conf.send_str[conf.send_str_size] = '\0';
|
||||
fclose(fp);
|
||||
break;
|
||||
case '?':
|
||||
printf("Usage:\n");
|
||||
printf("\t%s [-c max_concurrency] [-t connect_timeout] [-r read_timeout] \n\t"
|
||||
"[-v verbosity=0-5] [-d send_data_file] [-f ascii|hex|base64] -p port\n", argv[0]);
|
||||
exit(1);
|
||||
default:
|
||||
log_info("banner-grab-tcp", "hmmm..");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("banner-grab-tcp", "Using port %d with max_concurrency %d, %d s conn timeout, %d s read timeout",
|
||||
conf.port, conf.max_concurrent, conf.connect_timeout, conf.read_timeout);
|
||||
|
||||
event_base_dispatch(base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
3
examples/banner-grab/http-req
Normal file
3
examples/banner-grab/http-req
Normal file
@ -0,0 +1,3 @@
|
||||
GET / HTTP/1.1
|
||||
Host: %s
|
||||
|
22
examples/forge-socket/Makefile
Normal file
22
examples/forge-socket/Makefile
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
CFLAGS+=-I../../lib/ -I../../forge_socket -Wall
|
||||
LDFLAGS+=-lpcap -levent -levent_extra -lm
|
||||
VPATH=../../lib/
|
||||
|
||||
# from dpkg-buildflags --get CFLAGS, but use stack-protector-all and fPIC
|
||||
GCCHARDENING=-g -O2 -fstack-protector-all --param=ssp-buffer-size=4 -Wformat -Wformat-security -Werror=format-security -fPIC
|
||||
# from gpkg-buildflags --get LDFLAGS, + -z,now
|
||||
LDHARDENING=-Wl,-Bsymbolic-functions -Wl,-z,relro,-z,now
|
||||
|
||||
CFLAGS+=$(GCCHARDENING)
|
||||
LDFLAGS+=$(LDHARDENING)
|
||||
|
||||
|
||||
all: forge-socket
|
||||
|
||||
forge-socket: forge-socket.o logger.o
|
||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f forge-socket *.o
|
73
examples/forge-socket/README
Normal file
73
examples/forge-socket/README
Normal file
@ -0,0 +1,73 @@
|
||||
Forge-socket banner grab
|
||||
======
|
||||
|
||||
This utility, in combination with a kernel module
|
||||
(https://github.com/ewust/forge_socket/) will complete the half-open connection
|
||||
created by ZMap during a TCP-scan, optionally send a small message, and wait
|
||||
for the hosts response. The response is then printed along with their IP
|
||||
address on stdout. Periodic status messages appear on stderr.
|
||||
|
||||
This utility is functionally equivalent to banner-grab-tcp, however, instead of
|
||||
having the kernel send a RST packet for the server's SYN+ACK, and
|
||||
banner-grab-tcp attempting to start a fresh TCP connection with the host,
|
||||
forge-socket will take the parameters of the SYN+ACK packet, and use a kernel
|
||||
module to add it as an ESTABLISHED TCP connection socket. Then, the
|
||||
forge-socket user-space program can use this socket to send() and recv() as
|
||||
normal, and completes the banner-grab process (optionally send a small message,
|
||||
and receive the server's response).
|
||||
|
||||
|
||||
|
||||
USING:
|
||||
-----
|
||||
# Install forge-socket to the ZMap root directory:
|
||||
cd ./zmap/
|
||||
git clone git@github.com:ewust/forge_socket.git
|
||||
cd forge_socket
|
||||
make
|
||||
sudo insmod forge_socket.ko
|
||||
|
||||
# Don't send RST packets (forge-socket will complete these connections instead)
|
||||
sudo iptables -A OUTPUT -p tcp -m tcp --tcp-flags RST,RST RST,RST -j DROP
|
||||
|
||||
# Use ZMap + forge-socket simultaneously:
|
||||
make
|
||||
#echo -e -n "GET / HTTP/1.1\r\nHost: %s\r\n\r\n" > http-req
|
||||
sudo su
|
||||
ulimit -SHn 1000000 && ulimit -SSn 1000000
|
||||
zmap -p 80 -B 50M -N 1000 -O extended_file -o - | ./forge-socket -c 8000 -d http-req > http-banners.out
|
||||
|
||||
|
||||
The options are similar to banner-grab-tcp, except there is no connection timeout :)
|
||||
|
||||
OPTIONS:
|
||||
-----
|
||||
-c, --concurent Number of connections that can be going on at once.
|
||||
This, combined with timeouts, will decide the maximum
|
||||
rate at which banners are grabbed. If this value
|
||||
is set higher than 1000, you should use
|
||||
`ulimit -SSn 1000000` and `ulimit -SHn 1000000` to
|
||||
avoid running out of file descriptors (typically capped
|
||||
at 1024).
|
||||
|
||||
-r, --read-timeout Read timeout (seconds). Give up on a host if after
|
||||
connecting (and optionally sending data), it does
|
||||
not send any response by this time. Default: 4 seconds.
|
||||
|
||||
-v, --verbosity Set status verbosity. Status/error messages are outputed
|
||||
on stderr. This value can be 0-5, with 5 being the most
|
||||
verbose (LOG_TRACE). Default: 3 (LOG_INFO)
|
||||
|
||||
-f, --format Format to output banner responses. One of 'hex', 'ascii',
|
||||
or 'base64'.
|
||||
'hex' outputs ascii hex characters, e.g. 48656c6c6f.
|
||||
'ascii' outputs ascii, without separators, e.g. Hello
|
||||
'base64' outputs base64 encoding, e.g. SGVsbG8=
|
||||
Default is base64.
|
||||
|
||||
-d, --data Optional data file. This data will be sent to each host
|
||||
upon successful connection. Currently, this file does
|
||||
not allow null characters, but supports up to 4
|
||||
occurances of the current host's IP address, by replacing
|
||||
%s with the string (inet_ntoa) of that host's IP address.
|
||||
|
496
examples/forge-socket/forge-socket.c
Normal file
496
examples/forge-socket/forge-socket.c
Normal file
@ -0,0 +1,496 @@
|
||||
/*
|
||||
* Forge Socket Banner Grab Copyright 2013 Regents of the University of Michigan
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "logger.h"
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ulimit.h>
|
||||
|
||||
#include "forge_socket.h"
|
||||
|
||||
#define MAX_BANNER_LEN 1024
|
||||
#define BASE64_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
struct config {
|
||||
int read_timeout; // how long to wait once connected for the banner (seconds)
|
||||
int current_running;
|
||||
int max_concurrent;
|
||||
struct event_base *base;
|
||||
struct bufferevent *stdin_bev;
|
||||
int stdin_closed;
|
||||
enum {FORMAT_HEX, FORMAT_BASE64, FORMAT_ASCII} format;
|
||||
char *send_str;
|
||||
long send_str_size;
|
||||
|
||||
struct stats_st {
|
||||
int init_connected_hosts; // Number of hosts we have even tried to connect to
|
||||
int connected_hosts; // # hosts that picked up
|
||||
int conn_timed_out; // # hosts that timed out during connection
|
||||
int read_timed_out; // # hosts that connected, but sent no data (banner)
|
||||
int timed_out; // # hosts that timed out at all (conn_timed_out+read_timed_out)?
|
||||
int completed_hosts; // # hosts that presented a banner
|
||||
} stats;
|
||||
};
|
||||
|
||||
|
||||
struct state {
|
||||
struct config *conf;
|
||||
uint32_t src_ip;
|
||||
uint32_t dst_ip;
|
||||
uint16_t sport;
|
||||
uint16_t dport;
|
||||
uint32_t seq;
|
||||
uint32_t seq_ack;
|
||||
enum {CONNECTING, CONNECTED, RECEIVED} state;
|
||||
};
|
||||
|
||||
void stdin_readcb(struct bufferevent *bev, void *arg);
|
||||
|
||||
void print_status(evutil_socket_t fd, short events, void *arg)
|
||||
{
|
||||
struct event *ev;
|
||||
struct config *conf = arg;
|
||||
struct event_base *base = conf->base;
|
||||
struct timeval status_timeout = {1, 0};
|
||||
ev = evtimer_new(base, print_status, conf);
|
||||
evtimer_add(ev, &status_timeout);
|
||||
(void)fd; (void)events;
|
||||
|
||||
log_info("forge-socket", "(%d/%d in use) - Totals: %d inited, %d connected, %d conn timeout, %d read timeout %d completed",
|
||||
conf->current_running, conf->max_concurrent,
|
||||
conf->stats.init_connected_hosts,
|
||||
conf->stats.connected_hosts, conf->stats.conn_timed_out,
|
||||
conf->stats.read_timed_out, conf->stats.completed_hosts);
|
||||
}
|
||||
|
||||
void decrement_cur_running(struct state *st)
|
||||
{
|
||||
struct config *conf = st->conf;
|
||||
conf->current_running--;
|
||||
log_debug("forge-socket", "done, down to %d",
|
||||
conf->current_running);
|
||||
if (evbuffer_get_length(bufferevent_get_input(conf->stdin_bev)) > 0) {
|
||||
stdin_readcb(conf->stdin_bev, conf);
|
||||
}
|
||||
free(st);
|
||||
|
||||
if (conf->stdin_closed && conf->current_running == 0) {
|
||||
// Done
|
||||
log_info("forge-socket", "done");
|
||||
print_status(0, 0, conf);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void event_cb(struct bufferevent *bev, short events, void *arg)
|
||||
{
|
||||
struct state *st = arg;
|
||||
struct config *conf = st->conf;
|
||||
struct in_addr addr;
|
||||
addr.s_addr = st->src_ip;
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
log_error("forge-socket", "%s connected - wat?", inet_ntoa(addr));
|
||||
|
||||
} else {
|
||||
if (st->state == CONNECTED) {
|
||||
// Print out that we just didn't receive data
|
||||
printf("%s X\n", inet_ntoa(addr));
|
||||
fflush(stdout);
|
||||
|
||||
conf->stats.read_timed_out++;
|
||||
} else {
|
||||
conf->stats.conn_timed_out++;
|
||||
}
|
||||
log_debug("forge-socket", "%s bailing..", inet_ntoa(addr));
|
||||
bufferevent_free(bev);
|
||||
conf->stats.timed_out++;
|
||||
decrement_cur_running(st);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab these bytes, and close the connection.
|
||||
// Even if we don't need to read any bytes,
|
||||
// we have to have this so that libevent thinks we have
|
||||
// a read event, so that it can timeout TCP connects
|
||||
// (as a read timeout)
|
||||
void read_cb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
struct evbuffer *in = bufferevent_get_input(bev);
|
||||
struct state *st = arg;
|
||||
size_t len = evbuffer_get_length(in);
|
||||
struct in_addr addr;
|
||||
addr.s_addr = st->src_ip;
|
||||
|
||||
log_debug("forge-socket", "read_cb for %s", inet_ntoa(addr));
|
||||
|
||||
if (len > MAX_BANNER_LEN) {
|
||||
len = MAX_BANNER_LEN;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
// Grab the banner
|
||||
unsigned int i;
|
||||
unsigned char *buf = malloc(len+1);
|
||||
|
||||
st->state = RECEIVED;
|
||||
|
||||
if (!buf) {
|
||||
log_fatal("forge-socket", "cannot alloc %d byte buf", len+1);
|
||||
return;
|
||||
}
|
||||
evbuffer_remove(in, buf, len);
|
||||
|
||||
printf("%s ", inet_ntoa(addr));
|
||||
|
||||
if (st->conf->format == FORMAT_ASCII) {
|
||||
// Ascii
|
||||
buf[len] = '\0';
|
||||
printf("%s\n", buf);
|
||||
} else if (st->conf->format == FORMAT_HEX) {
|
||||
// Hex output
|
||||
for (i=0; i<len; i++) {
|
||||
printf("%02x", buf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
} else if (st->conf->format == FORMAT_BASE64) {
|
||||
// Base64
|
||||
int i=0;
|
||||
char out[4] = {0,0,0,0};
|
||||
while (i < len) {
|
||||
uint32_t value = 0;
|
||||
value += (i < len) ? buf[i++] << 16 : 0;
|
||||
value += (i < len) ? buf[i++] << 8 : 0;
|
||||
value += (i < len) ? buf[i++] : 0;
|
||||
out[0] = BASE64_ALPHABET[(value >> 18) & 0x3F];
|
||||
out[1] = BASE64_ALPHABET[(value >> 12) & 0x3F];
|
||||
out[2] = BASE64_ALPHABET[(value >> 6) & 0x3F];
|
||||
out[3] = BASE64_ALPHABET[(value ) & 0x3F];
|
||||
if (i < len) {
|
||||
printf("%c%c%c%c", out[0], out[1], out[2], out[3]);
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
switch (len % 3) {
|
||||
case 1:
|
||||
out[2] = '=';
|
||||
case 2:
|
||||
out[3] = '=';
|
||||
default:
|
||||
break;
|
||||
}
|
||||
printf("%c%c%c%c\n", out[0], out[1], out[2], out[3]);
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
free(buf);
|
||||
st->conf->stats.completed_hosts++;
|
||||
}
|
||||
bufferevent_free(bev);
|
||||
decrement_cur_running(st);
|
||||
}
|
||||
|
||||
int set_sock_state(int sock, struct tcp_state *st)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = st->src_ip;
|
||||
sin.sin_port = st->sport;
|
||||
|
||||
int value = 1;
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) {
|
||||
perror("setsockopt SO_REUSEADDR");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (setsockopt(sock, SOL_IP, IP_TRANSPARENT, &value, sizeof(value)) < 0) {
|
||||
perror("setsockopt IP_TRANSPARENT");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
|
||||
perror("bind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (setsockopt(sock, IPPROTO_TCP, TCP_STATE, st, sizeof(struct tcp_state)) < 0) {
|
||||
perror("setsockopt TCP_STATE");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void grab_banner(struct state *st)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
struct bufferevent *bev;
|
||||
struct timeval read_to = {st->conf->read_timeout, 0};
|
||||
struct tcp_state tcp_st;
|
||||
int sock = socket(AF_INET, SOCK_FORGE, 0);
|
||||
|
||||
addr.sin_addr.s_addr = st->src_ip;
|
||||
|
||||
if (sock < 0) {
|
||||
perror("SOCK_FORGE socket");
|
||||
log_fatal("forge_socket", "(did you insmod forge_socket.ko?)");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&tcp_st, 0, sizeof(tcp_st));
|
||||
|
||||
// These need to be in network order for forge socket"
|
||||
tcp_st.src_ip = st->dst_ip;
|
||||
tcp_st.dst_ip = st->src_ip;
|
||||
tcp_st.sport = htons(st->dport);
|
||||
tcp_st.dport = htons(st->sport);
|
||||
|
||||
// This should be in ???
|
||||
tcp_st.seq = st->seq_ack;
|
||||
tcp_st.ack = (st->seq + 1);
|
||||
|
||||
tcp_st.snd_wnd = 0x1000;
|
||||
tcp_st.rcv_wnd = 0x1000;
|
||||
|
||||
tcp_st.snd_una = tcp_st.seq;
|
||||
st->state = CONNECTING;
|
||||
st->conf->stats.init_connected_hosts++;
|
||||
|
||||
// consider this a non-blocking, but completed "connect()". heh.
|
||||
if (set_sock_state(sock, &tcp_st) != 0) {
|
||||
log_error("forge_socket", "set_sock_state failed\n");
|
||||
decrement_cur_running(st);
|
||||
return;
|
||||
}
|
||||
|
||||
evutil_make_socket_nonblocking(sock);
|
||||
|
||||
bev = bufferevent_socket_new(st->conf->base, sock, BEV_OPT_CLOSE_ON_FREE);
|
||||
|
||||
bufferevent_set_timeouts(bev, &read_to, &read_to);
|
||||
|
||||
bufferevent_setcb(bev, read_cb, NULL, event_cb, st);
|
||||
bufferevent_enable(bev, EV_READ);
|
||||
|
||||
|
||||
// Send data
|
||||
if (st->conf->send_str) {
|
||||
struct evbuffer *evout = bufferevent_get_output(bev);
|
||||
// HACK!!! TODO: make some messy parser that replaces ${IP} with IP etc
|
||||
// and allow null characters
|
||||
evbuffer_add_printf(evout, st->conf->send_str,
|
||||
inet_ntoa(addr.sin_addr), inet_ntoa(addr.sin_addr),
|
||||
inet_ntoa(addr.sin_addr), inet_ntoa(addr.sin_addr));
|
||||
log_trace("forge-socket", "sent str to %s", inet_ntoa(addr.sin_addr));
|
||||
}
|
||||
|
||||
// Update state/stats
|
||||
st->state = CONNECTED;
|
||||
st->conf->stats.connected_hosts++;
|
||||
|
||||
log_trace("forge-socket", "go %s go! read a byte!!", inet_ntoa(addr.sin_addr));
|
||||
}
|
||||
|
||||
void stdin_eventcb(struct bufferevent *bev, short events, void *ptr) {
|
||||
struct config *conf = ptr;
|
||||
|
||||
if (events & BEV_EVENT_EOF) {
|
||||
log_debug("forge-socket",
|
||||
"received EOF; quitting after buffer empties");
|
||||
conf->stdin_closed = 1;
|
||||
if (conf->current_running == 0) {
|
||||
log_info("forge-socket", "done");
|
||||
print_status(0, 0, conf);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stdin_readcb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
struct evbuffer *in = bufferevent_get_input(bev);
|
||||
struct config *conf = arg;
|
||||
|
||||
log_debug("forge-socket", "stdin cb %d < %d ?",
|
||||
conf->current_running, conf->max_concurrent);
|
||||
|
||||
while (conf->current_running < conf->max_concurrent &&
|
||||
evbuffer_get_length(in) > 0) {
|
||||
size_t line_len;
|
||||
char *line = evbuffer_readln(in, &line_len, EVBUFFER_EOL_LF);
|
||||
struct state *st;
|
||||
if (!line)
|
||||
break;
|
||||
log_debug("forge-socket", "line: '%s'", line);
|
||||
|
||||
//synack, 77.176.116.205, 141.212.121.125, 443, 49588, 3628826326, 3441755636, 0, 0,2013-08-11 19:16:05.799
|
||||
char synack[12];
|
||||
char srcip[INET_ADDRSTRLEN], dstip[INET_ADDRSTRLEN];
|
||||
uint32_t seq, seq_ack;
|
||||
uint16_t sport, dport;
|
||||
int cooldown, repeat=1;
|
||||
|
||||
|
||||
int ret = sscanf(line, "%11[^,], %15[^,], %15[^,], %hu, %hu, %u, %u, %d, %d,%*s",
|
||||
synack, srcip, dstip, &sport, &dport, &seq, &seq_ack, &cooldown, &repeat);
|
||||
|
||||
log_trace("forge-socket", "%d '%s' sip: '%s', dip: '%s', sport: %d, dport: %d, seq: %d, seq_ack: %d",
|
||||
ret, synack, srcip, dstip, sport, dport, seq, seq_ack);
|
||||
|
||||
if (ret==9 && !repeat && strcmp(synack, "synack") == 0) {
|
||||
st = malloc(sizeof(*st));
|
||||
st->conf = conf;
|
||||
st->src_ip = inet_addr(srcip);
|
||||
st->dst_ip = inet_addr(dstip);
|
||||
st->sport = sport;
|
||||
st->dport = dport;
|
||||
st->seq = seq;
|
||||
st->seq_ack = seq_ack;
|
||||
|
||||
conf->current_running++;
|
||||
grab_banner(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct event_base *base;
|
||||
struct event *status_timer;
|
||||
struct timeval status_timeout = {1, 0};
|
||||
int c;
|
||||
struct option long_options[] = {
|
||||
{"concurrent", required_argument, 0, 'c'},
|
||||
{"read-timeout", required_argument, 0, 'r'},
|
||||
{"verbosity", required_argument, 0, 'v'},
|
||||
{"format", no_argument, 0, 'f'},
|
||||
{"data", required_argument, 0, 'd'},
|
||||
{0, 0, 0, 0} };
|
||||
|
||||
struct config conf;
|
||||
int ret;
|
||||
FILE *fp;
|
||||
|
||||
log_init(stderr, LOG_INFO);
|
||||
|
||||
ret = ulimit(4, 1000000); // Allow us to open 1 million fds (instead of 1024)
|
||||
if (ret < 0) {
|
||||
log_fatal("forge-socket", "cannot set ulimit");
|
||||
perror("ulimit");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
base = event_base_new();
|
||||
conf.base = base;
|
||||
|
||||
// buffer stdin as an event
|
||||
conf.stdin_bev = bufferevent_socket_new(base, 0, BEV_OPT_DEFER_CALLBACKS);
|
||||
bufferevent_setcb(conf.stdin_bev, stdin_readcb, NULL, stdin_eventcb, &conf);
|
||||
bufferevent_enable(conf.stdin_bev, EV_READ);
|
||||
|
||||
// Status timer
|
||||
status_timer = evtimer_new(base, print_status, &conf);
|
||||
evtimer_add(status_timer, &status_timeout);
|
||||
|
||||
// Defaults
|
||||
conf.max_concurrent = 1;
|
||||
conf.current_running = 0;
|
||||
memset(&conf.stats, 0, sizeof(conf.stats));
|
||||
conf.read_timeout = 4;
|
||||
conf.stdin_closed = 0;
|
||||
conf.format = FORMAT_BASE64;
|
||||
conf.send_str = NULL;
|
||||
|
||||
// Parse command line args
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
c = getopt_long(argc, argv, "c:t:r:v:f:d:",
|
||||
long_options, &option_index);
|
||||
|
||||
if (c < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'c':
|
||||
conf.max_concurrent = atoi(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
conf.read_timeout = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
if (atoi(optarg) >= 0 && atoi(optarg) <= 5) {
|
||||
log_init(stderr, atoi(optarg));
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (strcmp(optarg, "hex") == 0) {
|
||||
conf.format = FORMAT_HEX;
|
||||
} else if (strcmp(optarg, "base64") == 0) {
|
||||
conf.format = FORMAT_BASE64;
|
||||
} else if (strcmp(optarg, "ascii") == 0) {
|
||||
conf.format = FORMAT_ASCII;
|
||||
} else {
|
||||
log_fatal("forge-socket", "Unknown format '%s'; use 'hex', 'base64', or 'ascii'",
|
||||
optarg);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
fp = fopen(optarg, "r");
|
||||
if (!fp) {
|
||||
log_error("forge-socket", "Could not open send data file '%s':", optarg);
|
||||
perror("fopen");
|
||||
exit(-1);
|
||||
}
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
conf.send_str_size = ftell(fp);
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
//assert(conf.send_str_size < 10000); // jumbo frames?
|
||||
conf.send_str = malloc(conf.send_str_size+1);
|
||||
if (!conf.send_str) {
|
||||
log_fatal("forge-socket", "Could not malloc %d bytes", conf.send_str_size+1);
|
||||
}
|
||||
if (fread(conf.send_str, conf.send_str_size, 1, fp) != 1) {
|
||||
log_fatal("forge-socket", "Couldn't read from send data file '%s':", optarg);
|
||||
}
|
||||
conf.send_str[conf.send_str_size] = '\0';
|
||||
fclose(fp);
|
||||
break;
|
||||
case '?':
|
||||
printf("Usage:\n");
|
||||
printf("\t%s [-c max_concurrency] [-r read_timeout] \n\t"
|
||||
"[-v verbosity=0-5] [-d send_data_file] [-f ascii|hex|base64]\n", argv[0]);
|
||||
exit(1);
|
||||
default:
|
||||
log_info("forge-socket", "hmmm..");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("forge-socket", "Using max_concurrency %d, %d s read timeout",
|
||||
conf.max_concurrent, conf.read_timeout);
|
||||
|
||||
event_base_dispatch(base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user