Day 4
Challenge
#define _GNU_SOURCE
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <stdbool.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
static volatile sig_atomic_t stop;
static void handle_sigint(int sig)
{
(void)sig;
stop = 1;
}
static int libbpf_print_fn(enum libbpf_print_level level,
const char *fmt, va_list args)
{
return vfprintf(stderr, fmt, args);
}
static void broadcast_cheer(void)
{
libbpf_set_print(libbpf_print_fn);
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
DIR *d = opendir("/dev/pts");
struct dirent *de;
char path[64];
char flag[256];
char banner[512];
ssize_t n;
if (!d)
return;
int ffd = open("/flag", O_RDONLY | O_CLOEXEC);
if (ffd >= 0) {
n = read(ffd, flag, sizeof(flag) - 1);
if (n >= 0)
flag[n] = '\0';
close(ffd);
} else {
strcpy(flag, "no-flag\n");
}
snprintf(
banner,
sizeof(banner),
"🎅 🎄 🎁 \x1b[1;31mHo Ho Ho\x1b[0m, \x1b[1;32mMerry Christmas!\x1b[0m\n"
"%s",
flag);
while ((de = readdir(d)) != NULL) {
const char *name = de->d_name;
size_t len = strlen(name);
bool all_digits = true;
if (len == 0 || name[0] == '.')
continue;
if (strcmp(name, "ptmx") == 0)
continue;
for (size_t i = 0; i < len; i++) {
if (!isdigit((unsigned char)name[i])) {
all_digits = false;
break;
}
}
if (!all_digits)
continue;
snprintf(path, sizeof(path), "/dev/pts/%s", name);
int fd = open(path, O_WRONLY | O_NOCTTY | O_CLOEXEC);
if (fd < 0)
continue;
write(fd, "\x1b[2J\x1b[H", 7);
write(fd, banner, strlen(banner));
close(fd);
}
closedir(d);
}
int main(void)
{
struct bpf_object *obj = NULL;
struct bpf_program *prog = NULL;
struct bpf_link *link = NULL;
struct bpf_map *success = NULL;
int map_fd;
__u32 key0 = 0;
int err;
int should_broadcast = 0;
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
setvbuf(stdout, NULL, _IONBF, 0);
obj = bpf_object__open_file("/challenge/tracker.bpf.o", NULL);
if (!obj) {
fprintf(stderr, "Failed to open BPF object: %s\n", strerror(errno));
return 1;
}
err = bpf_object__load(obj);
if (err) {
fprintf(stderr, "Failed to load BPF object: %s\n", strerror(-err));
goto cleanup;
}
prog = bpf_object__find_program_by_name(obj, "handle_do_linkat");
if (!prog) {
fprintf(stderr, "Could not find BPF program handle_do_linkat\n");
goto cleanup;
}
link = bpf_program__attach_kprobe(prog, false, "__x64_sys_linkat");
if (!link) {
fprintf(stderr, "Failed to attach kprobe __x64_sys_linkat: %s\n", strerror(errno));
goto cleanup;
}
signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
success = bpf_object__find_map_by_name(obj, "success");
if (!success) {
fprintf(stderr, "Failed to find success map\n");
goto cleanup;
}
map_fd = bpf_map__fd(success);
printf("Attached. Press Ctrl-C to quit.\n");
fflush(stdout);
while (!stop) {
__u32 v = 0;
if (bpf_map_lookup_elem(map_fd, &key0, &v) == 0 && v != 0) {
should_broadcast = 1;
stop = 1;
break;
}
usleep(100000);
}
if (should_broadcast)
broadcast_cheer();
cleanup:
if (link)
bpf_link__destroy(link);
if (obj)
bpf_object__close(obj);
return err ? 1 : 0;
}
My Solve
Flag is broadcasted to all opened tty after a should_broadcast is set to > 0; and should_broadcast is set to 1 only if a BPF map named success has a non zero value
After Decompiling the BPF object binary in ghidra, I found it to be checking values at linkat syscall, it has a global variable progress which it updates based on the arguments of the linkat syscall, only when progress is at value 8 and the second argument is blitzen will it update sucess to a non-zero value which will trigger the above and broadcast the flag. However progress value is updated based on the second argument and previous value of progress while first argument is sleigh. This means we must call linkat with these 2nd argument sequencially to reach our decided goal
solution
#include <fcntl.h> // For AT_FDCWD
#include <unistd.h> // For linkat()
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
const char *oldpath = "sleigh";
char *folders[] = {"dasher", "dancer", "prancer", "vixen", "comet","cupid","donner", "blitzen"};
for (int i = 0; i < 8; i++ ) {
// Remove newfile if it exists
unlink(folders[i]);
// Create the link
int ret = linkat(AT_FDCWD, oldpath, AT_FDCWD, folders[i], 0);
if (ret == 0) {
printf("Link created: %s -> %s\n", folders[i], oldpath);
} else {
printf("linkat failed: %s\n", strerror(errno));
return 1;
}
}
return 0;
}