Skip to content

Commit

Permalink
Added option to specify search path for kflowd plugin modules.
Browse files Browse the repository at this point in the history
  • Loading branch information
dirk29 committed Jun 5, 2024
1 parent d5fb50d commit cfe7e40
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 74 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
## Kernel-based Process Monitoring on Linux Endpoints via eBPF

### kflowd runs as agent on Linux endpoints to monitor processes via eBPF kernel subsystem for filesystem and TCP and UDP networking events, enabling immediate threat and anomaly detection on suspicious activities.
#### Advanced non-ebpf related features such as DNS and HTTP application message decoding, checksum calculation for virus detection, process and file versioning for vulnerability detection and file device, network interface and user-group identification for files and processes can be enabled via open-binary plugin modules. The modules can be downloaded [here](https://tarsal.co/kflowd-download/) or please contact us at [[email protected]](mailto:[email protected]) for more details.
#### Advanced non-ebpf related features such as DNS and HTTP application message decoding, checksum calculation for virus detection, process and file versioning for vulnerability detection and file device, network interface and user-group identification for files and processes can be enabled via open-binary plugin modules. These modules can be downloaded [here](https://tarsal.co/kflowd-download/) or please contact us at [[email protected]](mailto:[email protected]) for more details.
kflowd contains an eBPF program running in kernel context and its control application running in userspace.<br>
The eBPF program traces kernel functions to monitor processes based on file system and networking events. Events are aggregated into records and submitted into a ringbuffer where they are polled by the userspace control application. All Records are enriched with process information and then converted into a message in JSON output format.<br>
Final messages are printed to stdout console and can be sent via UDP protocol to specified hosts for ingestion in a security data pipeline.
Expand Down Expand Up @@ -298,7 +298,7 @@ For high performance UDP output the following kernel network settings are recomm
Usage:
kflowd [-m file,socket] [-t IDLE,ACTIVE] [-e EVENTS] [-o json|json-min|table] [-v] [-c]
[-p dns=PROTO/PORT,...] [-p http=PROTO/PORT,...] [-u IP:PORT] [-q] [-d] [-V]
[-T TOKEN] [-D PROCESS], [-l] [--legend], [-h] [--help], [--version]
[-T TOKEN] [-P PATH] [-D PROCESS], [-l] [--legend], [-h] [--help], [--version]
-m file,socket Monitor only specified kernel subsystem (filesystem or sockets)
(default: all, option omitted!)
-t IDLE,ACTIVE Timeout in seconds for idle or active network sockets until export
Expand All @@ -324,6 +324,7 @@ Usage:
Print eBPF load and co-re messages on start of eBPF program
to stderr console
-T TOKEN Token specified on host to be included in json output
-P PATH Path to search for kflowd plugin modules (default: '../lib/')
-l, --legend Show legend
-h, --help Show help
--version Show version
Expand Down Expand Up @@ -431,8 +432,8 @@ sudo apt install ./kflowd_x.x.x_arm64.deb
sudo yum install ./kflowd-x.x.x.x86_64.rpm
sudo yum install ./kflowd-x.x.x.aarch64.rpm
```
Note that build artifacts with binaries and packages (glibc 2.31+) of all commits can be downloaded under GitHub Actions in the Artifacts section of the kflowd-ci workflow run:\
[Pre-built x86_64 binaries, RPM and DEB packages (zipped)](https://github.com/tarsal-oss/kflowd/actions/workflows/kflowd-ci.yml)
Note that build artifacts can be downloaded under GitHub Actions in the Artifacts section of the kflowd-ci workflow run with binaries and packages compatible for both x86_64 and arm64 platforms (glibc 2.31+):\
[Pre-built binaries, RPM and DEB packages (zipped)](https://github.com/tarsal-oss/kflowd/actions/workflows/kflowd-ci.yml)
<br>
Expand Down
2 changes: 1 addition & 1 deletion plugins
121 changes: 62 additions & 59 deletions src/kflowd.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static char usage_str[] =
"Usage:\n"
" kflowd [-m file,socket] [-t IDLE,ACTIVE] [-e EVENTS] [-o json|json-min|table] [-v] [-c]\n"
" [-p dns=PROTO/PORT,...] [-p http=PROTO/PORT,...] [-u IP:PORT] [-q] [-d] [-V]\n"
" [-T TOKEN] [-D PROCESS], [-l] [--legend], [-h] [--help], [--version]\n"
" [-T TOKEN] [-P PATH] [-D PROCESS], [-l] [--legend], [-h] [--help], [--version]\n"
" -m file,socket Monitor only specified kernel subsystem (filesystem or sockets)\n"
" (default: all, option omitted!)\n"
" -t IDLE,ACTIVE Timeout in seconds for idle or active network sockets until export\n"
Expand All @@ -73,6 +73,7 @@ static char usage_str[] =
" Print eBPF load and co-re messages on start of eBPF program\n"
" to stderr console\n"
" -T TOKEN Token specified on host to be included in json output\n"
" -P PATH Path to search for kflowd plugin modules (default: '../lib/')\n"
" -l, --legend Show legend\n"
" -h, --help Show help\n"
" --version Show version\n"
Expand Down Expand Up @@ -156,6 +157,7 @@ static struct CONFIG {
int app_port_num[APP_MAX];
bool verbose;
char token[TOKEN_LEN_MAX];
char plugin_path[PATH_MAX];
char debug[DBG_LEN_MAX];
} config = {0};

Expand Down Expand Up @@ -308,22 +310,30 @@ static int udp_send_msg(char *, struct CONFIG *);
static char *mkjson(enum MKJSON_CONTAINER_TYPE, int, ...);
static char *mkjson_prettify(const char *, char *);

/* plugin function definitions */
typedef int plugin_dns_func(char *, int, struct APP_MSG_DNS *);
typedef int plugin_http_func(char *, int, struct APP_MSG_HTTP *);
typedef int plugin_virus_func(int, const char *, const char *, char *);
typedef int plugin_vuln_func(struct bpf_map *, int *, char *, bool, char *);
typedef int plugin_device_func(char **, char **);
typedef int plugin_interface_func(char **);
typedef int plugin_user_group_func(int, char **);
static plugin_dns_func *plugin_dns_decode;
static plugin_http_func *plugin_http_decode;
static plugin_virus_func *plugin_virus_get_checksum;
static plugin_vuln_func *plugin_vuln_version_cache;
static plugin_device_func *plugin_device_cache;
static plugin_interface_func *plugin_interface_cache;
static plugin_user_group_func *plugin_user_group_cache;
static void *plugin_handle;
/* plugin function pointer definitions */
static plugin_dns_func *plugin_dns_decode = NULL;
static plugin_http_func *plugin_http_decode = NULL;
static plugin_virus_func *plugin_virus_get_checksum = NULL;
static plugin_vuln_func *plugin_vuln_version_cache = NULL;
static plugin_device_func *plugin_device_cache = NULL;
static plugin_interface_func *plugin_interface_cache = NULL;
static plugin_user_group_func *plugin_user_group_cache = NULL;
struct PLUGIN_INFO {
plugin_func *func;
char *func_name;
char *module;
char *name;
char *desc;
} plugin[] = {
{&plugin_dns_decode, "plugin_dns_decode", "kflowd_mod_dns.so", "DNS", "DNS Decoder"},
{&plugin_http_decode, "plugin_http_decode", "kflowd_mod_http.so", "HTTP", "HTTP Decoder"},
{&plugin_virus_get_checksum, "plugin_virus_get_checksum", "kflowd_mod_virus.so", "Virus", "Virus Checksum"},
{&plugin_vuln_version_cache, "plugin_vuln_version_cache", "kflowd_mod_vuln.so", "Vuln",
"File/Process Version Vulnerability"},
{&plugin_device_cache, "plugin_device_cache", "kflowd_mod_device.so", "Device", "File Device Id"},
{&plugin_interface_cache, "plugin_interface_cache", "kflowd_mod_interface.so", "Interface", "Network Interface Id"},
{&plugin_user_group_cache, "plugin_user_group_cache", "kflowd_mod_user_group.so", "User-Group",
"File/Process User-Group Id"}};

/* handle signal */
static void sig_handler() {
Expand Down Expand Up @@ -1030,6 +1040,9 @@ int main(int argc, char **argv) {
char next_key_xfile[TASK_COMM_LEN] = {0};
char cmd_output[CMD_OUTPUT_LEN_MAX] = {0};
char cmd[CMD_LEN_MAX] = {0};
char plugin_link[PATH_MAX + FILENAME_LEN_MAX];
char plugin_file[PATH_MAX + FILENAME_LEN_MAX];
void *plugin_handle;
int kversion = 0;
int kmajor = 0;
int kminor = 0;
Expand Down Expand Up @@ -1062,10 +1075,11 @@ int main(int argc, char **argv) {
config.app_proto[APP_HTTP][0] = IPPROTO_TCP;
config.app_port[APP_HTTP][0] = HTTP_PORT;
config.app_port_num[APP_HTTP] = 1;
snprintf(config.plugin_path, sizeof(config.plugin_path), "%s", PLUGIN_PATH);

/* get system info and parse command line options */
uname(&utsn);
while ((opt = getopt_long(argc, argv, ":m:e:t:o:vcp:u:qdT:lhVD:", longopts, NULL)) != -1) {
while ((opt = getopt_long(argc, argv, ":m:e:t:o:vcp:u:qdT:P:lhVD:", longopts, NULL)) != -1) {
switch (opt) {
case 'm':
token = strtok(optarg, ",");
Expand Down Expand Up @@ -1242,6 +1256,13 @@ int main(int argc, char **argv) {
strncpy(config.token, optarg, sizeof(config.token) - 1);
argn += 2;
break;
case 'P':
if (strlen(optarg) > sizeof(config.plugin_path) - 1)
usage("Invalid plugin path with too many characters specified");
snprintf(config.plugin_path, sizeof(config.plugin_path), "%s%s", optarg,
optarg[strlen(optarg) - 1] != '/' ? "/" : "");
argn += 2;
break;
case 'l':
legend();
break;
Expand Down Expand Up @@ -1314,27 +1335,12 @@ int main(int argc, char **argv) {

/* try to load plugins */
chdir(dirname(argv[0]));
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_DNS, RTLD_LAZY);
if (!plugin_handle || !(plugin_dns_decode = dlsym(plugin_handle, "plugin_dns_decode")))
plugin_dns_decode = NULL;
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_HTTP, RTLD_LAZY);
if (!plugin_handle || !(plugin_http_decode = dlsym(plugin_handle, "plugin_http_decode")))
plugin_http_decode = NULL;
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_VIRUS, RTLD_LAZY);
if (!plugin_handle || !(plugin_virus_get_checksum = dlsym(plugin_handle, "plugin_virus_get_checksum")))
plugin_virus_get_checksum = NULL;
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_VULN, RTLD_LAZY);
if (!plugin_handle || !(plugin_vuln_version_cache = dlsym(plugin_handle, "plugin_vuln_version_cache")))
plugin_vuln_version_cache = NULL;
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_DEVICE, RTLD_LAZY);
if (!plugin_handle || !(plugin_device_cache = dlsym(plugin_handle, "plugin_device_cache")))
plugin_device_cache = NULL;
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_INTERFACE, RTLD_LAZY);
if (!plugin_handle || !(plugin_interface_cache = dlsym(plugin_handle, "plugin_interface_cache")))
plugin_interface_cache = NULL;
plugin_handle = dlopen(PLUGIN_PATH PLUGIN_MOD_USER_GROUP, RTLD_LAZY);
if (!plugin_handle || !(plugin_user_group_cache = dlsym(plugin_handle, "plugin_user_group_cache")))
plugin_user_group_cache = NULL;
for(cnt=0; cnt<P_MAX; cnt++) {
snprintf(plugin_link, sizeof(plugin_link), "%s%s", config.plugin_path, plugin[cnt].module);
plugin_handle = dlopen(plugin_link, RTLD_LAZY);
if (!plugin_handle || !(*(plugin[cnt].func) = dlsym(plugin_handle, plugin[cnt].func_name)))
*(plugin[cnt].func) = NULL;
}

/* set globals shared between user and kernel */
clock_gettime(CLOCK_MONOTONIC, &spec);
Expand Down Expand Up @@ -1466,31 +1472,28 @@ int main(int argc, char **argv) {
}

/* print plugin status */
fprintf(stderr, "Plugin Modules:\n");
fprintf(stderr, "\e[0;%s\e[0m DNS: %s DNS Decoder Module (kflowd_mod_dns.so)\n", plugin_dns_decode ? "32m[+]" : "33m[-]",
plugin_dns_decode ? "Loaded" : "NOT loaded");
fprintf(stderr, "\e[0;%s\e[0m HTTP: %s HTTP Decoder Module (kflowd_mod_http.so)\n", plugin_http_decode ? "32m[+]" : "33m[-]",
plugin_http_decode ? "Loaded" : "NOT loaded");
fprintf(stderr, "\e[0;%s\e[0m Virus: %s Virus Checksum Module (kflowd_mod_virus.so)\n", plugin_virus_get_checksum ? "32m[+]" : "33m[-]",
plugin_virus_get_checksum ? "Loaded" : "NOT loaded");
fprintf(stderr, "\e[0;%s\e[0m Vuln: %s File/Process Version Vulnerability Module (kflowd_mod_vuln.so)\n", plugin_vuln_version_cache ? "32m[+]" : "33m[-]",
plugin_vuln_version_cache ? "Loaded" : "NOT loaded");
fprintf(stderr, "\e[0;%s\e[0m Device: %s File Device Id Module (kflowd_mod_device.so)\n", plugin_device_cache ? "32m[+]" : "33m[-]",
plugin_device_cache ? "Loaded" : "NOT loaded");
fprintf(stderr, "\e[0;%s\e[0m Interface: %s Network Interface Id Module (kflowd_mod_interface.so)\n", plugin_interface_cache ? "32m[+]" : "33m[-]",
plugin_interface_cache ? "Loaded" : "NOT loaded");
fprintf(stderr, "\e[0;%s\e[0m User-Group: %s File/Process User-Group Id Module (kflowd_mod_user_group.so)\n", plugin_user_group_cache ? "32m[+]" : "33m[-]",
plugin_user_group_cache ? "Loaded" : "NOT loaded");

fprintf(stderr, "Plugin Modules (%s):\n", config.plugin_path);
for(cnt=0; cnt<P_MAX; cnt++) {
memset(plugin_file, 0, FILEPATH_LEN_MAX);
snprintf(plugin_link, sizeof(plugin_link), "%s%s", config.plugin_path, plugin[cnt].module);
fprintf(stderr, "\e[0;%s\e[0m \e[%s%s: %*s %s %s Module -> %s\e[0m\n",
*(plugin[cnt].func) ? "32m[+]" : "33m[-]", *(plugin[cnt].func) ? "0m" : "37m", plugin[cnt].name,
10 - (int)strlen(plugin[cnt].name), "", *(plugin[cnt].func) ? "Loaded" : "NOT loaded", plugin[cnt].desc,
readlink(plugin_link, plugin_file, FILEPATH_LEN_MAX) > 0 ? plugin_file : plugin[cnt].module);
}
fprintf(stderr, "\n");

/* print config options and success */
fprintf(stderr, "Configuration:\n");
fprintf(stderr, "\e[0;32m[+]\e[0m Monitored kernel subsystem(s)\n");
fprintf(stderr, "\e[0;%s\e[0m \e[%sFile System: %7u max records at %lu bytes \e[0m\n", config.monitor & MONITOR_FILE ? "32m[+]" : "33m[-]",
((config.monitor & MONITOR_FILE) || config.mode_daemon) ? "0m" : "0:37m", MAP_RECORDS_MAX, sizeof(struct RECORD_FS));
fprintf(stderr, "\e[0;%s\e[0m \e[%sNetwork sockets: %7u max records at %lu bytes\e[0m\n", config.monitor & MONITOR_SOCK ? "32m[+]" : "33m[-]",
((config.monitor & MONITOR_SOCK) || config.mode_daemon) ? "0m" : "0:37m", MAP_SOCKS_MAX, sizeof(struct RECORD_SOCK));
fprintf(stderr, "\e[0;%s\e[0m \e[%sFile System: %7u max records at %lu bytes \e[0m\n",
config.monitor & MONITOR_FILE ? "32m[+]" : "33m[-]",
((config.monitor & MONITOR_FILE) || config.mode_daemon) ? "0m" : "0:37m",
MAP_RECORDS_MAX, sizeof(struct RECORD_FS));
fprintf(stderr, "\e[0;%s\e[0m \e[%sNetwork sockets: %7u max records at %lu bytes\e[0m\n",
config.monitor & MONITOR_SOCK ? "32m[+]" : "33m[-]",
((config.monitor & MONITOR_SOCK) || config.mode_daemon) ? "0m" : "0:37m",
MAP_SOCKS_MAX, sizeof(struct RECORD_SOCK));
if (config.monitor & MONITOR_FILE) {
fprintf(stderr, "\e[0;%s\e[0m Filesystem aggregation by PID+Inode until\n",
config.agg_events_max == 1 ? "33m[-]" : "32m[+]");
Expand Down
23 changes: 13 additions & 10 deletions src/kflowd.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,6 @@ enum APP_TYPE { APP_DNS, APP_HTTP, APP_MAX };
#define APP_MSG_LEN_MAX 1400
#define APP_PORT_MAX 8

/* define plugin default search path and module names */
#define PLUGIN_PATH "../lib/"
#define PLUGIN_MOD_DNS "kflowd_mod_dns.so"
#define PLUGIN_MOD_HTTP "kflowd_mod_http.so"
#define PLUGIN_MOD_VIRUS "kflowd_mod_virus.so"
#define PLUGIN_MOD_VULN "kflowd_mod_vuln.so"
#define PLUGIN_MOD_DEVICE "kflowd_mod_device.so"
#define PLUGIN_MOD_INTERFACE "kflowd_mod_interface.so"
#define PLUGIN_MOD_USER_GROUP "kflowd_mod_user_group.so"

/* define macros for startup requirement checks */
#define CHECK_MAX 3
#define CHECK_MSG_LEN_MAX 64
Expand Down Expand Up @@ -659,6 +649,19 @@ struct XFILES {
int truncated;
};

/* define plugins types, functions and search path */
enum PLUGIN_TYPE { P_DNS, P_HTTP, P_VIRUS, P_VULN, P_DEVICE, P_INTERFACE, P_USER_GROUP, P_MAX };
typedef int (*plugin_func)();
typedef int plugin_dns_func(char *, int, struct APP_MSG_DNS *);
typedef int plugin_http_func(char *, int, struct APP_MSG_HTTP *);
typedef int plugin_virus_func(int, const char *, const char *, char *);
struct bpf_map; /* eliminate compiler warning */
typedef int plugin_vuln_func(struct bpf_map *, int *, char *, int, char *);
typedef int plugin_device_func(char **, char **);
typedef int plugin_interface_func(char **);
typedef int plugin_user_group_func(int, char **);
#define PLUGIN_PATH "../lib/"

/* define output types */
#define JSON_SUB_KEY_MAX 16
#define JSON_KEY_LEN_MAX 32
Expand Down

0 comments on commit cfe7e40

Please sign in to comment.