commit 0c10e12608786593b4c83932e1184b21ab18f857 Author: geoffrey Date: Fri Jan 3 13:56:03 2025 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71c7979 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +src/**.o +src/ssh_trace diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0964de1 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +GCC=gcc +CL=clang-11 +CFLAGS=-Wall +LIBS=-lbpf + +all: ssh-trace.ebpf.o ssh-trace + +ssh-trace.ebpf.o: src/ssh-trace.ebpf.c + $(CL) -g -O2 -target bpf -c src/ssh-trace.ebpf.c -o src/ssh-trace.ebpf.o + +ssh-trace: src/load_bpf.c + $(GCC) $(CFLAGS) src/load_bpf.c -o ssh-trace $(LIBS) + +clean: + rm -rf src/*.o && rm ssh-trace diff --git a/README.md b/README.md new file mode 100644 index 0000000..16ec4b6 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Introduction +Nowadays, with the increase of numbers of servers in an infrastructure, it's important to trace all users activities for investigating when a suspicious activity has been detected. This project is borned for resolving that issue, which trace all user connected through SSH and the outcome is print to the stdout or to a file in rsyslog format. + +The program detect all commands executed in the system from a user connected and the result is print into the terminal, the program has an advantage for detection any privilege escalations when the user switch to another one, and the program show to us the initial user connected with the username and the user who executed the command. The diagram below show us an example: + +![Example](example.png) + +# Installation +# Supported platforms +The program is based on [eBPF](https://ebpf.io/). It's a technology for developping a program which is loaded into the Kernel for security, networking and tracing all event in the kernel. This program has been tested on these systems: + +| System | Architecture | Version | Kernel version | +|--------|--------------|---------|----------------| +| Ubuntu | x64| 20.04| 5.15.0| +| Debian | x64| 11| 5.10.0| + +## Requirements +The program is based on eBPF and developped in C language. You should install these packages if you want to generate the binary: + +* bpftool +* clang-11 +* libbpf-dev +* gcc gcc-multilib + +# Usage +After you clone the project, you can move into it. The arboresence of the project is quite simple. You have the repository `src/` which contains all C sources and headers files and you have the Makefile for generating the binary with the command `make all`: + +``` +$ git clone https://gitea.bucchino.org/gbucchino/ssh-trace +$ cd ssh-trace +$ make all +``` + +That will generate the binary `ssh-trace` and you can execute it: + +``` +$ sudo ./ssh-trace +``` + +By default, the result is print into the stdout, but, you can export it to rsyslog file format with the parameter -f: + +``` +$ sudo ./ssh-trace -f ssh-trace_`$(echo date '+%F')`.log +$ cat ssh-trace*.log +$ sudo ./ssh-trace -f logs-ssh +Jan 03 12:21:33 ubuntu ssh-trace: host=user@192.168.1.37;ppid=8516;pathname=/usr/bin/ls --color=auto -la /home/user;pid=9112 +Jan 03 12:21:35 ubuntu ssh-trace: host=user@192.168.1.37;ppid=8516;pathname=/usr/sbin/ip address show;pid=9113 +Jan 03 12:21:37 ubuntu ssh-trace: host=user@192.168.1.37;ppid=8516;pathname=/usr/bin/cat /etc/group;pid=9114 +``` + +If you want to read more about the project, you should go to my [blog](https://www.bucchino.org/projects/sshtrace/), I made an article regarding it. Enjoy the read :). + +# References +* https://developers.redhat.com/articles/2023/10/19/ebpf-application-development-beyond-basics#an_example_c_application_using_libbpf diff --git a/example.png b/example.png new file mode 100644 index 0000000..aa0e4cb Binary files /dev/null and b/example.png differ diff --git a/exec.sh b/exec.sh new file mode 100755 index 0000000..a98a31e --- /dev/null +++ b/exec.sh @@ -0,0 +1,8 @@ +#!/usr/bin/sh + + +# clang-11 -Wall -g -O2 -target bpf -c src/ssh-trace.ebpf.c -o src/ssh-trace.ebpf.o && \ +# bpftool gen skeleton src/ssh-trace.ebpf.o name sshtrace > src/skel.ebpf.h +# gcc -Wall src/load_bpf.c -o src/ssh-trace -lbpf && \ +make clean +make all && sudo ./ssh-trace diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..d3fb0c0 --- /dev/null +++ b/src/common.h @@ -0,0 +1,28 @@ +#ifndef H_COMMON +#define H_COMMON + +#define FILENAME_SIZE 128 +#define COMM_LEN 24 +#define ARGS_CNT 20 +#define ARGS_LEN 128 +#define ARGS_TLEN (ARGS_CNT * ARGS_LEN) + + +struct execve { + pid_t pid; + pid_t ppid; + char filename[FILENAME_SIZE]; +}; + +struct event{ + pid_t pid; + pid_t ppid; + uid_t uid; + char filename[FILENAME_SIZE]; + char comm[COMM_LEN]; + int argc; + int tlen; + char args[ARGS_TLEN]; +}; + +#endif diff --git a/src/load_bpf.c b/src/load_bpf.c new file mode 100644 index 0000000..a29da09 --- /dev/null +++ b/src/load_bpf.c @@ -0,0 +1,387 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "skel.ebpf.h" +#include "common.h" + +static int fd_map_execve; +static int fd_map_args; +static struct arguments arguments; +static FILE *f; +static int running = 1; +static char hostname[127]; + +struct arguments { + char *filename; + int to_output; +}; + + +/* + * Functions for arguments + * https://www.gnu.org/software/libc/manual/html_node/Argp-Example-3.html + */ + +static char doc[] = "SSH Trace usage:"; +static char args_doc[] = "ARG1 ARG2"; + +static error_t parse_opts(int key, char *arg, struct argp_state *state){ + struct arguments *arguments = state->input; + switch(key){ + case 'f': + arguments->filename = arg; + arguments->to_output = 0; + break; + case 'o': + arguments->to_output = 1; + break; + case ARGP_KEY_ARG: + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + + } + return 0; +} + +struct arguments parse_args(int argc, char *argv[]){ + static const struct argp_option opts[] = { + {"filename", 'f', "FILENAME", 0, "Save result to logs"}, + {"to-output", 'o', NULL, 0, "Print to output"}, + {NULL, 'h', NULL, OPTION_HIDDEN, "help"}, + {}, + }; + struct arguments arguments; + arguments.filename = NULL; + arguments.to_output = 1; + static struct argp argp = {opts, parse_opts, args_doc, doc}; + + argp_parse(&argp, argc, argv, 0, 0, &arguments); + return arguments; +} + +/* End functions arguments */ + +static void signalHandler(int signum){ + running = 0; +} + +/* + * This function get the username from the uid + */ +static void get_username(uid_t uid, char *username){ + struct passwd *p; + p = getpwuid(uid); + if (p == NULL){ + printf("Failed to get the username from the UID\n"); + strncpy(username, "Unknown", 8); + return; + } + strncpy(username, p->pw_name, 64); +} + +/* + * With the pid, walk through the process tree and to find the sshd proc + */ +static pid_t walk_process_tree(const pid_t pid){ + char filename[64]; + FILE *fd; + int ppid; + char proc_name[128]; + + snprintf(filename, 64, "/proc/%d/stat", pid); + + if ((fd = fopen(filename, "r")) == NULL){ + printf("Failed to open the file\n"); + return -1; + } + + // 1866044 (sshd) S 1 1866044 + fscanf(fd, "%*d %s %*c %d", proc_name, &ppid); + + if (strncmp(proc_name, "(sshd)", 128) == 0){ + fclose(fd); + return ppid; + } + + // If parent processes + if (ppid == 0 || ppid == 1){ + fclose(fd); + return 0; + } + + // printf("PID: %d; PPID: %d; Proc name: %s\n", pid, ppid, proc_name); + + fclose(fd); + return walk_process_tree(ppid); +} + +/* + * We can find the user attached to the pid + */ +static struct utmp find_user_from_pid(pid_t pid){ + int fd; + size_t len; + struct utmp u = {0}; + + if ((fd = open("/var/run/utmp", O_RDONLY)) < 0){ + printf("Failed to open the file\n"); + return u; + } + + lseek(fd, 0, SEEK_SET); + while ((len = read(fd, &u, sizeof(struct utmp))) > 0){ + if (u.ut_type == USER_PROCESS && u.ut_pid == pid) + break; + memset(&u, 0, sizeof(struct utmp)); + } + close(fd); + return u; +} +/* + * Try to find the sshd forked in the map + */ +static struct execve find_sshd_daemon(pid_t pid){ + struct execve s_execve = {0}; + // Get the execve struct + int err = bpf_map_lookup_elem(fd_map_execve, &pid, &s_execve); + if (err != 0) + return s_execve; + + if(strncmp(s_execve.filename, "sshd", 4) == 0) + return s_execve; + else + return find_sshd_daemon(s_execve.ppid); +} +/* + * This function get the localtime into the rsyslog format + */ +static int syslog_time(time_t ts, char t[32], size_t l){ + const char format[] = "%b %d %T"; + struct tm *lt = localtime(&ts); + if(strftime(t, l, format, lt) == 0) + return -1; + return 0; +} +int handle_event(void *ctx, void *data, size_t data_sz){ + struct event *s_event = (struct event *)data; + struct execve s_execveParent = {0}; + struct utmp u = {0}; + time_t ts = time(NULL); + char username[64]; + + // Fid the sshd process parent + s_execveParent = find_sshd_daemon(s_event->ppid); + if (s_execveParent.pid == 0){ + //printf("SSH daemon not exist\t%s\t%d\n", s_execve.filename, s_execve.pid); + s_execveParent.ppid = walk_process_tree(s_event->pid); /* Walk through process tree to find the sshd daemon */ + if (s_execveParent.ppid <= 0) // We didn't find it + return 0; + } + + // Find the user related + u = find_user_from_pid(s_execveParent.ppid); + if (u.ut_type == 0) + return 0; + + // Get the username + get_username(s_event->uid, username); + + if (arguments.to_output == 1){ + char t[32]; + syslog_time(ts, t, sizeof(t)); + printf("%-20s%-10d%s@%-25s", t, s_execveParent.ppid, u.ut_user, u.ut_host); + printf("%-10s%-10d", username, s_event->pid); + printf("%-20s", s_event->filename); + printf("%s ", s_event->comm); + // Arguments + if (s_event->argc > 0){ + for(int i = 0; i < s_event->tlen; i++){ + char c = s_event->args[i]; + if (c == '\0') + printf(" "); + else + printf("%c", c); + } + } + printf("\n"); + } + + if (arguments.filename != NULL && f != NULL){ + /* + * Rsyslog format + *