commit 7fbba1e7b0a44ad3ce81e0af3606a5f08ab2f970 Author: Christopher Wellons Date: Sat Feb 2 22:38:59 2019 -0500 Initial working code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a19556a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +endlessh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..acb83b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.POSIX: +CC = cc +CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os +LDFLAGS = -ggdb3 +LDLIBS = + +endlessh: endlessh.c + $(CC) $(LDFLAGS) $(CFLAGS) -o $@ endlessh.c $(LDLIBS) + +clean: + rm -rf endlessh diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3cf2b0 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Endlessh: an SSH tarpit + +Endlessh is an SSH tarpit that *very* slowly sends a randomized SSH +banner. It keeps clients locked up for hours or even days at at time. +The idea is that you put your real SSH server on another port and let +the script kiddies themselves stuck in this tarpit instead of bothering +real server. + +Since the tarpit is in the banner before any cryptographic exchange +occurs, this program doesn't depend on any cryptographic libraries. It's +a simple, single-threaded, standalone C program. It uses `poll()` to +trap multiple clients at a time. + +### Usage + +Usage information is printed with `-h`. + +``` +Usage: endlessh [-vh] [-d MSECS] [-m LIMIT] [-p PORT] + -d INT Message millisecond delay [10000] + -h Print this help message and exit + -m INT Maximum number of clients [4096] + -p INT Listening port [2222] + -v Print diagnostics to standard output (repeatable) +``` + +The purpose of limiting the number of clients (`-m`) is to avoid tying +up too many system resources with the tarpit. Clients beyond this limit +are left in the accept queue, not rejected instantly. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/endlessh.c b/endlessh.c new file mode 100644 index 0000000..b794a25 --- /dev/null +++ b/endlessh.c @@ -0,0 +1,340 @@ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DEFAULT_MAX_CLIENTS 4096 +#define DEFAULT_PORT 2222 +#define DEFAULT_DELAY 10000 // milliseconds + +#define XSTR(s) STR(s) +#define STR(s) #s + +static long long +uepoch(void) +{ + struct timespec tv; + clock_gettime(CLOCK_REALTIME, &tv); + return tv.tv_sec * 1000ULL + tv.tv_nsec / 1000000ULL; +} + +struct client { + long long send_next; // epoch milliseconds for next line + struct client *next; + int fd; +}; + +static struct client * +client_new(int fd, long long send_next) +{ + struct client *c = malloc(sizeof(*c)); + if (c) { + c->send_next = send_next; + c->next = 0; + c->fd = fd; + } + return c; +} + +struct queue { + struct client *head; + struct client *tail; +}; + +static void +queue_init(struct queue *q) +{ + q->head = q->tail = 0; +} + +static struct client * +queue_remove(struct queue *q, int fd) +{ + struct client *c; + struct client **prev = &q->head; + for (c = q->head; c; prev = &c->next, c = c->next) { + if (c->fd == fd) { + if (q->tail == c) + q->tail = 0; + *prev = c->next; + c->next = 0; + break; + } + } + return c; +} + +static void +queue_append(struct queue *q, struct client *c) +{ + if (!q->tail) { + q->head = q->tail = c; + } else { + q->tail->next = c; + q->tail = c; + } +} + +struct pollvec { + size_t cap; + size_t fill; + struct pollfd *fds; +}; + +static void +pollvec_init(struct pollvec *v) +{ + v->cap = 4; + v->fill = 0; + v->fds = malloc(v->cap * sizeof(*v->fds)); + if (!v->fds) { + fprintf(stderr, "endlessh: pollvec initialization failed\n"); + exit(EXIT_FAILURE); + } +} + +static void +pollvec_clear(struct pollvec *v) +{ + v->fill = 0; +} + +static int +pollvec_push(struct pollvec *v, int fd, short events) +{ + if (v->cap == v->fill) { + size_t size = v->cap * 2 * sizeof(*v->fds); + if (size < v->cap * sizeof(*v->fds)) + return 0; // overflow + struct pollfd *grow = realloc(v->fds, size); + if (!grow) + return 0; + v->fds = grow; + } + v->fds[v->fill].fd = fd; + v->fds[v->fill].events = events; + v->fill++; + return 1; +} + +static void +check(int r) +{ + if (r == -1) { + fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static int +randline(char *line) +{ + int len = 1 + rand() % 252; + for (int i = 0; i < len - 2; i++) + line[i] = 32 + rand() % 95; + line[len - 2] = 13; + line[len - 1] = 10; + if (memcmp(line, "SSH-", 4) == 0) + line[0] = 'X'; + return len; +} + +static void +log(const char *format, ...) +{ + long long now = uepoch(); + fprintf(stderr, "%lld.%03lld: ", now / 1000, now % 1000); + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fputc('\n', stderr); +} + +static void +usage(FILE *f) +{ + fprintf(f, "Usage: endlessh [-vh] [-d MSECS] [-m LIMIT] [-p PORT]\n"); + fprintf(f, " -d INT Message millisecond delay [" + XSTR(DEFAULT_DELAY) "]\n"); + fprintf(f, " -h Print this help message and exit\n"); + fprintf(f, " -m INT Maximum number of clients [" + XSTR(DEFAULT_MAX_CLIENTS) "]\n"); + fprintf(f, " -p INT Listening port [" XSTR(DEFAULT_PORT) "]\n"); + fprintf(f, " -v Print diagnostics to standard output " + "(repeatable)\n"); +} + +int +main(int argc, char **argv) +{ + int verbose = 0; + int port = DEFAULT_PORT; + long delay = DEFAULT_DELAY; + long max_clients = DEFAULT_MAX_CLIENTS; + + int option; + while ((option = getopt(argc, argv, "d:hm:p:v")) != -1) { + long tmp; + char *end; + switch (option) { + case 'd': + delay = strtol(optarg, &end, 10); + if (errno || *end || delay < 0) { + fprintf(stderr, "endlessh: Invalid delay: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'h': + usage(stdout); + exit(EXIT_SUCCESS); + break; + case 'm': + tmp = strtol(optarg, &end, 10); + if (errno || *end || tmp < 0) { + fprintf(stderr, "endlessh: Invalid port: %s\n", optarg); + exit(EXIT_FAILURE); + } + max_clients = tmp; + break; + case 'p': + tmp = strtol(optarg, &end, 10); + if (errno || *end || tmp < 0 || tmp > 65535) { + fprintf(stderr, "endlessh: Invalid port: %s\n", optarg); + exit(EXIT_FAILURE); + } + port = tmp; + break; + case 'v': + verbose++; + break; + default: + usage(stderr); + exit(EXIT_FAILURE); + } + } + + long nclients = 0; + + struct queue queue[1]; + queue_init(queue); + + struct pollvec pollvec[1]; + pollvec_init(pollvec); + + int server = socket(AF_INET, SOCK_STREAM, 0); + check(server); + + int dummy = 1; + check(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof(dummy))); + + struct sockaddr_in addr = {AF_INET, htons(port), {htonl(INADDR_ANY)}}; + check(bind(server, (void *)&addr, sizeof(addr))); + check(listen(server, INT_MAX)); + + if (verbose >= 1) + log("listen(port=%d)", port); + + srand(time(0)); + for (;;) { + pollvec_clear(pollvec); + pollvec_push(pollvec, nclients < max_clients ? server : -1, POLLIN); + + /* Poll clients that are due for another message */ + int timeout = -1; + long long now = uepoch(); + for (struct client *c = queue->head; c; c = c->next) { + if (c->send_next <= now) { + pollvec_push(pollvec, c->fd, POLLOUT); + } else { + timeout = c->send_next - now; + break; + } + } + + /* Wait for next event */ + if (verbose >= 2) + log("poll(%zu, %d)%s", pollvec->fill, timeout, + nclients == max_clients ? " (no accept)" : ""); + int r = poll(pollvec->fds, pollvec->fill, timeout); + if (verbose >= 2) + log("= %d", r); + if (r == -1) { + switch (errno) { + case EINTR: + if (verbose >= 1) + log("EINTR"); + continue; + default: + fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + } + + /* Check for new incoming connections */ + if (pollvec->fds[0].revents & POLLIN) { + int fd = accept(server, 0, 0); + if (verbose >= 1) + log("accept() = %d", fd); + if (fd == -1) { + const char *msg = strerror(errno); + switch (errno) { + case ECONNABORTED: + case EINTR: + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: + case EPROTO: + fprintf(stderr, "endlessh: warning: %s\n", msg); + continue; + default: + fprintf(stderr, "endlessh: fatal: %s\n", msg); + exit(EXIT_FAILURE); + } + } else { + struct client *client = client_new(fd, uepoch() + delay / 2); + if (!client) { + fprintf(stderr, "endlessh: warning: out of memory\n"); + close(fd); + } + queue_append(queue, client); + nclients++; + } + } + + /* Write lines to ready clients */ + for (size_t i = 1; i < pollvec->fill; i++) { + short fd = pollvec->fds[i].fd; + short revents = pollvec->fds[i].revents; + struct client *client = queue_remove(queue, fd); + + if (revents & POLLHUP) { + if (verbose >= 1) + log("close(%d)", fd); + close(fd); + free(client); + nclients--; + + } else if (revents & POLLOUT) { + char line[255]; + int len = randline(line); + /* Don't really care if this send fails */ + send(fd, line, len, MSG_DONTWAIT); + client->send_next = uepoch() + delay; + queue_append(queue, client); + } + } + } +}