diff options
Diffstat (limited to 'src/noawareness.c')
| -rw-r--r-- | src/noawareness.c | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/src/noawareness.c b/src/noawareness.c new file mode 100644 index 0000000..dbabe44 --- /dev/null +++ b/src/noawareness.c @@ -0,0 +1,512 @@ +// https://www.kernel.org/doc/Documentation/connector/connector.txt +// TODO CLI options to toggle sniffer, proc_connector, fanotify, ... +// TODO map uids and gids to real names + // implement getgrgid and getpwuid myself because cant link these static +// TODO permissions, attributes, owner, groupships of files +// TODO limits? /proc/X/limits +// TODO atexit() handler to log when this dies +// TODO emit a json log startup event +// TODO emit heartbeat json logs +// TODO multiple interfaces for sniffing + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <limits.h> +#include <syslog.h> +#include <signal.h> +#include <netdb.h> +#include <fcntl.h> + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/stat.h> + +#include "net.h" +#include "error.h" +#include "output.h" +#include "sniffer.h" +#include "fanotify.h" +#include "proc_ledger.h" +#include "hash_ledger.h" +#include "proc_connector.h" +#include "time_common.h" +#include "agent_context.h" +#include "av_rules.h" +#include "aho-corasick.h" + +/* dirty hack to make this work on ancient systems */ +#ifndef PROC_EVENT_PTRACE +#define PROC_EVENT_PTRACE 0x00000100 +#endif + +#ifndef PROC_EVENT_COMM +#define PROC_EVENT_COMM 0x00000200 +#endif + +#ifndef PROC_EVENT_COREDUMP +#define PROC_EVENT_COREDUMP 0x40000000 +#endif + +/* + * Globals + */ +sock_t sock; +bool quiet = false; +bool use_syslog = true; +bool log_to_file = false; +bool remote_logging = true; +char *log_server = "127.0.0.1"; +port_t log_server_port = 55555; +char *outfile = "/var/log/noawareness.json.log"; +FILE *outfilep; + +bool promisc = false; + +unsigned long maxsize = 1024 * 1024 * 100; // 100Mb +unsigned long boot_time; +long ticks; + +bool daemonize = false; +char *pidfile = "/var/run/noawareness.pid"; +static volatile sig_atomic_t reload_config = false; + +char *rules_file = "rules"; + +// Write pid file to disk +void write_pid_file(const char *path, pid_t pid) { + FILE *pidfile; + + pidfile = fopen(path, "w"); + if (pidfile == NULL) { + error_fatal("Unable to open PID file %s: %s", path, strerror(errno)); + } + + fprintf(pidfile, "%d", pid); + fclose(pidfile); +} + +FILE *open_log_file(const char *outfile) { + FILE *fp; + + fp = fopen(outfile, "a+"); + if (fp == NULL) { + error_fatal("Unable to open log file: %s", strerror(errno)); + } + + return fp; +} + +// Sets up a UDP socket to a remote host for sending log events +// TODO strategies to do TCP, encryption, etc. the remote logging +// setup is pretty dirty +static void setup_remote_logging() { + struct addrinfo hints, *res, *rp; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + char pstr[6]; + snprintf(pstr, sizeof(pstr), "%d", log_server_port); + + if (getaddrinfo(log_server, pstr, &hints, &res) != 0) { + error_fatal("getaddrinfo(): %s", strerror(errno)); + } + + for (rp = res; rp != NULL; rp = rp->ai_next) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock == -1) { + continue; + } + + if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + + close(sock); + } + + if (rp == NULL) { + error_fatal("Unable to connect to %s: %s\n", log_server, strerror(errno)); + } + + char addr_s[INET6_ADDRSTRLEN]; + void *addr_ptr = NULL; + + if (rp->ai_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)rp->ai_addr; + addr_ptr = &(ipv4->sin_addr); + } else if (rp->ai_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)rp->ai_addr; + addr_ptr = &(ipv6->sin6_addr); + } + + if (addr_ptr && + inet_ntop(rp->ai_family, addr_ptr, addr_s, sizeof(addr_s))) { + msg("Connected to logserver: %s:%d", addr_s, log_server_port); + } + + freeaddrinfo(res); +} + +// SIGHUP handler +static void handle_sighup(int sig, siginfo_t *siginfo, void *context) { + reload_config = true; +} + +// install SIGHUP handler +static void install_sighup_handler() { + struct sigaction act = {0}; + + act.sa_sigaction = handle_sighup; + act.sa_flags = 0; + + if (sigaction(SIGHUP, &act, NULL) < 0) { + error_fatal("sigaction(): %s", strerror(errno)); + } +} + +// Variadic function that returns the maximum value input +static int max_fd(int count, ...) { + va_list args; + + va_start(args, count); + + int max = va_arg(args, int); + + for (int i = 1; i < count; i++) { + int val = va_arg(args, int); + if (val > max) { + max = val; + } + } + + va_end(args); + + return max; +} + +ac_context_t *build_matcher_from_rules(rule_set_t *rules) { + ac_context_t *ctx = ac_new(); + if (!ctx) { + return NULL; + } + + for (size_t i = 0; i < rules->patterns.count; i++) { + pattern_t *p = &rules->patterns.patterns[i]; + if (ac_add_pattern(ctx, p->id, p->bytes, p->len) != 0) { + error("Failed to add pattern %s to Aho-Corasick", p->id); + ac_free(ctx); + return NULL; + } + } + + if (ac_build(ctx) != 0) { + error("Failed to build Aho-Corasick automaton"); + ac_free(ctx); + return NULL; + } + + return ctx; +} + +// Display help/usage menu +static void usage(const char *progname) { + fprintf(stderr, "usage: %s [-h?]\n\n", progname); + fprintf(stderr, " -h/-? - Print this menu and exit.\n"); + fprintf(stderr, " -d - Daemonize. Default: %s\n", + daemonize ? "yes" : "no"); + fprintf(stderr, " -i <iface> - Interface to sniff\n"); + fprintf(stderr, " -x - Toggle promiscuous mode. Default: %s\n", + promisc ? "true" : "false"); + fprintf(stderr, " -m <bytes> - Max size of file to hash. Default: %ld\n", + maxsize); + fprintf(stderr, " -o <file> - Outfile for JSON output, Default: %s\n", + outfile); + fprintf(stderr, " -O - Toggle local JSON logging. Default: %s\n", + log_to_file ? "true" : "false"); + fprintf(stderr, " -P <path> - Path to PID file. Default: %s\n", pidfile); + fprintf(stderr, " -r - Toggle remote logging. Default: %s\n", + remote_logging ? "true" : "false"); + fprintf(stderr, " -R <path> - Path to AV rule definitions. Default: %s\n", rules_file); + fprintf(stderr, " -s <IP> - Remote log server. Default: %s\n", + log_server); + fprintf(stderr, " -S - Toggle syslog. Default: %s\n", + use_syslog ? "true" : "false"); + fprintf(stderr, " -p <port> - Port of remote server. Default: %d\n", + log_server_port); + fprintf(stderr, " -q - Toggle quiet mode. Default: %s\n", + quiet ? "true" : "false"); + + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) { + int opt; + pid_t pid; + sock_t proc_connector; + sock_t sniffer; // socket for sniffing to create network events + char *interface = NULL; + int fan_fd; // fanotify fd + fd_set fdset; + agent_context_t *agent_ctx; + + /* Parse CLI options */ + // TODO flag to enable/disable fanotify blocking + while((opt = getopt(argc, argv, "qrRdm:s:So:Op:rP:xi:h?")) != -1) { + switch (opt) { + case 'd': /* Daemonize */ + daemonize = daemonize ? false : true; + break; + + case 'i': /* Interface for SOCK_RAW sniffing */ + // TODO if optarg == "list", list interfaces + interface = optarg; + break; + + case 'm': /* Maximum filesize to hash */ + maxsize = atol(optarg); + break; + + case 'o': /* Path to outfile */ + // TODO check if writeable + outfile = optarg; + log_to_file = true; + break; + + case 'O': /* Toggle logging to a file */ + log_to_file = log_to_file ? false : true; + break; + + case 'p': /* Remote server port */ + log_server_port = atoi(optarg); + break; + + case 'P': /* PID file location */ + pidfile = optarg; + break; + + case 'q': /* Toggle quiet mode */ + quiet = quiet ? false : true; + break; + + case 'r': /* Toggle remote logging */ + remote_logging = remote_logging ? false : true; + break; + + case 'R': /* AV rules file */ + rules_file = optarg; + break; + + case 's': /* Remote server */ + log_server = optarg; + if (!validate_ip(log_server)) { + error_fatal("Invalid IP address: %s", log_server); + } + break; + + case 'S': /* Toggle syslog */ + use_syslog = use_syslog ? false : true; + break; + + case 'x': /* Toggle promiscuous mode */ + promisc = promisc ? false : true; + break; + + /* All of these effectively call usage(), so roll them over */ + case 'h': + case '?': + default: + usage(argv[0]); + } + } + + /* Set up syslog() */ + if (use_syslog) { + openlog("noawareness", LOG_PID, LOG_USER); + } + + /* SIGHUP handler. */ + install_sighup_handler(); + + /* Open log file. */ + if (log_to_file) { + outfilep = open_log_file(outfile); + } + + /* Allocate agent_ctx */ + agent_ctx = calloc(1, sizeof(*agent_ctx)); + if (!agent_ctx) { + error_fatal("calloc: %s", strerror(errno)); + } + /* Get our hostname for reporting purposes. */ + if (gethostname(agent_ctx->hostname, sizeof(agent_ctx->hostname)) == -1) { + error_fatal("gethostname(): %s", strerror(errno)); + } + + /* Get system boot time */ + ticks = sysconf(_SC_CLK_TCK); + boot_time = get_boot_time(); + + /* Set up remote logging */ + if (remote_logging) { + setup_remote_logging(); + } + + /* Create hash ledger */ + agent_ctx->hash_ledger = hash_ledger_init(4096); + if (!agent_ctx->hash_ledger) { + fprintf(stderr, "FATAL: Error creating hash ledger: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + /* Parse AV rules and create Aho-Corasick context */ + //agent_ctx->rules = {0}; + if (load_rules(rules_file, &agent_ctx->rules) != 0) { + error_fatal("FATAL: Failed to load AV signatures from file %s: %s", rules_file, strerror(errno)); + } + + agent_ctx->ac = build_matcher_from_rules(&agent_ctx->rules); + if (!agent_ctx->ac) { + error_fatal("FATAL: Failed to create Aho-Corasick context"); + } + + + /* Create process ledger--this needs to be done before the + fanotify handler, otherwise processes will be blocked until the + process ledger is hydrated */ + agent_ctx->proc_ledger = proc_ledger_init(4096); + proc_ledger_hydrate(agent_ctx); + + /* Create proc_connector socket. */ + proc_connector = setup_proc_connector(); + if (proc_connector == -1) { + error_fatal("error setting up proc_connector: %s", strerror(errno)); + } + + /* Create fanotify descriptor */ + fan_fd = setup_fanotify(); + if (fan_fd == -1) { + error("Error setting up fanotify: %s", strerror(errno)); + } + + /* Create sniffer raw socket */ + // TODO flag to toggle sniffer + if (!interface) { + interface = get_default_iface(); + if (interface == NULL) { + error_fatal("FATAL: Unable to determine network interface. Please specify with -i"); + } + } + + sniffer = sniffer_init_interface(interface, promisc); + if (sniffer == -1) { + error("Error setting up SOCK_RAW socket: %s", strerror(errno)); + } + + /* Daemonize the process if desired. */ + if (daemonize) { + pid = fork(); + if (pid < 0) { + error_fatal("FATAL: fork(): %s", strerror(errno)); + } + + else if (pid > 0) { + write_pid_file(pidfile, pid); + exit(EXIT_SUCCESS); + } + } + + /* Set up select loop. */ + int setsize = max_fd(3, proc_connector, fan_fd, sniffer) + 1; + + for(;;) { + FD_ZERO(&fdset); + if (proc_connector != -1) FD_SET(proc_connector, &fdset); + if (fan_fd != -1) FD_SET(fan_fd, &fdset); + if (sniffer != -1) FD_SET(sniffer, &fdset); + + struct timeval select_timeout; + select_timeout.tv_sec = 1; + select_timeout.tv_usec = 0; + + int activity = select(setsize, &fdset, NULL, NULL, &select_timeout); + if (activity < 0) { + if (errno != EINTR) { + error_fatal("select(): %s", strerror(errno)); + } + } + + if (activity == 0) { + //printf("no activity...\n"); + continue; + } + + if (FD_ISSET(proc_connector, &fdset)) { + select_proc_connector(proc_connector, agent_ctx); + } + + if (FD_ISSET(fan_fd, &fdset)) { + select_fanotify(fan_fd, agent_ctx); + } + + if (FD_ISSET(sniffer, &fdset)) { + sniffer_handle_packet(sniffer, agent_ctx); + } + + if (reload_config) { // TODO break this into a reload function + reload_config = false; + msg("Caught SIGHUP."); + + if (log_to_file) { + msg("Reloading JSON log file"); + fclose(outfilep); + outfilep = open_log_file(outfile); + } + + msg("Flushing hash ledger"); + hash_ledger_destroy(agent_ctx->hash_ledger); + agent_ctx->hash_ledger = hash_ledger_init(4096); + if (!agent_ctx->hash_ledger) { + error_fatal("FATAL: Error creating hash ledger: %s\n", strerror(errno)); + } + + msg("Reloading AV signatures"); + pattern_table_free(&agent_ctx->rules.patterns); + free_rules(&agent_ctx->rules); + ac_free(agent_ctx->ac); + if (load_rules(rules_file, &agent_ctx->rules) != 0) { + error_fatal("FATAL: Failed to load AV signatures from file %s: %s", rules_file, strerror(errno)); + } + agent_ctx->ac = build_matcher_from_rules(&agent_ctx->rules); + if (!agent_ctx->ac) { + error_fatal("FATAL: Failed to create Aho-Corasick context"); + } + + msg("Rehydrating process list"); + proc_ledger_destroy(agent_ctx->proc_ledger); + agent_ctx->proc_ledger = proc_ledger_init(4096); + proc_ledger_hydrate(agent_ctx); + } + } + + /* Shouldn't ever get here */ + proc_ledger_destroy(agent_ctx->proc_ledger); + hash_ledger_destroy(agent_ctx->hash_ledger); + ac_free(agent_ctx->ac); + pattern_table_free(&agent_ctx->rules.patterns); + free_rules(&agent_ctx->rules); + close(proc_connector); + close(sniffer); + close(sock); + closelog(); + + return EXIT_SUCCESS; +} |
